diff options
| author | pennae <pennae.git@quasiparticle.net> | 2021-08-11 07:12:26 +0200 | 
|---|---|---|
| committer | pennae <pennae.git@quasiparticle.net> | 2021-08-11 07:17:43 +0200 | 
| commit | 99d575a882e00e55871db934901e3817f5daba28 (patch) | |
| tree | 76f4b397d0719659d8ebd6ded310bc87875afbc0 /util | |
| download | seacrit-99d575a882e00e55871db934901e3817f5daba28.tar.gz seacrit-99d575a882e00e55871db934901e3817f5daba28.tar.xz seacrit-99d575a882e00e55871db934901e3817f5daba28.zip  | |
initial commit
Diffstat (limited to 'util')
| -rw-r--r-- | util/default.nix | 11 | ||||
| -rwxr-xr-x | util/seacrit | 123 | 
2 files changed, 134 insertions, 0 deletions
diff --git a/util/default.nix b/util/default.nix new file mode 100644 index 0000000..688611b --- /dev/null +++ b/util/default.nix @@ -0,0 +1,11 @@ +{ pkgs ? import <nixpkgs> {} }: + +pkgs.substituteAll { +  pname = "seacrit"; +  version = "0.0.1"; + +  src = ./seacrit; +  dir = "bin"; +  isExecutable = true; +  perl = pkgs.perl.withPackages (p: with p; [ JSON FileTemp ]); +} diff --git a/util/seacrit b/util/seacrit new file mode 100755 index 0000000..2004e0b --- /dev/null +++ b/util/seacrit @@ -0,0 +1,123 @@ +#!@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 <nixpkgs> {}).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 <secret-name> +            seacrit [ -i FILE ] rekey +            seacrit [ -i FILE ] genkey <key-path> + +        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";  | 
