<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Managing AWS Credentials on CI/CD Series</title><link>https://www.pulumi.com/blog/series/aws-credentials-cicd/</link><description>Pulumi blog posts: Managing AWS Credentials on CI/CD Series.</description><language>en-us</language><pubDate>Thu, 14 May 2020 00:00:00 +0000</pubDate><item><title>Managing AWS Credentials on CI/CD: Part 3</title><link>https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-3/</link><pubDate>Thu, 14 May 2020 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-3/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/managing-aws-credentials-on-cicd-part-3/index.png" /&gt;
&lt;p&gt;This article is the third part of a series on best practices for securely managing AWS credentials on CI/CD. In this article, we cover the
last leg of the continuous delivery process to update your AWS resources and how to store sensitive data using Pulumi securely.&lt;/p&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;In &lt;a href="https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-1/"&gt;part 1&lt;/a&gt;, we created a dedicated IAM User with
limited privileges. Then in &lt;a href="https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-2/"&gt;part 2&lt;/a&gt;, we set up a simple,
serverless Pulumi program that periodically rotated the User&amp;rsquo;s access keys and updated the CI/CD system.&lt;/p&gt;
&lt;p&gt;In this final post, we will &lt;em&gt;use&lt;/em&gt; those AWS credentials to update cloud resources as part of
a CI/CD workflow using Pulumi.&lt;/p&gt;
&lt;p&gt;We will cover AWS&amp;rsquo;s &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html"&gt;IAM Roles&lt;/a&gt; and how they can be
used to safely manage the access Pulumi has to your AWS account. We will also cover how sensitive data in your
stack is stored within the Pulumi Service.&lt;/p&gt;
&lt;h2 id="assuming-iam-roles"&gt;Assuming IAM Roles for Performing Updates&lt;/h2&gt;
&lt;p&gt;In the previous post, we automatically create and rotate the AWS access keys for an IAM User. So it seems natural to
think that we should just use that same AWS access key to gain access to AWS resources. However, using the
access keys for an IAM User has a few major drawbacks.&lt;/p&gt;
&lt;p&gt;First, those access keys are long-lived. If we didn&amp;rsquo;t create that Pulumi program to rotate them automatically, the
key would last forever. (Meaning that a bad actor, perhaps elsewhere, or far in the future, could take advantage
of a compromised key.) Second, that access key could be used to do anything that the IAM User could. So you cannot
differentiate the set of capabilities based on the task at hand. (For example, if updating the Pulumi stack &lt;code&gt;website/staging&lt;/code&gt;,
we might need a different set of permissions than if we were updating the stack &lt;code&gt;website/production&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;The preferred alternative to using IAM User access keys is to &amp;ldquo;assume&amp;rdquo; an IAM Role. An IAM Role is similar to an IAM
User in that it grants a set of &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html"&gt;policies and permissions&lt;/a&gt;.
However, unlike an IAM User, an IAM Role does not have a password, nor can it be added to a Group and inherit any policies.
Also, the credentials for using an IAM Role are always short-lived. (Lasting no more than 12 hours, and likely much, much less.)&lt;/p&gt;
&lt;p&gt;Instead, an IAM Role defines a set of capabilities that can be performed by any principal capable of assuming it. So
in other words, you can exchange your limited access IAM User&amp;rsquo;s credentials for a specialized IAM Role. The IAM Role then
can be specifically tailored to a specific— wait for it— &lt;em&gt;role&lt;/em&gt; to perform.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t worry if that doesn&amp;rsquo;t all make sense at first; let&amp;rsquo;s look at a concrete example.&lt;/p&gt;
&lt;h3 id="desired-end-state"&gt;Desired End State&lt;/h3&gt;
&lt;p&gt;In our situation, we want to create an IAM Role &lt;code&gt;WebsiteStackUpdaterRole&lt;/code&gt; that grants permissions necessary for
Pulumi to update the &lt;code&gt;website/production&lt;/code&gt; stack. We will custom tailor this role to only grant access to the set of resources
and operations we know the stack needs.&lt;/p&gt;
&lt;p&gt;We also want to restrict who can assume the &lt;code&gt;WebsiteStackUpdaterRole&lt;/code&gt;, so that only specific users/accounts can access it.
e.g., the IAM User &lt;code&gt;cicd-bot&lt;/code&gt; but not IAM User &lt;code&gt;dwight-over-in-sales&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And just to show a more real-world example, we&amp;rsquo;ll do this using &lt;em&gt;multiple&lt;/em&gt; AWS accounts, where the &lt;code&gt;website/production&lt;/code&gt; stack&amp;rsquo;s resources are housed in a &amp;ldquo;production&amp;rdquo; AWS account, and the &lt;code&gt;cicd-bot&lt;/code&gt; IAM User is defined in a
&lt;a href="https://blog.coinbase.com/you-need-more-than-one-aws-account-aws-bastions-and-assume-role-23946c6dfde3"&gt;bastion AWS account&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="creating-the-iam-role"&gt;Creating the IAM Role&lt;/h3&gt;
&lt;p&gt;The following snippet creates an &lt;a href="https://www.pulumi.com/registry/packages/aws/api-docs/iam/role/"&gt;IAM Role&lt;/a&gt; resource using Pulumi. The
most important input property of which is the &lt;code&gt;assumeRolePolicy&lt;/code&gt; document, which defines &lt;em&gt;who&lt;/em&gt; can assume this role.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a situation where Pulumi&amp;rsquo;s ability to declare the policy document in code is super-useful, as it makes it
much easier to reference other resources (e.g., another AWS account&amp;rsquo;s ID) as well as supporting inline comments.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// IAM Role used by cicd-bot to update the website stack.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;websiteStackUpdaterRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;WebsiteStackUpdaterRole&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;WebsiteStackUpdaterRole&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Broad access for updating Pulumi stacks in the &amp;#39;production&amp;#39; AWS account.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;maxSessionDuration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 30 minutes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;assumeRolePolicy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Statement&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Principal&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// We grant the &amp;#34;bastion account&amp;#34;, which defines IAM Users the ability to assume this role.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// The IAM Policies in that other AWS account will then determine which users, groups, etc.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// will be able to use the `sts:AssumeRole` action on this IAM Role.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;AWS&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`arn:aws:iam::&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;awsAccountIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bastionAccount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:root`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// But as an additional precaution against misconfiguration in the bastion account, we add
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// some additional checks to determine who can access this policy.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Condition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ForAllValues:StringEquals&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;aws:PrincipalTag/access-to-production&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sts:ExternalId&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;contoso/website/production&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;contoso/website/staging&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sts:AssumeRole&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a lot packed into that IAM Role&amp;rsquo;s configuration.&lt;/p&gt;
&lt;p&gt;First, the &amp;ldquo;principal&amp;rdquo; (who can assume the role) references a specific AWS account. This is an example of how you
can associate one AWS account with another. (And is best practice for isolating data between different departments, etc.)
We&amp;rsquo;ll cover how to configure the AWS bastion account side to use this role later.&lt;/p&gt;
&lt;p&gt;The next thing to call out is the use of the &lt;code&gt;Condition&lt;/code&gt; element of the policy document. To assume the role,
we require two checks to be performed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;That the principal assuming the role (e.g., IAM User) has the tag &lt;code&gt;&amp;quot;access-to-production&amp;quot;&lt;/code&gt; with value &lt;code&gt;&amp;quot;true.&amp;quot;&lt;/code&gt; This way,
we will reject any IAM User in the bastion account who isn&amp;rsquo;t tagged with &lt;code&gt;&amp;quot;access-to-production&amp;quot;&lt;/code&gt; from assuming this role.&lt;/li&gt;
&lt;li&gt;We also verify that the &amp;ldquo;external ID&amp;rdquo; (an optional parameter passed when assuming an IAM Role) matches a well-known Pulumi
stack name. This way, we can double-check that the entity using the IAM Role is doing so to use a specific stack.
(This is less of a security measure and more of a general hygiene type requirement.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="iam-policy-document"&gt;IAM Policy Document&lt;/h3&gt;
&lt;p&gt;Now we have an IAM Role; we need to attach a policy document that defines what permissions are granted.&lt;/p&gt;
&lt;p&gt;This is the &lt;strong&gt;most important step&lt;/strong&gt; since it defines precisely what the CI/CD bot can able to do.&lt;/p&gt;
&lt;p&gt;If we define the policy too broadly, then it is a general risk. For example, what if a compromised NPM package in your
Pulumi program did something nefarious. If the policy is defined too narrowly, then your &lt;code&gt;pulumi up&lt;/code&gt; might fail because
the necessary permissions weren&amp;rsquo;t available. When in doubt, err on the side of having a more restrictive policy.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It can be advantageous to have an identical &amp;ldquo;staging&amp;rdquo; and &amp;ldquo;production&amp;rdquo; instance of the same Pulumi stack,
so you can detect any permission issues before you update the user-facing production stack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It can take some time to winnow down the specific set of permissions necessary since that is ultimately specific to
the Pulumi program. For example, if the stack governs the networking configuration, then you can simply not deny
to the &lt;code&gt;s3:*&lt;/code&gt; actions.&lt;/p&gt;
&lt;p&gt;Unfortunately, Pulumi doesn&amp;rsquo;t currently support generating a reasonable IAM Policy document for your stack. For a
good introduction to some of the &amp;ldquo;worst-case scenarios&amp;rdquo; that could arise from a misconfigured IAM Policy,
see &lt;a href="https://know.bishopfox.com/blog/privilege-escalation-in-aws"&gt;this blog post from BishopFox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following is a hypothetical IAM policy that restricts the operations performed to just those for the
AWS CloudFront and S3 products. (Which might be sufficient for
&lt;a href="https://www.pulumi.com/blog/serving-a-static-website-on-aws-with-pulumi/"&gt;updating a CDN-based website hosted on AWS&lt;/a&gt;, but
likely not much else.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Policy document defining the permissions granted to the WebsiteStackUpdaterRole.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;websiteUpdaterPolicyDocument&lt;/span&gt;: &lt;span class="kt"&gt;aws.iam.PolicyDocument&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Statement&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Grant access to all AWS S3 actions, but scoped to just the specific S3 buckets
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// used for the given Pulumi stack.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Sid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GrantS3Access&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;s3:*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Content bucket.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::website-bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::website-bucket/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// S3 bucket which stores the access logs.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::website-logs-bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::website-logs-bucket/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Grant access to update a CloudFront distribution. For more information, see:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/access-control-managing-permissions.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Sid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GrantCloudFrontAccess&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudfront:CreateDistribution&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudfront:DeleteDistribution&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudfront:GetDistribution&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudfront:GetDistributionConfig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudfront:ListDistributions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudfront:UpdateDistribution&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudfront:ListCloudFrontOriginAccessIdentities&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// The specific CloudFront distribution used in the stack.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;arn:aws:cloudfront::0123456789:distribution/A123B1CABWZZ&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;websiteUpdaterPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;WebsiteUpdaterRolePolicy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;WebsiteUpdaterRolePolicy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Policy granting the permissions needed for updating the website/production Pulumi stack&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;: &lt;span class="kt"&gt;websiteUpdaterPolicyDocument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;resourceOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To actually associate the IAM Policy document with the new IAM Role, we need to create a
&lt;a href="https://www.pulumi.com/registry/packages/aws/api-docs/iam/policyattachment/#policyattachment"&gt;PolicyAttachment&lt;/a&gt;
associating the two.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rolePolicyAttachment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RolePolicyAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;WebsiteStackUpdaterRolePolicyAttachment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;role&lt;/span&gt;: &lt;span class="kt"&gt;websiteStackUpdaterRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;policyArn&lt;/span&gt;: &lt;span class="kt"&gt;websiteUpdaterPolicy.arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Reminder, we defined the IAM Role in our production AWS account, which allows any principal in the AWS
bastion account (with the right tag, external ID) to assume that role. We then need to update the bastion
account to grant the &lt;code&gt;sts:AssumeRole&lt;/code&gt; action on that IAM Role so it can be used! That isn&amp;rsquo;t covered in this post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="assuming-the-iam-role"&gt;Assuming the IAM Role&lt;/h3&gt;
&lt;p&gt;Finally! With everyone in place, we can now &lt;em&gt;use&lt;/em&gt; the IAM Role.&lt;/p&gt;
&lt;p&gt;The following is a set of commands you can run on your CI/CD worker to exchange the low-privilege IAM User
credentials for the specific IAM Role we just created in the production AWS account. But there are many
alternatives for programmatically assuming an IAM Role, such as using the
&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html"&gt;&lt;code&gt;AWS_PROFILE&lt;/code&gt; environment variable&lt;/a&gt;)
or the &lt;a href="https://github.com/coinbase/assume-role"&gt;&lt;code&gt;assume-role&lt;/code&gt;&lt;/a&gt; tool.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Use the AWS credentials passed to the CI system and available as environment variables.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# These are the low-privilege credentials that automatically get rotated.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws configure &lt;span class="nb"&gt;set&lt;/span&gt; aws_access_key_id &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws configure &lt;span class="nb"&gt;set&lt;/span&gt; aws_secret_access_key &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws configure &lt;span class="nb"&gt;set&lt;/span&gt; region &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;us&lt;/span&gt;&lt;span class="p"&gt;-west-2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Assume the desired IAM Role. Note how we pass the stack name in the --external-id parameter,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# as required to assume the role.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;readonly&lt;/span&gt; &lt;span class="nv"&gt;CREDS_JSON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;aws sts assume-role &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --role-arn &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARN_OF_IAM_ROLE_TO_ASSUME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --role-session-name &lt;span class="s2"&gt;&amp;#34;&amp;lt;useful description of the context, such as CI/CD job ID&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --external-id &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_STACK&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# The `aws sts assume-role` command just returns a JSON blob. So we now tease out the actual&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# access key and set the corresponding environment variables.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CREDS_JSON&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s2"&gt;&amp;#34;.Credentials.AccessKeyId&amp;#34;&lt;/span&gt; --raw-output&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CREDS_JSON&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s2"&gt;&amp;#34;.Credentials.SecretAccessKey&amp;#34;&lt;/span&gt; --raw-output&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AWS_SESSION_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CREDS_JSON&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s2"&gt;&amp;#34;.Credentials.SessionToken&amp;#34;&lt;/span&gt; --raw-output&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If everything worked correctly, then running the get-caller-identity command will refer to&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the IAM Role and not our IAM User.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Assumed AWS identity:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sts get-caller-identity
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="secrets-in-pulumi"&gt;Storing Sensitive Data using Pulumi&lt;/h2&gt;
&lt;p&gt;As a developer, the most critical security threat is access to your cloud account since that’s ultimately where your or your customer’s data is stored. But beyond that, there are places where Pulumi stores sensitive data.&lt;/p&gt;
&lt;p&gt;Pulumi goes to great lengths to safeguard your data. Here we review where Pulumi interacts with your sensitive data and what you can do to safeguard it better.&lt;/p&gt;
&lt;h3 id="secrets-in-configuration"&gt;Secrets in Configuration&lt;/h3&gt;
&lt;p&gt;A Pulumi stack can have configuration data associated with it, which allows you to parameterize
cloud resources. For example, a &lt;code&gt;machine-size&lt;/code&gt; configuration key might determine which class of
virtual machine to use. A dev stack might only need a &lt;code&gt;&amp;quot;t2.small&amp;quot;&lt;/code&gt; but your production instance
might need something more powerful.&lt;/p&gt;
&lt;p&gt;Configuration data is stored in a file like &lt;code&gt;Pulumi.website-production.yaml&lt;/code&gt; should be checked in
along with the source code for a stack, so that builds and stack updates are reproducible.&lt;/p&gt;
&lt;p&gt;However, what if those configuration settings contain secrets? Like you need to store an API key
to use a 3rd party API.&lt;/p&gt;
&lt;p&gt;Pulumi supports &lt;a href="https://www.pulumi.com/docs/concepts/config/#secrets"&gt;encrypting sensitive configuration data&lt;/a&gt;.
You just need to add the &lt;code&gt;--secret&lt;/code&gt; flag.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Securely add an API key to the stack&amp;#39;s configuration.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi config &lt;span class="nb"&gt;set&lt;/span&gt; api-key &lt;span class="s2"&gt;&amp;#34;hunter2&amp;#34;&lt;/span&gt; --secret
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For stacks hosted on the &lt;a href="https://app.pulumi.com/signin"&gt;Pulumi Service&lt;/a&gt;, the default is that your configuration
data is encrypted using a key specific to your stack. (So the ciphertext stored in the &lt;code&gt;Pulumi.yaml&lt;/code&gt; file
is safe to check into your source tree, since it cannot be copied/decrypted for another stack.)&lt;/p&gt;
&lt;h3 id="secrets-in-checkpoint-files"&gt;Secrets in Checkpoint Files&lt;/h3&gt;
&lt;p&gt;Pulumi keeps track of your cloud resources in a something called a &lt;a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/"&gt;checkpoint file&lt;/a&gt;,
and that too might contain sensitive information. For example, a Pulumi resource might have a &lt;code&gt;&amp;quot;password&amp;quot;&lt;/code&gt; output property.&lt;/p&gt;
&lt;p&gt;Pulumi &lt;a href="https://www.pulumi.com/docs/concepts/resources#additionalsecretoutputs"&gt;has support&lt;/a&gt; to mark that resource
output as &amp;ldquo;secret&amp;rdquo; and make sure that it is encrypted within the checkpoint file. (So if you were to look at the checkpoint file
contents via &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_export"&gt;&lt;code&gt;pulumi stack export&lt;/code&gt;&lt;/a&gt;, you would not be able to recover
the data.&lt;/p&gt;
&lt;p&gt;Just like for secret configuration values, the default for stacks hosted on the Pulumi Service is to encrypt
this data using a key that Pulumi manages and is specific to your stack.&lt;/p&gt;
&lt;h3 id="custom-secrets-providers"&gt;Custom Secrets Providers&lt;/h3&gt;
&lt;p&gt;We at Pulumi take great care in safeguarding your data, especially configuration or checkpoint data that is marked as sensitive.
However, you might feel more comfortable if your stack&amp;rsquo;s data were encrypted using a key that &lt;em&gt;you&lt;/em&gt; controlled. (So even if the
data stored in the Pulumi Service were available, it would be useless without your specific key.)&lt;/p&gt;
&lt;p&gt;If that&amp;rsquo;s the case, then you can use a configurable secrets provider, and swap out the default &amp;ldquo;Pulumi Service managed&amp;rdquo; encryption
scheme for your own. (And we won&amp;rsquo;t take it personally, promise.)&lt;/p&gt;
&lt;p&gt;When you create a new stack using &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_init/"&gt;pulumi stack init&lt;/a&gt;, you can optionally
specify a &lt;code&gt;--secrets-provider&lt;/code&gt; flag. That will determine where and how secrets get managed on your stack.&lt;/p&gt;
&lt;p&gt;For example, to use your own KMS key for encrypting data, you can pass the secrets provider
&lt;code&gt;&amp;quot;awskms://alias/ExampleAlias?region=us-east-1&amp;quot;&lt;/code&gt;. The Pulumi command-line will work and behave the same. Except that
whenever it needs to encrypt or decrypt some data, it will refer to the custom provider.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As you would expect, if you do use a custom secrets provider and lose access to your encryption key, then there is
no way to recover the encrypted data stored on your stack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For more information on custom secret providers, see
&lt;a href="https://www.pulumi.com/blog/peace-of-mind-with-cloud-secret-providers/"&gt;Peace of Mind with Cloud Secret Providers&lt;/a&gt; or
&lt;a href="https://www.pulumi.com/blog/managing-secrets-with-pulumi/"&gt;Managing Secrets with Pulumi&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Securely managing any sensitive data is challenging to get right, especially if there are many moving parts, such as when
coordinating access between your AWS account, CI/CD provider, and your Pulumi stack.&lt;/p&gt;
&lt;p&gt;In this blog series, we walked through the best practices in creating, securing, and managing access to an AWS account using
IAM Users and Roles. We then reviewed the specifics of where and how Pulumi stores sensitive data, in case you want to take
more control over your data.&lt;/p&gt;
&lt;p&gt;If you have any other questions, corrections, or other ideas you&amp;rsquo;d like to discuss about Pulumi, Continuous Delivery, or
security, please join the conversation over on the &lt;a href="https://slack.pulumi.com"&gt;Pulumi Community Slack&lt;/a&gt;.&lt;/p&gt;</description><author>Chris Smith</author><author>Sophia Parafina</author><category>continuous-delivery</category><category>security</category><category>secrets</category></item><item><title>Managing AWS Credentials on CI/CD: Part 2</title><link>https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-2/</link><pubDate>Thu, 26 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-2/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/managing-aws-credentials-on-cicd-part-2/index.png" /&gt;
&lt;p&gt;This article is the second part of a series on best practices for securely managing AWS credentials on CI/CD. In this article, we go in-depth on providing AWS credentials securely to a 3rd party and introduce a Pulumi program to automate rotating access keys.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; These recommendations do not apply if you are running a CI/CD system within your
AWS account, e.g., running a Jenkins server on EC2 or using &lt;a href="https://aws.amazon.com/codedeploy/"&gt;AWS CodeDeploy&lt;/a&gt;.
In those cases, please refer to AWS&amp;rsquo;s documentation for how to
&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html"&gt;assume IAM Roles when running on an EC2 instance&lt;/a&gt;
instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="providing-iam-credentials"&gt;Provide IAM credentials to your CI/CD system&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-1/"&gt;first post&lt;/a&gt; in our series, we created a dedicated IAM User to perform updates to AWS resources within your CI/CD system. The next step is to pass the AWS access keys for that user to your CI/CD system.&lt;/p&gt;
&lt;p&gt;We need to take great caution. &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"&gt;AWS&amp;rsquo;s documentation&lt;/a&gt; states, &lt;strong&gt;&amp;ldquo;Do not provide your access keys to a third party&amp;rdquo;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Providing AWS access keys to any system is dangerous because of the risk that a bad actor could obtain those keys and do something nefarious. Even if your CI/CD service takes great care to protect your secrets, those AWS credentials could be inadvertently exposed in debugging output, system logs, or in some other way.&lt;/p&gt;
&lt;p&gt;This is one of the main advantages of performing continuous deployment from within your AWS account. For example, running Jenkins on an EC2 instance you manage, or using the AWS CodeDeploy service. Because when performing a deployment from within an AWS compute environment, you can use built-in mechanisms to obtain credentials securely.&lt;/p&gt;
&lt;p&gt;However, there are many reasons to use a hosted service for performing your CI/CD, such as developer productivity, ease of use, performance, or simply because you don&amp;rsquo;t want to recreate your existing deployment workflows.&lt;/p&gt;
&lt;p&gt;Ultimately, it is essential to understand the risks involved and how to mitigate them.&lt;/p&gt;
&lt;h2 id="properly-storing-credentials"&gt;Properly Storing Credentials&lt;/h2&gt;
&lt;p&gt;If you choose to provide your CI/CD with credentials, the most important thing is to mark them as secret. Your CI/CD provider typically has built-in support for handling &amp;ldquo;secure variables&amp;rdquo; or other sensitive information. As opposed to general, configuration data or environment variables.&lt;/p&gt;
&lt;p&gt;User-supplied configuration values are typically write-only and only accessible by the CI/CD worker jobs at runtime. For example, Travis CI has &lt;a href="https://circleci.com/docs/2.0/env-vars/#overview"&gt;encrypted environment variables&lt;/a&gt; or &lt;a href="https://circleci.com/docs/2.0/env-vars/#overview"&gt;secure environment variables&lt;/a&gt; in CircleCI.&lt;/p&gt;
&lt;p&gt;Please consult your CI/CD provider&amp;rsquo;s documentation for how to pass and store sensitive information appropriately.&lt;/p&gt;
&lt;h3 id="using-a-secrets-service"&gt;Why not use a &amp;ldquo;secrets manager&amp;rdquo; service?&lt;/h3&gt;
&lt;p&gt;A common question when discussing is how to pass access keys to a CI/CD system securely:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rather than giving your CI/CD provider AWS credentials, why not have your CI/CD system obtain credentials from a specialized &amp;ldquo;secrets manager&amp;rdquo; service?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, if you choose &lt;em&gt;not&lt;/em&gt; to trust your CI/CD system with this data, can you &lt;em&gt;instead&lt;/em&gt; trust some other system dedicated for securely storing and retrieving sensitive information?&lt;/p&gt;
&lt;p&gt;It sounds like a good idea on the surface but doesn&amp;rsquo;t seem to make your data any more secure.&lt;/p&gt;
&lt;p&gt;Abstractly, the difference here is that the credentials are provided on-demand, rather than being available to the CI/CD job when it starts (and stored via the CI/CD provider). Instead, your CI/CD job would obtain credentials from the &amp;ldquo;secrets manager&amp;rdquo; only when needed.&lt;/p&gt;
&lt;p&gt;There are some advantages to this approach, such as providing a clear audit trail for access and more control over the distribution of sensitive information.&lt;/p&gt;
&lt;p&gt;However, the secrets provider system needs to be presented with some form of credentials. And &lt;em&gt;those&lt;/em&gt; credentials need to be available to your CI/CD environment. So using a secrets manager leaves you in the same place you started, i.e., needing to provide sensitive data to your CI/CD provider.&lt;/p&gt;
&lt;p&gt;Also, by adding a dependency on a secrets manager, you introduce additional risks. Not only do you need to be even more security-conscious about that secrets manager, but it also needs to be highly available. Any outage for that service would mean that you would be unable to perform deployments!&lt;/p&gt;
&lt;p&gt;So it does not seem that using a secrets manager to dole out AWS credentials to your CI/CD system is a good practice to follow. Or, at the very least, makes some tradeoffs without fundamentally making your approach to CI/CD any more or less secure.&lt;/p&gt;
&lt;p&gt;Instead, it is better to focus on other ways to reduce the risk if those AWS credentials do get exposed.&lt;/p&gt;
&lt;h2 id="automating-key-rotation"&gt;Automate Rotating and Revoking AWS Credentials&lt;/h2&gt;
&lt;p&gt;When providing credentials to a 3rd party, rather than hoping it will be 100% secure forever (which is impossible), we can make those credentials &lt;em&gt;volatile&lt;/em&gt;. If we regularly invalidate and rotate the credentials supplied to the CI/CD system, we can dramatically reduce the impact of any accidental disclosure. Even if the IAM User&amp;rsquo;s credentials were leaked, by the time they were discovered and used, they would no longer be valid.&lt;/p&gt;
&lt;p&gt;The AWS security blog describes how to &lt;a href="https://aws.amazon.com/blogs/security/how-to-rotate-access-keys-for-iam-users/"&gt;rotate access keys for IAM Users&lt;/a&gt;, by using the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a second access key in addition to the one in use.&lt;/li&gt;
&lt;li&gt;Update all your applications to use the new access key.&lt;/li&gt;
&lt;li&gt;Change the state of the previous access key to inactive.&lt;/li&gt;
&lt;li&gt;Verify everything is still working as expected.&lt;/li&gt;
&lt;li&gt;Delete the inactive access key.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That all sounds simple enough, but it&amp;rsquo;s certainly tedious. And if you need to repeat the process across dozens, if not hundreds of different CI/CD pipelines, you need a better solution. Fortunately, Pulumi provides an excellent and extensible way for writing a serverless program to
automate rotating credentials.&lt;/p&gt;
&lt;p&gt;The next few sections describe a simple infrastructure application for automating AWS IAM credential rotation. You can see the full application on GitHub at
&lt;a href="https://github.com/chrsmith/pulumi-aws-travis-cicd-demo"&gt;chrsmith/pulumi-aws-travis-cicd-demo&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="aws-credential-rotator-9000"&gt;AWS Credential Rotator 9000&lt;/h3&gt;
&lt;p&gt;The affectionally titled &lt;em&gt;AWS Credential Rotator 9000&lt;/em&gt; is a simple, serverless Pulumi application for rotating AWS access keys.&lt;/p&gt;
&lt;p&gt;It creates an AWS lambda function that&amp;rsquo;s triggered on a regular schedule, e.g., every 30 minutes, and performs the next step in the sequence for rotating access keys as outlined above.&lt;/p&gt;
&lt;p&gt;First, it creates a new access key and pushes the new value out. On the next iteration, it marks the older access key as &amp;ldquo;inactive.&amp;rdquo; On the following iteration, the inactive key is deleted. The process repeats, generating a new key and removing the inactive key.&lt;/p&gt;
&lt;h3 id="periodically-invoking-an-aws-lambda"&gt;Periodically Invoking an AWS Lambda&lt;/h3&gt;
&lt;p&gt;The heart of the application is triggering it to execute on a fixed interval. Thankfully this is super-easy to do using Pulumi since it allows you to seamlessly blend your &amp;ldquo;cloud infrastructure&amp;rdquo; with &amp;ldquo;code&amp;rdquo; in a natural way. The user guide for Pulumi Crosswalk for AWS has more information on &lt;a href="https://www.pulumi.com/docs/iac/clouds/aws/guides/lambda/"&gt;serverless eventing&lt;/a&gt;
if you would like to learn more.&lt;/p&gt;
&lt;p&gt;The following snippet is the core part of the credential rotator app. We define a function to handle the logic of key rotation in &lt;code&gt;rotateIAMUserKeys&lt;/code&gt;. Then we create an AWS Lambda resource
named &lt;code&gt;lambda&lt;/code&gt;. Finally, the &lt;code&gt;triggerSchedule&lt;/code&gt; resource invokes our lambda on a fixed schedule, thereby ensuring that the key rotation process goes on indefinitely. You can see the full code
&lt;a href="https://github.com/chrsmith/pulumi-aws-travis-cicd-demo/blob/master/infrastructure/key-rotator/iam-key-rotator.ts"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;rotateIAMUserKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iamUser&lt;/span&gt;: &lt;span class="kt"&gt;aws.iam.User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onNewCredentials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;aws.cloudwatch.EventRuleEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;keyRotatorLambda&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rotateIAMUserKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userToRotate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newKey&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newSecret&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;onNewCredentials callback called!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`A new AWS access key &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;newKey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#34; has been created.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;role&lt;/span&gt;: &lt;span class="kt"&gt;lambdaRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nodejs10.x&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;triggerSchedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onSchedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;keyRotatorScheduler&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rate(1 hour)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lambdaCallbackFn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When writing reusable infrastructure components in Pulumi however, it is helpful to organize things into a &lt;a href="https://www.pulumi.com/docs/concepts/resources#custom-resources"&gt;custom resource&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, we can bundle together the AWS Lambda, CloudWatcn schedule, and the associated IAM policies into a single conceptual resource &lt;code&gt;AccessKeyRotator&lt;/code&gt;. Bundling resources allows for the code reuse.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rotator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AccessKeyRotator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rotator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;: &lt;span class="kt"&gt;config.require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;user&lt;/span&gt;: &lt;span class="kt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;credentialPusher&lt;/span&gt;: &lt;span class="kt"&gt;demoTravisCIPusher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="pushing-credentials"&gt;Pushing Credentials&lt;/h3&gt;
&lt;p&gt;With the mechanics of updating an AWS access key out of the way, the next step is to notify dependent systems what the new access key should be. The key rotator app has a &lt;code&gt;CredentialPusher&lt;/code&gt; abstraction to provide a pluggable way for you to send credentials to where they need to be. The example on GitHub only supports updating a Travis CI project (See &lt;a href="https://github.com/chrsmith/pulumi-aws-travis-cicd-demo/blob/master/infrastructure/key-rotator/credential-pusher-travis.ts"&gt;&lt;code&gt;credential-pusher-travis.ts&lt;/code&gt;&lt;/a&gt;.), but it could be easily extended to support other CI/CD
systems.&lt;/p&gt;
&lt;p&gt;The following snippet shows how the &lt;code&gt;CredentialPusher&lt;/code&gt; is configured in the key rotator app. We load a Travis CI access token from the
&lt;a href="https://www.pulumi.com/docs/concepts/config/"&gt;Pulumi stack&amp;rsquo;s configuration&lt;/a&gt;, and hard-code the specific set of projects and encrypted environment variables to
store the new access key.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// A &amp;#34;credential pusher&amp;#34; is the component that pushes new AWS IAM credentials out to 3rd parties
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// as the older ones get rotated. For demonstration purposes, this updates the Travis CI settings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// for the chrsmith/pulumi-aws-travis-cicd-demo repo. But you can imagine another implementation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// that pushes the new IAM credentials to GitLab CI, or updating multiple CI/CD pipelines.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;demoTravisCIPusher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CredentialPusher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TravisCIPusher&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;accessKey&lt;/span&gt;: &lt;span class="kt"&gt;config.require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;travis-ci-token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;chrsmith/pulumi-aws-travis-cicd-demo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// In the Travis CI configuration for that GitHub repo, there are two
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// environment variables for storing the AWS credentials. So whenever the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// AWS credentials get rotated, the job&amp;#39;s configuration settings will be
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// updated to reflect the new values.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;accessKeyIDLocation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;AWS_ACCESS_KEY_ID&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;secretAccessKeyLocation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;AWS_SECRET_ACCESS_KEY&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="demo"&gt;Demo&lt;/h3&gt;
&lt;p&gt;To demonstrate the access keys rotation, we can examine the log files generated from AWS Lambda. These can be accessed from the command-line using the &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_logs"&gt;&lt;code&gt;pulumi logs&lt;/code&gt;&lt;/a&gt; command.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a summary of the output for clarity:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; START RequestId: 9406913e-a9d0-46a4-b861-2efdee508b2b Version: &lt;span class="nv"&gt;$LATEST&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9406913e-a9d0-46a4-b861-2efdee508b2b INFO IAM User has &lt;span class="m"&gt;2&lt;/span&gt; keys:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9406913e-a9d0-46a4-b861-2efdee508b2b INFO - AKIASHIVKXX3QEQNZNX5 &lt;span class="o"&gt;[&lt;/span&gt;Active&lt;span class="o"&gt;]&lt;/span&gt; Tue Mar &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt; 12:37:03 GMT+0000 &lt;span class="o"&gt;(&lt;/span&gt;Coordinated Universal Time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9406913e-a9d0-46a4-b861-2efdee508b2b INFO - AKIASHIVKXX3SA2K3ME7 &lt;span class="o"&gt;[&lt;/span&gt;Inactive&lt;span class="o"&gt;]&lt;/span&gt; Tue Mar &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt; 09:37:03 GMT+0000 &lt;span class="o"&gt;(&lt;/span&gt;Coordinated Universal Time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9406913e-a9d0-46a4-b861-2efdee508b2b INFO Deleting older, inactive access key AKIASHIVKXX3SA2K3ME7 &lt;span class="o"&gt;[&lt;/span&gt;Inactive&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9406913e-a9d0-46a4-b861-2efdee508b2b INFO Key rotation step complete.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; END RequestId: 9406913e-a9d0-46a4-b861-2efdee508b2b
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; START RequestId: 05644e4a-81a4-4197-8ad7-fc3517998270 Version: &lt;span class="nv"&gt;$LATEST&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO IAM User has &lt;span class="m"&gt;1&lt;/span&gt; keys:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO - AKIASHIVKXX3QEQNZNX5 &lt;span class="o"&gt;[&lt;/span&gt;Active&lt;span class="o"&gt;]&lt;/span&gt; Tue Mar &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt; 12:37:03 GMT+0000 &lt;span class="o"&gt;(&lt;/span&gt;Coordinated Universal Time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Created new key key AKIASHIVKXX3YX4IXRVH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Pushing out the new key to 3rd party services...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Pushing new credentials to Travis CI project &lt;span class="s1"&gt;&amp;#39;chrsmith/pulumi-aws-travis-cicd-demo&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Updating env var &lt;span class="s1"&gt;&amp;#39;AWS_ACCESS_KEY_ID&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;b246ab3b-c4cb-e76f-a6fc-50f4cbf451c0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Updated AWS access key ID. Got response code &lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Updating env var &lt;span class="s1"&gt;&amp;#39;AWS_SECRET_ACCESS_KEY&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ba9ac4be-fbb3-4da9-c5b8-35a1afe9e02b&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Updated AWS secret access key. Got response code &lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 05644e4a-81a4-4197-8ad7-fc3517998270 INFO Key rotation step complete.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; END RequestId: 05644e4a-81a4-4197-8ad7-fc3517998270
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; START RequestId: ca204959-32a7-44a8-ba20-738d139542ba Version: &lt;span class="nv"&gt;$LATEST&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca204959-32a7-44a8-ba20-738d139542ba INFO IAM User has &lt;span class="m"&gt;2&lt;/span&gt; keys:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca204959-32a7-44a8-ba20-738d139542ba INFO - AKIASHIVKXX3YX4IXRVH &lt;span class="o"&gt;[&lt;/span&gt;Active&lt;span class="o"&gt;]&lt;/span&gt; Tue Mar &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt; 15:37:03 GMT+0000 &lt;span class="o"&gt;(&lt;/span&gt;Coordinated Universal Time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca204959-32a7-44a8-ba20-738d139542ba INFO - AKIASHIVKXX3QEQNZNX5 &lt;span class="o"&gt;[&lt;/span&gt;Active&lt;span class="o"&gt;]&lt;/span&gt; Tue Mar &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt; 12:37:03 GMT+0000 &lt;span class="o"&gt;(&lt;/span&gt;Coordinated Universal Time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca204959-32a7-44a8-ba20-738d139542ba INFO Invalidating older access key AKIASHIVKXX3QEQNZNX5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca204959-32a7-44a8-ba20-738d139542ba INFO Key rotation step complete.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; END RequestId: ca204959-32a7-44a8-ba20-738d139542ba
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; START RequestId: de753fb4-0720-4f4f-a7aa-ad6b9d4c8832 Version: &lt;span class="nv"&gt;$LATEST&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; de753fb4-0720-4f4f-a7aa-ad6b9d4c8832 INFO IAM User has &lt;span class="m"&gt;2&lt;/span&gt; keys:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; de753fb4-0720-4f4f-a7aa-ad6b9d4c8832 INFO - AKIASHIVKXX3YX4IXRVH &lt;span class="o"&gt;[&lt;/span&gt;Active&lt;span class="o"&gt;]&lt;/span&gt; Tue Mar &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt; 15:37:03 GMT+0000 &lt;span class="o"&gt;(&lt;/span&gt;Coordinated Universal Time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; de753fb4-0720-4f4f-a7aa-ad6b9d4c8832 INFO - AKIASHIVKXX3QEQNZNX5 &lt;span class="o"&gt;[&lt;/span&gt;Inactive&lt;span class="o"&gt;]&lt;/span&gt; Tue Mar &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt; 12:37:03 GMT+0000 &lt;span class="o"&gt;(&lt;/span&gt;Coordinated Universal Time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; de753fb4-0720-4f4f-a7aa-ad6b9d4c8832 INFO Deleting older, inactive access key AKIASHIVKXX3QEQNZNX5 &lt;span class="o"&gt;[&lt;/span&gt;Inactive&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; de753fb4-0720-4f4f-a7aa-ad6b9d4c8832 INFO Key rotation step complete.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; END RequestId: de753fb4-0720-4f4f-a7aa-ad6b9d4c8832
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, the AWS credentials for the IAM User are automatically updated every few hours. And whenever a new access key is created, the value is pushed out to the impacted Travis CI projects automatically.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this post, we covered some of the things to consider when providing AWS credentials to your CI/CD system. (In short, be cautious and follow best practices.)&lt;/p&gt;
&lt;p&gt;We then showed how relatively easy it is to stand up a Pulumi infrastructure application for automatically rotating AWS access keys and updating your CI/CD system. By rotating credentials, you can limit the impact if the value is inadvertently disclosed.&lt;/p&gt;
&lt;p&gt;At this point, we&amp;rsquo;ve now given access keys to a CI/CD system for a low-privilege AWS IAM User account. But we still can&amp;rsquo;t &lt;em&gt;do&lt;/em&gt; anything with it. The IAM User whose credentials we have given to the CI/CD system doesn&amp;rsquo;t have access to your production AWS account.&lt;/p&gt;
&lt;p&gt;In the next post, we&amp;rsquo;ll go into the details about IAM Roles, and they can be used to securely and temporarily gain access to additional resources. This is how our low-privilege IAM User can access needed to update production data for your CI/CD pipelines.&lt;/p&gt;</description><author>Chris Smith</author><author>Sophia Parafina</author><category>continuous-delivery</category><category>security</category><category>secrets</category></item><item><title>Managing AWS Credentials on CI/CD: Part 1</title><link>https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-1/</link><pubDate>Thu, 12 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/managing-aws-credentials-on-cicd-part-1/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/managing-aws-credentials-on-cicd-part-1/index.png" /&gt;
&lt;p&gt;Continuous delivery requires providing highly sensitive credentials to your
deployment pipeline. Understanding the risks, mitigations, and best practices
for handling those credentials can be difficult. In this guide, we describe the
best practices for providing AWS credentials to a CI/CD system and to securely
automate updating your cloud infrastructure using Pulumi.&lt;/p&gt;
&lt;p&gt;You’ve heard about &lt;em&gt;Infrastructure as Code&lt;/em&gt; and decided to try your hand at using Pulumi to manage cloud infrastructure using familiar programming languages. Naturally, you’re curious and eager to start updating your cloud infrastructure in your CI/CD pipeline – but then panic hits&amp;hellip; Is updating your cloud infrastructure secure? If you start doing cloud deployments in your CI/CD system, doesn’t that mean you need to give it your production credentials? Aren’t those the most sensitive things your company has?!?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Take a deep breath&lt;/strong&gt;. It’s going to be OK. You &lt;em&gt;can&lt;/em&gt; securely provide AWS credentials to your CI/CD system and practice continuous delivery. This is the first post
in a series going in-depth on how to do just that.&lt;/p&gt;
&lt;p&gt;The goal of this article series is to give you a clear understanding of AWS credential management and how that relates to using Pulumi within a CI/CD environment.
Once you have the AWS credentials in-place, you can then follow our &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/continuous-delivery/"&gt;Continuous Delivery&lt;/a&gt; guide for
configuring your specific CI/CD service, whether you want to use &lt;a href="https://circleci.com"&gt;CircleCI&lt;/a&gt;, &lt;a href="https://docs.gitlab.com/ee/topics/build_your_application.html"&gt;GitLab CI&lt;/a&gt;, or
&lt;a href="https://travis-ci.org"&gt;Travis CI&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; These recommendations do not apply if you are running your own CI/CD system within your
AWS account, e.g., running a Jenkins server on EC2 or using &lt;a href="https://aws.amazon.com/codedeploy/"&gt;AWS CodeDeploy&lt;/a&gt;.
In those cases, please refer to AWS&amp;rsquo;s documentation for how to
&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html"&gt;assume IAM Roles when running on an EC2 instance&lt;/a&gt;
instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;The recommendations in this series describe a general &amp;ldquo;one-size fits most&amp;rdquo; approach for credential
management, which requires a minimal amount of work to configure and maintain. Depending on your
specific environment, needs, and constraints, there may be a better alternative for your use case.&lt;/p&gt;
&lt;h2 id="create-new-iam-user"&gt;Create a dedicated IAM User for your CI/CD&lt;/h2&gt;
&lt;p&gt;The first step for securely automating CI/CD is to create a dedicated IAM User for use in your CI/CD
pipelines. (Sometimes referred to as a &amp;ldquo;robot account.&amp;rdquo;)&lt;/p&gt;
&lt;p&gt;The following code snippet shows how to create a new AWS IAM User using Pulumi. (The code is in TypeScript,
but you could another language like Python, C#, or Go as well.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;cicdUser&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cicd-bot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;purpose&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Account used to perform Pulumi stack updates on CI/CD.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default, that IAM User doesn&amp;rsquo;t have permissions to do anything. The recommended best practice
is to &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#use-groups-for-permissions"&gt;use groups to assign permissions to IAM Users&lt;/a&gt;.
So next, we will create a &amp;ldquo;Pulumi Stack updaters&amp;rdquo; IAM Group, and add our robot account as a member.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pulumiStackUpdaters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PulumiStackUpdaters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groupMembership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GroupMembership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;cicdUserMembership&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;group&lt;/span&gt;: &lt;span class="kt"&gt;group.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="group-permissions"&gt;Group Permissions&lt;/h3&gt;
&lt;p&gt;The next part is where things start to get tricky. We now need to grant permissions to this IAM
Group&amp;rsquo;s members so they can access various APIs and resources. (For example, if your Pulumi program
will need to create or update EC2 instances, then you will need to provide AWS credentials that
contain the &lt;code&gt;ec2:StopInstances&lt;/code&gt; or &lt;code&gt;ec2:StartInstances&lt;/code&gt; action.)&lt;/p&gt;
&lt;p&gt;But rather than grant the newly created &lt;code&gt;cicd-bot&lt;/code&gt; IAM User those permissions, we will
instead only grant them access to the &lt;code&gt;sts:AssumeRole&lt;/code&gt; action. This is an AWS API that allows an IAM
User to gain temporary credentials that have a different set of permissions associated with them.
(That is, the permissions associated with an &lt;em&gt;IAM Role&lt;/em&gt;.)&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll go into more detail about the exact difference between an IAM User and an IAM Role in the next
post in the series since it isn&amp;rsquo;t entirely obvious why one would be more secure than the other.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentAwsIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getCallerIdentity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groupPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GroupPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pulumiStackUpdatersPolicy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;group&lt;/span&gt;: &lt;span class="kt"&gt;group.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Statement&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Allow anybody (i.e., members of the group) to call the sts:AssumeRole API.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This allows them to &amp;#34;assume the role&amp;#34; of a more permissive IAM Role
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// when they go to update a stack later.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sts:AssumeRole&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This is the set of resources that the &amp;#34;sts:AssumeRole&amp;#34; operation could be
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// performed on, which is to say any IAM role in the current AWS account.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;: &lt;span class="kt"&gt;pulumi.interpolate&lt;/span&gt; &lt;span class="sb"&gt;`arn:aws:iam::&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentAwsIdentity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:role/*`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Sid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it for the first post in our series. We&amp;rsquo;ve taken the first step and created an IAM User specifically for performing updates to AWS resources using Pulumi.&lt;/p&gt;
&lt;p&gt;In the next post, we will review over the security concerns when passing AWS credentials to your CI/CD provider, as well as show a Pulumi program that you can use to rotate the AWS credentials for a user automatically.&lt;/p&gt;</description><author>Chris Smith</author><author>Sophia Parafina</author><category>continuous-delivery</category></item></channel></rss>