#!@perl@/bin/perl use v5.30; use strict; use warnings; use feature 'signatures'; no warnings 'experimental::signatures'; use JSON; use File::Basename; use File::Temp; use File::Path qw( make_path ); # we deliberately don't attempt to keep decrypted secrets in mlock/dont-dump # memory because age doesn't either (yet). my $identity; $identity = "$ENV{HOME}/.seacrit/id" if defined $ENV{HOME}; sub encryptTo($ipath, $keys, $opath) { system("age", "-e", (map { ("-r", $_) } @$keys), "-o", $opath, $ipath) == 0 or die "age encryption failed"; } sub decrypt($ipath) { open(my $decfh, "-|", "age", "-d", "-i", $identity, $ipath) or die "decryption failed"; my $result = do { local $/; <$decfh>; }; close($decfh) or die "age decryption failed"; $result; } sub parseSecrets() { my $script = q{ { secrets, lib }: builtins.toJSON (lib.mapAttrs (_: a: a ++ secrets.default or []) secrets.secrets) } =~ s/'/'\\''/gr; my $result = qx{ nix-instantiate --eval --json --expr '$script' \\ --arg secrets 'import ./secrets.nix' \\ --arg lib '(import {}).lib' }; die "failed to evaluate secrets.nix" if $?; decode_json(decode_json($result)); } sub cmdEdit($config, $name, $editor = undef) { my $recipients = $config->{$name}; die "no such secret" unless defined $recipients; my $path = "store/$name"; $editor = $ENV{EDITOR} unless defined $editor; die 'no $EDITOR' unless defined $editor; -d dirname($path) || make_path(dirname($path)) or die "failed to create store dir"; my $tmp = File::Temp->new; print $tmp decrypt($path) if -e $path; system($editor, $tmp) == 0 or die "editing secret failed"; encryptTo($tmp, $recipients, $path); } sub cmdRekey($config) { for my $name (keys %$config) { cmdEdit($config, $name, "true") if -e "store/$name"; } } sub cmdGenkey($path) { umask 0077; -d dirname($path) || mkdir(dirname($path)) or die "could not create key dir"; system("age-keygen", "-o", $path) == 0 or die "key generation failed"; system("age-keygen", "-y", "-o", "$path.pub", $path) == 0 or die "key generation failed"; } sub usage() { print <<~EOT; usage: seacrit [ -i FILE ] edit seacrit [ -i FILE ] rekey seacrit [ -i FILE ] genkey General options: -h, --help print help -i, --identity FILE identity to use for decryption edit and rekey must be run in the directory of secrets.nix, genkey can be run anywhere. EOT } sub flop($msg) { say "error: ", $msg; say ""; usage; exit 1; } while (my $arg = shift @ARGV) { if ($arg =~ /^-h|--help$/) { usage; exit 0; } elsif ($arg =~ /^-i|--identity$/) { $identity = shift @ARGV or flop "-i needs an argument"; } elsif ($arg eq "edit") { flop "edit needs exactly one argument" unless $#ARGV == 0; cmdEdit(parseSecrets, $ARGV[0]); exit 0; } elsif ($arg eq "rekey") { flop "too many arguments" unless $#ARGV < 0; cmdRekey(parseSecrets); exit 0; } elsif ($arg eq "genkey") { flop "too many arguments" unless $#ARGV <= 0; cmdGenkey($ARGV[0] or $identity); exit 0; } else { flop "unknown argument $arg"; } } flop "need a command";