diff --git a/add-ons/authentik/blueprint-outpost.yaml b/add-ons/authentik/blueprint-outpost.yaml index 2b4fe8e..0ddc35b 100644 --- a/add-ons/authentik/blueprint-outpost.yaml +++ b/add-ons/authentik/blueprint-outpost.yaml @@ -14,6 +14,11 @@ data: identifiers: path: mounted/cm-blueprint-garage-ui/garage-ui.yaml required: true + - model: authentik_blueprints.metaapplyblueprint + attrs: + identifiers: + path: mounted/cm-blueprint-pgweb/pgweb.yaml + required: true - model: authentik_blueprints.metaapplyblueprint attrs: identifiers: @@ -26,4 +31,5 @@ data: attrs: providers: - !Find [authentik_providers_proxy.proxyprovider, [name, garage-ui]] + - !Find [authentik_providers_proxy.proxyprovider, [name, pgweb]] - !Find [authentik_providers_proxy.proxyprovider, [name, qbittorrent]] diff --git a/add-ons/authentik/blueprint-pgweb.yaml b/add-ons/authentik/blueprint-pgweb.yaml new file mode 100644 index 0000000..541e8da --- /dev/null +++ b/add-ons/authentik/blueprint-pgweb.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: blueprint-pgweb + namespace: authentik +data: + pgweb.yaml: | + version: 1 + metadata: + name: pgweb + entries: + - model: authentik_providers_proxy.proxyprovider + id: pgweb-provider + identifiers: + name: pgweb + attrs: + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + mode: forward_single + external_host: https://pgweb.whyredfire.dev + - model: authentik_core.application + id: pgweb + identifiers: + slug: pgweb + attrs: + name: pgweb + provider: !KeyOf pgweb-provider + - model: authentik_policies.policybinding + identifiers: + order: 0 + target: !KeyOf pgweb + attrs: + group: !Find [authentik_core.group, [name, admins]] diff --git a/add-ons/authentik/kustomization.yaml b/add-ons/authentik/kustomization.yaml index 815a35d..2efeede 100644 --- a/add-ons/authentik/kustomization.yaml +++ b/add-ons/authentik/kustomization.yaml @@ -7,6 +7,7 @@ resources: - blueprint-google-login.yaml - blueprint-grafana.yaml - blueprint-outpost.yaml + - blueprint-pgweb.yaml - blueprint-qbittorrent.yaml - blueprint-wireguard-admin.yaml - httproute.yaml diff --git a/add-ons/authentik/referencegrant.yaml b/add-ons/authentik/referencegrant.yaml index bf1d8a0..a3cf8fb 100644 --- a/add-ons/authentik/referencegrant.yaml +++ b/add-ons/authentik/referencegrant.yaml @@ -8,6 +8,9 @@ spec: - group: gateway.networking.k8s.io kind: HTTPRoute namespace: garage + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: pgweb - group: gateway.networking.k8s.io kind: HTTPRoute namespace: qbit diff --git a/add-ons/postgres/cluster.yaml b/add-ons/postgres/cluster.yaml index 0e5eeee..bfb2b4c 100644 --- a/add-ons/postgres/cluster.yaml +++ b/add-ons/postgres/cluster.yaml @@ -24,6 +24,13 @@ spec: name: legislative passwordSecret: name: legislative-db + - ensure: present + inRoles: + - pg_read_all_data + login: true + name: pgweb + passwordSecret: + name: pgweb-db - ensure: present login: true name: redoost @@ -31,8 +38,12 @@ spec: name: redoost-db postgresql: # Each app role may only connect to the database of the same name. + # pgweb is read-only and may browse every database except authentik's, + # which holds credentials and session tokens. pg_hba: - host sameuser all all scram-sha-256 + - host authentik pgweb all reject + - host all pgweb all scram-sha-256 - host all all all reject resources: limits: diff --git a/add-ons/postgres/kustomization.yaml b/add-ons/postgres/kustomization.yaml index f603feb..f6ec29c 100644 --- a/add-ons/postgres/kustomization.yaml +++ b/add-ons/postgres/kustomization.yaml @@ -8,5 +8,6 @@ resources: - grafana-database.yaml - legislative-db-sealedsecret.yaml - legislative-database.yaml + - pgweb-db-sealedsecret.yaml - redoost-db-sealedsecret.yaml - redoost-database.yaml diff --git a/add-ons/postgres/pgweb-db-sealedsecret.yaml b/add-ons/postgres/pgweb-db-sealedsecret.yaml new file mode 100644 index 0000000..e0fc450 --- /dev/null +++ b/add-ons/postgres/pgweb-db-sealedsecret.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: pgweb-db + namespace: postgres +spec: + encryptedData: + password: AgBOEIc6OuRugdd6OZGVRCFItD5XUaHxrRSfh4VDc5JION8/p+5KBlXd8wtHzNyGXCxHIFnenkGxWEZKWiaMAQS9v/At6tMJFRQYYwRHzFPx1u16H3lzmnE5+GgUhmoIayiRkhLA4Qs9aub/+mSzIW3sPo5RykLzhGZbnVbF0y7jQ39X01Sb6UFVJBGBXFjHuBkM9B5Iep01EgCM4a7SpWVgn0+kPzopfABbjJKAQM1ah0DMaXTFMCkKm2N0/e5KyGw1FNWC+Aor8mVkm6irCz9O2rFBJ3b/jY44cEoJ7zVIAfGN8y1HxTGOrVuBb7LkfrYTxIVVAT2CrxUaEdutAlGXgWyXmJ+wMgiQ4qds5rD5+T5ePdErCAiY74Jb8LWuyuZXG0uI/lGL0QTECcNiXJ/dvX8PNuq+L+Y6IZTK+WgxBvJXIgsfs2a3QyT7vIaB/k8QTQ7uF2KBBV1ol/cXhW45Dewu8yWz9Sjhb2roK1yNHvCiZaS79sUo9VPjEhGKZ3TcyWvi+eqWMFsLLxmXCz4wefU1XO83aLg8oPqZkfbo1IhcUC6oonv1dLX/jTy8RKudCPbkrytrlffKoVZ4aLhi+3ehkPY1d2J1oa1Nmmu2Vr5wDEXxtvQwtBJavB80ep5/esv89EYqjYYJglzUm4UBMv18v50E0dpGymem6mx/5yG5Ym+RI1hvE37x2Ed5GO9rX29BAjqob3H69Jd44OVlAuF6/Gz4fwVstPexi9gEbp2m2Mpiz+WOtrww2ofktENCKi211Qr+woc9h4Tq4DGI + username: AgAAdKsgTKEFmQTw0VxAdiocOEaT9WbTurBlaQVpdvneGMry2A6iAmKvrszFT1RaG+Iy0nRtxu1XM8bkpn5QDML0H6AB3O25QDrEOkhBd0feBBKjgaFCAwrcp64Wu3o8q+yEXTZcDJ5Ah3cXwvXDpc3PPMyvkDnIcGTqDKXEplJQY533NrE0iw3hPsBFztavb4QiH6Txlnq+JpPu/F2G++nRCcNh1kuaMG1kQ61IImj4xSdOoYDO7RLbIxA/cRcQYju4UkSliU2uMHTw4pDT7mDqP1GjSN/tvoezk9FjNHIgaBUg2WFk6cBzdLIFY/zafwchkUAeM0AX+kFpeAIhzRmI0uknyH8fuZWHkYxU07PuanE3kNJkhSbCQWQ0TtKTXCtccxgUYHaucskVk3ZC66cVJj+NCRQbV1lvUGaNHEuoF/lGueosdM5saCIChuidbJbJsmDIztf6OBBJM0nyOoQjV6cMwirXxO2Ap+vxb/tI5eLsoUkVORe/nxoiGF7P+529mv2S1KE7PWwd0A82s6A30RoovTmTyxFOTT2p9TmxYIzzzymiz52hRiGI4YBSDirajfGleOmxILDxWwLdiXpBhUNwgjaZ39taumgsZ+wyxxIaD6OxdHKGGI/u+fRyJMdvIITPn3CMbl16+MTwQkrvx/Qw7+52Pm5mp/gW5AQQqJHBSKdSGGosNYMV1Tx9UHHZ12GDnw== + template: + metadata: + labels: + cnpg.io/reload: "true" + name: pgweb-db + namespace: postgres + type: kubernetes.io/basic-auth diff --git a/apps/pgweb/configmap.yaml b/apps/pgweb/configmap.yaml new file mode 100644 index 0000000..aebf9db --- /dev/null +++ b/apps/pgweb/configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: pgweb-bookmarks + namespace: pgweb +data: + postgres.toml: | + host = "postgres-rw.postgres.svc.cluster.local" + user = "pgweb" + passwordvar = "DATABASE_PASSWORD" + database = "postgres" + sslmode = "require" + readonly = true diff --git a/apps/pgweb/deployment.yaml b/apps/pgweb/deployment.yaml new file mode 100644 index 0000000..82a4bb8 --- /dev/null +++ b/apps/pgweb/deployment.yaml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pgweb + namespace: pgweb +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: pgweb + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: pgweb + spec: + containers: + # Only the bookmarked server can be connected to; other databases on + # it are reachable through the database switcher. + - args: + - --bookmark=postgres + - --bookmarks-dir=/bookmarks + - --bookmarks-only + - --no-ssh + - --readonly + - --skip-open + env: + - name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + key: password + name: pgweb + image: docker.io/sosedoff/pgweb:0.17.0 + livenessProbe: + httpGet: + path: / + port: http + name: pgweb + ports: + - containerPort: 8081 + name: http + readinessProbe: + httpGet: + path: / + port: http + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + volumeMounts: + - mountPath: /bookmarks + name: bookmarks + readOnly: true + securityContext: + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + volumes: + - configMap: + name: pgweb-bookmarks + name: bookmarks diff --git a/apps/pgweb/httproute.yaml b/apps/pgweb/httproute.yaml new file mode 100644 index 0000000..31d2e95 --- /dev/null +++ b/apps/pgweb/httproute.yaml @@ -0,0 +1,37 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + annotations: + argocd.argoproj.io/sync-wave: "2" + name: pgweb + namespace: pgweb +spec: + hostnames: + - pgweb.whyredfire.dev + parentRefs: + - kind: Gateway + name: traefik-gateway + namespace: traefik + sectionName: websecure + rules: + - backendRefs: + - name: authentik-server + namespace: authentik + port: 80 + matches: + - path: + type: PathPrefix + value: /outpost.goauthentik.io + - backendRefs: + - name: pgweb + port: 8081 + filters: + - extensionRef: + group: traefik.io + kind: Middleware + name: authentik + type: ExtensionRef + matches: + - path: + type: PathPrefix + value: / diff --git a/apps/pgweb/kustomization.yaml b/apps/pgweb/kustomization.yaml new file mode 100644 index 0000000..d83b716 --- /dev/null +++ b/apps/pgweb/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - sealed-secret.yaml + - configmap.yaml + - deployment.yaml + - service.yaml + - networkpolicy.yaml + - middleware.yaml + - httproute.yaml diff --git a/apps/pgweb/middleware.yaml b/apps/pgweb/middleware.yaml new file mode 100644 index 0000000..f827b45 --- /dev/null +++ b/apps/pgweb/middleware.yaml @@ -0,0 +1,17 @@ +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + annotations: + argocd.argoproj.io/sync-wave: "1" + name: authentik + namespace: pgweb +spec: + forwardAuth: + address: http://authentik-server.authentik.svc.cluster.local/outpost.goauthentik.io/auth/traefik + authResponseHeaders: + - X-authentik-username + - X-authentik-groups + - X-authentik-email + - X-authentik-name + - X-authentik-uid + trustForwardHeader: true diff --git a/apps/pgweb/networkpolicy.yaml b/apps/pgweb/networkpolicy.yaml new file mode 100644 index 0000000..a1e47cd --- /dev/null +++ b/apps/pgweb/networkpolicy.yaml @@ -0,0 +1,45 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: pgweb + namespace: pgweb +spec: + # Only traefik may reach pgweb, so authentik can't be bypassed from inside + # the cluster, and pgweb may only reach postgres. + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + - ports: + - port: 5432 + protocol: TCP + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: postgres + podSelector: + matchLabels: + cnpg.io/cluster: postgres + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik + ports: + - port: 8081 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/name: pgweb + policyTypes: + - Ingress + - Egress diff --git a/apps/pgweb/sealed-secret.yaml b/apps/pgweb/sealed-secret.yaml new file mode 100644 index 0000000..f9bb294 --- /dev/null +++ b/apps/pgweb/sealed-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: pgweb + namespace: pgweb +spec: + encryptedData: + password: AgAZE1EB8YzFvC/qgXm8Mvb9Cy2eRubqST3Hxc42LB6/xGW6ZGj9fVYIw4WcVGTOYKXGfEB4JyP+hidluf20kynufYynHXJZw9zBR15ZHIEDVPnbtW1Z+iMua9Hd8gRwug75IeM9wFHMI6dcXQk164GsJQa77vB1EBts8bN78iSuQwuk4YcIUsMfhbx9NI+SGvV+Ih3B5XrUonhDnEIW2d4D46P/1SYXxqd2HhJPOdyTEWws0pSV191RltpKpEJAb/iJNHP6w1++O2Ld6eRTTgmHBAtJf8NtlRxpeZQ1SixAhpsspaPx1FbDrvq17a8JtY60Zyn4a4JnSY/rgErNadR0C82NkYsokePGZ8KMT1QI+jvz7M1NKQLee4GG9+khBAILmlstuXoZPMACB/HAt+nxJAOA8fAYJYxwCP7AsOEpCR/osK1zX2jlaIB2pvBOx7k3/aqamCWt8QUNOiDfqd5G7SH1PI5bAjOA4YjOVxm89t01MzTE9tIdSlDGMoAfodZDwyjA+zenBKe/ha4lddKUGMtucHlyL2e+CPHhfWSWSRw/aimcRkTZf4fVf+17tF6lm5byp9kRbAOW4ymMtfZLikQqTWJ6Aec1bMCLYD2SagrXMkVgnpuu6Z6W1U0p2f3ZHU8tuOZ4avCz53EjmLw+8gdcD2MXZGlJVtq1kxXGpK9sKRcyD4i05VbtESgZ2XmOiUNrbVAzmu2QhSn/k5NGZpDKg0uWMT3X7igQWSz8Af9S53mPDv6aJ2z/q65D7+BBAMwuE+gO2V7cyK37G2ze + template: + metadata: + name: pgweb + namespace: pgweb diff --git a/apps/pgweb/service.yaml b/apps/pgweb/service.yaml new file mode 100644 index 0000000..1af5b05 --- /dev/null +++ b/apps/pgweb/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: pgweb + namespace: pgweb +spec: + ports: + - name: http + port: 8081 + targetPort: http + selector: + app.kubernetes.io/name: pgweb diff --git a/argocd-apps/add-ons/authentik.yaml b/argocd-apps/add-ons/authentik.yaml index e3da936..d4fa37b 100644 --- a/argocd-apps/add-ons/authentik.yaml +++ b/argocd-apps/add-ons/authentik.yaml @@ -32,6 +32,7 @@ spec: - blueprint-google-login - blueprint-grafana - blueprint-outpost + - blueprint-pgweb - blueprint-qbittorrent - blueprint-wireguard-admin server: diff --git a/argocd-apps/apps/kustomization.yaml b/argocd-apps/apps/kustomization.yaml index c4964f9..aec7c8e 100644 --- a/argocd-apps/apps/kustomization.yaml +++ b/argocd-apps/apps/kustomization.yaml @@ -7,6 +7,7 @@ resources: - garage-ui.yaml - jellyfin.yaml - legislative-summarization.yaml + - pgweb.yaml - qbit.yaml - redoost.yaml - rustbin.yaml diff --git a/argocd-apps/apps/pgweb.yaml b/argocd-apps/apps/pgweb.yaml new file mode 100644 index 0000000..85b3bde --- /dev/null +++ b/argocd-apps/apps/pgweb.yaml @@ -0,0 +1,22 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + annotations: + argocd.argoproj.io/sync-wave: "3" + name: pgweb + namespace: argocd +spec: + destination: + namespace: pgweb + server: https://kubernetes.default.svc + project: default + source: + path: apps/pgweb + repoURL: https://gitea.whyredfire.dev/whyredfire/gitops.git + targetRevision: main + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true