]> Dogcows Code - chaz/groupsecret/blob - lib/App/GroupSecret.pm
98f321232d184d112fedb75f82cd105e4719e17d
[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 =method new
25
26 $script = App::GroupSecret->new;
27
28 Construct a new script object.
29
30 =cut
31
32 sub new {
33 my $class = shift;
34 return bless {}, $class;
35 }
36
37 =method main
38
39 $script->main(@ARGV);
40
41 Run a command with the given command-line arguments.
42
43 =cut
44
45 sub main {
46 my $self = shift;
47 my @args = @_;
48
49 my $filepath = '';
50 my $help = 0;
51 my $man = 0;
52 my $version = 0;
53 my $private_key = '';
54
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');
59 GetOptionsFromArray(
60 \@args,
61 'file|f=s' => \$filepath,
62 'help|h|?' => \$help,
63 'manual|man' => \$man,
64 'private-key|k=s' => \$private_key,
65 'version|v' => \$version,
66 ) or pod2usage(2);
67 Getopt::Long::Configure('default');
68
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;
72
73 $self->{private_key} = $private_key if $private_key;
74 $self->{filepath} = $filepath if $filepath;
75
76 my %commands = (
77 add_key => 'add_key',
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',
90 );
91
92 unshift @args, 'print' if !@args || $args[0] =~ /^-/;
93
94 my $command = shift @args;
95 my $lookup = $command;
96 $lookup =~ s/-/_/g;
97 my $method = '_action_' . ($commands{$lookup} || '');
98
99 if (!$self->can($method)) {
100 warn "Unknown command: $command\n";
101 pod2usage(2);
102 }
103
104 $self->$method(@args);
105 }
106
107 =method filepath
108
109 $filepath = $script->filepath;
110
111 Get the path to the keyfile.
112
113 =cut
114
115 sub filepath {
116 shift->{filepath} ||= $ENV{GROUPSECRET_KEYFILE} || 'groupsecret.yml';
117
118 }
119
120 =method file
121
122 $file = $script->file;
123
124 Get the L<App::GroupSecret::File> instance for the keyfile.
125
126 =cut
127
128 sub file {
129 my $self = shift;
130 return $self->{file} ||= App::GroupSecret::File->new($self->filepath);
131 }
132
133 =method private_key
134
135 $filepath = $script->private_key;
136
137 Get the path to a private key used to decrypt the keyfile.
138
139 =cut
140
141 sub private_key {
142 shift->{private_key} ||= $ENV{GROUPSECRET_PRIVATE_KEY} || "$ENV{HOME}/.ssh/id_rsa";
143 }
144
145 sub _action_print_secret {
146 my $self = shift;
147
148 my $decrypt = 1;
149 GetOptionsFromArray(
150 \@_,
151 'decrypt!' => \$decrypt,
152 ) or pod2usage(2);
153
154 my $file = $self->file;
155 die "No secret in file -- use the \`set-secret' command to set one.\n" if !$file->secret;
156
157 if ($decrypt) {
158 my $private_key = $self->private_key;
159 my $secret = $file->decrypt_secret(private_key => $private_key) or die "No secret.\n";
160 print $secret;
161 }
162 else {
163 print $file->secret;
164 }
165 }
166
167 sub _action_set_secret {
168 my $self = shift;
169
170 my $keep_passphrase = 0;
171 GetOptionsFromArray(
172 \@_,
173 'keep-passphrase!' => \$keep_passphrase,
174 ) or pod2usage(2);
175
176 my $secret_spec = shift;
177 if (!$secret_spec) {
178 warn "You must specify a secret to set.\n";
179 pod2usage(2);
180 }
181
182 my $passphrase;
183 my $secret;
184
185 if ($secret_spec =~ /^rand:(\d+)$/i) {
186 my $rand = encode_base64(generate_secure_random_bytes($1), '');
187 $secret = \$rand;
188 }
189 elsif ($secret_spec eq '-') {
190 my $in = do { local $/; <STDIN> };
191 $secret = \$in;
192 }
193 elsif ($secret_spec =~ /^file:(.*)$/i) {
194 $secret = $1;
195 }
196 else {
197 $secret = $secret_spec;
198 }
199
200 my $file = $self->file;
201
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);
206 }
207 else {
208 $passphrase = generate_secure_random_bytes(32);
209 $file->encrypt_secret($secret, $passphrase);
210 $file->encrypt_secret_passphrase($passphrase);
211 }
212
213 $file->save;
214 }
215
216 sub _action_add_key {
217 my $self = shift;
218
219 my $embed = 0;
220 my $update = 0;
221 GetOptionsFromArray(
222 \@_,
223 'embed' => \$embed,
224 'update|u' => \$update,
225 ) or pod2usage(2);
226
227 my $file = $self->file;
228 my $keys = $file->keys;
229
230 my $opts = {embed => $embed};
231
232 for my $public_key (@_) {
233 my $info = read_openssh_key_fingerprint($public_key);
234
235 if ($keys->{$info->{fingerprint}} && !$update) {
236 my $formatted_key = $file->format_key($info);
237 print "SKIP\t$formatted_key\n";
238 next;
239 }
240
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;
245 }
246
247 local $opts->{fingerprint_info} = $info;
248 my ($fingerprint, $key) = $file->add_key($public_key, $opts);
249
250 local $key->{fingerprint} = $fingerprint;
251 my $formatted_key = $file->format_key($key);
252 print "ADD\t$formatted_key\n";
253 }
254
255 $file->save;
256 }
257
258 sub _action_delete_key {
259 my $self = shift;
260
261 my $file = $self->file;
262
263 for my $fingerprint (@_) {
264 if ($fingerprint =~ s/^(?:MD5|SHA1|SHA256)://) {
265 $fingerprint =~ s/://g;
266 }
267 else {
268 my $info = read_openssh_key_fingerprint($fingerprint);
269 $fingerprint = $info->{fingerprint};
270 }
271
272 my $key = $file->keys->{$fingerprint};
273 $file->delete_key($fingerprint) if $key;
274
275 local $key->{fingerprint} = $fingerprint;
276 my $formatted_key = $file->format_key($key);
277 print "DELETE\t$formatted_key\n";
278 }
279
280 $file->save;
281 }
282
283 sub _action_list_keys {
284 my $self = shift;
285
286 my $file = $self->file;
287 my $keys = $file->keys;
288
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";
293 }
294 }
295
296 1;
This page took 0.046745 seconds and 3 git commands to generate.