symbiosis-ssl 5.68 KB
Newer Older
1
#!/usr/bin/ruby
2
#
3
# NAME
4
#   symbiosis-ssl - Manage and generate SSL certificates
5
6
#
# SYNOPSIS
7
8
#   symbiosis-ssl [ --threshold days ] [ --no-generate ] [ --no-rollover ] [ --select set ] [ --list ]
#     [ --prefix prefix ] [ --verbose ] [ --manual ] [ --help ] [ domain domain ... ]
9
10
#
# OPTIONS
11
#  --force          Re-generate certificates, and roll over to the new set even
12
#                   if they're not due to be renewed. Implies --verbose.
13
#
14
15
16
17
18
#  --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.
19
#
20
21
22
23
#  --no-generate    Do not try and generate keys or certificates.
#
#  --no-rollover    Do not try and generate keys or certificates.
#
24
25
26
27
28
29
30
31
32
33
34
35
36
#  --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
#
#  --verbose        Show debugging information.
#
# 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).
37
#
38
# PROVIDERS
39
#
40
41
42
43
# 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.
44
#
45
46
47
# 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.
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#
# AUTHOR
#   Patrick J. Cherry <patrick@bytemark.co.uk>
#

#
#  Modules we require
#

require 'getoptlong'

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

manual = help = false
$VERBOSE = false
74
prefix = "/srv"
75
76
do_list = do_generate = do_rollover = nil
rollover_to = nil
77
threshold = 21
78
79
80

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

#
# Output help as required.
#
if help or manual
  require 'symbiosis/utils'
  Symbiosis::Utils.show_help(__FILE__) if help
  Symbiosis::Utils.show_manual(__FILE__) if manual
  exit 0
end

119
120
121
122
123
124
125
126
#
# The required spawn a massive stack of warnings in verbose mode.  So let's
# hide them.
#
v = $VERBOSE
$VERBOSE = false

require 'symbiosis/domains'
127
128
require 'symbiosis/domain/ssl'
require 'symbiosis/ssl'
129
130
131
132
133
134
135
136
require 'symbiosis/ssl/letsencrypt'
require 'symbiosis/ssl/selfsigned'

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

137
138
139
140
141
142
143

domains = []

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

  if domain.nil?
144
    warn "** Unable to find/parse domain #{arg.inspect}"
145
146
147
148
149
150
    next
  end

  domains << domain
end

151
152
153
154
155
if rollover_to and ARGV.length != 1
  warn "** Exactly one domain must be specfied when rolling over to a specific set."
  exit 1
end

156
157
158
159
if ARGV.empty?
  domains = Symbiosis::Domains.all(prefix)
end

160
exit_code = 0
161

162
%w(INT TERM).each do |sig|
163
  trap(sig) do
164

165
166
167
    if 0 == Process.uid
      Process.euid = 0
      Process.egid = 0
168
169
    end

170
    exit 1
171
  end
172
end
173

174
now = Time.now
175

176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
domains.sort{|a,b| a.name <=> b.name}.each do |domain|

  if do_list or rollover_to
    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

    if rollover_to.nil?
      next
    end

    to_set  = domain.ssl_available_sets.find{|s| s.name.to_s == rollover_to}

    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

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

227
228
end

229
230
exit exit_code