Complete a 3 Question Survey and Earn a re:Post Badge
Help improve AWS Support Official channel in re:Post and share your experience - complete a quick three-question survey to earn a re:Post badge!
Using SPIFFE and X.509-SVID with Amazon VPC Lattice for authentication and authorization
This article shows a step by step example of how to use SPIFFE-SVID with VPC Lattice for AuthN/Z
Co-authored with Mohak Kohli, Sr. SDE, VPC Lattice
Introduction
As applications grow more distributed, managing how services identify and trust each other becomes more challenging. Amazon VPC Lattice helps with routing and access control across services, but it still needs a way to verify the identity of a client. That’s where SPIFFE comes in. It defines a standard way to issue strong, workload-based identities that do not rely on IPs or shared secrets. In this post, we show how you can use SPIFFE-issued identities with VPC Lattice to control which services can talk to each other. We’ll walk through how to plug SPIRE (SPIFFE Runtime Identity) into your environment, map identities to Lattice policies, and build a setup that’s simple, scalable, and secure.
SPIFFE
SPIFFE (Secure Production Identity Framework for Everyone) is a set of open-source standards for securely identifying software systems in dynamic and heterogeneous environments. And today, many systems have already adopted SPIFFE for their workloads running in Kubernetes platforms or on EC2. The workload can obtain a SPIFFE ID which is a string that uniquely and specifically identifies the workload. Workload can authenticate itself with other systems through SVID - SPIFFE Verifiable Identity Document.
There are 2 forms of SVID:
- X.509-SVID
- JWT-SVID
Here is an example of X.509-SVID, workload identity
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
...
Signature Algorithm: ecdsa-with-SHA256
Issuer: C=US, O=SPIFFE
Subject: C=us, O=SPIFFE
X509v3 extensions:
...
X509v3 Subject Alternative Name:
URI:spiffe://example.org/service/orders
Check out the SPIFFE documentation for details on the SPIFFE-SVID X509: https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md
Amazon VPC Lattice auth policies
VPC Lattice auth policies are IAM policy documents that you attach to service networks or services to control whether a specified principal has access to a group of services or specific service. You can attach one auth policy to each service network or service that you want to control access to. Auth policies are different from IAM identity-based policies. IAM identity-based policies are attached to IAM users, groups, or roles and define what actions those identities can do on which resources. Auth policies are attached to services and service networks. VPC Lattice auth policies are specified using the same syntax as IAM policies. For more information, see Identity-based policies and resource-based policies in the IAM User Guide.
An auth policy contains the following elements:
- Principal – The person or application who is allowed access to the actions and resources in the statement. In an auth policy, the principal is the IAM entity who is the recipient of this permission. The principal is authenticated as an IAM entity to make requests to a specific resource, or group of resources as in the case of services in a service network. You must specify a principal in a resource-based policy. Principals can include accounts, users, roles, federated users, or AWS services. For more information, see AWS JSON policy elements: Principal in the IAM User Guide.
- Effect – The effect when the specified principal requests the specific action. This can be either Allow or Deny. By default, when you enable access control on a service or service network using IAM, principals have no permissions to make requests to the service or service network.
- Actions – The specific API action for which you are granting or denying permission. VPC Lattice supports actions that use the vpc-lattice-svcs prefix. For more information, see Actions defined by Amazon VPC Lattice Services in the Service Authorization Reference.
- Resources – The services that are affected by the action.
- Condition – Conditions are optional. You can use them to control when your policy is in effect. For more information, see Condition keys for Amazon VPC Lattice Services in the Service Authorization Reference.
For more information, see Control access to VPC Lattice services using auth policies in the VPC Lattice User Guide.
Using X.509-SVID and AWS IAM Roles Anywhere
AWS IAM Roles Anywhere allow your workloads to get temporary AWS credentials using X.509 Certificates issued by your own Public Key Infrastructure (PKI).
Here are the high-level steps:
- SPIRE issues X.509-SVIDs to your workloads.
- These certificates follow the SPIFFE spec and include identities like: spiffe://example.org/service/
- Export your SPIFFE CA (the CA key pair used by SPIRE) and import it into AWS ACM as a trusted source.
- Create a trust anchor in IAM Roles Anywhere pointing to that ACM CA.
- Create IAM Role and policy that allows VPC Lattice services invocation: vpc-lattice:InvokeService.
- Create IAM Roles Anywhere profile that maps SPIFFE identity SANs (Subject Alternative Names) to the Role.
- Your workloads call AWS STS via IAM Roles Anywhere and present their SPIFFE X.509-SVID. If it matches the trust policy, they get temporary credentials.
- Using the temporary credentials, workloads sign their requests using SIGv4 and invoke Services through VPC Lattice
Setup
Let’s assume two VPC Lattice services, “Billing” and “Rates”. These services need to be securely accessed by “Orders’ and ”Reviews“ services. I use multiple accounts and VPCs just to showcase the possibility of using this setup, but they are not required.
Here is the security policy and identities setup:
- “Orders” has X.509 Certificate which contains SPIFFE ID URI:spiffe://example.org/service/orders
- “Reviews” has X.509 Certificate which contains SPIFFE ID URI:spiffe://example.org/service/reviews
- VPC Lattice Auth policy has the following rules:
- workload “Orders” is able to access “Rates” and “Billing”
- workload “Reviews” is only able to access “Rates”, and not “Billing”
- other workloads not under “spiffe/example.org” cannot access “Billing” or “Rates”
Service-owner steps
Step 1: Create Lambda functions representing “Billing” and “Rates” services
“Billing” lambda function for VPC Lattice service “Billing” target group
For demo purpose, we create a lambda target group for “Billing” service. So, when client invoke “Billing” service using path “/lambda”, the following function is called. Recently, we have enhanced VPC Lattice to pass along caller’s identity to backend, such as the workload X.509-SVID. In the lambda use case, all of them are encoded in the lambda event structure
import json
def lambda_handler(event, context):
response = {
"statusCode": 200,
"statusDescription": "200 OK",
"isBase64Encoded": False,
"headers": {
"Content-Type": "text/html; charset=utf-8",
"Server": "Lambda"
}
}
response['body'] = json.dumps({
"message": "HelloWorld from Billing Lambda",
"event": event,
})
return response
“Rates” Lambda Function for Lattice “Rates” Service
We also created a lambda target group for “Rates” VPC Lattice service. So, when a client invokes “Rates” service using path “/lambda”, the following function is called.
import json
def lambda_handler(event, context):
response = {
"statusCode": 200,
"statusDescription": "200 OK",
"isBase64Encoded": False,
"headers": {
"Content-Type": "text/html; charset=utf-8",
"Server": "Lambda"
}
}
response['body'] = json.dumps({
"message": "HelloWorld from Rates Lambda",
"event": event,
})
return response
Step 2: Create VPC Lattice services “Billing” and “Rates”, with the respective Lambda functions as targets
VPC Lattice service “Billing”
Here is the VPC Lattice service definition for “Billing”
VPC Lattice service “Rates”
Here is the VPC Lattice service definition for “Rates”
Step 3: Configure VPC Lattice Auth Policies for “Billing” and “Rates“
For this post we are using VPC Lattice service-level Auth policies. You can also configure these at the VPC Lattice service network level, if you want.
Billing can only be accessed by “Orders”
{
"Statement": {
"Effect": "Allow",
"Principal": "*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws: PrincipalTag/x509SAN/URI": "spiffe://example.org/service/orders",
"aws: PrincipalARN": "arn:aws:iam::ACCOUNT-ID:role/spiffe-workload-roleanywhere"
}
},
"Action": "vpc-lattice-svcs:Invoke"
}
}
Rates can be accessed by Orders and Reviews
{
"Statement": {
"Effect": "Allow",
"Principal": "*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws: PrincipalTag/x509SAN/URI": [
"spiffe://example.org/service/orders",
"spiffe://example.org/service/reviews"
],
"aws: PrincipalARN": "arn:aws:iam::ACCOUNT-ID:role/spiffe-workload-roleanywhere"
}
},
"Action": "vpc-lattice-svcs:Invoke"
}
}
Security-owner steps
Step 4: Create Trust Anchor Using external root CA
Create IAM anywhere Trust Anchor using an external Certificate Authority (CA):
Step 5: Create and configure IAM Role for Roles Anywhere
Create an IAM Anywhere Role
For permissions, we selected VPCLatticeServicesInvokeAccess
Step 6: Create an IAM Anywhere Profile that maps the X509-SVID SAN (Subject Alternative Name) to the IAM Role
Step 7: Assign X509-SVID certificates to “Orders” and “Reviews”
Certificate for “Orders”
openssl x509 -text -in ./orders.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
[Output omitted]
Signature Algorithm: [Output omitted]
Issuer: C=US, ST=WA, L=Seattle, O=Exmaple, OU=IAM, CN=Roles Anywhere CA
Validity
[Output omitted]
Subject: C=US, ST=WA, L=Seattle, O=Example, OU=IAM, CN=Roles Anywhere Client
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (384 bit)
pub:
[Output omitted]
ASN1 OID: secp384r1
NIST CURVE: P-384
X509v3 extensions:
X509v3 Subject Key Identifier:
[Output omitted]
X509v3 Authority Key Identifier:
keyid:[Output omitted]
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Subject Alternative Name:
URI:spiffe/example.org/service/orders
Signature Algorithm: ecdsa-with-SHA512
[Output omitted]
-----BEGIN CERTIFICATE-----
[Output omitted]
-----END CERTIFICATE-----
Certificate for “Reviews“
openssl x509 -text -in ./reviews.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
[Output omitted]
Signature Algorithm: [Output omitted]
Issuer: C=US, ST=WA, L=Seattle, O=Example, OU=IAM, CN=Roles Anywhere CA
Validity
[Output omitted]
Subject: C=US, ST=WA, L=Seattle, O=Example, OU=IAM, CN=Roles Anywhere Client
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (384 bit)
pub:
[Output omitted]
ASN1 OID: secp384r1
NIST CURVE: P-384
X509v3 extensions:
X509v3 Subject Key Identifier:
[Output omitted]
X509v3 Authority Key Identifier:
keyid:[Output omitted]
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Subject Alternative Name:
URI:spiffe/example.org/service/reviews
Signature Algorithm: ecdsa-with-SHA512
[Output omitted]
-----BEGIN CERTIFICATE-----
[Output omitted]
-----END CERTIFICATE-----
Client-side steps
Step 8: Get temporary security credentials from IAM Roles Anywhere
To obtain temporary security credentials from AWS Identity and Access Management Roles Anywhere, use the credential helper tool that IAM Roles Anywhere provides. This tool is compatible with the credential_process feature available across the language SDKs. When used with an AWS SDK, these credentials automatically refresh before they expire, requiring no additional implementation for credential renewal. The helper manages the process of creating a signature with the certificate and calling the endpoint to obtain session credentials; it returns the credentials to the calling process in a standard JSON format. More details here.
../aws_signing_helper credential-process \
--certificate ./orders.pem \
--private-key ./client.key \
--profile-arn <ARN of the IAM ROLES ANYWHERE PROFILE YOU CREATED> \
--role-arn <ARN of the IAM ROLE FOR ROLES ANYWHERE YOU CREATED> \
--trust-anchor-arn <ARN OF TRUST ANCHOR YOU CREATED>
../aws_signing_helper credential-process \
--certificate ./reviews.pem \
--private-key ./client.key \
--profile-arn <ARN of the IAM ROLES ANYWHERE PROFILE YOU CREATED> \
--role-arn <ARN of the IAM ROLE FOR ROLES ANYWHERE YOU CREATED> \
--trust-anchor-arn <ARN OF TRUST ANCHOR YOU CREATED>
Step 9: Test access
Note: I use some basic client code to sign requests using SIGv4. You can find examples here: https://github.com/aws-samples/sigv4-signing-examples
Request from Orders to Billing - AUTHORIZED
./sigv4_client --http-method GET \
--lattice-dns http://billing-default-038ac14ba[Output omitted]6.7d67968.vpc-lattice-svcs.us-west-2.on.aws/lambda
# Response from Billing Lambda
############## response ####################
{
...
"method": "GET",
"path": "/lambda",
"requestContext": {
"identity": {
"principal": "arn:aws:sts::[Output omitted]:assumed-role/spiffe-workload-roleanywhere/[Output omitted]",
"sessionName": "[Output omitted]",
"sourceVpcArn": "arn:aws:ec2:us-west-2:[Output omitted]:vpc/vpc-05c7[Output omitted]",
"type": "AWS_IAM",
"x509IssuerOu": "IAM",
"x509SanUri": "spiffe://example.org/service/orders",
"x509SubjectCn": "Roles Anywhere Client"
},
"region": "us-west-2",
"serviceArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:service/svc-038a[Output omitted]",
"serviceNetworkArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:servicenetwork/sn-0df021[Output omitted]",
"targetGroupArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:targetgroup/tg-0b3f71[Output omitted]",
"timeEpoch": "[Output omitted]"
},
"version": "2.0"
},
"message": "HelloWorld from Billing Lambda"
}
Request from Orders to Rates - AUTHORIZED
./sigv4_client --http-method GET \
--lattice-dns http://rates-default-02a0f6dbe800130d5.7d67968.vpc-lattice-svcs.us-west-2.on.aws/lambda
# Response from Rates Lambda
{
...
"method": "GET",
"path": "/lambda",
"requestContext": {
"identity": {
"principal": "arn:aws:sts::[Output omitted]:assumed-role/spiffe-workload-roleanywhere/[Output omitted]",
"sessionName": "[Output omitted]",
"sourceVpcArn": "arn:aws:ec2:us-west-2:[Output omitted]:vpc/vpc-05c7322a3[Output omitted]",
"type": "AWS_IAM",
"x509IssuerOu": "IAM",
"x509SanUri": "spiffe://example.org/service/orders",
"x509SubjectCn": "Roles Anywhere Client"
},
"region": "us-west-2",
"serviceArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:service/svc-02a0f6d[Output omitted]",
"serviceNetworkArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:servicenetwork/sn-0df021[Output omitted]",
"targetGroupArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:targetgroup/tg-06d1d[Output omitted]",
"timeEpoch": "[Output omitted]"
},
"version": "2.0"
},
"message": "HelloWorld from Rates Lambda"
}
Request from Reviews to Rates - AUTHORIZED
./sigv4_client --http-method GET \
--lattice-dns http://rates-default-02a0f6dbe800130d5.7d67968.vpc-lattice-svcs.us-west-2.on.aws/lambda
# response
{
"event": {
...
"method": "GET",
"path": "/lambda",
"requestContext": {
"identity": {
"principal": "arn:aws:sts::[Output omitted]:assumed-role/spiffe-workload-roleanywhere/00d9fa[Output omitted]",
"sessionName": "[Output omitted]",
"sourceVpcArn": "arn:aws:ec2:us-west-2:[Output omitted]:vpc/vpc-05c7322a[Output omitted]",
"type": "AWS_IAM",
"x509IssuerOu": "IAM",
"x509SanUri": "spiffe://example.org/service/reviews",
"x509SubjectCn": "Roles Anywhere Client"
},
"region": "us-west-2",
"serviceArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:service/svc-02a0f[Output omitted]",
"serviceNetworkArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:servicenetwork/sn-0df0[Output omitted]",
"targetGroupArn": "arn:aws:vpc-lattice:us-west-2:[Output omitted]:targetgroup/tg-06d[Output omitted]",
"timeEpoch": "[Output omitted]"
},
"version": "2.0"
},
"message": "HelloWorld from Rates Lambda"
}
#
Request from Reviews to Billing - NOT AUTHORIZED
./sigv4_client --http-method GET \
> --lattice-dns http://billing-default-038ac1[Output omitted].7d67968.vpc-lattice-svcs.us-west-2.on.aws/lambda
############## response ####################
AccessDeniedException: User: arn:aws:sts::[Output omitted]:assumed-role/spiffe-workload-roleanywhere/[Output omitted] is not authorized to perform: vpc-lattice-svcs:Invoke on resource: arn:aws:vpc-lattice:us-west-2:[Output omitted]:service/svc-038ac14[Output omitted]/lambda because no service-based policy allows the vpc-lattice-svcs:Invoke action
Relevant content
- asked 2 years ago