Skip to content

Commit 6aaf01a

Browse files
authored
Merge pull request #305 from SomtochiAma/no-cross-ns-ref
Allow disabling cross-namespace references
2 parents 895bb22 + 0ca01bd commit 6aaf01a

File tree

6 files changed

+84
-13
lines changed

6 files changed

+84
-13
lines changed

controllers/imageupdateautomation_controller.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"time"
3030

3131
"github.com/Masterminds/sprig/v3"
32-
3332
gogit "github.com/go-git/go-git/v5"
3433
libgit2 "github.com/libgit2/git2go/v31"
3534

@@ -55,7 +54,9 @@ import (
5554
"sigs.k8s.io/controller-runtime/pkg/source"
5655

5756
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
57+
apiacl "github.com/fluxcd/pkg/apis/acl"
5858
"github.com/fluxcd/pkg/apis/meta"
59+
"github.com/fluxcd/pkg/runtime/acl"
5960
"github.com/fluxcd/pkg/runtime/events"
6061
"github.com/fluxcd/pkg/runtime/logger"
6162
"github.com/fluxcd/pkg/runtime/metrics"
@@ -91,6 +92,7 @@ type ImageUpdateAutomationReconciler struct {
9192
EventRecorder kuberecorder.EventRecorder
9293
ExternalEventRecorder *events.Recorder
9394
MetricsRecorder *metrics.Recorder
95+
NoCrossNamespaceRef bool
9496
}
9597

9698
type ImageUpdateAutomationReconcilerOptions struct {
@@ -179,6 +181,19 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
179181
}
180182
debuglog.Info("fetching git repository", "gitrepository", originName)
181183

184+
if r.NoCrossNamespaceRef && gitRepoNamespace != auto.GetNamespace() {
185+
err := acl.AccessDeniedError(fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked",
186+
auto.Spec.SourceRef.Kind, originName))
187+
log.Error(err, "access denied to cross-namespaced resource")
188+
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, apiacl.AccessDeniedReason,
189+
err.Error())
190+
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
191+
return ctrl.Result{Requeue: true}, err
192+
}
193+
r.event(ctx, auto, events.EventSeverityError, err.Error())
194+
return ctrl.Result{}, nil
195+
}
196+
182197
if err := r.Get(ctx, originName, &origin); err != nil {
183198
if client.IgnoreNotFound(err) == nil {
184199
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.GitNotAvailableReason, "referenced git repository is missing")

controllers/update_test.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/ProtonMail/go-crypto/openpgp"
3232
"github.com/ProtonMail/go-crypto/openpgp/armor"
33+
"github.com/fluxcd/pkg/apis/acl"
3334
"github.com/go-git/go-billy/v5/memfs"
3435
"github.com/go-git/go-git/v5"
3536
"github.com/go-git/go-git/v5/config"
@@ -268,6 +269,7 @@ Images:
268269
})
269270

270271
AfterEach(func() {
272+
imageAutoReconciler.NoCrossNamespaceRef = false
271273
Expect(k8sClient.Delete(context.Background(), namespace)).To(Succeed())
272274
})
273275

@@ -290,8 +292,9 @@ Images:
290292

291293
Context("ref cross-ns GitRepository", func() {
292294
var (
293-
localRepo *git.Repository
294-
commitMessage string
295+
localRepo *git.Repository
296+
commitMessage string
297+
updateBySetters *imagev1.ImageUpdateAutomation
295298
)
296299

297300
const (
@@ -410,7 +413,7 @@ Images:
410413
Namespace: namespace.Name,
411414
Name: "update-test",
412415
}
413-
updateBySetters := &imagev1.ImageUpdateAutomation{
416+
updateBySetters = &imagev1.ImageUpdateAutomation{
414417
ObjectMeta: metav1.ObjectMeta{
415418
Name: updateKey.Name,
416419
Namespace: updateKey.Namespace,
@@ -465,6 +468,28 @@ Images:
465468
Expect(commit.Author.Name).To(Equal(authorName))
466469
Expect(commit.Author.Email).To(Equal(authorEmail))
467470
})
471+
472+
It("fails to reconcile if cross-namespace flag is set", func() {
473+
imageAutoReconciler.NoCrossNamespaceRef = true
474+
475+
// trigger reconcile
476+
var updatePatch imagev1.ImageUpdateAutomation
477+
Expect(k8sClient.Get(context.TODO(), client.ObjectKeyFromObject(updateBySetters), &updatePatch)).To(Succeed())
478+
updatePatch.Spec.Interval = metav1.Duration{Duration: 5 * time.Minute}
479+
Expect(k8sClient.Patch(context.Background(), &updatePatch, client.Merge)).To(Succeed())
480+
481+
resultAuto := &imagev1.ImageUpdateAutomation{}
482+
var readyCondition *metav1.Condition
483+
484+
Eventually(func() bool {
485+
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(updateBySetters), resultAuto)
486+
readyCondition = apimeta.FindStatusCondition(resultAuto.Status.Conditions, meta.ReadyCondition)
487+
return apimeta.IsStatusConditionFalse(resultAuto.Status.Conditions, meta.ReadyCondition)
488+
}, timeout, time.Second).Should(BeTrue())
489+
490+
Expect(readyCondition).ToNot(BeNil())
491+
Expect(readyCondition.Reason).To(Equal(acl.AccessDeniedReason))
492+
})
468493
})
469494

470495
Context("update path", func() {

docs/spec/v1beta1/imageupdateautomations.md

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type ImageUpdateAutomationSpec struct {
3232
// SourceRef refers to the resource giving access details
3333
// to a git repository.
3434
// +required
35-
SourceRef SourceReference `json:"sourceRef"`
35+
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
3636
// GitSpec contains all the git-specific definitions. This is
3737
// technically optional, but in practice mandatory until there are
3838
// other kinds of source allowed.
@@ -62,25 +62,51 @@ repository to be updated. The `kind` field in the reference currently only suppo
6262
`GitRepository`, which is the default.
6363

6464
```go
65-
// SourceReference contains enough information to let you locate the
66-
// typed, referenced source object.
67-
type SourceReference struct {
68-
// API version of the referent
65+
// CrossNamespaceSourceReference contains enough information to let you locate the
66+
// typed Kubernetes resource object at cluster level.
67+
type CrossNamespaceSourceReference struct {
68+
// API version of the referent.
6969
// +optional
7070
APIVersion string `json:"apiVersion,omitempty"`
7171

72-
// Kind of the referent
72+
// Kind of the referent.
7373
// +kubebuilder:validation:Enum=GitRepository
7474
// +kubebuilder:default=GitRepository
7575
// +required
7676
Kind string `json:"kind"`
7777

78-
// Name of the referent
78+
// Name of the referent.
7979
// +required
8080
Name string `json:"name"`
81+
82+
// Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.
83+
// +optional
84+
Namespace string `json:"namespace,omitempty"`
8185
}
8286
```
8387

88+
### Cross-namespace references
89+
90+
A ImageUpdateAutomation can refer to a GitRepository from a different namespace with
91+
`spec.sourceRef.namespace` e.g.:
92+
93+
```yaml
94+
apiVersion: image.toolkit.fluxcd.io/v1beta1
95+
kind: ImageUpdateAutomation
96+
metadata:
97+
name: webapp
98+
namespace: apps
99+
spec:
100+
interval: 5m
101+
sourceRef:
102+
kind: GitRepository # the only valid value, but good practice to be explicit here
103+
name: apps
104+
namespace: flux-system
105+
```
106+
107+
On multi-tenant clusters, platform admins can disable cross-namespace references with the
108+
`--no-cross-namespace-refs=true` flag.
109+
84110
To be able to commit changes back, the referenced `GitRepository` object must refer to credentials
85111
with write access; e.g., if using a GitHub deploy key, "Allow write access" should be checked when
86112
creating it. Only the `url`, `ref`, and `secretRef` fields of the `GitRepository` are used.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/fluxcd/image-reflector-controller/api v0.15.0
1414
github.com/fluxcd/pkg/apis/meta v0.10.2
1515
github.com/fluxcd/pkg/gittestserver v0.5.0
16-
github.com/fluxcd/pkg/runtime v0.12.3
16+
github.com/fluxcd/pkg/runtime v0.12.4
1717
github.com/fluxcd/pkg/ssh v0.2.0
1818
// If you bump this, change SOURCE_VER in the Makefile to match
1919
github.com/fluxcd/source-controller v0.21.0

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,9 @@ github.com/fluxcd/pkg/gitutil v0.1.0 h1:VO3kJY/CKOCO4ysDNqfdpTg04icAKBOSb3lbR5uE
366366
github.com/fluxcd/pkg/gitutil v0.1.0/go.mod h1:Ybz50Ck5gkcnvF0TagaMwtlRy3X3wXuiri1HVsK5id4=
367367
github.com/fluxcd/pkg/helmtestserver v0.4.0/go.mod h1:JOI9f3oXUFIWmMKWMBan7FjglAU+fRTO/sPPV/Kj3gQ=
368368
github.com/fluxcd/pkg/lockedfile v0.1.0/go.mod h1:EJLan8t9MiOcgTs8+puDjbE6I/KAfHbdvIy9VUgIjm8=
369-
github.com/fluxcd/pkg/runtime v0.12.3 h1:h21AZ3YG5MAP7DxFF9hfKrP+vFzys2L7CkUbPFjbP/0=
370369
github.com/fluxcd/pkg/runtime v0.12.3/go.mod h1:imJ2xYy/d4PbSinX2IefmZk+iS2c1P5fY0js8mCE4SM=
370+
github.com/fluxcd/pkg/runtime v0.12.4 h1:gA27RG/+adN2/7Qe03PB46Iwmye/MusPCpuS4zQ2fW0=
371+
github.com/fluxcd/pkg/runtime v0.12.4/go.mod h1:gspNvhAqodZgSmK1ZhMtvARBf/NGAlxmaZaIOHkJYsc=
371372
github.com/fluxcd/pkg/ssh v0.2.0 h1:e9V+HReOL7czm7edVzYS1e+CnFKz1/kHiUNfLRpBdH8=
372373
github.com/fluxcd/pkg/ssh v0.2.0/go.mod h1:EpQC7Ztdlbi8S/dlYXqVDZtHtLpN3FNl3N6zWujVzbA=
373374
github.com/fluxcd/pkg/testserver v0.1.0/go.mod h1:fvt8BHhXw6c1+CLw1QFZxcQprlcXzsrL4rzXaiGM+Iw=

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
3030

3131
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
32+
"github.com/fluxcd/pkg/runtime/acl"
3233
"github.com/fluxcd/pkg/runtime/client"
3334
"github.com/fluxcd/pkg/runtime/events"
3435
"github.com/fluxcd/pkg/runtime/leaderelection"
@@ -64,6 +65,7 @@ func main() {
6465
eventsAddr string
6566
healthAddr string
6667
clientOptions client.Options
68+
aclOptions acl.Options
6769
logOptions logger.Options
6870
leaderElectionOptions leaderelection.Options
6971
watchAllNamespaces bool
@@ -79,6 +81,7 @@ func main() {
7981
clientOptions.BindFlags(flag.CommandLine)
8082
logOptions.BindFlags(flag.CommandLine)
8183
leaderElectionOptions.BindFlags(flag.CommandLine)
84+
aclOptions.BindFlags(flag.CommandLine)
8285
flag.Parse()
8386

8487
log := logger.NewLogger(logOptions)
@@ -130,6 +133,7 @@ func main() {
130133
EventRecorder: mgr.GetEventRecorderFor(controllerName),
131134
ExternalEventRecorder: eventRecorder,
132135
MetricsRecorder: metricsRecorder,
136+
NoCrossNamespaceRef: aclOptions.NoCrossNamespaceRefs,
133137
}).SetupWithManager(mgr, controllers.ImageUpdateAutomationReconcilerOptions{
134138
MaxConcurrentReconciles: concurrent,
135139
}); err != nil {

0 commit comments

Comments
 (0)