Skip to content

Commit 9efccd6

Browse files
author
Niklas Simons
committed
Allow limiting Contour to watch certain namespaces
Signed-off-by: Niklas Simons <[email protected]>
1 parent aa750df commit 9efccd6

File tree

14 files changed

+417
-126
lines changed

14 files changed

+417
-126
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

@@ -313,8 +316,8 @@ e2e: | setup-kind-cluster load-contour-image-kind run-e2e cleanup-kind ## Run E2
313316
.PHONY: run-e2e
314317
run-e2e:
315318
CONTOUR_E2E_LOCAL_HOST=$(CONTOUR_E2E_LOCAL_HOST) \
316-
CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \
317-
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)
319+
CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \
320+
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)
318321

319322
.PHONY: cleanup-kind
320323
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 watch 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: 50 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

@@ -211,11 +213,27 @@ func NewServer(log logrus.FieldLogger, ctx *serveContext) (*Server, error) {
211213
return nil, fmt.Errorf("unable to create scheme: %w", err)
212214
}
213215

216+
var cacheFunc ctrl_cache.NewCacheFunc
217+
var namespace string
218+
if watchedNamespaces := ctx.watchedNamespaces(); watchedNamespaces != nil {
219+
// Watch a single namespace
220+
if len(watchedNamespaces) == 1 {
221+
namespace = watchedNamespaces[0]
222+
}
223+
// Watch multiple namespaces
224+
if len(watchedNamespaces) > 1 {
225+
cacheFunc = ctrl_cache.MultiNamespacedCacheBuilder(watchedNamespaces)
226+
}
227+
log.WithField("context", "watched-namespaces").Infof("watching namespaces %q", watchedNamespaces)
228+
}
229+
214230
// Instantiate a controller-runtime manager.
215231
options := manager.Options{
216232
Scheme: scheme,
217233
MetricsBindAddress: "0",
218234
HealthProbeBindAddress: "0",
235+
Namespace: namespace,
236+
NewCache: cacheFunc,
219237
}
220238
if ctx.LeaderElection.Disable {
221239
log.Info("Leader election disabled")
@@ -306,26 +324,47 @@ func (s *Server) doServe() error {
306324
return err
307325
}
308326

309-
// informerNamespaces is a set of namespaces that we should start informers for.
310-
// If empty, informers will be started for all namespaces.
311-
informerNamespaces := sets.NewString()
327+
// Check that all the necessary namespaces are being watched
328+
watchedNamespaces := sets.New(s.ctx.watchedNamespaces()...)
329+
rootNamespaces := contourConfiguration.HTTPProxy.RootNamespaces
330+
331+
if len(watchedNamespaces) > 0 {
332+
if !watchedNamespaces.IsSuperset(sets.New(rootNamespaces...)) {
333+
return fmt.Errorf("not all root namespaces are being watched")
334+
}
335+
336+
if fallbackCert := contourConfiguration.HTTPProxy.FallbackCertificate; fallbackCert != nil {
337+
if !watchedNamespaces.Has(fallbackCert.Namespace) {
338+
return fmt.Errorf("the fallbackCertificate namespace (%s) must be watched", fallbackCert.Namespace)
339+
}
340+
}
341+
if clientCert := contourConfiguration.Envoy.ClientCertificate; clientCert != nil {
342+
if !watchedNamespaces.Has(clientCert.Namespace) {
343+
return fmt.Errorf("the clientCertificate namespace (%s) must be watched", clientCert.Namespace)
344+
}
345+
}
346+
}
347+
348+
// secretNamespaces is a set of namespaces that we should start secret informer for.
349+
// If empty, secret informer will be started for all namespaces.
350+
secretNamespaces := sets.New[string]()
312351

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

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

321360
if fallbackCert := contourConfiguration.HTTPProxy.FallbackCertificate; fallbackCert != nil {
322361
s.log.WithField("context", "fallback-certificate").Infof("watching fallback certificate namespace %q", fallbackCert.Namespace)
323-
informerNamespaces.Insert(fallbackCert.Namespace)
362+
secretNamespaces.Insert(fallbackCert.Namespace)
324363
}
325364

326365
if clientCert := contourConfiguration.Envoy.ClientCertificate; clientCert != nil {
327366
s.log.WithField("context", "envoy-client-certificate").Infof("watching client certificate namespace %q", clientCert.Namespace)
328-
informerNamespaces.Insert(clientCert.Namespace)
367+
secretNamespaces.Insert(clientCert.Namespace)
329368
}
330369
}
331370

@@ -498,8 +537,8 @@ func (s *Server) doServe() error {
498537
var handler cache.ResourceEventHandler = eventHandler
499538

500539
// If root namespaces are defined, filter for secrets in only those namespaces.
501-
if len(informerNamespaces) > 0 {
502-
handler = k8s.NewNamespaceFilter(informerNamespaces.List(), eventHandler)
540+
if len(secretNamespaces) > 0 {
541+
handler = k8s.NewNamespaceFilter(sets.List(secretNamespaces), eventHandler)
503542
}
504543

505544
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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
resources:
2+
- ../render/
3+
4+
patchesJson6902:
5+
- target:
6+
group: rbac.authorization.k8s.io
7+
version: v1
8+
kind: ClusterRoleBinding
9+
name: contour
10+
# This patch changes the ClusterRoleBinding to a RoleBinding,
11+
# and renames the object to contour-resources to avoid conflict with existing RoleBinding.
12+
patch: |-
13+
- op: replace
14+
path: /kind
15+
value: RoleBinding
16+
- op: replace
17+
path: /metadata/name
18+
value: contour-resources
19+
- op: replace
20+
path: /roleRef/kind
21+
value: Role
22+
- op: replace
23+
path: /roleRef/name
24+
value: contour-resources
25+
- op: add
26+
path: /metadata/namespace
27+
value: projectcontour
28+
# This patch changes the ClusterRole to a Role,
29+
# and renames the object to contour-resources to avoid conflict with existing Role.
30+
- target:
31+
group: rbac.authorization.k8s.io
32+
version: v1
33+
kind: ClusterRole
34+
name: contour
35+
patch: |-
36+
- op: replace
37+
path: /kind
38+
value: Role
39+
- op: replace
40+
path: /metadata/name
41+
value: contour-resources
42+
- op: add
43+
path: /metadata/namespace
44+
value: projectcontour
45+
# Append argument to Contour serve command
46+
- target:
47+
group: apps
48+
version: v1
49+
kind: Deployment
50+
name: contour
51+
patch: |-
52+
- op: add
53+
path: /spec/template/spec/containers/0/args/-
54+
value: --watch-namespaces=projectcontour

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

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

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

9496
```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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ Many of these flags are mirrored in the [Contour Configuration File](#configurat
3636
| `--contour-cert-file=</path/to/file\|CONTOUR_CERT_FILE>` | Contour certificate file name for serving gRPC over TLS |
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 |
39-
| `--root-namespaces=<ns,ns>` | Restrict contour to searching these namespaces for root ingress routes |
39+
| `--root-namespaces=<ns,ns>` | Restrict root httpproxies and secrets to these namespaces |
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
@@ -219,6 +219,9 @@ Also see the [upgrade guides][15] on steps to roll out a new version of Contour.
219219

220220
It's possible to run multiple instances of Contour within a single Kubernetes cluster.
221221
This can be useful for separating external vs. internal ingress, for having separate ingress controllers for different ingress classes, and more.
222+
Each Contour instance can also be configured via the `--watch-namespaces` flag to handle their own namespaces. This allows the Kubernetes RBAC objects
223+
to be restricted further.
224+
222225
The recommended way to deploy multiple Contour instances is to put each instance in its own namespace.
223226
This avoids most naming conflicts that would otherwise occur, and provides better logical separation between the instances.
224227
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)