Skip to content

Commit 39e79a2

Browse files
committed
feat: generate rsa key pair automatically per instance
1 parent 39bd58b commit 39e79a2

8 files changed

Lines changed: 85 additions & 6 deletions

File tree

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ The following driver parameters are common to both instance types, but are not r
5050
- `oci_profile_name`, OCI profile to use (default: `DEFAULT`)
5151
- `oci_config`, Hash of additional `OCI::Config` settings. Allows you to test without an oci config file [[more](#use-without-oci-config-file)]
5252
- `ssh_keypath`, SSH public key (default: `~/.ssh/id_rsa.pub`)
53+
- `ssh_keygen`, Automatically generate the rsa key pair for an instance (default: `false`) [[more](#ssh-keygen)]
5354
- `post_create_script`, run a script on an instance after deployment
5455
- `post_create_reboot`, reboot the instance after instance creation (default: `false`)
5556
- `proxy_url`, Connect via the specified proxy URL [[more](#proxy-support)]
@@ -274,6 +275,26 @@ Alternately, if you simply pass a string to the user_data, it will be base64 enc
274275
gid: 1000
275276
```
276277

278+
## SSH Keygen
279+
280+
The driver can generate an ssh key pair for an instance during creation. In order to turn this feature on, add the `ssh_keygen` property to the `driver` and set the value to `true`. This can be set in the `driver` section on a
281+
per-platform or per-suite basis, but can also be enabled globally for the entire kitchen.yml in the top-level `driver` section.
282+
283+
Ensure that the `transport` section does not contain a path to a private key (the `ssh_key` property). If the `transport` has a value in `ssh_key` property, this will mismatch with the key pair that the driver will create causing your
284+
instance creation to be stuck in an endless loop waiting for `transport` to receive a confirmed ssh connection.
285+
286+
The generated key pair is stored in the `.kitchen/.ssh` directory and is named for the instance that generated it so each instance in your `kitchen.yml` can have its own key pair.
287+
288+
Upon instance termination (`kitchen destroy`), the generated key pair will be removed from the `.kitchen/.ssh` directory along with the state file as should be expected.
289+
290+
```yml
291+
driver:
292+
ssh_keygen: true
293+
294+
transport:
295+
username: opc
296+
```
297+
277298
## Proxy support
278299

279300
If running Kitchen on a private subnet with no public IPs permitted, it may be necessary to connect to the OCI API via a web proxy. The proxy URL can either be specified on the command line:

lib/kitchen/driver/oci.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class Oci < Kitchen::Driver::Base # rubocop:disable Metrics/ClassLength
6464
default_config :display_name, nil
6565
default_keypath = File.expand_path(File.join(%w{~ .ssh id_rsa.pub}))
6666
default_config :ssh_keypath, default_keypath
67+
default_config :ssh_keygen, false
6768
default_config :post_create_script, nil
6869
default_config :proxy_url, nil
6970
default_config :user_data, nil
@@ -180,6 +181,10 @@ def detatch_and_delete_volumes(state, oci, api)
180181
def terminate(state, inst)
181182
instance.transport.connection(state).close
182183
inst.terminate
184+
if state[:ssh_key]
185+
FileUtils.rm_f(state[:ssh_key])
186+
FileUtils.rm_f("#{state[:ssh_key]}.pub")
187+
end
183188
end
184189
end
185190
end

lib/kitchen/driver/oci/instance.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,43 @@ def public_ip_allowed?
9696
!subnet.prohibit_public_ip_on_vnic
9797
end
9898

99+
def public_key_file
100+
if config[:ssh_keygen]
101+
"#{config[:kitchen_root]}/.kitchen/.ssh/#{config[:instance_name]}_rsa.pub"
102+
else
103+
config[:ssh_keypath]
104+
end
105+
end
106+
107+
def private_key_file
108+
public_key_file.gsub(".pub", "")
109+
end
110+
111+
def gen_key_pair
112+
FileUtils.mkdir_p("#{config[:kitchen_root]}/.kitchen/.ssh")
113+
rsa_key = OpenSSL::PKey::RSA.new(4096)
114+
write_private_key(rsa_key)
115+
write_public_key(rsa_key)
116+
state.store(:ssh_key, private_key_file)
117+
end
118+
119+
def write_private_key(rsa_key)
120+
File.open(private_key_file, "wb") { |k| k.write(rsa_key.to_pem) }
121+
File.chmod(0600, private_key_file)
122+
end
123+
124+
def write_public_key(rsa_key)
125+
File.open(public_key_file, "wb") { |k| k.write("ssh-rsa #{encode_private_key(rsa_key)} #{config[:instance_name]}") }
126+
File.chmod(0600, public_key_file)
127+
end
128+
129+
def encode_private_key(rsa_key)
130+
prefix = "#{[7].pack("N")}ssh-rsa"
131+
exponent = rsa_key.e.to_s(0)
132+
modulus = rsa_key.n.to_s(0)
133+
["#{prefix}#{exponent}#{modulus}"].pack("m0")
134+
end
135+
99136
def random_password(special_chars)
100137
(Array.new(5) { special_chars.sample } +
101138
Array.new(5) { ("a".."z").to_a.sample } +

lib/kitchen/driver/oci/instance/dbaas.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def node_count
6161

6262
def pubkey
6363
result = []
64-
result << File.readlines(config[:ssh_keypath]).first.chomp
64+
result << read_public_key
6565
launch_details.ssh_public_keys = result
6666
end
6767

lib/kitchen/driver/oci/models/compute.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,19 @@ def create_vnic_details(name)
159159
end
160160

161161
def pubkey
162-
File.readlines(config[:ssh_keypath]).first.chomp
162+
if config[:ssh_keygen]
163+
logger.info("Generating public/private rsa key pair")
164+
gen_key_pair
165+
end
166+
File.readlines(public_key_file).first.chomp
163167
end
164168

165169
def metadata
166170
md = {}
167171
inject_powershell
168172
config[:custom_metadata]&.each { |k, v| md.store(k, v) }
169-
md.store("ssh_authorized_keys", pubkey)
170-
md.store("user_data", user_data) if config[:user_data] && !config[:user_data].empty?
173+
md.store("ssh_authorized_keys", pubkey) unless config[:setup_winrm]
174+
md.store("user_data", user_data) if user_data?
171175
md
172176
end
173177

@@ -182,6 +186,10 @@ def windows_state?
182186
config[:setup_winrm] && config[:password].nil? && state[:password].nil?
183187
end
184188

189+
def user_data?
190+
config[:user_data] && !config[:user_data].empty?
191+
end
192+
185193
def winrm_ps1
186194
filename = File.join(__dir__, %w{.. .. .. .. .. tpl setup_winrm.ps1.erb})
187195
tpl = ERB.new(File.read(filename))

lib/kitchen/driver/oci/models/dbaas.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ def hostname_prefix
9797
def long_hostname_suffix
9898
[random_string(25 - hostname_prefix.length), random_string(3)].compact.join("-")
9999
end
100+
101+
def read_public_key
102+
if config[:ssh_keygen]
103+
logger.info("Generating public/private rsa key pair")
104+
gen_key_pair
105+
end
106+
File.readlines(public_key_file).first.chomp
107+
end
100108
end
101109
end
102110
end

lib/kitchen/driver/oci/volumes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
module Kitchen
2121
module Driver
2222
class Oci
23-
# mixin for working with volume and attachments
23+
# mixin for working with volumes and attachments
2424
module Volumes
2525
def create_and_attach_volumes(config, state, oci, api)
2626
return if config[:volumes].empty?

lib/kitchen/driver/oci_version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020
module Kitchen
2121
module Driver
2222
# Version string for Oracle OCI Kitchen driver
23-
OCI_VERSION = "1.25.0"
23+
OCI_VERSION = "1.26.0"
2424
end
2525
end

0 commit comments

Comments
 (0)