mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-13 08:05:19 +09:00
feat(observability-proxy): add the chart and image build
Tenants read their logs, and later their metrics, through an authorizing proxy that sits in front of the VictoriaLogs and VictoriaMetrics read paths, so the proxy needs a deployable package before the console or tenant Grafana can reach it. The proxy reviews every token and access decision against the API server, so its own role is limited to creating TokenReviews and SubjectAccessReviews and reading ApplicationDefinitions and namespaces. The namespace logs root is authorized as list on cozystack.io logs, which no API server serves; the rule is aggregated into the tenant view role so that a namespace viewer passes that review, as upstream grants pods/log at view. The image ref lands without a digest until the first build stamps it, and the package joins the root build list so every build path refreshes it. Assisted-by: LLM Signed-off-by: ZverGuy <maximbel2003@gmail.com>
This commit is contained in:
1 parent
ca7f283b6a
commit
7ec648d863
13 files changed
+408
No files matched your search
@@ -17,6 +17,7 @@ build: build-deps
|
||||
make -C packages/system/cozystack-api image
|
||||
make -C packages/system/cozystack-controller image
|
||||
make -C packages/system/backup-controller image
|
||||
make -C packages/system/observability-proxy image
|
||||
make -C packages/system/backupstrategy-controller image
|
||||
make -C packages/system/lineage-controller-webhook image
|
||||
make -C packages/system/migration-controller image
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
apiVersion: v2
|
||||
name: cozy-observability-proxy
|
||||
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
|
||||
@@ -0,0 +1,20 @@
|
||||
NAME=observability-proxy
|
||||
NAMESPACE=cozy-observability-proxy
|
||||
|
||||
include ../../../hack/common-envs.mk
|
||||
include ../../../hack/package.mk
|
||||
|
||||
test:
|
||||
helm unittest .
|
||||
|
||||
image: image-observability-proxy
|
||||
|
||||
image-observability-proxy:
|
||||
docker buildx build -f images/observability-proxy/Dockerfile ../../.. \
|
||||
$(call image-tags,observability-proxy,v$(COZYSTACK_VERSION)) \
|
||||
$(call cache-args,observability-proxy) \
|
||||
--metadata-file images/observability-proxy.json \
|
||||
$(BUILDX_ARGS)
|
||||
IMAGE="$(REGISTRY)/observability-proxy:$(IMAGE_TAG)@$$(yq e '."containerimage.digest"' images/observability-proxy.json -o json -r)" \
|
||||
yq -i '.observabilityProxy.image = strenv(IMAGE)' values.yaml
|
||||
rm -f images/observability-proxy.json
|
||||
@@ -0,0 +1,25 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.27-alpine@sha256:8a5910f31396cd4d89662f56c68b3ae31d374308270a1c3bd96672ee5ed43414 AS builder
|
||||
ARG GOPROXY
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN for attempt in 1 2 3; do GOOS=$TARGETOS GOARCH=$TARGETARCH go mod download && break; [ "$attempt" = 3 ] && exit 1; sleep $((attempt * 10)); done
|
||||
|
||||
COPY api api/
|
||||
COPY pkg pkg/
|
||||
COPY cmd cmd/
|
||||
COPY internal internal/
|
||||
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags="-extldflags=-static" -o /observability-proxy ./cmd/observability-proxy
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /observability-proxy /observability-proxy
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
USER 65532:65532
|
||||
ENTRYPOINT ["/observability-proxy"]
|
||||
@@ -0,0 +1,68 @@
|
||||
{{- $clusterDomain := index (.Values._cluster | default dict) "cluster-domain" | default "cozy.local" }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: observability-proxy
|
||||
labels:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
spec:
|
||||
replicas: {{ .Values.observabilityProxy.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
spec:
|
||||
serviceAccountName: observability-proxy
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: observability-proxy
|
||||
image: {{ .Values.observabilityProxy.image | quote }}
|
||||
args:
|
||||
- --listen-address=:8080
|
||||
- --probe-address=:8081
|
||||
- --cluster-domain={{ $clusterDomain }}
|
||||
- --root-tenant={{ .Values.observabilityProxy.rootTenant }}
|
||||
- --auth-cache-ttl={{ .Values.observabilityProxy.authCacheTTL }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: probes
|
||||
containerPort: 8081
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: probes
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: probes
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
{{- with .Values.observabilityProxy.resources }}
|
||||
resources: {{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,33 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: cozy:observability-proxy
|
||||
rules:
|
||||
# Every request is authenticated and authorized against the API server.
|
||||
- apiGroups: ["authentication.k8s.io"]
|
||||
resources: ["tokenreviews"]
|
||||
verbs: ["create"]
|
||||
- apiGroups: ["authorization.k8s.io"]
|
||||
resources: ["subjectaccessreviews"]
|
||||
verbs: ["create"]
|
||||
# Maps an application plural in the path to its Kind.
|
||||
- apiGroups: ["cozystack.io"]
|
||||
resources: ["applicationdefinitions"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
# The namespace.cozystack.io/monitoring label names the logs store.
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: cozy:observability-proxy
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cozy:observability-proxy
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: observability-proxy
|
||||
namespace: {{ .Release.Namespace }}
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: observability-proxy
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: observability-proxy
|
||||
labels:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
selector:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
@@ -0,0 +1,14 @@
|
||||
# Aggregated into cozy-tenant-view (and consequently use, admin, super-admin).
|
||||
# No API server serves cozystack.io logs: the rule exists only for the
|
||||
# SubjectAccessReview the observability proxy asks before it answers a read
|
||||
# under /apis/cozystack.io/v1alpha1/namespaces/<ns>/logs/.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: cozy:observability:view
|
||||
labels:
|
||||
rbac.cozystack.io/aggregate-to-tenant-view: "true"
|
||||
rules:
|
||||
- apiGroups: ["cozystack.io"]
|
||||
resources: ["logs"]
|
||||
verbs: ["list"]
|
||||
@@ -0,0 +1,109 @@
|
||||
suite: observability-proxy Deployment
|
||||
# The flags, ports and pod label below are the contract the proxy binary, the
|
||||
# Service, the console nginx and the tenant egress policy are written against.
|
||||
# A rename on either side breaks routing without failing a render.
|
||||
templates:
|
||||
- templates/deployment.yaml
|
||||
|
||||
release:
|
||||
name: observability-proxy
|
||||
namespace: cozy-observability-proxy
|
||||
|
||||
tests:
|
||||
- it: labels the pods with the name the tenant egress policy selects on
|
||||
asserts:
|
||||
- isKind:
|
||||
of: Deployment
|
||||
- equal:
|
||||
path: spec.selector.matchLabels
|
||||
value:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
- equal:
|
||||
path: spec.template.metadata.labels["app.kubernetes.io/name"]
|
||||
value: observability-proxy
|
||||
- equal:
|
||||
path: spec.template.spec.serviceAccountName
|
||||
value: observability-proxy
|
||||
|
||||
- it: passes the listen, probe, root tenant and auth cache flags
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --listen-address=:8080
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --probe-address=:8081
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --root-tenant=tenant-root
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --auth-cache-ttl=10s
|
||||
|
||||
- it: falls back to cozy.local when the platform passes no cluster domain
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --cluster-domain=cozy.local
|
||||
|
||||
- it: passes the platform cluster domain
|
||||
set:
|
||||
_cluster:
|
||||
cluster-domain: cluster.local
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --cluster-domain=cluster.local
|
||||
- notContains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --cluster-domain=cozy.local
|
||||
|
||||
- it: exposes the API and probe ports
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.containers[0].ports
|
||||
value:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: probes
|
||||
containerPort: 8081
|
||||
protocol: TCP
|
||||
|
||||
- it: probes /healthz and /readyz on the probe port
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.containers[0].livenessProbe.httpGet
|
||||
value:
|
||||
path: /healthz
|
||||
port: probes
|
||||
- equal:
|
||||
path: spec.template.spec.containers[0].readinessProbe.httpGet
|
||||
value:
|
||||
path: /readyz
|
||||
port: probes
|
||||
|
||||
- it: runs from the image in values
|
||||
set:
|
||||
observabilityProxy.image: registry.example/observability-proxy:dev@sha256:0000
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.containers[0].image
|
||||
value: registry.example/observability-proxy:dev@sha256:0000
|
||||
|
||||
- it: satisfies the restricted pod security profile
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.securityContext
|
||||
value:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
- equal:
|
||||
path: spec.template.spec.containers[0].securityContext
|
||||
value:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,70 @@
|
||||
suite: observability-proxy RBAC
|
||||
# The proxy authenticates and authorizes every request through the API server,
|
||||
# so a dropped verb here turns every request into an error at runtime rather
|
||||
# than a render failure. The tenant role is what lets a namespace viewer pass
|
||||
# the SubjectAccessReview the proxy asks for the namespace logs root.
|
||||
templates:
|
||||
- templates/rbac.yaml
|
||||
- templates/tenant-clusterroles.yaml
|
||||
|
||||
release:
|
||||
name: observability-proxy
|
||||
namespace: cozy-observability-proxy
|
||||
|
||||
tests:
|
||||
- it: lets the proxy review tokens and access, and read ApplicationDefinitions and namespaces
|
||||
template: templates/rbac.yaml
|
||||
documentSelector:
|
||||
path: kind
|
||||
value: ClusterRole
|
||||
asserts:
|
||||
- equal:
|
||||
path: rules
|
||||
value:
|
||||
- apiGroups: ["authentication.k8s.io"]
|
||||
resources: ["tokenreviews"]
|
||||
verbs: ["create"]
|
||||
- apiGroups: ["authorization.k8s.io"]
|
||||
resources: ["subjectaccessreviews"]
|
||||
verbs: ["create"]
|
||||
- apiGroups: ["cozystack.io"]
|
||||
resources: ["applicationdefinitions"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
||||
- it: binds the proxy role to the proxy ServiceAccount in the release namespace
|
||||
template: templates/rbac.yaml
|
||||
documentSelector:
|
||||
path: kind
|
||||
value: ClusterRoleBinding
|
||||
asserts:
|
||||
- equal:
|
||||
path: roleRef
|
||||
value:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cozy:observability-proxy
|
||||
- equal:
|
||||
path: subjects
|
||||
value:
|
||||
- kind: ServiceAccount
|
||||
name: observability-proxy
|
||||
namespace: cozy-observability-proxy
|
||||
|
||||
- it: aggregates list on cozystack.io logs into the tenant view role
|
||||
template: templates/tenant-clusterroles.yaml
|
||||
asserts:
|
||||
- isKind:
|
||||
of: ClusterRole
|
||||
- equal:
|
||||
path: metadata.labels
|
||||
value:
|
||||
rbac.cozystack.io/aggregate-to-tenant-view: "true"
|
||||
- equal:
|
||||
path: rules
|
||||
value:
|
||||
- apiGroups: ["cozystack.io"]
|
||||
resources: ["logs"]
|
||||
verbs: ["list"]
|
||||
@@ -0,0 +1,30 @@
|
||||
suite: observability-proxy Service
|
||||
# The console nginx proxies to observability-proxy.<ns>.svc on port 80 and the
|
||||
# tenant egress policy allows the pods' 8080, so the name, the port and the
|
||||
# selector are pinned here.
|
||||
templates:
|
||||
- templates/service.yaml
|
||||
|
||||
release:
|
||||
name: observability-proxy
|
||||
namespace: cozy-observability-proxy
|
||||
|
||||
tests:
|
||||
- it: serves port 80 named http on the pods' http port
|
||||
asserts:
|
||||
- isKind:
|
||||
of: Service
|
||||
- equal:
|
||||
path: metadata.name
|
||||
value: observability-proxy
|
||||
- equal:
|
||||
path: spec.ports
|
||||
value:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
- equal:
|
||||
path: spec.selector
|
||||
value:
|
||||
app.kubernetes.io/name: observability-proxy
|
||||
@@ -0,0 +1,16 @@
|
||||
# Injected by the platform from the cozystack-values Secret. The empty
|
||||
# default keeps the chart renderable on its own, outside a cluster.
|
||||
_cluster: {}
|
||||
|
||||
observabilityProxy:
|
||||
image: "ghcr.io/cozystack/cozystack/observability-proxy:v0.0.0"
|
||||
replicas: 2
|
||||
rootTenant: tenant-root
|
||||
authCacheTTL: 10s
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
Reference in new issue
Block a user