SIGv4 authenticated requests for Amazon VPC Lattice - Amazon VPC Lattice

SIGv4 authenticated requests for Amazon VPC Lattice

VPC Lattice uses Signature Version 4 (SIGv4) or Signature Version 4A (SIGv4A) for client authentication. For more information, see Signing AWS API requests in the IAM User Guide.

Considerations
  • VPC Lattice attempts to authenticate any request that is signed with SIGv4 or SIGv4A. The request fails without authentication.

  • VPC Lattice does not support payload signing. You must send an x-amz-content-sha256 header with the value set to "UNSIGNED-PAYLOAD".

Python

This example sends the signed requests over a secure connection to a service registered in the network. If you prefer to use requests, the botocore package simplifies the authentication process, but is not strictly required. For more information, see Credentials in the Boto3 documentation.

To install the botocore and awscrt packages, use the following command. For more information, see AWS CRT Python.

pip install botocore awscrt

In the following example, replace the placeholder values with your own values.

SIGv4
from botocore import crt import requests from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials import botocore.session if __name__ == '__main__': session = botocore.session.Session() signer = crt.auth.CrtS3SigV4Auth(session.get_credentials(), 'vpc-lattice-svcs', 'us-west-2') endpoint = 'https://user-02222f67d3a427111.1234abc.vpc-lattice-svcs.us-west-2.on.aws/create' data = "some-data-here" headers = {'Content-Type': 'application/json'} request = AWSRequest(method='POST', url=endpoint, data=data, headers=headers) request.context["has_streaming_input"] = True # payload signing is not supported signer.add_auth(request) prepped = request.prepare() response = requests.post(prepped.url, headers=prepped.headers, data=data)
SIGv4A
from botocore import crt import requests from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials import botocore.session if __name__ == '__main__': session = botocore.session.Session() signer = crt.auth.CrtS3SigV4AsymAuth(session.get_credentials(), 'vpc-lattice-svcs', 'us-west-2') endpoint = 'https://user-02222f67d3a427111.1234abc.vpc-lattice-svcs.us-west-2.on.aws/create' data = "some-data-here" headers = {'Content-Type': 'application/json'} request = AWSRequest(method='POST', url=endpoint, data=data, headers=headers) request.context["has_streaming_input"] = True # payload signing is not supported signer.add_auth(request) prepped = request.prepare() response = requests.post(prepped.url, headers=prepped.headers, data=data)

Java with interceptor

This example uses Amazon Request Signing Interceptor to handle request signing.

import com.amazonaws.http.AwsRequestSigningApacheInterceptor; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.signer.Aws4UnsignedPayloadSigner; import software.amazon.awssdk.regions.Region; import java.nio.charset.StandardCharsets; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; public class App { public static void main(String[] args) { var interceptor = new AwsRequestSigningApacheInterceptor( "vpc-lattice-svcs", Aws4UnsignedPayloadSigner.create(), // requires HTTPS DefaultCredentialsProvider.create(), Region.US_WEST_2.id() ); CloseableHttpClient client = HttpClients.custom() .addInterceptorLast(interceptor) .build(); var httpPost = new HttpPost("https://user-02222f67d3a427111.1234abc.vpc-lattice-svcs.us-west-2.on.aws/create"); httpPost.addHeader("content-type", "application/json"); var body = """ { "name": "Jane Doe", "job": "Engineer" } """; httpPost.setEntity(new ByteArrayEntity(body.getBytes(StandardCharsets.UTF_8))); try (var response = client.execute(httpPost)) { System.out.println(new String(response.getEntity().getContent().readAllBytes())); } catch (Exception e) { throw new RuntimeException(e); } } }

Java without interceptor

This example shows how you can perform request signing by using custom interceptors. It uses the default credentials provider class from AWS SDK for Java 2.x, which gets the correct credentials for you. If you would prefer to use a specific credential provider, you can select one from the AWS SDK for Java 2.x. The AWS SDK for Java allows only unsigned payloads over HTTPS. However, you can extend the signer to support unsigned payloads over HTTP.

import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.signer.Aws4UnsignedPayloadSigner; import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.regions.Region; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; public class App { public static void main(String[] args) { var signer = Aws4UnsignedPayloadSigner.create(); // requires HTTPS Map<String, String> headers = new HashMap<>(); headers.put("content-type", "application/json"); var body = """ { "name": "Jane Doe", "job": "Engineer" } """; String endpoint = "https://user-02222f67d3a427111.1234abc.vpc-lattice-svcs.us-west-2.on.aws/create"; var sdkRequest = SdkHttpFullRequest.builder().method(SdkHttpMethod.POST); sdkRequest.host("user-02222f67d3a427111.1234abc.vpc-lattice-svcs.us-west-2.on.aws"); sdkRequest.protocol("HTTPS"); sdkRequest.encodedPath("/create"); sdkRequest.contentStreamProvider(() -> new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); for (Map.Entry<String, String> header : headers.entrySet()) { sdkRequest.putHeader(header.getKey(), header.getValue()); } ExecutionAttributes attributes = ExecutionAttributes.builder() .put(AwsSignerExecutionAttribute.AWS_CREDENTIALS, DefaultCredentialsProvider.create().resolveCredentials()) .put(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, "vpc-lattice-svcs") .put(AwsSignerExecutionAttribute.SIGNING_REGION, Region.US_WEST_2) .build(); SdkHttpFullRequest prepRequest = signer.sign(sdkRequest.build(), attributes); HttpPost httpPost = new HttpPost(endpoint); for (Map.Entry<String, List<String>> header : prepRequest.headers().entrySet()) { if (header.getKey().equalsIgnoreCase("host")) { continue; } for(var value : header.getValue()) { httpPost.addHeader(header.getKey(), value); } } CloseableHttpClient client = HttpClients.custom().build(); httpPost.setEntity(new ByteArrayEntity(body.getBytes(StandardCharsets.UTF_8))); try (var response = client.execute(httpPost)){ System.out.println(new String(response.getEntity().getContent().readAllBytes())); } catch (IOException e) { throw new RuntimeException(e); } } }

Node.js

This example uses aws-crt NodeJS bindings to send a signed request using HTTPS.

To install the aws-crt package, use the following command.

npm -i aws-crt

If the AWS_REGION environment variable exists, the example uses the Region specified by AWS_REGION. The default Region is us-east-1.

SIGv4
const https = require('https') const crt = require('aws-crt') const { HttpRequest } = require('aws-crt/dist/native/http') function sigV4Sign(method, endpoint, service, algorithm) { const host = new URL(endpoint).host const request = new HttpRequest(method, endpoint) request.headers.add('host', host) // crt.io.enable_logging(crt.io.LogLevel.INFO) const config = { service: service, region: process.env.AWS_REGION ? process.env.AWS_REGION : 'us-east-1', algorithm: algorithm, signature_type: crt.auth.AwsSignatureType.HttpRequestViaHeaders, signed_body_header: crt.auth.AwsSignedBodyHeaderType.XAmzContentSha256, signed_body_value: crt.auth.AwsSignedBodyValue.UnsignedPayload, provider: crt.auth.AwsCredentialsProvider.newDefault() } return crt.auth.aws_sign_request(request, config) } if (process.argv.length === 2) { console.error(process.argv[1] + ' <url>') process.exit(1) } const algorithm = crt.auth.AwsSigningAlgorithm.SigV4; sigV4Sign('GET', process.argv[2], 'vpc-lattice-svcs').then( httpResponse => { var headers = {} for (const sigv4header of httpResponse.headers) { headers[sigv4header[0]] = sigv4header[1] } const options = { hostname: new URL(process.argv[2]).host, path: '/', method: 'GET', headers: headers } req = https.request(options, res => { console.log('statusCode:', res.statusCode) console.log('headers:', res.headers) res.on('data', d => { process.stdout.write(d) }) }) req.on('error', err => { console.log('Error: ' + err) }) req.end() } )
SIGv4A
const https = require('https') const crt = require('aws-crt') const { HttpRequest } = require('aws-crt/dist/native/http') function sigV4Sign(method, endpoint, service, algorithm) { const host = new URL(endpoint).host const request = new HttpRequest(method, endpoint) request.headers.add('host', host) // crt.io.enable_logging(crt.io.LogLevel.INFO) const config = { service: service, region: process.env.AWS_REGION ? process.env.AWS_REGION : 'us-east-1', algorithm: algorithm, signature_type: crt.auth.AwsSignatureType.HttpRequestViaHeaders, signed_body_header: crt.auth.AwsSignedBodyHeaderType.XAmzContentSha256, signed_body_value: crt.auth.AwsSignedBodyValue.UnsignedPayload, provider: crt.auth.AwsCredentialsProvider.newDefault() } return crt.auth.aws_sign_request(request, config) } if (process.argv.length === 2) { console.error(process.argv[1] + ' <url>') process.exit(1) } const algorithm = crt.auth.AwsSigningAlgorithm.SigV4Asymmetric; sigV4Sign('GET', process.argv[2], 'vpc-lattice-svcs').then( httpResponse => { var headers = {} for (const sigv4header of httpResponse.headers) { headers[sigv4header[0]] = sigv4header[1] } const options = { hostname: new URL(process.argv[2]).host, path: '/', method: 'GET', headers: headers } req = https.request(options, res => { console.log('statusCode:', res.statusCode) console.log('headers:', res.headers) res.on('data', d => { process.stdout.write(d) }) }) req.on('error', err => { console.log('Error: ' + err) }) req.end() } )