]> Dogcows Code - chaz/groupsecret/blob - lib/App/GroupSecret.pm
fe6593de9afd8151303f50478d379410480d0e9d
[chaz/groupsecret] / lib / App / GroupSecret.pm
1 package App::GroupSecret;
2 # ABSTRACT: A simple tool for maintaining a shared group secret
3
4 =head1 DESCRIPTION
5
6 This module is part of the command-line interface for managing keyfiles.
7
8 See L<groupsecret> for documentation.
9
10 =cut
11
12 use warnings;
13 use strict;
14
15 our $VERSION = '9999.999'; # VERSION
16
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);
20 use MIME::Base64;
21 use Pod::Usage;
22 use namespace::clean;
23
24 sub new {
25 my $class = shift;
26 return bless {}, $class;
27 }
28
29 sub main {
30 my $self = shift;
31 my @args = @_;
32
33 my $filepath = '';
34 my $help = 0;
35 my $man = 0;
36 my $version = 0;
37 my $private_key = '';
38
39 # Parse options using pass_through so that we can pick out the global
40 # options, wherever they are in the arg list, and leave the rest to be
41 # parsed by each individual command.
42 Getopt::Long::Configure('pass_through');
43 GetOptionsFromArray(
44 \@args,
45 'file|f=s' => \$filepath,
46 'help|h|?' => \$help,
47 'manual|man' => \$man,
48 'private-key|k=s' => \$private_key,
49 'version|v' => \$version,
50 ) or pod2usage(2);
51 Getopt::Long::Configure('default');
52
53 pod2usage(-exitval => 1, -verbose => 99, -sections => [qw(SYNOPSIS OPTIONS COMMANDS)]) if $help;
54 pod2usage(-verbose => 2) if $man;
55 return print "groupsecret ${VERSION}\n" if $version;
56
57 $self->{private_key} = $private_key if $private_key;
58 $self->{filepath} = $filepath if $filepath;
59
60 my %commands = (
61 add_key => 'add_key',
62 add_keys => 'add_key',
63 change_secret => 'set_secret',
64 delete_key => 'delete_key',
65 delete_keys => 'delete_key',
66 list_keys => 'list_keys',
67 print => 'print_secret',
68 print_secret => 'print_secret',
69 remove_key => 'delete_key',
70 remove_keys => 'delete_key',
71 set_secret => 'set_secret',
72 show_secret => 'print_secret',
73 update_secret => 'set_secret',
74 );
75
76 unshift @args, 'print' if !@args || $args[0] =~ /^-/;
77
78 my $command = shift @args;
79 my $lookup = $command;
80 $lookup =~ s/-/_/g;
81 my $method = 'action_' . ($commands{$lookup} || '');
82
83 if (!$self->can($method)) {
84 warn "Unknown command: $command\n";
85 pod2usage(2);
86 }
87
88 $self->$method(@args);
89 }
90
91 sub filepath {
92 shift->{filepath} ||= $ENV{GROUPSECRET_KEYFILE} || 'groupsecret.yml';
93
94 }
95
96 sub file {
97 my $self = shift;
98 return $self->{file} ||= App::GroupSecret::File->new($self->filepath);
99 }
100
101 sub private_key {
102 shift->{private_key} ||= $ENV{GROUPSECRET_PRIVATE_KEY} || "$ENV{HOME}/.ssh/id_rsa";
103 }
104
105 sub action_print_secret {
106 my $self = shift;
107
108 my $decrypt = 1;
109 GetOptionsFromArray(
110 \@_,
111 'decrypt!' => \$decrypt,
112 ) or pod2usage(2);
113
114 my $file = $self->file;
115 die "No secret in file -- use the \`set-secret' command to set one.\n" if !$file->secret;
116
117 if ($decrypt) {
118 my $private_key = $self->private_key;
119 my $secret = $file->decrypt_secret(private_key => $private_key) or die "No secret.\n";
120 print $secret;
121 }
122 else {
123 print $file->secret;
124 }
125 }
126
127 sub action_set_secret {
128 my $self = shift;
129
130 my $keep_passphrase = 0;
131 GetOptionsFromArray(
132 \@_,
133 'keep-passphrase!' => \$keep_passphrase,
134 ) or pod2usage(2);
135
136 my $secret_spec = shift;
137 if (!$secret_spec) {
138 warn "You must specify a secret to set.\n";
139 pod2usage(2);
140 }
141
142 my $passphrase;
143 my $secret;
144
145 if ($secret_spec =~ /^rand:(\d+)$/i) {
146 my $rand = encode_base64(generate_secure_random_bytes($1), '');
147 $secret = \$rand;
148 }
149 elsif ($secret_spec eq '-') {
150 my $in = do { local $/; <STDIN> };
151 $secret = \$in;
152 }
153 elsif ($secret_spec =~ /^file:(.*)$/i) {
154 $secret = $1;
155 }
156 else {
157 $secret = $secret_spec;
158 }
159
160 my $file = $self->file;
161
162 if ($keep_passphrase) {
163 my $private_key = $self->private_key;
164 $passphrase = $file->decrypt_secret_passphrase($private_key);
165 $file->encrypt_secret($secret, $passphrase);
166 }
167 else {
168 $passphrase = generate_secure_random_bytes(32);
169 $file->encrypt_secret($secret, $passphrase);
170 $file->encrypt_secret_passphrase($passphrase);
171 }
172
173 $file->save;
174 }
175
176 sub action_add_key {
177 my $self = shift;
178
179 my $embed = 0;
180 my $update = 0;
181 GetOptionsFromArray(
182 \@_,
183 'embed' => \$embed,
184 'update|u' => \$update,
185 ) or pod2usage(2);
186
187 my $file = $self->file;
188 my $keys = $file->keys;
189
190 my $opts = {embed => $embed};
191
192 for my $public_key (@_) {
193 my $info = read_openssh_key_fingerprint($public_key);
194
195 if ($keys->{$info->{fingerprint}} && !$update) {
196 my $formatted_key = $file->format_key($info);
197 print "SKIP\t$formatted_key\n";
198 next;
199 }
200
201 if ($file->secret && !$opts->{passphrase}) {
202 my $private_key = $self->private_key;
203 my $passphrase = $file->decrypt_secret_passphrase($private_key);
204 $opts->{passphrase} = $passphrase;
205 }
206
207 local $opts->{fingerprint_info} = $info;
208 my ($fingerprint, $key) = $file->add_key($public_key, $opts);
209
210 local $key->{fingerprint} = $fingerprint;
211 my $formatted_key = $file->format_key($key);
212 print "ADD\t$formatted_key\n";
213 }
214
215 $file->save;
216 }
217
218 sub action_delete_key {
219 my $self = shift;
220
221 my $file = $self->file;
222
223 for my $fingerprint (@_) {
224 if ($fingerprint =~ s/^(?:MD5|SHA1|SHA256)://) {
225 $fingerprint =~ s/://g;
226 }
227 else {
228 my $info = read_openssh_key_fingerprint($fingerprint);
229 $fingerprint = $info->{fingerprint};
230 }
231
232 my $key = $file->keys->{$fingerprint};
233 $file->delete_key($fingerprint) if $key;
234
235 local $key->{fingerprint} = $fingerprint;
236 my $formatted_key = $file->format_key($key);
237 print "DELETE\t$formatted_key\n";
238 }
239
240 $file->save;
241 }
242
243 sub action_list_keys {
244 my $self = shift;
245
246 my $file = $self->file;
247 my $keys = $file->keys;
248
249 while (my ($fingerprint, $key) = each %$keys) {
250 local $key->{fingerprint} = $fingerprint;
251 my $formatted_key = $file->format_key($key);
252 print "$formatted_key\n";
253 }
254 }
255
256 1;
This page took 0.048195 seconds and 3 git commands to generate.