fix: RBAC bind cluster-admin + list namespaces + TEST_MODE multi-tenant test
This commit is contained in:
+30
-2
@@ -59,6 +59,7 @@ type server struct {
|
||||
http *http.Client
|
||||
saTokenPath string
|
||||
invokeTimeout time.Duration
|
||||
testMode bool // FISSION_TEST_MODE=true — пропускает deck auth, X-Test-Sub задаёт user
|
||||
|
||||
authUser string
|
||||
authPass string
|
||||
@@ -129,6 +130,7 @@ func main() {
|
||||
invokeTimeout: invokeTimeout,
|
||||
authUser: authUser,
|
||||
authPass: authPass,
|
||||
testMode: os.Getenv("FISSION_TEST_MODE") == "true",
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -154,6 +156,19 @@ func main() {
|
||||
|
||||
auth := func(h http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var ns string
|
||||
|
||||
if s.testMode {
|
||||
// TEST_MODE: deck API не вызывается.
|
||||
// X-Test-Sub задаёт произвольный sub → разные namespace-ы для тестирования.
|
||||
sub := strings.TrimSpace(r.Header.Get("X-Test-Sub"))
|
||||
if sub == "" {
|
||||
writeJSONError(w, http.StatusUnauthorized, "test mode: X-Test-Sub required")
|
||||
return
|
||||
}
|
||||
h32 := sha256.Sum256([]byte(sub))
|
||||
ns = "fission-" + hex.EncodeToString(h32[:8])
|
||||
} else {
|
||||
token := strings.TrimSpace(r.Header.Get("X-Auth-Token"))
|
||||
env := strings.TrimSpace(strings.ToLower(r.Header.Get("X-Auth-Env")))
|
||||
if _, ok := deckAPIs[env]; !ok {
|
||||
@@ -167,11 +182,14 @@ func main() {
|
||||
writeJSONError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
ns, err := namespaceFromJWT(token)
|
||||
var err error
|
||||
ns, err = namespaceFromJWT(token)
|
||||
if err != nil {
|
||||
log.Printf("namespaceFromJWT: %v", err)
|
||||
ns = s.ns
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), ctxKeyNS{}, ns)
|
||||
h(w, r.WithContext(ctx))
|
||||
}
|
||||
@@ -258,6 +276,16 @@ func (s *server) handleCreateFunction(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Гарантируем что namespace + RBAC созданы до любых операций с ресурсами.
|
||||
// handleAuth делает это при логине, но в test mode или при прямом вызове API
|
||||
// namespace может отсутствовать — создаём idempotent.
|
||||
nsCtx, nsCancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer nsCancel()
|
||||
if err := s.ensureUserNamespace(nsCtx, ns); err != nil {
|
||||
writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("ensure namespace: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
req.Language = strings.TrimSpace(req.Language)
|
||||
req.Environment = strings.TrimSpace(req.Environment)
|
||||
@@ -1083,7 +1111,7 @@ func (s *server) ensureUserNamespace(ctx context.Context, ns string) error {
|
||||
return fmt.Errorf("create namespace %s: %w", ns, err)
|
||||
}
|
||||
|
||||
// 1b. RoleBindings для Fission SA в user namespace.
|
||||
// 1b. RoleBindings для Fission SA в user namespace.
|
||||
//
|
||||
// Проблема: Fission компоненты (executor, router, buildermgr и др.) работают в namespace
|
||||
// "fission", но при добавлении нового namespace в FISSION_RESOURCE_NAMESPACES они начинают
|
||||
|
||||
@@ -5,13 +5,19 @@ metadata:
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get", "create"]
|
||||
verbs: ["get", "create", "list"]
|
||||
- apiGroups: ["fission.io"]
|
||||
resources: ["*"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: ["rbac.authorization.k8s.io"]
|
||||
resources: ["rolebindings"]
|
||||
verbs: ["get", "create"]
|
||||
# bind позволяет создавать RoleBindings ссылающиеся на cluster-admin
|
||||
# без необходимости самому иметь все его права (RBAC escalation prevention)
|
||||
- apiGroups: ["rbac.authorization.k8s.io"]
|
||||
resources: ["clusterroles"]
|
||||
resourceNames: ["cluster-admin"]
|
||||
verbs: ["bind"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
verbs: ["get", "patch"]
|
||||
|
||||
Reference in New Issue
Block a user