Secret Store Integration
The secret store integration provides a secure interface for managing sensitive data within the DAP orchestrator, ensuring that secret values are never exposed in command-line arguments, environment variables, or standard output logs. The architecture relies on a client layer that communicates with the orchestrator’s REST API to store secrets encrypted server-side, while the CLI layer enforces strict input handling by accepting values only through interactive prompts, stdin pipes, or files. This design minimizes the blast radius of potential leaks by ensuring that deployment records store only secret keys, and that the is_hidden_value flag prevents authenticated REST clients from retrieving plaintext values.
Client API and Data Flow
Section titled “Client API and Data Flow”The src/dap/secrets/client.py module exposes four primary functions for interacting with the orchestrator’s secret store at /api/v3.1/secrets/<key> 1. These functions utilize the open_orchestrator_client context manager to handle HTTP requests with appropriate authentication sessions or admin bootstrap results.
The upsert_secret function creates or updates a secret, supporting idempotent operations via the update_if_exists flag. It accepts a hidden parameter (defaulting to True) which sets is_hidden_value in the request body, ensuring that even authenticated clients receive value: null when retrieving the secret later. The list_secrets function retrieves metadata for all secrets but explicitly filters the response to exclude values, returning only keys, visibility, and hidden status to prevent accidental data exposure. The get_secret_meta function retrieves metadata for a specific key, returning the value only if is_hidden_value is false; otherwise, it returns None for the value field. The delete_secret function removes a secret, treating a 404 response as a successful no-op.
CLI Input Handling and Security
Section titled “CLI Input Handling and Security”The src/dap/secrets/commands.py module implements the dap secret subcommands, enforcing strict security policies on how secret values are provided to the system. The CLI explicitly rejects plaintext values via command-line arguments (--value) and environment variables to prevent leakage through /proc/<pid>/cmdline or shell history 2.
Instead, the CLI accepts secret values through three ranked channels, all handled by the read_secret helper from src/dap/secret_input:
- Interactive Prompt (Default): When stdin is a TTY, the CLI uses Click’s
hide_input=Trueto read the value directly from the terminal, ensuring it never appears in shell history or logs. - Stdin Pipe (
--value-stdin): Suitable for scripting, this flag reads the value from the process’s standard input, avoiding argv exposure. - File Path (
--value-file): Reads the value from a specified file, trimming whitespace.
The secret_put command uses these inputs to call upsert_secret, allowing operators to toggle the is_hidden_value flag via the --visible option. The secret_list command displays a table of keys and their visibility status but never prints values. The secret_show command displays metadata for a specific key; if the secret is hidden, it explicitly redacts the value field in the output. The secret_delete command removes the secret from the orchestrator.
"""DAP Secret store HTTP client.
The orchestrator stores secret values encrypted server-side under
``/api/v3.1/secrets/<key>``. Blueprints reference the values at
deployment-execution time via the ``{ get_secret: <key> }``
intrinsic function. The deployment record itself only stores the
secret KEY name, never the value -- so deployment inputs are safe
to inspect via the REST API or postgres dumps.
Two flags lock down the secret value's blast radius:
- ``is_hidden_value: true`` means even authenticated REST clients
receive ``value: null`` when they GET the secret. Only the
blueprint engine sees the plaintext during deployment evaluation.
- ``update_if_exists: true`` makes the PUT idempotent: re-running
upsert with the same value is a no-op, with a new value rotates
it in place.
"""
from __future__ import annotations
from typing import Any
from ..auth.session import Session
from ..bootstrap import BootstrapResult
from ..config import ClusterConfig
from ..errors import DapError
from ..orch_client import open_orchestrator_client
def upsert_secret(
config: ClusterConfig,
key: str,
value: str,
*,
session: Session | None = None,
admin: BootstrapResult | None = None,
insecure: bool = True,
visibility: str = "tenant",
hidden: bool = True,
"""``dap secret`` subcommand surface.
The secret VALUE never appears on the command line and never goes
through an environment variable. Three channels are accepted, ranked
by safety:
1. **Interactive prompt (default, when stdin is a TTY).** Click's
``hide_input=True`` reads from the terminal directly via getpass;
the value never lands in shell history, argv, env, or any captured
log. This is the recommended path for human operators.
2. ``--value-stdin`` -- pipe the value into the process's stdin.
Suitable for scripts (e.g. ``pass show bmc/admin | dap secret
put dap_bmc_password --value-stdin``).
3. ``--value-file PATH`` -- read the value from a file (whitespace
trimmed). Useful when the value already lives in an
access-controlled file on disk; the file path is the operator's
responsibility to clean up.
The deliberately-absent channels:
- ``--value <plaintext>`` is not accepted. Argv is exposed in
``/proc/<pid>/cmdline`` (world-readable on Linux); the secret would
leak to any UID that can read /proc.
- Environment variables are not accepted. They survive in shell
history when set via ``export``, are inherited by every
subprocess, and are frequently dumped by error reporters / crash
collectors.
"""
from __future__ import annotations
import click
from ..auth.session import load_session
from ..bootstrap import load_persisted
from ..config import load_config
from ..errors import DapError
from ..secret_input import read_secret
from .client import (
delete_secret as delete_secret_api,