feat(api): 内部面 token 按调用方拆分为 velocity/limbo/build/ops 并按路由限定调用方,审计来源区分调用方,安装器生成并下发各自 Secret,新增 felis rotate-token 轮换命令
This commit is contained in:
38 files changed
+1359
-209
No files matched your search
@@ -50,20 +50,18 @@ import (
|
||||
// kubernetes.io/hostname selector and PV node affinity) or the CronJob could
|
||||
// schedule on a node where the worlds-root is empty.
|
||||
const (
|
||||
// configSecretName / serviceTokenSecretName are referenced BY NAME and NEVER
|
||||
// rendered into the bundle: felis.toml carries the database URL (a credential)
|
||||
// and the service token is a credential, so writing either into a checked-in
|
||||
// manifest is a hard red line. The deployment provisions both Secrets
|
||||
// out-of-band before applying these workloads.
|
||||
// configSecretName and the caller token Secrets (naming.CallerTokens) are
|
||||
// referenced BY NAME and NEVER rendered into the bundle: felis.toml carries the
|
||||
// database URL (a credential) and each token is a credential, so writing any of
|
||||
// them into a checked-in manifest is a hard red line. The deployment provisions
|
||||
// these Secrets out-of-band before applying these workloads.
|
||||
configSecretName = "felis-config"
|
||||
configSecretKey = "felis.toml"
|
||||
configMountPath = "/etc/felis"
|
||||
configFilePath = "/etc/felis/felis.toml"
|
||||
felisBinaryPath = "/usr/local/bin/felis"
|
||||
// Single-sourced with the operator, which injects the same Secret into the
|
||||
// login system server's pod (see internal/naming).
|
||||
serviceTokenSecretName = naming.ServiceTokenSecretName
|
||||
serviceTokenSecretKey = naming.ServiceTokenSecretKey
|
||||
// The key every caller token Secret stores its value under (internal/naming).
|
||||
serviceTokenSecretKey = naming.ServiceTokenSecretKey
|
||||
|
||||
// Ports, single-sourced with the entrypoints (cmd/felis). The api external
|
||||
// port must match server.listen in felis.toml (default 0.0.0.0:8080); that
|
||||
@@ -323,8 +321,8 @@ func retentionEnabled(p Params) bool {
|
||||
// fence and is asserted in workloads_test.go.
|
||||
//
|
||||
// felis.toml is mounted read-only from a Secret (it carries the database URL, a
|
||||
// credential, so it must never be a ConfigMap); FELIS_SERVICE_TOKEN comes from a
|
||||
// second Secret by reference. FELIS_IMAGE is the felis image itself, so the
|
||||
// credential, so it must never be a ConfigMap); each internal caller's token
|
||||
// (naming.CallerTokens) comes from its own Secret by reference. FELIS_IMAGE is the felis image itself, so the
|
||||
// restore executor launches `felis restore` with the same image. FELIS_BACKUP_PVC
|
||||
// is rendered only when a backup PVC is named — otherwise the restore endpoint
|
||||
// degrades to 503 rather than enqueuing a Job that cannot mount its backup.
|
||||
@@ -336,22 +334,29 @@ func retentionEnabled(p Params) bool {
|
||||
func APIDeployment(p Params) *appsv1.Deployment {
|
||||
p = p.withDefaults()
|
||||
|
||||
env := []corev1.EnvVar{
|
||||
{
|
||||
Name: "FELIS_SERVICE_TOKEN",
|
||||
var env []corev1.EnvVar
|
||||
// Every internal caller's token, each from its own Secret. Required: the
|
||||
// installer applies all four before this Deployment, and a missing one should
|
||||
// stall the rollout on the old pods rather than start an api that turns that
|
||||
// caller away.
|
||||
for _, ct := range naming.CallerTokens {
|
||||
env = append(env, corev1.EnvVar{
|
||||
Name: ct.APIEnv,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: serviceTokenSecretName},
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: ct.Secret},
|
||||
Key: serviceTokenSecretKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "FELIS_IMAGE", Value: p.FelisImage},
|
||||
})
|
||||
}
|
||||
env = append(env,
|
||||
corev1.EnvVar{Name: "FELIS_IMAGE", Value: p.FelisImage},
|
||||
// The api's own internal-face base URL, so it derives the submission
|
||||
// context URLs that build Pods fetch through it. Same value the login gate
|
||||
// is handed; one address for one face.
|
||||
{Name: naming.EnvAPIBaseURL, Value: InternalAPIBaseURL(p.ControlNamespace)},
|
||||
}
|
||||
corev1.EnvVar{Name: naming.EnvAPIBaseURL, Value: InternalAPIBaseURL(p.ControlNamespace)},
|
||||
)
|
||||
if p.BackupPVC != "" {
|
||||
env = append(env, corev1.EnvVar{Name: "FELIS_BACKUP_PVC", Value: p.BackupPVC})
|
||||
}
|
||||
|
||||
@@ -233,13 +233,28 @@ func TestAPIDeployment_Wiring(t *testing.T) {
|
||||
if v := envValue(c.Env, "FELIS_API_BASE_URL"); v != InternalAPIBaseURL(p.ControlNamespace) {
|
||||
t.Errorf("FELIS_API_BASE_URL = %q, want %q", v, InternalAPIBaseURL(p.ControlNamespace))
|
||||
}
|
||||
// FELIS_SERVICE_TOKEN must come from a Secret, never a literal value.
|
||||
tok := envVar(c.Env, "FELIS_SERVICE_TOKEN")
|
||||
if tok == nil || tok.ValueFrom == nil || tok.ValueFrom.SecretKeyRef == nil {
|
||||
t.Fatal("FELIS_SERVICE_TOKEN must be sourced from a secretKeyRef")
|
||||
}
|
||||
if tok.Value != "" {
|
||||
t.Error("FELIS_SERVICE_TOKEN must not carry a literal value")
|
||||
// Each internal caller's token comes from its own Secret, never a literal
|
||||
// value, and none is optional: a missing Secret must hold the rollout back.
|
||||
for env, secret := range map[string]string{
|
||||
"FELIS_SERVICE_TOKEN": "felis-service-token",
|
||||
"FELIS_LIMBO_TOKEN": "felis-limbo-token",
|
||||
"FELIS_BUILD_TOKEN": "felis-build-token",
|
||||
"FELIS_OPS_TOKEN": "felis-ops-token",
|
||||
} {
|
||||
tok := envVar(c.Env, env)
|
||||
if tok == nil || tok.ValueFrom == nil || tok.ValueFrom.SecretKeyRef == nil {
|
||||
t.Fatalf("%s must be sourced from a secretKeyRef", env)
|
||||
}
|
||||
ref := tok.ValueFrom.SecretKeyRef
|
||||
if ref.Name != secret || ref.Key != "token" {
|
||||
t.Errorf("%s reads %s/%s, want %s/token", env, ref.Name, ref.Key, secret)
|
||||
}
|
||||
if ref.Optional != nil && *ref.Optional {
|
||||
t.Errorf("%s is optional; the api must not start without it", env)
|
||||
}
|
||||
if tok.Value != "" {
|
||||
t.Errorf("%s must not carry a literal value", env)
|
||||
}
|
||||
}
|
||||
|
||||
// felis.toml carries the DB URL, so its volume must be a Secret (NOT a
|
||||
|
||||
Reference in new issue
Block a user