#!python
"""sandbroker-token-set - install a 1Password service-account token for a vault.

    sandbroker-token-set <vault>          # reads the token from stdin (or a prompt)

The token is read from STDIN (or an interactive no-echo prompt), never from
argv, so it never appears in a command line, `ps`, shell history, or an agent's
context. It is written to:

    $SANDBROKER_BASE/var/tokens/<vault>.token   (0400, owner-only)

The daemon (_op_token_for) prefers this per-vault token, so a session bound to
<vault> can resolve op://<vault>/... and NOTHING else -- the token is scoped by
1Password to that one vault (constraint C3), so it physically cannot cross
vaults even if the daemon were tricked.

PRODUCTION note: the prod token dir is /opt/sandbroker/var/tokens (0700 sandbroker),
so on the prod box run this as the daemon user, e.g.:

    printf %s "$OP_TOKEN" | sudo -u sandbroker SANDBROKER_BASE=/opt/sandbroker sandbroker-token-set <vault>

Mint a FRESH per-project service account (never reuse a leaked one), scoped
read-only to exactly this vault, at 1password.com -> Developer -> Service Accounts.
"""
import os
import sys

BASE = os.environ.get("SANDBROKER_BASE", "/opt/sandbroker")
TOKENS_DIR = os.path.join(BASE, "var", "tokens")
VAULT_OK = __import__("re").compile(r"^[A-Za-z0-9][A-Za-z0-9 _.\-]{0,62}$")


def main(argv) -> int:
    if len(argv) != 2 or argv[1] in ("-h", "--help"):
        sys.stderr.write(__doc__)
        return 2
    vault = argv[1]
    if not VAULT_OK.match(vault):
        sys.stderr.write("sandbroker-token-set: bad vault name %r\n" % vault)
        return 2

    if sys.stdin.isatty():
        import getpass
        token = getpass.getpass("1Password service-account token for %r: " % vault)
    else:
        token = sys.stdin.readline().strip()
    if not token:
        sys.stderr.write("sandbroker-token-set: empty token, nothing written\n")
        return 2
    if not token.startswith("ops_"):
        sys.stderr.write("sandbroker-token-set: warning: token does not start with "
                         "'ops_' (1Password service-account tokens do)\n")

    try:
        os.makedirs(TOKENS_DIR, exist_ok=True)
        os.chmod(TOKENS_DIR, 0o700)
    except OSError as exc:
        sys.stderr.write("sandbroker-token-set: cannot create %s: %s\n" % (TOKENS_DIR, exc))
        return 1

    path = os.path.join(TOKENS_DIR, vault + ".token")
    tmp = path + ".tmp"
    fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o400)
    try:
        with os.fdopen(fd, "w") as fh:
            fh.write(token)
        os.replace(tmp, path)
        os.chmod(path, 0o400)
    except OSError as exc:
        sys.stderr.write("sandbroker-token-set: write failed: %s\n" % exc)
        return 1
    # Never echo the token; confirm only by length.
    print("stored token for vault %r (%d chars) at %s" % (vault, len(token), path))
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
