All checks were successful
CD Pipeline / select-latest-carrier (push) Successful in 52s
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m36s
CD Pipeline / revalidate-deploy-carrier (push) Successful in 36s
CD Pipeline / build-and-deploy (push) Successful in 17m5s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 2s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 31s
CD Pipeline / revalidate-post-deploy-carrier (push) Successful in 46s
CD Pipeline / post-deploy-checks (push) Successful in 4m54s
444 lines
15 KiB
Python
444 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""Host110-only SigNoz v0.113 first-admin and metadata PAT provisioner.
|
|
|
|
The program is streamed to ``python3 -`` by Agent99. Secrets are generated and
|
|
used only in this process; stdout contains a bounded metadata-only JSON object.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import http.client
|
|
import json
|
|
import os
|
|
import secrets
|
|
import stat
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
BASE_URL = "http://127.0.0.1:8080"
|
|
CREDENTIAL_PATH = Path("/etc/awoooi/secrets/signoz-metadata-api-key")
|
|
RECOVERY_PASSWORD_PATH = Path(
|
|
"/etc/awoooi/secrets/signoz-bootstrap-admin-password.recovery"
|
|
)
|
|
ADMIN_EMAIL = "awoooi-signoz-admin@example.invalid"
|
|
ORG_NAME = "AWOOOI"
|
|
PAT_NAME = "awoooi-metadata-export"
|
|
MAX_RESPONSE_BYTES = 1024 * 1024
|
|
EXPECTED_UID = 0
|
|
|
|
|
|
class ProvisionError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def request_json(
|
|
method: str,
|
|
path: str,
|
|
payload: dict[str, Any] | None = None,
|
|
*,
|
|
bearer: str | None = None,
|
|
api_key: str | None = None,
|
|
) -> dict[str, Any]:
|
|
headers = {"Accept": "application/json"}
|
|
body = None
|
|
if payload is not None:
|
|
headers["Content-Type"] = "application/json"
|
|
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
|
|
if bearer:
|
|
headers["Authorization"] = f"Bearer {bearer}"
|
|
if api_key:
|
|
headers["SIGNOZ-API-KEY"] = api_key
|
|
request = urllib.request.Request(
|
|
f"{BASE_URL}{path}", data=body, headers=headers, method=method
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=15) as response: # nosec B310
|
|
raw = response.read(MAX_RESPONSE_BYTES + 1)
|
|
except urllib.error.HTTPError as exc:
|
|
raise ProvisionError(f"http_{method.lower()}_{path.rsplit('/', 1)[-1]}_{exc.code}") from None
|
|
except (urllib.error.URLError, TimeoutError, OSError, http.client.HTTPException):
|
|
raise ProvisionError(f"transport_{method.lower()}_{path.rsplit('/', 1)[-1]}_failed") from None
|
|
if len(raw) > MAX_RESPONSE_BYTES:
|
|
raise ProvisionError("response_too_large")
|
|
try:
|
|
value = json.loads(raw)
|
|
except (UnicodeError, json.JSONDecodeError):
|
|
raise ProvisionError("response_json_invalid") from None
|
|
if not isinstance(value, dict) or value.get("status") != "success":
|
|
raise ProvisionError("response_status_invalid")
|
|
return value
|
|
|
|
|
|
def request_version() -> dict[str, Any]:
|
|
request = urllib.request.Request(f"{BASE_URL}/api/v1/version", method="GET")
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=15) as response: # nosec B310
|
|
raw = response.read(MAX_RESPONSE_BYTES + 1)
|
|
except (
|
|
urllib.error.HTTPError,
|
|
urllib.error.URLError,
|
|
TimeoutError,
|
|
OSError,
|
|
http.client.HTTPException,
|
|
):
|
|
raise ProvisionError("version_request_failed") from None
|
|
if len(raw) > MAX_RESPONSE_BYTES:
|
|
raise ProvisionError("version_response_too_large")
|
|
try:
|
|
value = json.loads(raw)
|
|
except (UnicodeError, json.JSONDecodeError):
|
|
raise ProvisionError("version_response_invalid") from None
|
|
if not isinstance(value, dict):
|
|
raise ProvisionError("version_shape_invalid")
|
|
return value
|
|
|
|
|
|
def request_no_content(
|
|
method: str,
|
|
path: str,
|
|
*,
|
|
bearer: str | None = None,
|
|
api_key: str | None = None,
|
|
) -> None:
|
|
headers = {"Accept": "application/json"}
|
|
if bearer:
|
|
headers["Authorization"] = f"Bearer {bearer}"
|
|
if api_key:
|
|
headers["SIGNOZ-API-KEY"] = api_key
|
|
request = urllib.request.Request(
|
|
f"{BASE_URL}{path}", headers=headers, method=method
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=15) as response: # nosec B310
|
|
status = response.status
|
|
raw = response.read(1)
|
|
except urllib.error.HTTPError as exc:
|
|
raise ProvisionError(f"http_{method.lower()}_{path.rsplit('/', 1)[-1]}_{exc.code}") from None
|
|
except (urllib.error.URLError, TimeoutError, OSError, http.client.HTTPException):
|
|
raise ProvisionError(f"transport_{method.lower()}_{path.rsplit('/', 1)[-1]}_failed") from None
|
|
if status != 204 or raw:
|
|
raise ProvisionError("no_content_response_invalid")
|
|
|
|
|
|
def secret_state(path: Path, *, read_value: bool = False) -> tuple[str, str | None]:
|
|
try:
|
|
metadata = path.lstat()
|
|
except FileNotFoundError:
|
|
return "absent", None
|
|
if not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != EXPECTED_UID:
|
|
raise ProvisionError("credential_not_root_regular_file")
|
|
if stat.S_IMODE(metadata.st_mode) not in (0o400, 0o600):
|
|
raise ProvisionError("credential_mode_invalid")
|
|
if metadata.st_size <= 0 or metadata.st_size > 4096:
|
|
raise ProvisionError("credential_size_invalid")
|
|
if not read_value:
|
|
return "present", None
|
|
token = path.read_text(encoding="utf-8").strip()
|
|
if not token or len(token) > 4096 or any(ch.isspace() for ch in token):
|
|
raise ProvisionError("credential_value_invalid")
|
|
return "present", token
|
|
|
|
|
|
def credential_state(*, read_value: bool = False) -> tuple[str, str | None]:
|
|
return secret_state(CREDENTIAL_PATH, read_value=read_value)
|
|
|
|
|
|
def setup_completed() -> bool:
|
|
value = request_version()
|
|
data = value.get("data", value)
|
|
if not isinstance(data, dict):
|
|
raise ProvisionError("version_shape_invalid")
|
|
completed = data.get("setupCompleted")
|
|
if not isinstance(completed, bool):
|
|
raise ProvisionError("setup_state_missing")
|
|
return completed
|
|
|
|
|
|
def verify_key(token: str, *, expected_pat_id: str | None = None) -> None:
|
|
value = request_json("GET", "/api/v1/dashboards", api_key=token)
|
|
if not isinstance(value.get("data"), (list, dict)):
|
|
raise ProvisionError("credential_dashboard_readback_invalid")
|
|
roles = request_json("GET", "/api/v1/roles", api_key=token)
|
|
if not isinstance(roles.get("data"), list):
|
|
raise ProvisionError("credential_admin_role_readback_invalid")
|
|
if expected_pat_id:
|
|
pats = request_json("GET", "/api/v1/pats", api_key=token)
|
|
pat_rows = pats.get("data")
|
|
if not isinstance(pat_rows, list) or not any(
|
|
isinstance(row, dict) and row.get("id") == expected_pat_id
|
|
for row in pat_rows
|
|
):
|
|
raise ProvisionError("credential_admin_pat_readback_invalid")
|
|
|
|
|
|
def write_secret_no_clobber(path: Path, value: str) -> None:
|
|
parent = path.parent
|
|
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
parent_metadata = parent.lstat()
|
|
if not stat.S_ISDIR(parent_metadata.st_mode) or stat.S_ISLNK(parent_metadata.st_mode):
|
|
raise ProvisionError("credential_parent_invalid")
|
|
temp = parent / f".{path.name}.{os.getpid()}.{secrets.token_hex(8)}.tmp"
|
|
fd = -1
|
|
try:
|
|
fd = os.open(temp, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
|
os.write(fd, value.encode("utf-8"))
|
|
os.fsync(fd)
|
|
os.close(fd)
|
|
fd = -1
|
|
os.chown(temp, 0, 0, follow_symlinks=False)
|
|
os.chmod(temp, 0o400, follow_symlinks=False)
|
|
os.link(temp, path, follow_symlinks=False)
|
|
os.unlink(temp)
|
|
except FileExistsError:
|
|
raise ProvisionError("credential_destination_exists") from None
|
|
finally:
|
|
if fd >= 0:
|
|
os.close(fd)
|
|
try:
|
|
temp.unlink()
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
|
|
def remove_secret_verified(path: Path) -> None:
|
|
try:
|
|
metadata = path.lstat()
|
|
except FileNotFoundError:
|
|
return
|
|
if not stat.S_ISREG(metadata.st_mode) or stat.S_ISLNK(metadata.st_mode):
|
|
raise ProvisionError("secret_cleanup_target_invalid")
|
|
try:
|
|
path.unlink()
|
|
except OSError:
|
|
raise ProvisionError("secret_cleanup_delete_failed") from None
|
|
try:
|
|
path.lstat()
|
|
except FileNotFoundError:
|
|
return
|
|
raise ProvisionError("secret_cleanup_residue_present")
|
|
|
|
|
|
def organization_id_for_admin() -> str:
|
|
value = request_json(
|
|
"GET", "/api/v2/sessions/context?email=awoooi-signoz-admin%40example.invalid"
|
|
)
|
|
data = value.get("data")
|
|
orgs = data.get("orgs") if isinstance(data, dict) else None
|
|
if not isinstance(orgs, list) or len(orgs) != 1 or not isinstance(orgs[0], dict):
|
|
raise ProvisionError("bootstrap_recovery_org_context_invalid")
|
|
org_id = orgs[0].get("id") or orgs[0].get("orgId")
|
|
if not isinstance(org_id, str) or not org_id:
|
|
raise ProvisionError("bootstrap_recovery_org_id_missing")
|
|
return org_id
|
|
|
|
|
|
def revoke_pat(access_token: str, pat_id: str, pat_token: str = "") -> bool:
|
|
if access_token:
|
|
try:
|
|
request_no_content(
|
|
"DELETE", f"/api/v1/pats/{pat_id}", bearer=access_token
|
|
)
|
|
return True
|
|
except ProvisionError:
|
|
pass
|
|
if pat_token:
|
|
try:
|
|
request_no_content(
|
|
"DELETE", f"/api/v1/pats/{pat_id}", api_key=pat_token
|
|
)
|
|
return True
|
|
except ProvisionError:
|
|
pass
|
|
return False
|
|
|
|
|
|
def close_session(access_token: str) -> bool:
|
|
try:
|
|
request_no_content("DELETE", "/api/v2/sessions", bearer=access_token)
|
|
return True
|
|
except ProvisionError:
|
|
return False
|
|
|
|
|
|
def rollback_created_pat(
|
|
access_token: str,
|
|
pat_id: str,
|
|
pat_token: str,
|
|
*,
|
|
file_created: bool,
|
|
) -> None:
|
|
revoked = revoke_pat(access_token, pat_id, pat_token) if pat_id else True
|
|
if not revoked:
|
|
raise ProvisionError("apply_failed_pat_revoke_unverified_credential_preserved")
|
|
if file_created:
|
|
remove_secret_verified(CREDENTIAL_PATH)
|
|
|
|
|
|
def apply() -> dict[str, Any]:
|
|
state, _ = credential_state()
|
|
if state != "absent":
|
|
raise ProvisionError("credential_already_present_no_clobber")
|
|
recovery_state, recovery_password = secret_state(
|
|
RECOVERY_PASSWORD_PATH, read_value=True
|
|
)
|
|
completed = setup_completed()
|
|
if recovery_state == "absent" and completed:
|
|
raise ProvisionError("setup_already_completed_without_bootstrap_recovery")
|
|
if recovery_state == "absent":
|
|
recovery_password = secrets.token_urlsafe(48)
|
|
write_secret_no_clobber(RECOVERY_PASSWORD_PATH, recovery_password)
|
|
recovery_state = "present"
|
|
if recovery_password is None:
|
|
raise ProvisionError("bootstrap_recovery_password_missing")
|
|
|
|
org_id = ""
|
|
if not completed:
|
|
try:
|
|
registration = request_json(
|
|
"POST",
|
|
"/api/v1/register",
|
|
{
|
|
"email": ADMIN_EMAIL,
|
|
"orgDisplayName": ORG_NAME,
|
|
"password": recovery_password,
|
|
},
|
|
)
|
|
registration_data = registration.get("data")
|
|
org_id = (
|
|
registration_data.get("orgId")
|
|
if isinstance(registration_data, dict)
|
|
else None
|
|
)
|
|
if not isinstance(org_id, str) or not org_id:
|
|
raise ProvisionError("registration_org_id_missing")
|
|
except ProvisionError:
|
|
if not setup_completed():
|
|
raise
|
|
org_id = organization_id_for_admin()
|
|
else:
|
|
org_id = organization_id_for_admin()
|
|
|
|
session = request_json(
|
|
"POST",
|
|
"/api/v2/sessions/email_password",
|
|
{"email": ADMIN_EMAIL, "password": recovery_password, "orgId": org_id},
|
|
)
|
|
recovery_password = ""
|
|
session_data = session.get("data")
|
|
access_token = session_data.get("accessToken") if isinstance(session_data, dict) else None
|
|
if not isinstance(access_token, str) or not access_token:
|
|
raise ProvisionError("session_access_token_missing")
|
|
|
|
pat_id = ""
|
|
token = ""
|
|
file_created = False
|
|
try:
|
|
me = request_json("GET", "/api/v1/user/me", bearer=access_token)
|
|
me_data = me.get("data")
|
|
if not isinstance(me_data, dict) or me_data.get("role") != "ADMIN" or me_data.get("orgId") != org_id:
|
|
raise ProvisionError("bootstrap_admin_identity_invalid")
|
|
pat = request_json(
|
|
"POST",
|
|
"/api/v1/pats",
|
|
{"name": PAT_NAME, "expiresInDays": 30, "role": "ADMIN"},
|
|
bearer=access_token,
|
|
)
|
|
pat_data = pat.get("data")
|
|
pat_id = pat_data.get("id") if isinstance(pat_data, dict) else None
|
|
token = pat_data.get("token") if isinstance(pat_data, dict) else None
|
|
if not isinstance(pat_id, str) or not pat_id or not isinstance(token, str) or not token:
|
|
raise ProvisionError("pat_response_invalid")
|
|
verify_key(token, expected_pat_id=pat_id)
|
|
write_secret_no_clobber(CREDENTIAL_PATH, token)
|
|
file_created = True
|
|
_, persisted = credential_state(read_value=True)
|
|
if persisted is None:
|
|
raise ProvisionError("credential_persist_readback_missing")
|
|
verify_key(persisted, expected_pat_id=pat_id)
|
|
if not close_session(access_token):
|
|
raise ProvisionError("session_cleanup_unverified")
|
|
access_token = ""
|
|
remove_secret_verified(RECOVERY_PASSWORD_PATH)
|
|
token = ""
|
|
except Exception as original:
|
|
try:
|
|
rollback_created_pat(
|
|
access_token,
|
|
pat_id,
|
|
token,
|
|
file_created=file_created,
|
|
)
|
|
except ProvisionError as rollback_error:
|
|
raise rollback_error from None
|
|
finally:
|
|
close_session(access_token)
|
|
access_token = ""
|
|
raise original
|
|
return {
|
|
"terminal": "applied_verified",
|
|
"setup_completed": True,
|
|
"admin_identity_verified": True,
|
|
"credential_file_created": True,
|
|
"credential_api_verified": True,
|
|
"session_cleanup_verified": True,
|
|
"bootstrap_recovery_removed": True,
|
|
"bootstrap_is_permanent": True,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--mode", choices=("check", "apply", "verify"), required=True)
|
|
args = parser.parse_args()
|
|
try:
|
|
if args.mode == "check":
|
|
state, _ = credential_state()
|
|
recovery_state, _ = secret_state(RECOVERY_PASSWORD_PATH)
|
|
result = {
|
|
"terminal": "check_pass_no_write",
|
|
"setup_completed": setup_completed(),
|
|
"credential_state": state,
|
|
"bootstrap_recovery_state": recovery_state,
|
|
"production_write": False,
|
|
}
|
|
elif args.mode == "apply":
|
|
result = apply()
|
|
else:
|
|
state, token = credential_state(read_value=True)
|
|
recovery_state, _ = secret_state(RECOVERY_PASSWORD_PATH)
|
|
if (
|
|
state != "present"
|
|
or token is None
|
|
or not setup_completed()
|
|
or recovery_state != "absent"
|
|
):
|
|
raise ProvisionError("verify_precondition_failed")
|
|
verify_key(token)
|
|
token = ""
|
|
result = {
|
|
"terminal": "verify_pass",
|
|
"setup_completed": True,
|
|
"credential_state": "present",
|
|
"credential_api_verified": True,
|
|
}
|
|
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
|
|
return 0
|
|
except ProvisionError as exc:
|
|
print(
|
|
json.dumps(
|
|
{"terminal": "failed", "error_code": str(exc)},
|
|
sort_keys=True,
|
|
separators=(",", ":"),
|
|
)
|
|
)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|