sync dari sicabe-doc/deploy @ e8f8fde
This commit is contained in:
+5
-3
@@ -713,9 +713,11 @@ bootstrap_starrocks() {
|
|||||||
# verify-phase4 KC_BASE). single-host → localhost:12080; cluster → <owner>:12080.
|
# verify-phase4 KC_BASE). single-host → localhost:12080; cluster → <owner>:12080.
|
||||||
# Sebelumnya pakai KEYCLOAK_URL (default localhost) → issuer salah saat keycloak existing.
|
# Sebelumnya pakai KEYCLOAK_URL (default localhost) → issuer salah saat keycloak existing.
|
||||||
local kc_base
|
local kc_base
|
||||||
kc_base="$(sicabe_local_url http KEYCLOAK_HOST "${KEYCLOAK_PORT:-12080}" KEYCLOAK_URL)"
|
kc_base="$(sicabe_keycloak_url)"
|
||||||
# Alamat Keycloak server→server (dari container StarRocks): service/owner + port container.
|
# Alamat Keycloak server→server (dari container StarRocks). KEYCLOAK_INTERNAL_URL bila
|
||||||
local kc_internal="http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}"
|
# diset (Keycloak EKSTERNAL ber-URL penuh, mis. https://sso.example.com), selain itu
|
||||||
|
# dibangun dari HOST:PORT (mis. 100.93.54.108:12080).
|
||||||
|
local kc_internal="${KEYCLOAK_INTERNAL_URL:-http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}}"
|
||||||
|
|
||||||
sr_mysql() {
|
sr_mysql() {
|
||||||
mysql -h 127.0.0.1 -P "${sr_port}" -u root -p"${STARROCKS_ROOT_PASSWORD}" -e "$1" 2>/dev/null
|
mysql -h 127.0.0.1 -P "${sr_port}" -u root -p"${STARROCKS_ROOT_PASSWORD}" -e "$1" 2>/dev/null
|
||||||
|
|||||||
@@ -62,6 +62,22 @@ components:
|
|||||||
# StarRocks FE ke host control bila compute sudah terlalu padat.
|
# StarRocks FE ke host control bila compute sudah terlalu padat.
|
||||||
# starrocks-fe: { host: control }
|
# starrocks-fe: { host: control }
|
||||||
|
|
||||||
|
# ── Komponen EKSTERNAL (dikelola sistem lain) ───────────────────────────────
|
||||||
|
# Tandai dengan `address` — maka komponen TIDAK dijalankan di cluster ini dan
|
||||||
|
# koneksi diarahkan ke alamat itu. Host = alamat IP/hostname biasa:
|
||||||
|
# keycloak: { address: 100.93.54.108 }
|
||||||
|
# URL boleh berupa **URL PENUH** (skema + host + port). Skema & port dihormati
|
||||||
|
# apa adanya (port default per skema, mis. https→443, boleh dihilangkan):
|
||||||
|
# keycloak: { address: https://sso.example.com }
|
||||||
|
# minio: { address: https://minio.corp:9000 }
|
||||||
|
# nessie: { address: https://nessie.corp }
|
||||||
|
# openldap: { address: ldaps://ldap.corp:636 }
|
||||||
|
# opa: { address: https://opa.corp }
|
||||||
|
# Efek URL penuh: `*_HOST` = host saja; `*_URL`/`*_PUBLIC_URL` = URL itu;
|
||||||
|
# server→server OIDC diarahkan lewat KEYCLOAK_INTERNAL_URL (Trino/Grafana);
|
||||||
|
# `https://…` ⇒ KEYCLOAK_SSL_REQUIRED=external. `host` dan `address` tak boleh
|
||||||
|
# bersamaan (host = dijalankan cluster; address = dikelola pihak lain).
|
||||||
|
|
||||||
# Dremio default NONAKTIF. Bila diaktifkan: taruh di host bertier `engine`
|
# Dremio default NONAKTIF. Bila diaktifkan: taruh di host bertier `engine`
|
||||||
# (butuh RAM ~4 GB lebih — cek kapasitas host).
|
# (butuh RAM ~4 GB lebih — cek kapasitas host).
|
||||||
dremio: { enabled: false }
|
dremio: { enabled: false }
|
||||||
|
|||||||
@@ -41,17 +41,18 @@ http-server.process-forwarded=${ENV:TRINO_PROCESS_FORWARDED}
|
|||||||
# single-host (localhost) dan multi-host tanpa TLS (http://<alamat-host-control>:12080).
|
# single-host (localhost) dan multi-host tanpa TLS (http://<alamat-host-control>:12080).
|
||||||
# Kalau di-hardcode, issuer di klaster multi-host tidak cocok dan token ditolak.
|
# Kalau di-hardcode, issuer di klaster multi-host tidak cocok dan token ditolak.
|
||||||
http-server.authentication.oauth2.issuer=${ENV:KEYCLOAK_PUBLIC_URL}/realms/sicabe
|
http-server.authentication.oauth2.issuer=${ENV:KEYCLOAK_PUBLIC_URL}/realms/sicabe
|
||||||
# Matikan OIDC discovery — Trino tidak bisa mengakses URL publik dari dalam container.
|
# Server-ke-server (token/jwks) pakai KEYCLOAK_INTERNAL_URL — URL penuh
|
||||||
# Endpoint di-set manual: browser pakai URL publik (auth-url); server-ke-server (token/jwks)
|
# (skema+host+port). Di multi-host dgn Keycloak EKSTERNAL berdomain mis.
|
||||||
# pakai KEYCLOAK_HOST:INTERNAL_PORT — di multi-host dgn Keycloak EKSTERNAL ini = ALAMAT host
|
# `https://sso.example.com`, nilainya persis itu (skema+port dihormati). Selain itu
|
||||||
# pemilik (mis. 100.93.54.108:12080). JANGAN literal `keycloak` (nama container, tak resolve
|
# compose mengisi default `http://${KEYCLOAK_HOST}:${KEYCLOAK_INTERNAL_PORT}`
|
||||||
|
# (mis. 100.93.54.108:12080). JANGAN literal `keycloak` (nama container, tak resolve
|
||||||
# di host lain → penukaran token gagal → "Authentication response could not be verified").
|
# di host lain → penukaran token gagal → "Authentication response could not be verified").
|
||||||
http-server.authentication.oauth2.oidc.discovery=false
|
http-server.authentication.oauth2.oidc.discovery=false
|
||||||
# Endpoint browser-facing (auth redirect) — harus URL publik juga
|
# Endpoint browser-facing (auth redirect) — harus URL publik juga
|
||||||
http-server.authentication.oauth2.auth-url=${ENV:KEYCLOAK_PUBLIC_URL}/realms/sicabe/protocol/openid-connect/auth
|
http-server.authentication.oauth2.auth-url=${ENV:KEYCLOAK_PUBLIC_URL}/realms/sicabe/protocol/openid-connect/auth
|
||||||
# Endpoint server-side (token exchange & JWKS) — reachable dari container
|
# Endpoint server-side (token exchange & JWKS) — reachable dari container
|
||||||
http-server.authentication.oauth2.token-url=http://${ENV:KEYCLOAK_HOST}:${ENV:KEYCLOAK_INTERNAL_PORT}/realms/sicabe/protocol/openid-connect/token
|
http-server.authentication.oauth2.token-url=${ENV:KEYCLOAK_INTERNAL_URL}/realms/sicabe/protocol/openid-connect/token
|
||||||
http-server.authentication.oauth2.jwks-url=http://${ENV:KEYCLOAK_HOST}:${ENV:KEYCLOAK_INTERNAL_PORT}/realms/sicabe/protocol/openid-connect/certs
|
http-server.authentication.oauth2.jwks-url=${ENV:KEYCLOAK_INTERNAL_URL}/realms/sicabe/protocol/openid-connect/certs
|
||||||
# Kunci HMAC tetap untuk `state` OAuth2 (default Trino = acak tiap coordinator start →
|
# Kunci HMAC tetap untuk `state` OAuth2 (default Trino = acak tiap coordinator start →
|
||||||
# sesi login batal saat restart). Nilai acak-per-instalasi dari .env.
|
# sesi login batal saat restart). Nilai acak-per-instalasi dari .env.
|
||||||
http-server.authentication.oauth2.state-key=${ENV:TRINO_OAUTH_STATE_KEY}
|
http-server.authentication.oauth2.state-key=${ENV:TRINO_OAUTH_STATE_KEY}
|
||||||
|
|||||||
@@ -489,6 +489,10 @@ services:
|
|||||||
# EKSTERNAL: ini = ALAMAT host pemilik (mis. 100.93.54.108) — bukan nama container
|
# EKSTERNAL: ini = ALAMAT host pemilik (mis. 100.93.54.108) — bukan nama container
|
||||||
# `keycloak` yang tak bisa di-resolve → penukaran token gagal ("could not be verified").
|
# `keycloak` yang tak bisa di-resolve → penukaran token gagal ("could not be verified").
|
||||||
KEYCLOAK_HOST: ${KEYCLOAK_HOST:-keycloak}
|
KEYCLOAK_HOST: ${KEYCLOAK_HOST:-keycloak}
|
||||||
|
# URL server→server LENGKAP (skema+host+port) untuk token/jwks. Default dibangun
|
||||||
|
# dari HOST:PORT; generator menimpanya bila Keycloak EKSTERNAL ber-URL penuh
|
||||||
|
# (mis. https://sso.example.com) — lihat config-coordinator.properties.
|
||||||
|
KEYCLOAK_INTERNAL_URL: ${KEYCLOAK_INTERNAL_URL:-http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}}
|
||||||
# Kunci HMAC tetap untuk state OAuth2 Trino (default Trino acak tiap start).
|
# Kunci HMAC tetap untuk state OAuth2 Trino (default Trino acak tiap start).
|
||||||
TRINO_OAUTH_STATE_KEY: ${TRINO_OAUTH_STATE_KEY:-trino-oauth-state-dev-only}
|
TRINO_OAUTH_STATE_KEY: ${TRINO_OAUTH_STATE_KEY:-trino-oauth-state-dev-only}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -1361,8 +1365,8 @@ services:
|
|||||||
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: grafana
|
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: grafana
|
||||||
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "${GRAFANA_CLIENT_SECRET:?err}"
|
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "${GRAFANA_CLIENT_SECRET:?err}"
|
||||||
GF_AUTH_GENERIC_OAUTH_AUTH_URL: http://localhost:${KEYCLOAK_PORT:-12080}/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/auth
|
GF_AUTH_GENERIC_OAUTH_AUTH_URL: http://localhost:${KEYCLOAK_PORT:-12080}/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/auth
|
||||||
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/token
|
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "${KEYCLOAK_INTERNAL_URL:-http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}}/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/token"
|
||||||
GF_AUTH_GENERIC_OAUTH_API_URL: http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/userinfo
|
GF_AUTH_GENERIC_OAUTH_API_URL: "${KEYCLOAK_INTERNAL_URL:-http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}}/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/userinfo"
|
||||||
GF_AUTH_GENERIC_OAUTH_SCOPES: openid profile email
|
GF_AUTH_GENERIC_OAUTH_SCOPES: openid profile email
|
||||||
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: contains(roles[*], 'admin') && 'Admin' || 'Viewer'
|
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: contains(roles[*], 'admin') && 'Admin' || 'Viewer'
|
||||||
GF_AUTH_DISABLE_LOGIN_FORM: "false"
|
GF_AUTH_DISABLE_LOGIN_FORM: "false"
|
||||||
|
|||||||
@@ -96,6 +96,30 @@ PUBLIC_URL_ENDPOINTS = {
|
|||||||
"MINIO_CONSOLE_PUBLIC_URL": ("minio", "http://{addr}:12901"),
|
"MINIO_CONSOLE_PUBLIC_URL": ("minio", "http://{addr}:12901"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _ext_url(ext: str, template: str, port: str = "") -> str:
|
||||||
|
"""URL untuk komponen EKSTERNAL dari `components.<c>.address`.
|
||||||
|
|
||||||
|
Bila operator menulis **URL penuh** (mengandung `://`, mis.
|
||||||
|
`https://sso.example.com`) → pakai APA ADANYA: skema + host + port dihormati
|
||||||
|
(default port per skema, mis. https→443, TIDAK ditulis bila default). Bila hanya
|
||||||
|
host/IP polos (perilaku lama) → susun dari `template` + `port` seperti sebelumnya.
|
||||||
|
"""
|
||||||
|
scheme, host, p = inventory.parse_address(ext)
|
||||||
|
if scheme:
|
||||||
|
# Buang port bila = default skema (https→443), supaya `https://host`, bukan
|
||||||
|
# `https://host:443` (ekuivalen, tapi lebih bersih & cocok utk issuer/URL publik).
|
||||||
|
show = p and p != inventory._DEFAULT_SCHEME_PORT.get(scheme)
|
||||||
|
return f"{scheme}://{host}" + (f":{p}" if show else "")
|
||||||
|
if port and "{port}" in template:
|
||||||
|
return template.format(addr=host, port=(p if p else port))
|
||||||
|
return template.format(addr=host)
|
||||||
|
|
||||||
|
|
||||||
|
def _keycloak_external_scheme(doc: dict) -> str:
|
||||||
|
"""Skema URL Keycloak eksternal ('' bila bukan URL penuh). Untuk sslRequired."""
|
||||||
|
return inventory.parse_address(inventory.external_address(doc, "keycloak") or "")[0]
|
||||||
|
|
||||||
# SENGAJA TIDAK diarahkan ke IP: KEYCLOAK_URL / TRINO_URL dan var `*_URL` lain yang
|
# SENGAJA TIDAK diarahkan ke IP: KEYCLOAK_URL / TRINO_URL dan var `*_URL` lain yang
|
||||||
# bersifat identitas (issuer OIDC harus stabil — lihat R10 §4.10.1: pakai DNS
|
# bersifat identitas (issuer OIDC harus stabil — lihat R10 §4.10.1: pakai DNS
|
||||||
# internal, bukan IP). Itu milik operator dan harus sudah benar di `cluster.yml`
|
# internal, bukan IP). Itu milik operator dan harus sudah benar di `cluster.yml`
|
||||||
@@ -195,7 +219,7 @@ def _is_topology_var(line: str) -> bool:
|
|||||||
"""
|
"""
|
||||||
key = line.split("=", 1)[0].strip()
|
key = line.split("=", 1)[0].strip()
|
||||||
owned = {
|
owned = {
|
||||||
"EXTERNAL_SERVICES", "LOCAL_SERVICES", "KEYCLOAK_SSL_REQUIRED",
|
"EXTERNAL_SERVICES", "LOCAL_SERVICES", "KEYCLOAK_SSL_REQUIRED", "KEYCLOAK_INTERNAL_URL",
|
||||||
"KC_PROXY_HEADERS", "TRINO_PROCESS_FORWARDED", "CLUSTER_DOMAIN", "TRAEFIK_BIND_ADDR",
|
"KC_PROXY_HEADERS", "TRINO_PROCESS_FORWARDED", "CLUSTER_DOMAIN", "TRAEFIK_BIND_ADDR",
|
||||||
*ENDPOINT_VARS, *URL_ENDPOINT_VARS,
|
*ENDPOINT_VARS, *URL_ENDPOINT_VARS,
|
||||||
*inventory.PUBLIC_URL_SERVICES, # KEYCLOAK_PUBLIC_URL, GRAFANA_PUBLIC_URL, …
|
*inventory.PUBLIC_URL_SERVICES, # KEYCLOAK_PUBLIC_URL, GRAFANA_PUBLIC_URL, …
|
||||||
@@ -458,7 +482,10 @@ def endpoint_lines(doc: dict, host: str, plan: dict) -> list[str]:
|
|||||||
for var, component in ENDPOINT_VARS.items():
|
for var, component in ENDPOINT_VARS.items():
|
||||||
ext = inventory.external_address(doc, component)
|
ext = inventory.external_address(doc, component)
|
||||||
if ext:
|
if ext:
|
||||||
lines.append(f"{var}={ext}") # EKSTERNAL (pihak lain) → alamat tetap
|
# EKSTERNAL → HOST saja (buang skema/port bila operator menulis URL penuh,
|
||||||
|
# mis. `https://sso.example.com` → `sso.example.com`). URL lengkapnya
|
||||||
|
# ditangani `*_URL` / KEYCLOAK_INTERNAL_URL, bukan var `*_HOST`.
|
||||||
|
lines.append(f"{var}={inventory.parse_address(ext)[1]}")
|
||||||
continue
|
continue
|
||||||
owners = placement.get(component)
|
owners = placement.get(component)
|
||||||
if not owners:
|
if not owners:
|
||||||
@@ -467,6 +494,13 @@ def endpoint_lines(doc: dict, host: str, plan: dict) -> list[str]:
|
|||||||
lines.append(f"{var}={component}") # SE-HOST → nama service
|
lines.append(f"{var}={component}") # SE-HOST → nama service
|
||||||
else:
|
else:
|
||||||
lines.append(f"{var}={plan[owners[0]]['address']}") # LINTAS host → alamat
|
lines.append(f"{var}={plan[owners[0]]['address']}") # LINTAS host → alamat
|
||||||
|
# Keycloak: URL server→server (token/jwks/Grafana). Hanya ditulis bila Keycloak
|
||||||
|
# EKSTERNAL memakai URL PENUH (mis. https://sso.example.com) agar skema+port
|
||||||
|
# dihormati; selain itu compose/script memakai default
|
||||||
|
# `http://${KEYCLOAK_HOST:-keycloak}:${KEYCLOAK_INTERNAL_PORT:-8080}`.
|
||||||
|
kc_ext = inventory.external_address(doc, "keycloak")
|
||||||
|
if kc_ext and inventory.parse_address(kc_ext)[0]:
|
||||||
|
lines.append(f"KEYCLOAK_INTERNAL_URL={_ext_url(kc_ext, '')}")
|
||||||
disabled = [n for n, v in inventory.enabled_components(doc).items() if v is False]
|
disabled = [n for n, v in inventory.enabled_components(doc).items() if v is False]
|
||||||
if disabled:
|
if disabled:
|
||||||
# Komponen `enabled: false` = tidak dijalankan di host mana pun → installer
|
# Komponen `enabled: false` = tidak dijalankan di host mana pun → installer
|
||||||
@@ -498,7 +532,7 @@ def public_url_lines(doc: dict, plan: dict | None = None) -> list[str]:
|
|||||||
for var, (component, tmpl) in PUBLIC_URL_ENDPOINTS.items():
|
for var, (component, tmpl) in PUBLIC_URL_ENDPOINTS.items():
|
||||||
ext = inventory.external_address(doc, component)
|
ext = inventory.external_address(doc, component)
|
||||||
if ext:
|
if ext:
|
||||||
resolved[var] = tmpl.format(addr=ext) # eksternal → alamat tetap
|
resolved[var] = _ext_url(ext, tmpl) # eksternal → URL penuh dihormati
|
||||||
continue
|
continue
|
||||||
owners = placement.get(component)
|
owners = placement.get(component)
|
||||||
if not owners:
|
if not owners:
|
||||||
@@ -523,8 +557,11 @@ def tls_policy_lines(doc: dict) -> list[str]:
|
|||||||
tls.mode `edge`/`full` -> `external` (ada Traefik/TLS di depan)
|
tls.mode `edge`/`full` -> `external` (ada Traefik/TLS di depan)
|
||||||
"""
|
"""
|
||||||
edge = inventory.tls_mode(doc) != "none"
|
edge = inventory.tls_mode(doc) != "none"
|
||||||
|
# Keycloak EKSTERNAL ber-URL `https://…` juga menuntut sslRequired=external (permintaan
|
||||||
|
# dari alamat non-lokal lewat TLS di depannya).
|
||||||
|
ext_https = _keycloak_external_scheme(doc) == "https"
|
||||||
return [
|
return [
|
||||||
f"KEYCLOAK_SSL_REQUIRED={'external' if edge else 'none'}",
|
f"KEYCLOAK_SSL_REQUIRED={'external' if (edge or ext_https) else 'none'}",
|
||||||
# Di belakang Traefik, Keycloak WAJIB memercayai X-Forwarded-* — tanpa itu
|
# Di belakang Traefik, Keycloak WAJIB memercayai X-Forwarded-* — tanpa itu
|
||||||
# permintaan HTTPS dari proxy dianggap HTTP (redirect salah / tuntutan HTTPS).
|
# permintaan HTTPS dari proxy dianggap HTTP (redirect salah / tuntutan HTTPS).
|
||||||
# Sengaja KOSONG saat tanpa proxy: memasangnya membuat Keycloak memercayai
|
# Sengaja KOSONG saat tanpa proxy: memasangnya membuat Keycloak memercayai
|
||||||
@@ -665,7 +702,7 @@ def url_endpoint_lines(doc: dict, host: str, plan: dict, env_path=None) -> list[
|
|||||||
for var, (component, template) in URL_ENDPOINT_VARS.items():
|
for var, (component, template) in URL_ENDPOINT_VARS.items():
|
||||||
ext = inventory.external_address(doc, component)
|
ext = inventory.external_address(doc, component)
|
||||||
if ext:
|
if ext:
|
||||||
lines.append(f"{var}={template.format(addr=ext, port=_endpoint_port(var, env_path))}")
|
lines.append(f"{var}={_ext_url(ext, template, _endpoint_port(var, env_path))}")
|
||||||
continue
|
continue
|
||||||
owners = placement.get(component)
|
owners = placement.get(component)
|
||||||
if not owners:
|
if not owners:
|
||||||
|
|||||||
@@ -145,6 +145,47 @@ def external_address(doc: dict, service: str) -> str | None:
|
|||||||
return a or None
|
return a or None
|
||||||
|
|
||||||
|
|
||||||
|
# Port default per skema — dipakai parse_address() bila operator menulis URL penuh
|
||||||
|
# TANPA port (mis. `https://sso.example.com` → port 443).
|
||||||
|
_DEFAULT_SCHEME_PORT = {
|
||||||
|
"https": 443, "http": 80, "ldaps": 636, "ldap": 389,
|
||||||
|
"postgres": 5432, "postgresql": 5432,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_address(addr: str) -> tuple[str, str, int | None]:
|
||||||
|
"""Pisah alamat komponen → (skema, host, port).
|
||||||
|
|
||||||
|
Menerima dua bentuk:
|
||||||
|
- **URL penuh** (skema eksplisit): `https://sso.example.com` → ('https','sso.example.com',443)
|
||||||
|
`http://minio.corp:9000` → ('http','minio.corp',9000)
|
||||||
|
- **host/IP polos** (perilaku lama): `sso.example.com` → ('','sso.example.com',None)
|
||||||
|
`10.20.0.10` → ('','10.20.0.10',None)
|
||||||
|
|
||||||
|
Skema non-standar diterima apa adanya; path (`/foo`) dibuang.
|
||||||
|
"""
|
||||||
|
a = (addr or "").strip()
|
||||||
|
scheme = ""
|
||||||
|
if "://" in a:
|
||||||
|
scheme, a = a.split("://", 1)
|
||||||
|
scheme = scheme.strip().lower()
|
||||||
|
a = a.split("/", 1)[0] # buang path bila ada
|
||||||
|
host, sep, port_s = a.rpartition(":")
|
||||||
|
# host:port HANYA bila tepat satu ':' dan port numerik — melindungi IPv6
|
||||||
|
# (mis. `2001:db8::1`) agar tak salah dipecah.
|
||||||
|
if sep and port_s.isdigit() and a.count(":") == 1:
|
||||||
|
host, port = host, int(port_s)
|
||||||
|
else:
|
||||||
|
host, port = a, _DEFAULT_SCHEME_PORT.get(scheme)
|
||||||
|
return scheme, host, port
|
||||||
|
|
||||||
|
|
||||||
|
def external_host(doc: dict, service: str) -> str | None:
|
||||||
|
"""HOST saja (tanpa skema/port) dari alamat eksternal — untuk var `*_HOST`."""
|
||||||
|
a = external_address(doc, service)
|
||||||
|
return parse_address(a)[1] if a else None
|
||||||
|
|
||||||
|
|
||||||
def resolve(doc: dict) -> dict[str, list[str]]:
|
def resolve(doc: dict) -> dict[str, list[str]]:
|
||||||
"""Komponen → daftar host (tier `engine` bisa >1 host). Komponen EKSTERNAL
|
"""Komponen → daftar host (tier `engine` bisa >1 host). Komponen EKSTERNAL
|
||||||
(ber-`address`) TIDAK masuk hasil — ia bukan milik host mana pun."""
|
(ber-`address`) TIDAK masuk hasil — ia bukan milik host mana pun."""
|
||||||
|
|||||||
@@ -188,6 +188,17 @@ sicabe_local_url() { # <scheme> <host_var> <port> [url_var]
|
|||||||
echo "${_slu_s}://localhost:${_slu_p}"
|
echo "${_slu_s}://localhost:${_slu_p}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ─── Helper: URL Keycloak yang REACHABLE dari host ini ──────────────────────
|
||||||
|
# Prioritas: KEYCLOAK_INTERNAL_URL (URL penuh skema+host+port, mis.
|
||||||
|
# `https://sso.example.com` — generator menulisnya bila Keycloak EKSTERNAL ber-URL
|
||||||
|
# penuh) → KEYCLOAK_URL eksplisit non-localhost (override operator) → host+port
|
||||||
|
# (perilaku lama: nama service bila co-located, alamat pemilik bila lintas host).
|
||||||
|
# Dipakai gate/verifikasi & bootstrap agar tak perlu set KEYCLOAK_URL manual.
|
||||||
|
sicabe_keycloak_url() {
|
||||||
|
if [[ -n "${KEYCLOAK_INTERNAL_URL:-}" ]]; then printf '%s' "${KEYCLOAK_INTERNAL_URL}"; return 0; fi
|
||||||
|
sicabe_local_url http KEYCLOAK_HOST "${KEYCLOAK_PORT:-12080}" KEYCLOAK_URL
|
||||||
|
}
|
||||||
|
|
||||||
# ─── Helper: HOST endpoint efektif (sadar EXTERNAL & multi-host) ────────────
|
# ─── Helper: HOST endpoint efektif (sadar EXTERNAL & multi-host) ────────────
|
||||||
# Urutan: (1) <fallback_var> bila NON-localhost (override operator / komponen EXTERNAL);
|
# Urutan: (1) <fallback_var> bila NON-localhost (override operator / komponen EXTERNAL);
|
||||||
# (2) multi-host (SICABE_HOST_NAME) → <host_var>;
|
# (2) multi-host (SICABE_HOST_NAME) → <host_var>;
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ grep -q '${MINIO_INTERNAL_PORT:-9000}' "${dc}" || miss="${miss} MINIO"
|
|||||||
hard="$(grep -nE '\$\{(POSTGRES_HOST|KEYCLOAK_HOST|MINIO_HOST)[^}]*\}:(5432|8080|9000)' "${dc}" || true)"
|
hard="$(grep -nE '\$\{(POSTGRES_HOST|KEYCLOAK_HOST|MINIO_HOST)[^}]*\}:(5432|8080|9000)' "${dc}" || true)"
|
||||||
[[ -z "${hard}" ]] && ok "docker-compose: tak ada port internal literal di var *_HOST" \
|
[[ -z "${hard}" ]] && ok "docker-compose: tak ada port internal literal di var *_HOST" \
|
||||||
|| bad "docker-compose: masih ada port literal: ${hard}"
|
|| bad "docker-compose: masih ada port literal: ${hard}"
|
||||||
grep -q '${ENV:KEYCLOAK_INTERNAL_PORT}' "${DEPLOY_DIR}/config/trino/config-coordinator.properties" \
|
grep -q '${ENV:KEYCLOAK_INTERNAL_URL}' "${DEPLOY_DIR}/config/trino/config-coordinator.properties" \
|
||||||
&& ok "Trino config: token/jwks-url pakai \${ENV:KEYCLOAK_INTERNAL_PORT}" \
|
&& ok "Trino config: token/jwks-url pakai \${ENV:KEYCLOAK_INTERNAL_URL} (skema+host+port)" \
|
||||||
|| bad "Trino config: port Keycloak masih literal 8080"
|
|| bad "Trino config: URL Keycloak belum terparameter (token/jwks literal?)"
|
||||||
grep -q ':{port}' "${DEPLOY_DIR}/lib/cluster/generate.py" \
|
grep -q ':{port}' "${DEPLOY_DIR}/lib/cluster/generate.py" \
|
||||||
&& ok "generate.py: URL_ENDPOINT_VARS memakai {port} (dari .env)" \
|
&& ok "generate.py: URL_ENDPOINT_VARS memakai {port} (dari .env)" \
|
||||||
|| bad "generate.py: URL_ENDPOINT_VARS masih port literal"
|
|| bad "generate.py: URL_ENDPOINT_VARS masih port literal"
|
||||||
@@ -225,20 +225,20 @@ else
|
|||||||
bad "verify-keycloak.sh hilang/tidak lengkap"
|
bad "verify-keycloak.sh hilang/tidak lengkap"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── 0k. Trino OAuth2: token/jwks pakai KEYCLOAK_HOST (bukan literal `keycloak`) ──
|
# ─── 0k. Trino OAuth2: token/jwks pakai KEYCLOAK_INTERNAL_URL (bukan literal) ──
|
||||||
# Kls bug sama dgn target MinIO Prometheus: Trino server-ke-server ke Keycloak. Bila
|
# Kls bug sama dgn target MinIO Prometheus: Trino server-ke-server ke Keycloak. Bila
|
||||||
# Keycloak EKSTERNAL (P3), literal `keycloak:` tak resolve → penukaran token gagal →
|
# Keycloak EKSTERNAL (P3), literal `keycloak:` tak resolve → penukaran token gagal →
|
||||||
# callback `/oauth2/callback` balas "Authentication response could not be verified".
|
# callback `/oauth2/callback` balas "Authentication response could not be verified".
|
||||||
_tc="${DEPLOY_DIR}/config/trino/config-coordinator.properties"
|
_tc="${DEPLOY_DIR}/config/trino/config-coordinator.properties"
|
||||||
if grep -q 'token-url=http://${ENV:KEYCLOAK_HOST}:${ENV:KEYCLOAK_INTERNAL_PORT}' "${_tc}" 2>/dev/null \
|
if grep -q 'token-url=${ENV:KEYCLOAK_INTERNAL_URL}' "${_tc}" 2>/dev/null \
|
||||||
&& grep -q 'jwks-url=http://${ENV:KEYCLOAK_HOST}:' "${_tc}" 2>/dev/null \
|
&& grep -q 'jwks-url=${ENV:KEYCLOAK_INTERNAL_URL}' "${_tc}" 2>/dev/null \
|
||||||
&& grep -q 'oauth2.state-key=${ENV:TRINO_OAUTH_STATE_KEY}' "${_tc}" 2>/dev/null \
|
&& grep -q 'oauth2.state-key=${ENV:TRINO_OAUTH_STATE_KEY}' "${_tc}" 2>/dev/null \
|
||||||
&& grep -q 'KEYCLOAK_HOST: ${KEYCLOAK_HOST' "${DEPLOY_DIR}/docker-compose.yml" 2>/dev/null \
|
&& grep -q 'KEYCLOAK_INTERNAL_URL: ${KEYCLOAK_INTERNAL_URL' "${DEPLOY_DIR}/docker-compose.yml" 2>/dev/null \
|
||||||
&& grep -q 'TRINO_OAUTH_STATE_KEY: ${TRINO_OAUTH_STATE_KEY' "${DEPLOY_DIR}/docker-compose.yml" 2>/dev/null \
|
&& grep -q 'TRINO_OAUTH_STATE_KEY: ${TRINO_OAUTH_STATE_KEY' "${DEPLOY_DIR}/docker-compose.yml" 2>/dev/null \
|
||||||
&& ! grep -qE 'token-url=http://keycloak:' "${_tc}" 2>/dev/null; then
|
&& ! grep -qE 'token-url=http://keycloak:' "${_tc}" 2>/dev/null; then
|
||||||
ok "trino oauth2: token-url/jwks-url pakai KEYCLOAK_HOST + state-key tetap"
|
ok "trino oauth2: token/jwks pakai KEYCLOAK_INTERNAL_URL + state-key tetap"
|
||||||
else
|
else
|
||||||
bad "trino oauth2: token-url/jwks-url literal 'keycloak:' atau state-key belum diset"
|
bad "trino oauth2: token/jwks belum pakai KEYCLOAK_INTERNAL_URL atau state-key belum diset"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── 0l. StarRocks OIDC: redirect_url = alamat browser (bukan localhost) ──────
|
# ─── 0l. StarRocks OIDC: redirect_url = alamat browser (bukan localhost) ──────
|
||||||
@@ -307,6 +307,56 @@ else
|
|||||||
bad "storage: konfigurasi bucket/warehouse belum lengkap →${wh_bad}"
|
bad "storage: konfigurasi bucket/warehouse belum lengkap →${wh_bad}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ─── 0o. Komponen EKSTERNAL ber-URL penuh (domain + skema) ────────────────────
|
||||||
|
# Client existing (mis. Keycloak) bisa sudah berdomain `https://sso.example.com`.
|
||||||
|
# Alamat itu WAJIB didukung: skema+port dihormati (tak ditempeli http://…:12xxx) dan
|
||||||
|
# server→server diarahkan lewat KEYCLOAK_INTERNAL_URL (token/jwks Trino & Grafana).
|
||||||
|
_uo=1
|
||||||
|
grep -q 'def parse_address' "${DEPLOY_DIR}/lib/cluster/inventory.py" || _uo=0
|
||||||
|
grep -q 'def _ext_url' "${DEPLOY_DIR}/lib/cluster/generate.py" || _uo=0
|
||||||
|
grep -q 'token-url=${ENV:KEYCLOAK_INTERNAL_URL}' "${DEPLOY_DIR}/config/trino/config-coordinator.properties" || _uo=0
|
||||||
|
grep -q 'KEYCLOAK_INTERNAL_URL' "${DEPLOY_DIR}/docker-compose.yml" || _uo=0
|
||||||
|
_uo_parse="$(python3 - "${DEPLOY_DIR}" <<'PY'
|
||||||
|
import importlib.util, sys
|
||||||
|
d = sys.argv[1]
|
||||||
|
s = importlib.util.spec_from_file_location("inv", f"{d}/lib/cluster/inventory.py")
|
||||||
|
inv = importlib.util.module_from_spec(s); s.loader.exec_module(inv)
|
||||||
|
cases = {"https://sso.example.com": ("https", "sso.example.com", 443),
|
||||||
|
"http://minio.corp:9000": ("http", "minio.corp", 9000),
|
||||||
|
"https://sso.example.com:8443": ("https", "sso.example.com", 8443),
|
||||||
|
"sso.example.com": ("", "sso.example.com", None),
|
||||||
|
"10.20.0.10": ("", "10.20.0.10", None)}
|
||||||
|
print("1" if all(inv.parse_address(k) == v for k, v in cases.items()) else "0")
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
cat > "${OUT}/inv-url.yml" <<'YML'
|
||||||
|
version: 1
|
||||||
|
tls: { mode: none }
|
||||||
|
hosts:
|
||||||
|
control: { address: 10.20.0.10, tiers: [identity, policy, pipeline, governance] }
|
||||||
|
compute: { address: 10.20.0.11, tiers: [coord, engine] }
|
||||||
|
state: { address: 10.20.0.12, tiers: [state] }
|
||||||
|
components:
|
||||||
|
keycloak: { address: https://sso.example.com }
|
||||||
|
YML
|
||||||
|
_uo_gen=0
|
||||||
|
if python3 "${DEPLOY_DIR}/lib/cluster/generate.py" render \
|
||||||
|
"${OUT}/inv-url.yml" "${DEPLOY_DIR}/docker-compose.yml" "${DEPLOY_DIR}/.env" \
|
||||||
|
"${OUT}/url" "${DEPLOY_DIR}/scripts/render-config.sh" > "${OUT}/uri.log" 2>&1; then
|
||||||
|
_ue="${OUT}/url/host-control/.env"
|
||||||
|
if grep -qx 'KEYCLOAK_HOST=sso.example.com' "${_ue}" 2>/dev/null \
|
||||||
|
&& grep -qx 'KEYCLOAK_PUBLIC_URL=https://sso.example.com' "${_ue}" 2>/dev/null \
|
||||||
|
&& grep -qx 'KEYCLOAK_INTERNAL_URL=https://sso.example.com' "${_ue}" 2>/dev/null \
|
||||||
|
&& grep -qx 'KEYCLOAK_SSL_REQUIRED=external' "${_ue}" 2>/dev/null; then
|
||||||
|
_uo_gen=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ "${_uo}" == "1" && "${_uo_parse}" == "1" && "${_uo_gen}" == "1" ]]; then
|
||||||
|
ok "eksternal URL penuh: domain https dihormati + KEYCLOAK_INTERNAL_URL + sslRequired=external"
|
||||||
|
else
|
||||||
|
bad "eksternal URL penuh belum didukung (wire=${_uo} parse=${_uo_parse} gen=${_uo_gen})"
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── 1. generate ────────────────────────────────────────────────────────────
|
# ─── 1. generate ────────────────────────────────────────────────────────────
|
||||||
if ! python3 "${DEPLOY_DIR}/lib/cluster/generate.py" render \
|
if ! python3 "${DEPLOY_DIR}/lib/cluster/generate.py" render \
|
||||||
"${INVENTORY}" "${DEPLOY_DIR}/docker-compose.yml" "${DEPLOY_DIR}/.env" \
|
"${INVENTORY}" "${DEPLOY_DIR}/docker-compose.yml" "${DEPLOY_DIR}/.env" \
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ source "${SCRIPT_DIR}/lib/common.sh"
|
|||||||
source "${SCRIPT_DIR}/config/environment.defaults.sh"
|
source "${SCRIPT_DIR}/config/environment.defaults.sh"
|
||||||
sicabe_load_env "${SCRIPT_DIR}" "$@" || exit 1
|
sicabe_load_env "${SCRIPT_DIR}" "$@" || exit 1
|
||||||
|
|
||||||
KC="$(sicabe_local_url http KEYCLOAK_HOST "${KEYCLOAK_PORT:-12080}" KEYCLOAK_URL)"
|
KC="$(sicabe_keycloak_url)"
|
||||||
REALM="${SICABE_REALM:-sicabe}"
|
REALM="${SICABE_REALM:-sicabe}"
|
||||||
|
|
||||||
REQUIRED_CLIENTS=("trino" "starrocks" "airflow" "openmetadata" "grafana")
|
REQUIRED_CLIENTS=("trino" "starrocks" "airflow" "openmetadata" "grafana")
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ sicabe_load_env "${SCRIPT_DIR}" "$@"
|
|||||||
# Config RELEVAN: mode host (--env-file) = BUNDLE host (ter-render), bukan deploy/config/ sumber.
|
# Config RELEVAN: mode host (--env-file) = BUNDLE host (ter-render), bukan deploy/config/ sumber.
|
||||||
if [[ -n "${SICABE_BUNDLE_DIR:-}" ]]; then CONFIG_DIR="${SICABE_BUNDLE_DIR}/config"; else CONFIG_DIR="${SCRIPT_DIR}/config"; fi
|
if [[ -n "${SICABE_BUNDLE_DIR:-}" ]]; then CONFIG_DIR="${SICABE_BUNDLE_DIR}/config"; else CONFIG_DIR="${SCRIPT_DIR}/config"; fi
|
||||||
|
|
||||||
KC="$(sicabe_local_url http KEYCLOAK_HOST "${KEYCLOAK_PORT:-12080}" KEYCLOAK_URL)"
|
KC="$(sicabe_keycloak_url)"
|
||||||
REALM="${SICABE_REALM:-sicabe}"
|
REALM="${SICABE_REALM:-sicabe}"
|
||||||
LDAP_HOST="${LDAP_HOST:-localhost}"
|
LDAP_HOST="${LDAP_HOST:-localhost}"
|
||||||
LDAP_PORT="${LDAP_PORT:-389}"
|
LDAP_PORT="${LDAP_PORT:-389}"
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ trino_exec_oidc() { _trino_stmt "$2" -H "Authorization: Bearer $1"; }
|
|||||||
# oidc_token <username> <password> → access token (client 'trino', scope openid)
|
# oidc_token <username> <password> → access token (client 'trino', scope openid)
|
||||||
oidc_token() {
|
oidc_token() {
|
||||||
curl -s --max-time 20 -X POST \
|
curl -s --max-time 20 -X POST \
|
||||||
"$(sicabe_local_url http KEYCLOAK_HOST "${KEYCLOAK_PORT:-12080}" KEYCLOAK_URL)/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/token" \
|
"$(sicabe_keycloak_url)/realms/${SICABE_REALM:-sicabe}/protocol/openid-connect/token" \
|
||||||
-d grant_type=password -d client_id=trino -d "client_secret=${TRINO_CLIENT_SECRET}" \
|
-d grant_type=password -d client_id=trino -d "client_secret=${TRINO_CLIENT_SECRET}" \
|
||||||
-d "username=$1" -d "password=$2" -d scope=openid 2>/dev/null |
|
-d "username=$1" -d "password=$2" -d scope=openid 2>/dev/null |
|
||||||
python3 -c "import sys,json;print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null
|
python3 -c "import sys,json;print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ BUCKET="${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}"
|
|||||||
S3_ENDPOINT="${S3_ENDPOINT:-http://minio:9000}"
|
S3_ENDPOINT="${S3_ENDPOINT:-http://minio:9000}"
|
||||||
S3_REGION="${S3_REGION:-us-east-1}"
|
S3_REGION="${S3_REGION:-us-east-1}"
|
||||||
LDAP_URL="${LDAP_URL:-ldap://openldap:389}"
|
LDAP_URL="${LDAP_URL:-ldap://openldap:389}"
|
||||||
KC_BASE="$(sicabe_local_url http KEYCLOAK_HOST "${KEYCLOAK_PORT:-12080}" KEYCLOAK_URL)"
|
KC_BASE="$(sicabe_keycloak_url)"
|
||||||
REALM="${SICABE_REALM:-sicabe}"
|
REALM="${SICABE_REALM:-sicabe}"
|
||||||
GROUP_PROVIDER_NAME="${STARROCKS_GROUP_PROVIDER:-sr_ldap}"
|
GROUP_PROVIDER_NAME="${STARROCKS_GROUP_PROVIDER:-sr_ldap}"
|
||||||
OIDC_NAME="${STARROCKS_OIDC_INTEGRATION:-sr_oidc}"
|
OIDC_NAME="${STARROCKS_OIDC_INTEGRATION:-sr_oidc}"
|
||||||
|
|||||||
Reference in New Issue
Block a user