159 lines
6.7 KiB
Bash
159 lines
6.7 KiB
Bash
#!/bin/bash
|
|
# Shared primitives for HTTPS Gateway listener and cert-manager certificate lifecycle.
|
|
#
|
|
# Source from component *-lib.sh scripts. Requires _kubectl to be defined
|
|
# in the calling context (standard in all catalog component libs).
|
|
#
|
|
# Functions:
|
|
# _lib_gateway_patch_https <gw_name> <gw_ns> <listener_name> <domain> <tls_secret> <cert_ns>
|
|
# _lib_gateway_remove_https <gw_name> <gw_ns> <listener_name>
|
|
# _lib_ensure_certificate <cert_name> <cert_ns> <domain> <cluster_issuer> [extra_domain...]
|
|
# _lib_create_cf_secret <secret_name> <cf_token> <cert_manager_ns>
|
|
|
|
# Upsert an HTTPS listener on a Gateway.
|
|
# If the listener already exists, ensures certificateRefs namespace matches cert_ns.
|
|
# Idempotent: safe to call on every update.
|
|
_lib_gateway_patch_https() {
|
|
local gw_name="$1" gw_ns="$2" listener_name="$3" domain="$4" tls_secret="$5" cert_ns="$6"
|
|
|
|
# Opt-out for gateways that own their listeners declaratively (adr-007). When a
|
|
# consumer sets MANAGE_GATEWAY_LISTENER=false, the listener is a property of the
|
|
# gateway's own manifest (e.g. private_gateway.https_listeners) and must NOT be
|
|
# imperatively patched here. Default true preserves behaviour for every workspace
|
|
# whose gateway does not declare the listener.
|
|
if [ "${MANAGE_GATEWAY_LISTENER:-true}" != "true" ]; then
|
|
echo " [skip] listener ${listener_name} owned declaratively by the gateway (MANAGE_GATEWAY_LISTENER=false)"
|
|
return 0
|
|
fi
|
|
|
|
[ -z "$domain" ] && { echo " [skip] domain not set — skipping gateway HTTPS patch"; return 0; }
|
|
|
|
# kubectl resolves the lookup server-side (jsonpath filter) — no JSON parser on the node.
|
|
local exists=""
|
|
[ -n "$(_kubectl get gateway "$gw_name" -n "$gw_ns" \
|
|
-o "jsonpath={.spec.listeners[?(@.name=='${listener_name}')].name}" 2>/dev/null)" ] \
|
|
&& exists="yes"
|
|
|
|
if [ "${exists:-no}" = "yes" ]; then
|
|
local current_cert_ns
|
|
current_cert_ns=$(_kubectl get gateway "$gw_name" -n "$gw_ns" \
|
|
-o "jsonpath={.spec.listeners[?(@.name=='${listener_name}')].tls.certificateRefs[0].namespace}" 2>/dev/null)
|
|
|
|
if [ "${current_cert_ns}" = "${cert_ns}" ]; then
|
|
echo " [skip] gateway listener ${listener_name} exists (cert ns: ${cert_ns})"
|
|
return 0
|
|
fi
|
|
|
|
# Listener array index for the JSON-patch path: kubectl streams names in order,
|
|
# bash counts to the match — no JSON parser.
|
|
local idx="" _i=0 _n
|
|
while IFS= read -r _n; do
|
|
[ "$_n" = "$listener_name" ] && { idx="$_i"; break; }
|
|
_i=$((_i + 1))
|
|
done < <(_kubectl get gateway "$gw_name" -n "$gw_ns" \
|
|
-o 'jsonpath={range .spec.listeners[*]}{.name}{"\n"}{end}' 2>/dev/null)
|
|
|
|
local cert_patch
|
|
cert_patch=$(printf '[{"op":"replace","path":"/spec/listeners/%s/tls/certificateRefs","value":[{"group":"","kind":"Secret","name":"%s","namespace":"%s"}]}]' \
|
|
"$idx" "$tls_secret" "$cert_ns")
|
|
_kubectl patch gateway "$gw_name" -n "$gw_ns" --type=json -p "$cert_patch"
|
|
echo " [ok] gateway ${gw_name}: cert ns updated ${current_cert_ns} → ${cert_ns} on ${listener_name}"
|
|
return 0
|
|
fi
|
|
|
|
local patch
|
|
patch=$(printf '[{"op":"add","path":"/spec/listeners/-","value":{"name":"%s","port":443,"protocol":"HTTPS","hostname":"%s","tls":{"mode":"Terminate","certificateRefs":[{"group":"","kind":"Secret","name":"%s","namespace":"%s"}]},"allowedRoutes":{"namespaces":{"from":"All"}}}}]' \
|
|
"$listener_name" "$domain" "$tls_secret" "$cert_ns")
|
|
_kubectl patch gateway "$gw_name" -n "$gw_ns" --type=json -p "$patch"
|
|
echo " [ok] gateway ${gw_name}: HTTPS listener ${listener_name} added for ${domain}"
|
|
}
|
|
|
|
# Remove an HTTPS listener from a Gateway by name.
|
|
_lib_gateway_remove_https() {
|
|
local gw_name="$1" gw_ns="$2" listener_name="$3"
|
|
|
|
# Symmetric with _lib_gateway_patch_https: when the gateway owns the listener
|
|
# declaratively, deleting the consumer must not strip it from the shared gateway.
|
|
if [ "${MANAGE_GATEWAY_LISTENER:-true}" != "true" ]; then
|
|
echo " [skip] listener ${listener_name} owned declaratively by the gateway (MANAGE_GATEWAY_LISTENER=false)"
|
|
return 0
|
|
fi
|
|
|
|
local idx="" _i=0 _n
|
|
while IFS= read -r _n; do
|
|
[ "$_n" = "$listener_name" ] && { idx="$_i"; break; }
|
|
_i=$((_i + 1))
|
|
done < <(_kubectl get gateway "$gw_name" -n "$gw_ns" \
|
|
-o 'jsonpath={range .spec.listeners[*]}{.name}{"\n"}{end}' 2>/dev/null)
|
|
|
|
[ -z "$idx" ] && { echo " [skip] gateway listener ${listener_name} not found"; return 0; }
|
|
|
|
_kubectl patch gateway "$gw_name" -n "$gw_ns" --type=json \
|
|
-p "[{\"op\":\"remove\",\"path\":\"/spec/listeners/${idx}\"}]"
|
|
echo " [ok] gateway ${gw_name}: HTTPS listener ${listener_name} removed"
|
|
}
|
|
|
|
# Ensure a cert-manager Certificate resource exists in the given namespace.
|
|
# Idempotent: creates only if absent. Does not overwrite existing certificates.
|
|
# Extra domains beyond the first are added as additional dnsNames.
|
|
_lib_ensure_certificate() {
|
|
local cert_name="$1" cert_ns="$2" domain="$3" cluster_issuer="$4"
|
|
shift 4
|
|
local extra_domains=("$@")
|
|
|
|
local existing
|
|
existing=$(_kubectl get certificate "$cert_name" -n "$cert_ns" --ignore-not-found -o name 2>/dev/null)
|
|
if [ -n "$existing" ]; then
|
|
echo " [skip] certificate ${cert_name} already exists in ${cert_ns}"
|
|
return 0
|
|
fi
|
|
|
|
# Build the dnsNames YAML list (primary domain + any extras) in pure bash —
|
|
# one indented ' - "domain"' line per non-empty entry, no trailing newline.
|
|
local dns_names="" _d
|
|
for _d in "$domain" "${extra_domains[@]}"; do
|
|
[ -z "$_d" ] && continue
|
|
dns_names="${dns_names} - \"${_d}\""$'\n'
|
|
done
|
|
dns_names="${dns_names%$'\n'}"
|
|
|
|
_kubectl apply -f - <<EOF
|
|
apiVersion: cert-manager.io/v1
|
|
kind: Certificate
|
|
metadata:
|
|
name: ${cert_name}
|
|
namespace: ${cert_ns}
|
|
labels:
|
|
app.kubernetes.io/managed-by: provisioning
|
|
spec:
|
|
secretName: ${cert_name}
|
|
issuerRef:
|
|
name: ${cluster_issuer}
|
|
kind: ClusterIssuer
|
|
dnsNames:
|
|
${dns_names}
|
|
usages:
|
|
- server auth
|
|
- digital signature
|
|
- key encipherment
|
|
EOF
|
|
echo " [ok] certificate ${cert_name} created in ${cert_ns}"
|
|
}
|
|
|
|
# Create or update the Cloudflare API token secret in the cert-manager namespace.
|
|
# Used by ClusterIssuers that require a CF token for DNS-01 challenges.
|
|
_lib_create_cf_secret() {
|
|
local secret_name="$1" cf_token="$2" cert_manager_ns="${3:-cert-manager}"
|
|
|
|
[ -z "$cf_token" ] && { echo " [warn] CF token empty — skipping secret ${secret_name}"; return 0; }
|
|
|
|
if _kubectl get secret "$secret_name" -n "$cert_manager_ns" >/dev/null 2>&1; then
|
|
echo " [skip] CF token secret ${secret_name} already exists in ${cert_manager_ns}"
|
|
return 0
|
|
fi
|
|
|
|
_kubectl create secret generic "$secret_name" \
|
|
--namespace "$cert_manager_ns" \
|
|
--from-literal=api-token="$cf_token"
|
|
echo " [ok] CF token secret ${secret_name} created in ${cert_manager_ns}"
|
|
}
|