ryantm / agenix

age-encrypted secrets for NixOS and Home manager
https://matrix.to/#/#agenix:nixos.org
Creative Commons Zero v1.0 Universal
1.33k stars 106 forks source link

feature: Secret generator/bootstrap options #236

Open Lehmanator opened 5 months ago

Lehmanator commented 5 months ago

Make bootstrapping new NixOS systems using encrypted secrets less of a chore by creating options to generate new secrets for secrets specified in config that are missing their corresponding .age secret file.

Here's a quick-n-dirty set of NixOS/home-manager options to implement this:


# Enable automatic secret generation for missing secrets.
#   (disabled by default)
age.generate.enable = true;

# Bootstrap SSH key (if missing)
age.secrets.my-ssh-key.generate = rec {
  outputFile = "<path>";   # Use stdout when null
  command = ''
    ssh-keygen -t ed25519 -f ${outputFile}
  '';
};

# Bootstrap random key (if missing)
age.secrets.my-random-base64.generate.command = ''
  openssl rand -base64 64
'';

When a secret name cannot be found, these options would be run as a bash script to initialize the secret file with the stdout of the command or a file path to be generated by the command.

Additionally, other options could be used for common secret formats. e.g. age.secrets.<name>.type. Secrets could be declared as that type to reuse common secret generation commands or manually specify how they are to be generated.

# agenix could provide the most common type definitions, which users could extend
age.generate.types.ssh-rsa4096 = rec {
  commandOutput = "<path>"; # or stdout when null.
  command = ''
    ssh-keygen -t rsa -b 4096 -f ${commandOutput}
  '';
};

# Secrets could then use this by setting:
age.secrets.my-4096bit-ssh-key.type = "ssh-rsa4096";

Once all missing secrets are resolved, agenix could optionally do one of the following:

age.generate.push = {
  # Disabled by default
  enable = true;

  # Push directly to a branch, otherwise open new PR
  skipRequest = true;

  # Set remote. Could be filesystem directory, URL, or attrset specifying host, owner, repo, branch, rev, etc.
  repo = "https://github.com/<user>/my-nixos-configs.git";

  commitMessage = ''
    command to generate summary of secret changes.
    --- OR ---
    plain string interpolated with vars
  '';
  # ... whatever other options that might facilitate this. 
};

Might be a lot for this feature, but would simplify bootstrapping even further. This could also be done with generic hooks on the generation command or upon fully resolving all missing secrets.

Something like this would make bootstrapping NixOS with per-host or per-user secrets so much less of a pain, especially when you need a mixture of secrets that are unique per-host and shared between hosts.

Obviously, this would break reproducibility until all missing secrets are fully resolved, but would maintain reproducibility thereafter...which is not different from manually bootstrapping secrets, just with more automation.

Another (potentially) simpler implementation options could just be surfacing options to hook the various parts of the process

age.hooks = {
  missing.<type> = ''
    <secret generation command>
  '';
  missingResolved = ''
    cd /etc/nixos
    git add /tmp/agenix-new-secrets/*.age
    git commit -m "Generate secrets for host: ${config.networking.hostName}"
    git push origin develop
  '';

  activation = ''
  '';

}