# Amazon Web Services (AWS)

## Let's Explain

The AWS integration allows either Spacelift [runs](https://spacelift-io.gitbook.io/spacelift/concepts/run) or [tasks](https://spacelift-io.gitbook.io/spacelift/concepts/run/task) to automatically [assume an IAM role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) in your AWS account, and in the process, generate a set of **temporary credentials.** These credentials are then exposed as [computed environment variables](https://spacelift-io.gitbook.io/spacelift/concepts/configuration/environment#computed-values) during the run/task that takes place on the particular Spacelift stack that the integration is attached to.

* `AWS_ACCESS_KEY_ID`
* `AWS_SECRET_ACCESS_KEY`
* `AWS_SECURITY_TOKEN`
* `AWS_SESSION_TOKEN`

This is enough for both the [AWS Terraform provider](https://www.terraform.io/docs/providers/aws/index.html#environment-variables) and/or [S3 state backend](https://www.terraform.io/docs/backends/types/s3.html) to generate a fully authenticated AWS session without further configuration. However, you will likely need to select one of the available regions with the former.

### Usage

To utilize the AWS integration you'll need to setup at least one cloud integration, and then attach that integration to any stack(s) that you'd like to have utilize that integration. Please follow the [Setup Guide](#setup-guide) for more information on this process.

## Trust Policy

When setting up a Spacelift AWS Cloud Integration you'll need to specify the ARN of an IAM Role to use. This role, will need to have its Trust Policy configured, to allow Spacelift to assume the role and generate temporary credentials.&#x20;

When completing the role assumption, Spacelift will pass extra information in the `ExternalId` attribute, allowing you to optionally add additional layers of security to your role.&#x20;

**External ID Format:**

\<spacelift-account-name>@\<integration-id>@\<stack-slug>@\<read|write>

* \<spacelift-account-name>
  * The name of the Spacelift account that initiated the role assumption.
* \<integration-id>
  * The ID of the AWS Cloud Integration that initiated the role assumption.
* \<stack-slug>
  * The slug of the stack that the AWS Cloud integration is attached to, that initiated the role assumption.
* \<read|write>
  * This will either be set to `read` or `write` based upon the event occurring that has initiated the role assumption.
    * **Planning:** Utilizes read
    * **Applying:** Utilizes write

## Setup Guide

**Pre-Requisites:**

* Ability to create IAM Roles in your AWS account.
* Admin access to your Spacelift account.

### Setup a Role in AWS

First things first - before proceeding, you'll want to have an [AWS IAM Role ](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html)within your AWS account that we will be configuring this cloud integration to use.

**Navigate to AWS IAM**

Within your AWS account, navigate to AWS IAM and click the **Create role** button.

![Within AWS IAM, click Create role](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FILeM7kgWJuoNmntavRBL%2FScreen%20Shot%202022-07-21%20at%202.48.55%20PM.png?alt=media\&token=dc94d505-edef-4ff6-9293-e897104f3925)

#### Configure Trust Policy

Next, we want to configure the [Trust Policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-custom.html) for the role to allow Spacelift to assume the role.

Here's the format of a custom trust policy statement you can use, that allows any stack within your Spacelift account to use this IAM Role:

```
{
	"Version": "2012-10-17",
	"Statement": [
          {
            "Action": "sts:AssumeRole",
            "Condition": {
              "StringLike": {
                "sts:ExternalId": "yourSpaceliftAccountName@*"
              }
            },
            "Effect": "Allow",
            "Principal": {
              "AWS": "324880187172"
            }
          }
	]
}
```

{% hint style="info" %}
Be sure to replace **yourSpaceliftAccountName** in the example above with your actual Spacelift account name.
{% endhint %}

![Configure a Custom Trust Policy on the IAM Role](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2F9AiFKRtiYpUCEbtzdMkf%2FScreen%20Shot%202022-07-21%20at%203.25.21%20PM.png?alt=media\&token=0965ed4f-d8fe-4e1d-9c2c-032e1509d8c5)

#### Optionally Configure Further Constraints on the Trust Policy

{% hint style="info" %}
By default, Spacelift passes the `ExternalId` value in this format:

\<spacelift-account-name>@\<integration-id>@\<stack-slug>@\<read|write>
{% endhint %}

Knowing the format of the external id passed by Spacelift, users can further secure their IAM Role trust policies if they desire a deeper level of granular security.

For example, users may wish to lock down an IAM Role, to only be used by a specific stack (e.g. `stack-a` in their Spacelift account `example`). In such a case, they could add the stack-slug into their Trust Policy like so:

```
{
	"Version": "2012-10-17",
	"Statement": [
          {
            "Action": "sts:AssumeRole",
            "Condition": {
              "StringLike": {
                "sts:ExternalId": "example@*@stack-a@*"
              }
            },
            "Effect": "Allow",
            "Principal": {
              "AWS": "324880187172"
            }
          }
	]
}
```

**Configure Role Permissions**

Next, you need to attach at least one IAM Policy to your IAM Role to provide it with sufficient permissions to deploy any resources that your IaC code defines.

{% hint style="info" %}
For Terraform users that are managing their own state file, don't forget to give your role sufficient permissions to access your state (Terraform documents the permissions required for S3-managed state [here](https://www.terraform.io/language/settings/backends/s3#s3-bucket-permissions), and for DynamoDB state locking [here](https://www.terraform.io/language/settings/backends/s3#dynamodb-table-permissions)).
{% endhint %}

**Create IAM Role**

Once you have your IAM Role's trust policy and IAM Policies configured, you can finish creating the role. Take a note of the IAM Role ARN, as you'll need this when setting up the integration in Spacelift in the next section.

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FdFu2ZCX40yq6Yupjliqq%2Fcopy-role-arn.png?alt=media\&token=1761a50b-18fd-4457-b635-87cd51790655)

### Navigate to Cloud Integrations

Now that you have an IAM Role created, navigate to the Cloud Integration page from the Spacelift navigation sidebar.

![Navigate to the Cloud integrations page](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2Fac5gEKNuNWTGKHdF8FlJ%2FScreen%20Shot%202022-07-20%20at%209.55.22%20AM.png?alt=media\&token=4a20f0a3-cb1b-4347-b673-95b7ef3e89dd)

### Create an Integration

Click the button to begin the creation of your first integration.

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FQ5RCrOz8UbSqg06Mjh1e%2FScreen%20Shot%202022-07-20%20at%2010.42.05%20AM.png?alt=media\&token=d50fec1f-9bc7-42ec-b5b3-b6a8b3f44c6a)

When creating an integration, you'll immediately notice that you need to specify two required fields: **Name** and **Role ARN.** Give the integration a name of your choosing, and paste in the ARN of the IAM Role that we just created.

When assuming the role on private workers, the process is straightforward: you provide the IAM role to assume, and, optionally, an external ID.

{% hint style="info" %}
**When creating your role in AWS, you'll need to ensure the role has a trust policy that allows Spacelift to assume the role to generate temporary credentials for runs,** assuming you are following this guide, you should have configured this in the [previous section](#configure-trust-policy).
{% endhint %}

{% hint style="warning" %}
Note that we have no way of validating whether your worker has the ability to assume the said role. You will need to trigger a job to verify that.
{% endhint %}

### Using the Integration

Now that you've created the integration, we need to attach it to the Spacelift Stack that you wish to use this integration.

**Navigate to Chosen Stack**

This part's up to you - navigate to a stack that you wish to attach the integration you just created to.

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FlKrgygWnX7ZsjfmZanvO%2FScreen%20Shot%202022-07-25%20at%209.27.13%20AM.png?alt=media\&token=ffc1efe1-93d7-4db1-85c5-bdc1e9e7d9c0)

**Navigate to Settings of the Stack**

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FwQualZJ1ICsbWK9ms3vU%2FScreen%20Shot%202022-07-25%20at%209.29.09%20AM.png?alt=media\&token=cab6ffbc-19cf-4f78-9589-2e7b6f4913c5)

**Select the Integrations Tab**

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FU8v0tF42Pmv7jjFY9PmU%2FScreen%20Shot%202022-07-25%20at%209.31.14%20AM.png?alt=media\&token=0e43203e-7682-409d-885f-22b5086ed029)

**Select the AWS Integration Option, and Select your Integration**

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LshwwDZmA4HXN0k9e8O%2Fuploads%2FfMpVRHvXjHt7NJhOds5H%2FScreen%20Shot%202022-07-25%20at%209.35.09%20AM.png?alt=media\&token=b53dd2d8-4568-4431-b758-b61278c2caf1)

**Read** indicates that this integration will be used during read phases of runs (for example, plans).

**Write** indicates that this integration will be used during write phases of runs (for example, applies).

If the Cloud Integration has the "Assume Role on Worker" setting disabled, Spacelift will verify the role assumption as soon as you click the attach button.

{% hint style="success" %}
This somewhat counterintuitive extra check is to prevent against malicious takeover of your account by someone who happens to know your AWS account ID, which isn't all that secret, really. The security vulnerability we're addressing here is known as the [*confused deputy problem*](https://en.wikipedia.org/wiki/Confused_deputy_problem).
{% endhint %}

## Programmatic Setup

You can also use the [Spacelift Terraform provider](https://spacelift-io.gitbook.io/spacelift/vendors/terraform/terraform-provider) in order to create an AWS Cloud integration from an [administrative stack](https://spacelift-io.gitbook.io/spacelift/concepts/stack#administrative), including the trust relationship. Note that in order to do that, your administrative stack will require AWS credentials itself, and ones powerful enough to be able to deal with IAM.

Here's a little example of what that might look like to create a Cloud Integration programmatically:

{% code title="example.tf" %}

```python
data "aws_caller_identity" "current" {}

locals {
  role_name = "example-role"
  role_arn  = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.role_name}"
}

resource "spacelift_stack" "this" {
  name         = "Example Stack"
  repository   = "your-awesome-repo"
  branch       = "main"
}

resource "spacelift_aws_integration" "this" {
  name = local.role_name

  # We need to set this manually rather than referencing the role to avoid a circular dependency
  role_arn                       = local.role_arn
  generate_credentials_in_worker = false
}

data "spacelift_aws_integration_attachment_external_id" "this" {
  integration_id = spacelift_aws_integration.this.id
  stack_id       = spacelift_stack.this.id
  read           = true
  write          = true
}

resource "aws_iam_role" "this" {
  name = local.role_name

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      jsondecode(data.spacelift_aws_integration_attachment_external_id.this.assume_role_policy_statement),
    ]
  })
}

resource "aws_iam_role_policy_attachment" "this" {
  policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
  role       = aws_iam_role.this.name
}

resource "spacelift_aws_integration_attachment" "this" {
  integration_id = spacelift_aws_integration.this.id
  stack_id       = spacelift_stack.this.id
  read           = true
  write          = true

  # The role needs to exist before we attach since we test role assumption during attachment.
  depends_on = [
    aws_iam_role.this
  ]
}
```

{% endcode %}

{% hint style="info" %}
Please always refer to the [provider documentation](https://github.com/spacelift-io/terraform-provider-spacelift) for the most up-to-date documentation.
{% endhint %}

## Is it safe?

Assuming role and generating credentials **on the private worker** is **perfectly safe**. Those credentials are never leaked to us in any shape or form. Hence, the rest of this section discusses the trust relationship established between the Spacelift account and your AWS account for the purpose of dynamically generating short-lived credentials. So, how safe is that?

Probably safer than storing static credentials in your stack environment. Unlike user keys that you'd normally have to use, role credentials are dynamically created and short-lived. We use the default expiration which is **1 hour**, and do not store them anywhere. Leaking them **accidentally** through the logs is not an option either because we mask AWS credentials.

The most tangible safety feature of the AWS integration is the breadcrumb trail it leaves in [CloudTrail](https://aws.amazon.com/cloudtrail/). Every resource change can be mapped to an individual Terraform [run](https://spacelift-io.gitbook.io/spacelift/concepts/run) or [task](https://spacelift-io.gitbook.io/spacelift/concepts/run/task) whose ID automatically becomes the username as the [`sts:AssumeRole`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) API call with that ID as `RoleSessionName`. In conjunction with AWS tools like [Config](https://aws.amazon.com/config/), it can be a very powerful compliance tool.

Let's have a look at a CloudTrail event showing an IAM role being created by what seems to be a Spacelift run:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LshwwDZmA4HXN0k9e8O%2F-Lv2X3C9tc1gOugAssC9%2F-Lv2ePiFYvA_oitfUnBn%2FCloudTrail_Management_Console.png?alt=media\&token=c087a505-c78c-482b-8bf9-c190fb26d344)

`01DSJ63P40BAZY4VW8BXXG7M4K` is indeed a run ID we can then trace back even further:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LshwwDZmA4HXN0k9e8O%2F-Lv2X3C9tc1gOugAssC9%2F-Lv2fA1-RetbaTVgZFNG%2F01DSJ63P40BAZY4VW8BXXG7M4K_%C2%B7_End-to-end_testing.png?alt=media\&token=05de7fa0-c9cb-4ce2-8e05-d356cd4e6b40)

## Roles assuming other roles

OK, we get it. Using everyone's favorite Inception meme:

![](https://3862190545-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LshwwDZmA4HXN0k9e8O%2F-Lv2fHT8svBOl1Ri9Hpo%2F-Lv2fb7yRr_kg_5UqZ5D%2Fimage.png?alt=media\&token=8f206053-3283-4a9b-8a6f-c51166d676dd)

Indeed, AWS Terraform provider allows you to [assume an IAM role during setup](https://www.terraform.io/docs/providers/aws/index.html#assume-role), effectively doing the same thing over again. This approach is especially useful if you want to control resources in multiple AWS accounts from a single Spacelift stack. This is totally fine - in IAM, roles can assume other roles, though what you need to do on your end is set up the trust relationship between the role you have Spacelift assume and the role for each provider instance to assume. But let's face it - at this level of sophistication, you sure know what you're doing.

One bit you might not want to miss though, is the guaranteed ability to map the change to a particular [run](https://spacelift-io.gitbook.io/spacelift/concepts/run) or [task](https://spacelift-io.gitbook.io/spacelift/concepts/run/task) that we described in the [previous section](#is-it-safe). One way of fixing that would be to use the `TF_VAR_spacelift_run_id` [computed environment variable](https://spacelift-io.gitbook.io/spacelift/concepts/configuration/environment#computed-values) available to each Spacelift workflow. Conveniently, it's already a [Terraform variable](https://www.terraform.io/docs/configuration/variables.html#environment-variables), so a setup like this should do the trick:

{% code title="aws.tf" %}

```python
variable "spacelift_run_id" {}

# That's our default provider with credentials generated by Spacelift.
provider "aws" {}

# That's where Terraform needs to run sts:AssumeRole with your
# Spacelift-generated credentials to obtain ones for the second account.
provider "aws" {
  alias = "second-account"

  assume_role {
    role_arn     = "<up-to-you>"
    session_name = var.spacelift_run_id
    external_id  = "<up-to-you>"
  }
}
```

{% endcode %}
