From c2f1925e02b701586a270c838c8a4de43378fe2e Mon Sep 17 00:00:00 2001 From: lcy Date: Wed, 2 Dec 2020 10:26:59 +0800 Subject: [PATCH] Support ShieldedInstanceConfig Add support for ShieldedInstanceConfig to modify the configuration of Shielded VM https://cloud.google.com/security/shielded-cloud/shielded-vm This required fog-google v1.12.0 --- README.md | 35 ++--- lib/vagrant-google/action/run_instance.rb | 162 ++++++++++++---------- lib/vagrant-google/config.rb | 93 ++++++++----- test/unit/common/config_test.rb | 69 ++++++--- vagrant-google.gemspec | 2 +- 5 files changed, 212 insertions(+), 149 deletions(-) diff --git a/README.md b/README.md index 3750a11..ed3aecb 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The maintainers for this plugin are @temikus(primary), @erjohnso(backup). * Boot Google Compute Engine instances. * SSH into the instances. * Provision the instances with any built-in Vagrant provisioner. -* Synced folder support via Vagrant's +* Synced folder support via Vagrant's [rsync action](https://www.vagrantup.com/docs/synced-folders/rsync.html). * Define zone-specific configurations so Vagrant can manage machines in multiple zones. @@ -73,8 +73,8 @@ Service Account for API Access. ## Quick Start After installing the plugin (instructions above), the quickest way to get -started is to actually use a dummy Google box from Atlas and specify all the -details manually within a `config.vm.provider` block. +started is to actually use a dummy Google box from Atlas and specify all the +details manually within a `config.vm.provider` block. So first, make a Vagrantfile that looks like the following, filling in your information where necessary: @@ -86,9 +86,9 @@ Vagrant.configure("2") do |config| config.vm.provider :google do |google, override| google.google_project_id = "YOUR_GOOGLE_CLOUD_PROJECT_ID" google.google_json_key_location = "/path/to/your/private-key.json" - + google.image_family = 'ubuntu-1604-lts' - + override.ssh.username = "USERNAME" override.ssh.private_key_path = "~/.ssh/id_rsa" #override.ssh.private_key_path = "~/.ssh/google_compute_engine" @@ -99,10 +99,10 @@ end And then run `vagrant up --provider=google`. -This will start a latest version of Ubuntu 16.04 LTS instance in the -`us-central1-f` zone, with an `n1-standard-1` machine, and the `"default"` -network within your project. And assuming your SSH information (see below) was -filled in properly within your Vagrantfile, SSH and provisioning will work as +This will start a latest version of Ubuntu 16.04 LTS instance in the +`us-central1-f` zone, with an `n1-standard-1` machine, and the `"default"` +network within your project. And assuming your SSH information (see below) was +filled in properly within your Vagrantfile, SSH and provisioning will work as well. Note that normally a lot of this boilerplate is encoded within the box file, @@ -165,13 +165,13 @@ configuration for this provider. This provider exposes quite a few provider-specific configuration options: * `google_json_key_location` - The location of the JSON private key file matching your - Service Account. + Service Account. (Can also be configured with `GOOGLE_JSON_KEY_LOCATION` environment variable.) -* `google_project_id` - The Project ID for your Google Cloud Platform account. +* `google_project_id` - The Project ID for your Google Cloud Platform account. (Can also be configured with `GOOGLE_PROJECT_ID` environment variable.) * `image` - The image name to use when booting your instance. -* `image_family` - Specify an "image family" to pull the latest image from. For example: `centos-7` -will pull the most recent CentOS 7 image. For more info, refer to +* `image_family` - Specify an "image family" to pull the latest image from. For example: `centos-7` +will pull the most recent CentOS 7 image. For more info, refer to [Google Image documentation](https://cloud.google.com/compute/docs/images#image_families). * `image_project_id` - The ID of the GCP project to search for the `image` or `image_family`. * `instance_group` - Unmanaged instance group to add the machine to. If one @@ -206,8 +206,8 @@ will pull the most recent CentOS 7 image. For more info, refer to utility aliases, for example: `['storage-full', 'bigquery', 'https://www.googleapis.com/auth/compute']`. * `service_account` - The IAM service account email to use for the instance. -* `additional_disks` - An array of additional disk configurations. `disk_size` is default to `10`GB; - `disk_name` is default to `name` + "-additional-disk-#{index}"; `disk_type` is default to `pd-standard`; +* `additional_disks` - An array of additional disk configurations. `disk_size` is default to `10`GB; + `disk_name` is default to `name` + "-additional-disk-#{index}"; `disk_type` is default to `pd-standard`; `autodelete_disk` is default to `true`. Here is an example of configuration. ```ruby [{ @@ -233,6 +233,9 @@ will pull the most recent CentOS 7 image. For more info, refer to google.on_host_maintenance = "TERMINATE" ``` +* `enable_secure_boot` - For [Shielded VM](https://cloud.google.com/security/shielded-cloud/shielded-vm), whether to enable Secure Boot. +* `enable_vtpm` - For [Shielded VM](https://cloud.google.com/security/shielded-cloud/shielded-vm), whether to enable vTPM. +* `enable_integrity_monitoring` - For [Shielded VM](https://cloud.google.com/security/shielded-cloud/shielded-vm), whether to enable Integrity monitoring. These can be set like typical provider-specific configuration: @@ -293,7 +296,7 @@ emit a warning, but will otherwise boot the GCE machine. ## Synced Folders Since plugin version 2.0, this is implemented via built-in `SyncedFolders` action. -See Vagrant's [rsync action](https://www.vagrantup.com/docs/synced-folders/rsync.html) +See Vagrant's [rsync action](https://www.vagrantup.com/docs/synced-folders/rsync.html) documentation for more info. ## Development diff --git a/lib/vagrant-google/action/run_instance.rb b/lib/vagrant-google/action/run_instance.rb index 8566d2c..496c0e1 100644 --- a/lib/vagrant-google/action/run_instance.rb +++ b/lib/vagrant-google/action/run_instance.rb @@ -42,67 +42,73 @@ def call(env) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize zone = env[:machine].provider_config.zone # Get the configs - zone_config = env[:machine].provider_config.get_zone_config(zone) - image = zone_config.image - image_family = zone_config.image_family - image_project_id = zone_config.image_project_id - instance_group = zone_config.instance_group - name = zone_config.name - machine_type = zone_config.machine_type - disk_size = zone_config.disk_size - disk_name = zone_config.disk_name - disk_type = zone_config.disk_type - network = zone_config.network - network_project_id = zone_config.network_project_id - subnetwork = zone_config.subnetwork - metadata = zone_config.metadata - labels = zone_config.labels - tags = zone_config.tags - can_ip_forward = zone_config.can_ip_forward - use_private_ip = zone_config.use_private_ip - external_ip = zone_config.external_ip - network_ip = zone_config.network_ip - preemptible = zone_config.preemptible - auto_restart = zone_config.auto_restart - on_host_maintenance = zone_config.on_host_maintenance - autodelete_disk = zone_config.autodelete_disk - service_account_scopes = zone_config.scopes - service_account = zone_config.service_account - project_id = zone_config.google_project_id - additional_disks = zone_config.additional_disks - accelerators = zone_config.accelerators + zone_config = env[:machine].provider_config.get_zone_config(zone) + image = zone_config.image + image_family = zone_config.image_family + image_project_id = zone_config.image_project_id + instance_group = zone_config.instance_group + name = zone_config.name + machine_type = zone_config.machine_type + disk_size = zone_config.disk_size + disk_name = zone_config.disk_name + disk_type = zone_config.disk_type + network = zone_config.network + network_project_id = zone_config.network_project_id + subnetwork = zone_config.subnetwork + metadata = zone_config.metadata + labels = zone_config.labels + tags = zone_config.tags + can_ip_forward = zone_config.can_ip_forward + use_private_ip = zone_config.use_private_ip + external_ip = zone_config.external_ip + network_ip = zone_config.network_ip + preemptible = zone_config.preemptible + auto_restart = zone_config.auto_restart + on_host_maintenance = zone_config.on_host_maintenance + autodelete_disk = zone_config.autodelete_disk + service_account_scopes = zone_config.scopes + service_account = zone_config.service_account + project_id = zone_config.google_project_id + additional_disks = zone_config.additional_disks + accelerators = zone_config.accelerators + enable_secure_boot = zone_config.enable_secure_boot + enable_vtpm = zone_config.enable_vtpm + enable_integrity_monitoring = zone_config.enable_integrity_monitoring # Launch! env[:ui].info(I18n.t("vagrant_google.launching_instance")) - env[:ui].info(" -- Name: #{name}") - env[:ui].info(" -- Project: #{project_id}") - env[:ui].info(" -- Type: #{machine_type}") - env[:ui].info(" -- Disk type: #{disk_type}") - env[:ui].info(" -- Disk size: #{disk_size} GB") - env[:ui].info(" -- Disk name: #{disk_name}") - env[:ui].info(" -- Image: #{image}") - env[:ui].info(" -- Image family: #{image_family}") - env[:ui].info(" -- Image Project: #{image_project_id}") if image_project_id - env[:ui].info(" -- Instance Group: #{instance_group}") - env[:ui].info(" -- Zone: #{zone}") if zone - env[:ui].info(" -- Network: #{network}") if network - env[:ui].info(" -- Network Project: #{network_project_id}") if network_project_id - env[:ui].info(" -- Subnetwork: #{subnetwork}") if subnetwork - env[:ui].info(" -- Metadata: '#{metadata}'") - env[:ui].info(" -- Labels: '#{labels}'") - env[:ui].info(" -- Network tags: '#{tags}'") - env[:ui].info(" -- IP Forward: #{can_ip_forward}") - env[:ui].info(" -- Use private IP: #{use_private_ip}") - env[:ui].info(" -- External IP: #{external_ip}") - env[:ui].info(" -- Network IP: #{network_ip}") - env[:ui].info(" -- Preemptible: #{preemptible}") - env[:ui].info(" -- Auto Restart: #{auto_restart}") - env[:ui].info(" -- On Maintenance: #{on_host_maintenance}") - env[:ui].info(" -- Autodelete Disk: #{autodelete_disk}") - env[:ui].info(" -- Scopes: #{service_account_scopes}") if service_account_scopes - env[:ui].info(" -- Service Account: #{service_account}") if service_account - env[:ui].info(" -- Additional Disks:#{additional_disks}") - env[:ui].info(" -- Accelerators: #{accelerators}") + env[:ui].info(" -- Name: #{name}") + env[:ui].info(" -- Project: #{project_id}") + env[:ui].info(" -- Type: #{machine_type}") + env[:ui].info(" -- Disk type: #{disk_type}") + env[:ui].info(" -- Disk size: #{disk_size} GB") + env[:ui].info(" -- Disk name: #{disk_name}") + env[:ui].info(" -- Image: #{image}") + env[:ui].info(" -- Image family: #{image_family}") + env[:ui].info(" -- Image Project: #{image_project_id}") if image_project_id + env[:ui].info(" -- Instance Group: #{instance_group}") + env[:ui].info(" -- Zone: #{zone}") if zone + env[:ui].info(" -- Network: #{network}") if network + env[:ui].info(" -- Network Project: #{network_project_id}") if network_project_id + env[:ui].info(" -- Subnetwork: #{subnetwork}") if subnetwork + env[:ui].info(" -- Metadata: '#{metadata}'") + env[:ui].info(" -- Labels: '#{labels}'") + env[:ui].info(" -- Network tags: '#{tags}'") + env[:ui].info(" -- IP Forward: #{can_ip_forward}") + env[:ui].info(" -- Use private IP: #{use_private_ip}") + env[:ui].info(" -- External IP: #{external_ip}") + env[:ui].info(" -- Network IP: #{network_ip}") + env[:ui].info(" -- Preemptible: #{preemptible}") + env[:ui].info(" -- Auto Restart: #{auto_restart}") + env[:ui].info(" -- On Maintenance: #{on_host_maintenance}") + env[:ui].info(" -- Autodelete Disk: #{autodelete_disk}") + env[:ui].info(" -- Scopes: #{service_account_scopes}") if service_account_scopes + env[:ui].info(" -- Service Account: #{service_account}") if service_account + env[:ui].info(" -- Additional Disks: #{additional_disks}") + env[:ui].info(" -- Accelerators: #{accelerators}") + env[:ui].info(" -- Secure Boot: #{enable_secure_boot}") if enable_secure_boot + env[:ui].info(" -- vTPM: #{enable_vtpm}") if enable_vtpm + env[:ui].info(" -- Integrity Monitoring: #{enable_integrity_monitoring}") if enable_integrity_monitoring # Munge image config if image_family @@ -144,6 +150,9 @@ def call(env) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize :accelerator_count => accelerator_count }) end + # Munge shieldedInstance config + shielded_instance_config = { :enable_secure_boot => enable_secure_boot, :enable_vtpm => enable_vtpm, :enable_integrity_monitoring => enable_integrity_monitoring } + begin request_start_time = Time.now.to_i disk = nil @@ -258,24 +267,25 @@ def call(env) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize end defaults = { - :name => name, - :zone => zone, - :machine_type => machine_type, - :disk_size => disk_size, - :disk_type => disk_type, - :image => image, - :network_interfaces => network_interfaces, - :metadata => { :items => metadata.each.map { |k, v| {:key => k.to_s, :value => v.to_s} } }, - :labels => labels, - :tags => { :items => tags }, - :can_ip_forward => can_ip_forward, - :use_private_ip => use_private_ip, - :external_ip => external_ip, - :network_ip => network_ip, - :disks => disks, - :scheduling => scheduling, - :service_accounts => service_accounts, - :guest_accelerators => accelerators_url + :name => name, + :zone => zone, + :machine_type => machine_type, + :disk_size => disk_size, + :disk_type => disk_type, + :image => image, + :network_interfaces => network_interfaces, + :metadata => { :items => metadata.each.map { |k, v| {:key => k.to_s, :value => v.to_s} } }, + :labels => labels, + :tags => { :items => tags }, + :can_ip_forward => can_ip_forward, + :use_private_ip => use_private_ip, + :external_ip => external_ip, + :network_ip => network_ip, + :disks => disks, + :scheduling => scheduling, + :service_accounts => service_accounts, + :guest_accelerators => accelerators_url, + :shielded_instance_config => shielded_instance_config, } server = env[:google_compute].servers.create(defaults) @logger.info("Machine '#{zone}:#{name}' created.") diff --git a/lib/vagrant-google/config.rb b/lib/vagrant-google/config.rb index 9cd8b8b..374a6fc 100644 --- a/lib/vagrant-google/config.rb +++ b/lib/vagrant-google/config.rb @@ -188,40 +188,58 @@ class Config < Vagrant.plugin("2", :config) # rubocop:disable Metrics/ClassLengt # @return [Array] attr_accessor :accelerators + # whether the instance has Secure Boot enabled + # + # @return Boolean + attr_accessor :enable_secure_boot + + # whether the instance has the vTPM enabled + # + # @return Boolean + attr_accessor :enable_vtpm + + # whether the instance has integrity monitoring enabled + # + # @return Boolean + attr_accessor :enable_integrity_monitoring + def initialize(zone_specific=false) - @google_json_key_location = UNSET_VALUE - @google_project_id = UNSET_VALUE - @image = UNSET_VALUE - @image_family = UNSET_VALUE - @image_project_id = UNSET_VALUE - @instance_group = UNSET_VALUE - @machine_type = UNSET_VALUE - @disk_size = UNSET_VALUE - @disk_name = UNSET_VALUE - @disk_type = UNSET_VALUE - @metadata = {} - @name = UNSET_VALUE - @network = UNSET_VALUE - @network_project_id = UNSET_VALUE - @subnetwork = UNSET_VALUE - @tags = [] - @labels = {} - @can_ip_forward = UNSET_VALUE - @external_ip = UNSET_VALUE - @network_ip = UNSET_VALUE - @use_private_ip = UNSET_VALUE - @autodelete_disk = UNSET_VALUE - @preemptible = UNSET_VALUE - @auto_restart = UNSET_VALUE - @on_host_maintenance = UNSET_VALUE - @instance_ready_timeout = UNSET_VALUE - @zone = UNSET_VALUE - @scopes = UNSET_VALUE - @service_accounts = UNSET_VALUE - @service_account = UNSET_VALUE - @additional_disks = [] - @setup_winrm_password = UNSET_VALUE - @accelerators = [] + @google_json_key_location = UNSET_VALUE + @google_project_id = UNSET_VALUE + @image = UNSET_VALUE + @image_family = UNSET_VALUE + @image_project_id = UNSET_VALUE + @instance_group = UNSET_VALUE + @machine_type = UNSET_VALUE + @disk_size = UNSET_VALUE + @disk_name = UNSET_VALUE + @disk_type = UNSET_VALUE + @metadata = {} + @name = UNSET_VALUE + @network = UNSET_VALUE + @network_project_id = UNSET_VALUE + @subnetwork = UNSET_VALUE + @tags = [] + @labels = {} + @can_ip_forward = UNSET_VALUE + @external_ip = UNSET_VALUE + @network_ip = UNSET_VALUE + @use_private_ip = UNSET_VALUE + @autodelete_disk = UNSET_VALUE + @preemptible = UNSET_VALUE + @auto_restart = UNSET_VALUE + @on_host_maintenance = UNSET_VALUE + @instance_ready_timeout = UNSET_VALUE + @zone = UNSET_VALUE + @scopes = UNSET_VALUE + @service_accounts = UNSET_VALUE + @service_account = UNSET_VALUE + @additional_disks = [] + @setup_winrm_password = UNSET_VALUE + @accelerators = [] + @enable_secure_boot = UNSET_VALUE + @enable_vtpm = UNSET_VALUE + @enable_integrity_monitoring = UNSET_VALUE # Internal state (prefix with __ so they aren't automatically # merged) @@ -400,6 +418,15 @@ def finalize! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedC @scopes = @service_accounts end + # enable_secure_boot defaults to nil + @enable_secure_boot = nil if @enable_secure_boot == UNSET_VALUE + + # enable_vtpm defaults to nil + @enable_vtpm = nil if @enable_vtpm == UNSET_VALUE + + # enable_integrity_monitoring defaults to nil + @enable_integrity_monitoring = nil if @enable_integrity_monitoring == UNSET_VALUE + # Compile our zone specific configurations only within # NON-zone-SPECIFIC configurations. unless @__zone_specific diff --git a/test/unit/common/config_test.rb b/test/unit/common/config_test.rb index 243a3ad..c21145e 100644 --- a/test/unit/common/config_test.rb +++ b/test/unit/common/config_test.rb @@ -30,27 +30,30 @@ end end - its("name") { should match "i-[0-9]{10}-[0-9a-f]{4}" } - its("image") { should be_nil } - its("image_family") { should be_nil } - its("image_project_id") { should be_nil } - its("instance_group") { should be_nil } - its("zone") { should == "us-central1-f" } - its("network") { should == "default" } - its("machine_type") { should == "n1-standard-1" } - its("disk_size") { should == 10 } - its("disk_name") { should be_nil } - its("disk_type") { should == "pd-standard" } - its("instance_ready_timeout") { should == 20 } - its("metadata") { should == {} } - its("tags") { should == [] } - its("labels") { should == {} } - its("scopes") { should == nil } - its("additional_disks") { should == [] } - its("preemptible") { should be_falsey } - its("auto_restart") { should } - its("on_host_maintenance") { should == "MIGRATE" } - its("accelerators") { should == [] } + its("name") { should match "i-[0-9]{10}-[0-9a-f]{4}" } + its("image") { should be_nil } + its("image_family") { should be_nil } + its("image_project_id") { should be_nil } + its("instance_group") { should be_nil } + its("zone") { should == "us-central1-f" } + its("network") { should == "default" } + its("machine_type") { should == "n1-standard-1" } + its("disk_size") { should == 10 } + its("disk_name") { should be_nil } + its("disk_type") { should == "pd-standard" } + its("instance_ready_timeout") { should == 20 } + its("metadata") { should == {} } + its("tags") { should == [] } + its("labels") { should == {} } + its("scopes") { should == nil } + its("additional_disks") { should == [] } + its("preemptible") { should be_falsey } + its("auto_restart") { should } + its("on_host_maintenance") { should == "MIGRATE" } + its("accelerators") { should == [] } + its("enable_secure_boot") { should be_nil } + its("enable_vtpm") { should be_nil } + its("enable_integrity_monitoring") { should be_nil } end describe "overriding defaults" do @@ -58,8 +61,28 @@ # simple boilerplate test, so I cut corners here. It just sets # each of these attributes to "foo" in isolation, and reads the value # and asserts the proper result comes back out. - [:name, :image, :image_family, :image_project_id, :zone, :instance_ready_timeout, :machine_type, :disk_size, :disk_name, :disk_type, - :network, :network_project_id, :metadata, :labels, :can_ip_forward, :external_ip, :autodelete_disk].each do |attribute| + [ + :name, + :image, + :image_family, + :image_project_id, + :zone, + :instance_ready_timeout, + :machine_type, + :disk_size, + :disk_name, + :disk_type, + :network, + :network_project_id, + :metadata, + :labels, + :can_ip_forward, + :external_ip, + :autodelete_disk, + :enable_secure_boot, + :enable_vtpm, + :enable_integrity_monitoring, + ].each do |attribute| it "should not default #{attribute} if overridden" do instance.send("#{attribute}=".to_sym, "foo") diff --git a/vagrant-google.gemspec b/vagrant-google.gemspec index 8c8a429..641ac91 100644 --- a/vagrant-google.gemspec +++ b/vagrant-google.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.6" s.rubyforge_project = "vagrant-google" - s.add_runtime_dependency "fog-google", "~> 1.10.0" + s.add_runtime_dependency "fog-google", "~> 1.12.0" # This is a restriction to avoid errors on `failure_message_for_should` # TODO: revise after vagrant_spec goes past >0.0.1 (at master@e623a56)