|
1 | 1 | package config |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/base64" |
4 | 5 | "errors" |
5 | 6 | "fmt" |
6 | 7 | "os" |
@@ -95,6 +96,14 @@ type Config struct { |
95 | 96 | // (and Ship defaults to true) — e.g. to configure the catalog never to ship. |
96 | 97 | CatalogPlane PlaneSettings `mapstructure:"catalog_plane" yaml:"catalog_plane"` |
97 | 98 |
|
| 99 | + // RegionKey selects and configures the region CEK wrap provider |
| 100 | + // (regionkey.Provider): the component that wraps each object's |
| 101 | + // content-encryption key under the region KEK for the read path (the |
| 102 | + // FilOne encryption design's region wrap). Optional until the encrypting |
| 103 | + // put/get paths are wired; anything consuming the provider fails at |
| 104 | + // startup when it is unconfigured. |
| 105 | + RegionKey RegionKeyConfig `mapstructure:"regionkey" yaml:"regionkey"` |
| 106 | + |
98 | 107 | // LogLevel is the zap level (debug|info|warn|error). |
99 | 108 | LogLevel string `mapstructure:"log_level" yaml:"log_level"` |
100 | 109 | // PostgresDSN is the registry/meta database. |
@@ -224,6 +233,48 @@ type IdentityConfig struct { |
224 | 233 | KeyFile string `mapstructure:"key_file" yaml:"key_file"` |
225 | 234 | } |
226 | 235 |
|
| 236 | +// RegionKeyConfig selects the region CEK wrap provider and carries each |
| 237 | +// implementation's settings. |
| 238 | +type RegionKeyConfig struct { |
| 239 | + // Provider names the implementation: "openbao" (production — the wrap |
| 240 | + // runs inside the region's OpenBao transit engine and the KEK never |
| 241 | + // enters ingot's process) or "inprocess" (AES-256-GCM in ingot's own |
| 242 | + // process; tests and development only). Empty means unconfigured. |
| 243 | + Provider string `mapstructure:"provider" yaml:"provider"` |
| 244 | + // OpenBao configures the "openbao" provider. |
| 245 | + OpenBao OpenBaoConfig `mapstructure:"openbao" yaml:"openbao"` |
| 246 | + // InProcess configures the "inprocess" provider. |
| 247 | + InProcess InProcessConfig `mapstructure:"inprocess" yaml:"inprocess"` |
| 248 | +} |
| 249 | + |
| 250 | +// OpenBaoConfig is the "openbao" region-key provider's connection and key |
| 251 | +// settings. |
| 252 | +type OpenBaoConfig struct { |
| 253 | + // Address of the OpenBao server, e.g. "https://bao.region.internal:8200" |
| 254 | + // or a unix socket "unix:///run/openbao/api.sock". Empty falls back to |
| 255 | + // the client's environment (BAO_ADDR, or upstream VAULT_ADDR). |
| 256 | + Address string `mapstructure:"address" yaml:"address"` |
| 257 | + // Token authenticates ingot to OpenBao; it needs encrypt/decrypt/rewrap |
| 258 | + // on the transit key and nothing else. Empty falls back to the client's |
| 259 | + // environment (BAO_TOKEN, or upstream VAULT_TOKEN). |
| 260 | + Token string `mapstructure:"token" yaml:"token"` |
| 261 | + // Mount is the transit engine's mount path. Empty means "transit". |
| 262 | + Mount string `mapstructure:"mount" yaml:"mount"` |
| 263 | + // Key is the transit key name holding the region KEK (provisioned with |
| 264 | + // type aes256-gcm96 and derived=true). Required when provider=openbao. |
| 265 | + Key string `mapstructure:"key" yaml:"key"` |
| 266 | +} |
| 267 | + |
| 268 | +// InProcessConfig is the "inprocess" region-key provider's settings. |
| 269 | +type InProcessConfig struct { |
| 270 | + // KEK is the region key, base64-encoded 32 bytes. Empty generates a |
| 271 | + // random key at startup — development only: wraps made under a generated |
| 272 | + // key are unreadable after a restart. |
| 273 | + KEK string `mapstructure:"kek" yaml:"kek"` |
| 274 | + // Version tags wraps with the KEK's version. Empty means "v1". |
| 275 | + Version string `mapstructure:"version" yaml:"version"` |
| 276 | +} |
| 277 | + |
227 | 278 | // Load reads daemon config from configFile (or the default search path) |
228 | 279 | // with env override (INGOT_* / nested keys via "_"). |
229 | 280 | func Load(configFile string) (*Config, error) { |
@@ -263,6 +314,17 @@ func Load(configFile string) (*Config, error) { |
263 | 314 | func setDefaults(v *viper.Viper) { |
264 | 315 | v.SetDefault("log_level", "info") |
265 | 316 | v.SetDefault("addr", "0.0.0.0:9000") |
| 317 | + // The regionkey keys are registered even where the default is empty: |
| 318 | + // viper's AutomaticEnv only overrides keys it already knows, so without |
| 319 | + // these an INGOT_REGIONKEY_* env var would be silently ignored whenever |
| 320 | + // the key is absent from the YAML. |
| 321 | + v.SetDefault("regionkey.provider", "") |
| 322 | + v.SetDefault("regionkey.openbao.address", "") |
| 323 | + v.SetDefault("regionkey.openbao.token", "") |
| 324 | + v.SetDefault("regionkey.openbao.mount", "transit") |
| 325 | + v.SetDefault("regionkey.openbao.key", "") |
| 326 | + v.SetDefault("regionkey.inprocess.kek", "") |
| 327 | + v.SetDefault("regionkey.inprocess.version", "v1") |
266 | 328 | } |
267 | 329 |
|
268 | 330 | // Validate checks the config for the selected mode, aggregating every |
@@ -308,6 +370,26 @@ func (c *Config) Validate() error { |
308 | 370 | errs = multierr.Append(errs, errors.New("revocation_service_url and revocation_service_did must be set together")) |
309 | 371 | } |
310 | 372 |
|
| 373 | + switch c.RegionKey.Provider { |
| 374 | + case "": |
| 375 | + // Unconfigured is valid until the encrypting put/get paths are wired. |
| 376 | + case "openbao": |
| 377 | + if c.RegionKey.OpenBao.Key == "" { |
| 378 | + errs = multierr.Append(errs, errors.New("regionkey.openbao.key (transit key name) is required when regionkey.provider is openbao")) |
| 379 | + } |
| 380 | + case "inprocess": |
| 381 | + if c.RegionKey.InProcess.KEK != "" { |
| 382 | + kek, err := base64.StdEncoding.DecodeString(c.RegionKey.InProcess.KEK) |
| 383 | + if err != nil { |
| 384 | + errs = multierr.Append(errs, fmt.Errorf("regionkey.inprocess.kek: %w", err)) |
| 385 | + } else if len(kek) != 32 { |
| 386 | + errs = multierr.Append(errs, fmt.Errorf("regionkey.inprocess.kek must decode to 32 bytes (AES-256), got %d", len(kek))) |
| 387 | + } |
| 388 | + } |
| 389 | + default: |
| 390 | + errs = multierr.Append(errs, fmt.Errorf("regionkey.provider %q is not one of openbao, inprocess", c.RegionKey.Provider)) |
| 391 | + } |
| 392 | + |
311 | 393 | if errs != nil { |
312 | 394 | return fmt.Errorf("invalid config: %w", errs) |
313 | 395 | } |
|
0 commit comments