DEV Community

Cover image for Generating temporary access actions in IAM policies using Terraform with DATE and TIME conditions in AWS.
Javier Sepúlveda
Javier Sepúlveda

Posted on

Generating temporary access actions in IAM policies using Terraform with DATE and TIME conditions in AWS.

Cloud people!

In this opportunity I want to share an example of as covering a scenario of temporary access in policies using condition of date and time.

This is useful in cases where it is necessary to use an action within an AWS policy to do something within a certain time and subsequently lose this access for security reasons.

The case is following:

An application need a user specific, this user is created during execution time and only users that have permissions to retrieve secrets in secrets manager can be authenticated inside bastion.

In addition, any other user who has permissions to connect to the bastion from Systems Manager and does not have permissions to retrieve the user's secret from secrets manager will not be able to perform actions even if they try to retrieve the secret from the bastion, as there is only a 5-minute gate to retrieve the secret from the instance profile, these 5 minutes are enough for the bootstrap process.

Requirements

Let's see how we can do this using terraform.

Reference Architecture

SysOps1 architecture

This is the link for all code terraform in branch sysops1.

Step 1.

Adding the kms-module allows the creation of KMS to encrypt resource.

module "kms" {
  source  = "terraform-aws-modules/kms/aws"
  version = "2.1.0"

  deletion_window_in_days = 7
  description             = "ec2 key for testing"
  enable_key_rotation     = false
  is_enabled              = true
  key_usage               = "ENCRYPT_DECRYPT"
  multi_region            = false

  aliases = ["ec2/testing"]

}
Enter fullscreen mode Exit fullscreen mode

Step 2.

Adding the ec2-module allows the creation of ec2 instance.

This is the code for deploy ec2 instance that is used as Bastion, in this step the kms arn is used to encrypt volumes EBS, remember that this a good practice.

module "ec2-instance" {
  source                      = "terraform-aws-modules/ec2-instance/aws"
  version                     = "5.6.0"
  name                        = "bastion"
  ami                         = "ami-023c11a32b0207432"
  instance_type               = "t3.medium"
  subnet_id                   = "subnet-0241994880cd395eb"
  vpc_security_group_ids      = ["sg-0ea884b195d4fd56b"]
  associate_public_ip_address = true
  availability_zone           = ""
  iam_instance_profile        = aws_iam_instance_profile.test_profile.name
  user_data                   = templatefile("${path.module}/userdatafile/scripts_bastion/cloud_init.yaml", { user0 = local.users[0], secret_name0 = aws_secretsmanager_secret.userlinux-pass.name })
  user_data_replace_on_change = true
  hibernation                 = true
  enable_volume_tags          = true
  root_block_device = [
    {
      encrypted   = true
      kms_key_id  = module.kms.key_id
      volume_type = "gp3"
      throughput  = 200
      volume_size = 20
    },
  ]
  ebs_block_device = [
    {
      device_name = "/dev/sdf"
      volume_type = "gp3"
      volume_size = 20
      throughput  = 200
      encrypted   = true
      encrypted   = true
      kms_key_id  = module.kms.key_id
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

In this case a terraform templatefile function is used to retrieve a value from terraform and pass it to cloud-init.yaml which is then used for the bootstrap process.

#cloud-config
package_update: true
packages:
  - unzip

write_files:
  - path: /tmp/install_bastion_userdata.sh
    content: |
      #!/bin/bash
      #versiones
      awscli_version=2.13.32

      cd /tmp
      #Instalando AWS CLI
      curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-$awscli_version.zip" -o "awscliv2.zip"
      unzip awscliv2.zip
      sudo ./aws/install

      #Instalación de SSM
      sudo yum install -y https://s3.us-east-1.amazonaws.com/amazon-ssm-us-east-1/latest/linux_amd64/amazon-ssm-agent.rpm
      sudo systemctl enable amazon-ssm-agent
      sudo systemctl start amazon-ssm-agent

      #Instalando jq
      sudo dnf install jq -y

    permissions: '0755'

runcmd:
  - /tmp/install_bastion_userdata.sh
  - GROUPNAME="SysOps"    #Creación de un grupo en Linux.
  - GROUP_UID=11001       #Creación de un grupo en Linux
  - USER0_UID=10001
  - groupadd -g $GROUP_UID $GROUPNAME    #Creación de un grupo en Linux
  - /usr/local/bin/aws secretsmanager get-secret-value --secret-id ${secret_name0} --query SecretString --output text > /tmp/secreto.txt --region us-east-1 #user
  - PASSWORD=$(jq -r '.password' /tmp/secreto.txt)
  - useradd -u $USER0_UID -m -d /home/${user0} -s /bin/bash -g $GROUPNAME ${user0}
  - echo "${user0}:$PASSWORD" | sudo chpasswd
  - rm -f /etc/sudoers.d/90-cloud-init-users && rm -f /etc/sudoers.d/ssm-agent-users && rm -f /tmp/secreto.txt
  - echo "${user0} ALL=(ALL) ALL" >> /etc/sudoers
  - rm -f /etc/sudoers.d/90-cloud-init-users  ssm-agent-users  #Remove ssm-user from sudoers
  - cd /etc/sudoers.d
  - echo "#User rules for ssm-user" > ssm-agent-users
Enter fullscreen mode Exit fullscreen mode

Step 3.

This step is important because it allows the bastion instance to have only 5 minutes to retrieve the secret that is then used to set the password of the user that is created during the runtime, in which the instance is parameterized using userdata.

resource "aws_iam_policy" "custom_policy" {
  name = "segoja7-policy-testing"

  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Action" : [
          "secretsmanager:GetSecretValue",
        ],
        "Resource" : [aws_secretsmanager_secret_version.userlinux-pass-val.arn],
        "Effect" : "Allow"
        "Condition" : {
          "DateGreaterThan" : { "aws:CurrentTime" :timestamp() },
          "DateLessThan" : { "aws:CurrentTime" : timeadd(timestamp(), "5m") }
        }
      },
      {
        "Effect" : "Allow",
        "Action" : [
          "kms:Decrypt",
          "kms:DescribeKey",
          "kms:GenerateDataKey"
        ],
        "Resource" : module.kms.key_arn
      }
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

In this step, it is creating a police with permissions to retrieve secrets in a resource specific. Additional, as the secret is encrypted, are necessary permissions about KMS.

Check that first statement have conditions and is using two functions of terraform that handle date and time.

For more information, check these links for function timestamp and timeadd.
And this link for AWS conditions policy

Step 4.

In this step, a role is being created with permissions so that the ec2 service can assume the role, in this case the bastion instance.

resource "aws_iam_role" "custom_role" {
  name = "custom-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        "Sid" : "EC2AssumeRole",
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "ec2.amazonaws.com"
        },
        "Action" : "sts:AssumeRole"
      },
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

Step 5.

In this step, two policies are attached to the role, the custom policy that was created in step 3 and a managed policy that allows to connect via systems manager.

resource "aws_iam_role_policy_attachment" "policy-attachment" {
  for_each = {
    "AmazonSSMManagedInstanceCore" = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
    "CustomPolicy" = aws_iam_policy.custom_policy.arn,
  }
  policy_arn = each.value
  role       = aws_iam_role.custom_role.name
}
Enter fullscreen mode Exit fullscreen mode
resource "aws_iam_instance_profile" "instance_profile" {
  name = "custom-profile"
  role = aws_iam_role.custom_role.name
}
Enter fullscreen mode Exit fullscreen mode

Step 6.

In this step, it is created the secret using a randon_password resource of terraform.

resource "aws_secretsmanager_secret" "userlinux-pass" {
  name                    = "secrets/bastion/${local.users[0]}"
  description             = "password secret for user: ${local.users[0]}"
  recovery_window_in_days = 0
  kms_key_id              = module.kms.key_id
}

resource "aws_secretsmanager_secret_version" "userlinux-pass-val" {
  secret_id = aws_secretsmanager_secret.userlinux-pass.id
  secret_string = jsonencode({
    username = local.users[0]
    password = random_password.user_password.result
  })
}

resource "random_password" "user_password" {
  length  = 16
  special = true
}
Enter fullscreen mode Exit fullscreen mode

For this moment, all code of main.tf is completed and now is possible run terraform apply.

terraform apply --auto-approve #use with precaution
Enter fullscreen mode Exit fullscreen mode

Terraform apply

Step 7.

Check the resources that were created.

An Ec2 bastion with an instance profile.

Ec2 bastion with an instance profile

Using cloud-init.yaml this is a part of the userdata result.

userdata

A Role with permission policies.

Role with permission policies

policy with date and time

The final result of the policy is a dynamic date and time value and it is only possible to retrieve the secret in the first 5 minutes after the policy was created.

A secret with password for the user created with cloud-init.yaml.

A secret with password

Step 8.

Checking that policy is functional and it is not posible retrieve the secret from ec2 bastion using the instance profile.

verifying that instance profile is work connecting from System Manager.

aws sts get-caller-identity
Enter fullscreen mode Exit fullscreen mode

aws sts get-caller-identity

try retrieve the secret.

aws secretsmanager get-secret-value --secret-id secrets/bastion/segoja7 --query SecretString --region us-east-1
Enter fullscreen mode Exit fullscreen mode

aws secretsmanager get-secret-value

Step 9.

connecting with user created in execution time using userdata.

retrieve secret

using secret

Conclusion, you have created a policy with temporary permission using terraform functions such as timeadd and timestamp. This is good for scenarios where it is necessary to provision or use actions from a policy for a short time and then revoke access.

Successful!!

Top comments (0)