DEV Community

Cover image for Setting up IAM Anywhere using terraform
Gerson Morales
Gerson Morales

Posted on • Edited on

Setting up IAM Anywhere using terraform

This post would walk you through the steps to configured IamAnywhere in AWS using Terraform and custom certificates.

Requirements

  1. AWS Account.
  2. Terraform.
  3. Certificate Authority [PrivateCA.pem, client.key and client.pem].
  4. aws_signing_helper from AWS https://docs.aws.amazon.com/rolesanywhere/latest/userguide/credential-helper.html

Scope

We are going to configure IamAnywhere to allow access to krakenmoto bucket in S3 without access-key or secret-access-key.

Initial steps

  1. Configure Certificate Authority. To use AWS IAM anywhere we need an X.509 certificate issued by a CA (Certificate Authority).You can use script below to be able to create Bundle-Certificate including in PrivateCA.pem and client.pem and client.key required to access once we have the infrastructure ready.

certificate.sh

#!/bin/bash

SERVER="${SERVER:-client}"

OUTPUT_PATH=${OUTPUT_PATH:-certificates}
mkdir -p $OUTPUT_PATH

CORPORATION=GERSONPLACE
GROUP="Engineering"
CITY="Cartago"
STATE="Paraiso"
COUNTRY=CR

CERT_AUTH_PASS=`openssl rand -base64 32`
echo $CERT_AUTH_PASS > cert_auth_password
CERT_AUTH_PASS=`cat cert_auth_password`

cat -<<EOF > config.cnf
[ req ]
distinguished_name  = req_distinguished_name
attributes      = req_attributes

[ req_distinguished_name ]
countryName         = Country Name (2 letter code)
countryName_min         = 2
countryName_max         = 2
stateOrProvinceName     = State or Province Name (full name)
localityName            = Locality Name (eg, city)
0.organizationName      = Organization Name (eg, company)
organizationalUnitName      = Organizational Unit Name (eg, section)
commonName          = Common Name (eg, fully qualified host name)
commonName_max          = 64
emailAddress            = Email Address
emailAddress_max        = 64

[ req_attributes ]
challengePassword       = A challenge password
challengePassword_min       = 4
challengePassword_max       = 20

[ v3_ca ]
basicConstraints        = critical, CA:TRUE
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always, issuer:always
keyUsage                = critical, cRLSign, digitalSignature, keyCertSign

[SAN]
subjectAltName=DNS:$SERVER"
EOF

echo "Create the certificate authority"
openssl genrsa -out $OUTPUT_PATH/PrivateCA.key 4096
openssl \
  req \
  -subj "/CN=$SERVER.ca/OU=$GROUP/O=$CORPORATION/L=$CITY/ST=$STATE/C=$COUNTRY" \
  -new \
  -x509 \
  -passout pass:$CERT_AUTH_PASS \
  -key $OUTPUT_PATH/PrivateCA.key \
  -out $OUTPUT_PATH/PrivateCA.pem \
  -config config.cnf \
  -extensions v3_ca \
  -days 36500

echo "Create client private key (used to decrypt the cert we get from the CA)"
openssl genrsa -out $OUTPUT_PATH/$SERVER.key 4096

cat -<<EOF > client.ext
basicConstraints = CA:FALSE
authorityKeyIdentifier = keyid,issuer
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment
EOF

echo "Create the CSR(Certitificate Signing Request)"

openssl req -new -key $OUTPUT_PATH/$SERVER.key -out $SERVER.csr -nodes \
  -subj "/CN=$SERVER/OU=$GROUP/O=$CORPORATION/L=$CITY/ST=$STATE/C=$COUNTRY" \
  -sha256

echo "Sign the certificate with the certificate authority"
openssl x509 -req -in $SERVER.csr -CA $OUTPUT_PATH/PrivateCA.pem -CAkey $OUTPUT_PATH/PrivateCA.key -CAcreateserial -out $OUTPUT_PATH/$SERVER.pem \
  -days 36500 \
  -extfile client.ext \
  -passin pass:$CERT_AUTH_PASS
Enter fullscreen mode Exit fullscreen mode

Run the script ./certificate.sh and it would generate the required certificates inside /certificates folder.

Image description

Once you have your custom certificates ready it is time to set up terraform code.

💻## Install terraform

## OSX ##
brew install hashicorp/tap/terraform

## Windows ##
choco install terraform

## Linux ##
sudo apt-get install terraform
Enter fullscreen mode Exit fullscreen mode

🛑Important= If you are using MAC M1/M2 you would probably run into this error when you run the module.

âš Error: Incompatible provider version
Provider registry.terraform.io/hashicorp/template v2.2.0 does not have a package available for your current platform, darwin_arm64.
Provider releases are separate from Terraform CLI releases, so not all providers are available for all platforms. Other versions
of this provider may have different platforms supported.
Enter fullscreen mode Exit fullscreen mode

🛠No worries lets get it fixed by running these 3 commands:

brew install kreuzwerker/taps/m1-terraform-provider-helper
m1-terraform-provider-helper activate -> [In case you have not activated the helper]
m1-terraform-provider-helper install hashicorp/template -v 2.10.0 --> [Install and compile]

Terraform module configuration

Code would create the following resources in AWS IAM

Trust Anchor: In trust Anchor, we establish trust between AWS IAM Role Anywhere and CA. An application running outside AWS authenticates against a trust anchor with X.509 client certificate to get temporary AWS credentials.

IAM Role: Trust Anchors assumes the AWS IAM role to grant allowed IAM policy permissions. To use a role we must trust the IAM Role Anywhere service principle in the role.

Profile: In the profile, we define an IAM role to be assumed by the client. We can set additional permissions boundaries on active sessions with AWS managed policies and condition blocks.

In this case we are going to make use of a custom module that creates all these resources at once, it starts by creating modules/custom folder including .tf files with the resources that terraform will create in AWS anchor.tf, iam.tf, outputs.tf, tls-crt.tf and variables.tf.

anchor.tf

# Trust anchors
resource "aws_rolesanywhere_trust_anchor" "trust_anchor" {
  name    = "${local.project_name}-trust_anchor"
  enabled = true
  source {
    source_data {
      x509_certificate_data = file("${path.module}/certificates/PrivateCA.pem")
    }
    source_type = "CERTIFICATE_BUNDLE"
  }
}

# Profile
resource "aws_rolesanywhere_profile" "profile" {
  enabled             = true
  name                = "${local.project_name}-profile"
  role_arns           = [aws_iam_role.roles.arn]
  managed_policy_arns = [aws_iam_policy.profile_managed_policies.arn]
}


# Profile policies
#Managed policies limit the permissions granted by the role's permissions policy and are assigned to the role session when the role is assumed.
resource "aws_iam_policy" "profile_managed_policies" {
  name        = "${local.project_name}-user-profile-policies"
  path        = "/"
  description = "Allows access to S3"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = [
        "s3:*",
      ]
      Resource = [
            "arn:aws:s3:::${var.bucket_name}",
            "arn:aws:s3:::${var.bucket_name}/*"
      ]
      Effect = "Allow"
    }]
  })
}
Enter fullscreen mode Exit fullscreen mode

iam.tf

locals {
  project_name = var.project_name

}
resource "aws_iam_role" "roles" {
  name = "${local.project_name}-iamanywhere-trust-role"
  path = "/"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "rolesanywhere.amazonaws.com",
        },
        Action = [
          "sts:AssumeRole",
          "sts:TagSession",
          "sts:SetSourceIdentity"
        ],
        Condition = {
          ArnEquals = {
            "aws:SourceArn" = "arn:aws:rolesanywhere:${var.region}:${var.aws_account}:trust-anchor/${aws_rolesanywhere_trust_anchor.trust_anchor.id}"

          }
        }
      }
    ]
  })
}

# Permission policies in the role of iamanywhere-trust-role
resource "aws_iam_policy" "s3_full_access" {
  name        = "${local.project_name}-iamanywhere-trust-role-policies"
  path        = "/"
  description = "Allows access to S3"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = [
        "s3:*",
      ]
      Resource = [
        "arn:aws:s3:::${var.bucket_name}",
        "arn:aws:s3:::${var.bucket_name}/*"
      ]
      Effect = "Allow"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "roles_s3_access" {
  role       = aws_iam_role.roles.name
  policy_arn = aws_iam_policy.s3_full_access.arn
}
Enter fullscreen mode Exit fullscreen mode

outputs.tf

output "anchor" {
  value = aws_rolesanywhere_trust_anchor.trust_anchor.arn
}

output "profile" {
  value = aws_rolesanywhere_profile.profile.arn
}

output "awsiam" {
  value = aws_iam_role.roles.arn
}

data "template_file" "aws_export_profile" {
  template = <<-EOT
[profile iam_anywhere]
region=us-east-1
credential_process = aws_signing_helper credential-process --trust-anchor-arn ${aws_rolesanywhere_trust_anchor.trust_anchor.arn} --profile-arn ${aws_rolesanywhere_profile.profile.arn} --role-arn ${aws_iam_role.roles.arn} --certificate /path/client.pem --private-key /path/client.key
EOT
  vars = {
    trust_anchor_arn = aws_rolesanywhere_trust_anchor.trust_anchor.arn
    profile_arn      = aws_rolesanywhere_profile.profile.arn
    role_arn         = aws_iam_role.roles.arn
  }
}

resource "local_file" "aws_export_profile" {
  content  = data.template_file.aws_export_profile.rendered
  filename = "./aws-config.txt"
}
Enter fullscreen mode Exit fullscreen mode

tls-crt.tf

resource "tls_private_key" "roles" {
  algorithm = "RSA"
}
Enter fullscreen mode Exit fullscreen mode

variables.tf

variable "aws_account" {
  description = "AWS account ID"
  type        = string
}

variable "region" {
  description = "AWS Region"
  type        = string
}

variable "bucket_name" {
  type = string
}

variable "project_name" {
  type = string
}
Enter fullscreen mode Exit fullscreen mode

Those 5 files configure the required terraform resources that you would need in order to create AWS infrastructure to access S3 via IAMAnywhere you can modify iam.tf to set additional policies if needed but in this example the access would be only to krakenmoto bucket that would be included in the main.tf as an input.

Below is how the terraform file structure should look like. Notice that /certificates folder from running ./certificates.sh is inside the root module as anchor resource would make use of it to configure certificate bundle.

Image description

Terraform code that would set variables to call the module

main.tf

module "Iamanywhere" {
  source = "./modules/custom"
  aws_account  = "112223334445"
  region       = "us-east-1"
  bucket_name  = "krakenmoto"
  project_name = "gersonplace"
}
Enter fullscreen mode Exit fullscreen mode

Now we are ready to run terraform so place your self in folder there main.tf is located and run

terraform init -reconfigure -upgrade
terraform validate
terraform plan
terraform apply

Once you applied the changes to AWS you would see a new file generated by terraform with the name aws-config.txt you can use the data on it in order to create a profile to connect to your AWS account to access bucket krakenmoto in S3.

Save this profile into .aws/credentials and download aws_signing_helper from https://docs.aws.amazon.com/rolesanywhere/latest/userguide/credential-helper.html

Once you have aws_signing_helper run
cp aws_signing_helper /usr/local/bin and
chmod +x /usr/local/bin/aws_signing_helper to set permissions.

At this point you have 2 options to authenticate

Manual

./aws_signing_helper credential-process --trust-anchor-arn arn:aws:rolesanywhere:us-east-1:112223334445:trust-anchor/957dd152-a4e2-4ac8-ab79-ff70ae66cf07 --profile-arn arn:aws:rolesanywhere:us-east-1:286514997612:profile/c25f019c-0234-4905-99cc-6dbeacc65b69 --role-arn arn:aws:iam::112223334445:role/gersonplace-iamanywhere-trust-role --certificate /path/client.pem --private-key /path/client.key
Enter fullscreen mode Exit fullscreen mode

Automatic

export AWS_PROFILE=iam_anywhere
Enter fullscreen mode Exit fullscreen mode

Which would use AWS_PROFILE configure in ~/.aws/credentials e.g

[iam_anywhere]
region=us-east-1
credential_process = aws_signing_helper credential-process --trust-anchor-arn arn:aws:rolesanywhere:us-east-1:112223334445:trust-anchor/957dd152-a4e2-4ac8-ab79-ff70ae66cf07 --profile-arn arn:aws:rolesanywhere:us-east-1:286514997612:profile/c25f019c-0234-4905-99cc-6dbeacc65b69 --role-arn arn:aws:iam::112223334445:role/gersonplace-iamanywhere-trust-role --certificate /path/client.pem --private-key /path/client.key
Enter fullscreen mode Exit fullscreen mode

🛑Important: You need to use client.pem and client.key in order to be able to use the anchor to authenticate with AWS.

At this point in your terminal and after setting up AWS profile to iam_anywhere you would be able to run aws s3 ls s3://krakenmoto and get files inside bucket.

✅💻## Verification
aws sts get-caller-identity

{
    "UserId": "AROAUFNM75FWHT6NA3BGE:3ebe8913d3f56b97407de5e686207ae4f8d99057",
    "Account": "112223334445",
    "Arn": "arn:aws:sts::112223334445:assumed-role/gersonplace-iamanywhere-trust-role/3ebe8913d3f56b97407de5e686207ae4f8d99057"
}
Enter fullscreen mode Exit fullscreen mode

aws s3 ls s3://krakenmoto

Image description

Top comments (0)