infra/terraform/vault/sub-ca.nix
Raito Bezarius 27e17b3f34 fix(terraform/vault/sub-ca): policy path calculation requires path resource IDs
It's impossible to recover the path resource IDs from the Terraform
resource IDs form.

Let's just add the path component and do the right thing.

Signed-off-by: Raito Bezarius <masterancpp@gmail.com>
2025-01-02 18:39:43 +01:00

199 lines
7.8 KiB
Nix

{ config, lib, ... }:
let
toplevelConfig = config;
inherit (lib) tf filterAttrs mapAttrs replaceChars addErrorContext concatMapAttrs mapAttrs';
# Create a Vault path for a certificate.
mkPath = { enableVersioning, name, version, certVersion, id }:
if enableVersioning then
"${id}/v${toString version}/${name}/v${toString certVersion}"
else
"${id}/unversioned/${name}";
mkCommonName = { enableVersioning, certVersion, prefix }:
if enableVersioning then
"${prefix} v${toString certVersion} "
else
"${prefix} unversioned ";
mkCsrCommonName = { parentConfig, chainVersion, enableVersioning, certVersion, prefix, name }:
if enableVersioning then
assert lib.assertMsg (parentConfig.enableVersioning) "Versioning requires all the chain to be versioned. You forgot to enable versioning on the parent of '${name}'!";
"${prefix} v${chainVersion}"
else
if parentConfig.enableVersioning then
"${prefix} v${toString parentConfig.chainVersion}.unversioned"
else
"${prefix} unversioned";
mkDescription = { enableVersioning, certVersion, version, name, displayName }:
if enableVersioning then
"PKI engine hosting v${toString version} ${name} v${toString certVersion} for ${displayName}"
else
"Unversioned PKI engine hosting ${name} for ${displayName}";
mkSignId = { parentConfig, enableVersioning, certVersion, resourceId }:
if enableVersioning then
"${resourceId}_by_${parentConfig.name}_v${toString certVersion}"
else
"${resourceId}_by_${parentConfig.name}_unversioned";
hour_in_secs = 1 * 60 * 60;
hour_in_days = 24;
day_in_secs = hour_in_days * hour_in_secs;
resourceIdAsPath = resourceId: replaceChars [ "_" ] [ "/" ] resourceId;
# Explore the subCA tree fragment and emit
bfs = f: parentCA: initialState:
let
immediateChildren = mapAttrs (name: _:
addErrorContext "while evaluating sub-CA configuration `${name}` in `${parentCA.name}`:"
(parentCA.subCA.${name} or { })
) parentCA.subCA;
in
concatMapAttrs (name: value:
let pairWithMore = f parentCA name value initialState;
in
(if builtins.length (builtins.attrValues pairWithMore.value) > 0 then { ${pairWithMore.name} = pairWithMore.value; } else { }) // bfs f value (pairWithMore.state or initialState))
(filterAttrs (_: subCA: subCA.enable) immediateChildren);
concatBfs = f: parentCA:
let
immediateChildren = mapAttrs (name: _:
addErrorContext "while evaluating sub-CA configuration `${name}` in `${parentCA.name}`:"
(parentCA.subCA.${name} or { })
) parentCA.subCA;
in
concatMapAttrs (name: value:
let attrs = f parentCA name value;
in
attrs // concatBfs f value)
(filterAttrs (_: subCA: subCA.enable) immediateChildren);
in
{
resource.vault_mount = bfs (parent: name: config: _:
{
name = "${toplevelConfig.infra.pki.org.id}_${config.partialResourceId}";
value = {
inherit (toplevelConfig.infra.pki) provider;
path = mkPath {
inherit (config) enableVersioning version certVersion name;
inherit (toplevelConfig.infra.pki.org) id;
};
type = "pki";
description = mkDescription {
inherit (config) enableVersioning version certVersion name;
displayName = config.cn;
};
default_lease_ttl_seconds = 1 * hour_in_secs;
max_lease_ttl_seconds = 365 * day_in_secs;
};
}) config.infra.pki.ica2 { };
resource.vault_pki_secret_backend_intermediate_cert_request = bfs (parentConfig: name: config: _:
let
resourceId = "${toplevelConfig.infra.pki.org.id}_${config.partialResourceId}";
in
{
name = resourceId;
value = {
inherit (toplevelConfig.infra.pki) provider;
depends_on = [ "vault_mount.${resourceId}" ];
backend = tf.ref "vault_mount.${resourceId}.path";
type = "internal";
common_name = mkCommonName {
inherit (config) enableVersioning certVersion;
prefix = config.cn;
};
key_type = config.keyType;
};
}) config.infra.pki.ica2 { };
resource.vault_pki_secret_backend_root_sign_intermediate = bfs (parentConfig: name: config: state:
let
parentResourceId = "${toplevelConfig.infra.pki.org.id}_${parentConfig.partialResourceId}";
resourceId = "${toplevelConfig.infra.pki.org.id}_${config.partialResourceId}";
chainVersion = "${toString config.certVersion}.${state.chainVersion}";
in
{
state = {
inherit chainVersion;
};
name = mkSignId {
inherit parentConfig resourceId;
inherit (config) enableVersioning certVersion;
};
value = {
inherit (toplevelConfig.infra.pki) provider;
depends_on = [
"vault_mount.${resourceId}"
"vault_pki_secret_backend_intermediate_cert_request.${resourceId}"
];
backend = tf.ref "vault_mount.${parentResourceId}.path";
csr = tf.ref "vault_pki_secret_backend_intermediate_cert_request.${resourceId}.csr";
common_name = mkCsrCommonName {
inherit parentConfig chainVersion;
inherit (config) enableVersioning certVersion name;
prefix = config.cn;
};
exclude_cn_from_sans = true;
inherit (toplevelConfig.infra.pki.org) ou organization country locality province;
max_path_length = config.extensions.pathlen;
ttl = config.expiry.days * day_in_secs;
};
}) config.infra.pki.ica2 {
chainVersion = "${toString config.infra.pki.ica2.certVersion}";
};
resource.vault_pki_secret_backend_intermediate_set_signed = bfs (parentConfig: name: config: _:
let
# We cannot inline it in the submodule as it depends `config.infra.pki.org.id`.
resourceId = "${toplevelConfig.infra.pki.org.id}_${config.partialResourceId}";
signId = mkSignId {
inherit parentConfig resourceId;
inherit (config) enableVersioning certVersion;
};
parentResourceId = "${toplevelConfig.infra.pki.org.id}_${parentConfig.partialResourceId}";
in
{
name = "${resourceId}_signed_cert";
value = {
inherit (toplevelConfig.infra.pki) provider;
# Here we wait upon the parent configuration to have its final chain ready.
# We wait for our own signature.
depends_on = [
"vault_pki_secret_backend_root_sign_intermediate.${signId}"
"vault_pki_secret_backend_intermediate_set_signed.${parentResourceId}_signed_cert"
];
backend = tf.ref "vault_mount.${resourceId}.path";
# The final chain is the concatenation of the previous chain plus our certificate.
certificate = ''
${tf.ref "vault_pki_secret_backend_root_sign_intermediate.${signId}.certificate"}
${tf.ref "vault_pki_secret_backend_intermediate_set_signed.${parentResourceId}_signed_cert.certificate"}
'';
};
}) config.infra.pki.ica2 { };
resource.vault_pki_secret_backend_role = concatBfs (parentConfig: name: config:
let
resourceId = "${toplevelConfig.infra.pki.org.id}_${config.partialResourceId}";
in
mapAttrs' (name: role:
{
name = "${resourceId}_${name}";
value = role // {
inherit (toplevelConfig.infra.pki) provider;
# Default issuer for this backend, i.e. the sub CA.
# TODO: make this the exact issuer ref we are using.
issuer_ref = "default";
backend = tf.ref "vault_mount.${resourceId}.path";
};
}
) config.roles) config.infra.pki.ica2;
# Generate the empty policy if there's nothing.
infra.vault.policies = concatBfs (parentConfig: name: config:
mapAttrs (name: value:
mapAttrs' (rulePath: value: {
name = "${toplevelConfig.infra.pki.org.id}/${config.partialResourceIdPath}/${rulePath}";
inherit value;
}) value
) config.policies)
config.infra.pki.ica2;
}