As you may already have seen, the team at Container Solutions have recently announced The birth of the external secrets community, where multiple people and organizations are joining efforts to create a single External Secrets solution based on existing projects. The new Kubernetes operator integrates external secret management systems like AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault and many more, and is designed to enable the synchronization of secrets from external APIs into Kubernetes. The project has extensive documentation, and, of course, it’s Open Source!
However, it’s not always easy to know what we need to do for every possible case when it comes to configuring both providers and permissions. This series of posts is going to focus on how to set up external-secrets with different providers. This article we’ll explore how to set up external-secrets with AWS SecretsManager, Subsequent posts will cover how to set external-secrets with GCP Secret Manager, Azure KeyVault and Hashicorp Vault.
To properly follow this tutorial, make sure you have installed the following tools:
• awscli version 1.20.60 or above |
• minikube - or any other kubernetes cluster |
• external-secrets deployed |
• jq |
In addition, make sure you already set up your cli environment by running aws configure
before continuing through this guide.
External-secrets allows configuration of several authentication methods for the AWS Secrets Manager provider. This guide will focus on programmatic Authentication mainly because there is no need to spin up any AWS resources other than Secrets Manager
.
In order to do so, we need to do the following steps:
1. Configure AWS:
a. create an IAM policy;
b. create an IAM group and bind the policy;
c. create an IAM user and attach it to a group; and
d. create credentials for that user;
2. Configure External-Secrets
We first starting by creating a policy named secrets-reader
:
POLICY_ARN=$(aws iam create-policy --policy-name secrets-reader --policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets",
"secretsmanager:GetSecretValue"
],
"Resource": [
"*"
]
}
]
}' | jq -r .Policy.Arn)
We now create a group that will use this policy:
aws iam create-group --group-name secret-readers
aws iam attach-group-policy --policy-arn $POLICY_ARN --group-name secret-readers
Next, we create an username and attach it to the recently created-group:
aws iam create-user --user-name external-secrets
aws iam add-user-to-group --group-name secret-readers --user-name external-secrets
Finally, we create a set of credentials for that user, and add it as a secret in kubernetes:
aws iam create-access-key --user-name external-secrets > creds.json
ACCESS_KEY=$(cat creds.json | jq -r .AccessKey.AccessKeyId)
SECRET_KEY=$(cat creds.json | jq -r .AccessKey.SecretAccessKey)
kubectl create secret generic aws-secret --from-literal=access-key=$ACCESS_KEY --from-literal=secret=$SECRET_KEY
Now, let’s add some secrets in our secret Store!
aws secretsmanager create-secret \
--name super-secret \
--secret-string my-custom-secret \
--region us-east-2
The first thing we need to do is define a SecretStore
. This resource is where all backend-related configuration is going to be stored, to allow external-secrets to reach out to Secrets-Manager.
In the example we create a SecretStore
called my-secret-store
, using the resources we created from previous steps:
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1alpha1
kind: SecretStore
metadata:
name: my-secret-store
spec:
provider:
aws: # set secretStore provider to AWS.
service: SecretsManager # Configure service to be Secrets Manager
region: us-east-2 # Region where the secret is.
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-secret # References the secret we created
key: access-key
secretAccessKeySecretRef:
name: aws-secret
key: secret
EOF
The next thing is to create an ExternalSecret
definition. In this definition, we are going to indicate which secrets from a SecretStore
we want to synchronize into a Kubernetes Secrets object. Here is an example of an ExternalSecret
called my-external-secret
which gets the secret we created in AWS Secrets Manager:
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: my-external-secret
spec:
refreshInterval: 1m
secretStoreRef:
name: my-secret-store #The secret store name we have just created.
kind: SecretStore
target:
name: my-kubernetes-secret # Secret name in k8s
data:
- secretKey: password # which key it's going to be stored
remoteRef:
key: super-secret # Our secret-name goes here
EOF
And that’s it! You can see that a secret called `my-kubernetes-secret` is created in kubernetes by the external-secrets operator. We can evaluate the secret value by typing:
kubectl get secrets my-kubernetes-secret -o json | jq -r .data.password | base64 -d
We can also check ExternalSecret
status with the following command line:
kubectl get externalsecrets.external-secrets.io my-external-secret
NAME STORE REFRESH INTERVAL STATUS
my-external-secret my-secret-store 1m SecretSynced
The example above is a good, simple demo, but it isn’t exactly production ready. For one thing the policy secrets-reader
we have created is not enforcing. It allows external-secrets to get any information from the AWS Secrets Manager. Although this might be valid for small deployments, production-ready environments need to be more strict on which resource a given account can access. One way to enforce this restriction is to follow the least privilege principle. In this session we will show how to configure AWS Policies to achieve this goal.
But first, let’s add some more secrets so we can test the policy effect:
aws secretsmanager create-secret \
--name another-secret \
--secret-string this_is_another_secret \
--region us-east-2
aws secretsmanager create-secret \
--name forbidden-secret \
--secret-string "We won't see this" \
--region us-east-2
The first thing we need to do is to create a more strict policy. In order to do so, the only thing we need to do is to add specific Resources instead of a general ‘*’ policy. This example below creates a policy that allows access to super-secret
and another-secret
, but no access to forbidden-secret
.
STRICT_POLICY_ARN=$(aws iam create-policy --policy-name strict-policy --policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets",
"secretsmanager:GetSecretValue"
],
"Resource": [
"arn:aws:secretsmanager:us-east-2:<YOUR_ACCOUNT_ID>:secret:super-secret-<foobar>",
"arn:aws:secretsmanager:us-east-2:<YOUR_ACCOUNT_ID>:secret:another-secret-<foobar>"
]
}
]
}' | jq -r .Policy.Arn)
Note: Please remember to replace the secrets ARN with the proper value for your scenario! You can check secrets ARN with the following command:
aws secretmanager list-secrets --region us-east-2
Now, let’s detach the old policy and attach this new one:
aws iam detach-group-policy --policy-arn $POLICY_ARN --group-name secret-readers
aws iam attach-group-policy --policy-arn $STRICT_POLICY_ARN --group-name secret-readers
Finally, let’s create two more ExternalSecret
objects: one for another-external-secret
and one for forbidden-secret
:
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: another-external-secret
spec:
refreshInterval: 1m
secretStoreRef:
name: my-secret-store
kind: SecretStore
target:
name: another-kubernetes-secret
data:
- secretKey: key-in-kubernetes
remoteRef:
key: another-secret
EOF
/pre>
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: invalid-permissions
spec:
refreshInterval: 1m
secretStoreRef:
name: my-secret-store
kind: SecretStore
target:
name: should-not-be-created
data:
- secretKey: wont-be-created
remoteRef:
key: forbidden-secret
EOF
And that’s it! If we look to the ExternalSecret
status we will see:
kubectl get external-secrets
NAME STORE REFRESH INTERVAL STATUS
another-external-secret my-secret-store 1m SecretSynced
invalid-permissions my-secret-store 1m SecretSyncedError
my-external-secret my-secret-store 1m SecretSynced
which shows us that our invalid-permissions
resource indeed cannot access forbidden-secret
!
In this post we’ve seen how to configure AWS
and external-secrets
to allow using Secrets Manager as a provider. There are other supported authentication methods, if you are running on an EKS cluster. You can check that in the official external-secrets documentation.
Wrapping up what we have done:
SecretStore
and ExternalSecrets
In the next post, we’ll see how to configure GCP Secret Manager
as a SecretStore
! Stay tuned!