The `BeyondtrustWorkloadCredentialsDynamicSecret` Generator provides an interface to BeyondTrust Workload Credentials's
dynamic secret generation capabilities. This enables obtaining temporary, short-lived credentials.

Dynamic secret definitions must be created in BeyondTrust Workload Credentials before they can be
referenced by the generator. The generator calls the generation endpoint to produce fresh credentials
each time it is invoked.

For complete BeyondTrust Workload Credentials API documentation, see: [https://docs.beyondtrust.com/bt-docs/docs/secrets-api](https://docs.beyondtrust.com/bt-docs/docs/secrets-api)

Any authentication method supported by the BeyondTrust Workload Credentials provider can be used here
(`provider` block of the spec).

## Example manifest

```yaml
{% include 'beyondtrustworkloadcredentials-dynamic-secret.yaml' %}
```

Example `ExternalSecret` that references the BeyondTrust Workload Credentials generator:
```yaml
{% include 'beyondtrustworkloadcredentials-dynamic-external-secret.yaml' %}
```

## Configuration

### Folder Path

The `folderPath` in the generator spec uses the format `{folder}/{secretName}`:
- `folder`: The folder containing the dynamic secret definition (e.g., `eso`)
- `secretName`: The name of the dynamic secret definition (e.g., `dynamic`)

For example, if your dynamic secret is stored at path `my/dynamic` in BeyondTrust Workload Credentials:

```yaml
spec:
  provider:
    folderPath: "my/dynamic"
```
### Generated Secret Fields

The generator maps the AWS-style fields returned by the BeyondTrust Workload Credentials dynamic-secret endpoint. The target Kubernetes secret always receives `accessKeyId`, `secretAccessKey`, `leaseId`, and `expiration`, plus `sessionToken` when the response includes one:

```yaml
stringData:
  accessKeyId: ASIAIOSFODNN7EXAMPLE
  secretAccessKey: wJal...YEKY
  sessionToken: IQoJ...Ek8=
  leaseId: 84038398-ec0f-417d-9a0f-02494fd7d22c
  expiration: 2025-12-29T22:35:29Z
```

Only these fields are populated. Dynamic-secret definitions that return a different shape are not yet mapped to additional keys.

### Credential Refresh and Expiration
**Important:** External Secrets Operator does NOT automatically handle credential expiration/TTL from BeyondTrust Workload Credentials. The refresh is controlled solely by the `refreshInterval` specified in the ExternalSecret spec.

#### Setting Refresh Interval

You should set `refreshInterval` to **less than** the credential lifetime to ensure credentials are refreshed before expiration:

```yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: aws-credentials
spec:
  refreshInterval: 45m  # If credentials expire in 1 hour
  target:
    name: aws-temp-creds
  dataFrom:
    - sourceRef:
        generatorRef:
          apiVersion: generators.external-secrets.io/v1alpha1
          kind: BeyondtrustWorkloadCredentialsDynamicSecret
          name: beyondtrustworkloadcredentials-ds
```

#### What happens if refreshInterval > credential expiration?

Credentials will expire before being refreshed. Users will see:
- ExternalSecret status: `SecretSyncError`
- Logs/events: Authorization errors when the application tries to use expired credentials
- The application will fail to authenticate with the target service

#### What happens if refreshInterval << credential expiration?

For example, if credentials expire in 1 hour but `refreshInterval: 1m`:
- New credentials are generated every minute
- Old credentials remain valid until their expiration time
- Multiple valid credential sets may exist simultaneously
- **These credentials expire automatically at their TTL in AWS** (for AssumeRole credentials).

**Recommendation:** Set `refreshInterval` to 75-80% of the credential lifetime. For example:
- 1-hour credentials → `refreshInterval: 45m`
- 12-hour credentials → `refreshInterval: 9h`
- 24-hour credentials → `refreshInterval: 18h`

### Generator Reusability

Generators are reusable Custom Resources. You can reference the same generator from multiple ExternalSecrets:

```yaml
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: app-1-aws-creds
spec:
  refreshInterval: 45m
  target:
    name: app-1-aws-credentials
  dataFrom:
    - sourceRef:
        generatorRef:
          kind: BeyondtrustWorkloadCredentialsDynamicSecret
          name: beyondtrustworkloadcredentials-ds
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: app-2-aws-creds
spec:
  refreshInterval: 45m
  target:
    name: app-2-aws-credentials
  dataFrom:
    - sourceRef:
        generatorRef:
          kind: BeyondtrustWorkloadCredentialsDynamicSecret
          name: beyondtrustworkloadcredentials-ds
```

**Important:** Each reference triggers a **new credential generation**. In the example above, `app-1` and `app-2` will receive different, independent sets of credentials.

### Authentication

The generator uses the same authentication mechanism as the BeyondTrust Workload Credentials provider (API key authentication):

```yaml
apiVersion: generators.external-secrets.io/v1alpha1
kind: BeyondtrustWorkloadCredentialsDynamicSecret
metadata:
  name: beyondtrustworkloadcredentials-ds
  namespace: external-secrets
spec:
  provider:
    auth:
      apikey:
        token:
          name: api-token
          key: token
```

Create the API token secret:
```bash
kubectl create secret generic api-token \
  --from-literal=token=<YOUR_API_TOKEN> \
  -n external-secrets
```

### Certificate Trust

If using self-signed certificates, configure trust using `caProvider`:

```yaml
spec:
  provider:
    # ... other config ...
    caProvider:
      type: Secret
      name: my-ca-bundle
      key: ca.crt
```

Create the CA bundle secret:
```bash
kubectl create secret generic my-ca-bundle \
  --from-file=ca.crt="/path/to/ca.crt" \
  -n external-secrets
```

### Server Configuration

Configure the BeyondTrust Workload Credentials API endpoint:

```yaml
spec:
  provider:
    server:
      apiUrl: "https://api.beyondtrust.io/site"
      siteId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
```

- `apiUrl`: The base URL of your BeyondTrust Workload Credentials API
- `siteId`: Your BeyondTrust site identifier (UUID format)

### Complete Example

Here's a complete example for AWS dynamic credentials:

1. Create the API token and CA bundle secrets:
```bash
kubectl create secret generic api-token \
  --from-literal=token=<YOUR_API_TOKEN> \
  -n external-secrets
kubectl create secret generic my-ca-bundle \
  --from-file=ca.crt="/path/to/ca.crt" \
  -n external-secrets
```

2. Create the generator:
```yaml
apiVersion: generators.external-secrets.io/v1alpha1
kind: BeyondtrustWorkloadCredentialsDynamicSecret
metadata:
  name: aws-dynamic-generator
  namespace: external-secrets
spec:
  provider:
    auth:
      apikey:
        token:
          name: api-token
          key: token
    server:
      apiUrl: "https://api.beyondtrust.io/site"
      siteId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    folderPath: "production/aws-temp"
```

3. Create an ExternalSecret that uses the generator:
```yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: app-aws-credentials
  namespace: external-secrets
spec:
  refreshInterval: 45m  # Refresh before 1-hour expiration
  target:
    name: aws-temp-credentials
    creationPolicy: Owner
  dataFrom:
    - sourceRef:
        generatorRef:
          apiVersion: generators.external-secrets.io/v1alpha1
          kind: BeyondtrustWorkloadCredentialsDynamicSecret
          name: aws-dynamic-generator
```

4. The resulting Kubernetes secret will contain:
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: aws-temp-credentials
  namespace: external-secrets
data:
  accessKeyId: QVNJ...R04=
  secretAccessKey: Z3dk...WFk=
  sessionToken: SVFv...Ek8=
  leaseId: NTdk...Nm1j
  expiration: MjAy...OVo=
```

### Troubleshooting

#### Empty Credential Fields

If the generated secret has empty values:
1. Verify the dynamic secret exists in BeyondTrust Workload Credentials at the specified path
2. Check the API token has permissions to generate credentials
3. Verify the `folderPath` format is correct (`folder/secretName`)
4. Check controller logs: `kubectl logs -l app.kubernetes.io/name=external-secrets -n external-secrets`

#### Authentication Errors

If you see 403/401 errors:
1. Verify the API token is valid and not expired
2. Check the token has `generate` permissions for the dynamic secret
3. Ensure the `caProvider` or `caBundle` is configured correctly if using self-signed certificates

#### Timeout Errors

If credential generation times out:
1. Check network connectivity from the cluster to BeyondTrust Workload Credentials API
2. Verify the API endpoint is responsive
3. Check if there are firewall rules blocking the connection

#### Credential Expiration Issues

If applications report authentication failures:
1. Check if `refreshInterval` is greater than credential lifetime
2. Review the `expiration` field in the secret to see when credentials expire
3. Adjust `refreshInterval` to be 75-80% of the credential lifetime
4. Check ExternalSecret status: `kubectl describe externalsecret <name> -n <namespace>`