# Kubernetes Integration

Sync KeyEnv secrets to Kubernetes using External Secrets Operator (ESO).

Source: https://keyenv.dev/docs/guides/kubernetes/

Sync KeyEnv secrets to Kubernetes using the [External Secrets Operator](https://external-secrets.io/) (ESO).

## Overview

The External Secrets Operator is a Kubernetes operator that synchronizes secrets from external providers into Kubernetes Secrets. KeyEnv provides native ESO webhook endpoints, allowing you to sync your secrets directly into Kubernetes without deploying additional infrastructure.

### How It Works

1. ESO calls KeyEnv's webhook endpoints to fetch secret values
2. KeyEnv authenticates the request using your service token
3. ESO creates or updates Kubernetes Secrets with the fetched values
4. Secrets are automatically refreshed based on your configured interval

## Prerequisites

Before setting up the integration, ensure you have:

- **Kubernetes cluster** with kubectl access
- **External Secrets Operator** installed ([installation guide](https://external-secrets.io/latest/introduction/getting-started/))
- **KeyEnv service token** with read access to your project

### Creating a Service Token

1. Navigate to your project in the KeyEnv dashboard
2. Go to **Settings → Service Tokens**
3. Create a new token with **Read** scope
4. Copy the token (starts with `env_`)

## Setup

### Step 1: Create a Kubernetes Secret with Your KeyEnv Token

Store your KeyEnv service token as a Kubernetes Secret:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: keyenv-token
  namespace: external-secrets
stringData:
  token: "env_your_service_token_here"
```

Apply with:

```bash
kubectl apply -f keyenv-token.yaml
```

### Step 2: Create a ClusterSecretStore

Create a ClusterSecretStore that configures ESO to communicate with KeyEnv:

```yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: keyenv
spec:
  provider:
    webhook:
      url: "https://api.keyenv.dev/api/v1/eso/secret?project=<PROJECT_ID>&env={{ .remoteRef.property }}&key={{ .remoteRef.key }}"
      result:
        jsonPath: "$.value"
      headers:
        Authorization: "Bearer {{ .auth.token }}"
      secrets:
        - name: auth
          secretRef:
            namespace: external-secrets
            name: keyenv-token
            key: token
```

Replace `<PROJECT_ID>` with your KeyEnv project ID or slug.

Apply with:

```bash
kubectl apply -f keyenv-secretstore.yaml
```

### Step 3: Create an ExternalSecret

Create an ExternalSecret to sync specific secrets from KeyEnv:

```yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: my-app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: keyenv
    kind: ClusterSecretStore
  target:
    name: my-app-secrets
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: DATABASE_URL
        property: production
    - secretKey: API_KEY
      remoteRef:
        key: API_KEY
        property: production
```

This creates a Kubernetes Secret named `my-app-secrets` containing:
- `DATABASE_URL` - fetched from KeyEnv's `production` environment
- `API_KEY` - fetched from KeyEnv's `production` environment

The `property` field specifies the KeyEnv environment, and `key` specifies the secret name.

## Bulk Export with dataFrom

To sync all secrets from an environment at once, use the bulk export endpoint with `dataFrom`.

### Create a Bulk ClusterSecretStore

```yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: keyenv-bulk
spec:
  provider:
    webhook:
      url: "https://api.keyenv.dev/api/v1/eso/secrets?project=<PROJECT_ID>&env={{ .remoteRef.key }}"
      result:
        jsonPath: "$.data"
      headers:
        Authorization: "Bearer {{ .auth.token }}"
      secrets:
        - name: auth
          secretRef:
            namespace: external-secrets
            name: keyenv-token
            key: token
```

### Create an ExternalSecret with dataFrom

```yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: all-production-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: keyenv-bulk
    kind: ClusterSecretStore
  target:
    name: production-secrets
  dataFrom:
    - extract:
        key: production
```

This syncs **all secrets** from the `production` environment into a single Kubernetes Secret named `production-secrets`.

## Environment Inheritance

KeyEnv supports environment inheritance, and this is fully supported by the ESO integration:

- **Single secret**: If the requested secret is not found in the target environment, the inheritance chain is searched upward to parent environments.
- **Bulk export**: Returns all secrets from the target environment and its parent chain, with child values taking precedence over parent values for duplicate keys.

> **Note**
>
> If you've explicitly hidden an inherited secret in KeyEnv (overriding without a value), that secret will not be returned by the ESO endpoints for that environment.

## API Reference

KeyEnv provides two ESO-compatible webhook endpoints:

### GET /api/v1/eso/secret

Fetch a single secret value.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `project` | Yes | Project ID or slug |
| `env` | Yes | Environment name or ID |
| `key` | Yes | Secret key name |

**Response:**

```json
{ "value": "secret-value-here" }
```

### GET /api/v1/eso/secrets

Fetch all secrets as a key-value map (for ESO `dataFrom`).

| Parameter | Required | Description |
|-----------|----------|-------------|
| `project` | Yes | Project ID or slug |
| `env` | Yes | Environment name or ID |

**Response:**

```json
{
  "data": {
    "DATABASE_URL": "postgres://...",
    "API_KEY": "sk_live_..."
  }
}
```

### Error Codes

| Status | Description |
|--------|-------------|
| 400 | Missing required parameters or circular inheritance detected |
| 401 | Invalid or missing token |
| 403 | Token lacks access to project |
| 404 | Project, environment, or secret not found |
| 500 | Internal server error |

## Troubleshooting

### ExternalSecret Status Shows "SecretSyncedError"

Check the ExternalSecret status for details:

```bash
kubectl describe externalsecret my-app-secrets
```

Common causes:
- **401 Unauthorized**: Verify your KeyEnv token is correct and hasn't expired
- **403 Forbidden**: Ensure the token has read access to the specified project
- **404 Not Found**: Check that the secret key and environment name are correct

### Secrets Not Updating

1. Check the `refreshInterval` on your ExternalSecret
2. Verify ESO controller is running:
   ```bash
   kubectl get pods -n external-secrets
   ```
3. Check ESO controller logs:
   ```bash
   kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets
   ```

### ClusterSecretStore Shows "NotReady"

Validate the ClusterSecretStore status:

```bash
kubectl describe clustersecretstore keyenv
```

Common issues:
- Token secret not found in the specified namespace
- Incorrect secret key name (should be `token`)
- Network connectivity issues to `api.keyenv.dev`

### Testing the Connection

Test the KeyEnv endpoint directly:

```bash
curl -H "Authorization: Bearer env_your_token" \
  "https://api.keyenv.dev/api/v1/eso/secret?project=my-project&env=production&key=DATABASE_URL"
```

A successful response returns:

```json
{ "value": "your-secret-value" }
```

## Best Practices

1. **Use dedicated service tokens** - Create a token specifically for Kubernetes with minimal permissions
2. **Set appropriate refresh intervals** - Balance between freshness and API load (1h is a good default)
3. **Use `dataFrom` for many secrets** - More efficient than individual secret syncs
4. **Monitor ESO status** - Set up alerts for sync failures
5. **Test in non-production first** - Verify your setup works before deploying to production
