Broker audit-core dynamic database credentials
This commit is contained in:
parent
d140e829eb
commit
9f6bdffec4
5 changed files with 245 additions and 12 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
version: 1
|
version: 1
|
||||||
updated: "2026-06-27"
|
updated: "2026-08-10"
|
||||||
owner_repo: railiance-platform
|
owner_repo: railiance-platform
|
||||||
owner_domain: financials
|
owner_domain: financials
|
||||||
workplan_id: RAILIANCE-WP-0005
|
workplan_id: RAILIANCE-WP-0005
|
||||||
|
|
@ -24,6 +24,84 @@ grant_classes:
|
||||||
- break-glass
|
- break-glass
|
||||||
|
|
||||||
grants:
|
grants:
|
||||||
|
- id: rapp-postgres/audit-core-runtime
|
||||||
|
title: audit-core runtime PostgreSQL lease
|
||||||
|
status: active
|
||||||
|
grant_class: self-service
|
||||||
|
credential_type: openbao-database-credential
|
||||||
|
issuer: openbao
|
||||||
|
audience: audit-core
|
||||||
|
description: Short-lived runtime connection to the audit-core database.
|
||||||
|
openbao:
|
||||||
|
namespace: openbao
|
||||||
|
token_role: audit-core-runtime-broker
|
||||||
|
issuer_policy: credential-broker-audit-core-runtime-issuer
|
||||||
|
policies: [credential-broker-audit-core-runtime]
|
||||||
|
disallowed_policies: [root, platform-admin]
|
||||||
|
mount_paths: [database/creds/audit-core-runtime]
|
||||||
|
database:
|
||||||
|
role: audit-core-runtime
|
||||||
|
host: platform-pg-rw.databases.svc.cluster.local
|
||||||
|
port: 5432
|
||||||
|
name: audit_core
|
||||||
|
env: {PGUSER: username, PGPASSWORD: password, PGHOST: host, PGPORT: port, PGDATABASE: database}
|
||||||
|
ttl: {default: 15m, max: 1h, renewable: false, requires_human_above: 1h}
|
||||||
|
actors:
|
||||||
|
allowed_types: [human-operator, approved-agent, ci-runner]
|
||||||
|
required_subject_binding: keycape-or-kubernetes-service-account
|
||||||
|
authorization:
|
||||||
|
flex_auth_required: false
|
||||||
|
flex_auth_mode: optional-preflight
|
||||||
|
approval_required: false
|
||||||
|
purpose_required: true
|
||||||
|
allowed_purpose_examples: [audit-core-runtime]
|
||||||
|
delivery:
|
||||||
|
allowed: [exec-env]
|
||||||
|
preferred: exec-env
|
||||||
|
denied: [chat, state-hub-body, git, command-line-token-argument, llm-prompt]
|
||||||
|
exec_env: {child_only: true, redact_logs: true}
|
||||||
|
audit: {openbao_audit_required: true, state_hub_metadata_allowed: true, record_secret_values: false}
|
||||||
|
revocation: {required: true, by_accessor: true, on_exec_exit: true, on_denied_request: false}
|
||||||
|
|
||||||
|
- id: rapp-postgres/audit-core-migration
|
||||||
|
title: audit-core migration PostgreSQL lease
|
||||||
|
status: active
|
||||||
|
grant_class: self-service
|
||||||
|
credential_type: openbao-database-credential
|
||||||
|
issuer: openbao
|
||||||
|
audience: audit-core
|
||||||
|
description: Short-lived migration connection to the audit-core database.
|
||||||
|
openbao:
|
||||||
|
namespace: openbao
|
||||||
|
token_role: audit-core-migration-broker
|
||||||
|
issuer_policy: credential-broker-audit-core-migration-issuer
|
||||||
|
policies: [credential-broker-audit-core-migration]
|
||||||
|
disallowed_policies: [root, platform-admin]
|
||||||
|
mount_paths: [database/creds/audit-core-migration]
|
||||||
|
database:
|
||||||
|
role: audit-core-migration
|
||||||
|
host: platform-pg-rw.databases.svc.cluster.local
|
||||||
|
port: 5432
|
||||||
|
name: audit_core
|
||||||
|
env: {PGUSER: username, PGPASSWORD: password, PGHOST: host, PGPORT: port, PGDATABASE: database}
|
||||||
|
ttl: {default: 10m, max: 30m, renewable: false, requires_human_above: 30m}
|
||||||
|
actors:
|
||||||
|
allowed_types: [human-operator, approved-agent, ci-runner]
|
||||||
|
required_subject_binding: keycape-or-kubernetes-service-account
|
||||||
|
authorization:
|
||||||
|
flex_auth_required: false
|
||||||
|
flex_auth_mode: optional-preflight
|
||||||
|
approval_required: false
|
||||||
|
purpose_required: true
|
||||||
|
allowed_purpose_examples: [audit-core-migration]
|
||||||
|
delivery:
|
||||||
|
allowed: [exec-env]
|
||||||
|
preferred: exec-env
|
||||||
|
denied: [chat, state-hub-body, git, command-line-token-argument, llm-prompt]
|
||||||
|
exec_env: {child_only: true, redact_logs: true}
|
||||||
|
audit: {openbao_audit_required: true, state_hub_metadata_allowed: true, record_secret_values: false}
|
||||||
|
revocation: {required: true, by_accessor: true, on_exec_exit: true, on_denied_request: false}
|
||||||
|
|
||||||
- id: ops-warden/warden-sign
|
- id: ops-warden/warden-sign
|
||||||
title: Ops Warden OpenBao SSH signing smoke token
|
title: Ops Warden OpenBao SSH signing smoke token
|
||||||
status: pilot
|
status: pilot
|
||||||
|
|
|
||||||
17
docs/rapp-postgres-boundary.md
Normal file
17
docs/rapp-postgres-boundary.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# rapp-postgres package boundary
|
||||||
|
|
||||||
|
`rapp-postgres` owns the PostgreSQL package manifests, consumer declarations,
|
||||||
|
role/database provisioning surface, isolation tests, database-specific
|
||||||
|
operations, and recovery procedure for the shared `platform-pg` service.
|
||||||
|
|
||||||
|
`railiance-platform` retains cluster-wide governance: the CNPG operator,
|
||||||
|
Kubernetes access, storage-class policy, monitoring substrate, approved S3
|
||||||
|
backup target and its credentials, and the OpenBao credential-broker grant
|
||||||
|
catalog. It configures the OpenBao database secrets engine from the creation
|
||||||
|
and revocation statements owned in the rapp-postgres playbook; it does not copy
|
||||||
|
those procedures into this repo.
|
||||||
|
|
||||||
|
The package never commits credentials. A consumer request is declared and
|
||||||
|
provisioned by rapp-postgres, while the workload receives a short-lived lease
|
||||||
|
through the platform broker. The upstream backup target and database-engine
|
||||||
|
bootstrap are governance prerequisites, not package-owned secret material.
|
||||||
|
|
@ -29,7 +29,7 @@ REQUIRED_DENIED_MODES = {
|
||||||
"llm-prompt",
|
"llm-prompt",
|
||||||
}
|
}
|
||||||
|
|
||||||
ALLOWED_CREDENTIAL_TYPES = {"openbao-token"}
|
ALLOWED_CREDENTIAL_TYPES = {"openbao-token", "openbao-database-credential"}
|
||||||
ALLOWED_GRANT_CLASSES = {"self-service", "approval-required", "break-glass"}
|
ALLOWED_GRANT_CLASSES = {"self-service", "approval-required", "break-glass"}
|
||||||
ALLOWED_GRANT_STATUSES = {"pilot", "active", "deprecated", "disabled"}
|
ALLOWED_GRANT_STATUSES = {"pilot", "active", "deprecated", "disabled"}
|
||||||
DISALLOWED_POLICIES = {"root", "platform-admin"}
|
DISALLOWED_POLICIES = {"root", "platform-admin"}
|
||||||
|
|
@ -151,6 +151,16 @@ def validate_grant(
|
||||||
)
|
)
|
||||||
require_list(openbao.get("mount_paths"), f"{prefix}.openbao.mount_paths", errors)
|
require_list(openbao.get("mount_paths"), f"{prefix}.openbao.mount_paths", errors)
|
||||||
|
|
||||||
|
if credential_type == "openbao-database-credential":
|
||||||
|
database = require_dict(grant_obj.get("database"), f"{prefix}.database", errors)
|
||||||
|
require_nonempty_string(database.get("role"), f"{prefix}.database.role", errors)
|
||||||
|
require_nonempty_string(database.get("host"), f"{prefix}.database.host", errors)
|
||||||
|
require_nonempty_string(database.get("name"), f"{prefix}.database.name", errors)
|
||||||
|
env = require_dict(database.get("env"), f"{prefix}.database.env", errors)
|
||||||
|
required_fields = {"username", "password", "host", "port", "database"}
|
||||||
|
if set(env.values()) != required_fields:
|
||||||
|
errors.append(f"{prefix}.database.env must map exactly {sorted(required_fields)}")
|
||||||
|
|
||||||
ttl = require_dict(grant_obj.get("ttl"), f"{prefix}.ttl", errors)
|
ttl = require_dict(grant_obj.get("ttl"), f"{prefix}.ttl", errors)
|
||||||
default_ttl = ttl_seconds(ttl.get("default"), f"{prefix}.ttl.default", errors)
|
default_ttl = ttl_seconds(ttl.get("default"), f"{prefix}.ttl.default", errors)
|
||||||
max_ttl = ttl_seconds(ttl.get("max"), f"{prefix}.ttl.max", errors)
|
max_ttl = ttl_seconds(ttl.get("max"), f"{prefix}.ttl.max", errors)
|
||||||
|
|
@ -228,6 +238,8 @@ def validate_grant(
|
||||||
)
|
)
|
||||||
if preferred and preferred not in allowed:
|
if preferred and preferred not in allowed:
|
||||||
errors.append(f"{prefix}.delivery.preferred must be in delivery.allowed")
|
errors.append(f"{prefix}.delivery.preferred must be in delivery.allowed")
|
||||||
|
if credential_type == "openbao-database-credential" and allowed != {"exec-env"}:
|
||||||
|
errors.append(f"{prefix}.delivery.allowed must be exec-env only for database credentials")
|
||||||
if "local-token-file" in allowed:
|
if "local-token-file" in allowed:
|
||||||
local_file = require_dict(
|
local_file = require_dict(
|
||||||
delivery.get("local_token_file"),
|
delivery.get("local_token_file"),
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,10 @@ def get_grant(catalog: dict[str, Any], grant_id: str) -> dict[str, Any]:
|
||||||
fail("catalog grants must be a list")
|
fail("catalog grants must be a list")
|
||||||
for grant in grants:
|
for grant in grants:
|
||||||
if isinstance(grant, dict) and grant.get("id") == grant_id:
|
if isinstance(grant, dict) and grant.get("id") == grant_id:
|
||||||
if grant.get("credential_type") != "openbao-token":
|
if grant.get("credential_type") not in {
|
||||||
|
"openbao-token",
|
||||||
|
"openbao-database-credential",
|
||||||
|
}:
|
||||||
fail(
|
fail(
|
||||||
f"unsupported credential_type for {grant_id}: {grant.get('credential_type')}"
|
f"unsupported credential_type for {grant_id}: {grant.get('credential_type')}"
|
||||||
)
|
)
|
||||||
|
|
@ -414,6 +417,7 @@ class BaoRunner:
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
use_token_helper: bool,
|
use_token_helper: bool,
|
||||||
issuer_token: str | None,
|
issuer_token: str | None,
|
||||||
|
local_bao: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.kubectl_parts = shlex.split(kubectl)
|
self.kubectl_parts = shlex.split(kubectl)
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
|
@ -421,6 +425,7 @@ class BaoRunner:
|
||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
self.use_token_helper = use_token_helper
|
self.use_token_helper = use_token_helper
|
||||||
self.issuer_token = issuer_token
|
self.issuer_token = issuer_token
|
||||||
|
self.local_bao = local_bao
|
||||||
|
|
||||||
def run(
|
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
|
||||||
|
|
@ -429,6 +434,28 @@ class BaoRunner:
|
||||||
print("DRY-RUN: bao " + shlex.join(args))
|
print("DRY-RUN: bao " + shlex.join(args))
|
||||||
return subprocess.CompletedProcess(args, 0, "", "")
|
return subprocess.CompletedProcess(args, 0, "", "")
|
||||||
|
|
||||||
|
if self.local_bao:
|
||||||
|
if not self.issuer_token:
|
||||||
|
raise RuntimeError("issuer token is required for --local-bao")
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["BAO_TOKEN"] = self.issuer_token
|
||||||
|
env["VAULT_TOKEN"] = self.issuer_token
|
||||||
|
result = subprocess.run(
|
||||||
|
["bao", *args], input=input_text, env=env,
|
||||||
|
capture_output=True, text=True, check=False,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
if result.stdout and not quiet:
|
||||||
|
print(redact(result.stdout, [self.issuer_token]), end="")
|
||||||
|
if result.stderr:
|
||||||
|
print(redact(result.stderr, [self.issuer_token]), file=sys.stderr, end="")
|
||||||
|
raise SystemExit(result.returncode)
|
||||||
|
if result.stdout and not quiet:
|
||||||
|
print(redact(result.stdout, [self.issuer_token]), end="")
|
||||||
|
if result.stderr and not quiet:
|
||||||
|
print(redact(result.stderr, [self.issuer_token]), file=sys.stderr, end="")
|
||||||
|
return result
|
||||||
|
|
||||||
if self.use_token_helper:
|
if self.use_token_helper:
|
||||||
cmd = (
|
cmd = (
|
||||||
self.kubectl_parts
|
self.kubectl_parts
|
||||||
|
|
@ -474,6 +501,54 @@ class BaoRunner:
|
||||||
print(redact(result.stderr), file=sys.stderr, end="")
|
print(redact(result.stderr), file=sys.stderr, end="")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def run_with_token(
|
||||||
|
self, token: str, args: list[str], *, quiet: bool = False
|
||||||
|
) -> subprocess.CompletedProcess[str]:
|
||||||
|
if self.dry_run:
|
||||||
|
print("DRY-RUN: bao " + shlex.join(args))
|
||||||
|
return subprocess.CompletedProcess(args, 0, "", "")
|
||||||
|
if self.local_bao:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["BAO_TOKEN"] = token
|
||||||
|
env["VAULT_TOKEN"] = token
|
||||||
|
result = subprocess.run(
|
||||||
|
["bao", *args], env=env, capture_output=True, text=True, check=False
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
if result.stdout and not quiet:
|
||||||
|
print(redact(result.stdout, [token]), end="")
|
||||||
|
if result.stderr:
|
||||||
|
print(redact(result.stderr, [token]), file=sys.stderr, end="")
|
||||||
|
raise SystemExit(result.returncode)
|
||||||
|
if result.stdout and not quiet:
|
||||||
|
print(redact(result.stdout, [token]), end="")
|
||||||
|
if result.stderr and not quiet:
|
||||||
|
print(redact(result.stderr, [token]), file=sys.stderr, end="")
|
||||||
|
return result
|
||||||
|
cmd = (
|
||||||
|
self.kubectl_parts
|
||||||
|
+ [
|
||||||
|
"exec", "-i", "-n", self.namespace, self.pod, "--", "sh", "-c",
|
||||||
|
'read -r BAO_TOKEN; export BAO_TOKEN; export VAULT_TOKEN="$BAO_TOKEN"; exec bao "$@"',
|
||||||
|
"sh",
|
||||||
|
]
|
||||||
|
+ args
|
||||||
|
)
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, input=token + "\n", capture_output=True, text=True, check=False
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
if result.stdout and not quiet:
|
||||||
|
print(redact(result.stdout, [token]), end="")
|
||||||
|
if result.stderr:
|
||||||
|
print(redact(result.stderr, [token]), file=sys.stderr, end="")
|
||||||
|
raise SystemExit(result.returncode)
|
||||||
|
if result.stdout and not quiet:
|
||||||
|
print(redact(result.stdout, [token]), end="")
|
||||||
|
if result.stderr and not quiet:
|
||||||
|
print(redact(result.stderr, [token]), file=sys.stderr, end="")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def token_create_args(
|
def token_create_args(
|
||||||
grant: dict[str, Any], ttl: str, wrap_ttl: str | None = None
|
grant: dict[str, Any], ttl: str, wrap_ttl: str | None = None
|
||||||
|
|
@ -501,6 +576,16 @@ def parse_token_create(stdout: str) -> tuple[str, str]:
|
||||||
return token, accessor
|
return token, accessor
|
||||||
|
|
||||||
|
|
||||||
|
def parse_database_credential(stdout: str) -> tuple[str, str, str]:
|
||||||
|
try:
|
||||||
|
payload = json.loads(stdout)
|
||||||
|
return payload["lease_id"], payload["data"]["username"], payload["data"]["password"]
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
raise SystemExit(
|
||||||
|
f"ERROR: could not parse database credential response: {exc}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
def parse_wrap_create(stdout: str) -> dict[str, Any]:
|
def parse_wrap_create(stdout: str) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
payload = json.loads(stdout)
|
payload = json.loads(stdout)
|
||||||
|
|
@ -793,6 +878,12 @@ def command_exec(
|
||||||
args=args, grant=grant, ttl=ttl, purpose=args.purpose, delivery="exec-env"
|
args=args, grant=grant, ttl=ttl, purpose=args.purpose, delivery="exec-env"
|
||||||
)
|
)
|
||||||
extra_env, program = split_env_prefix(args.command)
|
extra_env, program = split_env_prefix(args.command)
|
||||||
|
credential_type = grant["credential_type"]
|
||||||
|
child_env = [*extra_env.keys()]
|
||||||
|
if credential_type == "openbao-token":
|
||||||
|
child_env.append("VAULT_TOKEN")
|
||||||
|
else:
|
||||||
|
child_env.extend((grant.get("database") or {}).get("env", {}).keys())
|
||||||
if args.dry_run:
|
if args.dry_run:
|
||||||
emit_json(
|
emit_json(
|
||||||
{
|
{
|
||||||
|
|
@ -804,7 +895,7 @@ def command_exec(
|
||||||
"delivery_mode": "exec-env",
|
"delivery_mode": "exec-env",
|
||||||
"authorization_mode": authz.mode,
|
"authorization_mode": authz.mode,
|
||||||
"decision_id": authz.decision_id,
|
"decision_id": authz.decision_id,
|
||||||
"child_env": sorted([*extra_env.keys(), "VAULT_TOKEN"]),
|
"child_env": sorted(child_env),
|
||||||
"child_command": program,
|
"child_command": program,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -837,6 +928,28 @@ def command_exec(
|
||||||
)
|
)
|
||||||
result = runner.run(token_create_args(grant, ttl), quiet=True)
|
result = runner.run(token_create_args(grant, ttl), quiet=True)
|
||||||
token, accessor = parse_token_create(result.stdout)
|
token, accessor = parse_token_create(result.stdout)
|
||||||
|
lease_handle = accessor
|
||||||
|
secret_values = [token]
|
||||||
|
injected: dict[str, str]
|
||||||
|
if credential_type == "openbao-token":
|
||||||
|
injected = {"VAULT_TOKEN": token}
|
||||||
|
else:
|
||||||
|
database = grant.get("database") or {}
|
||||||
|
role = database.get("role")
|
||||||
|
env_map = database.get("env") or {}
|
||||||
|
credential = runner.run_with_token(
|
||||||
|
token, ["read", "-format=json", f"database/creds/{role}"], quiet=True
|
||||||
|
)
|
||||||
|
lease_handle, username, password = parse_database_credential(credential.stdout)
|
||||||
|
values = {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"host": str(database.get("host", "")),
|
||||||
|
"port": str(database.get("port", "5432")),
|
||||||
|
"database": str(database.get("name", "")),
|
||||||
|
}
|
||||||
|
injected = {name: values[field] for name, field in env_map.items()}
|
||||||
|
secret_values.extend((username, password))
|
||||||
record_state_hub(
|
record_state_hub(
|
||||||
args,
|
args,
|
||||||
state_hub_metadata(
|
state_hub_metadata(
|
||||||
|
|
@ -847,12 +960,12 @@ def command_exec(
|
||||||
ttl=ttl,
|
ttl=ttl,
|
||||||
delivery="exec-env",
|
delivery="exec-env",
|
||||||
authz=authz,
|
authz=authz,
|
||||||
lease_handle=accessor,
|
lease_handle=lease_handle,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env.update(extra_env)
|
env.update(extra_env)
|
||||||
env["VAULT_TOKEN"] = token
|
env.update(injected)
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
try:
|
try:
|
||||||
child = subprocess.run(
|
child = subprocess.run(
|
||||||
|
|
@ -860,14 +973,14 @@ def command_exec(
|
||||||
)
|
)
|
||||||
exit_code = child.returncode
|
exit_code = child.returncode
|
||||||
if child.stdout:
|
if child.stdout:
|
||||||
print(redact(child.stdout, [token]), end="")
|
print(redact(child.stdout, secret_values), end="")
|
||||||
if child.stderr:
|
if child.stderr:
|
||||||
print(redact(child.stderr, [token]), file=sys.stderr, end="")
|
print(redact(child.stderr, secret_values), file=sys.stderr, end="")
|
||||||
return child.returncode
|
return child.returncode
|
||||||
finally:
|
finally:
|
||||||
runner.run(
|
if credential_type == "openbao-database-credential":
|
||||||
["write", "auth/token/revoke-accessor", f"accessor={accessor}"], quiet=True
|
runner.run(["lease", "revoke", lease_handle], quiet=True)
|
||||||
)
|
runner.run(["write", "auth/token/revoke-accessor", f"accessor={accessor}"], quiet=True)
|
||||||
record_state_hub(
|
record_state_hub(
|
||||||
args,
|
args,
|
||||||
state_hub_metadata(
|
state_hub_metadata(
|
||||||
|
|
@ -878,7 +991,7 @@ def command_exec(
|
||||||
ttl=ttl,
|
ttl=ttl,
|
||||||
delivery="exec-env",
|
delivery="exec-env",
|
||||||
authz=authz,
|
authz=authz,
|
||||||
lease_handle=accessor,
|
lease_handle=lease_handle,
|
||||||
exit_code=exit_code,
|
exit_code=exit_code,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -962,6 +1075,10 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Use the OpenBao CLI token helper inside the pod",
|
help="Use the OpenBao CLI token helper inside the pod",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--local-bao", action="store_true",
|
||||||
|
help="Run the workstation bao CLI instead of execing into the server pod",
|
||||||
|
)
|
||||||
parser.add_argument("--lease-dir", type=Path, default=DEFAULT_LEASE_DIR)
|
parser.add_argument("--lease-dir", type=Path, default=DEFAULT_LEASE_DIR)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--actor", default=os.environ.get("CREDENTIAL_ACTOR", DEFAULT_ACTOR)
|
"--actor", default=os.environ.get("CREDENTIAL_ACTOR", DEFAULT_ACTOR)
|
||||||
|
|
@ -1063,6 +1180,7 @@ def main() -> int:
|
||||||
dry_run=args.dry_run,
|
dry_run=args.dry_run,
|
||||||
use_token_helper=args.use_token_helper,
|
use_token_helper=args.use_token_helper,
|
||||||
issuer_token=issuer_token,
|
issuer_token=issuer_token,
|
||||||
|
local_bao=args.local_bao,
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.command_name == "request":
|
if args.command_name == "request":
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,14 @@ def sample_grant() -> dict:
|
||||||
|
|
||||||
|
|
||||||
class CredentialHelperTests(unittest.TestCase):
|
class CredentialHelperTests(unittest.TestCase):
|
||||||
|
def test_database_credential_response_is_parsed_without_emission(self) -> None:
|
||||||
|
lease_id, username, password = credential.parse_database_credential(
|
||||||
|
'{"lease_id":"database/creds/runtime/lease-1","data":{"username":"leased-user","password":"leased-secret"}}'
|
||||||
|
)
|
||||||
|
self.assertEqual(lease_id, "database/creds/runtime/lease-1")
|
||||||
|
self.assertEqual(username, "leased-user")
|
||||||
|
self.assertEqual(password, "leased-secret")
|
||||||
|
|
||||||
def test_ttl_over_max_is_rejected(self) -> None:
|
def test_ttl_over_max_is_rejected(self) -> None:
|
||||||
with self.assertRaises(SystemExit):
|
with self.assertRaises(SystemExit):
|
||||||
credential.validate_issue_request(
|
credential.validate_issue_request(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue