Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions charts/flagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ The following tables lists the configurable parameters of the Flagger chart and
| `podPriorityClassName` | PriorityClass name for pod priority configuration | "" |
| `podDisruptionBudget.enabled` | A PodDisruptionBudget will be created if `true` | `false` |
| `podDisruptionBudget.minAvailable` | The minimal number of available replicas that will be set in the PodDisruptionBudget | `1` |
| `podDisruptionBudget.minAvailable` | The minimal number of available replicas that will be set in the PodDisruptionBudget | `1` |
| `noCrossNamespaceRefs` | If `true`, cross namespace references to custom resources will be disabled. | `false` |

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

Expand Down
3 changes: 3 additions & 0 deletions charts/flagger/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ spec:
{{- if .Values.clusterName }}
- -cluster-name={{ .Values.clusterName }}
{{- end }}
{{- if .Values.noCrossNamespaceRefs }}
- -no-cross-namespace-refs={{ .Values.noCrossNamespaceRefs }}
{{- end }}
livenessProbe:
exec:
command:
Expand Down
2 changes: 2 additions & 0 deletions charts/flagger/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,5 @@ podDisruptionBudget:
minAvailable: 1

podLabels: {}

noCrossNamespaceRefs: false
3 changes: 3 additions & 0 deletions cmd/flagger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ var (
ver bool
kubeconfigServiceMesh string
clusterName string
noCrossNamespaceRefs bool
)

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

func main() {
Expand Down Expand Up @@ -241,6 +243,7 @@ func main() {
version.VERSION,
fromEnv("EVENT_WEBHOOK_URL", eventWebhook),
clusterName,
noCrossNamespaceRefs,
)

// leader election context
Expand Down
11 changes: 11 additions & 0 deletions docs/gitbook/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ A window of downtime is the intended behavior when the analysis is disabled. Thi
a Kubernetes deployment initialization works. To avoid this, enable the analysis (`skipAnalysis: true`), wait for the initialization
to finish, and disable it afterward (`skipAnalysis: false`).

#### How to disable cross namespace references?

Flagger by default can access resources across namespaces (`AlertProivder`, `MetricProvider` and Gloo `Upsteream`).
If you're in a multi-tenant environment and wish to disable this, you can do so through the `no-cross-namespace-refs` flag.

```
flagger \
-no-cross-namespace-refs=true \
...
```

## Kubernetes services

#### How is an application exposed inside the cluster?
Expand Down
21 changes: 18 additions & 3 deletions pkg/apis/flagger/v1beta1/canary.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ type CanarySpec struct {
MetricsServer string `json:"metricsServer,omitempty"`

// TargetRef references a target resource
TargetRef CrossNamespaceObjectReference `json:"targetRef"`
TargetRef LocalObjectReference `json:"targetRef"`

// AutoscalerRef references an autoscaling resource
// +optional
AutoscalerRef *CrossNamespaceObjectReference `json:"autoscalerRef,omitempty"`
AutoscalerRef *LocalObjectReference `json:"autoscalerRef,omitempty"`

// Reference to NGINX ingress resource
// +optional
IngressRef *CrossNamespaceObjectReference `json:"ingressRef,omitempty"`
IngressRef *LocalObjectReference `json:"ingressRef,omitempty"`

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

// LocalObjectReference contains enough information to let you locate the typed
// referenced object in the same namespace.
type LocalObjectReference struct {
// API version of the referent
// +optional
APIVersion string `json:"apiVersion,omitempty"`

// Kind of the referent
// +optional
Kind string `json:"kind,omitempty"`

// Name of the referent
Name string `json:"name"`
}

// CustomMetadata holds labels and annotations to set on generated objects.
type CustomMetadata struct {
Labels map[string]string `json:"labels,omitempty"`
Expand Down
20 changes: 18 additions & 2 deletions pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/canary/daemonset_fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ func newDaemonSetControllerTestCanary(dc daemonsetConfigs) *flaggerv1.Canary {
Name: "podinfo",
},
Spec: flaggerv1.CanarySpec{
TargetRef: flaggerv1.CrossNamespaceObjectReference{
TargetRef: flaggerv1.LocalObjectReference{
Name: dc.name,
APIVersion: "apps/v1",
Kind: "DaemonSet",
Expand Down
4 changes: 2 additions & 2 deletions pkg/canary/deployment_fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,12 +392,12 @@ func newDeploymentControllerTestCanary(cc canaryConfigs) *flaggerv1.Canary {
Name: "podinfo",
},
Spec: flaggerv1.CanarySpec{
TargetRef: flaggerv1.CrossNamespaceObjectReference{
TargetRef: flaggerv1.LocalObjectReference{
Name: cc.targetName,
APIVersion: "apps/v1",
Kind: "Deployment",
},
AutoscalerRef: &flaggerv1.CrossNamespaceObjectReference{
AutoscalerRef: &flaggerv1.LocalObjectReference{
Name: "podinfo",
APIVersion: "autoscaling/v2beta2",
Kind: "HorizontalPodAutoscaler",
Expand Down
107 changes: 71 additions & 36 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,25 @@ const controllerAgentName = "flagger"

// Controller is managing the canary objects and schedules canary deployments
type Controller struct {
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
flaggerInformers Informers
flaggerSynced cache.InformerSynced
flaggerWindow time.Duration
workqueue workqueue.RateLimitingInterface
eventRecorder record.EventRecorder
logger *zap.SugaredLogger
canaries *sync.Map
jobs map[string]CanaryJob
recorder metrics.Recorder
notifier notifier.Interface
canaryFactory *canary.Factory
routerFactory *router.Factory
observerFactory *observers.Factory
meshProvider string
eventWebhook string
clusterName string
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
flaggerInformers Informers
flaggerSynced cache.InformerSynced
flaggerWindow time.Duration
workqueue workqueue.RateLimitingInterface
eventRecorder record.EventRecorder
logger *zap.SugaredLogger
canaries *sync.Map
jobs map[string]CanaryJob
recorder metrics.Recorder
notifier notifier.Interface
canaryFactory *canary.Factory
routerFactory *router.Factory
observerFactory *observers.Factory
meshProvider string
eventWebhook string
clusterName string
noCrossNamespaceRefs bool
}

type Informers struct {
Expand All @@ -89,6 +90,7 @@ func NewController(
version string,
eventWebhook string,
clusterName string,
noCrossNamespaceRefs bool,
) *Controller {
logger.Debug("Creating event broadcaster")
flaggerscheme.AddToScheme(scheme.Scheme)
Expand All @@ -103,24 +105,25 @@ func NewController(
recorder.SetInfo(version, meshProvider)

ctrl := &Controller{
kubeClient: kubeClient,
flaggerClient: flaggerClient,
flaggerInformers: flaggerInformers,
flaggerSynced: flaggerInformers.CanaryInformer.Informer().HasSynced,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerAgentName),
eventRecorder: eventRecorder,
logger: logger,
canaries: new(sync.Map),
jobs: map[string]CanaryJob{},
flaggerWindow: flaggerWindow,
observerFactory: observerFactory,
recorder: recorder,
notifier: notifier,
canaryFactory: canaryFactory,
routerFactory: routerFactory,
meshProvider: meshProvider,
eventWebhook: eventWebhook,
clusterName: clusterName,
kubeClient: kubeClient,
flaggerClient: flaggerClient,
flaggerInformers: flaggerInformers,
flaggerSynced: flaggerInformers.CanaryInformer.Informer().HasSynced,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerAgentName),
eventRecorder: eventRecorder,
logger: logger,
canaries: new(sync.Map),
jobs: map[string]CanaryJob{},
flaggerWindow: flaggerWindow,
observerFactory: observerFactory,
recorder: recorder,
notifier: notifier,
canaryFactory: canaryFactory,
routerFactory: routerFactory,
meshProvider: meshProvider,
eventWebhook: eventWebhook,
clusterName: clusterName,
noCrossNamespaceRefs: noCrossNamespaceRefs,
}

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

if err := c.verifyCanary(cd); err != nil {
return fmt.Errorf("invalid canary spec: %s", err)
}

// Finalize if canary has been marked for deletion and revert is desired
if cd.Spec.RevertOnDeletion && cd.ObjectMeta.DeletionTimestamp != nil {
// If finalizers have been previously removed proceed
Expand Down Expand Up @@ -312,6 +319,34 @@ func (c *Controller) enqueue(obj interface{}) {
c.workqueue.AddRateLimited(key)
}

func (c *Controller) verifyCanary(canary *flaggerv1.Canary) error {
if c.noCrossNamespaceRefs {
if err := verifyNoCrossNamespaceRefs(canary); err != nil {
return err
}
}
return nil
}

func verifyNoCrossNamespaceRefs(canary *flaggerv1.Canary) error {
if canary.Spec.UpstreamRef != nil && canary.Spec.UpstreamRef.Namespace != canary.Namespace {
return fmt.Errorf("can't access gloo upstream %s.%s, cross-namespace references are blocked", canary.Spec.UpstreamRef.Name, canary.Spec.UpstreamRef.Namespace)
}
if canary.Spec.Analysis != nil {
for _, metric := range canary.Spec.Analysis.Metrics {
if metric.TemplateRef != nil && metric.TemplateRef.Namespace != canary.Namespace {
return fmt.Errorf("can't access metric template %s.%s, cross-namespace references are blocked", metric.TemplateRef.Name, metric.TemplateRef.Namespace)
}
}
for _, alert := range canary.Spec.Analysis.Alerts {
if alert.ProviderRef.Namespace != canary.Namespace {
return fmt.Errorf("can't access alert provider %s.%s, cross-namespace references are blocked", alert.ProviderRef.Name, alert.ProviderRef.Namespace)
}
}
}
return nil
}

func checkCustomResourceType(obj interface{}, logger *zap.SugaredLogger) (flaggerv1.Canary, bool) {
var roll *flaggerv1.Canary
var ok bool
Expand Down
Loading