mirror of
https://gitlab.kube.huskypup.net/Scooby/Homelabv4.git
synced 2026-10-21 12:46:50 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-argocd
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
argocd.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: argocd-oidc
|
||||
entries:
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: argocd-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: ArgoCD
|
||||
attrs:
|
||||
name: ArgoCD
|
||||
client_id: 3791058a7de278045e5ec36be29f7a33d635119b8c786f20c86b185139c28841
|
||||
client_secret: f787416822f5bf4c46fe9170e63d1bf9520bab7703d53341aaaec107c8c4ba253d96c30b70d23a1c0b5a401ae1ae4bfb5db06092d338e29e0b0f04cd29bc0b36
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://argocd.kube.huskypup.net/auth/callback"
|
||||
matching_mode: strict
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]]
|
||||
|
||||
- model: authentik_core.application
|
||||
id: argocd-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: argocd
|
||||
attrs:
|
||||
name: ArgoCD
|
||||
slug: argocd
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf argocd-provider
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
# Ceph Dashboard SAML2 Provider Blueprint for Authentik
|
||||
# Creates a SAML2 provider for Ceph Dashboard native SSO
|
||||
# Ceph expects:
|
||||
# - Audience: https://ceph.kube.huskypup.net/auth/saml2/metadata
|
||||
# - ACS URL: https://ceph.kube.huskypup.net/auth/saml2
|
||||
# - Username attribute: "username"
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-ceph
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
ceph.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: ceph-dashboard-saml
|
||||
entries:
|
||||
# SAML Property Mapping for username attribute (required by Ceph)
|
||||
- model: authentik_providers_saml.samlpropertymapping
|
||||
id: ceph-username-mapping
|
||||
state: present
|
||||
identifiers:
|
||||
managed: goauthentik.io/providers/saml/ceph-username
|
||||
attrs:
|
||||
name: "Ceph Dashboard SAML: Username"
|
||||
saml_name: "username"
|
||||
expression: "return request.user.username"
|
||||
|
||||
# SAML2 Provider for Ceph Dashboard
|
||||
- model: authentik_providers_saml.samlprovider
|
||||
id: ceph-dashboard-saml-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: ceph-dashboard-saml
|
||||
attrs:
|
||||
name: ceph-dashboard-saml
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
acs_url: "https://ceph.kube.huskypup.net/auth/saml2"
|
||||
audience: "https://ceph.kube.huskypup.net/auth/saml2/metadata"
|
||||
sp_binding: post
|
||||
default_name_id_policy: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
||||
signing_kp: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
sign_response: true
|
||||
sign_assertion: true
|
||||
property_mappings:
|
||||
- !KeyOf ceph-username-mapping
|
||||
|
||||
# Application entry
|
||||
- model: authentik_core.application
|
||||
id: ceph-dashboard-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: ceph-dashboard
|
||||
attrs:
|
||||
name: Ceph Dashboard
|
||||
slug: ceph-dashboard
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf ceph-dashboard-saml-provider
|
||||
meta_launch_url: "https://ceph.kube.huskypup.net"
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
# DEPRECATED: Ceph Dashboard OIDC Provider Blueprint
|
||||
# This blueprint was used with oauth2-proxy which has been removed.
|
||||
# Ceph Dashboard auth is now handled by Istio ext_authz + Authentik forward-auth.
|
||||
# This file is NOT mounted in Authentik (not in configMaps list).
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-ceph-oidc
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
ceph-oidc.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: ceph-dashboard-oidc
|
||||
entries:
|
||||
# OAuth2/OIDC Provider for Ceph Dashboard (via oauth2-proxy)
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: ceph-dashboard-oidc-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: ceph-dashboard
|
||||
attrs:
|
||||
name: ceph-dashboard
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
client_type: confidential
|
||||
client_id: ceph-dashboard
|
||||
client_secret: ceph-dashboard-oidc-secret-homelab-2026
|
||||
redirect_uris: |
|
||||
https://ceph.kube.huskypup.net/oauth2/callback
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
access_code_validity: minutes=1
|
||||
access_token_validity: minutes=5
|
||||
refresh_token_validity: days=30
|
||||
include_claims_in_id_token: true
|
||||
sub_mode: user_username
|
||||
issuer_mode: per_provider
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
||||
|
||||
# Application entry - update existing or create new
|
||||
- model: authentik_core.application
|
||||
id: ceph-dashboard-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: ceph-dashboard
|
||||
attrs:
|
||||
name: Ceph Dashboard
|
||||
slug: ceph-dashboard
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf ceph-dashboard-oidc-provider
|
||||
meta_launch_url: "https://ceph.kube.huskypup.net"
|
||||
meta_icon: "https://ceph.io/assets/favicons/favicon.ico"
|
||||
@@ -0,0 +1,53 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-authentik
|
||||
namespace: authentik
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 2
|
||||
|
||||
# Resource limits to prevent OOM
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "25m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "250m"
|
||||
|
||||
# Spread replicas across different nodes
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 5Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
# PostgreSQL configuration for better performance
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "200"
|
||||
shared_buffers: "512MB"
|
||||
effective_cache_size: "1536MB"
|
||||
maintenance_work_mem: "128MB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
wal_buffers: "16MB"
|
||||
default_statistics_target: "100"
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
work_mem: "2621kB"
|
||||
min_wal_size: "1GB"
|
||||
max_wal_size: "4GB"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: app
|
||||
owner: app
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
metadata:
|
||||
name: cnpg-postgres
|
||||
namespace: authentik
|
||||
labels:
|
||||
release: prometheus
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
cnpg.io/cluster: pg-authentik
|
||||
podMetricsEndpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: authentik-cnpg-secret
|
||||
namespace: authentik
|
||||
spec:
|
||||
length: 42
|
||||
digits: 5
|
||||
symbols: 5
|
||||
symbolCharacters: "-_$@"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: authentik-cnpg-secret
|
||||
namespace: authentik
|
||||
spec:
|
||||
# how often to rotate the DB password
|
||||
refreshInterval: "24h"
|
||||
target:
|
||||
# This will merge the generated password into the existing pg-authentik-app secret
|
||||
name: pg-authentik-app
|
||||
creationPolicy: Merge
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
cnpg.io/reload: "true"
|
||||
data:
|
||||
# Override the password field with our ESO-generated password
|
||||
password: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: authentik-cnpg-secret
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
# Forward Auth Proxy Provider Blueprint for Authentik
|
||||
#
|
||||
# Replaces oauth2-proxy with Authentik's built-in forward-auth capability.
|
||||
# Istio's ext_authz filter calls Authentik's /outpost.goauthentik.io/auth/nginx
|
||||
# endpoint to authenticate requests to protected services.
|
||||
#
|
||||
# Services protected by this provider:
|
||||
# - Prometheus, Ceph Dashboard, TeslaMate, ESPHome, Frigate, Kiali, Nessus, Netbird
|
||||
#
|
||||
# Services with native OIDC are NOT included here (they handle auth themselves):
|
||||
# - GitLab, ArgoCD, Grafana, n8n, Home Assistant, Rancher, Nextcloud
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-forward-auth
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
forward-auth.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: forward-auth-provider
|
||||
entries:
|
||||
# ================================================================
|
||||
# Proxy Provider - Forward Auth (domain mode) for Istio ext_authz
|
||||
# ================================================================
|
||||
# forward_domain mode handles ALL subdomains of cookie_domain.
|
||||
# The embedded outpost matches any *.kube.huskypup.net request.
|
||||
# Auth session is shared across subdomains via a single cookie.
|
||||
- model: authentik_providers_proxy.proxyprovider
|
||||
id: forward-auth-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Forward Auth (Istio)
|
||||
attrs:
|
||||
name: Forward Auth (Istio)
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
mode: forward_domain
|
||||
external_host: "https://auth.kube.huskypup.net"
|
||||
cookie_domain: "kube.huskypup.net"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
|
||||
# ================================================================
|
||||
# Application - Umbrella app for all forward-auth protected services
|
||||
# ================================================================
|
||||
- model: authentik_core.application
|
||||
id: forward-auth-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: forward-auth
|
||||
attrs:
|
||||
name: Protected Services (Forward Auth)
|
||||
slug: forward-auth
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf forward-auth-provider
|
||||
|
||||
# ================================================================
|
||||
# Outpost - Embedded outpost for forward auth
|
||||
# Uses Authentik's built-in outpost (no separate deployment needed)
|
||||
# ================================================================
|
||||
- model: authentik_outposts.outpost
|
||||
id: forward-auth-outpost
|
||||
state: present
|
||||
identifiers:
|
||||
name: "authentik Embedded Outpost"
|
||||
attrs:
|
||||
type: proxy
|
||||
providers:
|
||||
- !KeyOf forward-auth-provider
|
||||
config:
|
||||
authentik_host: "https://auth.kube.huskypup.net/"
|
||||
authentik_host_insecure: true
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-gitlab
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
gitlab.yaml: |-
|
||||
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
|
||||
version: 1
|
||||
metadata:
|
||||
name: gitlab-oidc
|
||||
entries:
|
||||
# OAuth2/OIDC Provider for GitLab
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: gitlab-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: GitLab
|
||||
attrs:
|
||||
name: GitLab
|
||||
client_id: 70b9da5c8166cea0ba504a869824020bce6636
|
||||
client_secret: 2e9c0c5b7a8969eb782d08bf1bf3d50ad96f8a73d9c18d1e4141816aebe14a622e8064bf5a5bce835d80e89cdc5f4cc6cf4a8c8234556647dc8884888866bd7d
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://gitlab.kube.huskypup.net/users/auth/openid_connect/callback"
|
||||
matching_mode: strict
|
||||
enabled: true
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "days=7"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, default]]
|
||||
|
||||
# Application for GitLab
|
||||
- model: authentik_core.application
|
||||
id: gitlab-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: gitlab
|
||||
attrs:
|
||||
name: GitLab
|
||||
slug: gitlab
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf gitlab-provider
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-gitlab-saml
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
gitlab-saml.yaml: |-
|
||||
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
|
||||
version: 1
|
||||
metadata:
|
||||
name: gitlab-saml
|
||||
entries:
|
||||
# SAML Provider for GitLab
|
||||
- model: authentik_providers_saml.samlprovider
|
||||
id: gitlab-saml-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: GitLab SAML
|
||||
attrs:
|
||||
name: GitLab SAML
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
|
||||
# Redirect URI from the GitLab SAML docs
|
||||
# https://gitlab.kube.huskypup.net/users/auth/saml/callback
|
||||
acs_url: "https://gitlab.kube.huskypup.net/users/auth/saml/callback"
|
||||
audience: "https://gitlab.kube.huskypup.net"
|
||||
issuer: "https://gitlab.kube.huskypup.net"
|
||||
|
||||
# SAML-specific settings
|
||||
service_provider_slo_url: "https://gitlab.kube.huskypup.net/users/auth/saml/slo"
|
||||
digest_algorithm: "http://www.w3.org/2001/04/xmlenc#sha256"
|
||||
signature_algorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
||||
require_signing: true
|
||||
|
||||
# Property mappings for SAML attributes
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_saml.samlpropertymapping, [managed, goauthentik.io/providers/saml/name]]
|
||||
- !Find [authentik_providers_saml.samlpropertymapping, [managed, goauthentik.io/providers/saml/email]]
|
||||
- !Find [authentik_providers_saml.samlpropertymapping, [managed, goauthentik.io/providers/saml/username]]
|
||||
|
||||
# Signing certificate
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, default]]
|
||||
|
||||
# Application for GitLab SAML
|
||||
- model: authentik_core.application
|
||||
id: gitlab-saml-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: gitlab-saml
|
||||
attrs:
|
||||
name: GitLab SAML
|
||||
slug: gitlab-saml
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf gitlab-saml-provider
|
||||
launch_url: "https://gitlab.kube.huskypup.net"
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-grafana
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
grafana.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: grafana-oidc
|
||||
entries:
|
||||
# ============================================================================
|
||||
# AUTO-CREATE GRAFANA GROUPS
|
||||
# ============================================================================
|
||||
# These groups control access levels in Grafana via OAuth role mapping:
|
||||
# - Grafana Admins → Admin role (full access)
|
||||
# - Grafana Editors → Editor role (can edit dashboards)
|
||||
# - Grafana Viewers → Viewer role (read-only)
|
||||
#
|
||||
# AUTOMATIC ADMIN ACCESS:
|
||||
# - Anyone in "authentik Admins" automatically gets Grafana Admin access
|
||||
# - No manual configuration needed!
|
||||
# ============================================================================
|
||||
|
||||
- model: authentik_core.group
|
||||
id: grafana-admins-group
|
||||
state: present
|
||||
identifiers:
|
||||
name: Grafana Admins
|
||||
attrs:
|
||||
name: Grafana Admins
|
||||
is_superuser: false
|
||||
# Note: authentik Admins are automatically granted access via role_attribute_path
|
||||
# in Grafana configuration (see grafana/values.yaml)
|
||||
|
||||
- model: authentik_core.group
|
||||
id: grafana-editors-group
|
||||
state: present
|
||||
identifiers:
|
||||
name: Grafana Editors
|
||||
attrs:
|
||||
name: Grafana Editors
|
||||
is_superuser: false
|
||||
|
||||
- model: authentik_core.group
|
||||
id: grafana-viewers-group
|
||||
state: present
|
||||
identifiers:
|
||||
name: Grafana Viewers
|
||||
attrs:
|
||||
name: Grafana Viewers
|
||||
is_superuser: false
|
||||
|
||||
# ============================================================================
|
||||
# GRAFANA OAUTH2 PROVIDER
|
||||
# ============================================================================
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: grafana-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Grafana
|
||||
attrs:
|
||||
name: Grafana
|
||||
client_id: bd03e9139dd2063c6c44c4d2f65f51d69de3ba0b6d6b1b9b41c255d2376d2dcc
|
||||
client_secret: 6b6f2deecfe8fd56cae9c512cc71eedd463d67ff08f24c816b15b15e78ce36bc4f06a2276c5ffe3f67799d935994a32ed32cdc02ec674f279c82d7cfe3ca05d5
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://grafana.kube.huskypup.net/login/generic_oauth"
|
||||
matching_mode: strict
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, default]]
|
||||
|
||||
# ============================================================================
|
||||
# GRAFANA APPLICATION
|
||||
# ============================================================================
|
||||
|
||||
- model: authentik_core.application
|
||||
id: grafana-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: grafana
|
||||
attrs:
|
||||
name: Grafana
|
||||
slug: grafana
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf grafana-provider
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-guacamole
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
guacamole.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: guacamole-oidc
|
||||
labels:
|
||||
blueprints.goauthentik.io/instantiate: "true"
|
||||
entries:
|
||||
# ============================================================================
|
||||
# GUACAMOLE OAUTH2 PROVIDER
|
||||
# ============================================================================
|
||||
# This creates the OAuth2 provider that Guacamole will use for authentication.
|
||||
# The client_id and client_secret must match what's configured in Guacamole.
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# GROUPS SCOPE MAPPING
|
||||
# ============================================================================
|
||||
# Custom scope mapping that includes user's group names in the OIDC token.
|
||||
# ============================================================================
|
||||
|
||||
- model: authentik_providers_oauth2.scopemapping
|
||||
id: guacamole-groups-scope
|
||||
identifiers:
|
||||
managed: goauthentik.io/providers/oauth2/guacamole-groups
|
||||
attrs:
|
||||
name: "Guacamole Groups Scope"
|
||||
scope_name: groups
|
||||
expression: |
|
||||
return {
|
||||
"groups": [group.name for group in request.user.ak_groups.all()],
|
||||
}
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: guacamole-provider
|
||||
identifiers:
|
||||
name: guacamole
|
||||
attrs:
|
||||
name: Guacamole
|
||||
client_id: a55cbe3c6d2ae30c05c86385802ffcfd76a972c22c8d039f000281d4a149fd44
|
||||
client_secret: 718d4c4816aa68767a6a0da0fa4099d492473c700ba236cbeb64d294762b9548acda7e716ba11b2ad7729d6a1c16c7890b227e3b2dcfc35fd4af1eb0d6662b0f
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://guacamole.kube.huskypup.net/"
|
||||
matching_mode: strict
|
||||
- url: "https://guacamole.kube.huskypup.net/guacamole/"
|
||||
matching_mode: strict
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
- !KeyOf guacamole-groups-scope
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, "Lets Encrypt K8s Cert"]]
|
||||
|
||||
# ============================================================================
|
||||
# GUACAMOLE APPLICATION
|
||||
# ============================================================================
|
||||
# This creates the application entry that users will see in Authentik.
|
||||
# ============================================================================
|
||||
|
||||
- model: authentik_core.application
|
||||
id: guacamole-application
|
||||
identifiers:
|
||||
slug: guacamole
|
||||
attrs:
|
||||
name: Guacamole
|
||||
slug: guacamole
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf guacamole-provider
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
# Home Assistant OIDC Provider Blueprint for Authentik
|
||||
#
|
||||
# This blueprint creates an OAuth2/OIDC provider in Authentik for Home Assistant
|
||||
# Home Assistant will use the hass-openid custom integration for direct OIDC auth
|
||||
#
|
||||
# AUTOMATIC FEATURES:
|
||||
# - Users authenticate via Authentik OIDC
|
||||
# - User accounts auto-created in Home Assistant on first login
|
||||
# - Usernames sync from Authentik (preferred_username)
|
||||
# - No OAuth2 Proxy needed - direct integration!
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-home-assistant
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
home-assistant.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: home-assistant-oidc
|
||||
entries:
|
||||
# ============================================================================
|
||||
# HOME ASSISTANT OAUTH2 PROVIDER
|
||||
# ============================================================================
|
||||
# Manual client_id and client_secret for full automation (no random generation)
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: home-assistant-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Home Assistant
|
||||
attrs:
|
||||
name: Home Assistant
|
||||
client_id: ha1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab
|
||||
client_secret: ha_secret_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://homeassistant.kube.huskypup.net/auth/openid/callback"
|
||||
matching_mode: strict
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]]
|
||||
|
||||
# ============================================================================
|
||||
# HOME ASSISTANT APPLICATION
|
||||
# ============================================================================
|
||||
|
||||
- model: authentik_core.application
|
||||
id: home-assistant-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: home-assistant
|
||||
attrs:
|
||||
name: Home Assistant
|
||||
slug: home-assistant
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf home-assistant-provider
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: authentik
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- auth.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: authentik-server
|
||||
port: 80
|
||||
@@ -0,0 +1,179 @@
|
||||
# Nginx proxy that rewrites Host header for Authentik ext_authz
|
||||
#
|
||||
# Problem: Envoy's ext_authz HTTP filter forwards the ORIGINAL request's Host
|
||||
# header (e.g., prometheus.kube.huskypup.net) to Authentik. But Authentik's
|
||||
# embedded outpost only serves /outpost.goauthentik.io/auth/nginx on the
|
||||
# brand domain (auth.kube.huskypup.net), returning 404 for other hosts.
|
||||
#
|
||||
# Solution: This lightweight nginx proxy sits between Istio's ext_authz filter
|
||||
# and Authentik. It rewrites the Host header to auth.kube.huskypup.net while
|
||||
# preserving all other headers (X-Forwarded-Host, cookies, etc.) so Authentik
|
||||
# knows the original service being accessed.
|
||||
#
|
||||
# Flow:
|
||||
# 1. Client → Istio ingress gateway (Host: prometheus.kube.huskypup.net)
|
||||
# 2. ext_authz filter → this proxy (Host: prometheus.kube.huskypup.net)
|
||||
# 3. This proxy → Authentik (Host: auth.kube.huskypup.net, X-Forwarded-Host preserved)
|
||||
# 4. Authentik checks auth, returns 200 (allow) or 302 (redirect to login)
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ext-authz-proxy-config
|
||||
namespace: authentik
|
||||
data:
|
||||
nginx.conf: |
|
||||
load_module modules/ngx_http_js_module.so;
|
||||
|
||||
worker_processes 1;
|
||||
error_log /dev/stderr warn;
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 256;
|
||||
}
|
||||
|
||||
http {
|
||||
access_log /dev/stdout;
|
||||
proxy_temp_path /tmp/proxy_temp;
|
||||
client_body_temp_path /tmp/client_temp;
|
||||
|
||||
js_path /etc/nginx/njs/;
|
||||
js_import cookie from cookie_domain.js;
|
||||
|
||||
server {
|
||||
listen 4180;
|
||||
|
||||
location / {
|
||||
# Rewrite all paths to the exact Authentik traefik handler path.
|
||||
# Envoy ext_authz appends the original request path to pathPrefix,
|
||||
# creating paths like /auth/traefik/ which Authentik's Go router
|
||||
# doesn't match (exact path only, no trailing slash).
|
||||
rewrite ^ /outpost.goauthentik.io/auth/traefik break;
|
||||
proxy_pass http://authentik-server.authentik.svc.cluster.local;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host auth.kube.huskypup.net;
|
||||
proxy_set_header Connection "";
|
||||
proxy_pass_request_headers on;
|
||||
|
||||
# Add Domain=kube.huskypup.net to Set-Cookie headers from Authentik.
|
||||
# The outpost creates session cookies without Domain attribute, causing
|
||||
# browser to scope them to the original request domain (e.g., frigate.kube.huskypup.net).
|
||||
# The callback goes to auth.kube.huskypup.net, so the cookie must be domain-scoped.
|
||||
js_header_filter cookie.addDomain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cookie_domain.js: |
|
||||
function addDomain(r) {
|
||||
var cookies = r.headersOut['Set-Cookie'];
|
||||
if (cookies) {
|
||||
if (!Array.isArray(cookies)) {
|
||||
cookies = [cookies];
|
||||
}
|
||||
var modified = cookies.map(function(c) {
|
||||
if (c.indexOf('Domain=') === -1) {
|
||||
return c + '; Domain=kube.huskypup.net';
|
||||
}
|
||||
return c;
|
||||
});
|
||||
r.headersOut['Set-Cookie'] = modified;
|
||||
}
|
||||
}
|
||||
|
||||
export default { addDomain };
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ext-authz-proxy
|
||||
namespace: authentik
|
||||
labels:
|
||||
app: ext-authz-proxy
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ext-authz-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ext-authz-proxy
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.27-alpine
|
||||
ports:
|
||||
- containerPort: 4180
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/nginx/nginx.conf
|
||||
subPath: nginx.conf
|
||||
readOnly: true
|
||||
- name: njs
|
||||
mountPath: /etc/nginx/njs/cookie_domain.js
|
||||
subPath: cookie_domain.js
|
||||
readOnly: true
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: cache
|
||||
mountPath: /var/cache/nginx
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10101
|
||||
runAsGroup: 10101
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 4180
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 4180
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: ext-authz-proxy-config
|
||||
- name: njs
|
||||
configMap:
|
||||
name: ext-authz-proxy-config
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: cache
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ext-authz-proxy
|
||||
namespace: authentik
|
||||
labels:
|
||||
app: ext-authz-proxy
|
||||
spec:
|
||||
selector:
|
||||
app: ext-authz-proxy
|
||||
ports:
|
||||
- port: 4180
|
||||
targetPort: 4180
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,63 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: authentik
|
||||
spec:
|
||||
hosts:
|
||||
- auth.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
# Allow NetBird silent auth in an iframe (prompt=none)
|
||||
- match:
|
||||
- uri:
|
||||
prefix: /application/o/authorize/
|
||||
headers:
|
||||
response:
|
||||
remove:
|
||||
- x-frame-options
|
||||
set:
|
||||
content-security-policy: "frame-ancestors https://netbird.kube.huskypup.net"
|
||||
route:
|
||||
- destination:
|
||||
host: authentik-server.authentik.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Ensure CORS headers are present even on 401 responses
|
||||
- match:
|
||||
- uri:
|
||||
prefix: /application/o/userinfo/
|
||||
- uri:
|
||||
prefix: /application/o/token/
|
||||
headers:
|
||||
response:
|
||||
set:
|
||||
access-control-allow-origin: "https://netbird.kube.huskypup.net"
|
||||
access-control-allow-credentials: "true"
|
||||
access-control-allow-methods: "GET, POST, OPTIONS"
|
||||
access-control-allow-headers: "authorization, content-type"
|
||||
vary: "Origin"
|
||||
route:
|
||||
- destination:
|
||||
host: authentik-server.authentik.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# Forward auth endpoint for Istio ext_authz
|
||||
- match:
|
||||
- uri:
|
||||
prefix: /outpost.goauthentik.io
|
||||
route:
|
||||
- destination:
|
||||
host: authentik-server.authentik.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# All other traffic goes to Authentik
|
||||
- route:
|
||||
- destination:
|
||||
host: authentik-server.authentik.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,36 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: authentik-oauth-sync
|
||||
namespace: authentik
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
containers:
|
||||
- name: oauth-sync
|
||||
image: bitnami/kubectl:1.31
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
echo "=== Authentik PostSync - OAuth Sync ==="
|
||||
|
||||
echo "Waiting for Authentik server..."
|
||||
kubectl -n authentik rollout status deploy/authentik-server --timeout=600s
|
||||
|
||||
echo "Waiting for Authentik worker..."
|
||||
kubectl -n authentik rollout status deploy/authentik-worker --timeout=300s
|
||||
|
||||
echo "OAuth sync scripts should be run manually or via a separate automation."
|
||||
echo "Scripts are in the scripts/ directory of the Homelabv4 repo."
|
||||
echo "Run: bash scripts/sync-*-oauth.sh"
|
||||
|
||||
echo "=== Authentik PostSync Complete ==="
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,48 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: authentik-presync
|
||||
namespace: authentik
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
containers:
|
||||
- name: presync
|
||||
image: bitnami/kubectl:1.31
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
echo "=== Authentik PreSync ==="
|
||||
|
||||
# Wait for CNPG cluster to be ready (applied by ArgoCD as sync-wave resource)
|
||||
echo "Waiting for Authentik PostgreSQL cluster..."
|
||||
for i in $(seq 1 60); do
|
||||
PHASE=$(kubectl -n authentik get clusters.postgresql.cnpg.io pg-authentik -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
|
||||
if [ "$PHASE" = "Cluster in healthy state" ] || [ "$PHASE" = "Healthy" ]; then
|
||||
echo " PostgreSQL cluster ready"
|
||||
break
|
||||
fi
|
||||
echo " waiting for pg-authentik... (attempt $i/60, phase=$PHASE)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Generate Authentik secret key if it doesn't exist
|
||||
if ! kubectl -n authentik get secret authentik >/dev/null 2>&1; then
|
||||
echo "Generating Authentik secret key..."
|
||||
SECRET_KEY=$(openssl rand -hex 50)
|
||||
kubectl -n authentik create secret generic authentik \
|
||||
--from-literal=AUTHENTIK_SECRET_KEY="$SECRET_KEY"
|
||||
echo " Authentik secret key generated"
|
||||
else
|
||||
echo " Authentik secret already exists"
|
||||
fi
|
||||
|
||||
echo "=== Authentik PreSync Complete ==="
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-n8n
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
n8n.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: n8n-oidc
|
||||
entries:
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: n8n-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: n8n
|
||||
attrs:
|
||||
name: n8n
|
||||
client_id: e55cbe3c6d2ae30c05c86385802ffcfd76a972c22c8d039f000281d4a149fd44
|
||||
client_secret: 718d4c4816aa68767a6a0da0fa4099d492473c700ba236cbeb64d294762b9548acda7e716ba11b2ad7729d6a1c16c7890b227e3b2dcfc35fd4af1eb0d6662b0f
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://n8n.kube.huskypup.net/callback"
|
||||
matching_mode: strict
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Self-signed Certificate"]]
|
||||
|
||||
- model: authentik_core.application
|
||||
id: n8n-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: n8n
|
||||
attrs:
|
||||
name: n8n
|
||||
slug: n8n
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf n8n-provider
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
# Netbird OIDC Provider Blueprint for Authentik
|
||||
# Creates an OAuth2/OIDC provider for Netbird VPN SSO integration
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-netbird
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
netbird.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: netbird-oidc
|
||||
entries:
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: netbird-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Netbird
|
||||
attrs:
|
||||
name: Netbird
|
||||
# NetBird dashboard uses prompt=none for silent auth; this flow avoids consent UI.
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://netbird.kube.huskypup.net/.*"
|
||||
matching_mode: regex
|
||||
- url: "http://localhost:53000"
|
||||
matching_mode: strict
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, offline_access]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, "goauthentik.io/api"]]
|
||||
client_type: public
|
||||
include_claims_in_id_token: true
|
||||
sub_mode: user_id
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
|
||||
# Create VPN User group (used for non-admin NetBird access)
|
||||
- model: authentik_core.group
|
||||
id: vpn-user-group
|
||||
state: present
|
||||
identifiers:
|
||||
name: VPN User
|
||||
attrs:
|
||||
name: VPN User
|
||||
is_superuser: false
|
||||
|
||||
- model: authentik_core.application
|
||||
id: netbird-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: netbird
|
||||
attrs:
|
||||
name: Netbird VPN
|
||||
slug: netbird
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf netbird-provider
|
||||
|
||||
# Restrict NetBird access to authentik Admins and VPN User groups
|
||||
- model: authentik_policies.policybinding
|
||||
state: present
|
||||
identifiers:
|
||||
target: !KeyOf netbird-application
|
||||
group: !Find [authentik_core.group, [name, "authentik Admins"]]
|
||||
attrs:
|
||||
target: !KeyOf netbird-application
|
||||
group: !Find [authentik_core.group, [name, "authentik Admins"]]
|
||||
enabled: true
|
||||
order: 0
|
||||
negate: false
|
||||
timeout: 30
|
||||
|
||||
- model: authentik_policies.policybinding
|
||||
state: present
|
||||
identifiers:
|
||||
target: !KeyOf netbird-application
|
||||
group: !Find [authentik_core.group, [name, "VPN User"]]
|
||||
attrs:
|
||||
target: !KeyOf netbird-application
|
||||
group: !Find [authentik_core.group, [name, "VPN User"]]
|
||||
enabled: true
|
||||
order: 1
|
||||
negate: false
|
||||
timeout: 30
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-nextcloud
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
nextcloud.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: nextcloud-oidc
|
||||
entries:
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: nextcloud-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Nextcloud
|
||||
attrs:
|
||||
name: Nextcloud
|
||||
client_id: HmTGnUfhCvjFFbUUAne7JLrucNC2X675JicPbEnQ
|
||||
client_secret: qTUf4NYjzUuAqfNV5D7SFPuuhJJwVltU6U3LAuJVy7dddJWeJSv2B4wKaRxbbHqpIiSfYPDaKujhEysKbOWUnf4n9OHPKJ0bu1I2EzkYtChO1Gq4gUv5EHPTzQlMBPnl
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://nextcloud.kube.huskypup.net/apps/user_oidc/code"
|
||||
matching_mode: strict
|
||||
- url: "http://nextcloud.kube.huskypup.net/apps/user_oidc/code"
|
||||
matching_mode: strict
|
||||
enabled: true
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "days=7"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]]
|
||||
|
||||
- model: authentik_core.application
|
||||
id: nextcloud-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: nextcloud
|
||||
attrs:
|
||||
name: Nextcloud
|
||||
slug: nextcloud
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf nextcloud-provider
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-percona-everest
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
percona-everest.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: percona-everest-oidc
|
||||
labels:
|
||||
blueprints.goauthentik.io/instantiate: "true"
|
||||
entries:
|
||||
# ============================================================================
|
||||
# PERCONA EVEREST OIDC PROVIDER
|
||||
# ============================================================================
|
||||
# Everest uses OIDC with PKCE (public client) for authentication.
|
||||
# The client_id must match server.oidc.clientId in percona-everest/values.yaml.
|
||||
# ============================================================================
|
||||
|
||||
# Custom scope mapping to inject jti claim (required by Everest)
|
||||
- model: authentik_providers_oauth2.scopemapping
|
||||
id: percona-everest-jti-mapping
|
||||
state: present
|
||||
identifiers:
|
||||
managed: goauthentik.io/providers/proxy/scope-percona-everest-jti
|
||||
attrs:
|
||||
name: "Percona Everest JTI Claim"
|
||||
scope_name: "everest-jti"
|
||||
expression: |
|
||||
import uuid
|
||||
return {"jti": str(uuid.uuid4())}
|
||||
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: percona-everest-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Percona Everest
|
||||
attrs:
|
||||
name: Percona Everest
|
||||
client_id: percona-everest-oidc-client-id-static-2025
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://everest.kube.huskypup.net/login-callback"
|
||||
matching_mode: strict
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
- !KeyOf percona-everest-jti-mapping
|
||||
include_claims_in_id_token: true
|
||||
client_type: public
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
|
||||
# ============================================================================
|
||||
# PERCONA EVEREST APPLICATION
|
||||
# ============================================================================
|
||||
|
||||
- model: authentik_core.application
|
||||
id: percona-everest-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: percona-everest
|
||||
attrs:
|
||||
name: Percona Everest
|
||||
slug: percona-everest
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf percona-everest-provider
|
||||
meta_launch_url: "https://everest.kube.huskypup.net"
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-prometheus
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
prometheus.yaml: |-
|
||||
version: 1
|
||||
metadata:
|
||||
name: prometheus-app
|
||||
entries:
|
||||
- model: authentik_core.application
|
||||
id: prometheus-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: prometheus
|
||||
attrs:
|
||||
name: Prometheus
|
||||
slug: prometheus
|
||||
policy_engine_mode: any
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-blueprints-rancher
|
||||
namespace: authentik
|
||||
labels:
|
||||
goauthentik.io/blueprint: "true"
|
||||
data:
|
||||
rancher.yaml: |-
|
||||
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
|
||||
version: 1
|
||||
metadata:
|
||||
name: rancher-oidc
|
||||
entries:
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: rancher-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Rancher
|
||||
attrs:
|
||||
name: Rancher
|
||||
client_id: 94a6beeaa91d0d0407cf8af807e8208a611cf3f25ae42161e697a8dccd563449
|
||||
client_secret: 19ebd4545ba74a93c220d4471af634302c5991204cf650cabd522b0864d7617ded2c59b470ab6de7a5801e7de4c84e4b9741b983170815419c9503d55f849794
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
- url: "https://rancher.kube.huskypup.net/verify-auth"
|
||||
matching_mode: strict
|
||||
- url: "https://rancher.kube.huskypup.net"
|
||||
matching_mode: strict
|
||||
enabled: true
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
client_type: confidential
|
||||
access_code_validity: "minutes=10"
|
||||
access_token_validity: "hours=1"
|
||||
refresh_token_validity: "days=30"
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]]
|
||||
|
||||
- model: authentik_core.application
|
||||
id: rancher-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: rancher
|
||||
attrs:
|
||||
name: Rancher
|
||||
slug: rancher
|
||||
policy_engine_mode: any
|
||||
provider: !KeyOf rancher-provider
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
# Authentik configuration
|
||||
authentik:
|
||||
secret_key: env://AUTHENTIK_SECRET_KEY
|
||||
postgresql:
|
||||
host: env://AUTHENTIK_POSTGRESQL__HOST
|
||||
port: 5432
|
||||
name: env://AUTHENTIK_POSTGRESQL__NAME
|
||||
user: env://AUTHENTIK_POSTGRESQL__USER
|
||||
password: env://AUTHENTIK_POSTGRESQL__PASSWORD
|
||||
|
||||
global:
|
||||
envFrom:
|
||||
# First secret: Authentik-specific settings (AUTHENTIK_SECRET_KEY)
|
||||
- secretRef:
|
||||
name: authentik
|
||||
# Second secret: Database credentials from CNPG
|
||||
- secretRef:
|
||||
name: pg-authentik-app
|
||||
prefix: AUTHENTIK_POSTGRESQL__
|
||||
env:
|
||||
# Configure external URL for proper OIDC discovery responses
|
||||
- name: AUTHENTIK_URL
|
||||
value: "https://auth.kube.huskypup.net"
|
||||
# Override to use the correct field names from pg-authentik-app
|
||||
- name: AUTHENTIK_POSTGRESQL__HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-authentik-app
|
||||
key: host
|
||||
- name: AUTHENTIK_POSTGRESQL__NAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-authentik-app
|
||||
key: dbname
|
||||
- name: AUTHENTIK_POSTGRESQL__USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-authentik-app
|
||||
key: username
|
||||
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-authentik-app
|
||||
key: password
|
||||
- name: AUTHENTIK_POSTGRESQL__PORT
|
||||
value: "5432"
|
||||
|
||||
# Blueprints - Mount ConfigMaps for auto-discovery
|
||||
blueprints:
|
||||
configMaps:
|
||||
- authentik-blueprints-gitlab
|
||||
- authentik-blueprints-nextcloud
|
||||
- authentik-blueprints-argocd
|
||||
- authentik-blueprints-grafana
|
||||
- authentik-blueprints-forward-auth
|
||||
- authentik-blueprints-prometheus
|
||||
- authentik-blueprints-n8n
|
||||
- authentik-blueprints-ceph
|
||||
- authentik-blueprints-home-assistant
|
||||
- authentik-blueprints-guacamole
|
||||
- authentik-blueprints-percona-everest
|
||||
- authentik-blueprints-rancher
|
||||
- authentik-blueprints-netbird
|
||||
|
||||
# Enable Prometheus metrics
|
||||
server:
|
||||
# Single replica required: the embedded outpost stores forward-auth sessions
|
||||
# in-memory (no Redis). Multiple replicas cause callback failures because
|
||||
# the session created during ext_authz check is only on one pod.
|
||||
replicas: 1
|
||||
|
||||
containerSecurityContext:
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
labels:
|
||||
release: prometheus
|
||||
|
||||
# Resource limits to prevent OOM
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 1Gi
|
||||
|
||||
# Health probes - removed invalid 'enabled' fields
|
||||
startupProbe:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 24 # 120 seconds for Authentik startup
|
||||
|
||||
readinessProbe:
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 2
|
||||
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
# Spread across nodes
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: authentik
|
||||
app.kubernetes.io/component: server
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
worker:
|
||||
containerSecurityContext:
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
labels:
|
||||
release: prometheus
|
||||
|
||||
# Resource limits to prevent OOM
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 1Gi
|
||||
|
||||
# Spread across nodes
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: authentik
|
||||
app.kubernetes.io/component: worker
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
# Mount cert-manager secret for auto-import of Let's Encrypt cert
|
||||
volumeMounts:
|
||||
- name: certs
|
||||
mountPath: /certs
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: certs
|
||||
secret:
|
||||
secretName: authentik-tls
|
||||
optional: true
|
||||
|
||||
# Disable bundled Postgres
|
||||
postgresql:
|
||||
enabled: false
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-production
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: garrettstone499@gmail.com
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-production
|
||||
solvers:
|
||||
- dns01:
|
||||
cloudflare:
|
||||
email: garrettstone499@gmail.com
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-token-secret
|
||||
key: cloudflare-token
|
||||
selector:
|
||||
dnsZones:
|
||||
- "kube.huskypup.net"
|
||||
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-staging
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
email: garrettstone499@gmail.com
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-staging
|
||||
solvers:
|
||||
- dns01:
|
||||
cloudflare:
|
||||
email: garrettstone499@gmail.com
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-token-secret
|
||||
key: cloudflare-token
|
||||
selector:
|
||||
dnsZones:
|
||||
- "kube.huskypup.net"
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: cloudflare-token-secret
|
||||
namespace: cert-manager
|
||||
type: Opaque
|
||||
stringData:
|
||||
cloudflare-token: Mg9Yx1ku3-rIn7VR7Wf_PZ1uir7AqUsx3IZVpuRX # be sure you are generating an API token and not a global API key https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/#api-tokens
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
installCRDs: true
|
||||
replicaCount: 3
|
||||
|
||||
global:
|
||||
leaderElection:
|
||||
namespace: cert-manager
|
||||
|
||||
extraArgs:
|
||||
- --dns01-recursive-nameservers=1.1.1.1:53,9.9.9.9:53
|
||||
- --dns01-recursive-nameservers-only
|
||||
|
||||
podDnsPolicy: None
|
||||
podDnsConfig:
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
- 9.9.9.9
|
||||
|
||||
# Enable Prometheus metrics
|
||||
prometheus:
|
||||
enabled: true
|
||||
servicemonitor:
|
||||
enabled: true
|
||||
prometheusInstance: default
|
||||
targetPort: 9402
|
||||
path: /metrics
|
||||
interval: 60s
|
||||
scrapeTimeout: 30s
|
||||
labels:
|
||||
release: prometheus
|
||||
@@ -0,0 +1,96 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-checkov
|
||||
namespace: grafana
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
data:
|
||||
checkov-compliance.json: |
|
||||
{
|
||||
"annotations": { "list": [] },
|
||||
"editable": true,
|
||||
"panels": [
|
||||
{
|
||||
"title": "Checkov Scanner Job Status",
|
||||
"type": "timeseries",
|
||||
"datasource": { "type": "prometheus", "uid": "Prometheus" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "kube_job_status_succeeded{namespace=\"checkov\"}",
|
||||
"legendFormat": "Succeeded"
|
||||
},
|
||||
{
|
||||
"expr": "kube_job_status_failed{namespace=\"checkov\"}",
|
||||
"legendFormat": "Failed"
|
||||
}
|
||||
],
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }
|
||||
},
|
||||
{
|
||||
"title": "Last Successful Scan Age (hours)",
|
||||
"type": "stat",
|
||||
"datasource": { "type": "prometheus", "uid": "Prometheus" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "(time() - kube_cronjob_status_last_successful_time{namespace=\"checkov\", cronjob=\"checkov-scanner\"}) / 3600",
|
||||
"legendFormat": "Hours since last scan"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "h",
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{ "color": "green", "value": 0 },
|
||||
{ "color": "yellow", "value": 25 },
|
||||
{ "color": "red", "value": 49 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 }
|
||||
},
|
||||
{
|
||||
"title": "Kyverno Policy Violations (Audit Mode)",
|
||||
"type": "timeseries",
|
||||
"datasource": { "type": "prometheus", "uid": "Prometheus" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(kyverno_policy_results_total{rule_result=\"fail\"}) by (policy_name)",
|
||||
"legendFormat": "{{policy_name}}"
|
||||
}
|
||||
],
|
||||
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 8 }
|
||||
},
|
||||
{
|
||||
"title": "Kyverno Pass vs Fail",
|
||||
"type": "piechart",
|
||||
"datasource": { "type": "prometheus", "uid": "Prometheus" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(kyverno_policy_results_total) by (rule_result)",
|
||||
"legendFormat": "{{rule_result}}"
|
||||
}
|
||||
],
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 }
|
||||
},
|
||||
{
|
||||
"title": "Tetragon Security Events",
|
||||
"type": "stat",
|
||||
"datasource": { "type": "prometheus", "uid": "Prometheus" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(tetragon_events_total{type=~\"PROCESS_KPROBE|PROCESS_TRACEPOINT\"}[1h]))",
|
||||
"legendFormat": "Events/hr"
|
||||
}
|
||||
],
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 }
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["checkov", "kyverno", "compliance", "zero-trust"],
|
||||
"time": { "from": "now-24h", "to": "now" },
|
||||
"title": "Zero Trust Compliance Overview",
|
||||
"uid": "zero-trust-compliance"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: checkov-alerts
|
||||
namespace: prometheus
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
groups:
|
||||
- name: checkov-security
|
||||
rules:
|
||||
- alert: CheckovScanJobFailed
|
||||
expr: kube_job_status_failed{namespace="checkov", job_name=~"checkov-scanner.*"} > 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Checkov security scan job failed"
|
||||
description: "The Checkov IaC scanner CronJob has failed. Check logs for details."
|
||||
|
||||
- alert: CheckovScanNotRunRecently
|
||||
expr: time() - kube_cronjob_status_last_successful_time{namespace="checkov", cronjob="checkov-scanner"} > 172800
|
||||
for: 1h
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Checkov scan has not run in 48+ hours"
|
||||
description: "The Checkov security scanner has not completed successfully in over 48 hours."
|
||||
@@ -0,0 +1,245 @@
|
||||
---
|
||||
# Checkov & Regula IaC Scanner CronJob
|
||||
# Scans Kubernetes manifests daily for security misconfigurations
|
||||
# Results stored in ConfigMap for Grafana dashboard consumption
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: checkov
|
||||
labels:
|
||||
app: checkov-scanner
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: checkov-scanner
|
||||
namespace: checkov
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: checkov-scanner
|
||||
rules:
|
||||
# Read all resources for scanning
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "services", "configmaps", "secrets", "namespaces", "serviceaccounts"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses", "networkpolicies"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: ["rbac.authorization.k8s.io"]
|
||||
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
|
||||
verbs: ["get", "list"]
|
||||
# Write scan results
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["create"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["update", "patch"]
|
||||
resourceNames: ["checkov-scan-results"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: checkov-scanner
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: checkov-scanner
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: checkov-scanner
|
||||
namespace: checkov
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: checkov-scanner
|
||||
namespace: checkov
|
||||
labels:
|
||||
app: checkov-scanner
|
||||
spec:
|
||||
schedule: "0 3 * * *" # Daily at 3 AM
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
activeDeadlineSeconds: 1800 # 30 min timeout
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: checkov-scanner
|
||||
spec:
|
||||
serviceAccountName: checkov-scanner
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: scanner
|
||||
image: docker.io/bridgecrew/checkov:3.2.334
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
command:
|
||||
- python3
|
||||
- -c
|
||||
- |
|
||||
import subprocess, json, os, ssl, urllib.request, datetime
|
||||
|
||||
print("=== Checkov Kubernetes Security Scan ===")
|
||||
print(f"Scan started at: {datetime.datetime.utcnow().isoformat()}Z")
|
||||
|
||||
# Fetch Kubernetes resources as YAML files for scanning
|
||||
token_path = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
ca_path = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
api = "https://kubernetes.default.svc"
|
||||
|
||||
with open(token_path) as f:
|
||||
token = f.read().strip()
|
||||
|
||||
ctx = ssl.create_default_context(cafile=ca_path)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
os.makedirs("/tmp/k8s-manifests", exist_ok=True)
|
||||
|
||||
# Fetch deployments, statefulsets, daemonsets, pods, services
|
||||
# Focus on workload resources (skip pods/configmaps to reduce memory)
|
||||
resources = [
|
||||
("apps/v1", "deployments"),
|
||||
("apps/v1", "statefulsets"),
|
||||
("apps/v1", "daemonsets"),
|
||||
("v1", "services"),
|
||||
("networking.k8s.io/v1", "ingresses"),
|
||||
]
|
||||
|
||||
# Map API group to kind names
|
||||
kind_map = {
|
||||
"deployments": ("Deployment", "apps/v1"),
|
||||
"statefulsets": ("StatefulSet", "apps/v1"),
|
||||
"daemonsets": ("DaemonSet", "apps/v1"),
|
||||
"pods": ("Pod", "v1"),
|
||||
"services": ("Service", "v1"),
|
||||
"configmaps": ("ConfigMap", "v1"),
|
||||
"ingresses": ("Ingress", "networking.k8s.io/v1"),
|
||||
}
|
||||
for api_ver, kind in resources:
|
||||
url = f"{api}/apis/{api_ver}/{kind}" if "/" in api_ver and api_ver != "v1" else f"{api}/api/{api_ver}/{kind}"
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ctx) as resp:
|
||||
data = json.loads(resp.read())
|
||||
kind_name, api_version = kind_map.get(kind, (kind.title(), api_ver))
|
||||
for item in data.get("items", []):
|
||||
# Ensure apiVersion and kind are set (required by checkov)
|
||||
item["apiVersion"] = api_version
|
||||
item["kind"] = kind_name
|
||||
# Remove status (not part of manifest)
|
||||
item.pop("status", None)
|
||||
ns = item.get("metadata", {}).get("namespace", "default")
|
||||
name = item.get("metadata", {}).get("name", "unknown")
|
||||
fname = f"/tmp/k8s-manifests/{kind}-{ns}-{name}.json"
|
||||
with open(fname, "w") as f:
|
||||
json.dump(item, f)
|
||||
except Exception as e:
|
||||
print(f"Warning: failed to fetch {kind}: {e}")
|
||||
|
||||
file_count = len(os.listdir("/tmp/k8s-manifests"))
|
||||
print(f"Fetched {file_count} Kubernetes resources")
|
||||
|
||||
# Run checkov scan on fetched manifests
|
||||
print("Scanning resources with Checkov...")
|
||||
result = subprocess.run(
|
||||
["checkov", "--framework", "kubernetes", "-d", "/tmp/k8s-manifests",
|
||||
"--output", "json", "--output-file", "/tmp/checkov-out",
|
||||
"--soft-fail", "--quiet"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
|
||||
# Parse results (checkov creates dir with results_json.json inside)
|
||||
passed = failed = skipped = 0
|
||||
results_file = "/tmp/checkov-out/results_json.json"
|
||||
if not os.path.isfile(results_file):
|
||||
results_file = "/tmp/checkov-out"
|
||||
try:
|
||||
with open(results_file) as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, list):
|
||||
for entry in data:
|
||||
s = entry.get("summary", {})
|
||||
passed += s.get("passed", 0)
|
||||
failed += s.get("failed", 0)
|
||||
skipped += s.get("skipped", 0)
|
||||
else:
|
||||
s = data.get("summary", {})
|
||||
passed = s.get("passed", 0)
|
||||
failed = s.get("failed", 0)
|
||||
skipped = s.get("skipped", 0)
|
||||
except Exception as e:
|
||||
print(f"Warning: failed to parse results: {e}")
|
||||
|
||||
print(f"\n=== Scan Summary ===")
|
||||
print(f"Passed: {passed}")
|
||||
print(f"Failed: {failed}")
|
||||
print(f"Skipped: {skipped}")
|
||||
scan_time = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
print(f"Scan completed at: {scan_time}")
|
||||
|
||||
# Store results in ConfigMap via Kubernetes API
|
||||
cm = json.dumps({
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": {
|
||||
"name": "checkov-scan-results",
|
||||
"namespace": "checkov",
|
||||
"labels": {"app": "checkov-scanner"}
|
||||
},
|
||||
"data": {
|
||||
"last-scan": scan_time,
|
||||
"passed": str(passed),
|
||||
"failed": str(failed),
|
||||
"skipped": str(skipped),
|
||||
}
|
||||
}).encode()
|
||||
|
||||
# Try patch, then create
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
f"{api}/api/v1/namespaces/checkov/configmaps/checkov-scan-results",
|
||||
data=cm, headers={**headers, "Content-Type": "application/merge-patch+json"},
|
||||
method="PATCH")
|
||||
urllib.request.urlopen(req, context=ctx)
|
||||
print("Updated ConfigMap checkov-scan-results")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
req = urllib.request.Request(
|
||||
f"{api}/api/v1/namespaces/checkov/configmaps",
|
||||
data=cm, headers={**headers, "Content-Type": "application/json"},
|
||||
method="POST")
|
||||
urllib.request.urlopen(req, context=ctx)
|
||||
print("Created ConfigMap checkov-scan-results")
|
||||
else:
|
||||
print(f"Warning: failed to store results: {e}")
|
||||
resources:
|
||||
limits:
|
||||
memory: 1Gi
|
||||
cpu: 500m
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 100m
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Cilium L2 Announcement Policy - Replaces MetalLB
|
||||
# Announces LoadBalancer IPs via ARP on node interfaces
|
||||
---
|
||||
apiVersion: cilium.io/v2alpha1
|
||||
kind: CiliumL2AnnouncementPolicy
|
||||
metadata:
|
||||
name: default-l2-policy
|
||||
spec:
|
||||
loadBalancerIPs: true
|
||||
externalIPs: true
|
||||
interfaces:
|
||||
- ^eth[0-9]+
|
||||
- ^en[ops][0-9]+
|
||||
---
|
||||
# IP Pool - Same range as previous MetalLB pool (172.28.101.240-250)
|
||||
apiVersion: cilium.io/v2alpha1
|
||||
kind: CiliumLoadBalancerIPPool
|
||||
metadata:
|
||||
name: default-pool
|
||||
spec:
|
||||
blocks:
|
||||
- start: "172.28.101.240"
|
||||
stop: "172.28.101.250"
|
||||
@@ -0,0 +1,156 @@
|
||||
# Baseline CiliumNetworkPolicies for critical infrastructure namespaces
|
||||
# Provides L3/L4 ingress segmentation (Istio waypoints handle L7)
|
||||
#
|
||||
# IMPORTANT: CiliumNetworkPolicy egress rules with empty `{}` only match
|
||||
# Cilium-managed endpoints — NOT kube-apiserver, host, remote-node, or world.
|
||||
# Omit egress rules entirely to allow all egress (default behavior).
|
||||
# Ingress rules must explicitly include fromEntities for host/kube-apiserver
|
||||
# traffic (health probes, API server webhooks, kubelet).
|
||||
|
||||
# --- Vault: Only accessible from ESO, authentik sync scripts, and Prometheus ---
|
||||
---
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: vault-ingress
|
||||
namespace: vault
|
||||
spec:
|
||||
endpointSelector: {}
|
||||
ingress:
|
||||
# Kubernetes API server and kubelet (health probes, webhooks)
|
||||
- fromEntities:
|
||||
- host
|
||||
- kube-apiserver
|
||||
- remote-node
|
||||
# Istio ingress gateway
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: istio-system
|
||||
istio: ingressgateway
|
||||
# External Secrets Operator
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: external-secrets
|
||||
# Prometheus scraping
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: prometheus
|
||||
# Vault internal (raft replication)
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: vault
|
||||
|
||||
# --- Authentik: Accessible from ingress, apps doing OIDC, and Prometheus ---
|
||||
---
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: authentik-ingress
|
||||
namespace: authentik
|
||||
spec:
|
||||
endpointSelector: {}
|
||||
ingress:
|
||||
# Kubernetes API server and kubelet (health probes, webhooks)
|
||||
- fromEntities:
|
||||
- host
|
||||
- kube-apiserver
|
||||
- remote-node
|
||||
# Istio ingress gateway
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: istio-system
|
||||
# Internal authentik traffic (server <-> worker, pg access)
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: authentik
|
||||
# Apps doing OIDC token exchange
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: argocd
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: gitlab
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: grafana
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: n8n
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: nextcloud
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: home-assistant
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: guacamole
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: netbird
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: cattle-system
|
||||
# Prometheus scraping
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: prometheus
|
||||
|
||||
# --- CrowdSec: LAPI + agent + Prometheus + CNPG ---
|
||||
---
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: crowdsec-ingress
|
||||
namespace: crowdsec
|
||||
spec:
|
||||
endpointSelector: {}
|
||||
ingress:
|
||||
# Kubernetes API server and kubelet (health probes, webhooks)
|
||||
- fromEntities:
|
||||
- host
|
||||
- kube-apiserver
|
||||
- remote-node
|
||||
# Intra-namespace (LAPI <-> agent)
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: crowdsec
|
||||
# CNPG operator
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: cnpg-system
|
||||
# Prometheus scraping
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: prometheus
|
||||
|
||||
# --- Rook-Ceph: Only internal + Prometheus + CSI consumers ---
|
||||
---
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: rook-ceph-ingress
|
||||
namespace: rook-ceph
|
||||
spec:
|
||||
endpointSelector: {}
|
||||
ingress:
|
||||
# Kubernetes API server and kubelet (health probes, webhooks, CSI mounts)
|
||||
- fromEntities:
|
||||
- host
|
||||
- kube-apiserver
|
||||
- remote-node
|
||||
# Istio ingress gateway (Ceph dashboard)
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: istio-system
|
||||
# Internal Ceph traffic
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: rook-ceph
|
||||
# Prometheus scraping
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: prometheus
|
||||
# CSI consumers (any namespace can mount Ceph volumes via CSI)
|
||||
- fromEndpoints:
|
||||
- {}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Allow all pods to reach CoreDNS for name resolution
|
||||
# Uses enableDefaultDeny: false to avoid blocking non-DNS egress traffic
|
||||
# Istio AuthorizationPolicies handle zero-trust enforcement at L7
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumClusterwideNetworkPolicy
|
||||
metadata:
|
||||
name: allow-dns
|
||||
spec:
|
||||
endpointSelector: {}
|
||||
enableDefaultDeny:
|
||||
egress: false
|
||||
ingress: false
|
||||
egress:
|
||||
- toEndpoints:
|
||||
- matchLabels:
|
||||
k8s:io.kubernetes.pod.namespace: kube-system
|
||||
k8s-app: kube-dns
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "53"
|
||||
protocol: UDP
|
||||
- port: "53"
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,17 @@
|
||||
# Allow SNAT'd kubelet health probes into ambient mesh pods
|
||||
# Istio ambient rewrites kubelet probe source to 169.254.7.127 (link-local)
|
||||
# so ztunnel can identify and skip these packets. This policy ensures
|
||||
# CiliumNetworkPolicies don't block the rewritten probe traffic.
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumClusterwideNetworkPolicy
|
||||
metadata:
|
||||
name: allow-ambient-hostprobes
|
||||
spec:
|
||||
description: "Allows SNAT-ed kubelet health check probes into ambient pods"
|
||||
enableDefaultDeny:
|
||||
egress: false
|
||||
ingress: false
|
||||
endpointSelector: {}
|
||||
ingress:
|
||||
- fromCIDR:
|
||||
- "169.254.7.127/32"
|
||||
@@ -0,0 +1,120 @@
|
||||
# Cilium CNI - eBPF-based networking with kube-proxy replacement
|
||||
# Replaces: Calico CNI + kube-proxy + MetalLB
|
||||
|
||||
# Full kube-proxy replacement via eBPF
|
||||
kubeProxyReplacement: true
|
||||
k8sServiceHost: 172.28.101.40
|
||||
k8sServicePort: 6443
|
||||
|
||||
# IPAM - use existing pod CIDR
|
||||
ipam:
|
||||
mode: kubernetes
|
||||
operator:
|
||||
clusterPoolIPv4PodCIDRList:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
# Explicitly set devices to avoid using leftover Calico tunl0 interface
|
||||
devices: "ens+"
|
||||
|
||||
# Native routing (no encapsulation overhead)
|
||||
routingMode: native
|
||||
autoDirectNodeRoutes: true
|
||||
ipv4NativeRoutingCIDR: "10.244.0.0/16"
|
||||
|
||||
# eBPF features
|
||||
bpf:
|
||||
masquerade: false # Must be false for Istio ambient (breaks link-local SNAT for health probes)
|
||||
tproxy: true
|
||||
lbExternalClusterIP: true
|
||||
hostLegacyRouting: true # Use iptables host routing (eBPF host routing drops 169.254.7.127 packets)
|
||||
|
||||
# Socket LB - restrict to host namespace for Istio ambient compatibility
|
||||
socketLB:
|
||||
hostNamespaceOnly: true
|
||||
|
||||
# L2 announcements (replaces MetalLB)
|
||||
l2announcements:
|
||||
enabled: true
|
||||
|
||||
# Allow istio-cni to coexist
|
||||
cni:
|
||||
exclusive: false
|
||||
|
||||
# Hubble observability
|
||||
hubble:
|
||||
enabled: true
|
||||
relay:
|
||||
enabled: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
ui:
|
||||
enabled: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
metrics:
|
||||
enabled:
|
||||
- dns
|
||||
- drop
|
||||
- tcp
|
||||
- flow
|
||||
- port-distribution
|
||||
- icmp
|
||||
- httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
|
||||
# Operator settings
|
||||
operator:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
|
||||
# Agent resources
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
|
||||
# Prometheus metrics
|
||||
prometheus:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
|
||||
# Security
|
||||
securityContext:
|
||||
capabilities:
|
||||
ciliumAgent:
|
||||
- CHOWN
|
||||
- KILL
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
- IPC_LOCK
|
||||
- SYS_ADMIN
|
||||
- SYS_RESOURCE
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
cleanCiliumState:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
- SYS_RESOURCE
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-crowdsec
|
||||
namespace: crowdsec
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 3
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "25m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "100"
|
||||
shared_buffers: "256MB"
|
||||
effective_cache_size: "768MB"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: crowdsec
|
||||
owner: app
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,41 @@
|
||||
# CrowdSec secrets from Vault
|
||||
# - CAPI enrollment key (user registers at app.crowdsec.net)
|
||||
# - Bouncer API key (generated by postsync hook via cscli)
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: crowdsec-capi
|
||||
namespace: crowdsec
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: crowdsec-capi-key
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: enrollment-key
|
||||
remoteRef:
|
||||
key: crowdsec-capi
|
||||
property: enrollment-key
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: crowdsec-bouncer
|
||||
namespace: crowdsec
|
||||
spec:
|
||||
refreshInterval: "0"
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: crowdsec-bouncer-key
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: api-key
|
||||
remoteRef:
|
||||
key: crowdsec-bouncer
|
||||
property: api-key
|
||||
@@ -0,0 +1,164 @@
|
||||
# CrowdSec Firewall Bouncer - nftables enforcement on each node
|
||||
# Blocks malicious IPs before they reach Cilium eBPF processing
|
||||
# Requires: hostNetwork + privileged (nftables management)
|
||||
# Image: community Docker build of cs-firewall-bouncer (no official image exists)
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: crowdsec-firewall-bouncer-config
|
||||
namespace: crowdsec
|
||||
data:
|
||||
crowdsec-firewall-bouncer.yaml: |
|
||||
mode: nftables
|
||||
update_frequency: 10s
|
||||
log_mode: stdout
|
||||
log_level: info
|
||||
api_url: http://crowdsec-service.crowdsec.svc:8080/
|
||||
api_key: ${API_KEY}
|
||||
insecure_skip_verify: false
|
||||
disable_ipv6: false
|
||||
deny_action: DROP
|
||||
deny_log: true
|
||||
deny_log_prefix: "crowdsec: "
|
||||
supported_decisions_types:
|
||||
- ban
|
||||
blacklists_ipv4: crowdsec-blacklists
|
||||
blacklists_ipv6: crowdsec6-blacklists
|
||||
nftables:
|
||||
ipv4:
|
||||
enabled: true
|
||||
set-only: false
|
||||
table: crowdsec
|
||||
chain: crowdsec-chain
|
||||
priority: -10
|
||||
ipv6:
|
||||
enabled: true
|
||||
set-only: false
|
||||
table: crowdsec6
|
||||
chain: crowdsec6-chain
|
||||
priority: -10
|
||||
nftables_hooks:
|
||||
- input
|
||||
- forward
|
||||
prometheus:
|
||||
enabled: true
|
||||
listen_addr: 0.0.0.0
|
||||
listen_port: 60601
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: crowdsec-firewall-bouncer
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec-firewall-bouncer
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: crowdsec-firewall-bouncer
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: crowdsec-firewall-bouncer
|
||||
spec:
|
||||
hostNetwork: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
tolerations:
|
||||
- operator: Exists
|
||||
initContainers:
|
||||
# Render env vars in config template
|
||||
- name: render-config
|
||||
image: docker.io/library/busybox:1.36
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
sed "s|\${API_KEY}|${API_KEY}|g" /config-template/crowdsec-firewall-bouncer.yaml > /config/crowdsec-firewall-bouncer.yaml
|
||||
env:
|
||||
- name: API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: crowdsec-bouncer-key
|
||||
key: api-key
|
||||
volumeMounts:
|
||||
- name: config-template
|
||||
mountPath: /config-template
|
||||
- name: config-rendered
|
||||
mountPath: /config
|
||||
containers:
|
||||
- name: bouncer
|
||||
image: ghcr.io/shgew/cs-firewall-bouncer-docker:v0.0.34-patch1
|
||||
command:
|
||||
- /usr/local/bin/crowdsec-firewall-bouncer
|
||||
- -c
|
||||
- /config/crowdsec-firewall-bouncer.yaml
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
volumeMounts:
|
||||
- name: config-rendered
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
- name: nftables-lock
|
||||
mountPath: /run
|
||||
volumes:
|
||||
- name: config-template
|
||||
configMap:
|
||||
name: crowdsec-firewall-bouncer-config
|
||||
- name: config-rendered
|
||||
emptyDir: {}
|
||||
- name: nftables-lock
|
||||
hostPath:
|
||||
path: /run
|
||||
type: Directory
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: crowdsec-firewall-bouncer-metrics
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec-firewall-bouncer
|
||||
spec:
|
||||
selector:
|
||||
app: crowdsec-firewall-bouncer
|
||||
ports:
|
||||
- name: metrics
|
||||
port: 60601
|
||||
targetPort: 60601
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: crowdsec-firewall-bouncer
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec-firewall-bouncer
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: crowdsec-firewall-bouncer
|
||||
endpoints:
|
||||
- port: metrics
|
||||
interval: 30s
|
||||
@@ -0,0 +1,205 @@
|
||||
# CrowdSec Grafana Dashboard
|
||||
# Auto-discovered by Grafana sidecar via grafana_dashboard label
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-crowdsec
|
||||
namespace: grafana
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
data:
|
||||
crowdsec-overview.json: |
|
||||
{
|
||||
"annotations": { "list": [] },
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 1,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"title": "Active Decisions (Bans)",
|
||||
"type": "stat",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "cs_active_decisions",
|
||||
"legendFormat": "Active Bans"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": { "mode": "thresholds" },
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 10 },
|
||||
{ "color": "red", "value": 100 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Total Alerts",
|
||||
"type": "stat",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(increase(cs_alerts_total[24h]))",
|
||||
"legendFormat": "Alerts (24h)"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": { "mode": "thresholds" },
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 50 },
|
||||
{ "color": "red", "value": 200 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "CAPI Blocklist Entries",
|
||||
"type": "stat",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "cs_active_decisions{origin=\"CAPI\"}",
|
||||
"legendFormat": "Community Blocklist"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Bouncer Status",
|
||||
"type": "stat",
|
||||
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "cs_bouncers",
|
||||
"legendFormat": "Bouncers"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": { "mode": "thresholds" },
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{ "color": "red", "value": null },
|
||||
{ "color": "green", "value": 1 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Alerts by Scenario (24h)",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (scenario) (increase(cs_alerts_total[1h]))",
|
||||
"legendFormat": "{{ scenario }}"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": { "drawStyle": "bars", "stacking": { "mode": "normal" } }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Parser Hits (24h)",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (source) (increase(cs_parser_hits_total[1h]))",
|
||||
"legendFormat": "{{ source }}"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": { "drawStyle": "line", "fillOpacity": 10 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Decisions by Origin",
|
||||
"type": "piechart",
|
||||
"gridPos": { "h": 8, "w": 8, "x": 0, "y": 12 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "cs_active_decisions",
|
||||
"legendFormat": "{{ origin }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Bucket Overflows (Triggered Scenarios)",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 8, "w": 16, "x": 8, "y": 12 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (name) (increase(cs_bucket_overflows_total[1h]))",
|
||||
"legendFormat": "{{ name }}"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": { "drawStyle": "bars", "stacking": { "mode": "normal" } }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Firewall Bouncer - Processed Decisions",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 20 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (instance) (increase(cs_bouncer_decisions[1h]))",
|
||||
"legendFormat": "{{ instance }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "LAPI Requests",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 20 },
|
||||
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (method, route) (increase(cs_lapi_requests_total[1h]))",
|
||||
"legendFormat": "{{ method }} {{ route }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaVersion": 39,
|
||||
"tags": ["crowdsec", "security", "zero-trust"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": { "selected": false, "text": "Prometheus", "value": "prometheus" },
|
||||
"name": "DS_PROMETHEUS",
|
||||
"type": "datasource",
|
||||
"query": "prometheus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": { "from": "now-24h", "to": "now" },
|
||||
"title": "CrowdSec Overview",
|
||||
"uid": "crowdsec-overview"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: crowdsec-postsync
|
||||
namespace: crowdsec
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
containers:
|
||||
- name: postsync
|
||||
image: bitnami/kubectl:1.31
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
echo "=== CrowdSec PostSync ==="
|
||||
|
||||
# Wait for CrowdSec LAPI
|
||||
echo "Waiting for CrowdSec LAPI..."
|
||||
kubectl -n crowdsec rollout status deploy/crowdsec-lapi --timeout=180s 2>/dev/null || \
|
||||
echo "CrowdSec LAPI not ready yet"
|
||||
|
||||
# Register firewall bouncer and store key in Vault
|
||||
LAPI_POD=$(kubectl -n crowdsec get pod -l app.kubernetes.io/name=crowdsec,app.kubernetes.io/component=lapi -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
if [ -n "$LAPI_POD" ]; then
|
||||
ROOT_TOKEN=$(kubectl -n vault get secret vault-init-keys -o jsonpath='{.data.VAULT_ROOT_TOKEN}' 2>/dev/null | base64 -d)
|
||||
EXISTING_KEY=""
|
||||
if [ -n "$ROOT_TOKEN" ]; then
|
||||
EXISTING_KEY=$(kubectl exec -n vault vault-0 -- env "VAULT_TOKEN=${ROOT_TOKEN}" \
|
||||
vault kv get -field=api-key secret/crowdsec-bouncer 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [ -z "$EXISTING_KEY" ]; then
|
||||
echo "Registering firewall bouncer..."
|
||||
BOUNCER_KEY=$(kubectl -n crowdsec exec "$LAPI_POD" -- cscli bouncers add firewall-bouncer -o raw 2>/dev/null || echo "")
|
||||
if [ -n "$BOUNCER_KEY" ] && [ -n "$ROOT_TOKEN" ]; then
|
||||
kubectl exec -n vault vault-0 -- env "VAULT_TOKEN=${ROOT_TOKEN}" \
|
||||
vault kv put secret/crowdsec-bouncer api-key="$BOUNCER_KEY" 2>/dev/null && \
|
||||
echo " Bouncer API key stored in Vault"
|
||||
fi
|
||||
else
|
||||
echo " Firewall bouncer key already in Vault"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "=== CrowdSec PostSync Complete ==="
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,40 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: crowdsec-presync
|
||||
namespace: crowdsec
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
containers:
|
||||
- name: presync
|
||||
image: bitnami/kubectl:1.31
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
echo "=== CrowdSec PreSync ==="
|
||||
|
||||
# Set PodSecurity to privileged (firewall bouncer needs host access)
|
||||
kubectl label namespace crowdsec pod-security.kubernetes.io/enforce=privileged --overwrite 2>/dev/null || true
|
||||
|
||||
# Wait for CNPG cluster
|
||||
echo "Waiting for CrowdSec PostgreSQL cluster..."
|
||||
for i in $(seq 1 60); do
|
||||
PHASE=$(kubectl -n crowdsec get clusters.postgresql.cnpg.io pg-crowdsec -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
|
||||
if [ "$PHASE" = "Cluster in healthy state" ] || [ "$PHASE" = "Healthy" ]; then
|
||||
echo " CrowdSec PostgreSQL cluster ready"
|
||||
break
|
||||
fi
|
||||
echo " waiting for pg-crowdsec... (attempt $i/60, phase=$PHASE)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "=== CrowdSec PreSync Complete ==="
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,132 @@
|
||||
# CrowdSec Helm chart values
|
||||
# IP reputation + behavioral detection (Layer 0 of zero-trust stack)
|
||||
#
|
||||
# Components:
|
||||
# LAPI - Central decision engine (PostgreSQL backend via CNPG)
|
||||
# Agent - DaemonSet parsing pod logs from /var/log/pods/
|
||||
# Firewall Bouncer - Deployed separately (see firewall-bouncer.yaml)
|
||||
|
||||
container_runtime: containerd
|
||||
|
||||
lapi:
|
||||
env:
|
||||
# PostgreSQL password from CNPG auto-generated secret
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-crowdsec-app
|
||||
key: password
|
||||
# CAPI enrollment (optional — user registers at app.crowdsec.net)
|
||||
- name: ENROLL_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: crowdsec-capi-key
|
||||
key: enrollment-key
|
||||
optional: true
|
||||
- name: ENROLL_INSTANCE_NAME
|
||||
value: "homelabv5"
|
||||
- name: ENROLL_TAGS
|
||||
value: "k8s homelab talos"
|
||||
# Registration token for agent auto-registration (from chart-generated secret)
|
||||
- name: REGISTRATION_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: crowdsec-lapi-secrets
|
||||
key: registrationToken
|
||||
# Pre-register firewall bouncer API key
|
||||
- name: BOUNCER_KEY_firewall
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: crowdsec-bouncer-key
|
||||
key: api-key
|
||||
optional: true
|
||||
persistentVolume:
|
||||
data:
|
||||
enabled: false
|
||||
config:
|
||||
enabled: false
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
dashboard:
|
||||
enabled: false
|
||||
strategy:
|
||||
type: Recreate
|
||||
|
||||
agent:
|
||||
# Log acquisition sources
|
||||
acquisition:
|
||||
# Istio ingress gateway logs (all external traffic)
|
||||
- namespace: istio-system
|
||||
podName: istio-ingressgateway-*
|
||||
program: nginx
|
||||
poll_without_inotify: true
|
||||
# Netbird management logs
|
||||
- namespace: netbird
|
||||
podName: netbird-management-*
|
||||
program: netbird
|
||||
poll_without_inotify: true
|
||||
# Authentik server logs
|
||||
- namespace: authentik
|
||||
podName: authentik-server-*
|
||||
program: authentik
|
||||
poll_without_inotify: true
|
||||
|
||||
env:
|
||||
# Install community collections for log parsing
|
||||
- name: COLLECTIONS
|
||||
value: "crowdsecurity/linux crowdsecurity/nginx crowdsecurity/http-cve"
|
||||
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
# Wait for LAPI to be ready (must be a map, not scalar)
|
||||
wait_for_lapi:
|
||||
image:
|
||||
repository: busybox
|
||||
tag: "1.28"
|
||||
|
||||
# PostgreSQL database backend (CNPG) + auto-registration for agents
|
||||
config:
|
||||
config.yaml.local: |
|
||||
db_config:
|
||||
type: postgresql
|
||||
user: app
|
||||
password: ${DB_PASSWORD}
|
||||
db_name: crowdsec
|
||||
host: pg-crowdsec-rw.crowdsec.svc
|
||||
port: 5432
|
||||
sslmode: prefer
|
||||
api:
|
||||
server:
|
||||
auto_registration:
|
||||
enabled: true
|
||||
token: ${REGISTRATION_TOKEN}
|
||||
allowed_ranges:
|
||||
- "10.244.0.0/16"
|
||||
- "127.0.0.1/32"
|
||||
@@ -0,0 +1,313 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: gateway
|
||||
|
||||
---
|
||||
apiVersion: gateway.envoyproxy.io/v1alpha1
|
||||
kind: EnvoyProxy
|
||||
metadata:
|
||||
name: edge-proxy-config
|
||||
namespace: gateway
|
||||
spec:
|
||||
provider:
|
||||
type: Kubernetes
|
||||
kubernetes:
|
||||
envoyDeployment:
|
||||
replicas: 1
|
||||
patch:
|
||||
type: StrategicMerge
|
||||
value:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: envoy
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
- name: shutdown-manager
|
||||
resources:
|
||||
requests:
|
||||
cpu: 5m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 64Mi
|
||||
envoyHpa:
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 60
|
||||
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: edge-wildcard-kube-huskypup-net
|
||||
namespace: gateway
|
||||
spec:
|
||||
secretName: edge-wildcard-kube-huskypup-net
|
||||
issuerRef:
|
||||
kind: ClusterIssuer
|
||||
name: letsencrypt-production
|
||||
dnsNames:
|
||||
- "*.kube.huskypup.net"
|
||||
- "*.gitlab.kube.huskypup.net"
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
gatewayClassName: envoy-gateway
|
||||
infrastructure:
|
||||
parametersRef:
|
||||
group: gateway.envoyproxy.io
|
||||
kind: EnvoyProxy
|
||||
name: edge-proxy-config
|
||||
listeners:
|
||||
- name: https
|
||||
port: 443
|
||||
protocol: HTTPS
|
||||
tls:
|
||||
mode: Terminate
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: edge-wildcard-kube-huskypup-net
|
||||
allowedRoutes:
|
||||
namespaces:
|
||||
from: All
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-argocd-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: argocd
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-authentik-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: authentik
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-grafana-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: grafana
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-prometheus-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: prometheus
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-vault-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: vault
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-rook-ceph-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: rook-ceph
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-mqtt-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: mqtt
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-n8n-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: n8n
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-nextcloud-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: nextcloud
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-home-assistant-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: home-assistant
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-frigate-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: frigate
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-teslamate-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: teslamate
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-guacamole-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: guacamole
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: allow-gitlab-attach-edge
|
||||
namespace: gateway
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: gitlab
|
||||
to:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: edge
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: GatewayClass
|
||||
metadata:
|
||||
name: envoy-gateway
|
||||
spec:
|
||||
controllerName: gateway.envoyproxy.io/gatewayclass-controller
|
||||
@@ -0,0 +1,21 @@
|
||||
deployment:
|
||||
replicas: 2
|
||||
envoyGateway:
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 1024Mi
|
||||
|
||||
# Envoy Gateway control-plane Service (xDS); data-plane Services are created per-Gateway.
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
config:
|
||||
envoyGateway:
|
||||
extensionApis:
|
||||
enableBackend: true
|
||||
logging:
|
||||
level:
|
||||
default: info
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: external-dns-unifi-secret
|
||||
namespace: external-dns
|
||||
stringData:
|
||||
api-key: 3Qha5hupHsCkkFyQb8z-T2kh-BasIGSH
|
||||
@@ -0,0 +1,69 @@
|
||||
fullnameOverride: external-dns-unifi
|
||||
logLevel: &logLevel debug
|
||||
provider:
|
||||
name: webhook
|
||||
webhook:
|
||||
image:
|
||||
repository: ghcr.io/kashalls/external-dns-unifi-webhook
|
||||
tag: main # replace with a versioned release tag
|
||||
env:
|
||||
- name: UNIFI_HOST
|
||||
value: https://172.28.110.1 # replace with the address to your UniFi router/controller
|
||||
- name: UNIFI_EXTERNAL_CONTROLLER
|
||||
value: "false"
|
||||
- name: UNIFI_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: external-dns-unifi-secret
|
||||
key: api-key
|
||||
- name: LOG_LEVEL
|
||||
value: *logLevel
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http-webhook
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: http-webhook
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 5
|
||||
extraArgs:
|
||||
# The UniFi webhook listens on localhost:8888 inside the pod. Use IPv4
|
||||
# explicitly to avoid external-dns attempting ::1.
|
||||
- --webhook-provider-url=http://127.0.0.1:8888
|
||||
# We are switching ingress to Istio; manage records from the Istio ingress Service.
|
||||
policy: upsert-only
|
||||
sources: ["service"]
|
||||
|
||||
# UniFi DNS doesn't support TXT records; use noop registry.
|
||||
registry: noop
|
||||
domainFilters: ["kube.huskypup.net"] # replace with your domain
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: "external-dns"
|
||||
# No Ingress watching (Istio VirtualServices are used).
|
||||
|
||||
# Enable Prometheus metrics
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
additionalLabels:
|
||||
release: prometheus
|
||||
interval: 30s
|
||||
@@ -0,0 +1 @@
|
||||
installCRDs: true
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboards-provider
|
||||
namespace: grafana
|
||||
data:
|
||||
dashboards.yaml: |
|
||||
apiVersion: 1
|
||||
providers:
|
||||
- name: 'default'
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards/default
|
||||
- name: 'kubernetes'
|
||||
orgId: 1
|
||||
folder: 'Kubernetes'
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards/kubernetes
|
||||
- name: 'infrastructure'
|
||||
orgId: 1
|
||||
folder: 'Infrastructure'
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards/infrastructure
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: grafana
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- grafana.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: grafana
|
||||
port: 80
|
||||
@@ -0,0 +1,12 @@
|
||||
# infrastructure/grafana/grafana-oauth-secret.yaml
|
||||
# Grafana OAuth secret - hardcoded from blueprint
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: grafana-authentik-oauth
|
||||
namespace: grafana
|
||||
type: Opaque
|
||||
stringData:
|
||||
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "bd03e9139dd2063c6c44c4d2f65f51d69de3ba0b6d6b1b9b41c255d2376d2dcc"
|
||||
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "6b6f2deecfe8fd56cae9c512cc71eedd463d67ff08f24c816b15b15e78ce36bc4f06a2276c5ffe3f67799d935994a32ed32cdc02ec674f279c82d7cfe3ca05d5"
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: grafana
|
||||
spec:
|
||||
hosts:
|
||||
- grafana.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: grafana.grafana.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Secret containing TeslaMate database password for Grafana datasource
|
||||
# This is created by the Grafana presync hook from the CNPG secret
|
||||
#
|
||||
# The secret is referenced in values.yaml:
|
||||
# envFromSecrets:
|
||||
# - name: grafana-teslamate-datasource
|
||||
#
|
||||
# And used in the datasource configuration:
|
||||
# secureJsonData:
|
||||
# password: $__env{TESLAMATE_DB_PASSWORD}
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
# CronJob to sync TeslaMate database password from CNPG secret to Grafana datasource secret
|
||||
# This ensures Grafana always has the current password even when CNPG rotates it
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: sync-teslamate-password
|
||||
namespace: grafana
|
||||
spec:
|
||||
# Run every 30 minutes to catch password rotations (rotations happen at most daily)
|
||||
schedule: "*/30 * * * *"
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
concurrencyPolicy: Forbid
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: teslamate-password-sync
|
||||
spec:
|
||||
serviceAccountName: teslamate-password-sync
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: sync
|
||||
image: docker.io/alpine/k8s:1.32.13
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
env:
|
||||
- name: GRAFANA_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grafana-admin-secret
|
||||
key: admin-password
|
||||
optional: true
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
echo "Fetching current password from CNPG secret in teslamate namespace..."
|
||||
CURRENT_PASSWORD=$(kubectl get secret -n teslamate pg-teslamate-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
echo "Fetching current password from Grafana datasource secret..."
|
||||
GRAFANA_PASSWORD=$(kubectl get secret -n grafana grafana-teslamate-datasource -o jsonpath='{.data.TESLAMATE_DB_PASSWORD}' | base64 -d)
|
||||
|
||||
if [ "$CURRENT_PASSWORD" != "$GRAFANA_PASSWORD" ]; then
|
||||
echo "Passwords differ - updating Grafana secret..."
|
||||
kubectl create secret generic grafana-teslamate-datasource \
|
||||
--from-literal=TESLAMATE_DB_PASSWORD="$CURRENT_PASSWORD" \
|
||||
-n grafana \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
echo "Password synced to secret"
|
||||
else
|
||||
echo "Passwords match in secrets"
|
||||
fi
|
||||
|
||||
echo "Finding running Grafana pod..."
|
||||
GRAFANA_POD=$(kubectl get pod -n grafana -l app.kubernetes.io/name=grafana --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$GRAFANA_POD" ]; then
|
||||
echo "No running Grafana pod found, skipping API update"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Using Grafana pod: $GRAFANA_POD"
|
||||
echo "Updating Grafana datasource password via API (ensures password works after Grafana restart)..."
|
||||
|
||||
# Get admin password from Grafana secret (fallback to 'admin' if not found)
|
||||
ADMIN_PASS=$(kubectl get secret -n grafana grafana -o jsonpath='{.data.admin-password}' 2>/dev/null | base64 -d || echo "admin")
|
||||
|
||||
# Update datasource via API with current password
|
||||
RESULT=$(kubectl exec -n grafana "$GRAFANA_POD" -c grafana -- curl -s -X PUT \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "admin:$ADMIN_PASS" \
|
||||
http://localhost:3000/api/datasources/uid/TeslaMate \
|
||||
-d "{
|
||||
\"name\": \"TeslaMate\",
|
||||
\"type\": \"grafana-postgresql-datasource\",
|
||||
\"uid\": \"TeslaMate\",
|
||||
\"url\": \"pg-teslamate-rw.teslamate.svc.cluster.local:5432\",
|
||||
\"database\": \"teslamate\",
|
||||
\"user\": \"teslamate\",
|
||||
\"access\": \"proxy\",
|
||||
\"isDefault\": false,
|
||||
\"secureJsonData\": {
|
||||
\"password\": \"$CURRENT_PASSWORD\"
|
||||
},
|
||||
\"jsonData\": {
|
||||
\"sslmode\": \"disable\",
|
||||
\"postgresVersion\": 1600,
|
||||
\"timescaledb\": false,
|
||||
\"database\": \"teslamate\"
|
||||
}
|
||||
}")
|
||||
|
||||
echo "API Response: $RESULT"
|
||||
|
||||
# Test datasource connection
|
||||
echo "Testing datasource connection..."
|
||||
TEST_RESULT=$(kubectl exec -n grafana "$GRAFANA_POD" -c grafana -- curl -s -X POST \
|
||||
-u "admin:$ADMIN_PASS" \
|
||||
http://localhost:3000/api/datasources/uid/TeslaMate/health)
|
||||
|
||||
echo "Health Check: $TEST_RESULT"
|
||||
|
||||
if echo "$TEST_RESULT" | grep -q '"status":"OK"'; then
|
||||
echo "✅ Datasource password updated and verified successfully!"
|
||||
else
|
||||
echo "⚠️ Datasource updated but connection test failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Done!"
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
---
|
||||
# ServiceAccount for the CronJob
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
---
|
||||
# Role with permissions to read CNPG secret and update Grafana secret
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "create", "patch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "pods/exec"]
|
||||
verbs: ["get", "list", "watch", "create"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
verbs: ["get", "patch"]
|
||||
---
|
||||
# Role to read secret from teslamate namespace
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: teslamate
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["pg-teslamate-app"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
# RoleBinding in grafana namespace
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: teslamate-password-sync
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
# RoleBinding in teslamate namespace
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: teslamate-password-sync-grafana
|
||||
namespace: teslamate
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: teslamate-password-sync
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -0,0 +1,141 @@
|
||||
# values/grafana.values.yaml
|
||||
|
||||
# --- Admin credentials via Vault/ESO ---
|
||||
# Admin username & password are stored in Vault at:
|
||||
# secret/grafana-admin
|
||||
# and pulled into a K8s Secret grafana-admin-secret by ExternalSecret.
|
||||
# admin:
|
||||
# existingSecret: grafana-admin-secret
|
||||
# userKey: admin-user
|
||||
# passwordKey: admin-password
|
||||
|
||||
# These plain values are ignored when existingSecret is set, but leave
|
||||
# them harmless defaults so you can still helm template without ESO.
|
||||
adminUser: admin
|
||||
adminPassword: "admin"
|
||||
|
||||
# --- Deployment annotations for Reloader ---
|
||||
# Automatically restart Grafana when secrets change
|
||||
# Note: Unpoller has its own Reloader annotation in unpoller namespace
|
||||
deploymentAnnotations:
|
||||
secret.reloader.stakater.com/reload: "grafana-authentik-oauth,grafana-teslamate-datasource"
|
||||
|
||||
# --- Persistence for dashboards and config ---
|
||||
persistence:
|
||||
enabled: true
|
||||
type: pvc
|
||||
storageClassName: rook-ceph-block
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
size: 10Gi
|
||||
|
||||
# --- Service type ---
|
||||
service:
|
||||
type: ClusterIP
|
||||
# Security context for Talos compatibility
|
||||
securityContext:
|
||||
runAsUser: 472
|
||||
runAsGroup: 472
|
||||
runAsNonRoot: true
|
||||
fsGroup: 472
|
||||
|
||||
# --- Ingress disabled - Istio VirtualService handles routing ---
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# --- Authentik OIDC Integration ---
|
||||
# ============================================================================
|
||||
# AUTOMATIC ADMIN ACCESS - BOOTSTRAP READY
|
||||
# ============================================================================
|
||||
# Role mapping assigns Grafana roles based on Authentik group membership:
|
||||
#
|
||||
# Authentik Group → Grafana Role
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# authentik Admins → Admin (full access) - AUTOMATIC!
|
||||
# Grafana Admins → Admin (full access)
|
||||
# Grafana Editors → Editor (can edit dashboards)
|
||||
# Grafana Viewers → Viewer (read-only)
|
||||
# (any other user) → Viewer (read-only)
|
||||
#
|
||||
# BOOTSTRAP BEHAVIOR:
|
||||
# ✓ authentik Admins get automatic Grafana admin access (no manual config!)
|
||||
# ✓ Groups auto-created by Authentik blueprint during bootstrap
|
||||
# ✓ Add users to groups in Authentik UI for access control
|
||||
#
|
||||
# TO GRANT ADMIN ACCESS TO OTHER USERS:
|
||||
# 1. Log into Authentik at https://auth.kube.huskypup.net
|
||||
# 2. Go to Directory → Groups → "Grafana Admins"
|
||||
# 3. Add users to the group
|
||||
# 4. Users log out/in to Grafana to receive admin role
|
||||
# ============================================================================
|
||||
grafana.ini:
|
||||
server:
|
||||
root_url: https://grafana.kube.huskypup.net
|
||||
auth.generic_oauth:
|
||||
enabled: true
|
||||
name: Authentik
|
||||
scopes: openid profile email
|
||||
auth_url: https://auth.kube.huskypup.net/application/o/authorize/
|
||||
token_url: https://auth.kube.huskypup.net/application/o/token/
|
||||
api_url: https://auth.kube.huskypup.net/application/o/userinfo/
|
||||
# Role mapping: authentik Admins OR Grafana Admins → Admin, Grafana Editors → Editor, else → Viewer
|
||||
role_attribute_path: contains(groups[*], 'authentik Admins') && 'Admin' || contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'
|
||||
allow_sign_up: true
|
||||
client_id: $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_ID}
|
||||
client_secret: $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET}
|
||||
|
||||
|
||||
# Load OAuth credentials and datasource passwords from secrets as environment variables
|
||||
envFromSecrets:
|
||||
- name: grafana-authentik-oauth
|
||||
- name: grafana-teslamate-datasource
|
||||
|
||||
|
||||
|
||||
# --- Datasources ---
|
||||
datasources:
|
||||
datasources.yaml:
|
||||
apiVersion: 1
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
uid: Prometheus
|
||||
url: http://kube-prometheus-stack-prometheus.prometheus.svc.cluster.local:9090
|
||||
access: proxy
|
||||
isDefault: true
|
||||
- name: TeslaMate
|
||||
type: grafana-postgresql-datasource
|
||||
uid: TeslaMate
|
||||
url: pg-teslamate-rw.teslamate.svc.cluster.local:5432
|
||||
database: teslamate
|
||||
user: teslamate
|
||||
access: proxy
|
||||
isDefault: false
|
||||
editable: true
|
||||
secureJsonData:
|
||||
password: $__env{TESLAMATE_DB_PASSWORD}
|
||||
jsonData:
|
||||
sslmode: disable
|
||||
postgresVersion: 1600
|
||||
timescaledb: false
|
||||
database: teslamate
|
||||
|
||||
# --- Sidecar to auto-discover dashboards from ConfigMaps ---
|
||||
# Dashboards are deployed as ConfigMaps with the label grafana_dashboard: "1"
|
||||
# The sidecar automatically discovers them and loads them into Grafana
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
sidecar:
|
||||
dashboards:
|
||||
enabled: true
|
||||
label: grafana_dashboard
|
||||
labelValue: "1"
|
||||
folder: /var/lib/grafana/dashboards
|
||||
searchNamespace: ALL
|
||||
defaultFolderName: "General"
|
||||
provider:
|
||||
foldersFromFilesStructure: true
|
||||
@@ -0,0 +1,6 @@
|
||||
profile: ambient
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 100Mi
|
||||
@@ -0,0 +1,19 @@
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 1024Mi
|
||||
|
||||
service:
|
||||
externalTrafficPolicy: Cluster
|
||||
type: LoadBalancer
|
||||
annotations:
|
||||
# External-DNS (UniFi webhook) will create/update these A records.
|
||||
external-dns.alpha.kubernetes.io/hostname: "auth.kube.huskypup.net,grafana.kube.huskypup.net,prometheus.kube.huskypup.net,vault.kube.huskypup.net,mqtt.kube.huskypup.net,ceph.kube.huskypup.net,kiali.kube.huskypup.net,argocd.kube.huskypup.net,n8n.kube.huskypup.net,nextcloud.kube.huskypup.net,teslamate.kube.huskypup.net,homeassistant.kube.huskypup.net,esphome.kube.huskypup.net,frigate.kube.huskypup.net,guacamole.kube.huskypup.net,gitlab.kube.huskypup.net,registry.gitlab.kube.huskypup.net,minio.gitlab.kube.huskypup.net,kas.kube.huskypup.net,everest.kube.huskypup.net,rancher.kube.huskypup.net"
|
||||
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||
@@ -0,0 +1,54 @@
|
||||
# Istio Ambient Mode - ztunnel handles L4 mTLS, waypoints handle L7
|
||||
profile: ambient
|
||||
|
||||
pilot:
|
||||
autoscaleEnabled: true
|
||||
autoscaleMin: 1
|
||||
autoscaleMax: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
env:
|
||||
PILOT_ENABLE_AMBIENT: "true"
|
||||
|
||||
# Use Istio CNI for ambient mesh networking (ztunnel traffic redirection)
|
||||
cni:
|
||||
enabled: true
|
||||
|
||||
meshConfig:
|
||||
# ext_authz provider for Authentik forward-auth (via waypoint proxies)
|
||||
# In ambient mode, ext_authz is enforced by waypoint proxies, not sidecars.
|
||||
# Authentik's forward-auth endpoint is exposed under /outpost.goauthentik.io.
|
||||
extensionProviders:
|
||||
- name: authentik-ext-authz
|
||||
envoyExtAuthzHttp:
|
||||
# Route through Host-rewriting proxy (see authentik/ext-authz-proxy.yaml)
|
||||
# Authentik's embedded outpost only serves forward-auth on Host: auth.kube.huskypup.net
|
||||
service: ext-authz-proxy.authentik.svc.cluster.local
|
||||
port: 4180
|
||||
pathPrefix: /outpost.goauthentik.io/auth/traefik
|
||||
headersToDownstreamOnAllow:
|
||||
- set-cookie
|
||||
headersToUpstreamOnAllow:
|
||||
- cookie
|
||||
- set-cookie
|
||||
- authorization
|
||||
- x-authentik-username
|
||||
- x-authentik-groups
|
||||
- x-authentik-email
|
||||
- x-authentik-name
|
||||
- x-authentik-uid
|
||||
headersToDownstreamOnDeny:
|
||||
- set-cookie
|
||||
- content-type
|
||||
- location
|
||||
includeRequestHeadersInCheck:
|
||||
- cookie
|
||||
- authorization
|
||||
includeAdditionalHeadersInCheck:
|
||||
X-Forwarded-Proto: "%REQ(:SCHEME)%"
|
||||
X-Forwarded-Host: "%REQ(:AUTHORITY)%"
|
||||
X-Forwarded-Uri: "%REQ(:PATH)%"
|
||||
@@ -0,0 +1,322 @@
|
||||
# ==========================================================================
|
||||
# Application Namespace Authorization Policies
|
||||
# ==========================================================================
|
||||
#
|
||||
# These ALLOW policies permit the Istio ingress gateway (istio-system),
|
||||
# Prometheus, and intra-namespace traffic to reach application workloads.
|
||||
#
|
||||
# In ambient mode, ztunnel enforces L4 policies on ALL traffic including
|
||||
# intra-namespace. Each policy must explicitly allow same-namespace
|
||||
# communication for pods to reach their own databases, caches, etc.
|
||||
#
|
||||
# Applied by: apps/helmfile.yaml istio-app-routes postsync hook
|
||||
# ==========================================================================
|
||||
|
||||
# --- ArgoCD: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-argocd-access
|
||||
namespace: argocd
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (server ↔ repo-server ↔ redis ↔ controller)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- argocd
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- GitLab: Allow ingress + Prometheus + ArgoCD + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-gitlab-access
|
||||
namespace: gitlab
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (webservice ↔ gitaly ↔ redis ↔ postgres ↔ sidekiq ↔ praefect)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- gitlab
|
||||
# CNPG operator managing database instances
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- cnpg-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# ArgoCD pulls from GitLab repos
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- argocd
|
||||
# Redis operator managing Redis instances
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- redis-operator
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- n8n: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-n8n-access
|
||||
namespace: n8n
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (n8n ↔ postgres)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- n8n
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- Nextcloud: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-nextcloud-access
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (nextcloud ↔ mariadb ↔ redis)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- nextcloud
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- TeslaMate: Allow ingress + Prometheus + Grafana + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-teslamate-access
|
||||
namespace: teslamate
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (teslamate ↔ postgres)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- teslamate
|
||||
# CNPG operator managing database instances
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- cnpg-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# Grafana reads TeslaMate postgres directly for dashboards
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- grafana
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- Home Assistant: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-home-assistant-access
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (HA ↔ ESPHome)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- home-assistant
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- Frigate: Allow ingress + Prometheus + Home Assistant + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-frigate-access
|
||||
namespace: frigate
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- frigate
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# Home Assistant accesses Frigate API
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- home-assistant
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- Guacamole: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-guacamole-access
|
||||
namespace: guacamole
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (guacamole ↔ guacd ↔ postgres)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- guacamole
|
||||
# CNPG operator managing database instances
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- cnpg-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- Nessus: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-nessus-access
|
||||
namespace: nessus
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- nessus
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- Rancher: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-rancher-access
|
||||
namespace: cattle-system
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (rancher + fleet + webhook)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- cattle-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
@@ -0,0 +1,55 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: istio-mesh-app-enrollment
|
||||
namespace: istio-system
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: enroll
|
||||
image: bitnami/kubectl:1.29
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
echo "=== Enrolling app namespaces in Istio ambient mesh ==="
|
||||
|
||||
# Enroll app namespaces in ambient mesh
|
||||
APP_NAMESPACES=(argocd gitlab n8n nextcloud teslamate home-assistant frigate guacamole nessus cattle-system)
|
||||
for ns in "${APP_NAMESPACES[@]}"; do
|
||||
echo "Enrolling $ns in ambient mesh..."
|
||||
kubectl label namespace "$ns" istio.io/dataplane-mode=ambient --overwrite 2>/dev/null || true
|
||||
kubectl label namespace "$ns" istio-injection- 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Attach waypoint proxies for L7 policy enforcement
|
||||
echo "Attaching waypoint proxies to app namespaces..."
|
||||
WAYPOINT_APP_NAMESPACES=(argocd gitlab n8n nextcloud teslamate home-assistant frigate guacamole nessus)
|
||||
for ns in "${WAYPOINT_APP_NAMESPACES[@]}"; do
|
||||
kubectl label namespace "$ns" istio.io/use-waypoint=waypoint --overwrite 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Annotate services for NetBird VPN resource auto-discovery
|
||||
echo "Annotating services for NetBird VPN resource discovery..."
|
||||
kubectl annotate svc -n gitlab gitlab-webservice-default netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n grafana grafana netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n home-assistant home-assistant netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n guacamole guacamole netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n nextcloud nextcloud netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n argocd argocd-server netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n frigate frigate netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n teslamate teslamate netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n home-assistant esphome netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n prometheus kube-prometheus-stack-prometheus netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
kubectl annotate svc -n vault vault netbird.io/resource="true" --overwrite 2>/dev/null || true
|
||||
echo "App namespace enrollment and NetBird annotations complete"
|
||||
@@ -0,0 +1,78 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: istio-kube-huskypup-net
|
||||
namespace: istio-system
|
||||
spec:
|
||||
secretName: istio-kube-huskypup-net
|
||||
issuerRef:
|
||||
kind: ClusterIssuer
|
||||
name: letsencrypt-production
|
||||
dnsNames:
|
||||
- auth.kube.huskypup.net
|
||||
- grafana.kube.huskypup.net
|
||||
- prometheus.kube.huskypup.net
|
||||
- vault.kube.huskypup.net
|
||||
- mqtt.kube.huskypup.net
|
||||
- ceph.kube.huskypup.net
|
||||
- kiali.kube.huskypup.net
|
||||
- argocd.kube.huskypup.net
|
||||
- n8n.kube.huskypup.net
|
||||
- nextcloud.kube.huskypup.net
|
||||
- teslamate.kube.huskypup.net
|
||||
- homeassistant.kube.huskypup.net
|
||||
- esphome.kube.huskypup.net
|
||||
- frigate.kube.huskypup.net
|
||||
- guacamole.kube.huskypup.net
|
||||
- gitlab.kube.huskypup.net
|
||||
- registry.gitlab.kube.huskypup.net
|
||||
- minio.gitlab.kube.huskypup.net
|
||||
- kas.kube.huskypup.net
|
||||
- everest.kube.huskypup.net
|
||||
- rancher.kube.huskypup.net
|
||||
- netbird.kube.huskypup.net
|
||||
- nessus.kube.huskypup.net
|
||||
- scylla-manager.kube.huskypup.net
|
||||
|
||||
---
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: edge
|
||||
namespace: istio-system
|
||||
spec:
|
||||
selector:
|
||||
istio: ingressgateway
|
||||
servers:
|
||||
- port:
|
||||
number: 443
|
||||
name: https
|
||||
protocol: HTTPS
|
||||
hosts:
|
||||
- auth.kube.huskypup.net
|
||||
- grafana.kube.huskypup.net
|
||||
- prometheus.kube.huskypup.net
|
||||
- vault.kube.huskypup.net
|
||||
- mqtt.kube.huskypup.net
|
||||
- ceph.kube.huskypup.net
|
||||
- kiali.kube.huskypup.net
|
||||
- argocd.kube.huskypup.net
|
||||
- n8n.kube.huskypup.net
|
||||
- nextcloud.kube.huskypup.net
|
||||
- teslamate.kube.huskypup.net
|
||||
- homeassistant.kube.huskypup.net
|
||||
- esphome.kube.huskypup.net
|
||||
- frigate.kube.huskypup.net
|
||||
- guacamole.kube.huskypup.net
|
||||
- gitlab.kube.huskypup.net
|
||||
- registry.gitlab.kube.huskypup.net
|
||||
- minio.gitlab.kube.huskypup.net
|
||||
- kas.kube.huskypup.net
|
||||
- everest.kube.huskypup.net
|
||||
- rancher.kube.huskypup.net
|
||||
- netbird.kube.huskypup.net
|
||||
- nessus.kube.huskypup.net
|
||||
- scylla-manager.kube.huskypup.net
|
||||
tls:
|
||||
mode: SIMPLE
|
||||
credentialName: istio-kube-huskypup-net
|
||||
@@ -0,0 +1,67 @@
|
||||
# ==========================================================================
|
||||
# Global Authentik ext_authz Policy - Ingress Gateway
|
||||
# ==========================================================================
|
||||
#
|
||||
# This CUSTOM AuthorizationPolicy runs on the Istio ingress gateway and
|
||||
# requires Authentik forward-auth for ALL external traffic, EXCEPT:
|
||||
#
|
||||
# 1. auth.kube.huskypup.net - Authentik itself (must be reachable)
|
||||
# 2. Services with native OIDC - They redirect to Authentik themselves
|
||||
# 3. Machine-access services - Docker registry, git agent, MQTT, etc.
|
||||
# 4. Vault - Has its own auth system
|
||||
#
|
||||
# How it works:
|
||||
# - The ingress gateway is a regular Envoy proxy (not ztunnel), so CUSTOM
|
||||
# action works directly without waypoint proxies.
|
||||
# - Unauthenticated users get HTTP 302 redirect to Authentik login.
|
||||
# - After login, Authentik sets a session cookie and redirects back.
|
||||
# - Subsequent requests pass ext_authz automatically via cookie.
|
||||
#
|
||||
# Zero-trust: Any new *.kube.huskypup.net service is AUTOMATICALLY protected.
|
||||
# To exclude a new native-OIDC service, add its hostname to notValues below.
|
||||
#
|
||||
# Per-namespace ext_authz policies in ext-authz-policies.yaml provide
|
||||
# defense-in-depth at the waypoint proxy layer.
|
||||
# ==========================================================================
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: global-authentik-authz
|
||||
namespace: istio-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
istio: ingressgateway
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: "authentik-ext-authz" # Must match meshConfig extensionProviders[].name
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts: ["*.kube.huskypup.net"]
|
||||
when:
|
||||
- key: request.headers[host]
|
||||
notValues:
|
||||
# --- Authentik itself (must be reachable to log in) ---
|
||||
- "auth.kube.huskypup.net"
|
||||
|
||||
# --- Services with native Authentik OIDC (handle auth themselves) ---
|
||||
- "grafana.kube.huskypup.net"
|
||||
- "argocd.kube.huskypup.net"
|
||||
- "gitlab.kube.huskypup.net"
|
||||
- "n8n.kube.huskypup.net"
|
||||
- "nextcloud.kube.huskypup.net"
|
||||
- "homeassistant.kube.huskypup.net"
|
||||
- "guacamole.kube.huskypup.net"
|
||||
- "rancher.kube.huskypup.net"
|
||||
- "netbird.kube.huskypup.net"
|
||||
- "everest.kube.huskypup.net"
|
||||
|
||||
# --- Machine-access services (ext_authz would break automation) ---
|
||||
- "registry.gitlab.kube.huskypup.net" # Docker registry (docker push/pull)
|
||||
- "minio.gitlab.kube.huskypup.net" # Object storage (CI artifacts)
|
||||
- "kas.kube.huskypup.net" # GitLab Agent Server (cluster agent)
|
||||
- "mqtt.kube.huskypup.net" # MQTT protocol (IoT devices)
|
||||
|
||||
# --- Vault (has its own auth system + API access by ESO/apps) ---
|
||||
- "vault.kube.huskypup.net"
|
||||
@@ -0,0 +1,363 @@
|
||||
# ==========================================================================
|
||||
# Zero Trust Authorization Policies - Deny by Default, Allow Explicitly
|
||||
# ==========================================================================
|
||||
#
|
||||
# Policy hierarchy:
|
||||
# 1. Mesh-wide DENY (default - everything blocked)
|
||||
# 2. Ingress Gateway ALLOW (external traffic entry point)
|
||||
# 3. Service-to-service ALLOW (explicit inter-service communication)
|
||||
# 4. Monitoring ALLOW (Prometheus scraping, Kiali queries)
|
||||
#
|
||||
# In ambient mode, L7 policies are enforced by waypoint proxies in each
|
||||
# namespace. L4 policies (source namespace/principal) are enforced by ztunnel.
|
||||
# Each namespace with ALLOW/CUSTOM policies must have a waypoint Gateway.
|
||||
# ==========================================================================
|
||||
|
||||
# --- Ingress Gateway: Allow all external traffic through the edge gateway ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-ingress-gateway
|
||||
namespace: istio-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
istio: ingressgateway
|
||||
action: ALLOW
|
||||
rules:
|
||||
- {}
|
||||
|
||||
---
|
||||
# --- Allow Prometheus to scrape Istio control plane + gateway metrics ---
|
||||
# Selector scopes this to istio-system workloads only.
|
||||
# Without a selector, policies in the root namespace (istio-system) apply
|
||||
# mesh-wide in ambient mode, creating implicit deny for all ambient workloads.
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-prometheus-scraping
|
||||
namespace: istio-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/part-of: istio
|
||||
action: ALLOW
|
||||
rules:
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
to:
|
||||
- operation:
|
||||
ports:
|
||||
- "15014" # istiod control plane metrics
|
||||
- "15020" # sidecar/gateway merged metrics
|
||||
- "15090" # Envoy admin metrics
|
||||
|
||||
---
|
||||
# --- Allow Kiali to query istiod ---
|
||||
# Selector scopes this to istiod only (ambient root namespace caveat above).
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-kiali
|
||||
namespace: istio-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: istiod
|
||||
action: ALLOW
|
||||
rules:
|
||||
- from:
|
||||
- source:
|
||||
principals:
|
||||
- cluster.local/ns/istio-system/sa/kiali-service-account
|
||||
|
||||
---
|
||||
# --- Authentik: Allow traffic from ingress + apps doing OIDC ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-authentik-access
|
||||
namespace: authentik
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (server ↔ worker ↔ postgres)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- authentik
|
||||
# CNPG operator managing database instances
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- cnpg-system
|
||||
# Ingress gateway for browser flows
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
# Apps doing OIDC token exchange
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- argocd
|
||||
- gitlab
|
||||
- grafana
|
||||
- nextcloud
|
||||
- home-assistant
|
||||
- guacamole
|
||||
- netbird
|
||||
- cattle-system
|
||||
- frigate
|
||||
- teslamate
|
||||
# Prometheus scraping (L4-only; L7 path checks deferred to waypoint)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- Grafana: Allow ingress + Prometheus datasource queries + scraping ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-grafana-access
|
||||
namespace: grafana
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- grafana
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- Prometheus: Allow ingress + self-scraping + Grafana ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-prometheus-access
|
||||
namespace: prometheus
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (Prometheus ↔ alertmanager ↔ node-exporter)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- grafana
|
||||
# NetBird VPN cluster routers (non-mesh, use ipBlocks)
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "10.244.0.0/16"
|
||||
|
||||
---
|
||||
# --- MQTT: Allow Home Assistant + Frigate + ESPHome + Prometheus ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-mqtt-access
|
||||
namespace: mqtt
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- mqtt
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- home-assistant
|
||||
- frigate
|
||||
- teslamate
|
||||
# Prometheus scraping (L4-only; L7 path checks deferred to waypoint)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- External DNS: Allow internal access + Prometheus ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-external-dns
|
||||
namespace: external-dns
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- external-dns
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
# Prometheus scraping (L4-only; L7 path checks deferred to waypoint)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- Unpoller: Allow Prometheus scraping ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-unpoller-access
|
||||
namespace: unpoller
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- unpoller
|
||||
# Prometheus scraping (L4-only; L7 path checks deferred to waypoint)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- Netbird: Allow ingress + Prometheus ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-netbird-access
|
||||
namespace: netbird
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- netbird
|
||||
# CNPG operator managing database instances
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- cnpg-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
# Netbird operator querying management API
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- netbird-operator
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- Netbird Operator: Allow intra-namespace + Prometheus ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-netbird-operator-access
|
||||
namespace: netbird-operator
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- netbird-operator
|
||||
# Prometheus scraping (L4-only)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
|
||||
---
|
||||
# --- CrowdSec: Allow intra-namespace + CNPG + Prometheus ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-crowdsec-access
|
||||
namespace: crowdsec
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace (LAPI <-> agent)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- crowdsec
|
||||
# CNPG operator managing database instances
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- cnpg-system
|
||||
# Prometheus scraping (L4-only; L7 path checks deferred to waypoint)
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# Firewall bouncer (hostNetwork DaemonSet) connects from node IPs
|
||||
- from:
|
||||
- source:
|
||||
ipBlocks:
|
||||
- "172.28.101.0/24"
|
||||
|
||||
---
|
||||
# --- Scylla Manager: Allow ingress + Prometheus + intra-namespace ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: allow-scylla-manager-access
|
||||
namespace: scylla-manager
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
# Intra-namespace
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- scylla-manager
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- istio-system
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- prometheus
|
||||
# Scylla operator managing clusters
|
||||
- from:
|
||||
- source:
|
||||
namespaces:
|
||||
- scylla-operator
|
||||
@@ -0,0 +1,139 @@
|
||||
# ==========================================================================
|
||||
# Istio ext_authz Policies - Forward Auth via Authentik
|
||||
# ==========================================================================
|
||||
#
|
||||
# These policies protect services that don't have native OIDC integration.
|
||||
# Istio's ext_authz filter calls Authentik's forward-auth endpoint before
|
||||
# allowing traffic through.
|
||||
#
|
||||
# Services with native OIDC (GitLab, ArgoCD, Grafana, n8n, Home Assistant,
|
||||
# Rancher, Nextcloud) do NOT need ext_authz - they handle auth themselves.
|
||||
#
|
||||
# IMPORTANT: CUSTOM action does NOT support 'from' or source-based matching.
|
||||
# We use 'to.operation.hosts' to restrict to external hostnames only.
|
||||
# Internal service-to-service traffic uses k8s DNS names (*.svc.cluster.local)
|
||||
# which won't match, so it bypasses ext_authz and hits ALLOW policies instead.
|
||||
# ==========================================================================
|
||||
|
||||
# --- Prometheus: Require Authentik auth for browser access ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: ext-authz-prometheus
|
||||
namespace: prometheus
|
||||
spec:
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: authentik-ext-authz
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts:
|
||||
- prometheus.kube.huskypup.net
|
||||
paths:
|
||||
- /*
|
||||
---
|
||||
# --- TeslaMate: Require Authentik auth ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: ext-authz-teslamate
|
||||
namespace: teslamate
|
||||
spec:
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: authentik-ext-authz
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts:
|
||||
- teslamate.kube.huskypup.net
|
||||
---
|
||||
# --- ESPHome: Require Authentik auth ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: ext-authz-esphome
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: esphome
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: authentik-ext-authz
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts:
|
||||
- esphome.kube.huskypup.net
|
||||
---
|
||||
# --- Kiali: Require Authentik auth ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: ext-authz-kiali
|
||||
namespace: istio-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: kiali
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: authentik-ext-authz
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts:
|
||||
- kiali.kube.huskypup.net
|
||||
---
|
||||
# --- Frigate: Require Authentik auth ---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: ext-authz-frigate
|
||||
namespace: frigate
|
||||
spec:
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: authentik-ext-authz
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts:
|
||||
- frigate.kube.huskypup.net
|
||||
---
|
||||
# --- Scylla Manager: Require Authentik auth ---
|
||||
# NOTE: Namespace 'scylla-manager' must exist before applying this policy.
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: ext-authz-scylla-manager
|
||||
namespace: scylla-manager
|
||||
spec:
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: authentik-ext-authz
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts:
|
||||
- scylla-manager.kube.huskypup.net
|
||||
---
|
||||
# --- Nessus: Require Authentik auth ---
|
||||
# NOTE: Namespace 'nessus' must exist before applying this policy.
|
||||
# This policy will be skipped if the namespace doesn't exist yet.
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: ext-authz-nessus
|
||||
namespace: nessus
|
||||
spec:
|
||||
action: CUSTOM
|
||||
provider:
|
||||
name: authentik-ext-authz
|
||||
rules:
|
||||
- to:
|
||||
- operation:
|
||||
hosts:
|
||||
- nessus.kube.huskypup.net
|
||||
@@ -0,0 +1,68 @@
|
||||
# Istio Ambient Prometheus Monitors
|
||||
# Required for Kiali to display traffic graphs
|
||||
#
|
||||
# PodMonitor (ztunnel): Scrapes ztunnel L4 metrics from all nodes
|
||||
# PodMonitor (waypoint): Scrapes waypoint proxy L7 metrics
|
||||
# ServiceMonitor (istiod): Scrapes istiod control plane metrics
|
||||
#
|
||||
# All require label 'release: kube-prometheus-stack' to be picked up
|
||||
# by the Prometheus Operator's selector.
|
||||
---
|
||||
# ztunnel metrics - L4 mTLS traffic metrics from ambient mesh
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
metadata:
|
||||
name: ztunnel
|
||||
namespace: istio-system
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ztunnel
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- istio-system
|
||||
podMetricsEndpoints:
|
||||
- path: /stats/prometheus
|
||||
port: http-monitoring
|
||||
interval: 30s
|
||||
---
|
||||
# Waypoint proxy metrics - L7 AuthorizationPolicy + ext_authz metrics
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
metadata:
|
||||
name: istio-waypoint
|
||||
namespace: istio-system
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchExpressions:
|
||||
- key: gateway.networking.k8s.io/gateway-name
|
||||
operator: Exists
|
||||
namespaceSelector:
|
||||
any: true
|
||||
podMetricsEndpoints:
|
||||
- path: /stats/prometheus
|
||||
port: http-envoy-prom
|
||||
interval: 30s
|
||||
---
|
||||
# istiod control plane metrics (unchanged from sidecar mode)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: istiod
|
||||
namespace: istio-system
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
istio: pilot
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- istio-system
|
||||
endpoints:
|
||||
- port: http-monitoring
|
||||
interval: 30s
|
||||
@@ -0,0 +1,49 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: istio-mesh-infra-enrollment
|
||||
namespace: istio-system
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: enroll
|
||||
image: bitnami/kubectl:1.29
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
echo "=== Enrolling infrastructure namespaces in Istio ambient mesh ==="
|
||||
|
||||
# Enroll infrastructure namespaces
|
||||
# Excluded: vault (Shamir seal requires manual unseal after restart)
|
||||
# Excluded: kube-system, istio-system, cert-manager, kyverno (system namespaces)
|
||||
INFRA_NAMESPACES=(authentik cnpg-system redis-operator mariadb-system prometheus external-dns grafana mqtt unpoller netbird default gateway scylla-manager crowdsec)
|
||||
for ns in "${INFRA_NAMESPACES[@]}"; do
|
||||
echo "Enrolling $ns in ambient mesh..."
|
||||
kubectl label namespace "$ns" istio.io/dataplane-mode=ambient --overwrite 2>/dev/null || true
|
||||
kubectl label namespace "$ns" istio-injection- 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Attach waypoint proxies for L7 policy enforcement
|
||||
echo "Attaching waypoint proxies to infrastructure namespaces..."
|
||||
WAYPOINT_NAMESPACES=(authentik external-dns grafana mqtt unpoller netbird prometheus scylla-manager crowdsec)
|
||||
for ns in "${WAYPOINT_NAMESPACES[@]}"; do
|
||||
kubectl label namespace "$ns" istio.io/use-waypoint=waypoint --overwrite 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Deploy ext_authz Host-rewriting proxy for Authentik
|
||||
if [ -f /tmp/ext-authz-proxy.yaml ]; then
|
||||
kubectl apply -f /tmp/ext-authz-proxy.yaml
|
||||
fi
|
||||
|
||||
echo "Infrastructure namespace enrollment complete"
|
||||
@@ -0,0 +1,44 @@
|
||||
# PeerAuthentication PERMISSIVE for namespaces that receive non-mesh traffic
|
||||
# The API server calls webhooks from outside the mesh (no SPIFFE identity).
|
||||
# The CrowdSec firewall bouncer runs on hostNetwork (no mesh identity)
|
||||
# and must connect to LAPI over plaintext.
|
||||
# PERMISSIVE allows both mTLS and plaintext inbound.
|
||||
---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: PeerAuthentication
|
||||
metadata:
|
||||
name: allow-apiserver-webhooks
|
||||
namespace: cnpg-system
|
||||
spec:
|
||||
mtls:
|
||||
mode: PERMISSIVE
|
||||
---
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: PeerAuthentication
|
||||
metadata:
|
||||
name: allow-apiserver-webhooks
|
||||
namespace: mariadb-system
|
||||
spec:
|
||||
mtls:
|
||||
mode: PERMISSIVE
|
||||
---
|
||||
# Netbird operator webhook receives calls from the API server
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: PeerAuthentication
|
||||
metadata:
|
||||
name: allow-apiserver-webhooks
|
||||
namespace: netbird-operator
|
||||
spec:
|
||||
mtls:
|
||||
mode: PERMISSIVE
|
||||
---
|
||||
# CrowdSec firewall bouncer (hostNetwork DaemonSet) connects to LAPI
|
||||
# from the host network namespace without a mesh identity
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: PeerAuthentication
|
||||
metadata:
|
||||
name: allow-hostnetwork-bouncer
|
||||
namespace: crowdsec
|
||||
spec:
|
||||
mtls:
|
||||
mode: PERMISSIVE
|
||||
@@ -0,0 +1,12 @@
|
||||
# Mesh-wide STRICT mTLS - Zero Trust
|
||||
# All service-to-service communication must present valid SPIFFE SVID
|
||||
# In ambient mode, ztunnel enforces mTLS for all enrolled namespaces
|
||||
# Services outside the ambient mesh will be rejected
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: PeerAuthentication
|
||||
metadata:
|
||||
name: default
|
||||
namespace: istio-system
|
||||
spec:
|
||||
mtls:
|
||||
mode: STRICT
|
||||
@@ -0,0 +1,279 @@
|
||||
# Istio Ambient Waypoint Proxies
|
||||
# In ambient mode, L7 AuthorizationPolicies (ALLOW/CUSTOM/ext_authz) are enforced
|
||||
# by waypoint proxies, not sidecars. Each namespace with L7 policies needs a waypoint.
|
||||
#
|
||||
# Waypoints are deployed as Kubernetes Gateway resources using the istio-waypoint
|
||||
# gateway class. They handle:
|
||||
# - L7 AuthorizationPolicy evaluation
|
||||
# - ext_authz calls to Authentik for forward-auth
|
||||
# - HTTP header-based routing decisions
|
||||
|
||||
# --- Infrastructure namespaces ---
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: istio-system
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: authentik
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: grafana
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: prometheus
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: mqtt
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: external-dns
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: unpoller
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: netbird
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
|
||||
# --- Application namespaces ---
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: argocd
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: gitlab
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: n8n
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: nextcloud
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: teslamate
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: home-assistant
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: frigate
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: guacamole
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: nessus
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: waypoint
|
||||
namespace: scylla-manager
|
||||
labels:
|
||||
istio.io/waypoint-for: service
|
||||
spec:
|
||||
gatewayClassName: istio-waypoint
|
||||
listeners:
|
||||
- name: mesh
|
||||
port: 15008
|
||||
protocol: HBONE
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: kiali
|
||||
namespace: istio-system
|
||||
spec:
|
||||
hosts:
|
||||
- kiali.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- match:
|
||||
- uri:
|
||||
exact: /
|
||||
redirect:
|
||||
uri: /kiali
|
||||
- match:
|
||||
- uri:
|
||||
prefix: /kiali
|
||||
route:
|
||||
- destination:
|
||||
host: kiali.istio-system.svc.cluster.local
|
||||
port:
|
||||
number: 20001
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: kiali.io/v1alpha1
|
||||
kind: Kiali
|
||||
metadata:
|
||||
name: kiali
|
||||
namespace: istio-system
|
||||
spec:
|
||||
auth:
|
||||
strategy: anonymous
|
||||
deployment:
|
||||
namespace: istio-system
|
||||
cluster_wide_access: true
|
||||
replicas: 1
|
||||
pod_labels:
|
||||
sidecar.istio.io/inject: "true"
|
||||
istio.io/dataplane-mode: "none"
|
||||
resources:
|
||||
requests:
|
||||
cpu: "10m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
external_services:
|
||||
prometheus:
|
||||
url: "http://kube-prometheus-stack-prometheus.prometheus:9090"
|
||||
grafana:
|
||||
enabled: false
|
||||
tracing:
|
||||
enabled: false
|
||||
@@ -0,0 +1,7 @@
|
||||
# Install Kiali via operator and create the Kiali CR.
|
||||
watchNamespace: ""
|
||||
|
||||
# Install CRDs and operator first. We'll apply the Kiali CR separately
|
||||
# once the CRD exists (avoids helm-diff failing on first install).
|
||||
cr:
|
||||
create: false
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: kyverno.io/v2
|
||||
kind: PolicyException
|
||||
metadata:
|
||||
name: authentik-exemption
|
||||
namespace: kyverno
|
||||
spec:
|
||||
exceptions:
|
||||
- policyName: disallow-automount-sa-token
|
||||
ruleNames:
|
||||
- validate-automount
|
||||
- autogen-validate-automount
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment
|
||||
- ReplicaSet
|
||||
- Pod
|
||||
namespaces:
|
||||
- authentik
|
||||
names:
|
||||
- authentik-server*
|
||||
- authentik-worker*
|
||||
- pg-authentik*
|
||||
@@ -0,0 +1,68 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-automount-sa-token
|
||||
annotations:
|
||||
policies.kyverno.io/title: Disallow Automount Service Account Token
|
||||
policies.kyverno.io/category: DoD STIG
|
||||
policies.kyverno.io/severity: medium
|
||||
policies.kyverno.io/description: >-
|
||||
STIG V-242382: Pods that do not need to communicate with the
|
||||
Kubernetes API server should have automountServiceAccountToken
|
||||
set to false to prevent credential exposure.
|
||||
pod-policies.kyverno.io/autogen-controllers: none
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: validate-automount
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- kube-public
|
||||
- kube-node-lease
|
||||
- kyverno
|
||||
- istio-system
|
||||
- cert-manager
|
||||
- cnpg-system
|
||||
- external-secrets
|
||||
- vault
|
||||
- rook-ceph
|
||||
- prometheus
|
||||
- argocd
|
||||
- envoy-gateway-system
|
||||
- kiali-operator
|
||||
- mariadb-system
|
||||
- redis-operator
|
||||
- reloader
|
||||
- scylla-operator
|
||||
- crowdsec
|
||||
- external-dns
|
||||
- frigate
|
||||
- gitlab
|
||||
- grafana
|
||||
- guacamole
|
||||
- home-assistant
|
||||
- mqtt
|
||||
- netbird
|
||||
- nextcloud
|
||||
- teslamate
|
||||
- unpoller
|
||||
- authentik
|
||||
- checkov
|
||||
- n8n
|
||||
- nessus
|
||||
validate:
|
||||
message: >-
|
||||
Pods should set automountServiceAccountToken to false unless API
|
||||
access is required (STIG V-242382).
|
||||
pattern:
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
@@ -0,0 +1,33 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-default-namespace
|
||||
annotations:
|
||||
policies.kyverno.io/title: Disallow Default Namespace
|
||||
policies.kyverno.io/category: DoD STIG
|
||||
policies.kyverno.io/severity: medium
|
||||
policies.kyverno.io/description: >-
|
||||
STIG V-242383: Workloads must not run in the default namespace.
|
||||
The default namespace lacks proper RBAC and network policy boundaries.
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: true
|
||||
rules:
|
||||
- name: validate-namespace
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
- Deployment
|
||||
- StatefulSet
|
||||
- DaemonSet
|
||||
- Job
|
||||
- CronJob
|
||||
namespaces:
|
||||
- default
|
||||
validate:
|
||||
message: >-
|
||||
Workloads must not run in the default namespace (STIG V-242383).
|
||||
Create a dedicated namespace for your application.
|
||||
deny: {}
|
||||
@@ -0,0 +1,40 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-host-namespaces
|
||||
annotations:
|
||||
policies.kyverno.io/title: Disallow Host Namespaces
|
||||
policies.kyverno.io/category: DoD STIG
|
||||
policies.kyverno.io/severity: high
|
||||
policies.kyverno.io/description: >-
|
||||
STIG V-242386: Containers must not share the host process ID, IPC,
|
||||
or network namespace. Sharing host namespaces allows containers to
|
||||
access host-level resources and break isolation boundaries.
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: true
|
||||
rules:
|
||||
- name: host-namespaces
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- rook-ceph
|
||||
- istio-system
|
||||
- prometheus
|
||||
- kyverno
|
||||
- crowdsec
|
||||
validate:
|
||||
message: >-
|
||||
Sharing host PID, IPC, or network namespaces is not allowed (STIG V-242386).
|
||||
pattern:
|
||||
spec:
|
||||
=(hostPID): false
|
||||
=(hostIPC): false
|
||||
=(hostNetwork): false
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-latest-tag
|
||||
annotations:
|
||||
policies.kyverno.io/title: Disallow Latest Image Tag
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: medium
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: true
|
||||
rules:
|
||||
- name: disallow-latest
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- kyverno
|
||||
- crowdsec
|
||||
- home-assistant
|
||||
validate:
|
||||
message: "Using ':latest' image tag is not allowed. Specify an explicit tag."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "!*:latest"
|
||||
=(initContainers):
|
||||
- image: "!*:latest"
|
||||
@@ -0,0 +1,29 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-nodeport-services
|
||||
annotations:
|
||||
policies.kyverno.io/title: Disallow NodePort Services
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: medium
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: true
|
||||
rules:
|
||||
- name: disallow-nodeport
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Service
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- istio-system
|
||||
validate:
|
||||
message: "NodePort services are not allowed. Use ClusterIP or LoadBalancer."
|
||||
pattern:
|
||||
spec:
|
||||
type: "!NodePort"
|
||||
@@ -0,0 +1,43 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-privileged-containers
|
||||
annotations:
|
||||
policies.kyverno.io/title: Disallow Privileged Containers
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: high
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: true
|
||||
rules:
|
||||
- name: disallow-privileged
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- rook-ceph
|
||||
- istio-system
|
||||
- kube-node-lease
|
||||
- prometheus
|
||||
- kyverno
|
||||
- tetragon
|
||||
- crowdsec
|
||||
validate:
|
||||
message: "Privileged containers are not allowed."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- =(securityContext):
|
||||
=(privileged): false
|
||||
=(initContainers):
|
||||
- =(securityContext):
|
||||
=(privileged): false
|
||||
=(ephemeralContainers):
|
||||
- =(securityContext):
|
||||
=(privileged): false
|
||||
@@ -0,0 +1,47 @@
|
||||
apiVersion: kyverno.io/v2
|
||||
kind: PolicyException
|
||||
metadata:
|
||||
name: istio-waypoint-exemption
|
||||
namespace: kyverno
|
||||
spec:
|
||||
exceptions:
|
||||
- policyName: require-app-label
|
||||
ruleNames:
|
||||
- require-app-label
|
||||
- policyName: require-probes
|
||||
ruleNames:
|
||||
- require-probes
|
||||
- policyName: disallow-automount-sa-token
|
||||
ruleNames:
|
||||
- validate-automount
|
||||
- autogen-validate-automount
|
||||
- policyName: require-drop-all-capabilities
|
||||
ruleNames:
|
||||
- require-drop-all
|
||||
- autogen-require-drop-all
|
||||
- policyName: require-resource-limits
|
||||
ruleNames:
|
||||
- require-limits
|
||||
- autogen-require-limits
|
||||
- policyName: require-non-root
|
||||
ruleNames:
|
||||
- require-run-as-non-root
|
||||
- autogen-require-run-as-non-root
|
||||
- policyName: require-readonly-rootfs
|
||||
ruleNames:
|
||||
- require-ro-rootfs
|
||||
- autogen-require-ro-rootfs
|
||||
- policyName: disallow-latest-tag
|
||||
ruleNames:
|
||||
- disallow-latest
|
||||
- autogen-disallow-latest
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment
|
||||
- ReplicaSet
|
||||
- Pod
|
||||
selector:
|
||||
matchLabels:
|
||||
gateway.networking.k8s.io/gateway-name: waypoint
|
||||
@@ -0,0 +1,70 @@
|
||||
apiVersion: kyverno.io/v2
|
||||
kind: PolicyException
|
||||
metadata:
|
||||
name: rancher-exemption
|
||||
namespace: kyverno
|
||||
spec:
|
||||
exceptions:
|
||||
- policyName: restrict-image-registries
|
||||
ruleNames:
|
||||
- validate-container-registries
|
||||
- autogen-validate-container-registries
|
||||
- autogen-cronjob-validate-container-registries
|
||||
- policyName: require-drop-all-capabilities
|
||||
ruleNames:
|
||||
- require-drop-all
|
||||
- autogen-require-drop-all
|
||||
- autogen-cronjob-require-drop-all
|
||||
- policyName: require-resource-limits
|
||||
ruleNames:
|
||||
- require-limits
|
||||
- autogen-require-limits
|
||||
- autogen-cronjob-require-limits
|
||||
- policyName: disallow-automount-sa-token
|
||||
ruleNames:
|
||||
- validate-automount
|
||||
- autogen-validate-automount
|
||||
- autogen-cronjob-validate-automount
|
||||
- policyName: require-app-label
|
||||
ruleNames:
|
||||
- require-app-label
|
||||
- policyName: require-probes
|
||||
ruleNames:
|
||||
- require-probes
|
||||
- policyName: disallow-latest-tag
|
||||
ruleNames:
|
||||
- disallow-latest
|
||||
- autogen-disallow-latest
|
||||
- autogen-cronjob-disallow-latest
|
||||
- policyName: require-non-root
|
||||
ruleNames:
|
||||
- require-run-as-non-root
|
||||
- autogen-require-run-as-non-root
|
||||
- autogen-cronjob-require-run-as-non-root
|
||||
- policyName: require-readonly-rootfs
|
||||
ruleNames:
|
||||
- require-ro-rootfs
|
||||
- autogen-require-ro-rootfs
|
||||
- autogen-cronjob-require-ro-rootfs
|
||||
- policyName: disallow-default-namespace
|
||||
ruleNames:
|
||||
- validate-namespace
|
||||
- autogen-validate-namespace
|
||||
- autogen-cronjob-validate-namespace
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- cattle-system
|
||||
- cattle-fleet-system
|
||||
- cattle-fleet-local-system
|
||||
- cattle-capi-system
|
||||
- cattle-turtles-system
|
||||
- cattle-ui-plugin-system
|
||||
- cattle-impersonation-system
|
||||
- cattle-global-data
|
||||
- cattle-local-user-passwords
|
||||
- cattle-fleet-clusters-system
|
||||
- fleet-default
|
||||
- fleet-local
|
||||
- cluster-fleet-*
|
||||
@@ -0,0 +1,72 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-drop-all-capabilities
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Drop All Capabilities
|
||||
policies.kyverno.io/category: DoD STIG
|
||||
policies.kyverno.io/severity: high
|
||||
policies.kyverno.io/description: >-
|
||||
STIG V-242398: Containers must drop all Linux capabilities and only
|
||||
add back specific capabilities that are required. This follows the
|
||||
principle of least privilege for container security contexts.
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: require-drop-all
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- rook-ceph
|
||||
- istio-system
|
||||
- prometheus
|
||||
- kyverno
|
||||
- argocd
|
||||
- cert-manager
|
||||
- cnpg-system
|
||||
- envoy-gateway-system
|
||||
- kiali-operator
|
||||
- mariadb-system
|
||||
- redis-operator
|
||||
- reloader
|
||||
- scylla-operator
|
||||
- vault
|
||||
- mqtt
|
||||
- gitlab
|
||||
- netbird
|
||||
- grafana
|
||||
- crowdsec
|
||||
- external-dns
|
||||
- nextcloud
|
||||
- home-assistant
|
||||
- guacamole
|
||||
- teslamate
|
||||
- unpoller
|
||||
- frigate
|
||||
- authentik
|
||||
- checkov
|
||||
- external-secrets
|
||||
validate:
|
||||
message: >-
|
||||
Containers must drop ALL capabilities (STIG V-242398).
|
||||
Add securityContext.capabilities.drop: ["ALL"] to each container.
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
=(initContainers):
|
||||
- securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,81 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-istio-ambient-mesh
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Istio Ambient Mesh Enrollment
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: high
|
||||
policies.kyverno.io/description: >-
|
||||
Application namespaces must be enrolled in Istio ambient mesh via the
|
||||
istio.io/dataplane-mode=ambient label for zero-trust mTLS enforcement.
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: check-namespace-ambient-label
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Namespace
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
names:
|
||||
# Core Kubernetes
|
||||
- kube-system
|
||||
- kube-public
|
||||
- kube-node-lease
|
||||
- kyverno
|
||||
- default
|
||||
# Infrastructure
|
||||
- istio-system
|
||||
- cert-manager
|
||||
- cnpg-system
|
||||
- rook-ceph
|
||||
- vault
|
||||
- external-secrets
|
||||
- redis-operator
|
||||
- mariadb-system
|
||||
- scylla-operator
|
||||
- scylla-manager
|
||||
- reloader
|
||||
- checkov
|
||||
- kiali-operator
|
||||
- envoy-gateway-system
|
||||
- tetragon
|
||||
- prometheus
|
||||
- crowdsec
|
||||
- cilium-secrets
|
||||
- gateway
|
||||
- argocd
|
||||
- netbird-operator
|
||||
# Rancher (exact names)
|
||||
- cattle-system
|
||||
- cattle-fleet-system
|
||||
- cattle-fleet-local-system
|
||||
- cattle-capi-system
|
||||
- cattle-turtles-system
|
||||
- cattle-ui-plugin-system
|
||||
- cattle-impersonation-system
|
||||
- cattle-global-data
|
||||
- cattle-local-user-passwords
|
||||
- cattle-fleet-clusters-system
|
||||
- fleet-default
|
||||
- fleet-local
|
||||
- local
|
||||
# Rancher dynamic namespaces (wildcard)
|
||||
- cattle-*
|
||||
- fleet-*
|
||||
- c-*
|
||||
- p-*
|
||||
- u-*
|
||||
- user-*
|
||||
- cluster-fleet-*
|
||||
validate:
|
||||
message: "Application namespaces must have istio.io/dataplane-mode=ambient for zero-trust mTLS."
|
||||
pattern:
|
||||
metadata:
|
||||
labels:
|
||||
istio.io/dataplane-mode: ambient
|
||||
@@ -0,0 +1,43 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-app-label
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require App Label
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: low
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: require-app-label
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment
|
||||
- StatefulSet
|
||||
- DaemonSet
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- kube-node-lease
|
||||
- kyverno
|
||||
- rook-ceph
|
||||
- default
|
||||
- crowdsec
|
||||
- gitlab
|
||||
- scylla-manager
|
||||
- scylla-operator
|
||||
- cert-manager
|
||||
validate:
|
||||
message: "The label 'app' or 'app.kubernetes.io/name' is required."
|
||||
anyPattern:
|
||||
- metadata:
|
||||
labels:
|
||||
app: "?*"
|
||||
- metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: "?*"
|
||||
@@ -0,0 +1,37 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-non-root
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Non-Root User
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: medium
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: require-run-as-non-root
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- rook-ceph
|
||||
- istio-system
|
||||
- prometheus
|
||||
- kyverno
|
||||
- tetragon
|
||||
validate:
|
||||
message: "Containers must run as non-root. Set runAsNonRoot to true."
|
||||
pattern:
|
||||
spec:
|
||||
=(securityContext):
|
||||
=(runAsNonRoot): true
|
||||
containers:
|
||||
- =(securityContext):
|
||||
=(runAsNonRoot): true
|
||||
@@ -0,0 +1,56 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-probes
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Liveness and Readiness Probes
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: low
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: require-probes
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment
|
||||
- StatefulSet
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- kyverno
|
||||
- tetragon
|
||||
- rook-ceph
|
||||
- argocd
|
||||
- cert-manager
|
||||
- cnpg-system
|
||||
- envoy-gateway-system
|
||||
- external-secrets
|
||||
- istio-system
|
||||
- kiali-operator
|
||||
- mariadb-system
|
||||
- prometheus
|
||||
- redis-operator
|
||||
- reloader
|
||||
- scylla-operator
|
||||
- vault
|
||||
- gitlab
|
||||
- grafana
|
||||
- guacamole
|
||||
- nextcloud
|
||||
- scylla-manager
|
||||
validate:
|
||||
message: "Liveness and readiness probes are required for all containers."
|
||||
pattern:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- livenessProbe:
|
||||
periodSeconds: ">0"
|
||||
readinessProbe:
|
||||
periodSeconds: ">0"
|
||||
@@ -0,0 +1,63 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-resource-limits
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Resource Limits
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: medium
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: require-limits
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- kube-node-lease
|
||||
- kyverno
|
||||
- argocd
|
||||
- authentik
|
||||
- cert-manager
|
||||
- cnpg-system
|
||||
- envoy-gateway-system
|
||||
- external-secrets
|
||||
- istio-system
|
||||
- kiali-operator
|
||||
- mariadb-system
|
||||
- prometheus
|
||||
- redis-operator
|
||||
- reloader
|
||||
- rook-ceph
|
||||
- scylla-operator
|
||||
- vault
|
||||
- gitlab
|
||||
- netbird
|
||||
- grafana
|
||||
- crowdsec
|
||||
- external-dns
|
||||
- nextcloud
|
||||
- mqtt
|
||||
- home-assistant
|
||||
- guacamole
|
||||
- teslamate
|
||||
- unpoller
|
||||
- frigate
|
||||
- checkov
|
||||
- scylla-manager
|
||||
validate:
|
||||
message: "All containers must have CPU and memory limits defined."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- resources:
|
||||
limits:
|
||||
cpu: "?*"
|
||||
memory: "?*"
|
||||
@@ -0,0 +1,39 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-readonly-rootfs
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Read-Only Root Filesystem
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: low
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: require-ro-rootfs
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- rook-ceph
|
||||
- istio-system
|
||||
- kyverno
|
||||
- tetragon
|
||||
- prometheus
|
||||
- authentik
|
||||
- gitlab
|
||||
- nextcloud
|
||||
- home-assistant
|
||||
validate:
|
||||
message: "Containers should use a read-only root filesystem."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- =(securityContext):
|
||||
=(readOnlyRootFilesystem): true
|
||||
@@ -0,0 +1,107 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: restrict-image-registries
|
||||
annotations:
|
||||
policies.kyverno.io/title: Restrict Image Registries
|
||||
policies.kyverno.io/category: Zero Trust
|
||||
policies.kyverno.io/severity: high
|
||||
policies.kyverno.io/description: >-
|
||||
Validates that all container images come from allowed registries.
|
||||
Images must either use an explicit allowed registry prefix (docker.io/,
|
||||
ghcr.io/, quay.io/, etc.) or be short-name Docker Hub references
|
||||
(no dots before the first slash, e.g. grafana/grafana or busybox).
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: true
|
||||
rules:
|
||||
- name: validate-container-registries
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- kube-node-lease
|
||||
- kyverno
|
||||
validate:
|
||||
message: >-
|
||||
Images must come from allowed registries: docker.io, ghcr.io, quay.io,
|
||||
registry.k8s.io, gcr.io, lscr.io, registry.gitlab.com, cr.fluentbit.io,
|
||||
public.ecr.aws, ecr-public.aws.com, oci.external-secrets.io,
|
||||
docker-registry3.mariadb.com. Docker Hub short names (org/image or
|
||||
library images) are also allowed.
|
||||
foreach:
|
||||
- list: "request.object.spec.containers"
|
||||
deny:
|
||||
conditions:
|
||||
all:
|
||||
# Image does NOT start with any allowed registry prefix
|
||||
- key: "{{element.image}}"
|
||||
operator: AnyNotIn
|
||||
value:
|
||||
- "docker.io/*"
|
||||
- "ghcr.io/*"
|
||||
- "quay.io/*"
|
||||
- "registry.k8s.io/*"
|
||||
- "gcr.io/*"
|
||||
- "lscr.io/*"
|
||||
- "registry.gitlab.com/*"
|
||||
- "cr.fluentbit.io/*"
|
||||
- "public.ecr.aws/*"
|
||||
- "ecr-public.aws.com/*"
|
||||
- "oci.external-secrets.io/*"
|
||||
- "docker-registry3.mariadb.com/*"
|
||||
# AND image looks like a registry hostname (has a dot before the first slash)
|
||||
# This allows Docker Hub short names like grafana/grafana or busybox
|
||||
- key: "{{element.image}}"
|
||||
operator: Equals
|
||||
value: "*.*/*"
|
||||
- list: "request.object.spec.initContainers || []"
|
||||
deny:
|
||||
conditions:
|
||||
all:
|
||||
- key: "{{element.image}}"
|
||||
operator: AnyNotIn
|
||||
value:
|
||||
- "docker.io/*"
|
||||
- "ghcr.io/*"
|
||||
- "quay.io/*"
|
||||
- "registry.k8s.io/*"
|
||||
- "gcr.io/*"
|
||||
- "lscr.io/*"
|
||||
- "registry.gitlab.com/*"
|
||||
- "cr.fluentbit.io/*"
|
||||
- "public.ecr.aws/*"
|
||||
- "ecr-public.aws.com/*"
|
||||
- "oci.external-secrets.io/*"
|
||||
- "docker-registry3.mariadb.com/*"
|
||||
- key: "{{element.image}}"
|
||||
operator: Equals
|
||||
value: "*.*/*"
|
||||
- list: "request.object.spec.ephemeralContainers || []"
|
||||
deny:
|
||||
conditions:
|
||||
all:
|
||||
- key: "{{element.image}}"
|
||||
operator: AnyNotIn
|
||||
value:
|
||||
- "docker.io/*"
|
||||
- "ghcr.io/*"
|
||||
- "quay.io/*"
|
||||
- "registry.k8s.io/*"
|
||||
- "gcr.io/*"
|
||||
- "lscr.io/*"
|
||||
- "registry.gitlab.com/*"
|
||||
- "cr.fluentbit.io/*"
|
||||
- "public.ecr.aws/*"
|
||||
- "ecr-public.aws.com/*"
|
||||
- "oci.external-secrets.io/*"
|
||||
- "docker-registry3.mariadb.com/*"
|
||||
- key: "{{element.image}}"
|
||||
operator: Equals
|
||||
value: "*.*/*"
|
||||
@@ -0,0 +1,128 @@
|
||||
# Kyverno Policy Engine - Zero Trust Configuration
|
||||
# All policies start in Audit mode to avoid breaking existing workloads
|
||||
|
||||
admissionController:
|
||||
replicas: 3
|
||||
resources:
|
||||
limits:
|
||||
memory: 384Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
backgroundController:
|
||||
replicas: 2
|
||||
resources:
|
||||
limits:
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
cleanupController:
|
||||
replicas: 1
|
||||
resources:
|
||||
limits:
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
reportsController:
|
||||
replicas: 1
|
||||
resources:
|
||||
limits:
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
# Enable metrics for Prometheus
|
||||
metricsConfig:
|
||||
metricsExposure:
|
||||
enabled: true
|
||||
|
||||
# Exclude system namespaces from webhook
|
||||
config:
|
||||
webhooks:
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: kubernetes.io/metadata.name
|
||||
operator: NotIn
|
||||
values:
|
||||
- kube-system
|
||||
- kube-public
|
||||
- kube-node-lease
|
||||
- cattle-system
|
||||
- cattle-fleet-system
|
||||
- cattle-fleet-local-system
|
||||
- cattle-capi-system
|
||||
- cattle-turtles-system
|
||||
- cattle-ui-plugin-system
|
||||
- cattle-impersonation-system
|
||||
- cattle-global-data
|
||||
- cattle-local-user-passwords
|
||||
- cattle-fleet-clusters-system
|
||||
- fleet-default
|
||||
- fleet-local
|
||||
- local
|
||||
excludeGroups:
|
||||
- system:serviceaccounts:kube-system
|
||||
- system:nodes
|
||||
resourceFiltersExcludeNamespaces:
|
||||
# Core Kubernetes
|
||||
- kube-system
|
||||
- kube-public
|
||||
- kube-node-lease
|
||||
- kyverno
|
||||
# Rancher (exact names)
|
||||
- cattle-system
|
||||
- cattle-fleet-system
|
||||
- cattle-fleet-local-system
|
||||
- cattle-capi-system
|
||||
- cattle-turtles-system
|
||||
- cattle-ui-plugin-system
|
||||
- cattle-impersonation-system
|
||||
- cattle-global-data
|
||||
- cattle-local-user-passwords
|
||||
- cattle-fleet-clusters-system
|
||||
- fleet-default
|
||||
- fleet-local
|
||||
- local
|
||||
# Rancher dynamic namespaces (wildcard)
|
||||
- c-*
|
||||
- p-*
|
||||
- u-*
|
||||
- user-*
|
||||
- cluster-fleet-*
|
||||
# Infrastructure operators and system namespaces
|
||||
- rook-ceph
|
||||
- cnpg-system
|
||||
- redis-operator
|
||||
- mariadb-system
|
||||
- cert-manager
|
||||
- istio-system
|
||||
- external-secrets
|
||||
- scylla-operator
|
||||
- scylla-manager
|
||||
- kiali-operator
|
||||
- envoy-gateway-system
|
||||
- reloader
|
||||
- tetragon
|
||||
- cilium-secrets
|
||||
- vault
|
||||
- prometheus
|
||||
- netbird-operator
|
||||
|
||||
features:
|
||||
policyExceptions:
|
||||
enabled: true
|
||||
namespace: kyverno
|
||||
@@ -0,0 +1,6 @@
|
||||
apiService:
|
||||
create: true
|
||||
|
||||
args:
|
||||
- --kubelet-insecure-tls
|
||||
- --kubelet-preferred-address-types=InternalIP
|
||||
@@ -0,0 +1,137 @@
|
||||
# MQTT Broker - Eclipse Mosquitto
|
||||
# Used by Frigate, Home Assistant, and other IoT apps for event messaging
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: mqtt
|
||||
labels:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: mosquitto
|
||||
namespace: mqtt
|
||||
labels:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
data:
|
||||
mosquitto.conf: |
|
||||
listener 1883
|
||||
listener 9001
|
||||
protocol websockets
|
||||
allow_anonymous true
|
||||
persistence true
|
||||
persistence_location /mosquitto/data/
|
||||
log_dest file /mosquitto/log/mosquitto.log
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: mosquitto-data
|
||||
namespace: mqtt
|
||||
labels:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
storageClassName: rook-ceph-block
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mosquitto
|
||||
namespace: mqtt
|
||||
labels:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
runAsUser: 1883
|
||||
runAsGroup: 1883
|
||||
fsGroup: 1883
|
||||
containers:
|
||||
- name: mosquitto
|
||||
image: eclipse-mosquitto:2.0.18
|
||||
securityContext:
|
||||
runAsUser: 1883
|
||||
runAsGroup: 1883
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
ports:
|
||||
- containerPort: 1883
|
||||
name: mqtt
|
||||
- containerPort: 9001
|
||||
name: websocket
|
||||
volumeMounts:
|
||||
- name: mosquitto-data-persistent
|
||||
mountPath: /mosquitto/data
|
||||
- name: mosquitto-log
|
||||
mountPath: /mosquitto/log
|
||||
- name: mosquitto-config
|
||||
mountPath: /mosquitto/config
|
||||
readOnly: true
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 1883
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 1883
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: mosquitto-data-persistent
|
||||
persistentVolumeClaim:
|
||||
claimName: mosquitto-data
|
||||
- name: mosquitto-log
|
||||
emptyDir: {}
|
||||
- name: mosquitto-config
|
||||
configMap:
|
||||
name: mosquitto
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mosquitto
|
||||
namespace: mqtt
|
||||
labels:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
spec:
|
||||
ports:
|
||||
- name: mqtt
|
||||
port: 1883
|
||||
targetPort: 1883
|
||||
- name: websocket
|
||||
port: 9001
|
||||
targetPort: 9001
|
||||
selector:
|
||||
app.kubernetes.io/name: mosquitto
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: mosquitto-websocket
|
||||
namespace: mqtt
|
||||
spec:
|
||||
hosts:
|
||||
- mqtt.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: mosquitto.mqtt.svc.cluster.local
|
||||
port:
|
||||
number: 9001
|
||||
@@ -0,0 +1,46 @@
|
||||
# Netbird Kubernetes Operator
|
||||
# Auto-discovers K8s services via annotations and creates Netbird Networks
|
||||
# Chart: netbird-operator/kubernetes-operator
|
||||
|
||||
# API key from ExternalSecret-managed secret
|
||||
netbirdAPI:
|
||||
keyFromSecret:
|
||||
name: netbird-mgmt-api-key
|
||||
key: NB_API_KEY
|
||||
|
||||
# Explicit tag required — Kyverno disallow-latest-tag policy
|
||||
routingClientImage: "netbirdio/netbird:0.66.0"
|
||||
|
||||
cluster:
|
||||
name: homelab
|
||||
dns: svc.cluster.local
|
||||
|
||||
# Enable service exposure via annotations (netbird.io/expose=true)
|
||||
ingress:
|
||||
enabled: true
|
||||
namespacedNetworks: true
|
||||
allowAutomaticPolicyCreation: true
|
||||
|
||||
# Webhook TLS via cert-manager (already deployed)
|
||||
webhook:
|
||||
enableCertManager: true
|
||||
failurePolicy: Fail
|
||||
|
||||
operator:
|
||||
replicaCount: 1
|
||||
image:
|
||||
pullPolicy: IfNotPresent
|
||||
metrics:
|
||||
enabled: true
|
||||
port: 8080
|
||||
resources:
|
||||
limits:
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-netbird
|
||||
namespace: netbird
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 2
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "25m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "100"
|
||||
shared_buffers: "256MB"
|
||||
effective_cache_size: "768MB"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: netbird
|
||||
owner: app
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,104 @@
|
||||
# Netbird API Exporter - Exposes peer status, groups, users as Prometheus metrics
|
||||
# Uses the Netbird management API to collect data
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: netbird-api-exporter
|
||||
namespace: netbird
|
||||
labels:
|
||||
app.kubernetes.io/name: netbird-api-exporter
|
||||
app.kubernetes.io/component: exporter
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: netbird-api-exporter
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: netbird-api-exporter
|
||||
app.kubernetes.io/component: exporter
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: exporter
|
||||
image: ghcr.io/matanbaruch/netbird-api-exporter:v0.1.46
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: metrics
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: NETBIRD_API_URL
|
||||
value: "http://netbird-management.netbird.svc.cluster.local:80"
|
||||
- name: NETBIRD_API_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: netbird-api-token-secret
|
||||
key: api-token
|
||||
resources:
|
||||
limits:
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: metrics
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: metrics
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: netbird-api-exporter
|
||||
namespace: netbird
|
||||
labels:
|
||||
app.kubernetes.io/name: netbird-api-exporter
|
||||
app.kubernetes.io/component: exporter
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: metrics
|
||||
protocol: TCP
|
||||
name: metrics
|
||||
selector:
|
||||
app.kubernetes.io/name: netbird-api-exporter
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: netbird-api-exporter
|
||||
namespace: netbird
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: netbird-api-exporter
|
||||
endpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
@@ -0,0 +1,21 @@
|
||||
# ExternalSecret for Netbird API token (used by API exporter)
|
||||
# Token is stored in Vault by scripts/setup-netbird-api-token.sh
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: netbird-api-token
|
||||
namespace: netbird
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: netbird-api-token-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: api-token
|
||||
remoteRef:
|
||||
key: netbird-api-token
|
||||
property: api-token
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
# ExternalSecret to pull router setup key from Vault
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: netbird-router-setup-key
|
||||
namespace: netbird
|
||||
spec:
|
||||
refreshInterval: "0"
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: netbird-router-setup-key
|
||||
data:
|
||||
- secretKey: setup-key
|
||||
remoteRef:
|
||||
key: netbird-router-setup-key
|
||||
property: setup-key
|
||||
---
|
||||
# Shared cluster-wide NetBird router deployment (2 replicas)
|
||||
# Routes traffic for all exposed K8s services through NetBird VPN
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: netbird-cluster-router
|
||||
namespace: netbird
|
||||
labels:
|
||||
app.kubernetes.io/name: netbird-cluster-router
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: netbird-cluster-router
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: netbird-cluster-router
|
||||
annotations:
|
||||
# Router pods must opt out of ambient mesh — ztunnel iptables rules
|
||||
# interfere with WireGuard packet forwarding (DNS and TCP).
|
||||
# AuthorizationPolicies use ipBlocks for pod CIDR instead.
|
||||
ambient.istio.io/redirection: disabled
|
||||
spec:
|
||||
containers:
|
||||
- name: netbird
|
||||
image: netbirdio/netbird:0.66.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: NB_SETUP_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: netbird-router-setup-key
|
||||
key: setup-key
|
||||
- name: NB_MANAGEMENT_URL
|
||||
value: "https://netbird.kube.huskypup.net"
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_ADMIN
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
terminationGracePeriodSeconds: 30
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: netbird-oauth
|
||||
namespace: netbird
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: netbird-oauth-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: client-id
|
||||
remoteRef:
|
||||
key: netbird-oauth
|
||||
property: client-id
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: netbird-oauth
|
||||
property: client-secret
|
||||
- secretKey: service-username
|
||||
remoteRef:
|
||||
key: netbird-oauth
|
||||
property: service-username
|
||||
- secretKey: service-password
|
||||
remoteRef:
|
||||
key: netbird-oauth
|
||||
property: service-password
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user