Created
July 14, 2026 01:03
-
-
Save mohashari/320608d8955990760ea2b15cd3678c1c to your computer and use it in GitHub Desktop.
Automating Ephemeral SPIFFE/SPIRE ID Attestation for AWS Fargate Tasks via KMS and OIDC — code snippets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| resource "aws_kms_key" "spiffe_attestation" { | |
| description = "Asymmetric KMS Key for SPIFFE/SPIRE Fargate Attestation" | |
| customer_master_key_spec = "RSA_2048" | |
| key_usage = "SIGN_VERIFY" | |
| deletion_window_in_days = 7 | |
| policy = jsonencode({ | |
| Version = "2012-10-17" | |
| Statement = [ | |
| { | |
| Sid = "Enable IAM User Permissions" | |
| Effect = "Allow" | |
| Principal = { | |
| AWS = "arn:aws:iam::112233445566:root" | |
| } | |
| Action = "kms:*" | |
| Resource = "*" | |
| }, | |
| { | |
| Sid = "Allow Fargate Tasks to Sign Claims" | |
| Effect = "Allow" | |
| Principal = { | |
| AWS = [ | |
| "arn:aws:iam::112233445566:role/payment-service-fargate-task-role", | |
| "arn:aws:iam::112233445566:role/auth-service-fargate-task-role" | |
| ] | |
| } | |
| Action = "kms:Sign" | |
| Resource = "*" | |
| Condition = { | |
| StringEquals = { | |
| "kms:SigningAlgorithm" = ["RSA_SIGN_PSS_2048_SHA_256"] | |
| } | |
| } | |
| } | |
| ] | |
| }) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "Version": "2012-10-17", | |
| "Statement": [ | |
| { | |
| "Sid": "AllowSPIREServerToVerifyAndDescribe", | |
| "Effect": "Allow", | |
| "Action": [ | |
| "kms:GetPublicKey", | |
| "kms:Verify" | |
| ], | |
| "Resource": "arn:aws:kms:us-west-2:112233445566:key/a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" | |
| }, | |
| { | |
| "Sid": "AllowSPIREServerToDescribeECSTasks", | |
| "Effect": "Allow", | |
| "Action": [ | |
| "ecs:DescribeTasks" | |
| ], | |
| "Resource": "*", | |
| "Condition": { | |
| "ArnEquals": { | |
| "ecs:cluster": "arn:aws:ecs:us-west-2:112233445566:cluster/production-core" | |
| } | |
| } | |
| } | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "iss": "arn:aws:ecs:us-west-2:112233445566:task/production-core/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", | |
| "sub": "arn:aws:iam::112233445566:role/payment-service-fargate-task-role", | |
| "aud": "spire-server.production.internal", | |
| "exp": 1783929600, | |
| "iat": 1783926000, | |
| "ecs": { | |
| "cluster": "arn:aws:ecs:us-west-2:112233445566:cluster/production-core", | |
| "task_definition": "arn:aws:ecs:us-west-2:112233445566:task-definition/payment-service:42" | |
| }, | |
| "public_key_fingerprint": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "context" | |
| "crypto/sha256" | |
| "encoding/hex" | |
| "encoding/json" | |
| "fmt" | |
| "time" | |
| "github.com/aws/aws-sdk-go-v2/config" | |
| "github.com/aws/aws-sdk-go-v2/service/kms" | |
| "github.com/aws/aws-sdk-go-v2/service/kms/types" | |
| ) | |
| type Assertion struct { | |
| Issuer string `json:"iss"` | |
| Subject string `json:"sub"` | |
| Audience string `json:"aud"` | |
| ExpiresAt int64 `json:"exp"` | |
| IssuedAt int64 `json:"iat"` | |
| ECSMetadata map[string]string `json:"ecs"` | |
| PublicKeyFingerprint string `json:"public_key_fingerprint"` | |
| } | |
| func SignAssertion(ctx context.Context, keyID string, pubKeyPEM []byte) ([]byte, []byte, error) { | |
| cfg, err := config.LoadDefaultConfig(ctx) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to load AWS SDK config: %w", err) | |
| } | |
| kmsClient := kms.NewFromConfig(cfg) | |
| // Hash the public key to embed in the assertion | |
| hasher := sha256.New() | |
| hasher.Write(pubKeyPEM) | |
| pubKeyHash := hex.EncodeToString(hasher.Sum(nil)) | |
| assertion := Assertion{ | |
| Issuer: "arn:aws:ecs:us-west-2:112233445566:task/production-core/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", | |
| Subject: "arn:aws:iam::112233445566:role/payment-service-fargate-task-role", | |
| Audience: "spire-server.production.internal", | |
| IssuedAt: time.Now().Unix(), | |
| ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), | |
| ECSMetadata: map[string]string{ | |
| "cluster": "arn:aws:ecs:us-west-2:112233445566:cluster/production-core", | |
| "task": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", | |
| }, | |
| PublicKeyFingerprint: pubKeyHash, | |
| } | |
| payloadBytes, err := json.Marshal(assertion) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to marshal assertion: %w", err) | |
| } | |
| payloadHash := sha256.Sum256(payloadBytes) | |
| // Request KMS to sign the hashed assertion | |
| signOutput, err := kmsClient.Sign(ctx, &kms.SignInput{ | |
| KeyId: &keyID, | |
| Message: payloadHash[:], | |
| MessageType: types.MessageTypeDigest, | |
| SigningAlgorithm: types.SigningAlgorithmSpecRsaSignPss2048Sha256, | |
| }) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to call KMS Sign: %w", err) | |
| } | |
| return payloadBytes, signOutput.Signature, nil | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package attester | |
| import ( | |
| "crypto" | |
| "crypto/rsa" | |
| "crypto/sha256" | |
| "crypto/x509" | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| ) | |
| type Verifier struct { | |
| CachedKMSPublicKey crypto.PublicKey // Loaded via kms:GetPublicKey | |
| } | |
| func (v *Verifier) VerifyFargateAssertion(payload []byte, signature []byte, expectedRole string) error { | |
| var assertion Assertion | |
| if err := json.Unmarshal(payload, &assertion); err != nil { | |
| return fmt.Errorf("failed to decode assertion JSON: %w", err) | |
| } | |
| // 1. Validate role membership and signature parameters | |
| if assertion.Subject != expectedRole { | |
| return fmt.Errorf("role mismatch: got %s, expected %s", assertion.Subject, expectedRole) | |
| } | |
| // 2. Local signature verification using the cached KMS public key | |
| rsaPubKey, ok := v.CachedKMSPublicKey.(*rsa.PublicKey) | |
| if !ok { | |
| return errors.New("cached KMS public key is not an RSA key") | |
| } | |
| payloadHash := sha256.Sum256(payload) | |
| // Verify using RSA-PSS | |
| err := rsa.VerifyPSS(rsaPubKey, crypto.SHA256, payloadHash[:], signature, &rsa.PSSOptions{ | |
| SaltLength: rsa.PSSSaltLengthEqualsHash, | |
| }) | |
| if err != nil { | |
| return fmt.Errorf("signature verification failed: %w", err) | |
| } | |
| return nil | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| NodeAttester "aws_kms_oidc" { | |
| plugin_data { | |
| kms_key_arn = "arn:aws:kms:us-west-2:112233445566:key/a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" | |
| oidc_provider_url = "https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E" | |
| allowed_task_roles = [ | |
| "arn:aws:iam::112233445566:role/payment-service-fargate-task-role", | |
| "arn:aws:iam::112233445566:role/auth-service-fargate-task-role" | |
| ] | |
| ecs_metadata_validation = true | |
| kms_pubkey_cache_ttl = "1h" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| spiffe_id_template: "spiffe://example.com/ns/{{ .ecs.cluster | cluster_name }}/sa/{{ .sub | role_name }}/task/{{ .ecs.task }}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment