This post would walk you through the steps to configured IamAnywhere in AWS using Terraform and custom certificates.
Requirements
- AWS Account.
- Terraform.
- Certificate Authority [
PrivateCA.pem
,client.key
andclient.pem
]. -
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
- 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 createBundle-Certificate
including inPrivateCA.pem
andclient.pem
andclient.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
Run the script ./certificate.sh
and it would generate the required certificates inside /certificates
folder.
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
🛑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.
🛠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"
}]
})
}
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
}
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"
}
tls-crt.tf
resource "tls_private_key" "roles" {
algorithm = "RSA"
}
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
}
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.
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"
}
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
Automatic
export AWS_PROFILE=iam_anywhere
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
🛑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"
}
aws s3 ls s3://krakenmoto
Top comments (0)