|
|
1
|
+#!/bin/perl -w
|
|
|
2
|
+
|
|
|
3
|
+# This script is used to backup and deploy signing keys from/to a signing machine.
|
|
|
4
|
+#
|
|
|
5
|
+# The script takes as argument the ssh hostname of the signing machine.
|
|
|
6
|
+#
|
|
|
7
|
+# Use `--dry-run` to show what would be done without applying the changes.
|
|
|
8
|
+#
|
|
|
9
|
+# See README.md for more details.
|
|
|
10
|
+
|
|
|
11
|
+use strict;
|
|
|
12
|
+use English;
|
|
|
13
|
+use FindBin;
|
|
|
14
|
+use File::Basename;
|
|
|
15
|
+use File::Path qw(make_path);
|
|
|
16
|
+use Getopt::Long;
|
|
|
17
|
+use Capture::Tiny qw/tee/;
|
|
|
18
|
+
|
|
|
19
|
+my %signing_keys = (
|
|
|
20
|
+
|
|
|
21
|
+ gpg => {
|
|
|
22
|
+ 'private-keys-v1.d' => {
|
|
|
23
|
+ path => '/home/signing-gpg/.gnupg/private-keys-v1.d',
|
|
|
24
|
+ owner => 'signing-gpg',
|
|
|
25
|
+ },
|
|
|
26
|
+ pubring => {
|
|
|
27
|
+ path => '/home/signing-gpg/.gnupg/pubring.kbx',
|
|
|
28
|
+ owner => 'signing-gpg',
|
|
|
29
|
+ },
|
|
|
30
|
+ },
|
|
|
31
|
+
|
|
|
32
|
+ win => {
|
|
|
33
|
+ tpo_cert => {
|
|
|
34
|
+ path => '/home/signing-win/keys/key-1/the_tor_project_inc.crt',
|
|
|
35
|
+ owner => 'signing-win',
|
|
|
36
|
+ },
|
|
|
37
|
+ tpo_key => {
|
|
|
38
|
+ path => '/home/signing-win/keys/key-1/private.pem',
|
|
|
39
|
+ owner => 'signing-win',
|
|
|
40
|
+ },
|
|
|
41
|
+ },
|
|
|
42
|
+
|
|
|
43
|
+ mar => {
|
|
|
44
|
+ torbrowser_nssdb7 => {
|
|
|
45
|
+ path => '/home/signing-mar/nssdb/torbrowser-nssdb7',
|
|
|
46
|
+ owner => 'signing-mar',
|
|
|
47
|
+ },
|
|
|
48
|
+ mullvadbrowser_nssdb_1 => {
|
|
|
49
|
+ path => '/home/signing-mar/nssdb/mullvadbrowser-nssdb-1',
|
|
|
50
|
+ owner => 'signing-mar',
|
|
|
51
|
+ },
|
|
|
52
|
+ },
|
|
|
53
|
+
|
|
|
54
|
+ macos => {
|
|
|
55
|
+ key_1 => {
|
|
|
56
|
+ path => '/home/signing-macos/keys/key-1.p12',
|
|
|
57
|
+ owner => 'signing-macos',
|
|
|
58
|
+ },
|
|
|
59
|
+ },
|
|
|
60
|
+
|
|
|
61
|
+ aab => {
|
|
|
62
|
+ torvpn => {
|
|
|
63
|
+ path => '/home/signing-apk/keys/torvpn.p12',
|
|
|
64
|
+ owner => 'signing-apk',
|
|
|
65
|
+ },
|
|
|
66
|
+ },
|
|
|
67
|
+
|
|
|
68
|
+ apk => {
|
|
|
69
|
+ tba_release => {
|
|
|
70
|
+ path => '/home/signing-apk/keys/tba_release.p12',
|
|
|
71
|
+ owner => 'signing-apk',
|
|
|
72
|
+ },
|
|
|
73
|
+ tba_alpha => {
|
|
|
74
|
+ path => '/home/signing-apk/keys/tba_alpha.p12',
|
|
|
75
|
+ owner => 'signing-apk',
|
|
|
76
|
+ },
|
|
|
77
|
+ torvpn => {
|
|
|
78
|
+ path => '/home/signing-apk/keys/torvpn.p12',
|
|
|
79
|
+ owner => 'signing-apk',
|
|
|
80
|
+ },
|
|
|
81
|
+ },
|
|
|
82
|
+
|
|
|
83
|
+);
|
|
|
84
|
+
|
|
|
85
|
+my $backup_dir = "$FindBin::Bin/signing-keys";
|
|
|
86
|
+my $signing_machine;
|
|
|
87
|
+my $dry_run;
|
|
|
88
|
+
|
|
|
89
|
+sub exit_error {
|
|
|
90
|
+ print STDERR "Error: ", $_[0], "\n";
|
|
|
91
|
+ chdir '/';
|
|
|
92
|
+ exit (exists $_[1] ? $_[1] : 1);
|
|
|
93
|
+}
|
|
|
94
|
+
|
|
|
95
|
+my @errors;
|
|
|
96
|
+sub set_error {
|
|
|
97
|
+ my $error = join("\n", @_);
|
|
|
98
|
+ print STDERR $error, "\n";
|
|
|
99
|
+ push @errors, $error;
|
|
|
100
|
+}
|
|
|
101
|
+
|
|
|
102
|
+sub print_errors {
|
|
|
103
|
+ return unless @errors;
|
|
|
104
|
+ my $nb_errors = @errors;
|
|
|
105
|
+ print STDERR "There was $nb_errors errors:\n";
|
|
|
106
|
+ my $i = 1;
|
|
|
107
|
+ for my $error (@errors) {
|
|
|
108
|
+ print STDERR "*** Error $i:\n$error\n";
|
|
|
109
|
+ $i += 1;
|
|
|
110
|
+ }
|
|
|
111
|
+ print STDERR "\n";
|
|
|
112
|
+}
|
|
|
113
|
+
|
|
|
114
|
+sub run_cmd {
|
|
|
115
|
+ my (@cmd) = @_;
|
|
|
116
|
+ my $txt_cmd = join(' ', @cmd);
|
|
|
117
|
+ print "Running $txt_cmd\n";
|
|
|
118
|
+ my $exit_code;
|
|
|
119
|
+ my ($stdout, $stderr) = tee {
|
|
|
120
|
+ $exit_code = system(@cmd);
|
|
|
121
|
+ };
|
|
|
122
|
+ return if $exit_code == 0;
|
|
|
123
|
+ set_error("Error running $txt_cmd:\n$stderr");
|
|
|
124
|
+}
|
|
|
125
|
+
|
|
|
126
|
+sub backup_key {
|
|
|
127
|
+ my ($dir, $keyname) = @_;
|
|
|
128
|
+ print "Backing up $signing_keys{$dir}{$keyname}{path}\n";
|
|
|
129
|
+ make_path("$backup_dir/$dir") unless -d "$backup_dir/$dir";
|
|
|
130
|
+ my @cmd = ('rsync', '-avi', '--chmod=F0600,D0700', $dry_run ? ($dry_run) : (),
|
|
|
131
|
+ "root\@$signing_machine:$signing_keys{$dir}{$keyname}{path}",
|
|
|
132
|
+ "$backup_dir/$dir/$keyname");
|
|
|
133
|
+ run_cmd(@cmd);
|
|
|
134
|
+}
|
|
|
135
|
+
|
|
|
136
|
+sub backup_signing_keys {
|
|
|
137
|
+ foreach my $dir (keys %signing_keys) {
|
|
|
138
|
+ foreach my $keyname (keys %{$signing_keys{$dir}}) {
|
|
|
139
|
+ backup_key($dir, $keyname);
|
|
|
140
|
+ }
|
|
|
141
|
+ }
|
|
|
142
|
+}
|
|
|
143
|
+
|
|
|
144
|
+sub deploy_key {
|
|
|
145
|
+ my ($dir, $keyname) = @_;
|
|
|
146
|
+ print "Deploying $signing_keys{$dir}{$keyname}{path}\n";
|
|
|
147
|
+ my @cmd = ('rsync', '-avi', '--no-owner', '--no-group', $dry_run ? ($dry_run) : (),
|
|
|
148
|
+ '--chmod=F0600,D0700', "--chown=$signing_keys{$dir}{$keyname}{owner}",
|
|
|
149
|
+ "$backup_dir/$dir/$keyname", "root\@$signing_machine:$signing_keys{$dir}{$keyname}{path}");
|
|
|
150
|
+ if (!-e "$backup_dir/$dir/$keyname") {
|
|
|
151
|
+ set_error("Missing file or directory $backup_dir/$dir/$keyname",
|
|
|
152
|
+ "Cannot deploy $signing_keys{$dir}{$keyname}{path}");
|
|
|
153
|
+ return;
|
|
|
154
|
+ }
|
|
|
155
|
+ run_cmd(@cmd);
|
|
|
156
|
+}
|
|
|
157
|
+
|
|
|
158
|
+sub confirm_deploy {
|
|
|
159
|
+ return if $dry_run;
|
|
|
160
|
+ print "Are you sure you want to deploy keys to $signing_machine?\n";
|
|
|
161
|
+ print "This will overwrite existing files.\n";
|
|
|
162
|
+ print "You can check the changes before applying them with '--dry-run'\n";
|
|
|
163
|
+ print "Enter 'yes' to validate.\n";
|
|
|
164
|
+ my $word = <STDIN>;
|
|
|
165
|
+ chomp $word;
|
|
|
166
|
+ return if lc($word) eq 'yes';
|
|
|
167
|
+ print STDERR "Not doing anything.\n";
|
|
|
168
|
+ exit 1;
|
|
|
169
|
+}
|
|
|
170
|
+
|
|
|
171
|
+sub deploy_signing_keys {
|
|
|
172
|
+ confirm_deploy;
|
|
|
173
|
+ foreach my $dir (keys %signing_keys) {
|
|
|
174
|
+ foreach my $keyname (keys %{$signing_keys{$dir}}) {
|
|
|
175
|
+ deploy_key($dir, $keyname);
|
|
|
176
|
+ }
|
|
|
177
|
+ }
|
|
|
178
|
+}
|
|
|
179
|
+
|
|
|
180
|
+sub set_options {
|
|
|
181
|
+ my @options = qw(dry-run);
|
|
|
182
|
+ my %val;
|
|
|
183
|
+ Getopt::Long::GetOptionsFromArray(\@_, \%val, @options) || exit 1;
|
|
|
184
|
+ $dry_run = '--dry-run' if $val{'dry-run'};
|
|
|
185
|
+ exit_error "Usage: $PROGRAM_NAME [--dry-run] <signing-machine>" unless @_ == 1;
|
|
|
186
|
+ $signing_machine = $_[0];
|
|
|
187
|
+}
|
|
|
188
|
+
|
|
|
189
|
+my %actions = (
|
|
|
190
|
+ 'deploy-signing-keys' => \&deploy_signing_keys,
|
|
|
191
|
+ 'backup-signing-keys' => \&backup_signing_keys,
|
|
|
192
|
+);
|
|
|
193
|
+
|
|
|
194
|
+set_options(@ARGV);
|
|
|
195
|
+
|
|
|
196
|
+my $action = fileparse($PROGRAM_NAME);
|
|
|
197
|
+exit_error "Unknown action $action" unless $actions{$action};
|
|
|
198
|
+$actions{$action}->($signing_machine);
|
|
|
199
|
+
|
|
|
200
|
+print_errors; |