diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5b3cc53e..58107873 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -99,26 +99,3 @@ jobs: go-version-file: operator/go.mod - name: Build run: make build - - # token-broker is a separate Go module. The workflow-level - # defaults.run.working-directory pins every other job to `operator`, so - # nothing here compiled this module — it shipped in v0.4.0-rc.1 with an - # unresolvable authlib requirement (rossoctl/operator#537). This job builds - # and tests it so that cannot recur silently. - test-token-broker: - name: Token Broker Build & Tests - runs-on: ubuntu-latest - defaults: - run: - working-directory: token-broker - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version-file: token-broker/go.mod - - name: Build - run: go build ./... - - name: Vet - run: go vet ./... - - name: Run tests - run: go test ./... diff --git a/README.md b/README.md index 8ef33a17..934ee812 100644 --- a/README.md +++ b/README.md @@ -99,17 +99,71 @@ The operator runs the following controllers and webhooks: Rossoctl includes a dedicated bundle service used by AuthBridge clients to fetch authorization bundles. -This service is deployed using the manifests in `operator/config/bundleservice/` and is intended for SRE operational use. +The service ships inside the operator image and is **opt-in**. Enable it at install time: + +```sh +helm install rossoctl-operator ... --set bundleService.enabled=true +``` Key facts: - Deployment name: `bundle-service` -- Namespace: `system` +- Namespace: the release namespace - Service type: `ClusterIP` - Port: `8080` - Health endpoints: `/healthz`, `/readyz` -Use `operator/operator/cmd/bundle-service/README.md` for SRE runbook guidance and operational details. +Enabling it also installs a NetworkPolicy restricting callers to pods labelled +`rossoctl.dev/authbridge: "true"`. This is the service's **only** access control — it +performs no in-process authorization and serves any bundle named in the `?spiffe=` query +param — so it is installed with the component rather than behind `networkPolicy.enable`. +Note that it only takes effect on a cluster whose CNI enforces NetworkPolicy; kind's +default CNI does not. + +Use `operator/cmd/bundle-service/README.md` for SRE runbook guidance and operational details. + +## Token Broker + +The Token Broker enables HITL (Human-in-the-Loop) authorization: when an agent needs +permissions beyond those in its own token, the broker runs an OAuth 2.0 PKCE flow to +obtain just-in-time, user-scoped credentials. + +It also ships inside the operator image and is **opt-in**: + +```sh +helm install rossoctl-operator ... \ + --set tokenBroker.enabled=true +``` + +Key facts: + +- Deployment name: `token-broker` +- Namespace: the release namespace +- Service type: `ClusterIP` +- Port: `8190` +- Health endpoints: `/healthz`, `/readyz` +- Replicas: fixed at 1 — sessions and the token cache are in-memory, so scaling out + requires shared state first + +OAuth client credentials are **not** templated by the chart. Create the Secret out of +band and point `tokenBroker.oauth.existingSecret` at it: + +```sh +kubectl create secret generic github-oauth-credentials -n rossoctl-system \ + --from-literal=client-id= --from-literal=client-secret= +``` + +The chart also installs an HTTPRoute for the OAuth callback. Its hostname defaults to +`token-broker.localtest.me`, which works out of the box on a kind/dev cluster; the host +in `tokenBroker.oauth.callbackUrl` **must** match it, or the provider's post-consent +redirect 404s and the broker waits for a callback that never arrives. Override both for +real deployments, or set `tokenBroker.httpRoute.enabled=false` and route the callback +yourself. + +For production, set `tokenBroker.jwt.*` — incoming JWTs are not verified when those are +left unset. + +See `operator/cmd/token-broker/README.md` for the API reference and operational details. ## Quick Start diff --git a/operator/config/bundleservice/default-policy.yaml b/charts/operator/templates/bundleservice/default-policy.yaml similarity index 81% rename from operator/config/bundleservice/default-policy.yaml rename to charts/operator/templates/bundleservice/default-policy.yaml index 0d7ae8f8..6d8fd054 100644 --- a/operator/config/bundleservice/default-policy.yaml +++ b/charts/operator/templates/bundleservice/default-policy.yaml @@ -1,8 +1,16 @@ +{{- if .Values.bundleService.enabled }} +# Mandatory bootstrap data, not an example. These four Rego entry points are the +# packages AuthBridge's OPA evaluates; each is `default allow := false`, and +# namespace/client-scope CRs are modifiers layered on top. Without this CR the +# packages do not exist and every decision fails closed. +# +# Must live in the service's own namespace: watcher.go ignores global-scope CRs +# from any other namespace, logging only a warning. apiVersion: agent.rossoctl.dev/v1alpha1 kind: AuthorizationPolicy metadata: name: default - namespace: rossoctl-system + namespace: {{ .Release.Namespace }} spec: scope: global policies: @@ -62,3 +70,4 @@ spec: ns_ok if not data.authbridge.ns.outbound.response client_ok if data.authbridge.client.outbound.response.allow client_ok if not data.authbridge.client.outbound.response +{{- end -}} diff --git a/charts/operator/templates/bundleservice/deployment.yaml b/charts/operator/templates/bundleservice/deployment.yaml new file mode 100644 index 00000000..0fd14d43 --- /dev/null +++ b/charts/operator/templates/bundleservice/deployment.yaml @@ -0,0 +1,57 @@ +{{- if .Values.bundleService.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: bundle-service + name: bundle-service + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.bundleService.replicas }} + selector: + matchLabels: + app: bundle-service + {{- include "chart.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + app: bundle-service + {{- include "chart.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: bundle-service + spec: + containers: + - name: bundle-service + # Same image as the manager unless explicitly overridden; ENTRYPOINT is + # /manager, so `command` is what selects this binary. + image: {{ .Values.bundleService.container.image.repository | default .Values.controllerManager.container.image.repository }}:{{ .Values.bundleService.container.image.tag | default .Values.controllerManager.container.image.tag }} + imagePullPolicy: {{ .Values.bundleService.container.image.pullPolicy | default .Values.controllerManager.container.image.pullPolicy | default "IfNotPresent" }} + command: + - {{ .Values.bundleService.container.cmd }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + # Scopes which global-scope AuthorizationPolicy CRs are honoured: + # the watcher ignores global CRs outside the service's own namespace. + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- range $key, $value := .Values.bundleService.container.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + livenessProbe: + {{- toYaml .Values.bundleService.container.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.bundleService.container.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.bundleService.container.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.bundleService.container.securityContext | nindent 12 }} + securityContext: + {{- toYaml .Values.bundleService.securityContext | nindent 8 }} + serviceAccountName: {{ .Values.bundleService.serviceAccountName }} +{{- end -}} diff --git a/operator/config/bundleservice/networkpolicy.yaml b/charts/operator/templates/bundleservice/networkpolicy.yaml similarity index 58% rename from operator/config/bundleservice/networkpolicy.yaml rename to charts/operator/templates/bundleservice/networkpolicy.yaml index 72cb7390..f762e942 100644 --- a/operator/config/bundleservice/networkpolicy.yaml +++ b/charts/operator/templates/bundleservice/networkpolicy.yaml @@ -1,8 +1,17 @@ +{{- if .Values.bundleService.enabled }} +# This NetworkPolicy is the ONLY access control on bundle-service: the server +# performs no in-process authorization and will serve any bundle named in the +# ?spiffe= query param. It is therefore installed with the component rather +# than behind `networkPolicy.enable`. Requires a CNI that enforces +# NetworkPolicy — kind's default CNI does not. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: bundle-service name: bundle-service - namespace: system + namespace: {{ .Release.Namespace }} spec: podSelector: matchLabels: @@ -34,7 +43,8 @@ spec: port: 53 - protocol: TCP port: 53 - # Allow access to Kubernetes API server + # Allow access to the Kubernetes API server. The API server IP is + # cluster-specific, so this is deliberately broad. - to: - ipBlock: cidr: 0.0.0.0/0 @@ -43,3 +53,4 @@ spec: port: 443 - protocol: TCP port: 6443 +{{- end -}} diff --git a/charts/operator/templates/bundleservice/rbac.yaml b/charts/operator/templates/bundleservice/rbac.yaml new file mode 100644 index 00000000..6b85851c --- /dev/null +++ b/charts/operator/templates/bundleservice/rbac.yaml @@ -0,0 +1,36 @@ +{{- if and .Values.rbac.enable .Values.bundleService.enabled }} +# The service watches AuthorizationPolicy CRs cluster-wide to compose bundles. +# list+watch only: it serves from an informer cache and never writes status. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: bundle-service + name: rossoctl-bundle-service +rules: +- apiGroups: + - agent.rossoctl.dev + resources: + - authorizationpolicies + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: bundle-service + name: rossoctl-bundle-service +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: rossoctl-bundle-service +subjects: +- kind: ServiceAccount + name: {{ .Values.bundleService.serviceAccountName }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/operator/config/bundleservice/service.yaml b/charts/operator/templates/bundleservice/service.yaml similarity index 51% rename from operator/config/bundleservice/service.yaml rename to charts/operator/templates/bundleservice/service.yaml index 7df0b8c8..2fa6f918 100644 --- a/operator/config/bundleservice/service.yaml +++ b/charts/operator/templates/bundleservice/service.yaml @@ -1,8 +1,12 @@ +{{- if .Values.bundleService.enabled }} apiVersion: v1 kind: Service metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: bundle-service name: bundle-service - namespace: system + namespace: {{ .Release.Namespace }} spec: type: ClusterIP selector: @@ -12,3 +16,4 @@ spec: port: 8080 targetPort: http protocol: TCP +{{- end -}} diff --git a/charts/operator/templates/bundleservice/serviceaccount.yaml b/charts/operator/templates/bundleservice/serviceaccount.yaml new file mode 100644 index 00000000..6cc03a42 --- /dev/null +++ b/charts/operator/templates/bundleservice/serviceaccount.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.rbac.enable .Values.bundleService.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: bundle-service + name: {{ .Values.bundleService.serviceAccountName }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/operator/templates/tokenbroker/deployment.yaml b/charts/operator/templates/tokenbroker/deployment.yaml new file mode 100644 index 00000000..d21f6a01 --- /dev/null +++ b/charts/operator/templates/tokenbroker/deployment.yaml @@ -0,0 +1,112 @@ +{{- if .Values.tokenBroker.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: token-broker + name: token-broker + namespace: {{ .Release.Namespace }} +spec: + # Sessions and the token cache are held in memory, so this must stay at 1. + # Scaling out requires shared session state first. Enforced rather than merely + # documented: splitting sessions across pods fails at runtime, in the OAuth + # flow, well away from whoever set the replica count. + {{- if .Values.tokenBroker.replicas }} + {{- fail "tokenBroker.replicas is not supported: sessions and the token cache are in-memory, so the broker must run a single replica. Scaling out requires shared session state first." }} + {{- end }} + replicas: 1 + selector: + matchLabels: + app: token-broker + {{- include "chart.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + app: token-broker + {{- include "chart.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: token-broker + annotations: + # The broker terminates its own OAuth flows; no mesh sidecar required. + sidecar.istio.io/inject: "false" + spec: + containers: + - name: token-broker + # Same image as the manager unless explicitly overridden; ENTRYPOINT is + # /manager, so `command` is what selects this binary. + image: {{ .Values.tokenBroker.container.image.repository | default .Values.controllerManager.container.image.repository }}:{{ .Values.tokenBroker.container.image.tag | default .Values.controllerManager.container.image.tag }} + imagePullPolicy: {{ .Values.tokenBroker.container.image.pullPolicy | default .Values.controllerManager.container.image.pullPolicy | default "IfNotPresent" }} + command: + - {{ .Values.tokenBroker.container.cmd }} + ports: + - name: http + containerPort: 8190 + protocol: TCP + env: + - name: TOKEN_BROKER_PORT + value: "8190" + - name: OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.tokenBroker.oauth.existingSecret }} + key: {{ .Values.tokenBroker.oauth.clientIdKey }} + - name: OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.tokenBroker.oauth.existingSecret }} + key: {{ .Values.tokenBroker.oauth.clientSecretKey }} + # Host here MUST match httpRoute.hostname. + - name: OAUTH_CALLBACK_URL + value: {{ .Values.tokenBroker.oauth.callbackUrl | quote }} + - name: ALLOWED_REDIRECT_HOSTS + value: {{ .Values.tokenBroker.oauth.allowedRedirectHosts | quote }} + - name: RESOURCE_CONFIG + value: {{ .Values.tokenBroker.resourceConfig | quote }} + {{- with .Values.tokenBroker.oauth.authorizationEndpoint }} + - name: OAUTH_AUTHORIZATION_ENDPOINT + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.oauth.tokenEndpoint }} + - name: OAUTH_TOKEN_ENDPOINT + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.oauth.scopesSupported }} + - name: OAUTH_SCOPES_SUPPORTED + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.jwt.jwksUrl }} + - name: JWT_JWKS_URL + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.jwt.issuer }} + - name: JWT_ISSUER + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.jwt.audience }} + - name: JWT_AUDIENCE + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.sessionTimeout }} + - name: TOKEN_BROKER_SESSION_TIMEOUT + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.maxSessionsPerUser }} + - name: TOKEN_BROKER_MAX_SESSIONS_PER_USER + value: {{ . | quote }} + {{- end }} + {{- with .Values.tokenBroker.tokenWaitTimeout }} + - name: TOKEN_BROKER_TOKEN_WAIT_TIMEOUT + value: {{ . | quote }} + {{- end }} + livenessProbe: + {{- toYaml .Values.tokenBroker.container.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.tokenBroker.container.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.tokenBroker.container.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.tokenBroker.container.securityContext | nindent 12 }} + securityContext: + {{- toYaml .Values.tokenBroker.securityContext | nindent 8 }} + serviceAccountName: {{ .Values.tokenBroker.serviceAccountName }} +{{- end -}} diff --git a/charts/operator/templates/tokenbroker/httproute.yaml b/charts/operator/templates/tokenbroker/httproute.yaml new file mode 100644 index 00000000..d4cdf30f --- /dev/null +++ b/charts/operator/templates/tokenbroker/httproute.yaml @@ -0,0 +1,33 @@ +{{- if and .Values.tokenBroker.enabled .Values.tokenBroker.httpRoute.enabled }} +# Exposes the OAuth callback through the shared gateway. The provider redirects +# the user's browser to oauth.callbackUrl after authorization; that URL must +# resolve to GET /oauth/callback here, or the redirect 404s and the broker waits +# for a callback that never arrives. +# +# hostname MUST match the host in tokenBroker.oauth.callbackUrl. Both default to +# kind/dev values — override for production. +# +# Same-namespace backendRef, so no ReferenceGrant is required. +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: token-broker + name: token-broker-oauth-callback + namespace: {{ .Release.Namespace }} +spec: + parentRefs: + - name: {{ .Values.tokenBroker.httpRoute.gateway.name }} + namespace: {{ .Values.tokenBroker.httpRoute.gateway.namespace | default .Release.Namespace }} + hostnames: + - {{ .Values.tokenBroker.httpRoute.hostname | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: /oauth/callback + backendRefs: + - name: token-broker + port: 8190 +{{- end -}} diff --git a/charts/operator/templates/tokenbroker/service.yaml b/charts/operator/templates/tokenbroker/service.yaml new file mode 100644 index 00000000..badbd2cd --- /dev/null +++ b/charts/operator/templates/tokenbroker/service.yaml @@ -0,0 +1,21 @@ +{{- if .Values.tokenBroker.enabled }} +# Consumed in-cluster at +# http://token-broker..svc.cluster.local:8190 +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: token-broker + name: token-broker + namespace: {{ .Release.Namespace }} +spec: + type: ClusterIP + selector: + app: token-broker + ports: + - name: http + port: 8190 + targetPort: http + protocol: TCP +{{- end -}} diff --git a/charts/operator/templates/tokenbroker/serviceaccount.yaml b/charts/operator/templates/tokenbroker/serviceaccount.yaml new file mode 100644 index 00000000..82b55f6b --- /dev/null +++ b/charts/operator/templates/tokenbroker/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.rbac.enable .Values.tokenBroker.enabled }} +# The broker is stateless and calls no Kubernetes API, so it needs no +# Role/RoleBinding — just an identity to run as. +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: token-broker + name: {{ .Values.tokenBroker.serviceAccountName }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/operator/values.yaml b/charts/operator/values.yaml index 49e68909..2cb3f6a3 100644 --- a/charts/operator/values.yaml +++ b/charts/operator/values.yaml @@ -46,6 +46,155 @@ controllerManager: terminationGracePeriodSeconds: 10 serviceAccountName: controller-manager +# [BUNDLE SERVICE]: OPA authorization policy bundle server (opt-in) +# +# Serves composed OPA policy bundles assembled from AuthorizationPolicy CRs to +# the cortex-side `opa` AuthBridge plugin. Ships in the operator image and is +# selected by `cmd` below, so it always matches the manager's version. +# +# SECURITY: the service performs no in-process authorization (it serves any +# bundle requested via ?spiffe=) and listens on plain HTTP. The NetworkPolicy +# templated alongside it is the ONLY access control, restricting callers to +# pods labelled `rossoctl.dev/authbridge: "true"`. It is therefore installed +# whenever this component is enabled, independently of `networkPolicy.enable`. +# It only takes effect on a cluster whose CNI enforces NetworkPolicy — kind's +# default CNI does NOT, so treat a kind cluster as having no access control. +bundleService: + enabled: false + replicas: 1 + container: + # Runs out of the operator image, so the image is inherited from + # controllerManager.container.image and there is no second tag to pin + # (release.yml rewrites only the manager's tag). Override repository/tag + # here only to run a separately built bundle-service image. + image: {} + cmd: /bundle-service + resources: + # No CPU limit: bundle builds are bursty and CPU-throttling them adds + # latency to every AuthBridge policy fetch. + limits: + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + httpGet: + path: /healthz + port: http + readinessProbe: + initialDelaySeconds: 2 + periodSeconds: 5 + httpGet: + path: /readyz + port: http + securityContext: + allowPrivilegeEscalation: false + # The binary writes nothing to disk; bundles are assembled in memory. + readOnlyRootFilesystem: true + capabilities: + drop: + - "ALL" + securityContext: + runAsNonRoot: true + serviceAccountName: bundle-service + +# [TOKEN BROKER]: OAuth session and token broker (opt-in) +# +# Brokers OAuth authorization-code flows on behalf of agents and caches the +# resulting tokens for AuthBridge's tokenbroker plugin. Ships in the operator +# image and is selected by `cmd` below, so it always matches the manager's +# version. +# +# Calls no Kubernetes API, so it needs no Role/RoleBinding — only an identity. +tokenBroker: + enabled: false + container: + # Runs out of the operator image; see the note on bundleService.container.image. + image: {} + cmd: /token-broker + resources: + limits: + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + httpGet: + path: /healthz + port: http + readinessProbe: + initialDelaySeconds: 2 + periodSeconds: 5 + httpGet: + path: /readyz + port: http + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - "ALL" + securityContext: + runAsNonRoot: true + serviceAccountName: token-broker + + # OAuth client credentials. The chart does NOT create this Secret — create it + # out of band so credentials never pass through values or Helm release state: + # kubectl create secret generic github-oauth-credentials -n \ + # --from-literal=client-id= --from-literal=client-secret= + # (or wire it to ExternalSecrets / Vault). + oauth: + existingSecret: github-oauth-credentials + clientIdKey: client-id + clientSecretKey: client-secret + # Externally-reachable callback URL. Its host MUST match httpRoute.hostname + # below, or the provider's redirect 404s and the broker waits for a callback + # that never arrives. The default is a kind/dev value — override for real + # deployments. + callbackUrl: "http://token-broker.localtest.me:8080/oauth/callback" + # Permitted hostnames for backend_session_redirect_url. Comma-separated. + allowedRedirectHosts: "app-demo.localtest.me" + # Skip OAuth discovery by configuring endpoints directly (optional). + authorizationEndpoint: "" + tokenEndpoint: "" + scopesSupported: "" + + # JWT validation of incoming requests. Leave unset for dev/test; REQUIRED for + # production, otherwise incoming JWTs are not verified. + jwt: + jwksUrl: "" + issuer: "" + audience: "" + + # Per-resource-server OAuth config (scopes + endpoint overrides), JSON. + # Each key is the resource server URL and must match X-Server-Url exactly. + # The default points at the demo MCP server; override for real use. + resourceConfig: | + { + "http://mcp-server-service.rossoctl-demo.svc.cluster.local:8184": {"scopes": ["read:user", "user:email"]} + } + + # Optional tunables; defaults live in cmd/token-broker/main.go. + sessionTimeout: "" + maxSessionsPerUser: "" + tokenWaitTimeout: "" + + # HTTPRoute exposing the OAuth callback through the shared gateway. Defaults + # are kind/dev values that work out of the box; hostname MUST match the host + # in oauth.callbackUrl above. Override both for production, or set + # enabled: false and route the callback yourself. + httpRoute: + enabled: true + hostname: token-broker.localtest.me + gateway: + name: http + # Defaults to the release namespace when empty. + namespace: "" + # [RBAC]: To enable RBAC (Permissions) configurations rbac: enable: true diff --git a/operator/Dockerfile b/operator/Dockerfile index 90708c0c..5c98546e 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -1,4 +1,8 @@ -# Build the manager binary. +# Build the manager, bundle-service and token-broker binaries. +# +# Both ship in this single image, selected per-Deployment via an explicit +# `command:` (see charts/operator/templates/). ENTRYPOINT stays /manager so +# existing consumers that run the image with no command are unaffected. # # `--platform=$BUILDPLATFORM` pins the builder stage to the host arch so # Go cross-compiles to $TARGETARCH instead of running under QEMU. With @@ -18,8 +22,11 @@ RUN go mod download # Copy the go source COPY cmd/main.go cmd/main.go +COPY cmd/bundle-service/ cmd/bundle-service/ +COPY cmd/token-broker/ cmd/token-broker/ COPY api/ api/ COPY internal/ internal/ +COPY pkg/ pkg/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command @@ -28,11 +35,20 @@ COPY internal/ internal/ # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o manager cmd/main.go -# Use distroless as minimal base image to package the manager binary +# bundle-service serves OPA authorization policy bundles. Same module, so it +# reuses the layers above; only this stage's output differs. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o bundle-service ./cmd/bundle-service/ + +# token-broker brokers OAuth flows and caches tokens for AuthBridge. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o token-broker ./cmd/token-broker/ + +# Use distroless as minimal base image to package the binaries # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager . +COPY --from=builder /workspace/bundle-service . +COPY --from=builder /workspace/token-broker . USER 65532:65532 ENTRYPOINT ["/manager"] diff --git a/operator/Makefile b/operator/Makefile index 07f62c80..91dd3770 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -142,8 +142,13 @@ lint-config: golangci-lint ## Verify golangci-lint linter configuration ##@ Build .PHONY: build -build: manifests generate fmt vet ## Build manager binary. +build: manifests generate fmt vet ## Build manager, bundle-service and token-broker binaries. + # All three ship in the operator image (see Dockerfile), so all three are + # built here: a break confined to one of the secondary main packages would + # otherwise pass CI and only surface during the image build. go build -o bin/manager cmd/main.go + go build -o bin/bundle-service ./cmd/bundle-service/ + go build -o bin/token-broker ./cmd/token-broker/ # Self-signed TLS for the validating webhook when running outside the cluster (controller-runtime # otherwise expects tls.crt/tls.key under $TMPDIR/k8s-webhook-server/serving-certs). diff --git a/operator/cmd/bundle-service/ARCHITECTURE.md b/operator/cmd/bundle-service/ARCHITECTURE.md index 3459bc8a..53296f8a 100644 --- a/operator/cmd/bundle-service/ARCHITECTURE.md +++ b/operator/cmd/bundle-service/ARCHITECTURE.md @@ -29,7 +29,7 @@ The `Verifier` interface in `internal/bundleservice/identity/` is the extension The service collects policies from `AuthorizationPolicy` CRs and packages them into the bundle payload. -- **Global policies** — `scope: global`, must reside in the service namespace (`rossoctl-system`). These declare the OPA query entry-point packages (e.g., `package authbridge.inbound.request`) and contain the decision logic that combines namespace and client tiers. +- **Global policies** — `scope: global`, must reside in the service namespace (the Helm release namespace, `rossoctl-system` by default). These declare the OPA query entry-point packages (e.g., `package authbridge.inbound.request`) and contain the decision logic that combines namespace and client tiers. - **Namespace policies** — `scope: namespace`, scoped to the namespace from the client's SPIFFE ID. - **Client policies** — `scope: client`, scoped to the CR whose name and namespace match the SPIFFE ID. @@ -46,7 +46,7 @@ The global CR's Rego declares these packages directly. Namespace and client poli ### Default decision logic -The default global CR (`config/bundleservice/default-policy.yaml`) implements: +The default global CR (`charts/operator/templates/bundleservice/default-policy.yaml`) implements: ``` allow if ns.override @@ -134,14 +134,14 @@ Response headers for `200 OK`: Example request: ```bash -curl -v 'http://bundle-service.rossoctl-system.svc.cluster.local:8080/bundles?spiffe=localtest.me/ns/default/sa/my-agent' +curl -v 'http://bundle-service..svc.cluster.local:8080/bundles?spiffe=localtest.me/ns/default/sa/my-agent' ``` ETag example: ```bash curl -v -H 'If-None-Match: "sha256:abc123..."' \ - 'http://bundle-service.rossoctl-system.svc.cluster.local:8080/bundles?spiffe=localtest.me/ns/default/sa/my-agent' + 'http://bundle-service..svc.cluster.local:8080/bundles?spiffe=localtest.me/ns/default/sa/my-agent' ``` ### GET /healthz diff --git a/operator/cmd/bundle-service/Dockerfile b/operator/cmd/bundle-service/Dockerfile deleted file mode 100644 index 46af9c2b..00000000 --- a/operator/cmd/bundle-service/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# `--platform=$BUILDPLATFORM` pins the builder stage to the host arch so -# Go cross-compiles to $TARGETARCH instead of running under QEMU. With -# CGO disabled (below), the resulting binary is bit-for-bit identical to -# a native build but ~9x faster for the arm64 variant. -FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS builder -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /workspace -COPY go.mod go.mod -COPY go.sum go.sum -RUN go mod download - -COPY cmd/bundle-service/ cmd/bundle-service/ -COPY api/ api/ -COPY internal/bundleservice/ internal/bundleservice/ - -RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o bundle-service ./cmd/bundle-service/ - -FROM gcr.io/distroless/static:nonroot -WORKDIR / -COPY --from=builder /workspace/bundle-service . -USER 65532:65532 - -ENTRYPOINT ["/bundle-service"] diff --git a/operator/cmd/bundle-service/README.md b/operator/cmd/bundle-service/README.md index 7b441847..f3bb2639 100644 --- a/operator/cmd/bundle-service/README.md +++ b/operator/cmd/bundle-service/README.md @@ -11,26 +11,43 @@ The bundle service serves OPA authorization bundles to AuthBridge clients over H ### Quick start (kind) ```bash -./hack/bundle-service-kind.sh [cluster-name] [namespace] +./hack/kind-reload-all.sh [cluster-name] [namespace] # Defaults: cluster=rossoctl, namespace=rossoctl-system ``` -This script builds the image, loads it into kind, installs the CRD, applies the default global policy CR, and deploys the service. +Builds the operator image (which contains this binary), loads it into kind, and deploys +all three services. It exercises the same image and the same `command: [/bundle-service]` +selector that a real install uses, so there is no separate single-binary build to maintain. -### Production manifests +The controller-manager must already be installed (`make deploy`) — the script updates +existing deployments rather than creating the operator from scratch. -Deploy using the manifests in `operator/config/bundleservice/`: +### Install (Helm) -- `deployment.yaml` -- `service.yaml` -- `serviceaccount.yaml` -- `rbac.yaml` -- `networkpolicy.yaml` -- `default-policy.yaml` — the default global `AuthorizationPolicy` CR +The binary ships inside the operator image and is installed by the operator chart, +gated off by default: + +```bash +helm upgrade --install rossoctl-operator charts/operator \ + --namespace rossoctl-system \ + --set bundleService.enabled=true +``` + +Templates live in `charts/operator/templates/bundleservice/` — ServiceAccount, +ClusterRole/Binding, Deployment, Service, NetworkPolicy, and the default global +`AuthorizationPolicy` CR. Values are under `bundleService` in +`charts/operator/values.yaml`. + +Enabling the component also installs the NetworkPolicy, independently of +`networkPolicy.enable`: the service performs no in-process authorization and will +serve any bundle named in `?spiffe=`, so that policy's ingress restriction to pods +labelled `rossoctl.dev/authbridge: "true"` is the only access control there is. It +requires a CNI that enforces NetworkPolicy — kind's default CNI does **not**, so +treat a kind cluster as having none. Default deployment settings: -- Namespace: `rossoctl-system` +- Namespace: the release namespace - Deployment name: `bundle-service` - Service name: `bundle-service` - Port: `8080` @@ -46,7 +63,11 @@ kubectl apply -f config/crd/bases/agent.rossoctl.dev_authorizationpolicies.yaml The default global policy CR must be applied for the service to produce valid bundles: ```bash -kubectl apply -f config/bundleservice/default-policy.yaml +helm template rossoctl-operator charts/operator \ + --namespace rossoctl-system \ + --set bundleService.enabled=true \ + --show-only templates/bundleservice/default-policy.yaml \ + | kubectl apply -f - ``` ### Runtime configuration @@ -106,7 +127,7 @@ Structured logs via `log/slog`. Key log events: ## Global policy CR -The default global `AuthorizationPolicy` CR (`config/bundleservice/default-policy.yaml`) defines the decision logic for all four OPA query paths. It determines how namespace and client tiers are combined. +The default global `AuthorizationPolicy` CR (`charts/operator/templates/bundleservice/default-policy.yaml`) defines the decision logic for all four OPA query paths. It determines how namespace and client tiers are combined. Platform engineers can customize this CR to: @@ -129,7 +150,7 @@ Monitor: ## Related resources -- `operator/config/bundleservice/` — deployment manifests and default policy +- `charts/operator/templates/bundleservice/` — chart templates and the default policy - `operator/config/crd/bases/agent.rossoctl.dev_authorizationpolicies.yaml` — CRD definition - `operator/internal/bundleservice/` — service implementation diff --git a/token-broker/docs/API_SPECIFICATION.md b/operator/cmd/token-broker/API_SPECIFICATION.md similarity index 93% rename from token-broker/docs/API_SPECIFICATION.md rename to operator/cmd/token-broker/API_SPECIFICATION.md index d68bd91b..847f12f6 100644 --- a/token-broker/docs/API_SPECIFICATION.md +++ b/operator/cmd/token-broker/API_SPECIFICATION.md @@ -53,11 +53,11 @@ The Token Broker Service is a centralized OAuth session and token management ser ## Token Broker Service API -**Base URL**: `http://token-broker-service:8190` +**Base URL**: `http://token-broker..svc.cluster.local:8190` (Service name: `token-broker`) **Service Port**: 8190 (ClusterIP) -**Implementation**: [`internal/api/handlers.go`](../../internal/api/handlers.go) +**Implementation**: [`internal/tokenbroker/api/handlers.go`](../../internal/tokenbroker/api/handlers.go) --- @@ -164,7 +164,7 @@ X-Server-Url: **Timeout**: 300 seconds (5 minutes) - allows time for user to complete OAuth flow -**Implementation**: [`HandleGetToken()`](../../internal/api/handlers.go) +**Implementation**: [`HandleGetToken()`](../../internal/tokenbroker/api/handlers.go) --- @@ -247,7 +247,7 @@ Empty response body (session key is already known from JWT `jti` claim) } ``` -**Implementation**: [`HandleCreateSession()`](../../internal/api/handlers.go) +**Implementation**: [`HandleCreateSession()`](../../internal/tokenbroker/api/handlers.go) --- @@ -310,7 +310,7 @@ Location: } ``` -**Implementation**: [`HandleOAuthCallback()`](../../internal/api/handlers.go) +**Implementation**: [`HandleOAuthCallback()`](../../internal/tokenbroker/api/handlers.go) --- @@ -391,7 +391,7 @@ Authorization: Bearer } ``` -**Implementation**: [`HandleEvents()`](../../internal/api/handlers.go) +**Implementation**: [`HandleEvents()`](../../internal/tokenbroker/api/handlers.go) --- @@ -455,7 +455,7 @@ Empty response body ``` - `500 Internal Server Error`: Failed to end session -**Implementation**: [`HandleEndSession()`](../../internal/api/handlers.go) +**Implementation**: [`HandleEndSession()`](../../internal/tokenbroker/api/handlers.go) --- @@ -521,7 +521,7 @@ The Token Broker Service is configured via environment variables or configuratio | `TOKEN_BROKER_PORT` | 8190 | HTTP server port | | `OAUTH_CLIENT_ID` | **(required)** | OAuth client ID | | `OAUTH_CLIENT_SECRET` | **(required)** | OAuth client secret | -| `OAUTH_CALLBACK_URL` | **(required)** | Token Broker's public OAuth callback URL | +| `OAUTH_CALLBACK_URL` | `http://localhost:8190/oauth/callback` | Token Broker's public OAuth callback URL. The default is dev-only; the chart always sets this from `tokenBroker.oauth.callbackUrl`. | | `ALLOWED_REDIRECT_HOSTS` | *(none — all permitted, WARNING logged)* | Comma-separated permitted hostnames for `backend_session_redirect_url` | | `JWT_JWKS_URL` | *(none — validation disabled, WARNING logged)* | JWKS endpoint for JWT signature validation | | `JWT_ISSUER` | *(none)* | Expected JWT issuer | @@ -545,7 +545,7 @@ Tokens are cached per `(session_key, resource_url)` pair: - Expired tokens are automatically removed from cache - **Session isolation**: Only agents belonging to the same user session may use a cached token -**Implementation**: [`internal/cache/token_cache.go`](../../internal/cache/token_cache.go) +**Implementation**: [`internal/tokenbroker/cache/token_cache.go`](../../internal/tokenbroker/cache/token_cache.go) --- @@ -560,7 +560,7 @@ Sessions are managed with the following lifecycle: **Session Semaphore**: Each session has a semaphore (capacity: 1) to ensure only one OAuth flow runs at a time per session. -**Implementation**: [`internal/session/manager.go`](../../internal/session/manager.go) +**Implementation**: [`internal/tokenbroker/session/manager.go`](../../internal/tokenbroker/session/manager.go) --- @@ -595,7 +595,7 @@ When a token is needed and not cached, the Token Broker coordinates an OAuth flo - Sends `client_id`, `client_secret`, `code`, `code_verifier`, `redirect_uri` to OAuth provider's token_endpoint - OAuth provider validates PKCE and returns access token - Token Broker receives access token directly (no resource server intermediary) -11. **Cache**: Token Broker caches received token for `(user_id, resource_url)` +11. **Cache**: Token Broker caches received token for `(session_key, resource_url)` 12. **User Redirect**: Token Broker redirects user to Backend's `backend_session_redirect_url` (HTTP 302) 13. **Unblock**: Waiting token requests receive the token @@ -622,7 +622,7 @@ When a token is needed and not cached, the Token Broker coordinates an OAuth flo - resource server never sees PKCE parameters - Authorization code received directly by Token Broker (not forwarded through Backend) -**Implementation**: [`internal/oauth/`](../../internal/oauth/) +**Implementation**: [`internal/tokenbroker/oauth/`](../../internal/tokenbroker/oauth/) --- @@ -659,7 +659,7 @@ sequenceDiagram Note over TokenBroker: Extract session key from state
Validate state matches session TokenBroker->>+OAuth: POST /token
{client_id, client_secret,
code, code_verifier, redirect_uri} OAuth-->>-TokenBroker: {access_token, expires_in} - TokenBroker->>TokenBroker: Cache token for
(user_id, resource_url) + TokenBroker->>TokenBroker: Cache token for
(session_key, resource_url) TokenBroker-->>User: HTTP 302 Redirect to
backend_session_redirect_url TokenBroker-->>-AuthBridge: {token} @@ -687,10 +687,8 @@ sequenceDiagram ## Related Documentation - [Architecture Overview](ARCHITECTURE.md) -- [Deployment Guide](DEPLOYMENT.md) -- [Testing Guide](TESTING.md) -- [Token Broker Architecture](../../docs/token_broker_architecture.md) -- [Envoy Sidecar Design](../../docs/envoy_sidecar_claude.md) +- [Deployment and configuration](README.md#deployment) +- [AuthBridge webhook](../../docs/authbridge-webhook.md) --- @@ -705,10 +703,10 @@ routes: rules: - host: "mcp-server-service" action: "broker" - token_broker_url: "http://token-broker-service:8190" + token_broker_url: "http://token-broker.rossoctl-system.svc.cluster.local:8190" ``` -**Configuration**: See deployment YAML files in this directory +**Configuration**: See `charts/operator/templates/tokenbroker/` and the `tokenBroker` values in `charts/operator/values.yaml` ### Envoy Configuration @@ -718,7 +716,7 @@ Envoy is configured with ext_proc to call AuthBridge: - **Inbound Listener** (15124): Backend → Agent (JWT validation, bypassed in demo) - **ext_proc Cluster**: AuthBridge at 127.0.0.1:9090 -**Configuration**: See deployment YAML files in this directory +**Configuration**: See `charts/operator/templates/tokenbroker/` and the `tokenBroker` values in `charts/operator/values.yaml` --- diff --git a/token-broker/docs/ARCHITECTURE.md b/operator/cmd/token-broker/ARCHITECTURE.md similarity index 90% rename from token-broker/docs/ARCHITECTURE.md rename to operator/cmd/token-broker/ARCHITECTURE.md index ce284f89..64156b3e 100644 --- a/token-broker/docs/ARCHITECTURE.md +++ b/operator/cmd/token-broker/ARCHITECTURE.md @@ -26,7 +26,10 @@ Rossoctl supports three route types for outbound agent requests: └──────────────┘ ``` -The Token Broker is a standalone HTTP service. It is not a sidecar; it is a shared service within the cluster. +The Token Broker is a cluster-shared HTTP service running in its own pod — it is not a +sidecar. The binary ships inside the operator image and is selected with +`command: [/token-broker]`; it is installed by the operator Helm chart behind +`tokenBroker.enabled`. See `README.md` for build and deployment details. - **AuthBridge sidecar** calls `POST /sessions/token` to obtain a token for a resource server. This call blocks until the OAuth flow completes. - **Backend** creates sessions, long-polls for events (`POST /sessions/broker-events`), and ends sessions (`POST /sessions/end`). @@ -37,11 +40,13 @@ The Token Broker is a standalone HTTP service. It is not a sidecar; it is a shar ## Repository Structure +Paths are relative to the `github.com/rossoctl/operator` module root. + ``` -cmd/ +cmd/token-broker/ main.go # Service bootstrap, configuration, HTTP server, graceful shutdown -internal/ +internal/tokenbroker/ api/ # HTTP handlers and route registration auth/ # JWT claim extraction (without signature validation) cache/ # Token cache: per-(session_key, resource_url), JWT expiry parsing @@ -49,37 +54,39 @@ internal/ oauth/ # OAuth client (PKCE gen, URL building, token exchange) + endpoint discovery session/ # SessionManager lifecycle, semaphore, event/token waiter channels -pkg/ - oauth/ # PKCE: GeneratePKCEChallenge(), GenerateState() +pkg/oauth/ # PKCE: GeneratePKCEChallenge(), GenerateState() ``` +Note `cmd/main.go` (no subdirectory) is the **controller-manager** entrypoint, not +this service. + --- ## Package Responsibilities -### `cmd/main.go` +### `cmd/token-broker/main.go` Service entry point. Reads environment variables, constructs components, wires routes, runs HTTP server, handles graceful shutdown. -### `internal/core` +### `internal/tokenbroker/core` - **`interfaces.go`**: `SessionStore`, `TokenCache`, `OAuthDiscoverer`, `Clock`, `Semaphore` interfaces; `Session`, `OAuthTransaction`, `Event`, `TokenResult` types. - **`broker.go`**: `TokenBroker` — coordinates the full token acquisition flow: cache check → semaphore → OAuth discovery → PKCE generation → auth URL → event → wait for callback → token exchange → cache → unblock waiters. -### `internal/api` +### `internal/tokenbroker/api` HTTP handlers. Registers routes on a chi router. Two error formats: flat for the AuthBridge API (`/sessions/token`), nested for the Backend API (all other endpoints). -### `internal/oauth` +### `internal/tokenbroker/oauth` - **`discovery.go`**: `Discoverer` — calls `GET /.well-known/oauth-protected-resource` on the resource server; supports global and per-resource config overrides that skip discovery. - **`client.go`**: `Client` — `BuildAuthorizationURL()` constructs the OAuth redirect URL with PKCE parameters; `ExchangeToken()` calls the OAuth provider's token endpoint directly. -### `internal/session` +### `internal/tokenbroker/session` - **`manager.go`**: `SessionManager` — creates, validates, retrieves, and expires sessions; owns idle timeout timers; notifies token and event waiters on session end. - **`semaphore.go`**: `SimpleSemaphore` — channel-based semaphore used to enforce one active OAuth flow per session. -### `internal/cache` +### `internal/tokenbroker/cache` - **`token_cache.go`**: `TokenCache` — stores access tokens keyed by `(session_key, resource_url)`; checks expiry and near-expiry (< 5 min) on retrieval. - **`jwt_parser.go`**: Extracts the `exp` claim from a JWT to derive cache TTL. Non-JWT tokens are treated as long-lived (1 year). -### `internal/auth` +### `internal/tokenbroker/auth` JWT claim extraction (`sub`, `session_uid`/`jti`) without signature validation — used as a fallback when `JWT_JWKS_URL` is not configured. When it is configured, signature validation is handled by the `authlib` JWKS verifier. ### `pkg/oauth` diff --git a/token-broker/README.md b/operator/cmd/token-broker/README.md similarity index 78% rename from token-broker/README.md rename to operator/cmd/token-broker/README.md index 3054ed1b..8f95a49f 100644 --- a/token-broker/README.md +++ b/operator/cmd/token-broker/README.md @@ -1,6 +1,6 @@ # Token Broker Service -The Token Broker is a standalone Rossoctl service that enables **HITL (Human-in-the-Loop) Authorization** — allowing agents to obtain additional user permissions at runtime, beyond those provided when the task was submitted. +The Token Broker is a Rossoctl service that enables **HITL (Human-in-the-Loop) Authorization** — allowing agents to obtain additional user permissions at runtime, beyond those provided when the task was submitted. ## Overview @@ -42,10 +42,97 @@ Resources that can be brokered include MCP servers, LLM APIs, direct REST APIs, ## Building +The binary ships inside the operator image alongside `manager` and +`bundle-service`; `ENTRYPOINT` is `/manager`, so a Deployment selects this one +with `command: [/token-broker]`. + +```bash +# From the operator module +go build -o token-broker ./cmd/token-broker/ + +# Or the whole image (all three binaries) +docker build -f Dockerfile -t rossoctl-operator:dev . +``` + +## Deployment + +The broker ships inside the operator image and is installed by the operator Helm +chart, gated off by default: + +```bash +# Create the OAuth credentials Secret first — the chart never templates +# credentials, so they stay out of values and Helm release state. +kubectl create secret generic github-oauth-credentials -n rossoctl-system \ + --from-literal=client-id= \ + --from-literal=client-secret= + +helm upgrade --install rossoctl-operator charts/operator \ + --namespace rossoctl-system \ + --set tokenBroker.enabled=true +``` + +Templates live in `charts/operator/templates/tokenbroker/` (ServiceAccount, +Deployment, Service, HTTPRoute); values are under `tokenBroker` in +`charts/operator/values.yaml`. + +### OAuth credentials + +The chart never creates the Secret, so credentials never pass through values or +Helm release state. Create it however your environment manages secrets — the +`kubectl create secret` above for dev, or an external store (ExternalSecrets, +Vault, SOPS) in production. The Deployment reads it by reference: + +| Value | Default | Meaning | +|-------|---------|---------| +| `tokenBroker.oauth.existingSecret` | `github-oauth-credentials` | Secret name | +| `tokenBroker.oauth.clientIdKey` | `client-id` | key holding the client ID | +| `tokenBroker.oauth.clientSecretKey` | `client-secret` | key holding the client secret | + +Point these at whatever your secret store produces, e.g.: + +```bash +helm upgrade --install rossoctl-operator charts/operator \ + --namespace rossoctl-system \ + --set tokenBroker.enabled=true \ + --set tokenBroker.oauth.existingSecret=my-oauth-creds \ + --set tokenBroker.oauth.clientIdKey=id \ + --set tokenBroker.oauth.clientSecretKey=secret +``` + +The Secret must exist in the release namespace before the pod starts; without it +the container stays in `CreateContainerConfigError`. + +Default deployment settings: + +- Namespace: the release namespace +- Deployment / Service name: `token-broker` +- Port: `8190` +- Replicas: **fixed at 1** — sessions and the token cache are in-memory, so + scaling out requires shared state first. This is deliberately not a chart value. + +### The callback route must match + +`tokenBroker.oauth.callbackUrl` and `tokenBroker.httpRoute.hostname` must use the +**same host**. The OAuth provider redirects the browser to that URL and the gateway +routes host + `/oauth/callback` to the broker; a mismatch (or an unattached route) +makes the post-consent redirect 404, and the broker then blocks waiting for a +callback that never arrives. + +Both default to `token-broker.localtest.me`, which works out of the box on a +kind/dev cluster. Override both for real deployments, or set +`tokenBroker.httpRoute.enabled=false` and route the callback yourself. + +### Local development against kind + ```bash -go build -o token-broker ./cmd/ +./hack/kind-reload-all.sh [cluster-name] [namespace] +# Defaults: cluster=rossoctl, namespace=rossoctl-system ``` +Builds the operator image, loads it into kind, and deploys all three services. +The broker is deployed only if `operator/.env` supplies +`GITHUB_OAUTH_CLIENT_ID` and `GITHUB_OAUTH_CLIENT_SECRET`; otherwise it is skipped. + ## Configuration ### Environment Variables @@ -145,11 +232,12 @@ The service will start on port 8190 by default and log to stdout in JSON format. ### Docker ```bash -docker build -t token-broker . +docker build -f Dockerfile -t rossoctl-operator:dev . docker run -p 8190:8190 \ + --entrypoint /token-broker \ -e OAUTH_CLIENT_ID=Ov23liXXXXXXXXXXXXXX \ -e OAUTH_CLIENT_SECRET=your_secret \ - token-broker + rossoctl-operator:dev ``` ## API Endpoints @@ -390,11 +478,14 @@ The `backend_session_redirect_url` host is not in `ALLOWED_REDIRECT_HOSTS`. Add ## Development ### Project Structure + +The broker lives in the `github.com/rossoctl/operator` module: + ``` -cmd/ +cmd/token-broker/ main.go # Service bootstrap and configuration -internal/ +internal/tokenbroker/ api/ # HTTP handlers cache/ # Token cache with JWT expiry parsing core/ # Token acquisition orchestration and interfaces @@ -402,13 +493,12 @@ internal/ session/ # Session lifecycle, semaphore, event coordination auth/ # JWT claim extraction -pkg/ - oauth/ # PKCE challenge generation (GeneratePKCEChallenge, GenerateState) +pkg/oauth/ # PKCE challenge generation (GeneratePKCEChallenge, GenerateState) ``` ### Adding New Features -1. Update interfaces in `internal/core/interfaces.go` +1. Update interfaces in `internal/tokenbroker/core/interfaces.go` 2. Implement in the appropriate package 3. Add unit tests 4. Update this README diff --git a/token-broker/cmd/config_test.go b/operator/cmd/token-broker/config_test.go similarity index 100% rename from token-broker/cmd/config_test.go rename to operator/cmd/token-broker/config_test.go diff --git a/token-broker/cmd/main.go b/operator/cmd/token-broker/main.go similarity index 97% rename from token-broker/cmd/main.go rename to operator/cmd/token-broker/main.go index 08634690..d5308d74 100644 --- a/token-broker/cmd/main.go +++ b/operator/cmd/token-broker/main.go @@ -31,11 +31,11 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/rossoctl/cortex/authbridge/authlib/plugins/jwtvalidation/validation" - "github.com/rossoctl/token-broker/internal/api" - "github.com/rossoctl/token-broker/internal/cache" - "github.com/rossoctl/token-broker/internal/core" - "github.com/rossoctl/token-broker/internal/oauth" - "github.com/rossoctl/token-broker/internal/session" + "github.com/rossoctl/operator/internal/tokenbroker/api" + "github.com/rossoctl/operator/internal/tokenbroker/cache" + "github.com/rossoctl/operator/internal/tokenbroker/core" + "github.com/rossoctl/operator/internal/tokenbroker/oauth" + "github.com/rossoctl/operator/internal/tokenbroker/session" ) // Config holds the Token Broker configuration. diff --git a/operator/config/bundleservice/deployment.yaml b/operator/config/bundleservice/deployment.yaml deleted file mode 100644 index 0d4a05dc..00000000 --- a/operator/config/bundleservice/deployment.yaml +++ /dev/null @@ -1,58 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bundle-service - namespace: system - labels: - app: bundle-service -spec: - replicas: 1 - selector: - matchLabels: - app: bundle-service - template: - metadata: - labels: - app: bundle-service - spec: - serviceAccountName: bundle-service - securityContext: - runAsNonRoot: true - containers: - - name: bundle-service - image: ghcr.io/rossoctl/bundle-service:latest - ports: - - name: http - containerPort: 8080 - protocol: TCP - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: LOG_LEVEL - value: info - livenessProbe: - httpGet: - path: /healthz - port: http - initialDelaySeconds: 5 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /readyz - port: http - initialDelaySeconds: 2 - periodSeconds: 5 - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - memory: 256Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: - drop: - - ALL diff --git a/operator/config/bundleservice/rbac.yaml b/operator/config/bundleservice/rbac.yaml deleted file mode 100644 index e2aaf8c3..00000000 --- a/operator/config/bundleservice/rbac.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: rossoctl-bundle-service -rules: - - apiGroups: - - agent.rossoctl.dev - resources: - - authorizationpolicies - verbs: - - get - - list - - watch - - apiGroups: - - agent.rossoctl.dev - resources: - - authorizationpolicies/status - verbs: - - get - - update - - patch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: rossoctl-bundle-service -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: rossoctl-bundle-service -subjects: - - kind: ServiceAccount - name: bundle-service - namespace: system diff --git a/operator/config/bundleservice/serviceaccount.yaml b/operator/config/bundleservice/serviceaccount.yaml deleted file mode 100644 index 0aa1bc02..00000000 --- a/operator/config/bundleservice/serviceaccount.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: bundle-service - namespace: system diff --git a/operator/go.mod b/operator/go.mod index 5ba766cc..7bb79d3b 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -1,21 +1,25 @@ module github.com/rossoctl/operator -go 1.26.0 +go 1.26.5 godebug default=go1.23 require ( github.com/cert-manager/cert-manager v1.21.1 github.com/fsnotify/fsnotify v1.10.1 + github.com/go-chi/chi/v5 v5.3.0 github.com/go-logr/logr v1.4.4 + github.com/google/uuid v1.6.0 github.com/gowebpki/jcs v1.0.1 github.com/onsi/ginkgo/v2 v2.32.1 github.com/onsi/gomega v1.42.1 github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.2 + github.com/rossoctl/cortex/authbridge/authlib v0.0.0-20260915135208-2d373605612d github.com/sigstore/sigstore-go v1.3.0 github.com/spiffe/go-spiffe/v2 v2.8.1 - golang.org/x/sync v0.22.0 + github.com/stretchr/testify v1.12.1 + golang.org/x/sync v0.23.0 k8s.io/api v0.36.3 k8s.io/apiextensions-apiserver v0.36.3 k8s.io/apimachinery v0.36.3 @@ -40,12 +44,13 @@ require ( github.com/coreos/go-oidc/v3 v3.18.0 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/gkampitakis/go-snaps v0.5.22 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect @@ -60,35 +65,40 @@ require ( github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect github.com/go-openapi/spec v0.22.9 // indirect github.com/go-openapi/strfmt v0.27.0 // indirect - github.com/go-openapi/swag v0.26.1 // indirect - github.com/go-openapi/swag/cmdutils v0.27.0 // indirect - github.com/go-openapi/swag/conv v0.27.3 // indirect - github.com/go-openapi/swag/fileutils v0.27.3 // indirect - github.com/go-openapi/swag/jsonname v0.26.1 // indirect - github.com/go-openapi/swag/jsonutils v0.27.3 // indirect - github.com/go-openapi/swag/loading v0.27.3 // indirect - github.com/go-openapi/swag/mangling v0.27.3 // indirect - github.com/go-openapi/swag/netutils v0.27.0 // indirect - github.com/go-openapi/swag/pools v0.27.3 // indirect - github.com/go-openapi/swag/stringutils v0.27.3 // indirect - github.com/go-openapi/swag/typeutils v0.27.3 // indirect - github.com/go-openapi/swag/yamlutils v0.27.3 // indirect + github.com/go-openapi/swag v0.28.0 // indirect + github.com/go-openapi/swag/cmdutils v0.28.0 // indirect + github.com/go-openapi/swag/conv v0.28.0 // indirect + github.com/go-openapi/swag/fileutils v0.28.0 // indirect + github.com/go-openapi/swag/jsonutils v0.28.0 // indirect + github.com/go-openapi/swag/loading v0.28.0 // indirect + github.com/go-openapi/swag/mangling v0.28.0 // indirect + github.com/go-openapi/swag/netutils v0.28.0 // indirect + github.com/go-openapi/swag/pools v0.28.0 // indirect + github.com/go-openapi/swag/stringutils v0.28.0 // indirect + github.com/go-openapi/swag/typeutils v0.28.0 // indirect + github.com/go-openapi/swag/yamlutils v0.28.0 // indirect github.com/go-openapi/validate v0.26.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/google/cel-go v0.29.0 // indirect github.com/google/certificate-transparency-go v1.3.3 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.21.7 // indirect github.com/google/pprof v0.0.0-20260604005048-7023385849c0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect github.com/in-toto/attestation v1.2.0 // indirect github.com/in-toto/in-toto-golang v0.11.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedisct1/go-minisign v0.0.0-20260527172527-a09352b57a22 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/lestrrat-go/blackmagic v1.0.4 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/jwx/v2 v2.1.7 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/letsencrypt/boulder v0.20260608.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect @@ -100,6 +110,7 @@ require ( github.com/prometheus/common v0.70.1 // indirect github.com/prometheus/procfs v0.21.1 // indirect github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/protobuf-specs v0.5.1 // indirect github.com/sigstore/rekor v1.5.3 // indirect @@ -115,35 +126,35 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect + go.opentelemetry.io/otel v1.46.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 // indirect + go.opentelemetry.io/otel/metric v1.46.0 // indirect + go.opentelemetry.io/otel/sdk v1.46.0 // indirect + go.opentelemetry.io/otel/trace v1.46.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.step.sm/crypto v0.82.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.55.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect - golang.org/x/mod v0.38.0 // indirect - golang.org/x/net v0.58.0 // indirect + golang.org/x/mod v0.41.0 // indirect + golang.org/x/net v0.59.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/term v0.46.0 // indirect + golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.48.0 // indirect + golang.org/x/tools v0.49.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20260608224507-4308a22a1bab // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/grpc v1.83.2 // indirect - google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + google.golang.org/protobuf v1.36.12 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/apiserver v0.36.3 // indirect diff --git a/operator/go.sum b/operator/go.sum index ea05cca1..87bc70b7 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -100,6 +100,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= @@ -111,8 +113,8 @@ github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= @@ -150,34 +152,32 @@ github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+ github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= -github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGoQ= -github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= -github.com/go-openapi/swag/cmdutils v0.27.0 h1:aIKiqhB29AaP+7xm8/CPg3uOpeHx2SUp6TvMpu/a31Y= -github.com/go-openapi/swag/cmdutils v0.27.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.27.3 h1:iqJFmGEjmX3AY0lSszABFqRVqOSt99XS0LzNIMJYuhU= -github.com/go-openapi/swag/conv v0.27.3/go.mod h1:nPRmN6jgNme99hpf+nM0auDZGALWIqlwhisKPK/bQhQ= -github.com/go-openapi/swag/fileutils v0.27.3 h1:3UVoZ2RLaIs1lt+2jcKzL8RM3Yk0rmsDE9FLA/HGxFE= -github.com/go-openapi/swag/fileutils v0.27.3/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE= -github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc= -github.com/go-openapi/swag/jsonutils v0.27.3 h1:1DEz+O82frtSMBcos/7XIn1GnpNTbsD4Bru4Dc/uhRc= -github.com/go-openapi/swag/jsonutils v0.27.3/go.mod h1:qiDCoQvzkMxrV3G8FLEdIU5L+EFYc0zcDOHWT3Yofvo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3 h1:h/eT9kmGCDdFLJF29lOhzLtF0FmP1AX2MhLJWVebsb8= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= -github.com/go-openapi/swag/loading v0.27.3 h1:L9nQkEgzU7QgFQL+pLEMfGUKxeM4pWwGwbET9Z3weW0= -github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE= -github.com/go-openapi/swag/mangling v0.27.3 h1:gRzzD1PAUoLTtGMgI3KpBmCSOlTuLTFWnviLxLcTnyg= -github.com/go-openapi/swag/mangling v0.27.3/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.27.0 h1:lEUG+hHvPvLggB3A8snFk0IRKNf9uC0YKc+7WYqvAF8= -github.com/go-openapi/swag/netutils v0.27.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= -github.com/go-openapi/swag/pools v0.27.3 h1:gXjImP3F6/56wRRcFgEPld084Y6u2gs21ikPBt8NKBk= -github.com/go-openapi/swag/pools v0.27.3/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.27.3 h1:Ru28hnbAvN5wycALQYy8IobHvASq+FUFMlp1QzLM0JI= -github.com/go-openapi/swag/stringutils v0.27.3/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.27.3 h1:l6SSrx5eR5/WVwrGNzN6bQ9WqL04mrxNBl9YgQ3rcJ4= -github.com/go-openapi/swag/typeutils v0.27.3/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.27.3 h1:cRFCAoYtslYn9L9T0xWryHy1t7c1MACC+DMj3CLvwvs= -github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU= +github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw= +github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg= +github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q= +github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= +github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= +github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= +github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= +github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= +github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= +github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= +github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k= +github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= +github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= +github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= +github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= +github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= @@ -190,6 +190,8 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -227,8 +229,8 @@ github.com/gowebpki/jcs v1.0.1 h1:Qjzg8EOkrOTuWP7DqQ1FbYtcpEbeTzUoTN9bptp8FOU= github.com/gowebpki/jcs v1.0.1/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -273,6 +275,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.7 h1:bnYeET+S8IOyAw6W4LTc6SEeK7Xs58SKKZkR7scb3Ko= +github.com/lestrrat-go/jwx/v2 v2.1.7/go.mod h1:exQ9ZBuN1cMLYmxwhTlHUru08ykONG0z+HbLEeDG9qo= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/letsencrypt/boulder v0.20260608.0 h1:6IMbSzr4XC321Yqph4zokoGW7tdVDvR1on6lI/wJiMk= github.com/letsencrypt/boulder v0.20260608.0/go.mod h1:SCtxgc9za2EpV67oillMAaAQKdlZBXTRasJLgi9+GBM= github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg= @@ -319,6 +333,8 @@ github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+ github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rossoctl/cortex/authbridge/authlib v0.0.0-20260915135208-2d373605612d h1:MPmOOxFbqWLyfNCrycCG7PwvHmZiZkddj7K9pENUkXw= +github.com/rossoctl/cortex/authbridge/authlib v0.0.0-20260915135208-2d373605612d/go.mod h1:6d/6KaVHxNDmNz6vPWGQXPv3MhsJsV7f0c6D/E+y4h8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= @@ -328,6 +344,8 @@ github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgm github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= @@ -360,12 +378,14 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E= github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= github.com/theupdateframework/go-tuf/v2 v2.4.2 h1:w7976/W8uTwlsegP5nRymlpjPgrwSh+AXUf85is6nJk= @@ -400,26 +420,26 @@ github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97 github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0 h1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0/go.mod h1:DqEFwLumhzMBDQv9PcWbyoDxHI/4lAk6CM4nJBH39sc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04= +go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= +go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 h1:w53CDeOA/Kurp7yRsegSr6pbbr759dOvJ+yNmWM6Hxs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0/go.mod h1:BOmGMCbAtvcJiSJ+hLuhgPLdDbimnraSl8irz3iY8sY= +go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= +go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= +go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= +go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= +go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= +go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= +go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= +go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.step.sm/crypto v0.82.0 h1:JOT8b/7Jh4My3mxE4U7UkuaN2sUGkZ8fnjznXaTGoRE= go.step.sm/crypto v0.82.0/go.mod h1:qyLTv666WJ6ImFPUjljux+684Y/GGYUjAZcKCnc6yBs= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -430,30 +450,31 @@ go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= @@ -462,17 +483,15 @@ google.golang.org/api v0.286.0 h1:TdTXMvzYKnWV1/lPbCdbXRqBrkDqjPto22H2xeZZ8LI= google.golang.org/api v0.286.0/go.mod h1:NlOlUIr8MPoIhT9Bb/oUnRuHbJOLwxb6JSYJM8Yz+jQ= google.golang.org/genproto v0.0.0-20260608224507-4308a22a1bab h1:bG8JpL3dfsvJKRgrh7yMkswdxzBqQDRYqkLDHo3+708= google.golang.org/genproto v0.0.0-20260608224507-4308a22a1bab/go.mod h1:cVHIikDNAdx8ISZeW+2rYkEMf3xn0GSaBYmVnWXQBUo= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= diff --git a/operator/hack/bundle-service-kind.sh b/operator/hack/bundle-service-kind.sh deleted file mode 100755 index e5489fa4..00000000 --- a/operator/hack/bundle-service-kind.sh +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env bash -# Build the bundle-service image, load it into a kind cluster, and deploy. -# -# Usage: -# ./hack/bundle-service-kind.sh [kind-cluster-name] [namespace] -# -# Defaults: -# cluster: rossoctl -# namespace: rossoctl-system - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" - -CLUSTER="${1:-rossoctl}" -NAMESPACE="${2:-rossoctl-system}" -IMAGE="localhost/bundle-service:latest" - -echo "==> Building bundle-service image" -docker build -t "${IMAGE}" -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" "${ROOT_DIR}" - -echo "==> Loading image into kind cluster '${CLUSTER}'" -kind load docker-image "${IMAGE}" --name "${CLUSTER}" - -echo "==> Ensuring namespace '${NAMESPACE}' exists" -kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - - -echo "==> Ensuring CRD is installed" -if [ -f "${ROOT_DIR}/config/crd/bases/agent.rossoctl.dev_authorizationpolicies.yaml" ]; then - kubectl apply -f "${ROOT_DIR}/config/crd/bases/agent.rossoctl.dev_authorizationpolicies.yaml" -fi - -echo "==> Applying default global AuthorizationPolicy" -kubectl apply -f "${ROOT_DIR}/config/bundleservice/default-policy.yaml" - -echo "==> Deploying bundle-service to namespace '${NAMESPACE}'" - -# ServiceAccount -kubectl apply -f - < Waiting for rollout" -kubectl rollout status deployment/bundle-service -n "${NAMESPACE}" --timeout=60s - -echo "==> bundle-service deployed successfully" -echo " URL: http://bundle-service.${NAMESPACE}.svc.cluster.local:8080" -echo " To port-forward: kubectl port-forward -n ${NAMESPACE} svc/bundle-service 8080:8080" diff --git a/operator/hack/kind-reload-all.sh b/operator/hack/kind-reload-all.sh index 564557d1..a9ddd0be 100755 --- a/operator/hack/kind-reload-all.sh +++ b/operator/hack/kind-reload-all.sh @@ -1,11 +1,14 @@ #!/usr/bin/env bash -# Build all operator services, load them into a Kind cluster, and deploy. +# Build the operator image, load it into a Kind cluster, and deploy. # Creates deployments if they don't exist, updates them if they do. # -# Services: -# 1. rossoctl-controller-manager (operator) -# 2. bundle-service (OPA bundle server) -# 3. token-broker (OAuth token management) +# All three services ship in the SINGLE operator image and are selected by the +# container's `command:` (ENTRYPOINT is /manager): +# 1. rossoctl-controller-manager (/manager) +# 2. bundle-service (/bundle-service) +# 3. token-broker (/token-broker) +# +# token-broker additionally needs OAuth credentials; see the .env note below. # # Usage: # ./hack/kind-reload-all.sh [kind-cluster-name] [namespace] @@ -24,9 +27,8 @@ NAMESPACE="${2:-rossoctl-system}" CONTAINER_TOOL="${CONTAINER_TOOL:-docker}" IMAGE_TAG="$(git -C "${ROOT_DIR}" rev-parse --short HEAD)" +# One image, three binaries — see the header. OPERATOR_IMG="localhost/operator:${IMAGE_TAG}" -BUNDLE_IMG="localhost/bundle-service:${IMAGE_TAG}" -TOKEN_BROKER_IMG="localhost/token-broker:${IMAGE_TAG}" echo "============================================" echo " Building and loading to Kind: ${CLUSTER}" @@ -51,35 +53,15 @@ fi # --- Build images --- echo "" -echo "==> Building rossoctl-operator image" +echo "==> Building rossoctl-operator image (manager + bundle-service + token-broker)" ${CONTAINER_TOOL} build -t "${OPERATOR_IMG}" -f "${ROOT_DIR}/Dockerfile" "${ROOT_DIR}" -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then - echo "" - echo "==> Building bundle-service image" - ${CONTAINER_TOOL} build -t "${BUNDLE_IMG}" -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" "${ROOT_DIR}" -fi - -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then - echo "" - echo "==> Building token-broker image" - ${CONTAINER_TOOL} build -t "${TOKEN_BROKER_IMG}" -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" "${ROOT_DIR}" -fi - # --- Load into Kind --- echo "" echo "==> Loading images into Kind cluster '${CLUSTER}'" kind load docker-image "${OPERATOR_IMG}" --name "${CLUSTER}" -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then - kind load docker-image "${BUNDLE_IMG}" --name "${CLUSTER}" -fi - -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then - kind load docker-image "${TOKEN_BROKER_IMG}" --name "${CLUSTER}" -fi - # --- Deploy rossoctl-controller-manager --- echo "" @@ -95,160 +77,61 @@ fi # --- Deploy bundle-service (if source exists) --- -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/bundle-service" ]; then echo "" echo "==> Deploying bundle-service" -# ServiceAccount -kubectl apply -f - < Deploying token-broker" -# Load OAuth credentials from .env +# Load OAuth credentials from .env. +# Sourced rather than piped through xargs: xargs word-splits on whitespace, so a +# value containing a space would export a truncated variable, pass the guard +# below, and create the Secret with a partial credential — failing later at OAuth +# time with an error that points at the provider instead of at this loader. if [ -f "${ROOT_DIR}/.env" ]; then - export $(grep -v '^#' "${ROOT_DIR}/.env" | grep -v '^\s*$' | xargs) + set -a + # shellcheck disable=SC1091 + . "${ROOT_DIR}/.env" + set +a fi if [ -z "${GITHUB_OAUTH_CLIENT_ID:-}" ] || [ -z "${GITHUB_OAUTH_CLIENT_SECRET:-}" ]; then - echo " ERROR: OAuth credentials not set." - echo " Create ${ROOT_DIR}/.env with:" + echo " SKIPPED: OAuth credentials not set." + echo " To deploy token-broker, create ${ROOT_DIR}/.env with:" echo " GITHUB_OAUTH_CLIENT_ID=" echo " GITHUB_OAUTH_CLIENT_SECRET=" - echo " See .env.example for reference." - exit 1 -fi +else # Create/update OAuth secret kubectl delete secret github-oauth-credentials -n "${NAMESPACE}" 2>/dev/null || true @@ -258,25 +141,25 @@ kubectl create secret generic github-oauth-credentials \ --namespace="${NAMESPACE}" echo " OAuth secret created" -# Apply the deployment manifests (single source of truth). -# These pin namespace rossoctl-system and include the OAuth-callback HTTPRoute -# (attached to the shared "http" gateway). The secret is created above from -# .env, so the secret example manifest is intentionally skipped. -DEPLOY_DIR="${ROOT_DIR}/cmd/token-broker/deploy" -kubectl apply \ - -f "${DEPLOY_DIR}/00-serviceaccount.yaml" \ - -f "${DEPLOY_DIR}/02-deployment.yaml" \ - -f "${DEPLOY_DIR}/03-service.yaml" \ - -f "${DEPLOY_DIR}/04-httproute.yaml" - -# Inject the freshly built, git-tagged image (the manifest carries a placeholder -# tag with imagePullPolicy: IfNotPresent; kind-loaded images need Never). +# Render the token-broker manifests from the chart — the single source of truth — +# rather than duplicating them here. The secret is created above from .env. +helm template rossoctl-operator "${ROOT_DIR}/../charts/operator" \ + --namespace "${NAMESPACE}" \ + --set tokenBroker.enabled=true \ + --show-only templates/tokenbroker/serviceaccount.yaml \ + --show-only templates/tokenbroker/deployment.yaml \ + --show-only templates/tokenbroker/service.yaml \ + --show-only templates/tokenbroker/httproute.yaml \ + | kubectl apply -f - + +# Point at the freshly built, git-tagged local image; kind-loaded images need Never. kubectl set image deployment/token-broker \ - token-broker="${TOKEN_BROKER_IMG}" \ + token-broker="${OPERATOR_IMG}" \ -n "${NAMESPACE}" kubectl patch deployment/token-broker -n "${NAMESPACE}" --type=json \ -p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Never"}]' -echo " token-broker manifests applied (image ${TOKEN_BROKER_IMG})" +echo " token-broker deployed from chart (image ${OPERATOR_IMG})" +fi fi # --- Delete pods to pick up the new images --- @@ -285,11 +168,11 @@ echo "" echo "==> Deleting pods to pick up new images" kubectl delete pods -n "${NAMESPACE}" -l control-plane=controller-manager --wait=false 2>/dev/null || true -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/bundle-service" ]; then kubectl delete pods -n "${NAMESPACE}" -l app=bundle-service --wait=false fi -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/token-broker" ]; then kubectl delete pods -n "${NAMESPACE}" -l app=token-broker --wait=false fi @@ -300,12 +183,12 @@ echo "==> Waiting for rossoctl-controller-manager rollout" kubectl rollout status deployment/rossoctl-controller-manager -n "${NAMESPACE}" --timeout=120s 2>/dev/null || \ echo " WARNING: rossoctl-controller-manager rollout did not complete" -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/bundle-service" ]; then echo "==> Waiting for bundle-service rollout" kubectl rollout status deployment/bundle-service -n "${NAMESPACE}" --timeout=60s fi -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/token-broker" ]; then echo "==> Waiting for token-broker rollout" kubectl rollout status deployment/token-broker -n "${NAMESPACE}" --timeout=60s fi @@ -317,36 +200,30 @@ echo "============================================" echo " Done!" echo "" echo " Images loaded:" -echo " ${OPERATOR_IMG}" -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then - echo " ${BUNDLE_IMG}" -fi -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then - echo " ${TOKEN_BROKER_IMG}" -fi +echo " ${OPERATOR_IMG} (manager + bundle-service + token-broker)" echo "" echo " Namespace: ${NAMESPACE}" echo " - rossoctl-controller-manager" -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/bundle-service" ]; then echo " - bundle-service" fi -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/token-broker" ]; then echo " - token-broker" fi echo "" -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/bundle-service" ]; then echo " bundle-service URL: http://bundle-service.${NAMESPACE}.svc.cluster.local:8080" fi -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/token-broker" ]; then echo " token-broker URL: http://token-broker.${NAMESPACE}.svc.cluster.local:8190" echo " OAuth callback: http://token-broker.localtest.me:8080/oauth/callback (via 'http' gateway)" fi echo "" echo " To port-forward:" -if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/bundle-service" ]; then echo " kubectl port-forward -n ${NAMESPACE} svc/bundle-service 8080:8080" fi -if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then +if [ -d "${ROOT_DIR}/cmd/token-broker" ]; then echo " kubectl port-forward -n ${NAMESPACE} svc/token-broker 8190:8190" fi echo "============================================" diff --git a/token-broker/internal/api/handlers.go b/operator/internal/tokenbroker/api/handlers.go similarity index 99% rename from token-broker/internal/api/handlers.go rename to operator/internal/tokenbroker/api/handlers.go index 9ed1597d..ad44be9e 100644 --- a/token-broker/internal/api/handlers.go +++ b/operator/internal/tokenbroker/api/handlers.go @@ -11,8 +11,8 @@ import ( "github.com/go-chi/chi/v5" "github.com/rossoctl/cortex/authbridge/authlib/plugins/jwtvalidation/validation" - "github.com/rossoctl/token-broker/internal/auth" - "github.com/rossoctl/token-broker/internal/core" + "github.com/rossoctl/operator/internal/tokenbroker/auth" + "github.com/rossoctl/operator/internal/tokenbroker/core" ) // TokenBroker defines the interface for token acquisition operations. diff --git a/token-broker/internal/api/handlers_test.go b/operator/internal/tokenbroker/api/handlers_test.go similarity index 99% rename from token-broker/internal/api/handlers_test.go rename to operator/internal/tokenbroker/api/handlers_test.go index fd041553..6cad07b5 100644 --- a/token-broker/internal/api/handlers_test.go +++ b/operator/internal/tokenbroker/api/handlers_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/rossoctl/token-broker/internal/core" + "github.com/rossoctl/operator/internal/tokenbroker/core" ) // mockBroker implements a mock TokenBroker for testing diff --git a/token-broker/internal/auth/jwt.go b/operator/internal/tokenbroker/auth/jwt.go similarity index 100% rename from token-broker/internal/auth/jwt.go rename to operator/internal/tokenbroker/auth/jwt.go diff --git a/token-broker/internal/auth/jwt_test.go b/operator/internal/tokenbroker/auth/jwt_test.go similarity index 100% rename from token-broker/internal/auth/jwt_test.go rename to operator/internal/tokenbroker/auth/jwt_test.go diff --git a/token-broker/internal/cache/jwt_parser.go b/operator/internal/tokenbroker/cache/jwt_parser.go similarity index 100% rename from token-broker/internal/cache/jwt_parser.go rename to operator/internal/tokenbroker/cache/jwt_parser.go diff --git a/token-broker/internal/cache/jwt_parser_test.go b/operator/internal/tokenbroker/cache/jwt_parser_test.go similarity index 100% rename from token-broker/internal/cache/jwt_parser_test.go rename to operator/internal/tokenbroker/cache/jwt_parser_test.go diff --git a/token-broker/internal/cache/token_cache.go b/operator/internal/tokenbroker/cache/token_cache.go similarity index 98% rename from token-broker/internal/cache/token_cache.go rename to operator/internal/tokenbroker/cache/token_cache.go index 85e1703a..94fe0e35 100644 --- a/token-broker/internal/cache/token_cache.go +++ b/operator/internal/tokenbroker/cache/token_cache.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/rossoctl/token-broker/internal/core" + "github.com/rossoctl/operator/internal/tokenbroker/core" ) // TokenEntry represents a cached token. diff --git a/token-broker/internal/cache/token_cache_test.go b/operator/internal/tokenbroker/cache/token_cache_test.go similarity index 99% rename from token-broker/internal/cache/token_cache_test.go rename to operator/internal/tokenbroker/cache/token_cache_test.go index d7455446..d00665ee 100644 --- a/token-broker/internal/cache/token_cache_test.go +++ b/operator/internal/tokenbroker/cache/token_cache_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/rossoctl/token-broker/internal/core" + "github.com/rossoctl/operator/internal/tokenbroker/core" ) // FakeClock implements core.Clock for testing. diff --git a/token-broker/internal/core/broker.go b/operator/internal/tokenbroker/core/broker.go similarity index 98% rename from token-broker/internal/core/broker.go rename to operator/internal/tokenbroker/core/broker.go index 19f966af..7084af9f 100644 --- a/token-broker/internal/core/broker.go +++ b/operator/internal/tokenbroker/core/broker.go @@ -6,8 +6,8 @@ import ( "log/slog" "time" - "github.com/rossoctl/token-broker/internal/oauth" - pkgauth "github.com/rossoctl/token-broker/pkg/oauth" + "github.com/rossoctl/operator/internal/tokenbroker/oauth" + pkgauth "github.com/rossoctl/operator/pkg/oauth" ) // TokenBroker orchestrates token acquisition for OAuth sessions. diff --git a/token-broker/internal/core/broker_test.go b/operator/internal/tokenbroker/core/broker_test.go similarity index 91% rename from token-broker/internal/core/broker_test.go rename to operator/internal/tokenbroker/core/broker_test.go index 7993296f..0eb7fadd 100644 --- a/token-broker/internal/core/broker_test.go +++ b/operator/internal/tokenbroker/core/broker_test.go @@ -8,10 +8,10 @@ import ( "testing" "time" - "github.com/rossoctl/token-broker/internal/cache" - "github.com/rossoctl/token-broker/internal/core" - "github.com/rossoctl/token-broker/internal/oauth" - "github.com/rossoctl/token-broker/internal/session" + "github.com/rossoctl/operator/internal/tokenbroker/cache" + "github.com/rossoctl/operator/internal/tokenbroker/core" + "github.com/rossoctl/operator/internal/tokenbroker/oauth" + "github.com/rossoctl/operator/internal/tokenbroker/session" ) // TestAcquireToken_UnblocksOnSessionEnd verifies that ending a session while a diff --git a/token-broker/internal/core/errors.go b/operator/internal/tokenbroker/core/errors.go similarity index 100% rename from token-broker/internal/core/errors.go rename to operator/internal/tokenbroker/core/errors.go diff --git a/token-broker/internal/core/interfaces.go b/operator/internal/tokenbroker/core/interfaces.go similarity index 99% rename from token-broker/internal/core/interfaces.go rename to operator/internal/tokenbroker/core/interfaces.go index e6b2be04..5492dff8 100644 --- a/token-broker/internal/core/interfaces.go +++ b/operator/internal/tokenbroker/core/interfaces.go @@ -5,7 +5,7 @@ import ( "context" "time" - pkgauth "github.com/rossoctl/token-broker/pkg/oauth" + pkgauth "github.com/rossoctl/operator/pkg/oauth" ) // SessionStore manages OAuth sessions and their lifecycle. diff --git a/token-broker/internal/oauth/client.go b/operator/internal/tokenbroker/oauth/client.go similarity index 98% rename from token-broker/internal/oauth/client.go rename to operator/internal/tokenbroker/oauth/client.go index 5caccd37..69c630a6 100644 --- a/token-broker/internal/oauth/client.go +++ b/operator/internal/tokenbroker/oauth/client.go @@ -11,7 +11,7 @@ import ( "strings" // Reuse existing PKCE implementation - pkgauth "github.com/rossoctl/token-broker/pkg/oauth" + pkgauth "github.com/rossoctl/operator/pkg/oauth" ) // Config holds OAuth client configuration for the Token Broker. diff --git a/token-broker/internal/oauth/discovery.go b/operator/internal/tokenbroker/oauth/discovery.go similarity index 100% rename from token-broker/internal/oauth/discovery.go rename to operator/internal/tokenbroker/oauth/discovery.go diff --git a/token-broker/internal/oauth/discovery_test.go b/operator/internal/tokenbroker/oauth/discovery_test.go similarity index 100% rename from token-broker/internal/oauth/discovery_test.go rename to operator/internal/tokenbroker/oauth/discovery_test.go diff --git a/token-broker/internal/session/manager.go b/operator/internal/tokenbroker/session/manager.go similarity index 99% rename from token-broker/internal/session/manager.go rename to operator/internal/tokenbroker/session/manager.go index 8eced380..08f1386b 100644 --- a/token-broker/internal/session/manager.go +++ b/operator/internal/tokenbroker/session/manager.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/rossoctl/token-broker/internal/core" + "github.com/rossoctl/operator/internal/tokenbroker/core" ) // SessionManager manages OAuth sessions and their lifecycle. diff --git a/token-broker/internal/session/manager_test.go b/operator/internal/tokenbroker/session/manager_test.go similarity index 99% rename from token-broker/internal/session/manager_test.go rename to operator/internal/tokenbroker/session/manager_test.go index 1fcf5852..322b7e33 100644 --- a/token-broker/internal/session/manager_test.go +++ b/operator/internal/tokenbroker/session/manager_test.go @@ -8,7 +8,7 @@ import ( "os" "github.com/google/uuid" - "github.com/rossoctl/token-broker/internal/core" + "github.com/rossoctl/operator/internal/tokenbroker/core" ) // FakeClock for testing diff --git a/token-broker/internal/session/semaphore.go b/operator/internal/tokenbroker/session/semaphore.go similarity index 100% rename from token-broker/internal/session/semaphore.go rename to operator/internal/tokenbroker/session/semaphore.go diff --git a/token-broker/internal/session/semaphore_test.go b/operator/internal/tokenbroker/session/semaphore_test.go similarity index 100% rename from token-broker/internal/session/semaphore_test.go rename to operator/internal/tokenbroker/session/semaphore_test.go diff --git a/token-broker/pkg/oauth/pkce.go b/operator/pkg/oauth/pkce.go similarity index 100% rename from token-broker/pkg/oauth/pkce.go rename to operator/pkg/oauth/pkce.go diff --git a/token-broker/pkg/oauth/pkce_test.go b/operator/pkg/oauth/pkce_test.go similarity index 100% rename from token-broker/pkg/oauth/pkce_test.go rename to operator/pkg/oauth/pkce_test.go diff --git a/token-broker/Dockerfile b/token-broker/Dockerfile deleted file mode 100644 index fd6ea413..00000000 --- a/token-broker/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS builder -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /workspace -COPY go.mod go.mod -COPY go.sum go.sum -RUN go mod download - -COPY cmd/ cmd/ -COPY internal/ internal/ -COPY pkg/ pkg/ - -RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o token-broker ./cmd/ - -FROM gcr.io/distroless/static:nonroot -WORKDIR / -COPY --from=builder /workspace/token-broker . -USER 65532:65532 - -ENTRYPOINT ["/token-broker"] diff --git a/token-broker/deploy/00-serviceaccount.yaml b/token-broker/deploy/00-serviceaccount.yaml deleted file mode 100644 index 3da6da8a..00000000 --- a/token-broker/deploy/00-serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# ServiceAccount for the Token Broker. -# -# The Token Broker is a stateless, in-memory service. It does NOT call the -# Kubernetes API, so it needs no Role/RoleBinding — just an identity to run as. ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: token-broker - namespace: rossoctl-system - labels: - app: token-broker diff --git a/token-broker/deploy/01-secret.example.yaml b/token-broker/deploy/01-secret.example.yaml deleted file mode 100644 index 74e04e46..00000000 --- a/token-broker/deploy/01-secret.example.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# OAuth client credentials for the Token Broker. -# -# DO NOT commit real values. This is a TEMPLATE — copy it, fill in the GitHub -# (or other provider) OAuth app credentials, and apply it separately, or wire -# these keys to an external secret store (e.g. ExternalSecrets / Vault). -# -# Create directly without committing a file: -# kubectl create secret generic github-oauth-credentials -n rossoctl-system \ -# --from-literal=client-id= \ -# --from-literal=client-secret= ---- -apiVersion: v1 -kind: Secret -metadata: - name: github-oauth-credentials - namespace: rossoctl-system - labels: - app: token-broker -type: Opaque -stringData: - client-id: "REPLACE_WITH_OAUTH_CLIENT_ID" - client-secret: "REPLACE_WITH_OAUTH_CLIENT_SECRET" diff --git a/token-broker/deploy/02-deployment.yaml b/token-broker/deploy/02-deployment.yaml deleted file mode 100644 index ea767f01..00000000 --- a/token-broker/deploy/02-deployment.yaml +++ /dev/null @@ -1,115 +0,0 @@ -# Token Broker Deployment. -# -# Stateless service that brokers OAuth flows and caches tokens in memory. -# Placeholders to set per environment: -# - image: the tag your build script produces -# - OAUTH_CALLBACK_URL: the externally-reachable URL routed by 04-httproute.yaml; -# its host MUST match the HTTPRoute hostname. ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: token-broker - namespace: rossoctl-system - labels: - app: token-broker -spec: - replicas: 1 # In-memory sessions/token cache: do NOT scale >1 without shared state. - selector: - matchLabels: - app: token-broker - template: - metadata: - labels: - app: token-broker - annotations: - # Broker terminates its own OAuth flows; no mesh sidecar required. - sidecar.istio.io/inject: "false" - spec: - serviceAccountName: token-broker - securityContext: - runAsNonRoot: true - containers: - - name: token-broker - image: localhost/token-broker:latest # REPLACE with the tag your build script produces. - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 8190 - protocol: TCP - env: - - name: TOKEN_BROKER_PORT - value: "8190" - - name: OAUTH_CLIENT_ID - valueFrom: - secretKeyRef: - name: github-oauth-credentials - key: client-id - - name: OAUTH_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: github-oauth-credentials - key: client-secret - # Externally-reachable callback URL; host must match the HTTPRoute hostname. - - name: OAUTH_CALLBACK_URL - value: "http://token-broker.localtest.me:8080/oauth/callback" - # Permitted hostnames for backend_session_redirect_url. - # Comma-separated. Set to the hostname(s) of your backend UI. - - name: ALLOWED_REDIRECT_HOSTS - value: "app-demo.localtest.me" - # JWT validation (required for production — leave unset for dev/test only). - # JWT_JWKS_URL: JWKS endpoint of your identity provider. - # JWT_ISSUER: Expected issuer claim in incoming JWTs. - # JWT_AUDIENCE: Comma-separated expected audience(s). - # - name: JWT_JWKS_URL - # value: "https://keycloak.example.com/realms/rossoctl/protocol/openid-connect/certs" - # - name: JWT_ISSUER - # value: "https://keycloak.example.com/realms/rossoctl" - # - name: JWT_AUDIENCE - # value: "token-broker" - # Optional tunables (defaults in cmd/token-broker/main.go): - # - name: TOKEN_BROKER_SESSION_TIMEOUT - # value: "60s" - # - name: TOKEN_BROKER_MAX_SESSIONS_PER_USER - # value: "5" - # - name: TOKEN_BROKER_TOKEN_WAIT_TIMEOUT - # value: "300s" - # Optional: skip OAuth discovery by configuring endpoints directly. - # - name: OAUTH_AUTHORIZATION_ENDPOINT - # value: "https://github.com/login/oauth/authorize" - # - name: OAUTH_TOKEN_ENDPOINT - # value: "https://github.com/login/oauth/access_token" - # - name: OAUTH_SCOPES_SUPPORTED - # value: "repo,read:org,read:user" - # Per-server OAuth config (scopes + endpoint overrides). JSON format. - # Each key is the resource server URL (must match X-Server-Url exactly). - # Fields: scopes ([]string), authorization_endpoint (string), token_endpoint (string). - - name: RESOURCE_CONFIG - value: >- - { - "http://mcp-server-service.rossoctl-demo.svc.cluster.local:8184": {"scopes": ["read:user", "user:email"]} - } - livenessProbe: - httpGet: - path: /healthz - port: http - initialDelaySeconds: 5 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /readyz - port: http - initialDelaySeconds: 2 - periodSeconds: 5 - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - memory: 256Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: - drop: - - ALL diff --git a/token-broker/deploy/03-service.yaml b/token-broker/deploy/03-service.yaml deleted file mode 100644 index 9732a5c4..00000000 --- a/token-broker/deploy/03-service.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Token Broker Service (in-cluster). -# -# Consumed by AuthBridge and the Backend at: -# http://token-broker.rossoctl-system.svc.cluster.local:8190 ---- -apiVersion: v1 -kind: Service -metadata: - name: token-broker - namespace: rossoctl-system - labels: - app: token-broker -spec: - type: ClusterIP - selector: - app: token-broker - ports: - - name: http - port: 8190 - targetPort: http - protocol: TCP diff --git a/token-broker/deploy/04-httproute.yaml b/token-broker/deploy/04-httproute.yaml deleted file mode 100644 index 91764c3f..00000000 --- a/token-broker/deploy/04-httproute.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# HTTPRoute exposing the Token Broker OAuth callback through the shared gateway. -# -# The OAuth provider (e.g. GitHub) redirects the user's browser to -# OAUTH_CALLBACK_URL after authorization. That URL must resolve to GET -# /oauth/callback on the broker — otherwise the redirect 404s and the broker -# waits for a callback that never arrives. -# -# This route lives in rossoctl-system (same namespace as the Service AND the -# gateway), so: -# - the parentRef is local (gateway "http" in rossoctl-system), and -# - the backendRef is same-namespace, so NO ReferenceGrant is needed. -# -# The rossoctl-system namespace carries the label shared-gateway-access: "true", -# which the "http" gateway's listener selects — same as the working app-demo route. -# -# The hostname MUST match the host in the Deployment's OAUTH_CALLBACK_URL. ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: token-broker-oauth-callback - namespace: rossoctl-system - labels: - app: token-broker -spec: - parentRefs: - - name: http - namespace: rossoctl-system - hostnames: - - "token-broker.localtest.me" - rules: - - matches: - - path: - type: PathPrefix - value: /oauth/callback - backendRefs: - - name: token-broker - port: 8190 diff --git a/token-broker/deploy/README.md b/token-broker/deploy/README.md deleted file mode 100644 index c2f4770d..00000000 --- a/token-broker/deploy/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Token Broker — Kubernetes deployment - -Manifests that accompany the `token-broker` image produced by the build script. -All objects live in the `rossoctl-system` namespace. - -## Manifests (apply in order) - -| File | Object | Notes | -|------|--------|-------| -| `00-serviceaccount.yaml` | ServiceAccount | No RBAC — the broker does not call the Kubernetes API. | -| `01-secret.example.yaml` | Secret (template) | OAuth client credentials. **Do not commit real values** — see below. | -| `02-deployment.yaml` | Deployment | Set `image:` and `OAUTH_CALLBACK_URL`. `replicas: 1` (in-memory state). | -| `03-service.yaml` | Service (ClusterIP) | `token-broker.rossoctl-system.svc.cluster.local:8190`. | -| `04-httproute.yaml` | HTTPRoute | Exposes `GET /oauth/callback` via the shared `http` gateway. | - -## Credentials - -Do not apply `01-secret.example.yaml` as-is. Create the secret out of band: - -```sh -kubectl create secret generic github-oauth-credentials -n rossoctl-system \ - --from-literal=client-id= \ - --from-literal=client-secret= -``` - -## Apply - -```sh -kubectl apply -f 00-serviceaccount.yaml -# create the secret (see above) instead of applying the example -kubectl apply -f 02-deployment.yaml -f 03-service.yaml -f 04-httproute.yaml -``` - -## The callback route must match - -`OAUTH_CALLBACK_URL` in the Deployment and the `hostnames:` in the HTTPRoute -must use the **same host**. The OAuth provider redirects the browser to that -URL; the gateway routes the host + `/oauth/callback` to the broker. A mismatch -(or a missing/unattached route) makes the post-consent redirect 404, and the -broker then blocks waiting for a callback that never arrives. - -The route attaches to the gateway `http` in `rossoctl-system`. That listener -admits routes from namespaces labelled `shared-gateway-access: "true"`, which -`rossoctl-system` already carries. - -## Migrating from the old ad-hoc objects - -Earlier deployments placed the HTTPRoute in `rossoctl-demo` with a parentRef to a -non-existent `rossoctl-gateway`/`istio-system`, which never attached (404 on the -callback). The token broker does not deploy into `rossoctl-demo`. Remove the -stale objects: - -```sh -kubectl delete httproute token-broker-oauth-callback -n rossoctl-demo --ignore-not-found -kubectl delete referencegrant allow-demo-httproute-to-token-broker -n rossoctl-system --ignore-not-found -``` diff --git a/token-broker/go.mod b/token-broker/go.mod deleted file mode 100644 index 471a6e2b..00000000 --- a/token-broker/go.mod +++ /dev/null @@ -1,27 +0,0 @@ -module github.com/rossoctl/token-broker - -go 1.26.5 - -require ( - github.com/go-chi/chi/v5 v5.3.0 - github.com/google/uuid v1.6.0 - github.com/rossoctl/cortex/authbridge/authlib v0.0.0-20260915135208-2d373605612d - github.com/stretchr/testify v1.11.1 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect - github.com/goccy/go-json v0.10.6 // indirect - github.com/lestrrat-go/blackmagic v1.0.4 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.6 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx/v2 v2.1.7 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/segmentio/asm v1.2.1 // indirect - golang.org/x/crypto v0.57.0 // indirect - golang.org/x/sys v0.48.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/token-broker/go.sum b/token-broker/go.sum deleted file mode 100644 index 3f3f5c72..00000000 --- a/token-broker/go.sum +++ /dev/null @@ -1,43 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= -github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= -github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= -github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= -github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= -github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.7 h1:bnYeET+S8IOyAw6W4LTc6SEeK7Xs58SKKZkR7scb3Ko= -github.com/lestrrat-go/jwx/v2 v2.1.7/go.mod h1:exQ9ZBuN1cMLYmxwhTlHUru08ykONG0z+HbLEeDG9qo= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rossoctl/cortex/authbridge/authlib v0.0.0-20260915135208-2d373605612d h1:MPmOOxFbqWLyfNCrycCG7PwvHmZiZkddj7K9pENUkXw= -github.com/rossoctl/cortex/authbridge/authlib v0.0.0-20260915135208-2d373605612d/go.mod h1:6d/6KaVHxNDmNz6vPWGQXPv3MhsJsV7f0c6D/E+y4h8= -github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= -github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= -golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=