Skip to main content

Use IAM role on the KOPS cluster to access resources in a different AWS account

This article explains how to use an IAM role on the KOPS cluster to connect and authorize from applications running in Cloud platform to access AWS resources in a different AWS account.

The Cloud Platform uses KIAM to allow secured access to AWS APIs, by associating IAM roles to pods. By annotating the pod and the respective namespace, you can allow the application to assume a role, and can get temporary credentials to access AWS resources.

To do this:

  1. Create the IAM role

    Create a file (cross-iam-role.tf) inside your environment folder in the cloud-platform-environments repo, update the template below with the correct values and raise a PR.

    Click here to see a template code block
    
        data "aws_iam_policy_document" "-kiam-trust-chain" {
          # KIAM trust chain to allow pods to assume roles defined below
          statement {
            principals {
              type        = "AWS"
              identifiers = [data.aws_iam_role.nodes.arn]
            }
            actions = ["sts:AssumeRole"]
          }
        }
        variable "-tags" {
          type = map(string)
          default = {
            business-unit          = ""
            application            = ""
            is-production          = ""
            environment-name       = ""
            owner                  = ""
            infrastructure-support = ""
          }
        }
        resource "aws_iam_role" "-" {
          name               = "-"
          description        = "IAM role for XXXX"
          tags               = var.-tags
          assume_role_policy = data.aws_iam_policy_document.-kiam-trust-chain.json
        }
        resource "kubernetes_secret" "" {
          metadata {
            name      = ""
            namespace = ""
          }
          data = {
            arn       = aws_iam_role.-.arn
            name      = aws_iam_role.-.name
            unique_id = aws_iam_role.-.unique_id
          }
        }
        data "aws_iam_policy_document" "-" {
          # allow pods to assume this role
          statement {
            actions   = ["sts:AssumeRole"]
            resources = [aws_iam_role.-.arn]
          }
          # Provide list of permissions and target AWS account resources to allow access from
          statement {
            actions = [
              "",
            ]
            resources = [
              "/*",
            ]
          }
        }
        resource "aws_iam_policy" "-" {
          name   = "-"
          policy = data.aws_iam_policy_document.-.json
        }
        resource "aws_iam_role_policy_attachment" "-" {
          role       = aws_iam_role.-.name
          policy_arn = aws_iam_policy.-.arn
        }
    
    

    Here is an example using the template above, with typical values provided
    
        data "aws_iam_policy_document" "my-namespace-kiam-trust-chain" {
        # KIAM trust chain to allow pods to assume roles defined below
          statement {
            principals {
              type        = "AWS"
              identifiers = [data.aws_iam_role.nodes.arn]
            }
            actions = ["sts:AssumeRole"]
          }
        }
        variable "my-namespace-tags" {
          type = map(string)
          default = {
            business-unit          = "Cloud Platform"
            application            = "My Application"
            is-production          = "false"
            environment-name       = "Development"
            owner                  = "cloud-platform"
            infrastructure-support = "platforms@digital.justice.gov.uk"
          }
        }
        resource "aws_iam_role" "my-namespace-app-iam-role" {
          name               = "my-namespace-app-iam-role"
          description        = "IAM role for My app to access s3 bucket - AAA in 11111 AWS account"
          tags               = var.my-namespace-tags
          assume_role_policy = data.aws_iam_policy_document.my-namespace-kiam-trust-chain.json
        }
        resource "kubernetes_secret" "iam-role-creds-my-app" {
          metadata {
            name      = "iam-role-creds-my-app"
            namespace = "my-namespace"
          }
          data = {
            arn       = aws_iam_role.my-namespace-app-iam-role.arn
            name      = aws_iam_role.my-namespace-app-iam-role.name
            unique_id = aws_iam_role.my-namespace-app-iam-role.unique_id
          }
        }
        data "aws_iam_policy_document" "my-namespace-app-iam-role" {
    
          # allow pods to assume this role
          statement {
            actions   = ["sts:AssumeRole"]
            resources = [aws_iam_role.my-namespace-app-iam-role.arn]
          }
          # Provide list of permissions and target AWS account resources to allow access from
          statement {
            actions = [
              "s3.PutObject",
            ]
            resources = [
              "arn:aws:s3:::AAAAAAAA/AA-bucket/folder/*",
            ]
          }
        }
        resource "aws_iam_policy" "my-namespace-app-iam-policy" {
          name   = "my-namespace-app-iam-policy"
          policy = data.aws_iam_policy_document.my-namespace-app-iam-policy.json
        }
        resource "aws_iam_role_policy_attachment" "my-namespace-app-iam-policy" {
          role       = aws_iam_role.my-namespace-app-iam-role.name
          policy_arn = aws_iam_policy.my-namespace-app-iam-policy.arn
        }
    
    
  2. Annotate the pod with the IAM role

    Once the PR in step 1 is merged and applied, you will get the role ARN, name and ID as a kubernetes secret in your namespace.

    Decode the role ARN and annotate the pod to indicate which role will need access to the resource in the other AWS account.

    A deployment with the Pod template annotated would look like this (note the <role-name>):

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: wordpress
        namespace: my-namespace
        labels:
          app: wordpress
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: wordpress
            tier: frontend
        strategy:
          type: Recreate
        template:
          metadata:
            labels:
              app: wordpress
              tier: frontend
            annotations:
              iam.amazonaws.com/role: <role-name>
          spec:
            containers:
            - image: wordpressdemo/wpapp:1.1
              name: wordpress
    

    An example role arn annotated in the deployment would look like this:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: wordpress
        namespace: my-namespace
        labels:
          app: wordpress
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: wordpress
            tier: frontend
        strategy:
          type: Recreate
        template:
          metadata:
            labels:
              app: wordpress
              tier: frontend
            annotations:
              iam.amazonaws.com/role: my-namespace-app-iam-role
          spec:
            containers:
            - image: wordpressdemo/wpapp:1.1
              name: wordpress
    
  3. Annotate the namespace to allow the “AssumeRole”

    Next, annotate the namespace to indicate which roles are permitted to be assumed within the namespace.

    Update the annotations section in your 00-namespace.yaml file with the code below, and raise a PR:

      apiVersion: v1
      kind: Namespace
      metadata:
        name: my-namespace
        labels:
          cloud-platform.justice.gov.uk/is-production: "false"
          cloud-platform.justice.gov.uk/environment-name: "dev"
        annotations:
          cloud-platform.justice.gov.uk/business-unit: "Cloud Platform"
          cloud-platform.justice.gov.uk/application: "my-test-application"
          cloud-platform.justice.gov.uk/owner: "Cloud Platform: platforms@digital.justice.gov.uk"
          cloud-platform.justice.gov.uk/source-code: "https://github.com/ministryofjustice/cloud-platform-mywpapp"
          iam.amazonaws.com/permitted: "<role-name>"
    
    

    An example would look like this:

      apiVersion: v1
      kind: Namespace
      metadata:
        name: my-namespace
        labels:
          cloud-platform.justice.gov.uk/is-production: "false"
          cloud-platform.justice.gov.uk/environment-name: "dev"
        annotations:
          cloud-platform.justice.gov.uk/business-unit: "Cloud Platform"
          cloud-platform.justice.gov.uk/application: "my-test-application"
          cloud-platform.justice.gov.uk/owner: "Cloud Platform: platforms@digital.justice.gov.uk"
          cloud-platform.justice.gov.uk/source-code: "https://github.com/ministryofjustice/cloud-platform-mywpapp"
          iam.amazonaws.com/permitted: "my-namespace-app-iam-role"
    
  4. Use the IAM role in your application

There are several ways you can use the role created to access the resource in the other AWS account.

For example, to call the AWS SDK for Ruby and get temporary AWS credentials, you could do this:

  role_credentials = Aws::AssumeRoleCredentials.new(
    role_arn: role_arn,
    role_session_name: "myapp_session"
  )

This will result in role_credentials being a hash consisting of an access key ID, a secret access key, and a security token.

For more details on using assumeRole check the AWS documentation for AWS assume-role CLI and AW::AssumeRoleCredentials SDK

5.Allow the IAM role to permit access in the target AWS account

You also need to update the AWS resource policy of the target AWS account to allow the IAM role to perform actions.

Here is an example s3 bucket policy to allow an IAM role to perform specific actions:

  {
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Cross IAM permissions",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::11111111:role/my-namespace-app-iam-role"
      },
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
      ],
      "Resource": "arn:aws:s3:::target-s3-bucket/*"
    }
  ]
}
This page was last reviewed on 19 July 2021. It needs to be reviewed again on 19 October 2021 .
This page was set to be reviewed before 19 October 2021. This might mean the content is out of date.