Skip to content

Commit a8b4e9c

Browse files
authored
Merge pull request #1181 from aryan9600/no-cross-ns-refs
Add flag to disable cross namespace refs to Custom Resources
2 parents 95381e1 + 30ed9fb commit a8b4e9c

18 files changed

+258
-73
lines changed

charts/flagger/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ The following tables lists the configurable parameters of the Flagger chart and
179179
| `podPriorityClassName` | PriorityClass name for pod priority configuration | "" |
180180
| `podDisruptionBudget.enabled` | A PodDisruptionBudget will be created if `true` | `false` |
181181
| `podDisruptionBudget.minAvailable` | The minimal number of available replicas that will be set in the PodDisruptionBudget | `1` |
182+
| `podDisruptionBudget.minAvailable` | The minimal number of available replicas that will be set in the PodDisruptionBudget | `1` |
183+
| `noCrossNamespaceRefs` | If `true`, cross namespace references to custom resources will be disabled. | `false` |
182184

183185
Specify each parameter using the `--set key=value[,key=value]` argument to `helm upgrade`. For example,
184186

charts/flagger/templates/deployment.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ spec:
141141
{{- if .Values.clusterName }}
142142
- -cluster-name={{ .Values.clusterName }}
143143
{{- end }}
144+
{{- if .Values.noCrossNamespaceRefs }}
145+
- -no-cross-namespace-refs={{ .Values.noCrossNamespaceRefs }}
146+
{{- end }}
144147
livenessProbe:
145148
exec:
146149
command:

charts/flagger/values.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,5 @@ podDisruptionBudget:
165165
minAvailable: 1
166166

167167
podLabels: {}
168+
169+
noCrossNamespaceRefs: false

cmd/flagger/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ var (
8484
ver bool
8585
kubeconfigServiceMesh string
8686
clusterName string
87+
noCrossNamespaceRefs bool
8788
)
8889

8990
func init() {
@@ -117,6 +118,7 @@ func init() {
117118
flag.BoolVar(&ver, "version", false, "Print version")
118119
flag.StringVar(&kubeconfigServiceMesh, "kubeconfig-service-mesh", "", "Path to a kubeconfig for the service mesh control plane cluster.")
119120
flag.StringVar(&clusterName, "cluster-name", "", "Cluster name to be included in alert msgs.")
121+
flag.BoolVar(&noCrossNamespaceRefs, "no-cross-namespace-refs", false, "When set to true, Flagger can only refer to resources in the same namespace.")
120122
}
121123

122124
func main() {
@@ -241,6 +243,7 @@ func main() {
241243
version.VERSION,
242244
fromEnv("EVENT_WEBHOOK_URL", eventWebhook),
243245
clusterName,
246+
noCrossNamespaceRefs,
244247
)
245248

246249
// leader election context

docs/gitbook/faq.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ A window of downtime is the intended behavior when the analysis is disabled. Thi
7272
a Kubernetes deployment initialization works. To avoid this, enable the analysis (`skipAnalysis: true`), wait for the initialization
7373
to finish, and disable it afterward (`skipAnalysis: false`).
7474

75+
#### How to disable cross namespace references?
76+
77+
Flagger by default can access resources across namespaces (`AlertProivder`, `MetricProvider` and Gloo `Upsteream`).
78+
If you're in a multi-tenant environment and wish to disable this, you can do so through the `no-cross-namespace-refs` flag.
79+
80+
```
81+
flagger \
82+
-no-cross-namespace-refs=true \
83+
...
84+
```
85+
7586
## Kubernetes services
7687
7788
#### How is an application exposed inside the cluster?

pkg/apis/flagger/v1beta1/canary.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ type CanarySpec struct {
7070
MetricsServer string `json:"metricsServer,omitempty"`
7171

7272
// TargetRef references a target resource
73-
TargetRef CrossNamespaceObjectReference `json:"targetRef"`
73+
TargetRef LocalObjectReference `json:"targetRef"`
7474

7575
// AutoscalerRef references an autoscaling resource
7676
// +optional
77-
AutoscalerRef *CrossNamespaceObjectReference `json:"autoscalerRef,omitempty"`
77+
AutoscalerRef *LocalObjectReference `json:"autoscalerRef,omitempty"`
7878

7979
// Reference to NGINX ingress resource
8080
// +optional
81-
IngressRef *CrossNamespaceObjectReference `json:"ingressRef,omitempty"`
81+
IngressRef *LocalObjectReference `json:"ingressRef,omitempty"`
8282

8383
// Reference to Gloo Upstream resource. Upstream config is copied from
8484
// the referenced upstream to the upstreams generated by flagger.
@@ -393,6 +393,21 @@ type CrossNamespaceObjectReference struct {
393393
Namespace string `json:"namespace,omitempty"`
394394
}
395395

396+
// LocalObjectReference contains enough information to let you locate the typed
397+
// referenced object in the same namespace.
398+
type LocalObjectReference struct {
399+
// API version of the referent
400+
// +optional
401+
APIVersion string `json:"apiVersion,omitempty"`
402+
403+
// Kind of the referent
404+
// +optional
405+
Kind string `json:"kind,omitempty"`
406+
407+
// Name of the referent
408+
Name string `json:"name"`
409+
}
410+
396411
// CustomMetadata holds labels and annotations to set on generated objects.
397412
type CustomMetadata struct {
398413
Labels map[string]string `json:"labels,omitempty"`

pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/canary/daemonset_fixture_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ func newDaemonSetControllerTestCanary(dc daemonsetConfigs) *flaggerv1.Canary {
352352
Name: "podinfo",
353353
},
354354
Spec: flaggerv1.CanarySpec{
355-
TargetRef: flaggerv1.CrossNamespaceObjectReference{
355+
TargetRef: flaggerv1.LocalObjectReference{
356356
Name: dc.name,
357357
APIVersion: "apps/v1",
358358
Kind: "DaemonSet",

pkg/canary/deployment_fixture_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,12 +392,12 @@ func newDeploymentControllerTestCanary(cc canaryConfigs) *flaggerv1.Canary {
392392
Name: "podinfo",
393393
},
394394
Spec: flaggerv1.CanarySpec{
395-
TargetRef: flaggerv1.CrossNamespaceObjectReference{
395+
TargetRef: flaggerv1.LocalObjectReference{
396396
Name: cc.targetName,
397397
APIVersion: "apps/v1",
398398
Kind: "Deployment",
399399
},
400-
AutoscalerRef: &flaggerv1.CrossNamespaceObjectReference{
400+
AutoscalerRef: &flaggerv1.LocalObjectReference{
401401
Name: "podinfo",
402402
APIVersion: "autoscaling/v2beta2",
403403
Kind: "HorizontalPodAutoscaler",

pkg/controller/controller.go

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,25 @@ const controllerAgentName = "flagger"
4949

5050
// Controller is managing the canary objects and schedules canary deployments
5151
type Controller struct {
52-
kubeClient kubernetes.Interface
53-
flaggerClient clientset.Interface
54-
flaggerInformers Informers
55-
flaggerSynced cache.InformerSynced
56-
flaggerWindow time.Duration
57-
workqueue workqueue.RateLimitingInterface
58-
eventRecorder record.EventRecorder
59-
logger *zap.SugaredLogger
60-
canaries *sync.Map
61-
jobs map[string]CanaryJob
62-
recorder metrics.Recorder
63-
notifier notifier.Interface
64-
canaryFactory *canary.Factory
65-
routerFactory *router.Factory
66-
observerFactory *observers.Factory
67-
meshProvider string
68-
eventWebhook string
69-
clusterName string
52+
kubeClient kubernetes.Interface
53+
flaggerClient clientset.Interface
54+
flaggerInformers Informers
55+
flaggerSynced cache.InformerSynced
56+
flaggerWindow time.Duration
57+
workqueue workqueue.RateLimitingInterface
58+
eventRecorder record.EventRecorder
59+
logger *zap.SugaredLogger
60+
canaries *sync.Map
61+
jobs map[string]CanaryJob
62+
recorder metrics.Recorder
63+
notifier notifier.Interface
64+
canaryFactory *canary.Factory
65+
routerFactory *router.Factory
66+
observerFactory *observers.Factory
67+
meshProvider string
68+
eventWebhook string
69+
clusterName string
70+
noCrossNamespaceRefs bool
7071
}
7172

7273
type Informers struct {
@@ -89,6 +90,7 @@ func NewController(
8990
version string,
9091
eventWebhook string,
9192
clusterName string,
93+
noCrossNamespaceRefs bool,
9294
) *Controller {
9395
logger.Debug("Creating event broadcaster")
9496
flaggerscheme.AddToScheme(scheme.Scheme)
@@ -103,24 +105,25 @@ func NewController(
103105
recorder.SetInfo(version, meshProvider)
104106

105107
ctrl := &Controller{
106-
kubeClient: kubeClient,
107-
flaggerClient: flaggerClient,
108-
flaggerInformers: flaggerInformers,
109-
flaggerSynced: flaggerInformers.CanaryInformer.Informer().HasSynced,
110-
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerAgentName),
111-
eventRecorder: eventRecorder,
112-
logger: logger,
113-
canaries: new(sync.Map),
114-
jobs: map[string]CanaryJob{},
115-
flaggerWindow: flaggerWindow,
116-
observerFactory: observerFactory,
117-
recorder: recorder,
118-
notifier: notifier,
119-
canaryFactory: canaryFactory,
120-
routerFactory: routerFactory,
121-
meshProvider: meshProvider,
122-
eventWebhook: eventWebhook,
123-
clusterName: clusterName,
108+
kubeClient: kubeClient,
109+
flaggerClient: flaggerClient,
110+
flaggerInformers: flaggerInformers,
111+
flaggerSynced: flaggerInformers.CanaryInformer.Informer().HasSynced,
112+
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerAgentName),
113+
eventRecorder: eventRecorder,
114+
logger: logger,
115+
canaries: new(sync.Map),
116+
jobs: map[string]CanaryJob{},
117+
flaggerWindow: flaggerWindow,
118+
observerFactory: observerFactory,
119+
recorder: recorder,
120+
notifier: notifier,
121+
canaryFactory: canaryFactory,
122+
routerFactory: routerFactory,
123+
meshProvider: meshProvider,
124+
eventWebhook: eventWebhook,
125+
clusterName: clusterName,
126+
noCrossNamespaceRefs: noCrossNamespaceRefs,
124127
}
125128

126129
flaggerInformers.CanaryInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
@@ -250,6 +253,10 @@ func (c *Controller) syncHandler(key string) error {
250253
return nil
251254
}
252255

256+
if err := c.verifyCanary(cd); err != nil {
257+
return fmt.Errorf("invalid canary spec: %s", err)
258+
}
259+
253260
// Finalize if canary has been marked for deletion and revert is desired
254261
if cd.Spec.RevertOnDeletion && cd.ObjectMeta.DeletionTimestamp != nil {
255262
// If finalizers have been previously removed proceed
@@ -312,6 +319,34 @@ func (c *Controller) enqueue(obj interface{}) {
312319
c.workqueue.AddRateLimited(key)
313320
}
314321

322+
func (c *Controller) verifyCanary(canary *flaggerv1.Canary) error {
323+
if c.noCrossNamespaceRefs {
324+
if err := verifyNoCrossNamespaceRefs(canary); err != nil {
325+
return err
326+
}
327+
}
328+
return nil
329+
}
330+
331+
func verifyNoCrossNamespaceRefs(canary *flaggerv1.Canary) error {
332+
if canary.Spec.UpstreamRef != nil && canary.Spec.UpstreamRef.Namespace != canary.Namespace {
333+
return fmt.Errorf("can't access gloo upstream %s.%s, cross-namespace references are blocked", canary.Spec.UpstreamRef.Name, canary.Spec.UpstreamRef.Namespace)
334+
}
335+
if canary.Spec.Analysis != nil {
336+
for _, metric := range canary.Spec.Analysis.Metrics {
337+
if metric.TemplateRef != nil && metric.TemplateRef.Namespace != canary.Namespace {
338+
return fmt.Errorf("can't access metric template %s.%s, cross-namespace references are blocked", metric.TemplateRef.Name, metric.TemplateRef.Namespace)
339+
}
340+
}
341+
for _, alert := range canary.Spec.Analysis.Alerts {
342+
if alert.ProviderRef.Namespace != canary.Namespace {
343+
return fmt.Errorf("can't access alert provider %s.%s, cross-namespace references are blocked", alert.ProviderRef.Name, alert.ProviderRef.Namespace)
344+
}
345+
}
346+
}
347+
return nil
348+
}
349+
315350
func checkCustomResourceType(obj interface{}, logger *zap.SugaredLogger) (flaggerv1.Canary, bool) {
316351
var roll *flaggerv1.Canary
317352
var ok bool

0 commit comments

Comments
 (0)