1 package App
::GroupSecret
;
2 # ABSTRACT: A simple tool for maintaining a shared group secret
6 This module is part of the command-line interface for managing keyfiles.
8 See L<groupsecret> for documentation.
15 our $VERSION = '9999.999'; # VERSION
17 use App
::GroupSecret
::Crypt
qw(generate_secure_random_bytes read_openssh_key_fingerprint);
18 use App
::GroupSecret
::File
;
19 use Getopt
::Long
qw(GetOptionsFromArray);
26 $script = App
::GroupSecret-
>new;
28 Construct a new script object
.
34 return bless {}, $class;
41 Run a command with the
given command-line arguments
.
55 # Parse options using pass_through so that we can pick out the global
56 # options, wherever they are in the arg list, and leave the rest to be
57 # parsed by each individual command.
58 Getopt
::Long
::Configure
('pass_through');
61 'file|f=s' => \
$filepath,
63 'manual|man' => \
$man,
64 'private-key|k=s' => \
$private_key,
65 'version|v' => \
$version,
67 Getopt
::Long
::Configure
('default');
69 pod2usage
(-exitval
=> 1, -verbose
=> 99, -sections
=> [qw(SYNOPSIS OPTIONS COMMANDS)]) if $help;
70 pod2usage
(-verbose
=> 2) if $man;
71 return print "groupsecret ${VERSION}\n" if $version;
73 $self->{private_key
} = $private_key if $private_key;
74 $self->{filepath
} = $filepath if $filepath;
78 add_keys
=> 'add_key',
79 change_secret
=> 'set_secret',
80 delete_key
=> 'delete_key',
81 delete_keys
=> 'delete_key',
82 list_keys
=> 'list_keys',
83 print => 'print_secret',
84 print_secret
=> 'print_secret',
85 remove_key
=> 'delete_key',
86 remove_keys
=> 'delete_key',
87 set_secret
=> 'set_secret',
88 show_secret
=> 'print_secret',
89 update_secret
=> 'set_secret',
92 unshift @args, 'print' if !@args || $args[0] =~ /^-/;
94 my $command = shift @args;
95 my $lookup = $command;
97 my $method = '_action_' . ($commands{$lookup} || '');
99 if (!$self->can($method)) {
100 warn "Unknown command: $command\n";
104 $self->$method(@args);
109 $filepath = $script->filepath;
111 Get the path to the keyfile
.
116 shift-
>{filepath
} ||= $ENV{GROUPSECRET_KEYFILE
} || 'groupsecret.yml';
122 $file = $script->file;
124 Get the L
<App
::GroupSecret
::File
> instance
for the keyfile
.
130 return $self->{file
} ||= App
::GroupSecret
::File-
>new($self->filepath);
135 $filepath = $script->private_key;
137 Get the path to a private key used to decrypt the keyfile
.
142 shift-
>{private_key
} ||= $ENV{GROUPSECRET_PRIVATE_KEY
} || "$ENV{HOME}/.ssh/id_rsa";
145 sub _action_print_secret
{
151 'decrypt!' => \
$decrypt,
154 my $file = $self->file;
155 die "No secret in file -- use the \`set-secret' command to set one.\n" if !$file->secret;
158 my $private_key = $self->private_key;
159 my $secret = $file->decrypt_secret(private_key
=> $private_key) or die "No secret.\n";
167 sub _action_set_secret
{
170 my $keep_passphrase = 0;
173 'keep-passphrase!' => \
$keep_passphrase,
176 my $secret_spec = shift;
178 warn "You must specify a secret to set.\n";
185 if ($secret_spec =~ /^rand:(\d+)$/i) {
186 my $rand = encode_base64
(generate_secure_random_bytes
($1), '');
189 elsif ($secret_spec eq '-') {
190 my $in = do { local $/; <STDIN
> };
193 elsif ($secret_spec =~ /^file:(.*)$/i) {
197 $secret = $secret_spec;
200 my $file = $self->file;
202 if ($keep_passphrase) {
203 my $private_key = $self->private_key;
204 $passphrase = $file->decrypt_secret_passphrase($private_key);
205 $file->encrypt_secret($secret, $passphrase);
208 $passphrase = generate_secure_random_bytes
(32);
209 $file->encrypt_secret($secret, $passphrase);
210 $file->encrypt_secret_passphrase($passphrase);
216 sub _action_add_key
{
224 'update|u' => \
$update,
227 my $file = $self->file;
228 my $keys = $file->keys;
230 my $opts = {embed
=> $embed};
232 for my $public_key (@_) {
233 my $info = read_openssh_key_fingerprint
($public_key);
235 if ($keys->{$info->{fingerprint
}} && !$update) {
236 my $formatted_key = $file->format_key($info);
237 print "SKIP\t$formatted_key\n";
241 if ($file->secret && !$opts->{passphrase
}) {
242 my $private_key = $self->private_key;
243 my $passphrase = $file->decrypt_secret_passphrase($private_key);
244 $opts->{passphrase
} = $passphrase;
247 local $opts->{fingerprint_info
} = $info;
248 my ($fingerprint, $key) = $file->add_key($public_key, $opts);
250 local $key->{fingerprint
} = $fingerprint;
251 my $formatted_key = $file->format_key($key);
252 print "ADD\t$formatted_key\n";
258 sub _action_delete_key
{
261 my $file = $self->file;
263 for my $fingerprint (@_) {
264 if ($fingerprint =~ s/^(?:MD5|SHA1|SHA256)://) {
265 $fingerprint =~ s/://g;
268 my $info = read_openssh_key_fingerprint
($fingerprint);
269 $fingerprint = $info->{fingerprint
};
272 my $key = $file->keys->{$fingerprint};
273 $file->delete_key($fingerprint) if $key;
275 local $key->{fingerprint
} = $fingerprint;
276 my $formatted_key = $file->format_key($key);
277 print "DELETE\t$formatted_key\n";
283 sub _action_list_keys
{
286 my $file = $self->file;
287 my $keys = $file->keys;
289 while (my ($fingerprint, $key) = each %$keys) {
290 local $key->{fingerprint
} = $fingerprint;
291 my $formatted_key = $file->format_key($key);
292 print "$formatted_key\n";