Skip to content

Commit

Permalink
Merge pull request #549 from fluxcd/no-cross-namespace-refs
Browse files Browse the repository at this point in the history
Allow disabling cross-namespace references
  • Loading branch information
stefanprodan authored Jan 27, 2022
2 parents d22f984 + 518c8a0 commit 09e6c29
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 14 deletions.
2 changes: 1 addition & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.17
require (
github.com/fluxcd/pkg/apis/kustomize v0.3.1
github.com/fluxcd/pkg/apis/meta v0.10.2
github.com/fluxcd/pkg/runtime v0.12.3
github.com/fluxcd/pkg/runtime v0.12.4
k8s.io/apiextensions-apiserver v0.23.1
k8s.io/apimachinery v0.23.1
sigs.k8s.io/controller-runtime v0.11.0
Expand Down
5 changes: 3 additions & 2 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,13 @@ github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMi
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/pkg/apis/acl v0.0.3/go.mod h1:XPts6lRJ9C9fIF9xVWofmQwftvhY25n1ps7W9xw0XLU=
github.com/fluxcd/pkg/apis/kustomize v0.3.1 h1:wmb5D9e1+Rr3/5O3235ERuj+h2VKUArVfYYk68QKP+w=
github.com/fluxcd/pkg/apis/kustomize v0.3.1/go.mod h1:k2HSRd68UwgNmOYBPOd6WbX6a2MH2X/Jeh7e3s3PFPc=
github.com/fluxcd/pkg/apis/meta v0.10.2 h1:pnDBBEvfs4HaKiVAYgz+e/AQ8dLvcgmVfSeBroZ/KKI=
github.com/fluxcd/pkg/apis/meta v0.10.2/go.mod h1:KQ2er9xa6koy7uoPMZjIjNudB5p4tXs+w0GO6fRcy7I=
github.com/fluxcd/pkg/runtime v0.12.3 h1:h21AZ3YG5MAP7DxFF9hfKrP+vFzys2L7CkUbPFjbP/0=
github.com/fluxcd/pkg/runtime v0.12.3/go.mod h1:imJ2xYy/d4PbSinX2IefmZk+iS2c1P5fY0js8mCE4SM=
github.com/fluxcd/pkg/runtime v0.12.4 h1:gA27RG/+adN2/7Qe03PB46Iwmye/MusPCpuS4zQ2fW0=
github.com/fluxcd/pkg/runtime v0.12.4/go.mod h1:gspNvhAqodZgSmK1ZhMtvARBf/NGAlxmaZaIOHkJYsc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down
135 changes: 135 additions & 0 deletions controllers/kustomization_acl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
"context"
"fmt"
"testing"
"time"

kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
apiacl "github.com/fluxcd/pkg/apis/acl"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
. "github.com/onsi/gomega"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestKustomizationReconciler_NoCrossNamespaceRefs(t *testing.T) {
g := NewWithT(t)
id := "force-" + randStringRunes(5)
revision := "v1.0.0"

err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")

err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")

manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "secret.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: Secret
metadata:
name: %[1]s
stringData:
key: "%[2]s"
`, name, data),
},
}
}

artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
g.Expect(err).NotTo(HaveOccurred(), "failed to create artifact from files")

sourceNamespace := fmt.Sprintf("source-%v", id)
err = createNamespace(sourceNamespace)
g.Expect(err).NotTo(HaveOccurred(), "failed to create source namespace")

repositoryName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: sourceNamespace,
}

err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())

kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("force-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
},
}

g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())

resultK := &kustomizev1.Kustomization{}
readyCondition := &metav1.Condition{}

t.Run("reconciles from cross-namespace source", func(t *testing.T) {
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())

g.Expect(readyCondition.Reason).To(Equal(meta.ReconciliationSucceededReason))
})

t.Run("fails to reconcile from cross-namespace source", func(t *testing.T) {
reconciler.NoCrossNamespaceRefs = true

revision = "v2.0.0"
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())

g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return apimeta.IsStatusConditionFalse(resultK.Status.Conditions, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())

g.Expect(readyCondition.Reason).To(Equal(apiacl.AccessDeniedReason))
})
}
32 changes: 26 additions & 6 deletions controllers/kustomization_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/kustomize/kyaml/filesys"

apiacl "github.com/fluxcd/pkg/apis/acl"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/acl"
"github.com/fluxcd/pkg/runtime/events"
"github.com/fluxcd/pkg/runtime/metrics"
"github.com/fluxcd/pkg/runtime/predicates"
Expand Down Expand Up @@ -83,6 +85,7 @@ type KustomizationReconciler struct {
MetricsRecorder *metrics.Recorder
StatusPoller *polling.StatusPoller
ControllerName string
NoCrossNamespaceRefs bool
}

type KustomizationReconcilerOptions struct {
Expand Down Expand Up @@ -178,17 +181,28 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
msg := fmt.Sprintf("Source '%s' not found", kustomization.Spec.SourceRef.String())
kustomization = kustomizev1.KustomizationNotReady(kustomization, "", kustomizev1.ArtifactFailedReason, msg)
if err := r.patchStatus(ctx, req, kustomization.Status); err != nil {
log.Error(err, "unable to update status for source not found")
return ctrl.Result{Requeue: true}, err
}
r.recordReadiness(ctx, kustomization)
log.Info(msg)
// do not requeue immediately, when the source is created the watcher should trigger a reconciliation
return ctrl.Result{RequeueAfter: kustomization.GetRetryInterval()}, nil
} else {
// retry on transient errors
return ctrl.Result{Requeue: true}, err
}

if acl.IsAccessDenied(err) {
kustomization = kustomizev1.KustomizationNotReady(kustomization, "", apiacl.AccessDeniedReason, err.Error())
if err := r.patchStatus(ctx, req, kustomization.Status); err != nil {
return ctrl.Result{Requeue: true}, err
}
log.Error(err, "access denied to cross-namespace source")
r.recordReadiness(ctx, kustomization)
r.event(ctx, kustomization, "unknown", events.EventSeverityError, err.Error(), nil)
return ctrl.Result{RequeueAfter: kustomization.GetRetryInterval()}, nil
}

// retry on transient errors
return ctrl.Result{Requeue: true}, err

}

if source.GetArtifact() == nil {
Expand Down Expand Up @@ -236,15 +250,13 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
// set the reconciliation status to progressing
kustomization = kustomizev1.KustomizationProgressing(kustomization, "reconciliation in progress")
if err := r.patchStatus(ctx, req, kustomization.Status); err != nil {
log.Error(err, "unable to update status to progressing")
return ctrl.Result{Requeue: true}, err
}
r.recordReadiness(ctx, kustomization)

// reconcile kustomization by applying the latest revision
reconciledKustomization, reconcileErr := r.reconcile(ctx, *kustomization.DeepCopy(), source)
if err := r.patchStatus(ctx, req, reconciledKustomization.Status); err != nil {
log.Error(err, "unable to update status after reconciliation")
return ctrl.Result{Requeue: true}, err
}
r.recordReadiness(ctx, reconciledKustomization)
Expand Down Expand Up @@ -569,10 +581,18 @@ func (r *KustomizationReconciler) getSource(ctx context.Context, kustomization k
if kustomization.Spec.SourceRef.Namespace != "" {
sourceNamespace = kustomization.Spec.SourceRef.Namespace
}

namespacedName := types.NamespacedName{
Namespace: sourceNamespace,
Name: kustomization.Spec.SourceRef.Name,
}

if r.NoCrossNamespaceRefs && sourceNamespace != kustomization.GetNamespace() {
return source, acl.AccessDeniedError(
fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked",
kustomization.Spec.SourceRef.Kind, namespacedName))
}

switch kustomization.Spec.SourceRef.Kind {
case sourcev1.GitRepositoryKind:
var repository sourcev1.GitRepository
Expand Down
3 changes: 2 additions & 1 deletion controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const (
const vaultVersion = "1.2.2"

var (
reconciler *KustomizationReconciler
k8sClient client.Client
testEnv *testenv.Environment
testServer *testserver.ArtifactServer
Expand Down Expand Up @@ -159,7 +160,7 @@ func TestMain(m *testing.M) {
controllerName := "kustomize-controller"
testEventsH = controller.MakeEvents(testEnv, controllerName, nil)
testMetricsH = controller.MustMakeMetrics(testEnv)
reconciler := &KustomizationReconciler{
reconciler = &KustomizationReconciler{
ControllerName: controllerName,
Client: testEnv,
EventRecorder: testEventsH.EventRecorder,
Expand Down
22 changes: 22 additions & 0 deletions docs/spec/v1beta2/kustomization.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,28 @@ Source supported types:
> If your Git repository or S3 bucket contains only plain manifests,
> then a kustomization.yaml will be automatically generated.
### Cross-namespace references

A Kustomization can refer to a source from a different namespace with `spec.sourceRef.namespace` e.g.:

```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: webapp
namespace: apps
spec:
interval: 5m
path: "./deploy"
sourceRef:
kind: GitRepository
name: webapp
namespace: shared
```
On multi-tenant clusters, platform admins can disable cross-namespace references with the
`--no-cross-namespace-refs=true` flag.

## Generate kustomization.yaml

If your repository contains plain Kubernetes manifests, the `kustomization.yaml`
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ require (
github.com/cyphar/filepath-securejoin v0.2.2
github.com/drone/envsubst v1.0.3-0.20200804185402-58bc65f69603
github.com/fluxcd/kustomize-controller/api v0.19.1
github.com/fluxcd/pkg/apis/acl v0.0.3
github.com/fluxcd/pkg/apis/kustomize v0.3.1
github.com/fluxcd/pkg/apis/meta v0.10.2
github.com/fluxcd/pkg/runtime v0.12.3
github.com/fluxcd/pkg/runtime v0.12.4
github.com/fluxcd/pkg/ssa v0.11.1
github.com/fluxcd/pkg/testserver v0.2.0
github.com/fluxcd/pkg/untar v0.1.0
Expand Down Expand Up @@ -71,7 +72,6 @@ require (
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/fluxcd/pkg/apis/acl v0.0.3 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-errors/errors v1.4.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,8 @@ github.com/fluxcd/pkg/apis/kustomize v0.3.1 h1:wmb5D9e1+Rr3/5O3235ERuj+h2VKUArVf
github.com/fluxcd/pkg/apis/kustomize v0.3.1/go.mod h1:k2HSRd68UwgNmOYBPOd6WbX6a2MH2X/Jeh7e3s3PFPc=
github.com/fluxcd/pkg/apis/meta v0.10.2 h1:pnDBBEvfs4HaKiVAYgz+e/AQ8dLvcgmVfSeBroZ/KKI=
github.com/fluxcd/pkg/apis/meta v0.10.2/go.mod h1:KQ2er9xa6koy7uoPMZjIjNudB5p4tXs+w0GO6fRcy7I=
github.com/fluxcd/pkg/runtime v0.12.3 h1:h21AZ3YG5MAP7DxFF9hfKrP+vFzys2L7CkUbPFjbP/0=
github.com/fluxcd/pkg/runtime v0.12.3/go.mod h1:imJ2xYy/d4PbSinX2IefmZk+iS2c1P5fY0js8mCE4SM=
github.com/fluxcd/pkg/runtime v0.12.4 h1:gA27RG/+adN2/7Qe03PB46Iwmye/MusPCpuS4zQ2fW0=
github.com/fluxcd/pkg/runtime v0.12.4/go.mod h1:gspNvhAqodZgSmK1ZhMtvARBf/NGAlxmaZaIOHkJYsc=
github.com/fluxcd/pkg/ssa v0.11.1 h1:iZMMe6Pdgt/sv3pZPJ5y4oRDa+8IXHbpPYgpjEmaq/8=
github.com/fluxcd/pkg/ssa v0.11.1/go.mod h1:S+qig7BTOxop0c134y8Yv8/iQST4Kt7S2xXiFkP4VMA=
github.com/fluxcd/pkg/testserver v0.2.0 h1:Mj0TapmKaywI6Fi5wvt1LAZpakUHmtzWQpJNKQ0Krt4=
Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"

"github.com/fluxcd/pkg/runtime/acl"
"github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/pkg/runtime/events"
"github.com/fluxcd/pkg/runtime/leaderelection"
Expand Down Expand Up @@ -68,6 +69,7 @@ func main() {
clientOptions client.Options
logOptions logger.Options
leaderElectionOptions leaderelection.Options
aclOptions acl.Options
watchAllNamespaces bool
httpRetry int
)
Expand All @@ -83,6 +85,7 @@ func main() {
clientOptions.BindFlags(flag.CommandLine)
logOptions.BindFlags(flag.CommandLine)
leaderElectionOptions.BindFlags(flag.CommandLine)
aclOptions.BindFlags(flag.CommandLine)
flag.Parse()

ctrl.SetLogger(logger.NewLogger(logOptions))
Expand Down Expand Up @@ -136,6 +139,7 @@ func main() {
ExternalEventRecorder: eventRecorder,
MetricsRecorder: metricsRecorder,
StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), nil),
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
}).SetupWithManager(mgr, controllers.KustomizationReconcilerOptions{
MaxConcurrentReconciles: concurrent,
DependencyRequeueInterval: requeueDependency,
Expand Down

0 comments on commit 09e6c29

Please sign in to comment.