Module: HerokuSsl

Defined in:
lib/heroku_ssl.rb,
lib/heroku_ssl/ssl.rb,
lib/heroku_ssl/engine.rb,
lib/heroku_ssl/version.rb,
app/jobs/heroku_ssl/application_job.rb,
app/helpers/heroku_ssl/heroku_ssl_helper.rb,
app/models/heroku_ssl/application_record.rb,
app/helpers/heroku_ssl/application_helper.rb,
app/mailers/heroku_ssl/application_mailer.rb,
app/controllers/heroku_ssl/heroku_ssl_controller.rb,
app/controllers/heroku_ssl/application_controller.rb

Defined Under Namespace

Modules: ApplicationHelper, HerokuSslHelper Classes: ApplicationController, ApplicationJob, ApplicationMailer, ApplicationRecord, Engine, HerokuSslController

Constant Summary collapse

VERSION =
'0.8.1'

Class Method Summary collapse

Class Method Details

.authorize(domain) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/heroku_ssl/ssl.rb', line 102

def authorize(domain)
  if domain.is_a? Array
    domain.each do |dom|
      authorize dom
    end

    return
  end

  authorization = client.authorize(domain: domain)

  return if authorization.status == 'valid'

  # This example is using the http-01 challenge type. Other challenges are dns-01 or tls-sni-01.
  challenge = authorization.http01

  redis_instance.set("ssl-challenge-#{challenge.filename.split('/').last}", challenge.file_content)
  redis_instance.expire("ssl-challenge-#{challenge.filename.split('/').last}", 5.minutes)

  challenge.request_verification

  # Wait a bit for the server to make the request, or just blink. It should be fast.
  sleep(1)

  status = nil
  begin
    # May sometimes give an error, for mysterious reasons
    status = challenge.authorization.verify_status
  rescue
  end

  #alternate method to read authorization status
  status = client.authorize(domain: domain).status if status == 'pending' || status.blank?

  unless status == 'valid'
    puts challenge.error
    raise "Did not verify client. Status is still #{status}"
  end
end

.cert_directoryObject

Where the certificates are stored



25
26
27
# File 'lib/heroku_ssl/ssl.rb', line 25

def cert_directory
  Rails.root.join('certs')
end

.clientObject



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/heroku_ssl/ssl.rb', line 74

def client
  @client ||= Acme::Client.new(
      private_key: private_key,
      endpoint: endpoint,
      connection_options: {
          request: {
              open_timeout: 5,
              timeout: 5
          }
      }
  )
end

.create_dh_paramsObject



180
181
182
183
184
# File 'lib/heroku_ssl/ssl.rb', line 180

def create_dh_params
  gen_unless_exists 'dhparam.pem' do |filename|
    `openssl dhparam -out #{filename} 4096`
  end
end

.endpointObject



8
9
10
11
12
# File 'lib/heroku_ssl/ssl.rb', line 8

def endpoint
  # Use 'https://acme-staging.api.letsencrypt.org/' for development

  'https://acme-v01.api.letsencrypt.org/'
end

.gen_unless_exists(filename) ⇒ Object



43
44
45
46
47
48
49
50
51
# File 'lib/heroku_ssl/ssl.rb', line 43

def gen_unless_exists(filename)
  existing = read filename
  return existing if existing.present?

  created = yield filename
  write filename, created

  created
end

.private_keyObject

returns any existing account private key; only generates a new one if none exist



62
63
64
65
66
67
68
69
70
71
# File 'lib/heroku_ssl/ssl.rb', line 62

def private_key
  return @private_key if @private_key.present?

  pem = read "#{Rails.env}/account.pem"
  if pem.present?
    @private_key = OpenSSL::PKey::RSA.new(pem)
  else
    regenerate_private_key
  end
end

.read(filename) ⇒ Object



35
36
37
38
39
40
41
# File 'lib/heroku_ssl/ssl.rb', line 35

def read(filename)
  FileUtils.mkdir_p cert_directory

  return nil unless File.exists? cert_directory.join(filename)

  File.read cert_directory.join(filename)
end

.redis_instanceObject



14
15
16
17
18
19
20
21
22
# File 'lib/heroku_ssl/ssl.rb', line 14

def redis_instance

  return $redis if $redis.present?
  return $heroku_ssl_redis if $heroku_ssl_redis.present?

  redis_url = ENV['REDIS_URL'] || ENV['HEROKU_REDIS_URL'] || 'redis://127.0.0.1:6379/0'
  $heroku_ssl_redis = Redis.new(:url => redis_url)

end

.regenerate_private_keyObject

forcibly regenerates the account private key



54
55
56
57
58
59
# File 'lib/heroku_ssl/ssl.rb', line 54

def regenerate_private_key
  @private_key = OpenSSL::PKey::RSA.new(4096)
  write('account.pem', @private_key.export)

  @private_key
end

.register(email) ⇒ Object

adds a contact for a domain



88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/heroku_ssl/ssl.rb', line 88

def register(email)
  # If the private key is not known to the server, we need to register it for the first time.
  registration = client.register(contact: "mailto:#{email}")

  # You may need to agree to the terms of service (that's up the to the server to require it or not but boulder does by default)
  registration.agree_terms
rescue Acme::Client::Error::Malformed => e
  if e.message == 'Registration key is already in use'
    puts 'Already registered'
  else
    raise e
  end
end

.request_certificate(domain) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/heroku_ssl/ssl.rb', line 158

def request_certificate(domain)
  unless try_authorize domain
    puts 'Domain authorization failed. Aborting operation'
    return
  end

  csr = Acme::Client::CertificateRequest.new(names: [*domain])

  # We can now request a certificate. You can pass anything that returns
  # a valid DER encoded CSR when calling to_der on it. For example an
  # OpenSSL::X509::Request should work too.
  certificate = client.new_certificate(csr)

  {
      privkey: certificate.request.private_key.to_pem,
      cert: certificate.to_pem,
      chain: certificate.chain_to_pem,
      fullchain: certificate.fullchain_to_pem
  }

end

.try_authorize(domain, retries = 1) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/heroku_ssl/ssl.rb', line 142

def try_authorize(domain, retries=1)
  begin
    authorize domain
    return true
  rescue RuntimeError => e
    puts e.message

    if retries > 0
      puts 'Retrying domain authorization...'
      return try_authorize domain, retries-1
    else
      return false
    end
  end
end

.write(filename, content) ⇒ Object



29
30
31
32
33
# File 'lib/heroku_ssl/ssl.rb', line 29

def write(filename, content)
  FileUtils.mkdir_p cert_directory

  File.write(cert_directory.join(filename), content)
end