kitchen-ec2 3.19.1 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/lib/kitchen/driver/aws/client.rb +1 -1
- data/lib/kitchen/driver/aws/instance_connect.rb +44 -0
- data/lib/kitchen/driver/aws/instance_generator.rb +6 -3
- data/lib/kitchen/driver/aws/standard_platform/alma.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/amazon.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/amazon2.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/amazon2023.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/centos.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/debian.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/fedora.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/freebsd.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/macos.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/rhel.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/rocky.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/ubuntu.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform/windows.rb +1 -1
- data/lib/kitchen/driver/aws/standard_platform.rb +2 -2
- data/lib/kitchen/driver/ec2.rb +317 -3
- data/lib/kitchen/driver/ec2_version.rb +2 -2
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d6b1c659fad578e57f13b71e617d6b1f8dce59393f17287bb0b33fe4894a5f0
|
4
|
+
data.tar.gz: c99cb5f69df034d26af00ae8a78a2bcbe9999ed67065cdf76efd897c80080c69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 508886f38d0c6bba27e1724360e4a0ee4587bd92f14e21341e7c3dba1a40a951b69408cea2214b9b9249e911a31e378c12b2fe49334fe35e5df353fc9ba2f98d
|
7
|
+
data.tar.gz: d422f9a8542e95c7a3e0fcdcb882898dcbb996a6f556a5445ae96fb63fa149b81cbc39136216cb047886edb215c83f8ed5d090d4811a6aec6758e5fd5f9db7fb
|
data/LICENSE
CHANGED
@@ -6,7 +6,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
you may not use this file except in compliance with the License.
|
7
7
|
You may obtain a copy of the License at
|
8
8
|
|
9
|
-
|
9
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
10
10
|
|
11
11
|
Unless required by applicable law or agreed to in writing, software
|
12
12
|
distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -8,7 +8,7 @@
|
|
8
8
|
# you may not use this file except in compliance with the License.
|
9
9
|
# You may obtain a copy of the License at
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
12
12
|
#
|
13
13
|
# Unless required by applicable law or agreed to in writing, software
|
14
14
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Alex Kokkinos
|
3
|
+
#
|
4
|
+
# Copyright:: 2025, Alex Kokkinos
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
require "aws-sdk-ec2instanceconnect"
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
module Driver
|
21
|
+
class Aws
|
22
|
+
class InstanceConnect
|
23
|
+
def initialize(config, logger)
|
24
|
+
@config = config
|
25
|
+
@logger = logger
|
26
|
+
@client = ::Aws::EC2InstanceConnect::Client.new(region: config[:region])
|
27
|
+
end
|
28
|
+
|
29
|
+
def send_ssh_public_key(instance_id, username, public_key)
|
30
|
+
@logger.info("Sending SSH public key to instance #{instance_id} for user #{username}")
|
31
|
+
|
32
|
+
@client.send_ssh_public_key({
|
33
|
+
instance_id: instance_id,
|
34
|
+
instance_os_user: username,
|
35
|
+
ssh_public_key: public_key,
|
36
|
+
availability_zone: @config[:availability_zone],
|
37
|
+
})
|
38
|
+
|
39
|
+
@logger.debug("SSH public key successfully sent to instance")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
# you may not use this file except in compliance with the License.
|
9
9
|
# You may obtain a copy of the License at
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
12
12
|
#
|
13
13
|
# Unless required by applicable law or agreed to in writing, software
|
14
14
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -35,8 +35,8 @@ module Kitchen
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# Transform the provided kitchen config into the hash we'll use to create the aws instance
|
38
|
-
# can be passed in null, others need to be
|
39
|
-
# Some fields can be passed in null, others need to be
|
38
|
+
# can be passed in null, others need to be omitted if they are null
|
39
|
+
# Some fields can be passed in null, others need to be omitted if they are null
|
40
40
|
# @return [Hash]
|
41
41
|
def ec2_instance_data
|
42
42
|
# Support for looking up security group id and subnet id using tags.
|
@@ -173,6 +173,9 @@ module Kitchen
|
|
173
173
|
if config[:security_group_ids]
|
174
174
|
i[:network_interfaces][0][:groups] = i.delete(:security_group_ids)
|
175
175
|
end
|
176
|
+
if config[:associate_ipv6]
|
177
|
+
i[:network_interfaces][0][:ipv_6_address_count] = 1
|
178
|
+
end
|
176
179
|
end
|
177
180
|
availability_zone = config[:availability_zone]
|
178
181
|
if availability_zone
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
9
9
|
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# you may not use this file except in compliance with the License.
|
7
7
|
# You may obtain a copy of the License at
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
10
|
#
|
11
11
|
# Unless required by applicable law or agreed to in writing, software
|
12
12
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -82,7 +82,7 @@ module Kitchen
|
|
82
82
|
#
|
83
83
|
# @return [String]
|
84
84
|
#
|
85
|
-
# @see
|
85
|
+
# @see SUPPORTED_ARCHITECTURES
|
86
86
|
#
|
87
87
|
attr_reader :architecture
|
88
88
|
|
data/lib/kitchen/driver/ec2.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# you may not use this file except in compliance with the License.
|
9
9
|
# You may obtain a copy of the License at
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
12
12
|
#
|
13
13
|
# Unless required by applicable law or agreed to in writing, software
|
14
14
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -16,6 +16,7 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
+
require "sshkey" unless defined?(SSHKey)
|
19
20
|
require "benchmark" unless defined?(Benchmark)
|
20
21
|
require "json" unless defined?(JSON)
|
21
22
|
require "kitchen"
|
@@ -37,12 +38,14 @@ require_relative "aws/standard_platform/freebsd"
|
|
37
38
|
require_relative "aws/standard_platform/macos"
|
38
39
|
require_relative "aws/standard_platform/ubuntu"
|
39
40
|
require_relative "aws/standard_platform/windows"
|
41
|
+
require_relative "aws/instance_connect"
|
40
42
|
require "aws-sdk-ec2"
|
41
43
|
require "aws-sdk-core/waiters/errors"
|
42
44
|
require "retryable" unless defined?(Retryable)
|
43
45
|
require "time" unless defined?(Time)
|
44
46
|
require "etc" unless defined?(Etc)
|
45
47
|
require "socket" unless defined?(Socket)
|
48
|
+
require "shellwords" unless defined?(Shellwords)
|
46
49
|
|
47
50
|
module Kitchen
|
48
51
|
module Driver
|
@@ -85,6 +88,7 @@ module Kitchen
|
|
85
88
|
default_config :image_search, nil
|
86
89
|
default_config :username, nil
|
87
90
|
default_config :associate_public_ip, nil
|
91
|
+
default_config :associate_ipv6, nil
|
88
92
|
default_config :interface, nil
|
89
93
|
default_config :http_proxy, ENV["HTTPS_PROXY"] || ENV.fetch("HTTP_PROXY", nil)
|
90
94
|
default_config :retry_limit, 3
|
@@ -94,6 +98,9 @@ module Kitchen
|
|
94
98
|
default_config :skip_cost_warning, false
|
95
99
|
default_config :allocate_dedicated_host, false
|
96
100
|
default_config :deallocate_dedicated_host, false
|
101
|
+
default_config :use_instance_connect, false
|
102
|
+
default_config :instance_connect_endpoint_id, nil
|
103
|
+
default_config :instance_connect_max_tunnel_duration, 3600
|
97
104
|
|
98
105
|
include Kitchen::Driver::Mixins::DedicatedHosts
|
99
106
|
|
@@ -225,7 +232,8 @@ module Kitchen
|
|
225
232
|
case config[:aws_ssh_key_id]
|
226
233
|
when nil
|
227
234
|
create_key(state)
|
228
|
-
|
235
|
+
# Don't set aws_ssh_key_id if using Instance Connect
|
236
|
+
config[:aws_ssh_key_id] = state[:auto_key_id] unless config[:use_instance_connect]
|
229
237
|
when "_disable"
|
230
238
|
info("Disabling AWS-managed SSH key pairs for this EC2 instance.")
|
231
239
|
info("The key pairs for the kitchen transport config and the AMI must match.")
|
@@ -271,6 +279,11 @@ module Kitchen
|
|
271
279
|
end
|
272
280
|
|
273
281
|
info("EC2 instance <#{state[:server_id]}> ready (hostname: #{state[:hostname]}).")
|
282
|
+
|
283
|
+
if config[:use_instance_connect]
|
284
|
+
instance_connect_setup_ready(state)
|
285
|
+
end
|
286
|
+
|
274
287
|
instance.transport.connection(state).wait_until_ready
|
275
288
|
attach_network_interface(state) unless config[:elastic_network_interface_id].nil?
|
276
289
|
create_ec2_json(state) if /chef/i.match?(instance.provisioner.name)
|
@@ -914,7 +927,7 @@ module Kitchen
|
|
914
927
|
puts "ENI #{config[:elastic_network_interface_id]} already attached."
|
915
928
|
end
|
916
929
|
rescue ::Aws::EC2::Errors::InvalidNetworkInterfaceIDNotFound => e
|
917
|
-
warn(e
|
930
|
+
warn(e)
|
918
931
|
end
|
919
932
|
end
|
920
933
|
|
@@ -944,6 +957,307 @@ module Kitchen
|
|
944
957
|
state.delete(:auto_key_id)
|
945
958
|
File.unlink("#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem")
|
946
959
|
end
|
960
|
+
|
961
|
+
def finalize_config!(instance)
|
962
|
+
super
|
963
|
+
|
964
|
+
# Set up Instance Connect transport override if configured
|
965
|
+
if config[:use_instance_connect]
|
966
|
+
debug("[AWS EC2 Instance Connect] Setting up Instance Connect overrides")
|
967
|
+
instance_connect_setup_override(instance)
|
968
|
+
instance_connect_setup_inspec_override(instance)
|
969
|
+
end
|
970
|
+
|
971
|
+
self
|
972
|
+
end
|
973
|
+
|
974
|
+
private
|
975
|
+
|
976
|
+
def instance_connect_setup_override(instance)
|
977
|
+
# Prevent double pushing of the SSH public keys
|
978
|
+
return if instance.transport.respond_to?(:instance_connect_override_applied)
|
979
|
+
|
980
|
+
# Store reference to driver for use in override
|
981
|
+
driver_instance = self
|
982
|
+
use_instance_connect = config[:use_instance_connect]
|
983
|
+
|
984
|
+
# Override the transport's connection method to inject Instance Connect setup
|
985
|
+
original_connection = instance.transport.method(:connection)
|
986
|
+
|
987
|
+
instance.transport.define_singleton_method(:connection) do |state, &block|
|
988
|
+
# Set up Instance Connect configuration before every connection
|
989
|
+
if use_instance_connect
|
990
|
+
# Refresh Instance Connect SSH key
|
991
|
+
driver_instance.send(:instance_connect_refresh_key, state)
|
992
|
+
|
993
|
+
# Configure connection mode based on endpoint availability
|
994
|
+
if driver_instance.send(:instance_connect_endpoint_available?, state)
|
995
|
+
# Proxy command mode - ensure ssh_proxy_command is set
|
996
|
+
unless state[:ssh_proxy_command]
|
997
|
+
driver_instance.send(:instance_connect_configure_ssh_proxy_command, state)
|
998
|
+
end
|
999
|
+
driver_instance.debug("[AWS EC2 Instance Connect] Transport using proxy command mode")
|
1000
|
+
else
|
1001
|
+
# Direct SSH mode - ensure hostname is set to public DNS
|
1002
|
+
driver_instance.send(:instance_connect_configure_direct_ssh, state)
|
1003
|
+
driver_instance.debug("[AWS EC2 Instance Connect] Transport using direct SSH mode")
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
# Call original connection method
|
1007
|
+
original_connection.call(state, &block)
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
# Mark as applied to prevent double pushing of the SSH public keys
|
1011
|
+
instance.transport.define_singleton_method(:instance_connect_override_applied) { true }
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
def instance_connect_setup_inspec_override(instance)
|
1015
|
+
# Only apply to InSpec verifier
|
1016
|
+
return unless instance.verifier.name.downcase == "inspec"
|
1017
|
+
return if instance.verifier.respond_to?(:instance_connect_inspec_override_applied)
|
1018
|
+
|
1019
|
+
# Store reference to driver for use in override
|
1020
|
+
driver_instance = self
|
1021
|
+
use_instance_connect = config[:use_instance_connect]
|
1022
|
+
|
1023
|
+
# Override the verifier's call method to inject proxy command setup
|
1024
|
+
original_call = instance.verifier.method(:call)
|
1025
|
+
|
1026
|
+
instance.verifier.define_singleton_method(:call) do |state|
|
1027
|
+
driver_instance.debug("[AWS EC2 Instance Connect] InSpec call method intercepted, connecting using kitchen-ec2 driver AWS EC2 Instance Connect")
|
1028
|
+
driver_instance.debug("[AWS EC2 Instance Connect] Instance ID: #{state[:server_id]}")
|
1029
|
+
|
1030
|
+
# If using Instance Connect, set up the override just before the call
|
1031
|
+
if use_instance_connect && state[:server_id]
|
1032
|
+
|
1033
|
+
# Check if we already have the override method defined
|
1034
|
+
unless respond_to?(:instance_connect_original_runner_options_for_ssh)
|
1035
|
+
# Store the original method
|
1036
|
+
define_singleton_method(:instance_connect_original_runner_options_for_ssh, method(:runner_options_for_ssh))
|
1037
|
+
|
1038
|
+
# Override runner_options_for_ssh
|
1039
|
+
define_singleton_method(:runner_options_for_ssh) do |config_data|
|
1040
|
+
|
1041
|
+
# Get the original options
|
1042
|
+
opts = instance_connect_original_runner_options_for_ssh(config_data)
|
1043
|
+
|
1044
|
+
# Inject Instance Connect configuration if enabled
|
1045
|
+
if use_instance_connect && config_data[:server_id]
|
1046
|
+
# Refresh Instance Connect SSH key
|
1047
|
+
driver_instance.send(:instance_connect_refresh_key, config_data)
|
1048
|
+
|
1049
|
+
# Check if we should use proxy command or direct SSH
|
1050
|
+
if driver_instance.send(:instance_connect_endpoint_available?, config_data)
|
1051
|
+
# Build proxy command with the instance ID from state
|
1052
|
+
proxy_command = [
|
1053
|
+
"aws", "ec2-instance-connect", "open-tunnel",
|
1054
|
+
"--instance-id", config_data[:server_id]
|
1055
|
+
]
|
1056
|
+
|
1057
|
+
# Add optional parameters
|
1058
|
+
if driver_instance.config[:instance_connect_endpoint_id]
|
1059
|
+
proxy_command += ["--instance-connect-endpoint-id", driver_instance.config[:instance_connect_endpoint_id]]
|
1060
|
+
end
|
1061
|
+
if driver_instance.config[:instance_connect_max_tunnel_duration]
|
1062
|
+
proxy_command += ["--max-tunnel-duration", driver_instance.config[:instance_connect_max_tunnel_duration].to_s]
|
1063
|
+
end
|
1064
|
+
if driver_instance.config[:shared_credentials_profile]
|
1065
|
+
proxy_command += ["--profile", driver_instance.config[:shared_credentials_profile]]
|
1066
|
+
end
|
1067
|
+
proxy_command += ["--region", driver_instance.config[:region]]
|
1068
|
+
|
1069
|
+
opts["proxy_command"] = proxy_command.join(" ")
|
1070
|
+
driver_instance.info("[AWS EC2 Instance Connect] InSpec using proxy command: #{opts["proxy_command"]}")
|
1071
|
+
else
|
1072
|
+
# Direct SSH mode - ensure we're using the public DNS and proper SSH options
|
1073
|
+
server = driver_instance.ec2.get_instance(config_data[:server_id])
|
1074
|
+
public_dns = server&.public_dns_name
|
1075
|
+
|
1076
|
+
if public_dns && !public_dns.empty?
|
1077
|
+
opts["host"] = public_dns
|
1078
|
+
opts["ssh_options"] = (opts["ssh_options"] || {}).merge({
|
1079
|
+
"IdentitiesOnly" => "yes",
|
1080
|
+
})
|
1081
|
+
driver_instance.info("[AWS EC2 Instance Connect] InSpec using direct SSH to #{public_dns} with IdentitiesOnly=yes")
|
1082
|
+
else
|
1083
|
+
driver_instance.warn("[AWS EC2 Instance Connect] No public DNS available for direct SSH mode")
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
else
|
1087
|
+
driver_instance.info("[AWS EC2 Instance Connect] Not configuring Instance Connect - use_instance_connect: #{use_instance_connect}, server_id present: #{!!config_data[:server_id]}")
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
opts
|
1091
|
+
end
|
1092
|
+
end
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
# Call the original method
|
1096
|
+
original_call.call(state)
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
# Mark as applied to prevent double setup
|
1100
|
+
instance.verifier.define_singleton_method(:instance_connect_inspec_override_applied) { true }
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
def instance_connect_setup_ready(state)
|
1104
|
+
# Determine whether to use proxy command or direct SSH based on endpoint availability
|
1105
|
+
if instance_connect_endpoint_available?(state)
|
1106
|
+
# Configure SSH proxy command if not already done
|
1107
|
+
instance_connect_configure_ssh_proxy_command(state) unless state[:ssh_proxy_command]
|
1108
|
+
info("[AWS EC2 Instance Connect] Using tunnel mode - Instance Connect endpoint available")
|
1109
|
+
else
|
1110
|
+
# Configure direct SSH with public DNS
|
1111
|
+
instance_connect_configure_direct_ssh(state)
|
1112
|
+
info("[AWS EC2 Instance Connect] Using direct SSH mode - no Instance Connect endpoint")
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
# Refresh Instance Connect SSH key before connection
|
1116
|
+
instance_connect_refresh_key(state)
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
def instance_connect_refresh_key(state)
|
1120
|
+
# Extract public key from the key that was already set up
|
1121
|
+
key_path = state[:ssh_key] || instance.transport[:ssh_key]
|
1122
|
+
return unless key_path
|
1123
|
+
|
1124
|
+
public_key = instance_connect_extract_public_key(key_path)
|
1125
|
+
return unless public_key
|
1126
|
+
|
1127
|
+
username = state[:username] || actual_platform&.username
|
1128
|
+
|
1129
|
+
# Build AWS CLI command to send public key
|
1130
|
+
cmd = [
|
1131
|
+
"aws", "ec2-instance-connect", "send-ssh-public-key",
|
1132
|
+
"--instance-id", state[:server_id],
|
1133
|
+
"--instance-os-user", username,
|
1134
|
+
"--ssh-public-key", public_key,
|
1135
|
+
"--region", config[:region]
|
1136
|
+
]
|
1137
|
+
|
1138
|
+
cmd += ["--profile", config[:shared_credentials_profile]] if config[:shared_credentials_profile]
|
1139
|
+
|
1140
|
+
# Execute the command with proper shell escaping
|
1141
|
+
debug("[AWS EC2 Instance Connect] Refreshing SSH public key for #{state[:server_id]}")
|
1142
|
+
escaped_cmd = cmd.map { |arg| Shellwords.escape(arg) }.join(" ")
|
1143
|
+
result = `#{escaped_cmd} 2>&1`
|
1144
|
+
unless $?.success?
|
1145
|
+
warn("[AWS EC2 Instance Connect] Failed to refresh SSH key: #{result}")
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def instance_connect_configure_ssh_proxy_command(state)
|
1150
|
+
info("[AWS EC2 Instance Connect] Configuring proxy command mode (tunnel)")
|
1151
|
+
|
1152
|
+
# Build the AWS CLI command for the tunnel
|
1153
|
+
proxy_command = [
|
1154
|
+
"aws", "ec2-instance-connect", "open-tunnel",
|
1155
|
+
"--instance-id", state[:server_id]
|
1156
|
+
]
|
1157
|
+
|
1158
|
+
# Add optional parameters
|
1159
|
+
if config[:instance_connect_endpoint_id]
|
1160
|
+
proxy_command += ["--instance-connect-endpoint-id", config[:instance_connect_endpoint_id]]
|
1161
|
+
end
|
1162
|
+
if config[:instance_connect_max_tunnel_duration]
|
1163
|
+
proxy_command += ["--max-tunnel-duration", config[:instance_connect_max_tunnel_duration].to_s]
|
1164
|
+
end
|
1165
|
+
if config[:shared_credentials_profile]
|
1166
|
+
proxy_command += ["--profile", config[:shared_credentials_profile]]
|
1167
|
+
end
|
1168
|
+
proxy_command += ["--region", config[:region]]
|
1169
|
+
proxy_command_str = proxy_command.join(" ")
|
1170
|
+
|
1171
|
+
info("Configuring SSH to use Instance Connect tunnel: #{proxy_command_str}")
|
1172
|
+
state[:ssh_proxy_command] = proxy_command_str
|
1173
|
+
|
1174
|
+
# Store Instance Connect details for the transport to use
|
1175
|
+
state[:instance_connect_config] = {
|
1176
|
+
server_id: state[:server_id],
|
1177
|
+
username: state[:username] || actual_platform&.username,
|
1178
|
+
region: config[:region],
|
1179
|
+
profile: config[:shared_credentials_profile],
|
1180
|
+
tunnel_mode: true,
|
1181
|
+
}
|
1182
|
+
end
|
1183
|
+
|
1184
|
+
def instance_connect_endpoint_available?(state)
|
1185
|
+
# If explicitly configured, respect that configuration
|
1186
|
+
return true if config[:instance_connect_endpoint_id]
|
1187
|
+
|
1188
|
+
# Check if there are any instance connect endpoints in the VPC
|
1189
|
+
vpc_id = get_vpc_id_for_instance(state)
|
1190
|
+
return false unless vpc_id
|
1191
|
+
|
1192
|
+
begin
|
1193
|
+
endpoints = ec2.client.describe_instance_connect_endpoints(
|
1194
|
+
filters: [
|
1195
|
+
{ name: "vpc-id", values: [vpc_id] },
|
1196
|
+
{ name: "state", values: ["create-complete"] },
|
1197
|
+
]
|
1198
|
+
).instance_connect_endpoints
|
1199
|
+
|
1200
|
+
!endpoints.empty?
|
1201
|
+
rescue ::Aws::EC2::Errors::InvalidAction, ::Aws::EC2::Errors::UnauthorizedOperation => e
|
1202
|
+
# Instance Connect endpoints may not be available in this region or account
|
1203
|
+
debug("[AWS EC2 Instance Connect] Cannot check for endpoints: #{e.message}")
|
1204
|
+
false
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
def get_vpc_id_for_instance(state)
|
1209
|
+
# Get the instance details to find its VPC
|
1210
|
+
return nil unless state[:server_id]
|
1211
|
+
|
1212
|
+
begin
|
1213
|
+
instance_info = ec2.client.describe_instances(instance_ids: [state[:server_id]]).reservations.first&.instances&.first
|
1214
|
+
return nil unless instance_info
|
1215
|
+
|
1216
|
+
instance_info.vpc_id
|
1217
|
+
rescue => e
|
1218
|
+
debug("[AWS EC2 Instance Connect] Error getting VPC ID for instance: #{e.message}")
|
1219
|
+
nil
|
1220
|
+
end
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
def instance_connect_configure_direct_ssh(state)
|
1224
|
+
# For direct SSH, we need to ensure the hostname is the public DNS name
|
1225
|
+
# and configure SSH options appropriately
|
1226
|
+
server = ec2.get_instance(state[:server_id])
|
1227
|
+
public_dns = server.public_dns_name
|
1228
|
+
|
1229
|
+
if public_dns && !public_dns.empty?
|
1230
|
+
info("[AWS EC2 Instance Connect] Configuring direct SSH to #{public_dns}")
|
1231
|
+
state[:hostname] = public_dns
|
1232
|
+
|
1233
|
+
# Store Instance Connect details for direct SSH mode
|
1234
|
+
state[:instance_connect_config] = {
|
1235
|
+
server_id: state[:server_id],
|
1236
|
+
username: state[:username] || actual_platform&.username,
|
1237
|
+
region: config[:region],
|
1238
|
+
profile: config[:shared_credentials_profile],
|
1239
|
+
direct_ssh: true,
|
1240
|
+
hostname: public_dns,
|
1241
|
+
}
|
1242
|
+
else
|
1243
|
+
warn("[AWS EC2 Instance Connect] No public DNS available for direct SSH, falling back to existing hostname")
|
1244
|
+
end
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
def instance_connect_extract_public_key(private_key_path)
|
1248
|
+
public_key_path = "#{private_key_path}.pub"
|
1249
|
+
|
1250
|
+
if File.exist?(public_key_path)
|
1251
|
+
return File.read(public_key_path).strip
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
begin
|
1255
|
+
key = SSHKey.new(File.read(private_key_path))
|
1256
|
+
key.ssh_public_key
|
1257
|
+
rescue => e
|
1258
|
+
raise "Unable to extract public key from #{private_key_path}: #{e.message}"
|
1259
|
+
end
|
1260
|
+
end
|
947
1261
|
end
|
948
1262
|
end
|
949
1263
|
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
# you may not use this file except in compliance with the License.
|
9
9
|
# You may obtain a copy of the License at
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
12
12
|
#
|
13
13
|
# Unless required by applicable law or agreed to in writing, software
|
14
14
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -19,6 +19,6 @@
|
|
19
19
|
module Kitchen
|
20
20
|
module Driver
|
21
21
|
# Version string for EC2 Test Kitchen driver
|
22
|
-
EC2_VERSION = "3.
|
22
|
+
EC2_VERSION = "3.21.0".freeze
|
23
23
|
end
|
24
24
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kitchen-ec2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Test Kitchen Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-ec2
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sdk-ec2instanceconnect
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: retryable
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,13 +58,27 @@ dependencies:
|
|
44
58
|
- - "<"
|
45
59
|
- !ruby/object:Gem::Version
|
46
60
|
version: '4.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: sshkey
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.0'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2.0'
|
47
75
|
- !ruby/object:Gem::Dependency
|
48
76
|
name: test-kitchen
|
49
77
|
requirement: !ruby/object:Gem::Requirement
|
50
78
|
requirements:
|
51
79
|
- - ">="
|
52
80
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
81
|
+
version: 3.9.0
|
54
82
|
- - "<"
|
55
83
|
- !ruby/object:Gem::Version
|
56
84
|
version: '4'
|
@@ -60,7 +88,7 @@ dependencies:
|
|
60
88
|
requirements:
|
61
89
|
- - ">="
|
62
90
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
91
|
+
version: 3.9.0
|
64
92
|
- - "<"
|
65
93
|
- !ruby/object:Gem::Version
|
66
94
|
version: '4'
|
@@ -74,6 +102,7 @@ files:
|
|
74
102
|
- LICENSE
|
75
103
|
- lib/kitchen/driver/aws/client.rb
|
76
104
|
- lib/kitchen/driver/aws/dedicated_hosts.rb
|
105
|
+
- lib/kitchen/driver/aws/instance_connect.rb
|
77
106
|
- lib/kitchen/driver/aws/instance_generator.rb
|
78
107
|
- lib/kitchen/driver/aws/standard_platform.rb
|
79
108
|
- lib/kitchen/driver/aws/standard_platform/alma.rb
|