
# Upstream AWS Service Auth Policy

Authenticate to AWS upstreams using an IAM access key pair, optionally exchanged
for an IAM role's temporary credentials via STS AssumeRole. This policy resolves
AWS credentials and makes them available to the AWS Lambda handler and to custom
code, which sign upstream requests with AWS Signature Version 4.

With this policy, you'll benefit from:

- **Reusable Credentials**: Resolve AWS credentials once and use them from the
  AWS Lambda handler or your own code via `AwsClient.fromContext(context)`
- **Role Assumption**: Optionally assume an IAM role (STS AssumeRole) to obtain
  short-lived, least-privilege temporary credentials
- **Automatic Token Management**: Temporary credentials are cached in memory and
  refreshed before they expire
- **Credential Security**: Store access keys securely in your Zuplo environment
  and keep them out of source control

To avoid storing any long-lived AWS keys at all, use the
`upstream-aws-federated-auth` policy instead, which uses Workload Identity
Federation.

:::caution{title="Beta"}

This policy is in beta. You can use it today, but it may change in non-backward compatible ways before the final release.

:::

:::info{title="Enterprise Feature"}

This policy is only available as part of our enterprise plans. It's free to try only any plan for development only purposes. If you would like to use this in production reach out to us: [sales@zuplo.com](mailto:sales@zuplo.com)

:::

## Configuration

The configuration shows how to configure the policy in the 'policies.json' document.

```json title="config/policies.json"
{
  "name": "my-upstream-aws-service-auth-inbound-policy",
  "policyType": "upstream-aws-service-auth-inbound",
  "handler": {
    "export": "UpstreamAwsServiceAuthInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "accessKeyId": "$env(AWS_ACCESS_KEY_ID)",
      "region": "us-east-1",
      "secretAccessKey": "$env(AWS_SECRET_ACCESS_KEY)"
    }
  }
}
```

### Policy Configuration

- `name` <code className="text-green-600">&lt;string&gt;</code> - The name of your policy instance. This is used as a reference in your routes.
- `policyType` <code className="text-green-600">&lt;string&gt;</code> - The identifier of the policy. This is used by the Zuplo UI. Value should be `upstream-aws-service-auth-inbound`.
- `handler.export` <code className="text-green-600">&lt;string&gt;</code> - The name of the exported type. Value should be `UpstreamAwsServiceAuthInboundPolicy`.
- `handler.module` <code className="text-green-600">&lt;string&gt;</code> - The module containing the policy. Value should be `$import(@zuplo/runtime)`.
- `handler.options` <code className="text-green-600">&lt;object&gt;</code> - The options for this policy. [See Policy Options](#policy-options) below.

### Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

- `accessKeyId` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The AWS access key ID. Store the value in an environment variable rather than in source.
- `secretAccessKey` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The AWS secret access key. Always store this in an environment variable.
- `region` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The AWS region. Used for the STS endpoint when 'roleArn' is set, and as the default signing region for consumers of the resolved credentials.
- `sessionToken` <code className="text-green-600">&lt;string&gt;</code> - The AWS session token, required only when the configured access key pair is itself a temporary credential.
- `roleArn` <code className="text-green-600">&lt;string&gt;</code> - An IAM role to assume using STS AssumeRole with the configured access keys. When set, the resolved credentials are the role's temporary credentials instead of the static keys.
- `externalId` <code className="text-green-600">&lt;string&gt;</code> - The external ID to pass to STS AssumeRole when the role's trust policy requires one.
- `roleSessionName` <code className="text-green-600">&lt;string&gt;</code> - The STS role session name recorded in AWS CloudTrail. Must match the pattern \[\\w+=,.@-\]{2,64}. Defaults to `"zuplo-gateway"`.
- `durationSeconds` <code className="text-green-600">&lt;number&gt;</code> - The lifetime in seconds of the temporary credentials requested from STS (900-43200). The assumed role's maximum session duration may further limit this value. Defaults to `3600`.
- `stsEndpoint` <code className="text-green-600">&lt;string&gt;</code> - Overrides the STS endpoint URL. Only needed for non-standard partitions such as AWS GovCloud or AWS China.
- `tokenRetries` <code className="text-green-600">&lt;number&gt;</code> - The number of times to retry fetching temporary credentials in the event of a failure. Defaults to `3`.
- `expirationOffsetSeconds` <code className="text-green-600">&lt;number&gt;</code> - The number of seconds before credential expiration at which cached credentials are discarded and refreshed. Defaults to `300`.

## Using the Policy

## Overview

The Upstream AWS Service Auth policy resolves AWS credentials and registers them
on the request context. Unlike the other upstream auth policies, it does **not**
add an `Authorization` header itself — AWS Signature Version 4 signs the exact
final request (method, host, path, query and body hash), which is only known
inside the handler. Instead, the AWS Lambda handler and your own code read the
resolved credentials and sign the requests they build.

There are two modes:

- **Static credentials** — the configured `accessKeyId` / `secretAccessKey`
  (and optional `sessionToken`) are used directly.
- **Assumed role** — when `roleArn` is set, the policy calls STS `AssumeRole`
  (signed with the configured keys) and uses the role's short-lived temporary
  credentials. These are cached in memory and refreshed before they expire.

## Using the credentials

### With the AWS Lambda handler

Attach this policy to a route whose handler is `awsLambdaHandler`, and omit the
handler's own `accessKeyId` / `secretAccessKey`. The handler will use the
credentials resolved by the policy.

```json
{
  "paths": {
    "/lambda/{path}*": {
      "x-zuplo-route": {
        "handler": {
          "export": "awsLambdaHandler",
          "module": "$import(@zuplo/runtime)",
          "options": {
            "region": "us-east-1",
            "functionName": "my-function"
          }
        },
        "policies": { "inbound": ["upstream-aws-service-auth"] }
      }
    }
  }
}
```

### With custom code (for example, API Gateway with IAM auth, or S3)

Attach the policy, then sign outbound requests with `AwsClient.fromContext`:

```typescript
import { AwsClient } from "@zuplo/runtime/aws";
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const aws = AwsClient.fromContext(context, {
    service: "execute-api",
    region: "us-east-1",
  });
  return aws.fetch(
    "https://abc123.execute-api.us-east-1.amazonaws.com/prod/orders",
  );
}
```

If a route does not attach the policy, you can run it on demand from custom code
with `await context.invokeInboundPolicy("upstream-aws-service-auth", request)`
before calling `AwsClient.fromContext`.

## Configuration

```json
{
  "name": "upstream-aws-service-auth",
  "policyType": "upstream-aws-service-auth",
  "handler": {
    "export": "UpstreamAwsServiceAuthInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "accessKeyId": "$env(AWS_ACCESS_KEY_ID)",
      "secretAccessKey": "$env(AWS_SECRET_ACCESS_KEY)",
      "region": "us-east-1",
      "roleArn": "arn:aws:iam::123456789012:role/my-gateway-role"
    }
  }
}
```

Store `accessKeyId` and `secretAccessKey` in environment variables using
`$env(...)` — never commit them to source.

Read more about [how policies work](/articles/policies)
