add htpasswd auth to private docker registry + expose at registry.viktorbarzin.me
- Add auth.htpasswd section to config-private.yml - Mount htpasswd file in registry-private container, fix healthcheck for 401 - Rename registry UI from registry.viktorbarzin.me → docker.viktorbarzin.me - Add Docker CLI ingress at registry.viktorbarzin.me (HTTPS backend, no rate-limit, unlimited body) - Add docker to cloudflare_proxied_names (registry stays non-proxied) - Add Kyverno ClusterPolicy to sync registry-credentials secret to all namespaces - Update infra provisioning to install apache2-utils and generate htpasswd from Vault
This commit is contained in:
parent
e4f478b490
commit
36171bcda4
6 changed files with 123 additions and 5 deletions
BIN
config.tfvars
BIN
config.tfvars
Binary file not shown.
|
|
@ -16,6 +16,10 @@ storage:
|
|||
age: 168h
|
||||
interval: 4h
|
||||
dryrun: false
|
||||
auth:
|
||||
htpasswd:
|
||||
realm: "Registry Realm"
|
||||
path: /auth/htpasswd
|
||||
http:
|
||||
addr: :5000
|
||||
headers:
|
||||
|
|
|
|||
|
|
@ -92,10 +92,12 @@ services:
|
|||
volumes:
|
||||
- /opt/registry/data/private:/var/lib/registry
|
||||
- /opt/registry/config-private.yml:/etc/docker/registry/config.yml:ro
|
||||
- /opt/registry/htpasswd:/auth/htpasswd:ro
|
||||
networks:
|
||||
- registry
|
||||
healthcheck:
|
||||
test: ["CMD", "sh", "-c", "wget -qO- http://localhost:5000/v2/ >/dev/null 2>&1"]
|
||||
# 401 is expected (auth required) — any HTTP response means the registry is healthy
|
||||
test: ["CMD", "sh", "-c", "wget -qS -O /dev/null http://localhost:5000/v2/ 2>&1 | grep -q 'HTTP/'"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ data "vault_kv_secret_v2" "secrets" {
|
|||
name = "infra"
|
||||
}
|
||||
|
||||
data "vault_kv_secret_v2" "viktor" {
|
||||
mount = "secret"
|
||||
name = "viktor"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Locals
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -176,8 +181,8 @@ module "docker-registry-template" {
|
|||
|
||||
# Setup registry config and start container
|
||||
provision_cmds = [
|
||||
# Install and enable QEMU guest agent for remote management
|
||||
"apt-get install -y qemu-guest-agent",
|
||||
# Install dependencies (QEMU guest agent + htpasswd for registry auth)
|
||||
"apt-get install -y qemu-guest-agent apache2-utils",
|
||||
"systemctl enable qemu-guest-agent",
|
||||
"systemctl start qemu-guest-agent",
|
||||
# Stop host nginx — we run nginx inside Docker instead
|
||||
|
|
@ -185,6 +190,11 @@ module "docker-registry-template" {
|
|||
"systemctl disable nginx || true",
|
||||
# Create directory structure
|
||||
"mkdir -p /opt/registry/data/dockerhub /opt/registry/data/ghcr /opt/registry/data/quay /opt/registry/data/k8s /opt/registry/data/kyverno /opt/registry/data/private /opt/registry/tls",
|
||||
# Generate htpasswd file for private registry authentication
|
||||
format("htpasswd -Bbn %s %s > /opt/registry/htpasswd",
|
||||
data.vault_kv_secret_v2.viktor.data["registry_user"],
|
||||
data.vault_kv_secret_v2.viktor.data["registry_password"]
|
||||
),
|
||||
# Write Docker Compose file
|
||||
format("echo %s | base64 -d > /opt/registry/docker-compose.yml",
|
||||
base64encode(file("${path.root}/../../modules/docker-registry/docker-compose.yml"))
|
||||
|
|
|
|||
83
stacks/kyverno/modules/kyverno/registry-credentials.tf
Normal file
83
stacks/kyverno/modules/kyverno/registry-credentials.tf
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
# =============================================================================
|
||||
# Private Docker Registry Credentials — Auto-sync to all namespaces
|
||||
# =============================================================================
|
||||
# Source secret in kyverno namespace, cloned by ClusterPolicy into every NS.
|
||||
# Pods use imagePullSecrets: [{name: registry-credentials}] to pull from
|
||||
# registry.viktorbarzin.me (or 10.0.20.10:5050 internally).
|
||||
|
||||
data "vault_kv_secret_v2" "viktor" {
|
||||
mount = "secret"
|
||||
name = "viktor"
|
||||
}
|
||||
|
||||
resource "kubernetes_secret" "registry_credentials" {
|
||||
metadata {
|
||||
name = "registry-credentials"
|
||||
namespace = kubernetes_namespace.kyverno.metadata[0].name
|
||||
}
|
||||
type = "kubernetes.io/dockerconfigjson"
|
||||
data = {
|
||||
".dockerconfigjson" = jsonencode({
|
||||
auths = {
|
||||
"registry.viktorbarzin.me" = {
|
||||
auth = base64encode("${data.vault_kv_secret_v2.viktor.data["registry_user"]}:${data.vault_kv_secret_v2.viktor.data["registry_password"]}")
|
||||
}
|
||||
"10.0.20.10:5050" = {
|
||||
auth = base64encode("${data.vault_kv_secret_v2.viktor.data["registry_user"]}:${data.vault_kv_secret_v2.viktor.data["registry_password"]}")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_manifest" "sync_registry_credentials" {
|
||||
manifest = {
|
||||
apiVersion = "kyverno.io/v1"
|
||||
kind = "ClusterPolicy"
|
||||
metadata = {
|
||||
name = "sync-registry-credentials"
|
||||
}
|
||||
spec = {
|
||||
rules = [
|
||||
{
|
||||
name = "sync-registry-secret"
|
||||
match = {
|
||||
any = [
|
||||
{
|
||||
resources = {
|
||||
kinds = ["Namespace"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
exclude = {
|
||||
any = [
|
||||
{
|
||||
resources = {
|
||||
namespaces = ["kube-system", "kube-public", "kube-node-lease"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
generate = {
|
||||
apiVersion = "v1"
|
||||
kind = "Secret"
|
||||
name = "registry-credentials"
|
||||
namespace = "{{request.object.metadata.name}}"
|
||||
synchronize = true
|
||||
clone = {
|
||||
namespace = "kyverno"
|
||||
name = "registry-credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
helm_release.kyverno,
|
||||
kubernetes_secret.registry_credentials,
|
||||
]
|
||||
}
|
||||
|
|
@ -186,10 +186,10 @@ module "proxmox" {
|
|||
}
|
||||
}
|
||||
|
||||
# https://registry.viktorbarzin.me/
|
||||
# https://docker.viktorbarzin.me/ (registry web UI)
|
||||
module "docker-registry-ui" {
|
||||
source = "./factory"
|
||||
name = "registry"
|
||||
name = "docker"
|
||||
external_name = "docker-registry.viktorbarzin.lan"
|
||||
port = 8080
|
||||
tls_secret_name = var.tls_secret_name
|
||||
|
|
@ -206,6 +206,25 @@ module "docker-registry-ui" {
|
|||
}
|
||||
}
|
||||
|
||||
# https://registry.viktorbarzin.me/ (Docker CLI push/pull endpoint)
|
||||
module "docker-registry-cli" {
|
||||
source = "./factory"
|
||||
name = "registry"
|
||||
external_name = "docker-registry.viktorbarzin.lan"
|
||||
port = 5050
|
||||
backend_protocol = "HTTPS"
|
||||
tls_secret_name = var.tls_secret_name
|
||||
protected = false # Docker CLI uses htpasswd, NOT Authentik
|
||||
max_body_size = "0" # unlimited - Docker layers can be large
|
||||
depends_on = [kubernetes_namespace.reverse-proxy]
|
||||
extra_annotations = {
|
||||
# Skip rate-limit (Docker push/pull generates many rapid requests)
|
||||
# Keep CrowdSec for L7 protection
|
||||
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd"
|
||||
"gethomepage.dev/enabled" = "false"
|
||||
}
|
||||
}
|
||||
|
||||
# https://valchedrym.viktorbarzin.me/
|
||||
module "valchedrym" {
|
||||
source = "./factory"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue