feat: complete credential broker source flow
This commit is contained in:
@@ -10,6 +10,9 @@ import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -20,11 +23,22 @@ REPO_DIR = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_CATALOG = REPO_DIR / "credential-grants/catalog.yaml"
|
||||
DEFAULT_LEASE_DIR = REPO_DIR / ".local/credential-leases"
|
||||
TOKEN_MARKERS = re.compile(
|
||||
r"\bhv[bcms]\.[A-Za-z0-9._-]+|\b(?:VAULT_TOKEN|BAO_TOKEN)=[^\s]+"
|
||||
r"\bhv[bcms]\.[A-Za-z0-9._-]+|\b(?:VAULT_TOKEN|BAO_TOKEN|OPENBAO_ROOT_TOKEN)=[^\s]+"
|
||||
)
|
||||
ENV_ASSIGNMENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
|
||||
UNSAFE_TOKEN_ENV = {"VAULT_TOKEN", "BAO_TOKEN", "OPENBAO_ROOT_TOKEN"}
|
||||
UNSAFE_VERBOSE_VALUES = {"debug", "trace"}
|
||||
DEFAULT_ACTOR_TYPE = "approved-agent"
|
||||
DEFAULT_ACTOR = f"codex:{os.environ.get('USER', 'unknown')}"
|
||||
DEFAULT_SUBJECT = "agent:codex/railiance-platform"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AuthorizationResult:
|
||||
allowed: bool
|
||||
mode: str
|
||||
decision_id: str | None = None
|
||||
reason: str | None = None
|
||||
|
||||
|
||||
def emit_json(payload: dict[str, Any]) -> None:
|
||||
@@ -35,6 +49,10 @@ def fail(message: str) -> None:
|
||||
raise SystemExit(f"ERROR: {message}")
|
||||
|
||||
|
||||
def warn(message: str) -> None:
|
||||
print(f"WARN: {message}", file=sys.stderr)
|
||||
|
||||
|
||||
def redact(text: str, extra_secrets: list[str] | None = None) -> str:
|
||||
redacted = TOKEN_MARKERS.sub("[REDACTED]", text)
|
||||
for secret in extra_secrets or []:
|
||||
@@ -116,7 +134,11 @@ def get_grant(catalog: dict[str, Any], grant_id: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def validate_issue_request(
|
||||
grant: dict[str, Any], ttl: str, purpose: str, delivery: str | None = None
|
||||
grant: dict[str, Any],
|
||||
ttl: str,
|
||||
purpose: str,
|
||||
delivery: str | None = None,
|
||||
actor_type: str | None = None,
|
||||
) -> None:
|
||||
if not purpose.strip():
|
||||
fail("--purpose is required and must not be empty")
|
||||
@@ -132,6 +154,14 @@ def validate_issue_request(
|
||||
fail(
|
||||
f"delivery mode {delivery!r} is not allowed for grant {grant.get('id')}"
|
||||
)
|
||||
if actor_type:
|
||||
allowed_actor_types = set(
|
||||
(grant.get("actors") or {}).get("allowed_types") or []
|
||||
)
|
||||
if actor_type not in allowed_actor_types:
|
||||
fail(
|
||||
f"actor type {actor_type!r} is not allowed for grant {grant.get('id')}"
|
||||
)
|
||||
|
||||
|
||||
def safe_handle(handle: str) -> str:
|
||||
@@ -140,6 +170,239 @@ def safe_handle(handle: str) -> str:
|
||||
return f"{cleaned}-{digest}"
|
||||
|
||||
|
||||
def bool_env(name: str) -> bool:
|
||||
return os.environ.get(name, "").lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def join_url(base: str, path: str) -> str:
|
||||
return base.rstrip("/") + "/" + path.lstrip("/")
|
||||
|
||||
|
||||
def post_json(
|
||||
url: str, payload: dict[str, Any], timeout: float = 10.0
|
||||
) -> dict[str, Any]:
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
raw = response.read().decode("utf-8")
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
raise RuntimeError(f"HTTP {exc.code} from {url}: {detail}") from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise RuntimeError(f"could not reach {url}: {exc.reason}") from exc
|
||||
if not raw.strip():
|
||||
return {}
|
||||
data = json.loads(raw)
|
||||
if not isinstance(data, dict):
|
||||
raise RuntimeError(f"expected JSON object from {url}")
|
||||
return data
|
||||
|
||||
|
||||
def request_metadata(
|
||||
*,
|
||||
grant: dict[str, Any],
|
||||
ttl: str,
|
||||
purpose: str,
|
||||
delivery: str,
|
||||
actor: str,
|
||||
actor_type: str,
|
||||
subject: str,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"grant_id": grant["id"],
|
||||
"actor": actor,
|
||||
"actor_type": actor_type,
|
||||
"subject": subject,
|
||||
"purpose": purpose,
|
||||
"requested_ttl": ttl,
|
||||
"delivery_mode": delivery,
|
||||
"audience": grant.get("audience"),
|
||||
"issuer": grant.get("issuer"),
|
||||
"credential_type": grant.get("credential_type"),
|
||||
}
|
||||
|
||||
|
||||
def authorize_request(
|
||||
*,
|
||||
args: argparse.Namespace,
|
||||
grant: dict[str, Any],
|
||||
ttl: str,
|
||||
purpose: str,
|
||||
delivery: str,
|
||||
) -> AuthorizationResult:
|
||||
validate_issue_request(grant, ttl, purpose, delivery, args.actor_type)
|
||||
if args.decision_id:
|
||||
return AuthorizationResult(
|
||||
True,
|
||||
"provided-decision",
|
||||
args.decision_id,
|
||||
"caller supplied prior decision id",
|
||||
)
|
||||
if args.dry_run:
|
||||
return AuthorizationResult(
|
||||
True, "dry-run-local", None, "dry-run does not call flex-auth"
|
||||
)
|
||||
if not args.flex_auth_url:
|
||||
if args.require_flex_auth:
|
||||
fail(
|
||||
"--require-flex-auth was set, but no --flex-auth-url or FLEX_AUTH_URL is configured"
|
||||
)
|
||||
return AuthorizationResult(
|
||||
True,
|
||||
"local-preauthorized",
|
||||
None,
|
||||
"flex-auth unavailable; grant policy is locally preauthorized",
|
||||
)
|
||||
|
||||
endpoint = join_url(args.flex_auth_url, args.flex_auth_path)
|
||||
payload = request_metadata(
|
||||
grant=grant,
|
||||
ttl=ttl,
|
||||
purpose=purpose,
|
||||
delivery=delivery,
|
||||
actor=args.actor,
|
||||
actor_type=args.actor_type,
|
||||
subject=args.subject,
|
||||
)
|
||||
try:
|
||||
response = post_json(endpoint, payload, timeout=args.http_timeout)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
if args.require_flex_auth:
|
||||
fail(f"flex-auth preflight failed: {exc}")
|
||||
warn(
|
||||
f"flex-auth preflight unavailable; continuing by local preauthorization: {exc}"
|
||||
)
|
||||
return AuthorizationResult(
|
||||
True,
|
||||
"local-preauthorized",
|
||||
None,
|
||||
"flex-auth unavailable; local preauthorization used",
|
||||
)
|
||||
|
||||
allowed_value = response.get("allowed")
|
||||
decision_value = str(
|
||||
response.get("decision") or response.get("status") or ""
|
||||
).lower()
|
||||
allowed = allowed_value is True or decision_value in {
|
||||
"allow",
|
||||
"allowed",
|
||||
"approved",
|
||||
"pass",
|
||||
}
|
||||
denied = allowed_value is False or decision_value in {
|
||||
"deny",
|
||||
"denied",
|
||||
"rejected",
|
||||
"fail",
|
||||
}
|
||||
decision_id = response.get("decision_id") or response.get("id")
|
||||
reason = response.get("reason") or response.get("message")
|
||||
if denied or not allowed:
|
||||
fail(f"flex-auth denied credential request: {reason or 'no reason supplied'}")
|
||||
return AuthorizationResult(
|
||||
True,
|
||||
"flex-auth",
|
||||
str(decision_id) if decision_id else None,
|
||||
str(reason) if reason else None,
|
||||
)
|
||||
|
||||
|
||||
def state_hub_enabled(args: argparse.Namespace) -> bool:
|
||||
return args.record_state_hub or bool_env("CREDENTIAL_RECORD_STATE_HUB")
|
||||
|
||||
|
||||
def state_hub_metadata(
|
||||
*,
|
||||
args: argparse.Namespace,
|
||||
grant: dict[str, Any] | None,
|
||||
status: str,
|
||||
purpose: str | None = None,
|
||||
ttl: str | None = None,
|
||||
delivery: str | None = None,
|
||||
authz: AuthorizationResult | None = None,
|
||||
lease_handle: str | None = None,
|
||||
wrapping_accessor: str | None = None,
|
||||
wrapped_accessor: str | None = None,
|
||||
exit_code: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
metadata: dict[str, Any] = {
|
||||
"status": status,
|
||||
"actor": args.actor,
|
||||
"actor_type": args.actor_type,
|
||||
"subject": args.subject,
|
||||
}
|
||||
if grant is not None:
|
||||
metadata.update(
|
||||
{
|
||||
"grant_id": grant.get("id"),
|
||||
"audience": grant.get("audience"),
|
||||
"issuer": grant.get("issuer"),
|
||||
"credential_type": grant.get("credential_type"),
|
||||
}
|
||||
)
|
||||
if purpose is not None:
|
||||
metadata["purpose"] = purpose
|
||||
if ttl is not None:
|
||||
metadata["requested_ttl"] = ttl
|
||||
if delivery is not None:
|
||||
metadata["delivery_mode"] = delivery
|
||||
if authz is not None:
|
||||
metadata["authorization_mode"] = authz.mode
|
||||
metadata["decision_id"] = authz.decision_id
|
||||
metadata["decision_reason"] = authz.reason
|
||||
if lease_handle is not None:
|
||||
metadata["lease_accessor"] = lease_handle
|
||||
if wrapping_accessor is not None:
|
||||
metadata["wrapping_accessor"] = wrapping_accessor
|
||||
if wrapped_accessor is not None:
|
||||
metadata["wrapped_accessor"] = wrapped_accessor
|
||||
if exit_code is not None:
|
||||
metadata["exit_code"] = exit_code
|
||||
return metadata
|
||||
|
||||
|
||||
def record_state_hub(args: argparse.Namespace, metadata: dict[str, Any]) -> None:
|
||||
if not state_hub_enabled(args):
|
||||
return
|
||||
if args.dry_run:
|
||||
print("DRY-RUN: would record non-secret State Hub credential metadata")
|
||||
return
|
||||
url = args.state_hub_url.rstrip("/") + "/progress/"
|
||||
summary_parts = [
|
||||
"credential-broker",
|
||||
str(metadata.get("status")),
|
||||
f"grant={metadata.get('grant_id')}",
|
||||
f"actor={metadata.get('actor')}",
|
||||
f"purpose={metadata.get('purpose')}",
|
||||
f"ttl={metadata.get('requested_ttl')}",
|
||||
f"delivery={metadata.get('delivery_mode')}",
|
||||
]
|
||||
if metadata.get("decision_id"):
|
||||
summary_parts.append(f"decision={metadata.get('decision_id')}")
|
||||
if metadata.get("lease_accessor"):
|
||||
summary_parts.append(f"lease={metadata.get('lease_accessor')}")
|
||||
payload = {
|
||||
"summary": " ".join(
|
||||
part for part in summary_parts if part and not part.endswith("=None")
|
||||
),
|
||||
"detail": json.dumps(metadata, sort_keys=True),
|
||||
"event_type": "note",
|
||||
"author": "credential-broker",
|
||||
}
|
||||
if args.state_hub_workstream_id:
|
||||
payload["workstream_id"] = args.state_hub_workstream_id
|
||||
try:
|
||||
post_json(url, payload, timeout=args.http_timeout)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
warn(f"could not record State Hub metadata: {exc}")
|
||||
|
||||
|
||||
class BaoRunner:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -159,11 +422,7 @@ class BaoRunner:
|
||||
self.issuer_token = issuer_token
|
||||
|
||||
def run(
|
||||
self,
|
||||
args: list[str],
|
||||
*,
|
||||
input_text: str | None = None,
|
||||
quiet: bool = False,
|
||||
self, args: list[str], *, input_text: str | None = None, quiet: bool = False
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
if self.dry_run:
|
||||
print("DRY-RUN: bao " + shlex.join(args))
|
||||
@@ -222,13 +481,7 @@ def token_create_args(
|
||||
args = ["token", "create"]
|
||||
if wrap_ttl:
|
||||
args.append(f"-wrap-ttl={wrap_ttl}")
|
||||
args.extend(
|
||||
[
|
||||
f"-role={openbao['token_role']}",
|
||||
f"-ttl={ttl}",
|
||||
"-format=json",
|
||||
]
|
||||
)
|
||||
args.extend([f"-role={openbao['token_role']}", f"-ttl={ttl}", "-format=json"])
|
||||
for policy in openbao["policies"]:
|
||||
args.append(f"-policy={policy}")
|
||||
return args
|
||||
@@ -278,6 +531,7 @@ def write_local_lease(
|
||||
ttl: str,
|
||||
token: str,
|
||||
accessor: str,
|
||||
authz: AuthorizationResult,
|
||||
) -> dict[str, Any]:
|
||||
stem = safe_handle(accessor)
|
||||
token_path = lease_dir / f"{stem}.openbao-token"
|
||||
@@ -292,6 +546,8 @@ def write_local_lease(
|
||||
"expires_at": expires_at(ttl),
|
||||
"ttl": ttl,
|
||||
"delivery_mode": "local-token-file",
|
||||
"authorization_mode": authz.mode,
|
||||
"decision_id": authz.decision_id,
|
||||
"token_file": str(token_path),
|
||||
"status": "issued",
|
||||
}
|
||||
@@ -374,11 +630,53 @@ def split_env_prefix(command: list[str]) -> tuple[dict[str, str], list[str]]:
|
||||
return extra_env, program
|
||||
|
||||
|
||||
def kubernetes_auth_payload(
|
||||
grant: dict[str, Any], ttl: str, purpose: str, authz: AuthorizationResult
|
||||
) -> dict[str, Any]:
|
||||
delivery = grant.get("delivery") or {}
|
||||
kubernetes_auth = delivery.get("kubernetes_auth") or {}
|
||||
return {
|
||||
"grant_id": grant["id"],
|
||||
"purpose": purpose,
|
||||
"ttl": ttl,
|
||||
"delivery_mode": "kubernetes-auth",
|
||||
"status": "delegated",
|
||||
"authorization_mode": authz.mode,
|
||||
"decision_id": authz.decision_id,
|
||||
"openbao_auth_mount": kubernetes_auth.get("mount", "auth/kubernetes"),
|
||||
"openbao_auth_role": kubernetes_auth.get("role")
|
||||
or grant["openbao"].get("kubernetes_auth_role"),
|
||||
"service_account_names": kubernetes_auth.get("service_account_names", []),
|
||||
"namespaces": kubernetes_auth.get("namespaces", []),
|
||||
"note": "Workload must authenticate with its Kubernetes service account JWT; no bearer token is issued by this helper.",
|
||||
}
|
||||
|
||||
|
||||
def command_request(
|
||||
args: argparse.Namespace, runner: BaoRunner, grant: dict[str, Any]
|
||||
) -> int:
|
||||
ttl = args.ttl or grant["ttl"]["default"]
|
||||
validate_issue_request(grant, ttl, args.purpose, args.delivery)
|
||||
authz = authorize_request(
|
||||
args=args, grant=grant, ttl=ttl, purpose=args.purpose, delivery=args.delivery
|
||||
)
|
||||
if args.delivery == "kubernetes-auth":
|
||||
payload = kubernetes_auth_payload(grant, ttl, args.purpose, authz)
|
||||
payload["status"] = "dry-run" if args.dry_run else payload["status"]
|
||||
emit_json(payload)
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status=payload["status"],
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery=args.delivery,
|
||||
authz=authz,
|
||||
),
|
||||
)
|
||||
return 0
|
||||
|
||||
if args.dry_run:
|
||||
emit_json(
|
||||
{
|
||||
@@ -388,6 +686,8 @@ def command_request(
|
||||
"purpose": args.purpose,
|
||||
"ttl": ttl,
|
||||
"delivery_mode": args.delivery,
|
||||
"authorization_mode": authz.mode,
|
||||
"decision_id": authz.decision_id,
|
||||
}
|
||||
)
|
||||
if args.delivery == "response-wrap":
|
||||
@@ -397,23 +697,62 @@ def command_request(
|
||||
)
|
||||
else:
|
||||
print("DRY-RUN: bao " + shlex.join(token_create_args(grant, ttl)))
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="dry-run",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery=args.delivery,
|
||||
authz=authz,
|
||||
),
|
||||
)
|
||||
return 0
|
||||
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="requested",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery=args.delivery,
|
||||
authz=authz,
|
||||
),
|
||||
)
|
||||
if args.delivery == "response-wrap":
|
||||
result = runner.run(token_create_args(grant, ttl, args.wrap_ttl), quiet=True)
|
||||
wrap_info = parse_wrap_create(result.stdout)
|
||||
emit_json(
|
||||
{
|
||||
"grant_id": grant["id"],
|
||||
"purpose": args.purpose,
|
||||
"ttl": ttl,
|
||||
"delivery_mode": "response-wrap",
|
||||
"wrapping_token": wrap_info.get("token"),
|
||||
"wrapping_accessor": wrap_info.get("accessor"),
|
||||
"wrapped_accessor": wrap_info.get("wrapped_accessor"),
|
||||
"wrap_ttl": wrap_info.get("ttl"),
|
||||
"status": "wrapped",
|
||||
}
|
||||
payload = {
|
||||
"grant_id": grant["id"],
|
||||
"purpose": args.purpose,
|
||||
"ttl": ttl,
|
||||
"delivery_mode": "response-wrap",
|
||||
"wrapping_token": wrap_info.get("token"),
|
||||
"wrapping_accessor": wrap_info.get("accessor"),
|
||||
"wrapped_accessor": wrap_info.get("wrapped_accessor"),
|
||||
"wrap_ttl": wrap_info.get("ttl"),
|
||||
"authorization_mode": authz.mode,
|
||||
"decision_id": authz.decision_id,
|
||||
"status": "wrapped",
|
||||
}
|
||||
emit_json(payload)
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="wrapped",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery=args.delivery,
|
||||
authz=authz,
|
||||
wrapping_accessor=payload.get("wrapping_accessor"),
|
||||
wrapped_accessor=payload.get("wrapped_accessor"),
|
||||
),
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -426,8 +765,22 @@ def command_request(
|
||||
ttl=ttl,
|
||||
token=token,
|
||||
accessor=accessor,
|
||||
authz=authz,
|
||||
)
|
||||
emit_json(payload)
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="issued",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery=args.delivery,
|
||||
authz=authz,
|
||||
lease_handle=accessor,
|
||||
),
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -435,7 +788,9 @@ def command_exec(
|
||||
args: argparse.Namespace, runner: BaoRunner, grant: dict[str, Any]
|
||||
) -> int:
|
||||
ttl = args.ttl or grant["ttl"]["default"]
|
||||
validate_issue_request(grant, ttl, args.purpose, "exec-env")
|
||||
authz = authorize_request(
|
||||
args=args, grant=grant, ttl=ttl, purpose=args.purpose, delivery="exec-env"
|
||||
)
|
||||
extra_env, program = split_env_prefix(args.command)
|
||||
if args.dry_run:
|
||||
emit_json(
|
||||
@@ -446,22 +801,63 @@ def command_exec(
|
||||
"purpose": args.purpose,
|
||||
"ttl": ttl,
|
||||
"delivery_mode": "exec-env",
|
||||
"authorization_mode": authz.mode,
|
||||
"decision_id": authz.decision_id,
|
||||
"child_env": sorted([*extra_env.keys(), "VAULT_TOKEN"]),
|
||||
"child_command": program,
|
||||
}
|
||||
)
|
||||
print("DRY-RUN: bao " + shlex.join(token_create_args(grant, ttl)))
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="dry-run",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery="exec-env",
|
||||
authz=authz,
|
||||
),
|
||||
)
|
||||
return 0
|
||||
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="requested",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery="exec-env",
|
||||
authz=authz,
|
||||
),
|
||||
)
|
||||
result = runner.run(token_create_args(grant, ttl), quiet=True)
|
||||
token, accessor = parse_token_create(result.stdout)
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="issued",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery="exec-env",
|
||||
authz=authz,
|
||||
lease_handle=accessor,
|
||||
),
|
||||
)
|
||||
env = os.environ.copy()
|
||||
env.update(extra_env)
|
||||
env["VAULT_TOKEN"] = token
|
||||
exit_code = 1
|
||||
try:
|
||||
child = subprocess.run(
|
||||
program, env=env, capture_output=True, text=True, check=False
|
||||
)
|
||||
exit_code = child.returncode
|
||||
if child.stdout:
|
||||
print(redact(child.stdout, [token]), end="")
|
||||
if child.stderr:
|
||||
@@ -471,6 +867,20 @@ def command_exec(
|
||||
runner.run(
|
||||
["write", "auth/token/revoke-accessor", f"accessor={accessor}"], quiet=True
|
||||
)
|
||||
record_state_hub(
|
||||
args,
|
||||
state_hub_metadata(
|
||||
args=args,
|
||||
grant=grant,
|
||||
status="revoked",
|
||||
purpose=args.purpose,
|
||||
ttl=ttl,
|
||||
delivery="exec-env",
|
||||
authz=authz,
|
||||
lease_handle=accessor,
|
||||
exit_code=exit_code,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def command_status(args: argparse.Namespace, runner: BaoRunner) -> int:
|
||||
@@ -552,16 +962,53 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
help="Use the OpenBao CLI token helper inside the pod",
|
||||
)
|
||||
parser.add_argument("--lease-dir", type=Path, default=DEFAULT_LEASE_DIR)
|
||||
parser.add_argument(
|
||||
"--actor", default=os.environ.get("CREDENTIAL_ACTOR", DEFAULT_ACTOR)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--actor-type",
|
||||
default=os.environ.get("CREDENTIAL_ACTOR_TYPE", DEFAULT_ACTOR_TYPE),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--subject", default=os.environ.get("CREDENTIAL_SUBJECT", DEFAULT_SUBJECT)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--decision-id", default=os.environ.get("CREDENTIAL_DECISION_ID")
|
||||
)
|
||||
parser.add_argument("--flex-auth-url", default=os.environ.get("FLEX_AUTH_URL"))
|
||||
parser.add_argument(
|
||||
"--flex-auth-path",
|
||||
default=os.environ.get("FLEX_AUTH_PATH", "/credential-grants/authorize"),
|
||||
)
|
||||
parser.add_argument("--require-flex-auth", action="store_true")
|
||||
parser.add_argument(
|
||||
"--state-hub-url",
|
||||
default=os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--state-hub-workstream-id", default=os.environ.get("STATE_HUB_WORKSTREAM_ID")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--record-state-hub",
|
||||
action="store_true",
|
||||
help="Record non-secret request lifecycle metadata to State Hub",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--http-timeout",
|
||||
type=float,
|
||||
default=float(os.environ.get("CREDENTIAL_HTTP_TIMEOUT", "10")),
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command_name", required=True)
|
||||
|
||||
request = subparsers.add_parser(
|
||||
"request", help="Issue a wrapped token or local-token-file lease"
|
||||
"request",
|
||||
help="Issue a wrapped token, local-token-file lease, or Kubernetes-auth delegation",
|
||||
)
|
||||
add_common_issue_args(request)
|
||||
request.add_argument(
|
||||
"--delivery",
|
||||
choices=["local-token-file", "response-wrap"],
|
||||
choices=["local-token-file", "response-wrap", "kubernetes-auth"],
|
||||
default="local-token-file",
|
||||
)
|
||||
request.add_argument("--wrap-ttl", default="5m")
|
||||
@@ -593,6 +1040,8 @@ def main() -> int:
|
||||
args = parser.parse_args()
|
||||
args.lease_dir = resolve_repo_path(args.lease_dir)
|
||||
catalog = load_catalog(resolve_repo_path(Path(args.catalog)))
|
||||
if not args.state_hub_workstream_id:
|
||||
args.state_hub_workstream_id = catalog.get("state_hub_workstream_id")
|
||||
grant = None
|
||||
if args.command_name in {"request", "exec"}:
|
||||
grant = get_grant(catalog, args.grant)
|
||||
|
||||
Reference in New Issue
Block a user