Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use AWS secret for chef config overrides #36366

Merged
merged 3 commits into from
Aug 28, 2020
Merged

Conversation

wjordan
Copy link
Contributor

@wjordan wjordan commented Aug 20, 2020

Use AWS secret for chef config overrides

Overview

This PR adds a Secrets Manager secret to the Code.org application CloudFormation stack. The secret is a JSON string containing stack-wide Chef cookbook attributes. The value contained in this secret is passed as an argument to chef-client whenever our system invokes Chef, whether as part of its bootstrapping process or through a CI build.

Background

This AWS secret providing chef-config attributes will help transition existing cookbooks comprising our application's provisioning process from hosted to local-mode Chef. Specifically, the Chef Environment Attributes and Role Attributes currently stored in Hosted Chef Server will be manually merged and stored in this secret, then eventually deleted from the Hosted Chef resources.

This is the last part of our Chef provisioning depending on state stored in the Hosted Chef Server repo.

Implementation details

  • The SecretsManager::Secret resource's logical id is ChefConfig, and the secret name is CfnStack/[StackName]/chef.
  • To ensure the secret can be passed as an argument to chef-client whenever our system runs Chef, a new shell script is written to Cdo::CloudFormation::CdoApp::CHEF_CLIENT_BIN (a constant equal to '/usr/local/bin/chef-client') which fetches the secret (via aws secretsmanager get-secret-value) and passes it to chef-client (via the --json-attributes option). Existing calls to /opt/chef/bin/chef-client in our CI build scripts have been updated to call this helper script instead.
  • Our bootstrap also sets a number of Chef attributes through a first-boot.json file it creates. In order to ensure these first-boot attributes are applied in addition to the secret attributes, a separate call to chef-client with an empty runlist is now done first to persist the first-boot attributes to the newly-created Chef node.

Test Story

  • Deployed a new adhoc instance and verified it provisions correctly
  • Modified a Chef attribute within the created secret on the adhoc stack
  • Manually ran an additional CI build on the adhoc instance and verified it ran correctly and used the Chef attribute from the updated secret

Migration Plan

  • Merge this PR which will update the bootstrap scripts and create an empty secret in each application-stack (each infrastructure-managed environment)
    • Verify deploy without issue
  • Copy attributes from Hosted Chef resources to secret (merging Environment-specific attributes with the baseline role attributes for each stack/environment)
    • Verify deploy without issue
  • Remove attributes from Hosted Chef resources
    • Verify deploy without issue

@uponthesun uponthesun self-requested a review August 20, 2020 03:59
@wjordan wjordan force-pushed the chef_config_aws_secret branch 2 times, most recently from fbd5196 to 1999a17 Compare August 20, 2020 21:04
@wjordan
Copy link
Contributor Author

wjordan commented Aug 21, 2020

Realized that we'll need to update these attributes before both calls to chef-client in ci.rake as well:

RakeUtils.sudo '/opt/chef/bin/chef-client --chef-license accept-silent'
command = 'sudo /opt/chef/bin/chef-client --chef-license accept-silent'

This could make things quite a bit messier and harder to maintain- I might spend a bit more time rethinking the approach to see if there are any simpler alternatives.

Add a Secrets Manager secret to CloudFormation stack,
containing stack-wide Chef cookbook attributes.
Pass attributes as argument to chef-client in bootstrap and CI.
@wjordan wjordan marked this pull request as ready for review August 27, 2020 00:37
@wjordan wjordan requested a review from sureshc August 27, 2020 00:37
Properties:
Description: !Sub "Custom Chef attributes for ${AWS::StackName} CloudFormation stack"
Name: !Sub "CfnStack/${AWS::StackName}/chef"
SecretString: '{}'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to mention that we manage the values stored in this Secret manually (outside of this CloudFormation template).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4155489.

Copy link

@uponthesun uponthesun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking that I understand the migration plan - with these changes, chef attributes will be set both from Secrets Manager (via --json-attributes) and from the Hosted Chef server. In the middle step, where they'll be set in both places, the values from Secrets will override the ones from Hosted Chef (though it won't really matter because the values should be identical in both places.) Is this right?

sudo -u ubuntu bash -c "aws s3 cp <%=bootstrap_script_path%> - | sudo bash -s -- $OPTIONS"
[ $? -eq 0 ] && STATUS=SUCCESS || STATUS=<%=daemon ? 'SUCCESS' : 'FAILURE'%>

# Bash script that invokes chef-client with fetched config attributes and provided run-list.
CHEF_CLIENT_BIN=<%=CHEF_CLIENT_BIN%>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine to me, but I was curious on your thoughts on inlining this here, rather than uploading to S3 like we do for chef-bootstrap.sh. (Why isn't chef-bootstrap.sh just part of this script, anyway? Is it just to break up this script into smaller chunks?)

Copy link
Contributor Author

@wjordan wjordan Aug 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was curious on your thoughts on inlining this here, rather than uploading to S3 like we do for chef-bootstrap.sh.

The parameterized arguments ${Chefconfig} (which is a reference to the CFn resource) and $RUN_LIST (which is a reference to the run_list option passed from the stack ERB including this script) make it more complicated to render this script to S3 at compile-time like we do chef-bootstrap.sh, especially since this script is being invoked from the CI-build as well, not just from this bootstrap script.

Why isn't chef-bootstrap.sh just part of this script, anyway? Is it just to break up this script into smaller chunks?

chef-bootstrap.sh was an experiment with deploying a self-contained bash script partly to break apart the bootstrap script as you guessed, but also because I thought that its purpose was narrow/isolated enough that it could be written as a standalone script, manually deployed once to a static location and touched very rarely after that.

That experiment turned out to be challenging as soon as it became a production-build dependency: deploying any meaningful change to the script required either careful backwards-compatibility with each change, and/or synchronization across all environments to prevent breaking the production workflow. So to avoid these issues (without setting up a whole more elaborate versioning, packaging and deployment system for such a tiny thing), I made a change to deploy it to a separate path per stack. So now it's basically tied to the stack anyway, contrary to the original design. It could be in-lined at this point to reduce the overall complexity (with some additional work).

@@ -24,6 +24,7 @@ class CdoApp < StackTemplate
end

# Hard-coded constants and default values.
CHEF_CLIENT_BIN = '/usr/local/bin/chef-client'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name both the variable and the file itself something that indicates it's not just another copy of chef-client? It's clear from this PR, but confusing if you see it in isolation. Maybe something like chef-client-with-secrets?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about chef-cdo-app, to make it clear it's invoking chef to provision cdo-app (see 60b476c).

Update this secret manually after the stack is created to customize Chef configuration.
The value should never be changed in the stack itself, since it would clobber all other changes.
@wjordan
Copy link
Contributor Author

wjordan commented Aug 28, 2020

(CI unit-test failure seems unrelated..)

@wjordan
Copy link
Contributor Author

wjordan commented Aug 28, 2020

Just checking that I understand the migration plan - with these changes, chef attributes will be set both from Secrets Manager (via --json-attributes) and from the Hosted Chef server. In the middle step, where they'll be set in both places, the values from Secrets will override the ones from Hosted Chef (though it won't really matter because the values should be identical in both places.) Is this right?

Yes, this is correct!

@wjordan wjordan merged commit 5ec2a4d into staging Aug 28, 2020
@wjordan wjordan deleted the chef_config_aws_secret branch August 28, 2020 17:14
wjordan added a commit that referenced this pull request Aug 28, 2020
Fixes constant not found error.
Followup to #36366.
wjordan added a commit that referenced this pull request Aug 28, 2020
Partial revert of #36366.
The script needs to be created on existing environments
by rebooting existing instances before the path can be safely updated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants