Skip to content

Commit

Permalink
openbao: add module and tests
Browse files Browse the repository at this point in the history
Closes #386848
  • Loading branch information
brianmay committed Mar 4, 2025
1 parent 3df7288 commit 3d8270a
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 3 deletions.
1 change: 1 addition & 0 deletions describe
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
openbao: add module and tests
4 changes: 2 additions & 2 deletions nixos/modules/misc/ids.nix
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ in
#btsync = 113; # unused
#minecraft = 114; #dynamically allocated as of 2021-09-03
vault = 115;
# rippled = 116; #dynamically allocated as of 2021-09-18
openbao = 116;
murmur = 117;
foundationdb = 118;
newrelic = 119;
Expand Down Expand Up @@ -495,7 +495,7 @@ in
#btsync = 113; # unused
#minecraft = 114; # unused
vault = 115;
#ripped = 116; # unused
openbao = 116;
murmur = 117;
foundationdb = 118;
newrelic = 119;
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1407,6 +1407,7 @@
./services/security/torify.nix
./services/security/torsocks.nix
./services/security/usbguard.nix
./services/security/openbao.nix
./services/security/vault.nix
./services/security/vault-agent.nix
./services/security/vaultwarden/default.nix
Expand Down
261 changes: 261 additions & 0 deletions nixos/modules/services/security/openbao.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
{
config,
lib,
options,
pkgs,
...
}:
let
cfg = config.services.openbao;
opt = options.services.openbao;

configFile = pkgs.writeText "openbao.hcl" ''
# openbao in dev mode will refuse to start if its configuration sets listener
${lib.optionalString (!cfg.dev) ''
listener "tcp" {
address = "${cfg.address}"
${
if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then
''
tls_disable = "true"
''
else
''
tls_cert_file = "${cfg.tlsCertFile}"
tls_key_file = "${cfg.tlsKeyFile}"
''
}
${cfg.listenerExtraConfig}
}
''}
storage "${cfg.storageBackend}" {
${lib.optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
${lib.optionalString (cfg.storageConfig != null) cfg.storageConfig}
}
${lib.optionalString (cfg.telemetryConfig != "") ''
telemetry {
${cfg.telemetryConfig}
}
''}
${cfg.extraConfig}
'';

allConfigPaths = [ configFile ] ++ cfg.extraSettingsPaths;
configOptions = lib.escapeShellArgs (
lib.optional cfg.dev "-dev"
++ lib.optional (cfg.dev && cfg.devRootTokenID != null) "-dev-root-token-id=${cfg.devRootTokenID}"
++ (lib.concatMap (p: [
"-config"
p
]) allConfigPaths)
);

in

{
options = {
services.openbao = {
enable = lib.mkEnableOption "OpenBao daemon";

package = lib.mkPackageOption pkgs "openbao" { };

dev = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
In this mode, OpenBao runs in-memory and starts unsealed. This option is not meant production but for development and testing i.e. for nixos tests.
'';
};

devRootTokenID = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Initial root token. This only applies when {option}`services.openbao.dev` is true
'';
};

address = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1:8200";
description = "The name of the ip interface to listen to";
};

tlsCertFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/path/to/your/cert.pem";
description = "TLS certificate file. TLS will be disabled unless this option is set";
};

tlsKeyFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/path/to/your/key.pem";
description = "TLS private key file. TLS will be disabled unless this option is set";
};

listenerExtraConfig = lib.mkOption {
type = lib.types.lines;
default = ''
tls_min_version = "tls12"
'';
description = "Extra text appended to the listener section.";
};

storageBackend = lib.mkOption {
type = lib.types.enum [
"inmem"
"file"
"consul"
"zookeeper"
"s3"
"azure"
"dynamodb"
"etcd"
"mssql"
"mysql"
"postgresql"
"swift"
"gcs"
"raft"
];
default = "inmem";
description = "The name of the type of storage backend";
};

storagePath = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default =
if cfg.storageBackend == "file" || cfg.storageBackend == "raft" then "/var/lib/openbao" else null;
defaultText = lib.literalExpression ''
if config.${opt.storageBackend} == "file" || cfg.storageBackend == "raft"
then "/var/lib/openbao"
else null
'';
description = "Data directory for file backend";
};

storageConfig = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = ''
HCL configuration to insert in the storageBackend section.
Confidential values should not be specified here because this option's
value is written to the Nix store, which is publicly readable.
Provide credentials and such in a separate file using
[](#opt-services.openbao.extraSettingsPaths).
'';
};

telemetryConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Telemetry configuration";
};

extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Extra text appended to {file}`openbao.hcl`.";
};

extraSettingsPaths = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = ''
Configuration files to load besides the immutable one defined by the NixOS module.
This can be used to avoid putting credentials in the Nix store, which can be read by any user.
Each path can point to a JSON- or HCL-formatted file, or a directory
to be scanned for files with `.hcl` or
`.json` extensions.
To upload the confidential file with NixOps, use for example:
```
# https://releases.nixos.org/nixops/latest/manual/manual.html#opt-deployment.keys
deployment.keys."openbao.hcl" = let db = import ./db-credentials.nix; in {
text = ${"''"}
storage "postgresql" {
connection_url = "postgres://''${db.username}:''${db.password}@host.example.com/exampledb?sslmode=verify-ca"
}
${"''"};
user = "openbao";
};
services.openbao.extraSettingsPaths = ["/run/keys/openbao.hcl"];
services.openbao.storageBackend = "postgresql";
users.users.openbao.extraGroups = ["keys"];
```
'';
};
};
};

config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
message = ''The "inmem" storage expects no services.openbao.storagePath nor services.openbao.storageConfig'';
}
{
assertion = (
(cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null))
&& (cfg.storagePath != null -> (cfg.storageBackend == "file" || cfg.storageBackend == "raft"))
);
message = ''You must set services.openbao.storagePath only when using the "file" or "raft" backend'';
}
];

users.users.openbao = {
name = "openbao";
group = "openbao";
uid = config.ids.uids.openbao;
description = "OpenBao daemon user";
};
users.groups.openbao.gid = config.ids.gids.openbao;

systemd.tmpfiles.rules = lib.optional (
cfg.storagePath != null
) "d '${cfg.storagePath}' 0700 openbao openbao - -";

systemd.services.openbao = {
description = "OpenBao server daemon";

wantedBy = [ "multi-user.target" ];
after =
[
"network.target"
]
++ lib.optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";

restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.

startLimitIntervalSec = 60;
startLimitBurst = 3;
serviceConfig = {
User = "openbao";
Group = "openbao";
ExecStart = "${cfg.package}/bin/bao server ${configOptions}";
ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
StateDirectory = "openbao";
# In `dev` mode openbao will put its token here
Environment = lib.optional (cfg.dev) "HOME=/var/lib/openbao";
PrivateDevices = true;
PrivateTmp = true;
ProtectSystem = "full";
ProtectHome = "read-only";
AmbientCapabilities = "cap_ipc_lock";
NoNewPrivileges = true;
LimitCORE = 0;
KillSignal = "SIGINT";
TimeoutStopSec = "30s";
Restart = "on-failure";
};

unitConfig.RequiresMountsFor = lib.optional (cfg.storagePath != null) cfg.storagePath;
};
};

}
3 changes: 3 additions & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,9 @@ in {
ollama-rocm = runTestOn ["x86_64-linux" "aarch64-linux"] ./ollama-rocm.nix;
ombi = handleTest ./ombi.nix {};
openarena = handleTest ./openarena.nix {};
openbao = handleTest ./openbao.nix {};
openbao-dev = handleTest ./openbao-dev.nix {};
openbao-postgresql = handleTest ./openbao-postgresql.nix {};
openldap = handleTest ./openldap.nix {};
opensearch = discoverTests (import ./opensearch.nix);
openresty-lua = handleTest ./openresty-lua.nix {};
Expand Down
41 changes: 41 additions & 0 deletions nixos/tests/openbao-dev.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "openbao-dev";
meta = with pkgs.lib.maintainers; {
maintainers = [
brianmay
];
};
nodes.machine =
{ pkgs, config, ... }:
{
environment.systemPackages = [ pkgs.openbao ];
environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
environment.variables.VAULT_TOKEN = "phony-secret";

services.openbao = {
enable = true;
dev = true;
devRootTokenID = config.environment.variables.VAULT_TOKEN;
};
};

testScript = ''
import json
start_all()
machine.wait_for_unit("multi-user.target")
machine.wait_for_unit("openbao.service")
machine.wait_for_open_port(8200)
out = machine.succeed("bao status -format=json")
print(out)
status = json.loads(out)
assert status.get("initialized") == True
machine.succeed("bao kv put secret/foo bar=baz")
out = machine.succeed("bao kv get -format=json secret/foo")
print(out)
status = json.loads(out)
assert status.get("data", {}).get("data", {}).get("bar") == "baz"
'';
}
)
Loading

0 comments on commit 3d8270a

Please sign in to comment.