# AWS Lambda Layer

Pre-built Lambda Layer that fetches secrets from KeyEnv at cold start.

Source: https://keyenv.dev/docs/sdks/aws-lambda/

Pre-built Lambda Layer that automatically fetches secrets from KeyEnv and injects them into your Lambda function's environment variables.

## Features

- Fetches secrets once at cold start (minimal latency impact)
- Caches secrets in memory for warm invocations
- Supports Node.js 18/20 and Python 3.9/3.10/3.11/3.12
- Automatic retry with exponential backoff
- Falls back to existing env vars if KeyEnv is unreachable
- Zero code changes required

## Layer ARN

```
arn:aws:lambda:<region>:123456789012:layer:keyenv:<version>
```

Available in all AWS regions. Replace `<region>` with your region (e.g., `us-east-1`).

## Quick Start

1. Add the KeyEnv layer to your Lambda function
2. Set environment variables:
   - `KEYENV_TOKEN`: Your service token
   - `KEYENV_ENVIRONMENT`: Environment name (e.g., `production`)
3. Deploy - secrets are automatically loaded

## Environment Variables

| Variable | Description | Required | Default |
|----------|-------------|----------|---------|
| `KEYENV_TOKEN` | KeyEnv service token | Yes | - |
| `KEYENV_ENVIRONMENT` | Environment name | Yes | - |
| `KEYENV_PROJECT_ID` | Project ID. Optional if using project-scoped token. | No | - |
| `KEYENV_API_URL` | KeyEnv API URL | No | `https://api.keyenv.dev` |
| `KEYENV_CACHE_TTL` | Cache TTL in seconds (0 = cache forever) | No | `0` |

## Examples

### AWS Console

1. Open your Lambda function in the AWS Console
2. Go to **Layers** > **Add a layer**
3. Choose **Specify an ARN**
4. Enter: `arn:aws:lambda:us-east-1:123456789012:layer:keyenv:1`
5. Add environment variables in **Configuration > Environment variables**

### Serverless Framework

```yaml
# serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs20.x
  layers:
    - arn:aws:lambda:${aws:region}:123456789012:layer:keyenv:1
  environment:
    KEYENV_TOKEN: ${ssm:/keyenv/token}
    KEYENV_ENVIRONMENT: ${opt:stage, 'development'}

functions:
  api:
    handler: handler.main
    events:
      - http:
          path: /
          method: get
```

### AWS SAM

```yaml
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Runtime: nodejs20.x
    Layers:
      - arn:aws:lambda:!Ref AWS::Region:123456789012:layer:keyenv:1
    Environment:
      Variables:
        KEYENV_TOKEN: !Ref KeyEnvToken
        KEYENV_ENVIRONMENT: !Ref Environment

Parameters:
  KeyEnvToken:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /keyenv/token
  Environment:
    Type: String
    Default: development

Resources:
  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      CodeUri: ./src
      Events:
        Api:
          Type: Api
          Properties:
            Path: /
            Method: GET
```

### Terraform

```hcl
# main.tf
resource "aws_lambda_function" "api" {
  function_name = "my-api"
  runtime       = "nodejs20.x"
  handler       = "index.handler"
  filename      = "function.zip"

  layers = [
    "arn:aws:lambda:${var.region}:123456789012:layer:keyenv:1"
  ]

  environment {
    variables = {
      KEYENV_TOKEN       = var.keyenv_token
      KEYENV_ENVIRONMENT = var.environment
    }
  }
}

variable "region" {
  default = "us-east-1"
}

variable "keyenv_token" {
  sensitive = true
}

variable "environment" {
  default = "development"
}
```

### AWS CDK (TypeScript)

```typescript
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';

export class MyStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const keyenvLayer = lambda.LayerVersion.fromLayerVersionArn(
      this, 'KeyEnvLayer',
      `arn:aws:lambda:${this.region}:123456789012:layer:keyenv:1`
    );

    new lambda.Function(this, 'ApiFunction', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('src'),
      layers: [keyenvLayer],
      environment: {
        KEYENV_TOKEN: process.env.KEYENV_TOKEN!,
        KEYENV_ENVIRONMENT: 'production',
      },
    });
  }
}
```

### AWS CDK (Python)

```python
from aws_cdk import (
    Stack,
    aws_lambda as lambda_,
)
from constructs import Construct

class MyStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        keyenv_layer = lambda_.LayerVersion.from_layer_version_arn(
            self, "KeyEnvLayer",
            f"arn:aws:lambda:{self.region}:123456789012:layer:keyenv:1"
        )

        lambda_.Function(
            self, "ApiFunction",
            runtime=lambda_.Runtime.PYTHON_3_12,
            handler="index.handler",
            code=lambda_.Code.from_asset("src"),
            layers=[keyenv_layer],
            environment={
                "KEYENV_TOKEN": os.environ["KEYENV_TOKEN"],
                "KEYENV_ENVIRONMENT": "production",
            },
        )
```

### Pulumi (TypeScript)

```typescript
import * as aws from "@pulumi/aws";

const keyenvLayerArn = `arn:aws:lambda:${aws.config.region}:123456789012:layer:keyenv:1`;

const apiFunction = new aws.lambda.Function("api", {
    runtime: "nodejs20.x",
    handler: "index.handler",
    code: new pulumi.asset.FileArchive("./src"),
    layers: [keyenvLayerArn],
    environment: {
        variables: {
            KEYENV_TOKEN: config.requireSecret("keyenvToken"),
            KEYENV_ENVIRONMENT: "production",
        },
    },
});
```

## How It Works

1. **Cold Start**: When Lambda initializes, the layer's extension runs first
2. **Fetch Secrets**: The extension calls KeyEnv API to fetch secrets
3. **Inject**: Secrets are injected into the process environment variables
4. **Your Code Runs**: Your handler sees secrets as regular env vars
5. **Warm Invocations**: Cached secrets are reused (no API call)

```
┌─────────────────────────────────────────────────────┐
│                   Cold Start                         │
├─────────────────────────────────────────────────────┤
│  1. Lambda Runtime Initializes                      │
│  2. KeyEnv Layer Extension Runs                     │
│     └─> Fetches secrets from KeyEnv API             │
│     └─> Injects into process.env                    │
│  3. Your Handler Initializes                        │
│  4. Handler Invoked                                 │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│                  Warm Invocation                    │
├─────────────────────────────────────────────────────┤
│  1. Handler Invoked (secrets already in process.env)│
└─────────────────────────────────────────────────────┘
```

## Performance

| Scenario | Latency Impact |
|----------|---------------|
| Cold start (first invocation) | +50-150ms |
| Warm invocation | 0ms (cached) |
| KeyEnv unreachable | 0ms (fallback to existing env vars) |

The layer is optimized for minimal cold start impact:
- Single HTTP request to KeyEnv API
- Response caching for warm invocations
- Graceful fallback if API is unreachable

## Using Secrets in Your Code

### Node.js

```javascript
// Secrets are automatically available in process.env
export const handler = async (event) => {
  const databaseUrl = process.env.DATABASE_URL;
  const apiKey = process.env.API_KEY;

  // Use secrets normally
  const db = await connectToDatabase(databaseUrl);

  return {
    statusCode: 200,
    body: JSON.stringify({ message: 'Hello!' }),
  };
};
```

### Python

```python
import os

def handler(event, context):
    # Secrets are automatically available in os.environ
    database_url = os.environ['DATABASE_URL']
    api_key = os.environ['API_KEY']

    # Use secrets normally
    db = connect_to_database(database_url)

    return {
        'statusCode': 200,
        'body': json.dumps({'message': 'Hello!'})
    }
```

## Cache TTL

By default, secrets are cached forever for the lifetime of the Lambda instance. To refresh secrets periodically:

```yaml
# serverless.yml
environment:
  KEYENV_CACHE_TTL: "300"  # Refresh every 5 minutes
```

| TTL Value | Behavior |
|-----------|----------|
| `0` (default) | Cache forever (best performance) |
| `300` | Refresh every 5 minutes |
| `3600` | Refresh every hour |

## Setting Up Your Token

1. Go to your KeyEnv dashboard
2. Navigate to **Settings > Service Tokens**
3. Create a new token with:
   - **Scope**: Select your project (recommended)
   - **Permissions**: `secrets:read`
4. Store the token securely:
   - **Recommended**: AWS Systems Manager Parameter Store (SecureString)
   - **Alternative**: AWS Secrets Manager

### Using SSM Parameter Store

```bash
# Store token
aws ssm put-parameter \
  --name /keyenv/token \
  --value "your-service-token" \
  --type SecureString

# Reference in serverless.yml
environment:
  KEYENV_TOKEN: ${ssm:/keyenv/token}
```

## Security

- The service token should be stored in SSM Parameter Store or Secrets Manager
- Secrets are transmitted over HTTPS
- Secrets are cached in memory only (not written to disk)
- Lambda execution role needs no additional permissions

## Supported Runtimes

| Runtime | Supported |
|---------|-----------|
| Node.js 20.x | Yes |
| Node.js 18.x | Yes |
| Python 3.12 | Yes |
| Python 3.11 | Yes |
| Python 3.10 | Yes |
| Python 3.9 | Yes |
| Other runtimes | Use SDK instead |

## Self-Hosted KeyEnv

If you're running a self-hosted KeyEnv instance:

```yaml
environment:
  KEYENV_API_URL: https://keyenv.your-company.com
```

## Troubleshooting

### "Secrets not loading"

- Check CloudWatch logs for extension errors
- Verify `KEYENV_TOKEN` is set correctly
- Ensure the Lambda has internet access (VPC configuration)

### "Authentication failed"

- Verify your token is correct
- Check the token hasn't expired
- Ensure the token is stored correctly in SSM/environment

### "Access denied"

- The token may not have access to the specified project
- The token may not have access to the specified environment
- Check token permissions in KeyEnv dashboard

### "Timeout during initialization"

- The KeyEnv API may be unreachable
- Check VPC and security group configuration
- Ensure NAT Gateway or VPC endpoints are configured

### "High cold start latency"

- This is expected (~50-150ms overhead)
- Consider using Provisioned Concurrency for latency-sensitive functions
- The overhead only applies to cold starts
