Skip to content

Commit 9f0cfb4

Browse files
committed
feat: add Vault agent template for static AWS creds
This template is useful for systems that use static AWS-compatible credentials, like Ceph RGW. For genuine AWS credentials, prefer the `aws-sts-credentials` template over this one, as the former supports STS for short-lived credentials. Signed-off-by: Drew Hess <src@drewhess.com>
1 parent dd46cf2 commit 9f0cfb4

File tree

5 files changed

+211
-40
lines changed

5 files changed

+211
-40
lines changed

examples/nix-darwin/build-host.nix

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@
9494
owner = "root";
9595
group = "root";
9696
};
97+
services.vault-agent.template.aws-credentials.binary-cache = {
98+
vaultPath = "aws/nix-binary-cache";
99+
dir = "/home/dhess/.aws";
100+
owner = "root";
101+
group = "root";
102+
};
97103
services.vault-agent.template.cachix.hackworthltd = {
98104
vaultPath = "secret/cachix/hackworthltd/write";
99105
dir = "/home/dhess/.config/cachix";

examples/nixos/build-host.nix

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ args
100100
owner = "root";
101101
group = "root";
102102
};
103+
services.vault-agent.template.aws-credentials.binary-cache = {
104+
vaultPath = "aws/nix-binary-cache";
105+
dir = "/home/dhess/.aws";
106+
owner = "root";
107+
group = "root";
108+
};
103109
services.vault-agent.template.cachix.hackworthltd = {
104110
vaultPath = "secret/cachix/hackworthltd/write";
105111
dir = "/home/dhess/.config/cachix";

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@
291291

292292
./nix/common/config/services/vault/agent/auth/approle
293293
./nix/common/config/services/vault/agent/template
294+
./nix/common/config/services/vault/agent/template/aws-credentials
294295
./nix/common/config/services/vault/agent/template/aws-sts-credentials
295296
./nix/common/config/services/vault/agent/template/cachix
296297
./nix/common/config/services/vault/agent/template/github-credentials
@@ -316,6 +317,7 @@
316317

317318
./nix/common/config/services/vault/agent/auth/approle
318319
./nix/common/config/services/vault/agent/template
320+
./nix/common/config/services/vault/agent/template/aws-credentials
319321
./nix/common/config/services/vault/agent/template/aws-sts-credentials
320322
./nix/common/config/services/vault/agent/template/cachix
321323
./nix/common/config/services/vault/agent/template/github-credentials
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
{
2+
config,
3+
pkgs,
4+
lib,
5+
inputs,
6+
...
7+
}:
8+
let
9+
cfg = config.services.vault-agent.template.aws-sts-credentials;
10+
enabled = cfg != { };
11+
12+
# A limitation of AWS STS tokens.
13+
maxTTL = 60 * 60 * 12;
14+
15+
credentialsFile = creds: "${creds.dir}/credentials";
16+
17+
awsTemplate =
18+
creds:
19+
pkgs.writeText "aws.credentials.ctmpl" ''
20+
[${creds.awsProfile}]
21+
{{ with secret "${creds.vaultPath}" "ttl=${builtins.toString creds.tokenTTL}" }}aws_access_key_id={{ .Data.access_key }}
22+
aws_secret_access_key={{ .Data.secret_key }}
23+
aws_session_token={{ .Data.security_token }}{{ end }}
24+
'';
25+
26+
# Note: while there is some chance of a race condition here between
27+
# the time when Vault Agent writes the credentials file and it
28+
# executes this script to "fix" the file's ownership, I expect in
29+
# practice that Vault Agent does not actually try to change the
30+
# owner when it writes the file, so in reality, this race should
31+
# only occur upon first launch.
32+
fixCredsOwner =
33+
creds:
34+
pkgs.writeShellScript "fix-aws-sts-credentials-owner" ''
35+
${pkgs.coreutils}/bin/chown ${creds.owner}:${creds.group} ${credentialsFile creds}
36+
'';
37+
38+
error_on_missing_key = creds: if creds.exitOnMissingKey then "true" else "false";
39+
40+
listOfCreds = lib.mapAttrsToList (_: creds: creds) cfg;
41+
vaultConfig = lib.concatMapStrings (creds: ''
42+
template {
43+
destination = "${credentialsFile creds}"
44+
source = "${awsTemplate creds}"
45+
perms = "0400"
46+
create_dest_dirs = false
47+
error_on_missing_key = ${error_on_missing_key creds}
48+
command = "${fixCredsOwner creds}"
49+
}
50+
'') listOfCreds;
51+
52+
mkdirsCmds = lib.concatMapStringsSep "\n" (
53+
creds: "${pkgs.coreutils}/bin/install -d -m 0700 -o ${creds.owner} -g ${creds.group} ${creds.dir}"
54+
) listOfCreds;
55+
56+
assertions = lib.flatten (
57+
map (creds: [
58+
{
59+
assertion = (creds.tokenTTL <= 60 * 60 * 12);
60+
message = "`services.vault-agent.template.aws-sts-credentials.${creds.name}.tokenTTL` is ${builtins.toString creds.tokenTTL}, but must be <= ${builtins.toString maxTTL}";
61+
}
62+
]) listOfCreds
63+
);
64+
65+
credentials =
66+
{ name, ... }:
67+
{
68+
options = {
69+
name = lib.mkOption {
70+
type = pkgs.lib.types.nonEmptyStr;
71+
default = name;
72+
example = "binary-cache";
73+
description = ''
74+
A short descriptive name for the generated credentials.
75+
'';
76+
};
77+
78+
vaultPath = lib.mkOption {
79+
type = pkgs.lib.types.nonEmptyStr;
80+
example = "aws/sts/rolename";
81+
description = ''
82+
The Vault AWS secrets engine path for the generated AWS
83+
credentials.
84+
'';
85+
};
86+
87+
awsProfile = lib.mkOption {
88+
type = pkgs.lib.types.nonEmptyStr;
89+
default = "default";
90+
example = "hydra";
91+
description = ''
92+
The AWS profile for which to configure the credentials.
93+
'';
94+
};
95+
96+
dir = lib.mkOption {
97+
type = pkgs.lib.types.nonStorePath;
98+
example = "/root/.aws";
99+
description = ''
100+
The directory where the AWS credentials will be persisted.
101+
In this directory, this module will persist the AWS
102+
credentials in a file whose name is
103+
<literal>credentials</literal>.
104+
105+
Note that, unfortunately, Vault can't handle paths like
106+
<literal>~user</literal>, so if you want to set this to a
107+
particular user's home directory, you'll need to specify the
108+
literal pathname here.
109+
'';
110+
};
111+
112+
owner = lib.mkOption {
113+
type = pkgs.lib.types.nonEmptyStr;
114+
example = "hydra-queue-runner";
115+
description = ''
116+
The filesystem owner of the credentials file that Vault
117+
Agent will persist to disk.
118+
'';
119+
};
120+
121+
group = lib.mkOption {
122+
type = pkgs.lib.types.nonEmptyStr;
123+
example = "hydra";
124+
description = ''
125+
The filesystem group of the credentials file that Vault
126+
Agent will persist to disk.
127+
'';
128+
};
129+
130+
tokenTTL = lib.mkOption {
131+
type = pkgs.lib.types.int;
132+
default = maxTTL;
133+
example = 60 * 60 * 2;
134+
description = ''
135+
The TTL of the generated AWS credentials, in seconds. Note
136+
that due to the mechanism used to generate these credentials,
137+
${builtins.toString maxTTL} is the maximum TTL.
138+
139+
Vault Agent will take care of renewing the credentials as
140+
they get close to expiry. Shorter values are better for
141+
security, but on the other hand, they also generate a higher
142+
load on the Vault server and increase the chance of failed
143+
AWS operations during any potential Vault downtime. The
144+
default value is probably best in most cases.
145+
'';
146+
};
147+
148+
exitOnMissingKey = lib.mkOption {
149+
type = pkgs.lib.types.bool;
150+
default = true;
151+
example = false;
152+
description = ''
153+
If true (the default), a failure to render the AWS credentials
154+
will cause Vault Agent to exit with an error. This is a
155+
safeguard against silent failure. As this is extremely
156+
unlikely to occur in normal operation, you should probably
157+
keep the default value.
158+
'';
159+
};
160+
};
161+
};
162+
163+
in
164+
{
165+
options.services.vault-agent.template.aws-sts-credentials = lib.mkOption {
166+
type = pkgs.lib.types.attrsOf (pkgs.lib.types.submodule credentials);
167+
default = { };
168+
example = {
169+
binary-cache = {
170+
vaultPath = "aws/sts/nix-binary-cache";
171+
dir = "/root/.aws";
172+
owner = "root";
173+
group = "root";
174+
};
175+
};
176+
description = "Configure AWS credentials templates.";
177+
};
178+
179+
config = lib.mkIf enabled {
180+
inherit assertions;
181+
services.vault-agent.config = vaultConfig;
182+
services.vault-agent.preCommands = mkdirsCmds;
183+
};
184+
}

nix/common/config/services/vault/agent/template/aws-sts-credentials/default.nix

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,17 @@
66
...
77
}:
88
let
9-
cfg = config.services.vault-agent.template.aws-sts-credentials;
9+
cfg = config.services.vault-agent.template.aws-credentials;
1010
enabled = cfg != { };
1111

12-
# A limitation of AWS STS tokens.
13-
maxTTL = 60 * 60 * 12;
14-
1512
credentialsFile = creds: "${creds.dir}/credentials";
1613

1714
awsTemplate =
1815
creds:
1916
pkgs.writeText "aws.credentials.ctmpl" ''
2017
[${creds.awsProfile}]
21-
{{ with secret "${creds.vaultPath}" "ttl=${builtins.toString creds.tokenTTL}" }}aws_access_key_id={{ .Data.access_key }}
22-
aws_secret_access_key={{ .Data.secret_key }}
23-
aws_session_token={{ .Data.security_token }}{{ end }}
18+
{{ with secret "${creds.vaultPath}" }}aws_access_key_id={{ .Data.AWS_ACCESS_KEY_ID }}
19+
aws_secret_access_key={{ .Data.AWS_SECRET_ACCESS_KEY }}
2420
'';
2521

2622
# Note: while there is some chance of a race condition here between
@@ -31,7 +27,7 @@ let
3127
# only occur upon first launch.
3228
fixCredsOwner =
3329
creds:
34-
pkgs.writeShellScript "fix-aws-sts-credentials-owner" ''
30+
pkgs.writeShellScript "fix-aws-credentials-owner" ''
3531
${pkgs.coreutils}/bin/chown ${creds.owner}:${creds.group} ${credentialsFile creds}
3632
'';
3733

@@ -53,15 +49,6 @@ let
5349
creds: "${pkgs.coreutils}/bin/install -d -m 0700 -o ${creds.owner} -g ${creds.group} ${creds.dir}"
5450
) listOfCreds;
5551

56-
assertions = lib.flatten (
57-
map (creds: [
58-
{
59-
assertion = (creds.tokenTTL <= 60 * 60 * 12);
60-
message = "`services.vault-agent.template.aws-sts-credentials.${creds.name}.tokenTTL` is ${builtins.toString creds.tokenTTL}, but must be <= ${builtins.toString maxTTL}";
61-
}
62-
]) listOfCreds
63-
);
64-
6552
credentials =
6653
{ name, ... }:
6754
{
@@ -77,10 +64,14 @@ let
7764

7865
vaultPath = lib.mkOption {
7966
type = pkgs.lib.types.nonEmptyStr;
80-
example = "aws/sts/rolename";
67+
example = "secret/aws/credentials";
8168
description = ''
82-
The Vault AWS secrets engine path for the generated AWS
83-
credentials.
69+
The Vault path where the secret containing the credentials is stored.
70+
71+
Typically this will be a KV store path containing static credentials.
72+
For genuine AWS credentials, it's safer to use Vault's support for
73+
AWS STS credentials. In that case, prefer the
74+
<literal>aws-sts-credentials</literal> Vault agent template over this one.
8475
'';
8576
};
8677

@@ -127,24 +118,6 @@ let
127118
'';
128119
};
129120

130-
tokenTTL = lib.mkOption {
131-
type = pkgs.lib.types.int;
132-
default = maxTTL;
133-
example = 60 * 60 * 2;
134-
description = ''
135-
The TTL of the generated AWS credentials, in seconds. Note
136-
that due to the mechanism used to generate these credentials,
137-
${builtins.toString maxTTL} is the maximum TTL.
138-
139-
Vault Agent will take care of renewing the credentials as
140-
they get close to expiry. Shorter values are better for
141-
security, but on the other hand, they also generate a higher
142-
load on the Vault server and increase the chance of failed
143-
AWS operations during any potential Vault downtime. The
144-
default value is probably best in most cases.
145-
'';
146-
};
147-
148121
exitOnMissingKey = lib.mkOption {
149122
type = pkgs.lib.types.bool;
150123
default = true;
@@ -162,12 +135,12 @@ let
162135

163136
in
164137
{
165-
options.services.vault-agent.template.aws-sts-credentials = lib.mkOption {
138+
options.services.vault-agent.template.aws-credentials = lib.mkOption {
166139
type = pkgs.lib.types.attrsOf (pkgs.lib.types.submodule credentials);
167140
default = { };
168141
example = {
169142
binary-cache = {
170-
vaultPath = "aws/sts/nix-binary-cache";
143+
vaultPath = "secret/aws/nix-binary-cache";
171144
dir = "/root/.aws";
172145
owner = "root";
173146
group = "root";

0 commit comments

Comments
 (0)