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
1 change: 0 additions & 1 deletion api/bindings/v1alpha1/boundendpoint_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ type BoundEndpoint struct {
}

// +kubebuilder:object:root=true
// +kubebuilder:rbac:groups=core,resources=services,verbs=get;create;update;delete;list;watch

// BoundEndpointList contains a list of BoundEndpoint
type BoundEndpointList struct {
Expand Down
20 changes: 16 additions & 4 deletions cmd/agent-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand Down Expand Up @@ -157,12 +158,23 @@ func runAgentController(_ context.Context, opts agentManagerOpts) error {
LeaderElection: false,
}

// The KubernetesOperator CR is a singleton owned by the operator and always
// lives in the release namespace, regardless of `watchNamespace`. Pin its
// cache scope to the release namespace so the drain state checker can always
// read it, and so RBAC for it can stay narrowly scoped to the release namespace.
options.Cache = cache.Options{
ByObject: map[client.Object]cache.ByObject{
&ngrokv1alpha1.KubernetesOperator{}: {
Namespaces: map[string]cache.Config{
opts.namespace: {},
},
},
},
}
if opts.watchNamespace != "" {
setupLog.Info("watching namespace", "namespace", opts.watchNamespace)
options.Cache = cache.Options{
DefaultNamespaces: map[string]cache.Config{
opts.watchNamespace: {},
},
options.Cache.DefaultNamespaces = map[string]cache.Config{
opts.watchNamespace: {},
}
}

Expand Down
19 changes: 15 additions & 4 deletions cmd/api-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,22 @@ func loadManager(k8sConfig *rest.Config, opts apiManagerOpts) (manager.Manager,
LeaderElectionID: opts.electionID,
}

if opts.ingressWatchNamespace != "" {
options.Cache = cache.Options{
DefaultNamespaces: map[string]cache.Config{
opts.ingressWatchNamespace: {},
// The KubernetesOperator CR is a singleton owned by the operator and always
// lives in the release namespace, regardless of `watchNamespace`. Pin its
// cache scope to the release namespace so the controller can always list/watch
// it, and so RBAC for it can stay narrowly scoped to the release namespace.
options.Cache = cache.Options{
ByObject: map[client.Object]cache.ByObject{
&ngrokv1alpha1.KubernetesOperator{}: {
Namespaces: map[string]cache.Config{
opts.namespace: {},
},
},
},
}
if opts.ingressWatchNamespace != "" {
options.Cache.DefaultNamespaces = map[string]cache.Config{
opts.ingressWatchNamespace: {},
}
}

Expand Down
3 changes: 2 additions & 1 deletion helm/ngrok-operator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ To run multiple ngrok-operator instances in the same cluster (e.g., in different
| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
| `serviceAccount.name` | The name of the ServiceAccount to use. | `""` |
| `serviceAccount.annotations` | Additional annotations to add to the ServiceAccount | `{}` |
| `clusterRole.annotations` | Additional annotations to add to all ClusterRoles. | `{}` |
| `crdAccessRoles.create` | Whether to create editor/viewer ClusterRoles for CRDs | `true` |
| `crdAccessRoles.annotations` | Annotations for CRD access ClusterRoles (e.g., RBAC aggregation) | `{}` |
| `defaultDomainReclaimPolicy` | The default domain reclaim policy to use for domains created by the operator. Valid values are "Delete" and "Retain". The default is "Delete". | `Delete` |

### Logging configuration
Expand Down
68 changes: 68 additions & 0 deletions helm/ngrok-operator/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,71 @@ Return the ngrok operator image name
{{- $tag := .Values.image.tag | default .Chart.AppVersion | toString -}}
{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
{{- end -}}

{{/*
Whether RBAC should use namespace-scoped Roles instead of ClusterRoles.
True when watchNamespace is set (either via deprecated top-level or ingress.watchNamespace).
*/}}
{{- define "ngrok-operator.isNamespaced" -}}
{{- if (.Values.watchNamespace | default .Values.ingress.watchNamespace) -}}
true
{{- end -}}
{{- end -}}

{{/*
The namespace to watch. Returns the watchNamespace value (deprecated top-level takes precedence).
*/}}
{{- define "ngrok-operator.watchNamespace" -}}
{{- .Values.watchNamespace | default .Values.ingress.watchNamespace -}}
{{- end -}}

{{/*
api-manager rules for cluster-scoped Kubernetes resources.
These resources have no namespace and always require a ClusterRole, regardless of watchNamespace.
*/}}
{{- define "ngrok-operator.api-manager.clusterScopedRules" -}}
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- update
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
- gatewayclasses
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
- gatewayclasses/status
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
- gatewayclasses/finalizers
verbs:
- patch
- update
{{- end -}}
2 changes: 1 addition & 1 deletion helm/ngrok-operator/templates/agent/deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{- if and .Values.ingress.enabled (not .Values.oneClickDemoMode) }}
{{- $component := "agent" }}
{{- $rbacChecksum := include (print $.Template.BasePath "/agent/rbac.yaml") . | sha256sum }}
{{- $rbacChecksum := include (print $.Template.BasePath "/agent/role.yaml") . | sha256sum }}
{{- $agent := .Values.agent }}
---
apiVersion: apps/v1
Expand Down
117 changes: 0 additions & 117 deletions helm/ngrok-operator/templates/agent/rbac.yaml

This file was deleted.

36 changes: 36 additions & 0 deletions helm/ngrok-operator/templates/agent/release-namespace-role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{{- if .Values.ingress.enabled }}
# Operator-state RBAC for the agent: the agent reads the singleton
# KubernetesOperator CR for drain state. The CR always lives in the release
# namespace regardless of `watchNamespace`, and the agent's controller-runtime
# cache pins this resource to the release namespace (see cmd/agent-manager.go),
# so the corresponding RBAC also lives here rather than in the watchNamespace
# Role.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "ngrok-operator.fullname" . }}-agent-operator-state-role
namespace: {{ .Release.Namespace }}
rules:
- apiGroups:
- ngrok.k8s.ngrok.com
resources:
- kubernetesoperators
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "ngrok-operator.fullname" . }}-agent-operator-state-rolebinding
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "ngrok-operator.fullname" . }}-agent-operator-state-role
subjects:
- kind: ServiceAccount
name: {{ template "ngrok-operator.agent.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
79 changes: 79 additions & 0 deletions helm/ngrok-operator/templates/agent/role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{{- if .Values.ingress.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if (include "ngrok-operator.isNamespaced" .) }}
kind: Role
metadata:
name: {{ include "ngrok-operator.fullname" . }}-agent-role
namespace: {{ include "ngrok-operator.watchNamespace" . }}
{{- else }}
kind: ClusterRole
metadata:
name: {{ include "ngrok-operator.fullname" . }}-agent-role
{{- end }}
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- ingress.k8s.ngrok.com
resources:
- domains
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ngrok.k8s.ngrok.com
resources:
- agentendpoints
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- ngrok.k8s.ngrok.com
resources:
- agentendpoints/finalizers
verbs:
- patch
- update
- apiGroups:
- ngrok.k8s.ngrok.com
resources:
- agentendpoints/status
verbs:
- get
- patch
- update
# KubernetesOperator is intentionally NOT in this role. The KubernetesOperator
# CR is a singleton owned by the api-manager and always lives in the release
# namespace, independent of `watchNamespace`. The agent reads it for drain
# state via a release-namespace-pinned cache scope; its rules live in
# release-namespace-role.yaml.
- apiGroups:
- ngrok.k8s.ngrok.com
resources:
- ngroktrafficpolicies
verbs:
- get
- list
- watch
{{- end }}
Loading
Loading