Skip to content

Instantly share code, notes, and snippets.

@jimbo8098
Last active June 26, 2023 14:54
Show Gist options
  • Save jimbo8098/caf59ab131ea251dc8e1c8875fed560d to your computer and use it in GitHub Desktop.
Save jimbo8098/caf59ab131ea251dc8e1c8875fed560d to your computer and use it in GitHub Desktop.
Create an assumable IAM role to access AWS Secrets Manager parameters using the Secret Store CSI Provider for AWS (ASCP)
---
apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
kind: SecretProviderClass
metadata:
name: example-secret-provider
namespace: secrets-csi-test
spec:
provider: aws
secretObjects:
# Each distinct secretObject creates a Secret which can be used within the namespace. Note that any pod able to access
# the secret can access it's contents - hence the potential danger for the syncSecrets option. Be wary of this.
- secretName: example-secret
type: Opaque
data:
# objectName refers to the objectAlias defined within the parameters defined further down. If your example isn't
# as complex as mine, objectAlias may simply be the objectName of the secret - assuming it's a unique identifier.
- objectName: username
key: username
- objectName: password
key: password
# The secret will be created with a key named secret which contains "MySecret", the entire secret defined in
# objects below. This is really just to demonstrate that you can set an alias an use it.
- objectName: MySecret
key: secret
parameters:
# In the object parameter, we take the JSON formatted AWS secret using it's ARN and, using the jmesPath notation, extract the
# username and password fields. The aliases are used in the secretObjects section to create a Secret object when the provider
# is called
objects: |
- objectName: "<arn>"
objectAlias: MySecret # optional
jmesPath:
- path: username
objectAlias: username
- path: password
objectAlias: password
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: secrets-csi-test
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
serviceAccountName: deployment-service-account
volumes:
# Volume may contain multiple AWS secrets but is deployment specific. The secretProviderClass
# field defines which of the providers are used to create the secret.
- name: deployment-aws-secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: 'example-secret-provider'
containers:
- name: nginx-deployment
image: nginx
ports:
- containerPort: 80
volumeMounts:
# Mount the secret to the filesystem. You can read the contents of this secret
# just like any other secret. This does *not* require the use of syncSecrets but
# it is required to use that feature - even if you don't want to use the secret
# in this way.
- name: deployment-aws-secrets
mountPath: '/mnt/secrets-store'
readOnly: true
env:
# The secret created by secretProviderName is mounted as an environment variable
# This is an experimental feature at this time.
- name: USERNAME
valueFrom:
secretKeyRef:
# The secret named `example-secret` is created within the namespace of `secretProviderName`
# This needs to be within the same namespace as the pod. The secret is created during pod
# creation and removed during deletion.
name: example-secret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: example-secret
key: password
data "aws_iam_policy_document" "read-secret" {
statement {
actions = [
"secretsmanager:ListSecretVersionIds",
"secretsmanager:GetSecretValue",
"secretsmanager:GetResourcePolicy",
"secretsmanager:DescribeSecret"
]
resources = var.accessible_secret_arns
}
}
# Deduced from https://github.com/terraform-aws-modules/terraform-aws-iam/blob/5bdf6cbbb1612e1ad520c17781c0ebf533b42881/modules/iam-role-for-service-accounts-eks/main.tf#L38
# The module used here is usable but adds quite a lot of functionality which is not necessary for our purposes.
data "aws_iam_policy_document" "assume-role" {
dynamic "statement" {
for_each = {
main = {
provider_arn = var.eks_oidc_provider_arn
namespace_service_accounts = var.namespace_service_accounts
}
}
content {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [statement.value.provider_arn]
}
condition {
test = "StringEquals"
variable = "${replace(statement.value.provider_arn, "/^(.*provider/)/", "")}:sub"
values = [for sa in statement.value.namespace_service_accounts : "system:serviceaccount:${sa}"]
}
# https://aws.amazon.com/premiumsupport/knowledge-center/eks-troubleshoot-oidc-and-irsa/?nc1=h_ls
condition {
test = "StringEquals"
variable = "${replace(statement.value.provider_arn, "/^(.*provider/)/", "")}:aud"
values = ["sts.amazonaws.com"]
}
}
}
}
resource "aws_iam_policy" "read-secrets" {
name = "ReadSecrets"
description = "Provide read-only access to required secrets"
policy = data.aws_iam_policy_document.read-secret.json
}
resource "aws_iam_role" "role" {
name = "service-account"
description = "The service account used by the service account to access secret parameters"
assume_role_policy = data.aws_iam_policy_document.assume-role.json
}
resource "aws_iam_role_policy_attachment" "read-secret-attachment" {
role = aws_iam_role.role.name
policy_arn = aws_iam_policy.read-secrets.arn
}
# An annotation needs to be added to set the IAM role to assume which allows for situations where there may be
# multiple possible roles to assume. This is probably not very useful since it's not entirely dynamic. Setting
# the annotation using:
#
# kubectl annotate serviceaccount -n secrets-csi-test deployment-service-account <eks-pod-iam-role-arn>
#
# is probably better.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: deployment-service-account
namespace: secrets-csi-test
annotations:
eks.amazonaws.com/role-arn: <eks-pod-iam-role-arn>
variable "eks_oidc_provider_arn" {
type = string
description = "The ARN for the OIDC provider for the EKS cluster"
}
variable "namespace_service_accounts" {
type = list(string)
default = ["secrets-csi-test:deployment-service-account"]
description = "An array of namespace:service_account_names to create. Each of these SAs have access to the provided accessible_secret_arns"
}
variable "accessible_secret_arns" {
type = list(string)
description = "A list of ARNs which the service account should be able to access, e.g. postgres login"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment