Skip to content

Instantly share code, notes, and snippets.

@thejohnny
Forked from devops-adeel/aws_iam_federated.tf
Created August 8, 2023 20:15
Show Gist options
  • Save thejohnny/24bcce607b0cc4165875fa2f58062fa0 to your computer and use it in GitHub Desktop.
Save thejohnny/24bcce607b0cc4165875fa2f58062fa0 to your computer and use it in GitHub Desktop.
WIP Code in creating workload identity

Run Once

  1. Create a named key used by a role to sign tokens.

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/oidc/key/$DOMAIN_ID" -d '{"allowed_client_ids":["$DOMAIN_ID"]}'

resource "vault_identity_oidc_key" "domain_id" {
  name               = var.domain_id
  algorithm          = "RS256"
  allowed_client_ids = tolist(var.domain_id)
}

NOTE: The allowed_client_ids is to authorise which Identity-Role is allowed to use this key to sign an ID token.

  1. Create an Identity Role; ID tokens are generated against a role & signed against a named key.

Sample Template (string-ified JSON):

{
  "username": {{identity.entity.aliases.accessor_id.metadata.userid}}
  "group": {{identity.entity.groups.names}}
  "arn": {{identity.entity.aliases.accessor_id.metadata.arn}}
  "aws_account": {{identity.entity.aliases.accessor_id.metadata.account}}
}

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/oidc/role/$DOMAIN_ID" -d '{"key":"$DOMAIN_ID", "client_id":"sts.amazonaws.com", "template":"base64adda234q3b3"}'

NOTE: the value of client_id will render as the value of aud in the rendered claims.

locals {
  userid = format(
    "{{identity.entity.aliases.%s.metadata.userid}}",
    data.vault_auth_backend.default.accessor
  )

  arn = format(
    "{{identity.entity.aliases.%s.metadata.arn}}",
    data.vault_auth_backend.default.accessor
  )

  aws_account = format(
    "{{identity.entity.aliases.%s.metadata.aws_account}}",
    data.vault_auth_backend.default.accessor
  )

  template_hcl {
    group       = "{{identity.entity.groups.names}}"
    username    = local.username
    arn         = local.arn
    aws_account = local.aws_account
  }

  template = base64encode(jsonencode(local.template_hcl))
}

resource "vault_identity_oidc_role" "domain_id" {
  name      = var.domain_id
  key       = vault_identity_oidc_key.domain_id.name
  client_id = "sts.amazonaws.com"
  template  = local.template
}
  1. Create an ACL policy against the path for requesting a signed token:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/sys/policy/$DOMAIN_ID" -d '{"policy":"path \"identity/oidc/token/$DOMAIN_ID\" {..."}'

data "vault_policy_document" "domain_id" {
  rule {
    path         = format("identity/oidc/token/%s", var.domain_id)
    capabilities = ["read"]
    description  = "allow generate signed id token named after metadata keys"
  }
}

resource "vault_policy" "domain_id" {
  name   = var.domain_id
  policy = data.vault_policy_document.domain_id.hcl
}
  1. Create a Vault Identity Group.

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/group" -d '{"name":"$DOMAIN_ID", "policies":["aws"]}'

Sample Response:

{
  "data": {
    "id": "363926d8-dd8b-c9f0-21f8-7b248be80ce1",
    "name": "$DOMAIN_ID"
  }
}

For dynamic entity & alias

Configure the way that Vault interacts with the Identity store. Generate the identity alias when using the iam auth method.

Sample Payload

{
  "iam_alias": "unique_id"
}

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/aws/config/identity" -d '{"iam_alias":"unique_id"}'

Run for every user

  1. Create AWS auth role:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/auth/aws/role/$USER_ID" -d '{"bound_iam_principal_arn":["$ARN_ID"], "resolve_aws_unique_ids":true}'

  1. Create an Entity with Metadata:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/entity" -d '{"name":"$USER_ID", "metadata":{"user-id":"$USER_ID"}}'

Sample Response:

{
  "data": {
    "id": "8d6a45e5-572f-8f13-d226-cd0d1ec57297",
    "aliases": null
    "metadata": {
      "user-id": "$USER_ID"
    }
  }
}
  1. Create an Entity Alias:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/entity-alias" -d '{"name":"$IAM_ARN", "canonical_id":"entity_canonical_id", "mount_accessor":"auth_aws_123", "custom_metadata":{"user-id":"$USER_ID"}}'

Sample Response:

{
  "data": {
    "canonical_id": "404e57bc-a0b1-a80f-0a73-b6e92e8a52d3",
    "id": "34982d3d-e3ce-5d8b-6e5f-b9bb34246c31"
  }
}
  1. Update identity group with entity member:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/group/id/363926d8-dd8b-c9f0-21f8-7b248be80ce1" -d '{"member_entity_ids":["8d6a45e5-572f-8f13-d226-cd0d1ec57297"]}'

User Workflow

  1. Login to Vault with AWS IAM Role:

curl -X POST "http://127.0.0.1:8200/v1/auth/aws/login" -d '{"role":"$USER_ID", "iam_http_request_method": "POST", "iam_request_url": "aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8=", "iam_request_body": "QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ==", "iam_request_headers": "eyJDb250ZW50LUxlbmd0aCI6IFsiNDMiXSwgIlVzZXItQWdlbnQiOiBbImF3cy1zZGstZ28vMS40LjEyIChnbzEuNy4xOyBsaW51eDsgYW1kNjQpIl0sICJYLVZhdWx0LUFXU0lBTS1TZXJ2ZXItSWQiOiBbInZhdWx0LmV4YW1wbGUuY29tIl0sICJYLUFtei1EYXRlIjogWyIyMDE2MDkzMFQwNDMxMjFaIl0sICJDb250ZW50LVR5cGUiOiBbImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZDsgY2hhcnNldD11dGYtOCJdLCAiQXV0aG9yaXphdGlvbiI6IFsiQVdTNC1ITUFDLVNIQTI1NiBDcmVkZW50aWFsPWZvby8yMDE2MDkzMC91cy1lYXN0LTEvc3RzL2F3czRfcmVxdWVzdCwgU2lnbmVkSGVhZGVycz1jb250ZW50LWxlbmd0aDtjb250ZW50LXR5cGU7aG9zdDt4LWFtei1kYXRlO3gtdmF1bHQtc2VydmVyLCBTaWduYXR1cmU9YTY5ZmQ3NTBhMzQ0NWM0ZTU1M2UxYjNlNzlkM2RhOTBlZWY1NDA0N2YxZWI0ZWZlOGZmYmM5YzQyOGMyNjU1YiJdfQ==" }'

  1. Generate a signed ID token as the AWS IAM Role:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/oidc/token/$DOMAIN_ID"

Sample Response:

{
  "data": {
    "client_id": "P6CfCzyHsQY4pMcA6kWAOCItA7",
    "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjJkMGI4YjlkLWYwNGQtNzFlYy1iNjc0LWM3MzU4NDMyYmM1YiJ9.eyJhdWQiOiJQNkNmQ3p5SHNRWTRwTWNBNmtXQU9DSXRBNyIsImV4cCI6MTU2MTQ4ODQxMiwiaWF0IjoxNTYxNDAyMDEyLCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tOjEyMzQiLCJzdWIiOiI2YzY1ZWFmNy1kNGY0LTEzMzMtMDJiYy0xYzc1MjE5YzMxMDIifQ.IcbWTmks7P5eVtwmIBl5rL1B88MI55a9JJuYVLIlwE9aP_ilXpX5fE38CDm5PixDDVJb8TI2Q_FO4GMMH0ymHDO25ZvA917WcyHCSBGaQlgcS-WUL2fYTqFjSh-pezszaYBgPuGvH7hJjlTZO6g0LPCyUWat3zcRIjIQdXZum-OyhWAelQlveEL8sOG_ldyZ8v7fy7GXDxfJOK1kpw5AX9DXJKylbwZTBS8tLb-7edq8uZ0lNQyWy9VPEW_EEIZvGWy0AHua-Loa2l59GRRP8mPxuMYxH_c88x1lsSw0vH9E3rU8AXLyF3n4d40PASXEjZ-7dnIf4w4hf2P4L0xs_g",
    "ttl": 86400
  }
}
  1. Introspect a signed ID token:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/oidc/introspect" -d '{"client_id":"sts.amazonaws.com", "token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjJkMGI4YjlkLWYwNGQtNzFlYy1iNjc0LWM3MzU4NDMyYmM1YiJ9.eyJhdWQiOiJQNkNmQ3p5SHNRWTRwTWNBNmtXQU9DSXRBNyIsImV4cCI6MTU2MTQ4ODQxMiwiaWF0IjoxNTYxNDAyMDEyLCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tOjEyMzQiLCJzdWIiOiI2YzY1ZWFmNy1kNGY0LTEzMzMtMDJiYy0xYzc1MjE5YzMxMDIifQ.IcbWTmks7P5eVtwmIBl5rL1B88MI55a9JJuYVLIlwE9aP_ilXpX5fE38CDm5PixDDVJb8TI2Q_FO4GMMH0ymHDO25ZvA917WcyHCSBGaQlgcS-WUL2fYTqFjSh-pezszaYBgPuGvH7hJjlTZO6g0LPCyUWat3zcRIjIQdXZum-OyhWAelQlveEL8sOG_ldyZ8v7fy7GXDxfJOK1kpw5AX9DXJKylbwZTBS8tLb-7edq8uZ0lNQyWy9VPEW_EEIZvGWy0AHua-Loa2l59GRRP8mPxuMYxH_c88x1lsSw0vH9E3rU8AXLyF3n4d40PASXEjZ-7dnIf4w4hf2P4L0xs_g"}'

Sample Response:

{
  "active": true,
  "iss": "https://10.1.1.45:8200/v1/identity/oidc",
  "sub": "a2cd63d3-5364-406f-980e-8d71bb0692f5",
  "aud": "SxSouteCYPBoaTFy94hFghmekos",
  "iat": 1561411915,
  "exp": 1561412215,
  "userid": "bob",
  "groups": ["$DOMAIN_ID"],
  "arn-id": "arn:aws:iam:111122223333:role/myRole",
}

AWS Cloud API flow

  1. Configure an AWS IAM role for Vault OIDC identity provider.

The following example AWS IAM trust policy limits acess to the defined vault identity group.

data "tls_certificate" "default" {
  url = "https://vault.example.org"
}

resource "aws_iam_openid_connect_provider" "default" {
  url             = "https://vault.example.org"
  client_id_list  = ["aws.workload.identity"]
  thumbprint_list = [data.tls_certificate.default.certificates.0.sha1_fingerprint]
}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::012345678910:oidc-provider/token.vault.company.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.vault.company.com:aud": "sts.amazonaws.com",
        }
        "ForAllValues:StringEquals": {
          "token.vault.company.com:groups": [
            "$DOMAIN_ID"
          ]
        }
      }
    }
  ]
}
data "aws_iam_policy_document" "default" {
  version = "2012-10-17"

  statement {
    sid     = "FederatedTrustVaultOIDC"
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = tolist(aws_iam_openid_connect_provider.default.arn)
    }

    condition {
      test     = "StringEquals"
      variable = "vault.example.com:aud"
      values   = ["sts.amazonaws.com"]
    }

    condition {
      test     = "ForAllValues:StringEquals"
      variable = "vault.example.com:groups"
      values   = [var.domain_id]
    }
  }
}

resource "aws_iam_policy" "default" {
  name   = "FederatedOIDC"
  path   = "/"
  policy = data.aws_iam_policy_document.default.json
}
data "aws_iam_policy_document" "default" {
version = "2012-10-17"
statement {
sid = "FederatedTrustVaultOIDC"
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity", ]
principals {
type = "Federated"
identifiers = ["arn:aws:iam::${var.account}:oidc-provider/vault.example.com"]
}
condition {
test = "StringEquals"
variable = "vault.example.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "ForAllValues:StringEquals"
variable = "vault.example.com:groups"
values = ["aws_reader"]
}
}
}
resource "aws_iam_policy" "default" {
name = "FederatedOIDC"
path = "/"
policy = data.aws_iam_policy_document.default.json
}
locals {
aws_identity {
iam_alias = "role_id"
}
group_id = var.identity_group_id
application = var.application_name
mount_accessor = var.mount_accessor
backend_path = "aws"
}
data "aws_iam_role" "default" {
name = var.aws_iam_role
}
resource "vault_auth_backend" "default" {
type = "aws"
}
resource "vault_aws_auth_backend_client" "default" {
backend = vault_auth_backend.default.path
access_key = "INSERT_AWS_ACCESS_KEY"
secret_key = "INSERT_AWS_SECRET_KEY"
}
resource "vault_generic_endpoint" "default" {
disable_delete = true
path = "auth/aws/config/identity"
ignore_absent_fields = true
data_json = jsonencode(local.aws_identity)
}
// run for every AWS IAM Role
resource "vault_aws_auth_backend_role" "default" {
backend = vault_auth_backend.aws.path
role = var.aws_iam_role
auth_type = "iam"
bound_iam_role_arns = tolist(data.aws_iam_role.default.arn)
}
resource "vault_identity_entity" "default" {
name = var.aws_iam_role
metadata = {
notebook_id = local.application
role = "aws_oidc"
}
}
resource "vault_identity_entity_alias" "default" {
name = data.aws_iam_role.default.id
mount_accessor = local.mount_accessor
canonical_id = vault_identity_entity.default.id
}
resource "vault_identity_group_member_entity_ids" "default" {
member_entity_ids = [vault_identity_entity.default.id]
exclusive = false
group_id = local.group_id
}
locals {
template_hcl {
notebook_id = "{{identity.entity.metadata.notebook_id}}"
nbf = "{{time.now}}"
}
template = base64encode(jsonencode(local.template_hcl))
}
resource "vault_identity_oidc" "default" {
issuer = "https://vault.example.org"
}
resource "vault_identity_oidc_key" "default" {
name = var.application_id
algorithm = "RS256"
allowed_client_ids = ["*"]
}
resource "vault_identity_oidc_role" "default" {
name = "aws_oidc"
key = vault_identity_oidc_key.key.name
client_id = "sts.amazonaws.com"
template = local.template
}
resource "vault_identity_oidc_key_allowed_client_id" "default" {
key_name = vault_identity_oidc_key.key.name
allowed_client_id = vault_identity_oidc_role.role.client_id
}
data "vault_policy_document" "default" {
rule {
path = "identity/oidc/token/{{identity.entity.metadata.role}}"
capabilities = ["read"]
description = "allow generate signed id token named after metadata keys"
}
}
resource "vault_policy" "default" {
name = "signed_id_token"
policy = data.vault_policy_document.default.hcl
}
resource "vault_identity_group" "default" {
name = "signed_id_token"
type = "internal"
external_policies = true
external_member_entity_ids = true
}
resource "vault_identity_group_policies" "default" {
group_id = vault_identity_group.default.id
exclusive = true
policies = [
"default",
vault_policy.default.name,
]
}

Run Once

  1. Create a named key used by a role to sign tokens.

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/oidc/key/aws_reader" -d '{"allowed_client_ids":["*"]}'

  1. Create an Identity Role; ID tokens are generated against a role & signed against a named key.

Sample Template (string-ified JSON):

{
  "userid": {{identity.entity.aliases.oidc_accessor_123.metadata.userid}}
  "username": {{identity.entity.aliases.oidc_accessor_123.metadata.username}}
  "workspace-id": {{identity.entity.aliases.oidc_accessor_123.metadata.workspace-id}}
  "groups": {{identity.entity.groups.names}}
}

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/oidc/role/aws_reader" -d '{"key":"aws_reader", "client_id":"sts.amazonaws.com", "template":"base64adda234q3b3"}'

NOTE: the value of client_id will render as the value of aud in the rendered claims.

  1. Create an ACL policy against the path for requesting a signed token:

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/sys/policy/aws_reader" -d '{"policy":"path \"identity/oidc/token/aws_reader\" {..."}'

  1. Create a Vault Identity Group.

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/identity/group" -d '{"name":"aws_reader", "policies":["aws"]}'

Sample Response:

{
  "data": {
    "id": "363926d8-dd8b-c9f0-21f8-7b248be80ce1",
    "name": "aws_reader"
  }
}

Run for every user

  1. Create JWT auth role:

Sample Payload:

{
  "bound_audiences": "https://vault.example.org",
  "user_claim": "upn",
  "bound_claims": {
    "workspace": "WS1",
    "unique_name": "Bob",
  },
  "claim_mappings": {
    "upn": "username",
    "oidc_id": "userid",
    "workspace": "workspace-id"
  }
}

NOTE: There is an opportunity to map the claims from the original OIDC to entity-alias metadata, allowing a form of consistency when aligning multiple aliases to a single entity.

curl -X POST -H "X-Vault-Token:123" "http://127.0.0.1:8200/v1/auth/jwt/role/$USER_ID" -d @payload.json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment