Skip to content

Commit

Permalink
Support AWS IAM Anywhere (#6)
Browse files Browse the repository at this point in the history
* Add Terraform work for X509 setup with AWS IAM Anywhere

* Change to file

* Fix things in Terraform

* Start of the updates to readme

* Add extra binary

* Add helm work

* Fix typo

* Change aws-spiffe-workload-helper version

* Add clarification in README

---------

Co-authored-by: Mattias Gees <[email protected]>
  • Loading branch information
MattiasGees and Mattias Gees authored Feb 6, 2025
1 parent c31a511 commit 9431473
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 43 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ go.work

# Certs folders
certs
root.pem

# Compiled binary
/spiffe-demo
Expand All @@ -32,3 +33,6 @@ certs
.terraform.lock.hcl
terraform.tfstate
terraform.tfstate.backup

# Environment variables file
env.sh
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.23.4 as builder
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.23.4 AS builder

ARG TARGETPLATFORM
ARG BUILDPLATFORM
Expand Down Expand Up @@ -39,5 +39,6 @@ USER 1001
COPY --from=builder /workspace/bin/spiffe-demo /usr/bin/spiffe-demo
COPY --from=tools-builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=tools-builder /spiffe-aws-assume-role /usr/bin/spiffe-aws-assume-role
COPY --from=ghcr.io/spiffe/aws-spiffe-workload-helper:0.0.1-rc.7 /ko-app/cmd /usr/bin/aws-spiffe-workload-helper

ENTRYPOINT ["/usr/bin/spiffe-demo"]
55 changes: 52 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The customer is the entry point for customers through an Ingress. It serves a si

1. Connect to a SPIFFE server backend. This connects to another application that runs with the backend subcommand. The connection is SPIFFE authenticated and authorized. This showcases the potential when SPIFFE is integrated in the application layer.
1. Connect to a non-SPIFFE server backend. connects to another application that runs with the httpbackend subcommand. In the Kubernetes deployment we have put an Envoy in front that will authenticate and authorize the SPIFFE connection. This showcases the potential when SPIFFE can't be integrated in the application layer
1. Talk to AWS Services. This writes and reads from an AWS S3 bucket with a SPIFFE JWT identity. In the container we abstract everything away from the application (for the application it is as it would run natively in AWS). This is done through the [spiffe-aws-assume-role](https://github.com/MattiasGees/spiffe-aws-assume-role) binary. That binary gets called through the AWS Profile [`credential_process`](TODO).
1. Talk to AWS S3 Service. This writes and reads from an AWS S3 bucket with a SPIFFE JWT identity. In the container we abstract everything away from the application (for the application it is as it would run natively in AWS). This is done through the [spiffe-aws-assume-role](https://github.com/MattiasGees/spiffe-aws-assume-role) binary. That binary gets called through the AWS Profile [`credential_process`](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-sourcing-external.html). Alternatively you can also use the X.509 authentication with [AWS IAM Roles Anywhere](https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html) and the [aws-spiffe-workload-helper](https://github.com/spiffe/aws-spiffe-workload-helper).
1. Talk to Google Cloud Service. This writes and reads from an GCS bucket with a SPIFFE JWT identity. In the container we abstract everything away from the application (for the application it is as it would run natively in Google Cloud). This is done through the [spiffe-gcp-proxy](https://github.com/GoogleCloudPlatform/professional-services/tree/main/tools/spiffe-gcp-proxy) proxy. That proxy gets called when making a call to the internal metadata API of Google Cloud.
1. Talk to a PostgreSQL database with its SVID. It writes a randomly generated user to a database every time you click the button. With the retrieval function it will retrieve all previous generated users from the database. No username or password authentication is required. It uses the [SPIFFE-helper](https://github.com/spiffe/spiffe-helper/) to let PostgreSQL consume the SVID that it got issued. The SPIFFE-helper is responsible for writing it to an in-memory filesystem that is accessible by the PostgreSQL container and than reloads the PostgreSQL config to make sure that PostgreSQL is aware of the latest certificates. As PostgreSQL doesn't understand SPIFFE IDs, it does verification based on the CN on the X.509. By configuring SPIRE in such a way, it will create those extra entries for the application SVID and that way it can authenticate and authorize itself to PostgreSQL
1. A SPIFFE retriever endpoint `HOSTNAME/spifferetriever` to show the SVID details.
Expand Down Expand Up @@ -83,6 +83,10 @@ To be able to run this demo, there are a few prerequisites

The AWS and Google Cloud bits are optional. When you deploy the `spiffe-demo` application on your Kubernetes cluster, it will deploy everything but off-course if you haven't done the cloud provider setup those specific bits will not work.

#### Optional

For AWS, you can also decide to either use JWT (OIDC Federation) or X.509 (AWS IAM Anywhere) authentication. The default is AWS, If you wish to use X.509, change the value of the environment variable `AWS_AUTH` to `X509`.

### Prepare environment

Some values are going to be specific to your environment. We are going to prepare these now:
Expand All @@ -92,14 +96,15 @@ export DEMO_HOSTNAME=demo.yourdomain.com
export DEMO_ROGUE_HOSTNAME=demo-rogue.yourdomain.com
export OIDC_HOSTNAME=oidc.yourdomain.com
export AWS_BUCKET_NAME=myrandombucketname
export AWS_AUTH=JWT
export GCP_BUCKET_NAME=mygcpbucketname
export GCP_PROJECT_NAME=my-gcp-project-name
export GCP_PROJECT_NUMBER=11111111

sed -i '' "s/OIDC_HOSTNAME/$OIDC_HOSTNAME/g" deploy/spire/values.yaml
sed -i '' "s/OIDC_HOSTNAME/$OIDC_HOSTNAME/g; s/AWS_BUCKET_NAME/$AWS_BUCKET_NAME/g" deploy/terraform/aws/variables.tf
sed -i '' "s/OIDC_HOSTNAME/$OIDC_HOSTNAME/g; s/AWS_BUCKET_NAME/$AWS_BUCKET_NAME/g; s/JWT/$AWS_AUTH/g" deploy/terraform/aws/variables.tf
sed -i '' "s/OIDC_HOSTNAME/$OIDC_HOSTNAME/g; s/GCP_BUCKET_NAME/$GCP_BUCKET_NAME/g; s/GCP_PROJECT_NAME/$GCP_PROJECT_NAME/g" deploy/terraform/google/variables.tf
sed -i '' "s/AWS_BUCKET_NAME/$AWS_BUCKET_NAME/g; s/GCP_BUCKET_NAME/$GCP_BUCKET_NAME/g; s/GCP_PROJECT_NAME/$GCP_PROJECT_NAME/g; s/GCP_PROJECT_NUMBER/$GCP_PROJECT_NUMBER/g; s/DEMO_HOSTNAME/$DEMO_HOSTNAME/g; s/DEMO_ROGUE_HOSTNAME/$DEMO_ROGUE_HOSTNAME/g" deploy/chart/spiffe-demo/values.yaml
sed -i '' "s/AWS_BUCKET_NAME/$AWS_BUCKET_NAME/g; s/GCP_BUCKET_NAME/$GCP_BUCKET_NAME/g; s/GCP_PROJECT_NAME/$GCP_PROJECT_NAME/g; s/GCP_PROJECT_NUMBER/$GCP_PROJECT_NUMBER/g; s/DEMO_HOSTNAME/$DEMO_HOSTNAME/g; s/DEMO_ROGUE_HOSTNAME/$DEMO_ROGUE_HOSTNAME/g; s/AWS_AUTH/$AWS_AUTH/g" deploy/chart/spiffe-demo/values.yaml
```

### SPIRE
Expand All @@ -118,11 +123,54 @@ helm upgrade --install -n spire spire spire -f ./deploy/spire/values.yaml --repo

Make sure you have access to an AWS account through the CLI, Terraform will use the same method to create the necessary resources. Take a look at `deploy/terraform/aws/variables.tf` to verify the expected environment specific values.

##### (optional) X509

If you have set the environemnt variable `AWS_AUTH` to `X509` earlier, you need to create a `root.pem` file with the content of your root CA of your SPIRE server in `deploy/terraform/aws`. You can retrieve the root CA the following way:

```bash
# Create temp pod
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
labels:
app: spire-tools
name: spire-tools
namespace: default
spec:
containers:
- image: mattiasgees/spire-tools:latest
imagePullPolicy: Always
name: spire-tools
resources: {}
volumeMounts:
- mountPath: /spiffe-workload-api
name: spiffe-workload-api
readOnly: true
volumes:
- csi:
driver: csi.spiffe.io
readOnly: true
name: spiffe-workload-api
EOF

# Get root.pem
kubectl exec -it spire-tools -- sh -c 'spire-agent api fetch -socketPath /spiffe-workload-api/socket -output json | jq -r ".svids[0].bundle" | awk "BEGIN {print \"-----BEGIN CERTIFICATE-----\"} {print} END {print \"-----END CERTIFICATE-----\"}" | fold -w 64 > /tmp/root.pem && cat /tmp/root.pem' > deploy/terraform/aws/root.pem

# Delete temp pod
kubectl delete pod spire-tools

```

##### Deploy

```bash
cd deploy/terraform/aws
# Change variables.tf to match your environment before running the next command
terraform init
terraform apply
terraform output -json | jq -r '@sh "export AWS_ROLE_ARN=\(.role_arn.value)\nexport AWS_TRUST_ANCHOR_ARN=\(.trust_anchor_arn.value)\nexport AWS_PROFILE_ARN=\(.profile_arn.value)"' >env.sh
source env.sh
```

#### Google Cloud
Expand All @@ -141,6 +189,7 @@ terraform apply
Take a look `deploy/chart/spiffe-demo/values.yaml` to verify it matches your environment and after that do a Helm install.

```bash
sed -i '' "s/AWS_ROLE_ARN/$AWS_ROLE_ARN/g; s/AWS_TRUST_ANCHOR_ARN/$AWS_TRUST_ANCHOR_ARN/g; s/AWS_PROFILE_ARN/$AWS_PROFILE_ARN/g" deploy/chart/spiffe-demo/values.yaml
helm upgrade --install -n spiffe-demo2 spife-demo ./deploy/chart/spiffe-demo --create-namespace
```

Expand Down
6 changes: 6 additions & 0 deletions deploy/chart/spiffe-demo/templates/spiffe-customer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ spec:
env:
- name: AWS_ROLE_ARN
value: "{{- .Values.initContainer.awsRoleArn -}}"
- name: AWS_TRUST_ANCHOR_ARN
value: "{{- .Values.initContainer.awsTrustAnchorArn -}}"
- name: AWS_PROFILE_ARN
value: "{{- .Values.initContainer.awsProfileArn -}}"
- name: AWS_AUTH
value: "{{- .Values.initContainer.awsAuth -}}"
- name: JWT_AUDIENCE
value: "{{- .Values.initContainer.JWTAudience -}}"
- name: SPIFFE_ENDPOINT_SOCKET
Expand Down
5 changes: 4 additions & 1 deletion deploy/chart/spiffe-demo/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ spiffeApp:
initContainer:
imageName: mattiasgees/spiffe-demo-init
imageTag: latest
awsRoleArn: arn:aws:iam::228615251467:role/demo-spiffe-role
awsRoleArn: AWS_ROLE_ARN
awsTrustAnchorArn: AWS_TRUST_ANCHOR_ARN
awsProfileArn: AWS_PROFILE_ARN
awsAuth: AWS_AUTH
JWTAudience: demo

spiffeGcpProxy:
Expand Down
7 changes: 7 additions & 0 deletions deploy/initcontainer/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ set -e

mkdir -p /tmp/aws

if [ "$AWS_AUTH" == "X509" ]; then
cat > /tmp/aws/config <<EOL
[default]
credential_process = /usr/bin/aws-spiffe-workload-helper x509-credential-process --profile-arn ${AWS_PROFILE_ARN} --trust-anchor-arn ${AWS_TRUST_ANCHOR_ARN} --role-arn ${AWS_ROLE_ARN}
EOL
else
cat > /tmp/aws/config <<EOL
[default]
credential_process = /usr/bin/spiffe-aws-assume-role credentials --role-arn ${AWS_ROLE_ARN} --audience ${JWT_AUDIENCE} --workload-socket ${SPIFFE_ENDPOINT_SOCKET}
EOL
fi
39 changes: 39 additions & 0 deletions deploy/terraform/aws/jwt.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
data "tls_certificate" "oidc-certificate" {
count = var.auth-type == "JWT" ? 1 : 0
url = "https://${var.oidc-url}"
}

resource "aws_iam_openid_connect_provider" "oidc-spire" {
count = var.auth-type == "JWT" ? 1 : 0
url = "https://${var.oidc-url}"

client_id_list = [
"demo",
]

thumbprint_list = [data.tls_certificate.oidc-certificate[0].certificates[0].sha1_fingerprint]
}

resource "aws_iam_role" "oidc-spire-role" {
count = var.auth-type == "JWT" ? 1 : 0
name = "demo-spiffe-role"

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRoleWithWebIdentity",
Effect = "Allow",
Principal = {
Federated = aws_iam_openid_connect_provider.oidc-spire[0].arn,
},
Condition = {
StringEquals = {
"${var.oidc-url}:aud" = "demo",
"${var.oidc-url}:sub" = "${var.spiffe-id}"
}
}
},
],
})
}
39 changes: 1 addition & 38 deletions deploy/terraform/aws/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ terraform {
}
}

data "tls_certificate" "oidc-certificate" {
url = "https://${var.oidc-url}"
}

provider "aws" {
region = var.aws-region
}
Expand All @@ -24,42 +20,9 @@ resource "aws_s3_bucket" "oidc-test" {
}
}

resource "aws_iam_openid_connect_provider" "oidc-spire" {
url = "https://${var.oidc-url}"

client_id_list = [
"demo",
]

thumbprint_list = [data.tls_certificate.oidc-certificate.certificates[0].sha1_fingerprint]
}

resource "aws_iam_role" "oidc-spire-role" {
name = "demo-spiffe-role"

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRoleWithWebIdentity",
Effect = "Allow",
Principal = {
Federated = aws_iam_openid_connect_provider.oidc-spire.arn,
},
Condition = {
StringEquals = {
"${var.oidc-url}:aud" = "demo",
"${var.oidc-url}:sub" = "${var.spiffe-id}"
}
}
},
],
})
}

resource "aws_iam_role_policy" "s3" {
name = "demo-spiffe-policy"
role = aws_iam_role.oidc-spire-role.name
role = var.auth-type == "JWT" ? aws_iam_role.oidc-spire-role[0].name : aws_iam_role.x509-spire-role[0].name

policy = <<EOF
{
Expand Down
11 changes: 11 additions & 0 deletions deploy/terraform/aws/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
output "role_arn" {
value = var.auth-type == "JWT" ? aws_iam_role.oidc-spire-role[0].arn : aws_iam_role.x509-spire-role[0].arn
}

output "trust_anchor_arn" {
value = var.auth-type == "X509" ? aws_rolesanywhere_trust_anchor.x509-spire[0].arn : ""
}

output "profile_arn" {
value = var.auth-type == "X509" ? aws_rolesanywhere_profile.x509-spire[0].arn : ""
}
9 changes: 9 additions & 0 deletions deploy/terraform/aws/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ variable "spiffe-id" {
variable "aws-region" {
default = "eu-west-2"
}

variable "auth-type" {
description = "Authentication type to use either pick JWT or X509"
default = "JWT"
}

variable "root-CA" {
default = "root.pem"
}
44 changes: 44 additions & 0 deletions deploy/terraform/aws/x509.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
resource "aws_rolesanywhere_trust_anchor" "x509-spire" {
count = var.auth-type == "X509" ? 1 : 0
name = "spire-root-ca"
enabled = true
source {
source_data {
x509_certificate_data = file(var.root-CA)
}
source_type = "CERTIFICATE_BUNDLE"
}
}

resource "aws_rolesanywhere_profile" "x509-spire" {
count = var.auth-type == "X509" ? 1 : 0
name = "spire-x509-profile"
enabled = true
role_arns = [aws_iam_role.x509-spire-role[0].arn]
}

resource "aws_iam_role" "x509-spire-role" {
count = var.auth-type == "X509" ? 1 : 0
name = "demo-spiffe-role-x509"

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = ["sts:AssumeRole", "sts:TagSession", "sts:SetSourceIdentity"]
Effect = "Allow",
Principal = {
Service = "rolesanywhere.amazonaws.com",
},
Condition = {
StringLike = {
"aws:PrincipalTag/x509SAN/URI" = "${var.spiffe-id}",
}
ArnEquals = {
"aws:SourceArn" = aws_rolesanywhere_trust_anchor.x509-spire[0].arn
}
}
},
],
})
}

0 comments on commit 9431473

Please sign in to comment.