NAME

Langertha::Skeid::KeyBroker::OpenBao - OpenBao-backed KeyBroker with AppRole auth and token renewal

VERSION

version 0.003

SYNOPSIS

my $broker = Langertha::Skeid::KeyBroker::OpenBao->new(
  addr        => $ENV{OPENBAO_ADDR} // 'http://127.0.0.1:8200',
  role_id     => $ENV{OPENBAO_ROLE_ID},
  secret_id   => $ENV{OPENBAO_SECRET_ID},
  renew_secs  => 300,  # renew token every 5 minutes
);

my $api_key = $broker->resolve_key('secret/skeid/remote/openai');   # blocking; CLI and tests

# the request path
$broker->key_async('secret/skeid/remote/openai', sub { my ($key, $error) = @_; ... });

addr

The OpenBao address (default OPENBAO_ADDR, else http://127.0.0.1:8200).

role_id

Required. The AppRole role id, injected at container start (OPENBAO_ROLE_ID).

secret_id

Required. The AppRole secret id, injected at container start (OPENBAO_SECRET_ID). Held in memory only.

renew_secs

How often "start_renewal" renews the token, in seconds (default 300; below 1 means 60).

verify_ssl

Whether to verify the OpenBao server's TLS certificate (default 1, override with OPENBAO_VERIFY_SSL=0). Turning it off on an https:// address means anything that can intercept the connection can hand out this process's AppRole token and every secret it resolves. It exists for a dev vault with a self-signed certificate and nothing else.

needs_refresh

True when there is no client token yet or it expires within the next 60 seconds.

refresh

Renews the token through renew-self, blocking. Dies when there is no token to renew with or the renewal fails -- the AppRole credential is then spent and the process has to restart. The request path renews through "resolve_key_async" instead.

resolve_key

my $key = $broker->resolve_key('secret/skeid/remote/groq');

Reads the KV-v2 secret the reference names and returns its api_key field, renewing the token first when it is due. secret/skeid/remote/groq is read from secret/data/skeid/remote/groq. Blocking -- never call it from a request handler. Returns undef for an empty reference, a failed read (warned with the reference and the HTTP status only) or a secret without api_key.

resolve_key_async

Non-blocking resolution against OpenBao, renewing the token first when it is due. Used by "key_async" in Langertha::Skeid::KeyBroker, which is what the request path calls; a cache hit never gets here.

With no IO loop running there is nothing to yield to, so this falls back to the blocking path — that is the CLI and the test suite, not the proxy.

start_renewal

$broker->start_renewal;

Renews the token on a timer instead of waiting for a request to notice it expired, so the request path finds a valid token already there. Called by "build_app" in Langertha::Skeid::Proxy; safe to call twice.

A failed renewal warns while the current token is still valid — a blip is not worth dropping in-flight requests for. Once it has actually expired the process dies, because a Skeid that cannot resolve keys is not a Skeid that should keep answering: the container restarts and logs in again with its AppRole credentials (ADR 0003).

list_secrets

my $names = $broker->list_secrets('secret/metadata/skeid/remote');

The keys under a path, via a blocking LIST. The path is the API path as given -- for KV-v2 that is secret/metadata/...; no secret/ rewriting happens here. Returns an empty arrayref on any failure. For setup scripts, not the request path.

DESCRIPTION

This KeyBroker implementation fetches API keys from OpenBao KV-v2 secrets. Security model:

  • AppRole credentials (role_id + secret_id) are injected at container start. Construction logs in with them, blocking, and dies when the login fails.

  • The login token is used only to call renew-self; the first resolution does that and gets the client token for API calls.

  • renew-self returns a new client token, which is also the next renew token.

  • The token is renewed every renew_secs seconds via renew-self once "start_renewal" runs.

  • If renewal fails after the token expired, the process dies, so the container restarts and logs in again.

  • No secret is ever written to disk. Tokens and resolved keys live only in memory, and are dropped when the broker is destroyed.

SEE ALSO

Langertha::Skeid::KeyBroker, "build_app" in Langertha::Skeid::Proxy (which builds this broker from OPENBAO_ROLE_ID, OPENBAO_SECRET_ID and OPENBAO_ADDR)

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-skeid/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.