diff --git a/chart/flux/README.md b/chart/flux/README.md index fa08569ed..8d425d8ae 100755 --- a/chart/flux/README.md +++ b/chart/flux/README.md @@ -202,10 +202,11 @@ The following tables lists the configurable parameters of the Flux chart and the | `env.secretName` | `` | Name of the secret that contains environment variables which should be defined in the Flux container (using `envFrom`) | `rbac.create` | `true` | If `true`, create and use RBAC resources | `rbac.pspEnabled` | `false` | If `true`, create and use a restricted pod security policy for Flux pod(s) +| `allowedNamespaces` | `[]` | Allow flux to manage resources in the specified namespaces. The namespace flux is deployed in will always be included | `serviceAccount.create` | `true` | If `true`, create a new service account | `serviceAccount.name` | `flux` | Service account to be used | `serviceAccount.annotations` | `` | Additional Service Account annotations -| `clusterRole.create` | `true` | If `false`, Flux and the Helm Operator will be restricted to the namespace where they are deployed +| `clusterRole.create` | `true` | If `false`, Flux will be restricted to the namespaces given in `allowedNamespaces` and the namespace where it is deployed | `service.type` | `ClusterIP` | Service type to be used (exposing the Flux API outside of the cluster is not advised) | `service.port` | `3030` | Service port to be used | `sync.state` | `git` | Where to keep sync state; either a tag in the upstream repo (`git`), or as an annotation on the SSH secret (`secret`) diff --git a/chart/flux/templates/deployment.yaml b/chart/flux/templates/deployment.yaml index ac99fda18..a5683f774 100644 --- a/chart/flux/templates/deployment.yaml +++ b/chart/flux/templates/deployment.yaml @@ -175,7 +175,7 @@ spec: {{- end }} args: {{- if not .Values.clusterRole.create }} - - --k8s-allow-namespace={{ .Release.Namespace }} + - --k8s-allow-namespace={{ join "," (append .Values.allowedNamespaces .Release.Namespace) }} {{- end}} {{- if .Values.logFormat }} - --log-format={{ .Values.logFormat }} diff --git a/chart/flux/templates/rbac-role.yaml b/chart/flux/templates/rbac-role.yaml index 43767a668..00f33021e 100644 --- a/chart/flux/templates/rbac-role.yaml +++ b/chart/flux/templates/rbac-role.yaml @@ -1,13 +1,15 @@ {{- if and .Values.rbac.create (eq .Values.clusterRole.create false) -}} +{{- range $namespace := (append .Values.allowedNamespaces .Release.Namespace) }} apiVersion: rbac.authorization.k8s.io/v1beta1 kind: Role metadata: - name: {{ template "flux.fullname" . }} + name: {{ template "flux.fullname" $ }} + namespace: {{ $namespace }} labels: - app: {{ template "flux.name" . }} - chart: {{ template "flux.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} + app: {{ template "flux.name" $ }} + chart: {{ template "flux.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} rules: - apiGroups: - '*' @@ -19,21 +21,23 @@ rules: apiVersion: rbac.authorization.k8s.io/v1beta1 kind: RoleBinding metadata: - name: {{ template "flux.fullname" . }} + name: {{ template "flux.fullname" $ }} + namespace: {{ $namespace }} labels: - app: {{ template "flux.name" . }} - chart: {{ template "flux.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} + app: {{ template "flux.name" $ }} + chart: {{ template "flux.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: {{ template "flux.fullname" . }} + name: {{ template "flux.fullname" $ }} subjects: - - name: {{ template "flux.serviceAccountName" . }} - namespace: {{ .Release.Namespace | quote }} + - name: {{ template "flux.serviceAccountName" $ }} + namespace: {{ $.Release.Namespace | quote }} kind: ServiceAccount --- +{{- end }} apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: diff --git a/chart/flux/values.yaml b/chart/flux/values.yaml index ff04800bd..02342a82a 100644 --- a/chart/flux/values.yaml +++ b/chart/flux/values.yaml @@ -17,6 +17,9 @@ service: type: ClusterIP port: 3030 +# Specifies which namespaces flux should have access to +allowedNamespaces: [] + rbac: # Specifies whether RBAC resources should be created create: true @@ -32,10 +35,9 @@ serviceAccount: # Annotations for the Service Account annotations: {} -# If create is `false` and no name is given, Flux and the Helm -# Operator will be restricted to the namespace where they are -# deployed, and the kubeconfig default context will be set to that -# namespace. +# If create is `false` and no name is given, Flux will be restricted to +# namespaces listed in allowedNamespaces and the namespace where it is +# deployed, and the kubeconfig default context will be set to that namespace. clusterRole: create: true # The name of a cluster role to bind to; if not set and create is diff --git a/cmd/fluxd/main.go b/cmd/fluxd/main.go index 7a667e7a7..7f470e776 100644 --- a/cmd/fluxd/main.go +++ b/cmd/fluxd/main.go @@ -493,7 +493,10 @@ func main() { client := kubernetes.MakeClusterClientset(clientset, dynamicClientset, fhrClientset, hrClientset, discoClientset) kubectlApplier := kubernetes.NewKubectl(kubectl, restClientConfig) - allowedNamespaces := append(*k8sNamespaceWhitelist, *k8sAllowNamespace...) + allowedNamespaces := make(map[string]struct{}) + for _, n := range append(*k8sNamespaceWhitelist, *k8sAllowNamespace...) { + allowedNamespaces[n] = struct{}{} + } k8sInst := kubernetes.NewCluster(client, kubectlApplier, sshKeyRing, logger, allowedNamespaces, *registryExcludeImage) k8sInst.GC = *syncGC k8sInst.DryGC = *dryGC diff --git a/pkg/cluster/kubernetes/kubernetes.go b/pkg/cluster/kubernetes/kubernetes.go index 0a545845c..d111a74e1 100644 --- a/pkg/cluster/kubernetes/kubernetes.go +++ b/pkg/cluster/kubernetes/kubernetes.go @@ -106,7 +106,7 @@ type Cluster struct { syncErrors map[resource.ID]error muSyncErrors sync.RWMutex - allowedNamespaces []string + allowedNamespaces map[string]struct{} loggedAllowedNS map[string]bool // to keep track of whether we've logged a problem with seeing an allowed namespace imageExcludeList []string @@ -114,7 +114,7 @@ type Cluster struct { } // NewCluster returns a usable cluster. -func NewCluster(client ExtendedClient, applier Applier, sshKeyRing ssh.KeyRing, logger log.Logger, allowedNamespaces []string, imageExcludeList []string) *Cluster { +func NewCluster(client ExtendedClient, applier Applier, sshKeyRing ssh.KeyRing, logger log.Logger, allowedNamespaces map[string]struct{}, imageExcludeList []string) *Cluster { c := &Cluster{ client: client, applier: applier, @@ -304,7 +304,7 @@ func (c *Cluster) PublicSSHKey(regenerate bool) (ssh.PublicKey, error) { func (c *Cluster) getAllowedAndExistingNamespaces(ctx context.Context) ([]string, error) { if len(c.allowedNamespaces) > 0 { nsList := []string{} - for _, name := range c.allowedNamespaces { + for name, _ := range c.allowedNamespaces { if err := ctx.Err(); err != nil { return nil, err } @@ -350,12 +350,8 @@ func (c *Cluster) IsAllowedResource(id resource.ID) bool { namespaceToCheck = name } - for _, allowedNS := range c.allowedNamespaces { - if namespaceToCheck == allowedNS { - return true - } - } - return false + _, ok := c.allowedNamespaces[namespaceToCheck] + return ok } type yamlThroughJSON struct { diff --git a/pkg/cluster/kubernetes/kubernetes_test.go b/pkg/cluster/kubernetes/kubernetes_test.go index 2d08ac0b2..eddba0918 100644 --- a/pkg/cluster/kubernetes/kubernetes_test.go +++ b/pkg/cluster/kubernetes/kubernetes_test.go @@ -3,6 +3,7 @@ package kubernetes import ( "context" "reflect" + "sort" "testing" "github.com/go-kit/kit/log" @@ -27,20 +28,22 @@ func testGetAllowedNamespaces(t *testing.T, namespace []string, expected []strin clientset := fakekubernetes.NewSimpleClientset(newNamespace("default"), newNamespace("kube-system")) client := ExtendedClient{coreClient: clientset} - c := NewCluster(client, nil, nil, log.NewNopLogger(), namespace, []string{}) + allowedNamespaces := make(map[string]struct{}) + for _, n := range namespace { + allowedNamespaces[n] = struct{}{} + } + c := NewCluster(client, nil, nil, log.NewNopLogger(), allowedNamespaces, []string{}) namespaces, err := c.getAllowedAndExistingNamespaces(context.Background()) if err != nil { t.Errorf("The error should be nil, not: %s", err) } - result := []string{} - for _, namespace := range namespaces { - result = append(result, namespace) - } + sort.Strings(namespaces) // We cannot be sure of the namespace order, since they are saved in a map in cluster struct + sort.Strings(expected) - if reflect.DeepEqual(result, expected) != true { - t.Errorf("Unexpected namespaces: %v != %v.", result, expected) + if reflect.DeepEqual(namespaces, expected) != true { + t.Errorf("Unexpected namespaces: %v != %v.", namespaces, expected) } }