Add keycape check-config; reject duplicate clientIds
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m25s
Authentication acceptance / provider-contract (push) Successful in 25s
Build and Publish Container Image / build-and-push (push) Successful in 43s

net-kingdom asked for a way to validate a rendered configuration before
keycape-client.py restarts KeyCape (oidc-browser-relying-party review R4).
check-config runs every startup check that needs no secret, key or upstream:
validation, registry build, global tokenLifetime, policy-store clients and,
with --env-names, the secretRef bindings. The report carries no values.

ValidateConfig now refuses a clientId registered twice. The registry is a
map, so a duplicate silently replaced the earlier registration.

Also records the owners' amended CCR-2026-0018 sequence in KEY-WP-0013.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 779409@bnt-lap001
Assistant-Session: c658ca06-19b5-468d-be64-2985dc20bfd3
This commit is contained in:
tegwick 2026-10-10 13:28:36 +02:00
parent 5bc4a366b0
commit c528daa5be
9 changed files with 267 additions and 6 deletions

View file

@ -37,6 +37,10 @@ import (
const version = "0.1.0"
func main() {
if len(os.Args) > 1 && os.Args[1] == "check-config" {
os.Exit(runCheckConfig(os.Args[2:], os.Stdout))
}
if len(os.Args) > 1 && os.Args[1] == "probe-upstream-issuer" {
if err := authelia.RunIssuerProbe(context.Background(), os.Args[2:], os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err)
@ -387,6 +391,12 @@ func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
// buildClientRegistry converts []ClientConfig into the map used by handlers.
func buildClientRegistry(cfgClients []config.ClientConfig) (map[string]*domain.Client, error) {
return buildClientRegistryWith(cfgClients, os.Getenv)
}
// buildClientRegistryWith resolves env: secretRefs through getenv, so
// check-config can build the registry without any secret value.
func buildClientRegistryWith(cfgClients []config.ClientConfig, getenv func(string) string) (map[string]*domain.Client, error) {
m := make(map[string]*domain.Client, len(cfgClients))
for i := range cfgClients {
c := &cfgClients[i]
@ -394,7 +404,7 @@ func buildClientRegistry(cfgClients []config.ClientConfig) (map[string]*domain.C
var clientTokenLifetime time.Duration
if strings.HasPrefix(c.SecretRef, "env:") {
envName := strings.TrimPrefix(c.SecretRef, "env:")
clientSecret = os.Getenv(envName)
clientSecret = getenv(envName)
if clientSecret == "" {
return nil, fmt.Errorf("client %q secret environment variable %q is empty", c.ClientID, envName)
}