symbiosis-ssl 5.74 KB
Newer Older
1
#!/usr/bin/ruby
2
#
3
# NAME
4
#   symbiosis-ssl - Manage and generate SSL certificates
5
6
#
# SYNOPSIS
Jamie Nguyen's avatar
Jamie Nguyen committed
7
8
9
#   symbiosis-ssl [ --threshold days ] [ --no-generate ] [ --no-rollover ] [ --select set ]
#     [ --list ] [ --prefix prefix ] [ --verbose ] [ --debug ] [ --manual ] [ --help ]
#     [ domain domain ... ]
10
11
#
# OPTIONS
12
#  --force          Re-generate certificates, and roll over to the new set even
13
#                   if they're not due to be renewed. Implies --verbose.
14
#
15
16
17
18
19
#  --threshold days  Number of days before expiry that certificates should be renewed. Defaults to 21.
#
#  --select set     Select a specific set for a single domain. A domain must be specified.
#
#  --list           List available SSL certificate sets for a domain.
20
#
21
22
23
24
#  --no-generate    Do not try and generate keys or certificates.
#
#  --no-rollover    Do not try and generate keys or certificates.
#
25
26
27
28
29
30
#  --prefix prefix  Set the directory prefix for Symbiosis. Defaults to /srv.
#
#  --help           Show the help information for this script.
#
#  --manual         Show the manual for this script
#
Jamie Nguyen's avatar
Jamie Nguyen committed
31
32
33
#  --verbose        Show verbose information.
#
#  --debug          Show debugging information.
34
35
36
37
38
39
#
# USAGE
#
# This command is used to manage certificate sets automatically for domains on
# a Symbiosis system. It can request certificates from LetsEncrypt or generate
# self-signed ones (see PROVIDERS).
40
#
41
# PROVIDERS
42
#
43
44
45
46
# Currently two providers are supported, namely LetsEncrypt and SelfSigned. A
# domain can be set up to use either provider by setting a file
# /srv/example.com/config/ssl-provider with the name of the desired provider in
# it.
47
#
48
49
50
# If the provider is set to something else (e.g. CertificateProviderDuJour)
# then no certificates will be generated, but it is possible to manage updating
# certificates with this program.
51
52
53
54
55
56
57
58
59
#
# AUTHOR
#   Patrick J. Cherry <patrick@bytemark.co.uk>
#

#
#  Modules we require
#

60
require 'English'
61
62
63
require 'getoptlong'

opts = GetoptLong.new(
64
65
66
67
68
69
70
71
72
73
74
  ['--help', '-h', GetoptLong::NO_ARGUMENT],
  ['--manual', '-m', GetoptLong::NO_ARGUMENT],
  ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
  ['--debug', '-d', GetoptLong::NO_ARGUMENT],
  ['--force', '-f', GetoptLong::NO_ARGUMENT],
  ['--list', '-l', GetoptLong::NO_ARGUMENT],
  ['--threshold', '-t', GetoptLong::REQUIRED_ARGUMENT],
  ['--no-generate', '-G', GetoptLong::NO_ARGUMENT],
  ['--no-rollover', '-R', GetoptLong::NO_ARGUMENT],
  ['--select', '-s', GetoptLong::REQUIRED_ARGUMENT],
  ['--prefix', '-p', GetoptLong::REQUIRED_ARGUMENT]
75
76
77
78
)

manual = help = false
$VERBOSE = false
Jamie Nguyen's avatar
Jamie Nguyen committed
79
$DEBUG = false
80
prefix = '/srv'
81
82
do_list = do_generate = do_rollover = nil
rollover_to = nil
83
threshold = 21
84
85
86

opts.each do |opt,arg|
  case opt
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  when '--no-generate'
    do_generate = false
  when '--no-rollover'
    do_rollover = false
  when '--select'
    rollover_to = arg.to_s
  when '--force'
    do_generate = do_rollover = true
    $VERBOSE = true
  when '--threshold'
    begin
      threshold = Integer(arg)
    rescue ArgumentError
      warn "** Could not parse #{arg.inspect} as an integer for --threshold"
    end
  when '--help'
    help = true
  when '--manual'
    manual = true
  when '--prefix'
    prefix = arg
  when '--list'
    do_list = true
  when '--verbose'
    $VERBOSE = true
  when '--debug'
    $DEBUG = true
114
115
116
117
118
119
  end
end

#
# Output help as required.
#
120
if help || manual
121
122
123
124
125
126
  require 'symbiosis/utils'
  Symbiosis::Utils.show_help(__FILE__) if help
  Symbiosis::Utils.show_manual(__FILE__) if manual
  exit 0
end

127
#
128
# The requires spawn a massive stack of warnings in verbose mode.  So let's
129
130
131
132
133
134
# hide them.
#
v = $VERBOSE
$VERBOSE = false

require 'symbiosis/domains'
135
136
require 'symbiosis/domain/ssl'
require 'symbiosis/ssl'
137
138
139
140
141
142
143
144
require 'symbiosis/ssl/letsencrypt'
require 'symbiosis/ssl/selfsigned'

#
# And unhide.  Ugh.
#
$VERBOSE = v

145
146
147
148
149
150
domains = []

ARGV.each do |arg|
  domain = Symbiosis::Domains.find(arg.to_s, prefix)

  if domain.nil?
151
    warn "** Unable to find/parse domain #{arg.inspect}"
152
153
154
155
156
157
    next
  end

  domains << domain
end

158
159
if rollover_to && ARGV.length != 1
  warn '** Exactly one domain must be specfied when rolling over to a specific set.'
160
161
162
  exit 1
end

163
domains = Symbiosis::Domains.all(prefix) if ARGV.empty?
164

165
exit_code = 0
166

167
%w[INT TERM].each do |sig|
168
  trap(sig) do
169
    if Process.uid.zero?
170
171
      Process.euid = 0
      Process.egid = 0
172
173
    end

174
    exit 1
175
  end
176
end
177

178
now = Time.now
179

180
181
domains.sort_by(&:name).each do |domain|
  if do_list || rollover_to
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    puts "Certificate sets for #{domain}:"

    if domain.ssl_available_sets.empty?
      puts "\t** No sets found\n\n"
      next
    end

    domain.ssl_available_sets.each do |this_set|
      if this_set.certificate.issuer == this_set.certificate.subject
        puts "\tSSL set #{this_set.name}: self-signed for #{this_set.certificate.issuer}, expires #{this_set.certificate.not_after}"
      else
        puts "\tSSL set #{this_set.name}: signed by #{this_set.certificate.issuer}, expires #{this_set.certificate.not_after}"
      end
    end

    current = domain.ssl_current_set
    puts "\tCurrent SSL set: #{current.name}\n" unless $VERBOSE

200
    next if rollover_to.nil?
201

202
    to_set = domain.ssl_available_sets.find { |s| s.name.to_s == rollover_to }
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219

    if to_set.nil?
      puts "\tThere is no set '#{rollover_to}' available for this domain."
      next
    end

    if to_set == current
      puts "\tNo need to change to set #{to_set.name} as this is already current."
      next
    end

    puts "\tRolling over from set #{current.name} to #{to_set.name}"
    domain.ssl_rollover(to_set)
    puts "\tCurrent SSL set now: #{domain.ssl_current_set.name}\n"
    next
  end

220
  begin
221
    domain.ssl_magic(threshold, do_generate, do_rollover, now)
222
  rescue StandardError => err
223
    puts "\t!! Failed: #{err.to_s.gsub($RS, '')}" if $VERBOSE
224
225
    puts err.backtrace.join("\n") if $DEBUG
    exit_code = 1
226
227
228
  end
end

229
exit exit_code