From 89c2d55671a2b873c3d07950f425867db196097a Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 13 Feb 2019 19:00:17 -0800 Subject: [PATCH 01/10] (maint) Sync README using make generate_docs --- examples/simple_example/README.md | 4 ++-- test/fixtures/simulated_ci_environment/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/simple_example/README.md b/examples/simple_example/README.md index 6dfcb60..c53dc07 100644 --- a/examples/simple_example/README.md +++ b/examples/simple_example/README.md @@ -149,8 +149,8 @@ $ curl -H Metadata-Flavor:Google http://metadata.google.internal/computeMetadata | Name | Description | |------|-------------| | nat_ip | Public IP address of the example compute instance. | -| project_id | | -| region | | +| project_id | The project id used when managing resources. | +| region | The region used when managing resources. | [^]: (autogen_docs_end) diff --git a/test/fixtures/simulated_ci_environment/README.md b/test/fixtures/simulated_ci_environment/README.md index 7fec0a1..978bcaf 100644 --- a/test/fixtures/simulated_ci_environment/README.md +++ b/test/fixtures/simulated_ci_environment/README.md @@ -48,6 +48,6 @@ For example: | Name | Description | |------|-------------| -| phoogle_sa | The SA KEY JSON content. Store in GOOGLE_CREDENTIALS. | +| service_account_private_key | The SA KEY JSON content. Store in GOOGLE_CREDENTIALS. This is equivalent to the `phoogle_sa` output in the infra repository | [^]: (autogen_docs_end) From 12f64b1e5f149c6252965af03fb38f672ca70448 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 13 Feb 2019 19:09:58 -0800 Subject: [PATCH 02/10] WIP Add stdlib::setup_sudoers Read the metadata key `sudoers` and configure each CSV listed username in /etc/sudoers with full root access. --- files/setup_sudoers.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 files/setup_sudoers.sh diff --git a/files/setup_sudoers.sh b/files/setup_sudoers.sh new file mode 100644 index 0000000..d9b207a --- /dev/null +++ b/files/setup_sudoers.sh @@ -0,0 +1,40 @@ +#! /bin/bash +# Read the metadata key named "sudoers" and add each comma separated value to +# the sudoers file. +stdlib::setup_sudoers() { + local user user_list sudoers_file + user_list="$(metadata_get -k 'project/attributes/sudoers')" + + if [[ -z "${user_list}" ]]; then + stdlib::debug "Skipping sudoers setup. The value of the project metadata key named sudoers is empty. Set sudoers to a comma separated list to enable sudo support, e.g. sudoers=jmccune,pames" + return 0 + fi + + sudoers_file="$(mktemp)" + cp /etc/sudoers "${sudoers_file}" + + for user in ${user_list//,/ }; do + if grep -q "^${user}"'\b' "${sudoers_file}"; then + stdlib::debug "User ${user} is already in /etc/sudoers, taking no action" + else + stdlib::info "Adding ${user} to /etc/sudoers" + echo -e "${user}\tALL= (ALL)\tNOPASSWD: ALL" >>"${sudoers_file}" + fi + done + + if cmp --silent /etc/sudoers "${sudoers_file}"; then + stdlib::debug "No changes necessary to sudoers file, doing nothing." + return 0 + else + stdlib::info "Staged changes to sudoers:" + diff -U2 /etc/sudoers "${sudoers_file}" || true + if visudo -cf "${sudoers_file}"; then + stdlib::info "Installing new /etc/sudoers file" + stdlib::cmd install -o 0 -g 0 -m 0440 "${sudoers_file}" /etc/sudoers + return $? + else + stdlib::error "Skipping modification of /etc/sudoers because the temporary sudoers file is invalid." + return 1 + fi + fi +} From b37c1e9c3d6acefe685a5b1b9dc94baed26776c5 Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Wed, 13 Mar 2019 21:04:27 -0400 Subject: [PATCH 03/10] add retriable to Gemfile --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 1ece7c3..31521a8 100644 --- a/Gemfile +++ b/Gemfile @@ -16,4 +16,5 @@ ruby '2.5.3' source 'https://rubygems.org/' do gem 'kitchen-terraform', '~> 4.1.0' + gem 'retriable', '~> 3.1.2' end From 22683df6b20ee36666edaa1cf1142558e7b2e3b1 Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Wed, 20 Feb 2019 23:37:55 -0500 Subject: [PATCH 04/10] introduce user facing feature flag boolean var enable_setup_sudoers that when enabled stuffs function stdlib::setup_sudoers into the start-script-stdlib-body to be invoked by choice in startup-scripts-custom +squash , variable code review --- files/setup_sudoers.sh | 2 +- main.tf | 4 ++++ variables.tf | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/files/setup_sudoers.sh b/files/setup_sudoers.sh index d9b207a..761aba9 100644 --- a/files/setup_sudoers.sh +++ b/files/setup_sudoers.sh @@ -1,5 +1,5 @@ #! /bin/bash -# Read the metadata key named "sudoers" and add each comma separated value to +# Read the project metadata key named "sudoers" and add each comma separated value to # the sudoers file. stdlib::setup_sudoers() { local user user_list sudoers_file diff --git a/main.tf b/main.tf index 478e067..b36edb7 100644 --- a/main.tf +++ b/main.tf @@ -17,17 +17,21 @@ locals { stdlib_head = "${file("${path.module}/files/startup-script-stdlib-head.sh")}" gsutil_el = "${var.enable_init_gsutil_crcmod_el ? file("${path.module}/files/init_gsutil_crcmod_el.sh") : ""}" + sudoers = "${var.enable_setup_sudoers ? file("${path.module}/files/setup_sudoers.sh") : ""}" get_from_bucket = "${var.enable_get_from_bucket ? file("${path.module}/files/get_from_bucket.sh") : ""}" setup_init_script = "${(var.enable_setup_init_script && var.enable_get_from_bucket) ? file("${path.module}/files/setup_init_script.sh") : ""}" stdlib_body = "${file("${path.module}/files/startup-script-stdlib-body.sh")}" + # List representing complete content, to be concatenated together. stdlib_list = [ "${local.stdlib_head}", "${local.gsutil_el}", "${local.get_from_bucket}", "${local.setup_init_script}", + "${local.sudoers}", "${local.stdlib_body}", ] + # Final content output to the user stdlib = "${join("", local.stdlib_list)}" } diff --git a/variables.tf b/variables.tf index 146e9bb..09baa8e 100644 --- a/variables.tf +++ b/variables.tf @@ -25,5 +25,9 @@ variable "enable_get_from_bucket" { variable "enable_setup_init_script" { description = "If not false, include stdlib::setup_init_script() prior to executing startup-script-custom. Call this function to load an init script from GCS into /etc/init.d and initialize it with chkconfig. This function depends on stdlib::get_from_bucket, so this function won't be enabled if enable_get_from_bucket is false." +} + +variable "enable_setup_sudoers" { + description = "If true, include stdlib::setup_sudoers() prior to executing startup-script-custom. Call this function from startup-script-custom to setup unix usernames in sudoers Comma separated values must be posted to the project metadata key project/attributes/sudoers" default = "false" } From e8fb7d8bf48f26cfb8ab4256f929360b9990c021 Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Mon, 25 Feb 2019 12:55:05 -0500 Subject: [PATCH 05/10] add example project metadata kv, sudoers=example_user to a) display how the setup_sudoers feature flag can be used, b) utilize for tests --- examples/simple_example/main.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/simple_example/main.tf b/examples/simple_example/main.tf index 10cd920..4bdc430 100644 --- a/examples/simple_example/main.tf +++ b/examples/simple_example/main.tf @@ -30,6 +30,12 @@ data "google_compute_image" "os" { family = "centos-7" } +resource "google_compute_project_metadata" "example" { + metadata = { + sudoers = "example_user" + } +} + resource "google_compute_instance" "example" { name = "startup-scripts-example1" description = "Startup Scripts Example" From d0a1eee0c0c523d5abb411169306b34304c56274 Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Mon, 25 Feb 2019 12:55:17 -0500 Subject: [PATCH 06/10] terraform fmt --- examples/simple_example/main.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/simple_example/main.tf b/examples/simple_example/main.tf index 4bdc430..aad9f60 100644 --- a/examples/simple_example/main.tf +++ b/examples/simple_example/main.tf @@ -55,6 +55,7 @@ resource "google_compute_instance" "example" { boot_disk { auto_delete = true + initialize_params { image = "${data.google_compute_image.os.self_link}" type = "pd-standard" @@ -63,6 +64,7 @@ resource "google_compute_instance" "example" { network_interface { network = "default" + access_config { // Ephemeral IP } From 2ce06d4cb548cef26b50cfb8ee5395516ede5964 Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Mon, 25 Feb 2019 20:01:02 -0500 Subject: [PATCH 07/10] refactor setup_sudoers to add entries from metadata as files in /etc/sudoers.d +squash, add license header to setup_sudoers +squash add todo for new issue to be created stage sudoers entry to /etc/sudoers.d/\ instead of /etc/sudoers. check if exists in sudoers and in /etc/sudoers.d +WIP +drop this commit , WIP. This functionality will be replicated into a simple sudoers check/validation finish cleanup, +sq remove todo, moved to bug --- files/setup_sudoers.sh | 55 ++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/files/setup_sudoers.sh b/files/setup_sudoers.sh index 761aba9..518e84b 100644 --- a/files/setup_sudoers.sh +++ b/files/setup_sudoers.sh @@ -1,40 +1,53 @@ #! /bin/bash +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Read the project metadata key named "sudoers" and add each comma separated value to # the sudoers file. stdlib::setup_sudoers() { local user user_list sudoers_file - user_list="$(metadata_get -k 'project/attributes/sudoers')" + user_list="$(stdlib::metadata_get -k 'project/attributes/sudoers')" if [[ -z "${user_list}" ]]; then - stdlib::debug "Skipping sudoers setup. The value of the project metadata key named sudoers is empty. Set sudoers to a comma separated list to enable sudo support, e.g. sudoers=jmccune,pames" + stdlib::debug "Skipping sudoers setup. \ + The value of the project metadata key named sudoers is empty. \ + Set sudoers to a comma separated list to enable sudo \ + support, e.g. sudoers=jmccune,pames" return 0 fi - sudoers_file="$(mktemp)" - cp /etc/sudoers "${sudoers_file}" + sudoers_file="/etc/sudoers" + sudoers_d="/etc/sudoers.d" for user in ${user_list//,/ }; do - if grep -q "^${user}"'\b' "${sudoers_file}"; then - stdlib::debug "User ${user} is already in /etc/sudoers, taking no action" + if [[ -f "${sudoers_d}/${user}" ]] \ + && ( grep -q "^${user}"'\b' "${sudoers_d}/${user}" ) \ + || (grep -q "^${user}"'\b' "${sudoers_file}") ; then + stdlib::debug "User ${user} is already in /etc/sudoers or \ + /etc/sudoers.d/${user}, taking no action" else - stdlib::info "Adding ${user} to /etc/sudoers" - echo -e "${user}\tALL= (ALL)\tNOPASSWD: ALL" >>"${sudoers_file}" + stdlib::info "Adding ${user} to /etc/sudoers.d/${user}" + echo -e "${user}\tALL= (ALL)\tNOPASSWD: ALL" \ + > "${sudoers_d}/${user}" \ + && chmod 0440 "${sudoers_d}/${user}" fi done - if cmp --silent /etc/sudoers "${sudoers_file}"; then - stdlib::debug "No changes necessary to sudoers file, doing nothing." - return 0 + if visudo -c ; then + stdlib::info "sudoers config valid!" else - stdlib::info "Staged changes to sudoers:" - diff -U2 /etc/sudoers "${sudoers_file}" || true - if visudo -cf "${sudoers_file}"; then - stdlib::info "Installing new /etc/sudoers file" - stdlib::cmd install -o 0 -g 0 -m 0440 "${sudoers_file}" /etc/sudoers - return $? - else - stdlib::error "Skipping modification of /etc/sudoers because the temporary sudoers file is invalid." - return 1 - fi + stdlib::error "sudoers config invalid!" + return 1 fi } From a5ca79a8340c3f5ea76f016e438010349aea201c Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Mon, 25 Feb 2019 21:26:51 -0500 Subject: [PATCH 08/10] add serial console output test for sudoers addition for example_user of the simple_example, which is being tested by the test --- .../files/startup-script-custom | 13 +++++- examples/simple_example/main.tf | 2 +- .../simple_example/controls/sudoers.rb | 41 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 test/integration/simple_example/controls/sudoers.rb diff --git a/examples/simple_example/files/startup-script-custom b/examples/simple_example/files/startup-script-custom index bf06a39..0802cb9 100644 --- a/examples/simple_example/files/startup-script-custom +++ b/examples/simple_example/files/startup-script-custom @@ -1,5 +1,16 @@ #! /bin/bash + +# add example_user for test +sudo useradd -m example_user1 + +# add another user for inverse test - \ +# sudo should not work for this user +sudo useradd -m example_user2 +sudo sh -c 'echo testpassword2019 | passwd --stdin example_user2' + +stdlib::info "Checking whether to add any users to sudoers ..." +stdlib::setup_sudoers + URL=${URL:-"http://ifconfig.co/json"} stdlib::info "Fetching ${URL}" stdlib::cmd curl --silent "${URL}" -echo diff --git a/examples/simple_example/main.tf b/examples/simple_example/main.tf index aad9f60..5730da6 100644 --- a/examples/simple_example/main.tf +++ b/examples/simple_example/main.tf @@ -32,7 +32,7 @@ data "google_compute_image" "os" { resource "google_compute_project_metadata" "example" { metadata = { - sudoers = "example_user" + sudoers = "example_user1,example_user2" } } diff --git a/test/integration/simple_example/controls/sudoers.rb b/test/integration/simple_example/controls/sudoers.rb new file mode 100644 index 0000000..ba9fc4b --- /dev/null +++ b/test/integration/simple_example/controls/sudoers.rb @@ -0,0 +1,41 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'retriable' + +control 'simple startup-script-custom' do + title "With the simple example of startup-script-custom calling stdlib::info and stdlib::cmd" + + describe "gcloud ... get-serial-port-output startup-scripts-example1" do + # Avoid racing against the instance boot sequence + before :all do + Retriable.retriable(tries: 20) do + get_serial_port_output = "gcloud compute instances get-serial-port-output startup-scripts-example1" + @cmd = command("#{get_serial_port_output} --project #{attribute('project_id')} --zone #{attribute('region')}-a") + if not %r{systemd: Startup finished}.match(@cmd.stdout) + raise StandardError, "Not found: 'systemd: Startup finished' in console output, cannot proceed" + end + end + end + + subject do + @cmd + end + + its('exit_status') { should be 0 } + its('stdout') { should match(%r{Info \[\d+\]: Adding example_user1 to /etc/sudoers}) } + its('stdout') { should match(%r{Info \[\d+\]: sudoers config valid!}) } + its('stdout') { should match('INFO startup-script: Return code 0.') } + end +end From 9f8e61057c9c6fdf757187944ddcfccd48a01118 Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Mon, 25 Feb 2019 21:27:12 -0500 Subject: [PATCH 09/10] enable_setup_sudoers in the simple_example example --- examples/simple_example/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/simple_example/main.tf b/examples/simple_example/main.tf index 5730da6..769285e 100644 --- a/examples/simple_example/main.tf +++ b/examples/simple_example/main.tf @@ -23,6 +23,7 @@ provider "google" { module "startup-scripts" { source = "../../" + enable_setup_sudoers = true } data "google_compute_image" "os" { From 5b39b3161203559fd349ff028ce35cdb3b3e0a39 Mon Sep 17 00:00:00 2001 From: Eric Malloy Date: Fri, 15 Mar 2019 11:03:48 -0400 Subject: [PATCH 10/10] default enable_setup_init_script to false --- variables.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/variables.tf b/variables.tf index 09baa8e..599b84f 100644 --- a/variables.tf +++ b/variables.tf @@ -25,6 +25,7 @@ variable "enable_get_from_bucket" { variable "enable_setup_init_script" { description = "If not false, include stdlib::setup_init_script() prior to executing startup-script-custom. Call this function to load an init script from GCS into /etc/init.d and initialize it with chkconfig. This function depends on stdlib::get_from_bucket, so this function won't be enabled if enable_get_from_bucket is false." + default = "false" } variable "enable_setup_sudoers" {