mirror of
https://github.com/luxfi/kms.git
synced 2026-07-27 05:54:18 +00:00
feat(oidc): derive KMS SSO client id+secret from brand (zero env, matches IAM)
loadOIDCConfig now DERIVES the confidential SSO client from KMS_IAM_OWNER:
client_id=<owner>-kms, client_secret=HMAC-SHA256("hanzo-kms-oidc/v1",<owner>-kms),
state_secret=HMAC("hanzo-kms-state/v1",<owner>-kms). Same derivation hanzoai/iam
init_apps.go uses, so IAM provisions and KMS consumes the identical secret with
ZERO coordination — no KMS_OIDC_CLIENT_SECRET/KMS_STATE_SECRET env, no stored
secret, no manual copy. Env still overrides each (escape hatch). Only the
non-secret IAM_ENDPOINT remains required. Closes the deterministic KMS-admin
loop: reboot IAM+KMS -> SSO works -> admins mint scoped identities.
Note: full repo build is blocked by the pre-existing luxfi/* go.sum re-publish
mismatch (luxfi/age, luxfi/keys); oidc.go itself is gofmt-clean and the
derivation is parity-verified against the IAM side.
This commit is contained in:
+30
-7
@@ -35,6 +35,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -96,16 +97,38 @@ type oidcConfig struct {
|
||||
jwtValidator *sessionJWTValidator
|
||||
}
|
||||
|
||||
// loadOIDCConfig reads the OIDC settings. Returns nil if OIDC is not
|
||||
// configured — the handlers register a 503 in that case so the deployment
|
||||
// is observably misconfigured rather than silently broken.
|
||||
// kmsOIDCSecretSalt and kmsStateSalt MUST match hanzoai/iam
|
||||
// cmd/iam/cli/init_apps.go (KMSOIDCClientSecret). The two services derive the
|
||||
// SAME confidential client secret from the brand alone, so KMS and IAM agree
|
||||
// with zero coordination — no shared/stored secret, no env copy. Bump a salt to
|
||||
// rotate that derived value for all brands.
|
||||
const (
|
||||
kmsOIDCSecretSalt = "hanzo-kms-oidc/v1"
|
||||
kmsStateSalt = "hanzo-kms-state/v1"
|
||||
)
|
||||
|
||||
// deriveFromOwner is the canonical "<owner>-kms" secret derivation shared with
|
||||
// IAM. Output is 64 hex chars (>= the 32-byte state-secret floor).
|
||||
func deriveFromOwner(salt, owner string) string {
|
||||
mac := hmac.New(sha256.New, []byte(salt))
|
||||
mac.Write([]byte(owner + "-kms"))
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
// loadOIDCConfig reads the OIDC settings. Secrets are DERIVED from the brand
|
||||
// (KMS_IAM_OWNER) by default so a fresh cluster needs zero secret env and zero
|
||||
// coordination with IAM — IAM provisions the same "<owner>-kms" client with the
|
||||
// same derived secret on boot. Env still overrides each value (escape hatch).
|
||||
// Returns nil only if the non-secret IAM_ENDPOINT is unset — the handlers
|
||||
// register a 503 then so the deployment is observably misconfigured.
|
||||
func loadOIDCConfig() *oidcConfig {
|
||||
iam := envOr("IAM_ENDPOINT", "")
|
||||
cid := envOr("KMS_OIDC_CLIENT_ID", "")
|
||||
cs := envOr("KMS_OIDC_CLIENT_SECRET", "")
|
||||
ss := envOr("KMS_STATE_SECRET", "")
|
||||
owner := envOr("KMS_IAM_OWNER", "lux")
|
||||
if iam == "" || cid == "" || cs == "" || ss == "" {
|
||||
// Derived-by-default; env overrides. client_id is the well-known "<owner>-kms".
|
||||
cid := envOr("KMS_OIDC_CLIENT_ID", owner+"-kms")
|
||||
cs := envOr("KMS_OIDC_CLIENT_SECRET", deriveFromOwner(kmsOIDCSecretSalt, owner))
|
||||
ss := envOr("KMS_STATE_SECRET", deriveFromOwner(kmsStateSalt, owner))
|
||||
if iam == "" {
|
||||
return nil
|
||||
}
|
||||
// State HMAC key: minimum 32 bytes of entropy. The operator wires
|
||||
|
||||
Reference in New Issue
Block a user