Skip to content

Commit dcc12d9

Browse files
authored
Allow limiting Contour to watch certain namespaces (#5214)
Signed-off-by: Niklas Simons <[email protected]>
1 parent 180ebd3 commit dcc12d9

File tree

14 files changed

+431
-125
lines changed

14 files changed

+431
-125
lines changed

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ CONTOUR_E2E_LOCAL_HOST ?= $(LOCALIP)
2020
CONTOUR_UPGRADE_FROM_VERSION ?= $(shell ./test/scripts/get-contour-upgrade-from-version.sh)
2121
CONTOUR_E2E_IMAGE ?= ghcr.io/projectcontour/contour:main
2222
CONTOUR_E2E_PACKAGE_FOCUS ?= ./test/e2e
23+
# Optional variables
24+
# Run specific test specs (matched by regex)
25+
CONTOUR_E2E_TEST_FOCUS ?=
2326

2427
TAG_LATEST ?= false
2528

@@ -322,8 +325,8 @@ e2e: | setup-kind-cluster load-contour-image-kind run-e2e cleanup-kind ## Run E2
322325
.PHONY: run-e2e
323326
run-e2e:
324327
CONTOUR_E2E_LOCAL_HOST=$(CONTOUR_E2E_LOCAL_HOST) \
325-
CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \
326-
go run github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -skip-package=upgrade,bench -keep-going -randomize-suites -randomize-all -poll-progress-after=120s -r $(CONTOUR_E2E_PACKAGE_FOCUS)
328+
CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \
329+
go run github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -skip-package=upgrade,bench -keep-going -randomize-suites -randomize-all -poll-progress-after=120s --focus '$(CONTOUR_E2E_TEST_FOCUS)' -r $(CONTOUR_E2E_PACKAGE_FOCUS)
327330

328331
.PHONY: cleanup-kind
329332
cleanup-kind:
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Watching specific namespaces
2+
3+
The `contour serve` command takes a new optional flag, `--watch-namespaces`, that can
4+
be used to restrict the namespaces where the Contour instance watches for resources.
5+
Consequently, resources in other namespaces will not be known to Contour and will not
6+
be acted upon.
7+
8+
You can watch a single or multiple namespaces, and you can further restrict the root
9+
namespaces with `--root-namespaces` just like before. Root namespaces must be a subset
10+
of the namespaces being watched, for example:
11+
12+
`--watch-namespaces=my-admin-namespace,my-app-namespace --root-namespaces=my-admin-namespace`
13+
14+
If the `--watch-namespaces` flag is not used, then all namespaces will be watched by default.

cmd/contour/serve.go

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ func registerServe(app *kingpin.Application) (*kingpin.CmdClause, *serveContext)
166166

167167
serve.Flag("use-proxy-protocol", "Use PROXY protocol for all listeners.").BoolVar(&ctx.useProxyProto)
168168

169+
serve.Flag("watch-namespaces", "Restrict contour to watch resources in these namespaces only.").PlaceHolder("<ns,ns>").StringVar(&ctx.watchNamespaces)
170+
169171
serve.Flag("xds-address", "xDS gRPC API address.").PlaceHolder("<ipaddr>").StringVar(&ctx.xdsAddr)
170172
serve.Flag("xds-port", "xDS gRPC API port.").PlaceHolder("<port>").IntVar(&ctx.xdsPort)
171173

@@ -256,6 +258,12 @@ func NewServer(log logrus.FieldLogger, ctx *serveContext) (*Server, error) {
256258
},
257259
},
258260
}
261+
262+
if watchedNamespaces := ctx.watchedNamespaces(); watchedNamespaces != nil {
263+
log.WithField("namespaces", watchedNamespaces).Info("watching subset of namespaces")
264+
options.Cache.Namespaces = watchedNamespaces
265+
}
266+
259267
if ctx.LeaderElection.Disable {
260268
log.Info("Leader election disabled")
261269
options.LeaderElection = false
@@ -345,26 +353,47 @@ func (s *Server) doServe() error {
345353
return err
346354
}
347355

348-
// informerNamespaces is a set of namespaces that we should start informers for.
349-
// If empty, informers will be started for all namespaces.
350-
informerNamespaces := sets.NewString()
356+
// Check that all the necessary namespaces are being watched
357+
watchedNamespaces := sets.New(s.ctx.watchedNamespaces()...)
358+
rootNamespaces := contourConfiguration.HTTPProxy.RootNamespaces
359+
360+
if len(watchedNamespaces) > 0 {
361+
if !watchedNamespaces.IsSuperset(sets.New(rootNamespaces...)) {
362+
return fmt.Errorf("not all root namespaces are being watched")
363+
}
364+
365+
if fallbackCert := contourConfiguration.HTTPProxy.FallbackCertificate; fallbackCert != nil {
366+
if !watchedNamespaces.Has(fallbackCert.Namespace) {
367+
return fmt.Errorf("the fallbackCertificate namespace (%s) must be watched", fallbackCert.Namespace)
368+
}
369+
}
370+
if clientCert := contourConfiguration.Envoy.ClientCertificate; clientCert != nil {
371+
if !watchedNamespaces.Has(clientCert.Namespace) {
372+
return fmt.Errorf("the clientCertificate namespace (%s) must be watched", clientCert.Namespace)
373+
}
374+
}
375+
}
376+
377+
// secretNamespaces is a set of namespaces that we should start secret informer for.
378+
// If empty, secret informer will be started for all namespaces.
379+
secretNamespaces := sets.New[string]()
351380

352-
if rootNamespaces := contourConfiguration.HTTPProxy.RootNamespaces; len(rootNamespaces) > 0 {
381+
if len(rootNamespaces) > 0 {
353382
s.log.WithField("context", "root-namespaces").Infof("watching root namespaces %q", rootNamespaces)
354-
informerNamespaces.Insert(rootNamespaces...)
383+
secretNamespaces.Insert(rootNamespaces...)
355384

356-
// The fallback cert and client cert's namespaces only need to be added to informerNamespaces
357-
// if we're processing specifici root namespaces, because otherwise, the informers will start
385+
// The fallback cert and client cert's namespaces only need to be added to secretNamespaces
386+
// if we're processing specific root namespaces, because otherwise, the informer will start
358387
// for all namespaces so the below will automatically be included.
359388

360389
if fallbackCert := contourConfiguration.HTTPProxy.FallbackCertificate; fallbackCert != nil {
361390
s.log.WithField("context", "fallback-certificate").Infof("watching fallback certificate namespace %q", fallbackCert.Namespace)
362-
informerNamespaces.Insert(fallbackCert.Namespace)
391+
secretNamespaces.Insert(fallbackCert.Namespace)
363392
}
364393

365394
if clientCert := contourConfiguration.Envoy.ClientCertificate; clientCert != nil {
366395
s.log.WithField("context", "envoy-client-certificate").Infof("watching client certificate namespace %q", clientCert.Namespace)
367-
informerNamespaces.Insert(clientCert.Namespace)
396+
secretNamespaces.Insert(clientCert.Namespace)
368397
}
369398
}
370399

@@ -536,8 +565,8 @@ func (s *Server) doServe() error {
536565
var handler cache.ResourceEventHandler = eventHandler
537566

538567
// If root namespaces are defined, filter for secrets in only those namespaces.
539-
if len(informerNamespaces) > 0 {
540-
handler = k8s.NewNamespaceFilter(informerNamespaces.List(), eventHandler)
568+
if len(secretNamespaces) > 0 {
569+
handler = k8s.NewNamespaceFilter(sets.List(secretNamespaces), eventHandler)
541570
}
542571

543572
if err := informOnResource(&corev1.Secret{}, handler, s.mgr.GetCache()); err != nil {

cmd/contour/servecontext.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ type serveContext struct {
6262
// httpproxy root namespaces
6363
rootNamespaces string
6464

65+
// Watch only these namespaces to allow running with limited RBAC permissions.
66+
watchNamespaces string
67+
6568
// ingress class
6669
ingressClassName string
6770

@@ -249,6 +252,17 @@ func (ctx *serveContext) proxyRootNamespaces() []string {
249252
return ns
250253
}
251254

255+
func (ctx *serveContext) watchedNamespaces() []string {
256+
if strings.TrimSpace(ctx.watchNamespaces) == "" {
257+
return nil
258+
}
259+
var ns []string
260+
for _, s := range strings.Split(ctx.watchNamespaces, ",") {
261+
ns = append(ns, strings.TrimSpace(s))
262+
}
263+
return ns
264+
}
265+
252266
// parseDefaultHTTPVersions parses a list of supported HTTP versions
253267
// (of the form "HTTP/xx") into a slice of unique version constants.
254268
func parseDefaultHTTPVersions(versions []contour_api_v1alpha1.HTTPVersionType) []envoy_v3.HTTPVersionType {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# This kustomize file can be used as an example for deploying Contour without ClusterRole and
2+
# ClusterRoleBinding RBAC privileges.
3+
# It changes the cluster-wide RBAC rules in the example deployment manifest to namespaced RBAC rules.
4+
# It is meant to be used together with contour serve --watch-namespaces=<ns> option to restrict
5+
# Contour to a certain namespace.
6+
# Run with:
7+
# kubectl kustomize examples/namespaced/
8+
apiVersion: kustomize.config.k8s.io/v1beta1
9+
kind: Kustomization
10+
resources:
11+
- ../render/
12+
patches:
13+
- patch: |-
14+
- op: replace
15+
path: /kind
16+
value: RoleBinding
17+
- op: replace
18+
path: /metadata/name
19+
value: contour-resources
20+
- op: replace
21+
path: /roleRef/kind
22+
value: Role
23+
- op: replace
24+
path: /roleRef/name
25+
value: contour-resources
26+
- op: add
27+
path: /metadata/namespace
28+
value: projectcontour
29+
target:
30+
group: rbac.authorization.k8s.io
31+
kind: ClusterRoleBinding
32+
name: contour
33+
version: v1
34+
- patch: |-
35+
- op: replace
36+
path: /kind
37+
value: Role
38+
- op: replace
39+
path: /metadata/name
40+
value: contour-resources
41+
- op: add
42+
path: /metadata/namespace
43+
value: projectcontour
44+
target:
45+
group: rbac.authorization.k8s.io
46+
kind: ClusterRole
47+
name: contour
48+
version: v1
49+
- patch: |-
50+
- op: add
51+
path: /spec/template/spec/containers/0/args/-
52+
value: --watch-namespaces=projectcontour
53+
target:
54+
group: apps
55+
kind: Deployment
56+
name: contour
57+
version: v1

site/content/docs/main/config/inclusion-delegation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ spec:
9191
Inclusion can also happen across Namespaces by specifying a `namespace` in the `inclusion`.
9292
This is a particularly powerful paradigm for enabling multi-team Ingress management.
9393

94+
If the `--watch-namespaces` configuration flag is used, it must define all namespaces that will be referenced by the inclusion.
95+
9496
In this example, the root HTTPProxy has included configuration for paths matching `/blog` to the `blog` HTTPProxy object in the `marketing` namespace.
9597

9698
```yaml

site/content/docs/main/config/tls-delegation.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ In order to support wildcard certificates, TLS certificates for a `*.somedomain.
44
This facility allows the owner of a TLS certificate to delegate, for the purposes of referencing the TLS certificate, permission to Contour to read the Secret object from another namespace.
55
Delegation works for both HTTPProxy and Ingress resources, however it needs an annotation to work with Ingress v1.
66

7+
If the `--watch-namespaces` configuration flag is used, it must define all namespaces that will be referenced by the delegation.
8+
79
The [`TLSCertificateDelegation`][1] resource defines a set of `delegations` in the `spec`.
810
Each delegation references a `secretName` from the namespace where the `TLSCertificateDelegation` is created as well as describing a set of `targetNamespaces` in which the certificate can be referenced.
911
If all namespaces should be able to reference the secret, then set `"*"` as the value of `targetNamespaces` (see example below).
@@ -24,7 +26,7 @@ spec:
2426
- "*"
2527
```
2628
27-
In this example, the permission for Contour to reference the Secret `example-com-wildcard` in the `admin` namespace has been delegated to HTTPProxy and Ingress objects in the `example-com` namespace.
29+
In this example, the permission for Contour to reference the Secret `example-com-wildcard` in the `www-admin` namespace has been delegated to HTTPProxy and Ingress objects in the `example-com` namespace.
2830
Also, the permission for Contour to reference the Secret `another-com-wildcard` from all namespaces has been delegated to all HTTPProxy and Ingress objects in the cluster.
2931

3032
To reference the secret from an HTTPProxy or Ingress v1beta1 you must use the slash syntax in the `secretName`:

site/content/docs/main/config/virtual-hosts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ Proper RBAC rules should also be created to restrict what namespaces Contour has
132132
An example of this is included in the [examples directory][1] and shows how you might create a namespace called `root-httproxy`.
133133

134134
_**Note:** The restricted root namespace feature is only supported for HTTPProxy CRDs.
135-
`--root-namespaces` does not affect the operation of Ingress objects._
135+
`--root-namespaces` does not affect the operation of Ingress objects. In order to limit other resources, see the `--watch-namespaces` configuration flag._
136136

137137
[1]: {{< param github_url>}}/tree/{{< param branch >}}/examples/root-rbac
138138
[2]: api/#projectcontour.io/v1.VirtualHost

site/content/docs/main/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Many of these flags are mirrored in the [Contour Configuration File](#configurat
3737
| `--contour-key-file=</path/to/file\|CONTOUR_KEY_FILE>` | Contour key file name for serving gRPC over TLS |
3838
| `--insecure` | Allow serving without TLS secured gRPC |
3939
| `--root-namespaces=<ns,ns>` | Restrict contour to searching these namespaces for root ingress routes |
40+
| `--watch-namespaces=<ns,ns>` | Restrict contour to searching these namespaces for all resources |
4041
| `--ingress-class-name=<name>` | Contour IngressClass name (comma-separated list allowed) |
4142
| `--ingress-status-address=<address>` | Address to set in Ingress object status |
4243
| `--envoy-http-access-log=</path/to/file>` | Envoy HTTP access log |

site/content/docs/main/deploy-options.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ Also see the [upgrade guides][15] on steps to roll out a new version of Contour.
223223

224224
It's possible to run multiple instances of Contour within a single Kubernetes cluster.
225225
This can be useful for separating external vs. internal ingress, for having separate ingress controllers for different ingress classes, and more.
226+
Each Contour instance can also be configured via the `--watch-namespaces` flag to handle their own namespaces. This allows the Kubernetes RBAC objects
227+
to be restricted further.
228+
226229
The recommended way to deploy multiple Contour instances is to put each instance in its own namespace.
227230
This avoids most naming conflicts that would otherwise occur, and provides better logical separation between the instances.
228231
However, it is also possible to deploy multiple instances in a single namespace if needed; this approach requires more modifications to the example manifests to function properly.

0 commit comments

Comments
 (0)