Skip to content

Commit 7309959

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

File tree

14 files changed

+445
-126
lines changed

14 files changed

+445
-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

@@ -318,8 +321,8 @@ e2e: | setup-kind-cluster load-contour-image-kind run-e2e cleanup-kind ## Run E2
318321
.PHONY: run-e2e
319322
run-e2e:
320323
CONTOUR_E2E_LOCAL_HOST=$(CONTOUR_E2E_LOCAL_HOST) \
321-
CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \
322-
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)
324+
CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \
325+
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)
323326

324327
.PHONY: cleanup-kind
325328
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

@@ -493,8 +532,8 @@ func (s *Server) doServe() error {
493532
var handler cache.ResourceEventHandler = eventHandler
494533

495534
// If root namespaces are defined, filter for secrets in only those namespaces.
496-
if len(informerNamespaces) > 0 {
497-
handler = k8s.NewNamespaceFilter(informerNamespaces.List(), eventHandler)
535+
if len(secretNamespaces) > 0 {
536+
handler = k8s.NewNamespaceFilter(sets.List(secretNamespaces), eventHandler)
498537
}
499538

500539
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
@@ -63,6 +63,9 @@ type serveContext struct {
6363
// httpproxy root namespaces
6464
rootNamespaces string
6565

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

@@ -250,6 +253,17 @@ func (ctx *serveContext) proxyRootNamespaces() []string {
250253
return ns
251254
}
252255

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