feat: Fission SA RoleBindings in user namespace (cluster-admin) + v0.6.2

This commit is contained in:
Naeel
2026-04-19 14:40:03 +03:00
parent e541ba6996
commit 8a520cdcc2
2 changed files with 135 additions and 0 deletions
+132
View File
@@ -27,6 +27,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
@@ -39,6 +40,7 @@ var (
httpTrigGVR = schema.GroupVersionResource{Group: "fission.io", Version: "v1", Resource: "httptriggers"}
timeTrigGVR = schema.GroupVersionResource{Group: "fission.io", Version: "v1", Resource: "timetriggers"}
namespaceGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}
deploymentGVR = schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
)
const defaultSATokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
@@ -798,6 +800,92 @@ func namespaceFromJWT(token string) (string, error) {
}
// ensureUserNamespace создаёт K8s namespace и shared environments если не существуют.
// addNSToFission dynamically adds ns to FISSION_RESOURCE_NAMESPACES on all Fission deployments.
func (s *server) addNSToFission(ctx context.Context, ns string) error {
fissionNS := os.Getenv("FISSION_SYSTEM_NAMESPACE")
if fissionNS == "" {
fissionNS = "fission"
}
fissionDeployments := []string{"router", "executor", "buildermgr", "kubewatcher", "timer"}
// Читаем текущее значение из router
routerDep, err := s.dyn.Resource(deploymentGVR).Namespace(fissionNS).Get(ctx, "router", metav1.GetOptions{})
if err != nil {
return fmt.Errorf("get router deployment: %w", err)
}
currentVal := "default"
containerName := "router"
containers, _, _ := unstructured.NestedSlice(routerDep.Object, "spec", "template", "spec", "containers")
for _, c := range containers {
cont, ok := c.(map[string]any)
if !ok {
continue
}
if n, ok := cont["name"].(string); ok {
containerName = n
}
envs, _, _ := unstructured.NestedSlice(cont, "env")
for _, e := range envs {
env, ok := e.(map[string]any)
if !ok {
continue
}
if env["name"] == "FISSION_RESOURCE_NAMESPACES" {
if v, ok := env["value"].(string); ok && v != "" {
currentVal = v
}
}
}
break
}
// Проверяем что namespace ещё не в списке
for _, existing := range strings.Split(currentVal, ",") {
if strings.TrimSpace(existing) == ns {
return nil // уже есть
}
}
newVal := currentVal + "," + ns
// Патчим все Fission deployments
patch := map[string]any{
"spec": map[string]any{
"template": map[string]any{
"spec": map[string]any{
"containers": []any{
map[string]any{
"name": containerName,
"env": []any{
map[string]any{
"name": "FISSION_RESOURCE_NAMESPACES",
"value": newVal,
},
},
},
},
},
},
},
}
patchBytes, err := json.Marshal(patch)
if err != nil {
return fmt.Errorf("marshal patch: %w", err)
}
for _, dep := range fissionDeployments {
// container name совпадает с deployment name в fission
patch["spec"].(map[string]any)["template"].(map[string]any)["spec"].(map[string]any)["containers"].([]any)[0].(map[string]any)["name"] = dep
patchBytes, _ = json.Marshal(patch)
_, patchErr := s.dyn.Resource(deploymentGVR).Namespace(fissionNS).Patch(
ctx, dep, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{})
if patchErr != nil {
log.Printf("addNSToFission: patch deployment %s: %v", dep, patchErr)
}
}
log.Printf("addNSToFission: added %s, new list: %s", ns, newVal)
return nil
}
func (s *server) ensureUserNamespace(ctx context.Context, ns string) error {
// 1. Создать namespace
nsObj := &unstructured.Unstructured{
@@ -813,10 +901,54 @@ func (s *server) ensureUserNamespace(ctx context.Context, ns string) error {
},
}
_, err := s.dyn.Resource(namespaceGVR).Create(ctx, nsObj, metav1.CreateOptions{})
newlyCreated := err == nil
if err != nil && !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("create namespace %s: %w", ns, err)
}
// 1b. Создаём RoleBindings для Fission SA (всегда — idempotent через IsAlreadyExists)
fissionSAs := []string{"fission-executor", "fission-router", "fission-buildermgr", "fission-kubewatcher", "fission-timer", "fission-fetcher", "fission-builder"}
fissionSysNS := os.Getenv("FISSION_SYSTEM_NAMESPACE")
if fissionSysNS == "" {
fissionSysNS = "fission"
}
for _, sa := range fissionSAs {
rbObj := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": map[string]any{
"name": "fission-" + sa + "-user-ns",
"namespace": ns,
},
"roleRef": map[string]any{
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "cluster-admin",
},
"subjects": []any{
map[string]any{
"kind": "ServiceAccount",
"name": sa,
"namespace": fissionSysNS,
},
},
},
}
rbGVR := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebindings"}
_, rbErr := s.dyn.Resource(rbGVR).Namespace(ns).Create(ctx, rbObj, metav1.CreateOptions{})
if rbErr != nil && !apierrors.IsAlreadyExists(rbErr) {
log.Printf("ensureUserNamespace: create rolebinding %s/%s: %v", ns, sa, rbErr)
}
}
// 1c. Патчим Fission если namespace новый
if newlyCreated {
if patchErr := s.addNSToFission(ctx, ns); patchErr != nil {
log.Printf("ensureUserNamespace: addNSToFission: %v", patchErr)
}
}
// 2. Создать shared environments для всех языков
for lang, def := range langEnvMap {
envName := lang + "-env"