Skip to content

Commit 4ddd3ca

Browse files
domenkozarclaude
andcommitted
feat: declarative secret generation with type and generate fields
Secrets can now be auto-generated when missing by adding `type` and `generate` fields to the secret config. Supported types: password, hex, base64, uuid, and command. Generation triggers during check/run when a secret is missing and stores the value via the configured provider. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e04985d commit 4ddd3ca

File tree

16 files changed

+1233
-37
lines changed

16 files changed

+1233
-37
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Declarative secret generation: secrets can now be auto-generated when missing by adding
12+
`type` and `generate` fields to secret config. Supported types: `password`, `hex`, `base64`,
13+
`uuid`, and `command` (for arbitrary shell commands). Generation triggers during `check`/`run`
14+
when a secret is missing, and the generated value is stored via the configured provider.
15+
1016
### Changed
1117
- OnePassword provider: Significant performance improvement by caching authentication status
1218
and using batch fetching with parallel threads. Reduces CLI calls from 2N sequential to

Cargo.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ google-cloud-secretmanager-v1 = "1.2"
4040
tokio = { version = "1", features = ["rt"] }
4141
secretspec-derive = { version = "0.6.2", path = "./secretspec-derive" }
4242
secretspec = { version = "0.6.2", path = "./secretspec" }
43+
rand = "0.9"
44+
uuid = { version = "1", features = ["v4"] }
45+
data-encoding = "2"
4346

4447
# The profile that 'dist' will build with
4548
[profile.dist]

docs/src/content/docs/concepts/declarative.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ SECRET_NAME = {
3535
- `description`: Explains the secret's purpose (required)
3636
- `required`: Whether the secret must be provided (default: `true`)
3737
- `default`: Fallback value for optional secrets
38+
- `type`: Secret type for auto-generation (`password`, `hex`, `base64`, `uuid`, `command`)
39+
- `generate`: Enable auto-generation when the secret is missing (`true` or a table with options)
3840

3941
## Configuration Inheritance
4042

@@ -89,6 +91,19 @@ extends = ["../../shared/base", "../../shared/database", "../../shared/auth"]
8991
- Each profile is merged independently
9092
- Paths are relative to the containing file
9193

94+
## Secret Generation
95+
96+
Secrets can be declared with `type` and `generate` to be auto-generated when missing. This is useful for passwords, tokens, and keys that don't need to be shared:
97+
98+
```toml
99+
[profiles.default]
100+
DB_PASSWORD = { description = "Database password", type = "password", generate = true }
101+
API_TOKEN = { description = "API token", type = "hex", generate = { bytes = 32 } }
102+
SESSION_KEY = { description = "Session key", type = "base64", generate = { bytes = 64 } }
103+
```
104+
105+
Generated values are stored via the configured provider and reused on subsequent runs. See the [configuration reference](/reference/configuration/#secret-generation) for all generation types and options.
106+
92107
## Best Practices
93108

94109
1. **Descriptive names**: Use `STRIPE_API_KEY` instead of generic `API_KEY`

docs/src/content/docs/index.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ TLS_CERT = { description = "TLS certificate", as_path = true }
5050

5151
Secrets with `as_path = true` are written to temporary files - useful for credentials that must be passed as file paths (TLS certs, service account keys).
5252

53+
Secrets with `type` and `generate` are automatically created when missing — no manual setup needed for passwords, tokens, and keys:
54+
55+
```toml
56+
DB_PASSWORD = { description = "Database password", type = "password", generate = true }
57+
```
58+
5359
```bash
5460
# Initialize secretspec.toml, possibly from `.env`
5561
$ secretspec init --from dotenv

docs/src/content/docs/reference/configuration.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,12 @@ Each secret variable is defined as a table with the following fields:
4747
| `default` | string | No** | Default value if not provided |
4848
| `providers` | array[string] | No | List of provider aliases to use in fallback order |
4949
| `as_path` | boolean | No | Write secret to temp file and return file path (default: false) |
50+
| `type` | string | No*** | Secret type for generation: `password`, `hex`, `base64`, `uuid`, `command` |
51+
| `generate` | boolean or table | No*** | Enable auto-generation when secret is missing |
5052

5153
*If `default` is provided, `required` defaults to false
5254
**Only valid when `required = false`
55+
***`type` is required when `generate` is enabled; `generate` and `default` cannot both be set
5356

5457
## Complete Example
5558

@@ -123,6 +126,45 @@ GOOGLE_APPLICATION_CREDENTIALS = { description = "GCP service account", as_path
123126
| Rust SDK | Files cleaned up when `ValidatedSecrets` is dropped; use `keep_temp_files()` to persist |
124127
| Rust SDK types | `PathBuf` or `Option<PathBuf>` instead of `String` |
125128

129+
### Secret Generation
130+
131+
When `type` and `generate` are set, missing secrets are automatically generated during `check` or `run` and stored via the configured provider:
132+
133+
```toml
134+
[profiles.default]
135+
# Simple: generate with type defaults
136+
DB_PASSWORD = { description = "Database password", type = "password", generate = true }
137+
REQUEST_ID = { description = "Request ID prefix", type = "uuid", generate = true }
138+
139+
# Custom options
140+
API_TOKEN = { description = "API token", type = "hex", generate = { bytes = 32 } }
141+
SESSION_KEY = { description = "Session key", type = "base64", generate = { bytes = 64 } }
142+
143+
# Shell command
144+
MONGO_KEY = { description = "MongoDB keyfile", type = "command", generate = { command = "openssl rand -base64 765" } }
145+
146+
# Type without generate: informational only, no auto-generation
147+
MANUAL_SECRET = { description = "Manually managed", type = "password" }
148+
```
149+
150+
#### Generation Types
151+
152+
| Type | Default Output | Options |
153+
|------|---------------|---------|
154+
| `password` | 32 alphanumeric chars | `length` (int), `charset` (`"alphanumeric"` or `"ascii"`) |
155+
| `hex` | 64 hex chars (32 bytes) | `bytes` (int) |
156+
| `base64` | 44 chars (32 bytes) | `bytes` (int) |
157+
| `uuid` | UUID v4 (36 chars) | none |
158+
| `command` | stdout of command | `command` (string, required) |
159+
160+
#### Behavior
161+
162+
- Generation only triggers when a secret is **missing** — existing secrets are never overwritten
163+
- Generated values are stored via the secret's configured provider (or the default provider)
164+
- Subsequent runs find the stored value and skip generation (idempotent)
165+
- `generate` and `default` cannot both be set on the same secret
166+
- `type = "command"` requires `generate = { command = "..." }` (not just `generate = true`)
167+
126168
## Profile Inheritance
127169

128170
- All profiles automatically inherit from `[profiles.default]`

0 commit comments

Comments
 (0)