Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# kitchen-oci CHANGELOG

# 3.1.0
- feat: add `kms_key_id` to encrypt the compute boot volume with a customer-managed (Vault) key
- feat: auto-detect security token (RPST) authentication when a `security_token_file` is present in the selected profile

# 3.0.0
- feat: default value for `are_legacy_imds_endpoints_disabled` of `true` set at instance provisioning time
> BREAKING CHANGE: This change creates a situation where older images can get stuck in a `wait_until_ready` loop
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ as the `image_id`. Both require you to do some work up front. The trade-off bet
These settings are optional:

- `boot_volume_size_in_gbs`, The size of the boot volume, in GB (range: 50GB - 32TB)
- `kms_key_id`, OCID of the customer-managed (Vault) key used to encrypt the boot volume. [[more](#boot-volume-encryption)]
- `user_data`, Add user data scripts to cloud-init [[more](#support-for-user-data-scripts-and-cloud-init)]
- `display_name`, Overrides the display name and hostname randomaization provided by the `hostname_prefix` setting
- `hostname_prefix`, Prefix for the generated hostnames (note that OCI doesn't like underscores)
Expand Down Expand Up @@ -231,6 +232,8 @@ platforms:
...
```

Security token (session) authentication is also detected automatically: if the selected profile contains a `security_token_file` entry, the driver will use it to build a `SecurityTokenSigner` even when `use_token_auth` is not set. This makes ephemeral RPST sessions (such as those issued in CI by an OCI session exchange) work without any additional configuration. Setting `use_token_auth: true` explicitly remains supported and is equivalent.

## Use without OCI config file

If you want to run without running `oci setup config` (such as on a build server) you can specify configuration settings that would be in the `~/.oci/config` file directly in the `kitchen.yml`
Expand Down Expand Up @@ -409,6 +412,20 @@ driver:
vpus_per_gb: 30
```

## Boot Volume Encryption

By default OCI encrypts boot volumes with an Oracle-managed key. Compartments that are governed by a [Security Zone](https://docs.oracle.com/en-us/iaas/security-zone/using/security-zone-policies.htm) often require that resources be encrypted with a customer-managed key from the OCI Vault service. Provide the OCID of the key with `kms_key_id` and the driver will encrypt the instance boot volume with it at launch time:

```yml
---
driver:
name: oci
...
kms_key_id: ocid1.key.oc1..aaaaaaaa...
```

This applies when launching an instance from an image (`image_id`/`image_name`). When unset, OCI's default encryption behavior is used.

## IMDSv2
In accordance with [OCI security guidelines](https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm), the driver is disabling the IMDSv1 endpoint by default. This overrides the current default setting
in OCI and could cause issues with [unsupported images](https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm#upgrading-v2__supported-images). In the event legacy IMDS support is required,
Expand Down
1 change: 1 addition & 0 deletions lib/kitchen/driver/oci.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class Oci < Kitchen::Driver::Base
default_config :preemptible_instance, false
default_config :boot_volume_size_in_gbs, nil
default_config :use_private_ip, false
default_config :kms_key_id, nil
default_config :volumes, {}

# dbaas configs
Expand Down
15 changes: 14 additions & 1 deletion lib/kitchen/driver/oci/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,28 @@ def generic_api(klass)

# Determines the signing method if one is specified.
#
# Security token (session) authentication is used when <b>use_token_auth</b> is set explicitly or
# when the selected OCI profile contains a <b>security_token_file</b>. The latter allows sessions
# created by <tt>oci session authenticate</tt> (RPST) to be detected automatically.
#
# @return [OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner, OCI::Auth::Signers::SecurityTokenSigner] an instance of the specified token signer.
def signer
if config[:use_instance_principals]
OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new
elsif config[:use_token_auth]
elsif config[:use_token_auth] || security_token_file?
token_signer
end
end

# Whether the loaded OCI config contains a security_token_file pointing at an existing token.
#
# @return [Boolean]
def security_token_file?
oci_config.respond_to?(:security_token_file) &&
!oci_config.security_token_file.to_s.empty? &&
File.exist?(oci_config.security_token_file)
end

# Creates the token signer with a provided key.
#
# @return [OCI::Auth::Signers::SecurityTokenSigner]
Expand Down
6 changes: 4 additions & 2 deletions lib/kitchen/driver/oci/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ def initialize(driver_config)
#
# @return [OCI::Config]
def oci_config
# OCI::Config is missing this
OCI::Config.class_eval { attr_accessor :security_token_file } if @driver_config[:use_token_auth]
# OCI::Config is missing this attribute. It is always added so that a security_token_file
# present in the selected profile is loaded, which enables auto-detection of session
# (RPST) token authentication even when use_token_auth is not explicitly set.
OCI::Config.class_eval { attr_accessor :security_token_file } unless OCI::Config.instance_methods.include?(:security_token_file)
conf = config_loader(config_file_location: @driver_config[:oci_config_file], profile_name: @driver_config[:oci_profile_name])
@driver_config[:oci_config].each do |key, value|
conf.send("#{key}=", value) unless value.nil? || value.empty?
Expand Down
5 changes: 4 additions & 1 deletion lib/kitchen/driver/oci/instance/compute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,16 @@ def agent_config
end

# Adds the source_details property to the launch_details for an instance that is being created from an image.
# When <b>kms_key_id</b> is specified the boot volume is encrypted with the provided customer-managed key,
# which is required by compartments governed by a Security Zone that mandates customer-managed encryption.
def instance_source_via_image
return if config[:boot_volume_id]

launch_details.source_details = OCI::Core::Models::InstanceSourceViaImageDetails.new(
sourceType: "image",
imageId: image_id,
bootVolumeSizeInGBs: config[:boot_volume_size_in_gbs]
bootVolumeSizeInGBs: config[:boot_volume_size_in_gbs],
kmsKeyId: config[:kms_key_id]
)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/kitchen/driver/oci_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ module Driver
# Version string for Oracle OCI Kitchen driver
#
# @author Stephen Pearson (<stephen.pearson@oracle.com>)
OCI_VERSION = "3.0.0"
OCI_VERSION = "3.1.0"
end
end
42 changes: 42 additions & 0 deletions spec/kitchen/driver/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@
end
end

shared_examples "a client with token auth" do |clients|
clients.each do |method, klass|
it "creates #{method} client" do
expect(klass).to receive(:new).with(signer: signer, config: oci_config)
subject.send(method)
end
end
end

clients = {
compute: OCI::Core::ComputeClient,
network: OCI::Core::VirtualNetworkClient,
Expand All @@ -76,4 +85,37 @@
let(:driver_config) { { use_instance_principals: true } }
it_behaves_like "a client with instance principals", clients
end

context "clients using token auth" do
before do
OCI::Config.class_eval { attr_accessor :security_token_file } unless OCI::Config.instance_methods.include?(:security_token_file)
allow(File).to receive(:read).with(key_file).and_return(key_content)
allow(File).to receive(:read).with(security_token_file).and_return(token)
allow(File).to receive(:exist?).with(security_token_file).and_return(true)
allow(OpenSSL::PKey::RSA).to receive(:new).with(key_content, nil).and_return(private_key)
allow(OCI::Auth::Signers::SecurityTokenSigner).to receive(:new).with(token, private_key).and_return(signer)
end
let(:key_file) { "/fake/.oci/key.pem" }
let(:key_content) { "fake-private-key" }
let(:security_token_file) { "/fake/.oci/token" }
let(:token) { "fake-security-token" }
let(:private_key) { instance_double(OpenSSL::PKey::RSA) }
let(:signer) { instance_double(OCI::Auth::Signers::SecurityTokenSigner) }
let(:oci_config) do
OCI::Config.new.tap do |c|
c.key_file = key_file
c.security_token_file = security_token_file
end
end

context "when use_token_auth is set explicitly" do
let(:driver_config) { { use_token_auth: true } }
it_behaves_like "a client with token auth", clients
end

context "when a security_token_file is detected in the profile (RPST session)" do
let(:driver_config) { {} }
it_behaves_like "a client with token auth", clients
end
end
end
19 changes: 19 additions & 0 deletions spec/kitchen/driver/compute_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,25 @@
end
end

context "standard compute (Linux) with kms_key_id" do
let(:driver_config) do
compute_driver_config.merge!({
kms_key_id: kms_key_ocid,
})
end

it "creates a compute instance with the boot volume encrypted by the kms key" do
expect(compute_client).to receive(:launch_instance).with(launch_instance_request_with_kms)
driver.create(state)
expect(state).to match(
{
hostname: private_ip,
server_id: instance_ocid,
}
)
end
end

context "standard compute (Linux) from boot volume" do
let(:driver_config) do
base_driver_config.merge!({
Expand Down
17 changes: 17 additions & 0 deletions spec/spec_helper/compute_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@
end
end

let(:kms_key_ocid) { "ocid1.key.oc1.fake.aaaaaaaaaabcdefghijklmnopqrstuvwxyz12345" }

let(:launch_instance_request_with_kms) do
launch_instance_request_base.tap do |l|
l.source_details = OCI::Core::Models::InstanceSourceViaImageDetails.new(
sourceType: "image",
imageId: image_ocid,
bootVolumeSizeInGBs: nil,
kmsKeyId: kms_key_ocid
)
l.instance_options = OCI::Core::Models::InstanceOptions.new(
are_legacy_imds_endpoints_disabled: true
)
l.capacity_reservation_id = capacity_reservation
end
end

let(:launch_instance_from_bv_request) do
launch_instance_request_base.tap do |l|
l.source_details = OCI::Core::Models::InstanceSourceViaBootVolumeDetails.new(
Expand Down
Loading