class Heroku::Command::Certs
manage ssl endpoints for an app
Constants
- SSL_DOCTOR
Public Instance Methods
certs:add CRT KEY
Add an ssl endpoint to an app.
--bypass # bypass the trust chain completion step
# File lib/heroku/command/certs.rb, line 74 def add crt, key = read_crt_and_key endpoint = action("Adding SSL Endpoint to #{app}") { heroku.ssl_endpoint_add(app, crt, key) } display_warnings(endpoint) display "#{app} now served by #{endpoint['cname']}" display "Certificate details:" display_certificate_info(endpoint) rescue UsageError fail("Usage: heroku certs:add CRT KEY\nMust specify CRT and KEY to add cert.") end
certs:chain CRT [CRT …]
Print the ordered and complete chain for the given certificate.
Optional intermediate certificates may be given too, and will be used during chain resolution.
# File lib/heroku/command/certs.rb, line 48 def chain puts read_crt_through_ssl_doctor rescue UsageError fail("Usage: heroku certs:chain CRT [CRT ...]\nMust specify at least one certificate file.") end
certs:generate DOMAIN
Generate a key and certificate signing request (or self-signed certificate) for an app. Prompts for information to put in the certificate unless –now is used, or at least one of the –subject, –owner, –country, –area, or –city options is specified.
--selfsigned # generate a self-signed certificate instead of a CSR --keysize BITSIZE # RSA key size in bits (default: 2048) --owner NAME # name of organization certificate belongs to --country COUNTRY # country of owner, as a two-letter ISO country code --area AREA # sub-country area (state, province, etc.) of owner --city CITY # city of owner --subject SUBJECT # specify entire certificate subject --now # do not prompt for any owner information
# File lib/heroku/command/certs.rb, line 176 def generate request = Heroku::OpenSSL::CertificateRequest.new request.domain = args[0] || error("certs:generate must specify a domain") request.subject = cert_subject_for_domain_and_options(request.domain, options) request.self_signed = options[:selfsigned] || false request.key_size = (options[:keysize] || request.key_size).to_i result = request.generate explain_step_after_generate result rescue Heroku::OpenSSL::NotInstalledError => ex error("The OpenSSL command-line tools must be installed to use certs:generate.\n" + ex.installation_hint) rescue Heroku::OpenSSL::GenericError => ex error(ex.message) end
certs
List ssl endpoints for an app.
# File lib/heroku/command/certs.rb, line 16 def index endpoints = heroku.ssl_endpoint_list(app) if endpoints.empty? display "#{app} has no SSL Endpoints." display "Use `heroku certs:add CRT KEY` to add one." else endpoints.map! do |endpoint| ssl_cert_attributes = {} if cert = endpoint['ssl_cert'] ssl_cert_attributes.merge!( 'domains' => cert['cert_domains'].join(', '), 'expires_at' => format_date(cert['expires_at']), 'ca_signed?' => cert['ca_signed?'].to_s.capitalize) end { 'cname' => endpoint['cname'] }.merge(ssl_cert_attributes) end display_table( endpoints, %w( cname domains expires_at ca_signed? ), [ "Endpoint", "Common Name(s)", "Expires", "Trusted" ] ) end end
certs:info
Show certificate information for an ssl endpoint.
-e, --endpoint ENDPOINT # name of the endpoint to check info on
# File lib/heroku/command/certs.rb, line 111 def info cname = options[:endpoint] || current_endpoint endpoint = action("Fetching SSL Endpoint #{cname} info for #{app}") do heroku.ssl_endpoint_info(app, cname) end if endpoint display "Certificate details:" display_certificate_info(endpoint) else error "No certificate found." end end
certs:key CRT KEY [KEY …]
Print the correct key for the given certificate.
You must pass one single certificate, and one or more keys. The first key that signs the certificate will be printed back.
# File lib/heroku/command/certs.rb, line 61 def key _, key = read_crt_and_key_through_ssl_doctor("Testing for signing key") puts key rescue UsageError fail("Usage: heroku certs:key CRT KEY [KEY ...]\nMust specify one certificate file and at least one key file.") end
certs:remove
Remove an SSL Endpoint from an app.
-e, --endpoint ENDPOINT # name of the endpoint to remove
# File lib/heroku/command/certs.rb, line 131 def remove cname = options[:endpoint] || current_endpoint message = "WARNING: Potentially Destructive Action\nThis command will remove the endpoint #{cname} from #{app}." return unless confirm_command(app, message) action("Removing SSL Endpoint #{cname} from #{app}") do heroku.ssl_endpoint_remove(app, cname) end display "NOTE: Billing is still active. Remove SSL Endpoint add-on to stop billing." end
certs:rollback
Rollback an SSL Endpoint for an app.
-e, --endpoint ENDPOINT # name of the endpoint to rollback
# File lib/heroku/command/certs.rb, line 147 def rollback cname = options[:endpoint] || current_endpoint message = "WARNING: Potentially Destructive Action\nThis command will rollback the certificate of endpoint #{cname} on #{app}." return unless confirm_command(app, message) endpoint = action("Rolling back SSL Endpoint #{cname} for #{app}") do heroku.ssl_endpoint_rollback(app, cname) end display "New active certificate details:" display_certificate_info(endpoint) end
certs:update CRT KEY
Update an SSL Endpoint on an app.
--bypass # bypass the trust chain completion step -e, --endpoint ENDPOINT # name of the endpoint to update
# File lib/heroku/command/certs.rb, line 92 def update crt, key = read_crt_and_key cname = options[:endpoint] || current_endpoint message = "WARNING: Potentially Destructive Action\nThis command will change the certificate of endpoint #{cname} on #{app}." return unless confirm_command(app, message) endpoint = action("Updating SSL Endpoint #{cname} for #{app}") { heroku.ssl_endpoint_update(app, cname, crt, key) } display_warnings(endpoint) display "Updated certificate details:" display_certificate_info(endpoint) rescue UsageError fail("Usage: heroku certs:update CRT KEY\nMust specify CRT and KEY to update cert.") end
Private Instance Methods
# File lib/heroku/command/certs.rb, line 272 def all_endpoint_domains endpoints = heroku.ssl_endpoint_list(app) endpoints.select { |endpoint| endpoint['ssl_cert'] && endpoint['ssl_cert']['cert_domains'] } .map { |endpoint| endpoint['ssl_cert']['cert_domains'] } .reduce(:+) end
# File lib/heroku/command/certs.rb, line 288 def cert_subject_for_domain_and_options(domain, options = {}) raise ArgumentError, "domain cannot be empty" if domain.nil? || domain.empty? subject, country, area, city, owner, now = options.values_at(:subject, :country, :area, :city, :owner, :now) if val_empty? subject if !now && [country, area, city, owner].all? { |v| val_empty? v } owner = prompt "Owner of this certificate" country = prompt "Country of owner (two-letter ISO code)" area = prompt "State/province/etc. of owner" city = prompt "City of owner" end subject = "" subject += "/C=#{country}" unless val_empty? country subject += "/ST=#{area}" unless val_empty? area subject += "/L=#{city}" unless val_empty? city subject += "/O=#{owner}" unless val_empty? owner subject += "/CN=#{domain}" end subject end
# File lib/heroku/command/certs.rb, line 197 def current_endpoint endpoint = heroku.ssl_endpoint_list(app).first || error("#{app} has no SSL Endpoints.") endpoint["cname"] end
# File lib/heroku/command/certs.rb, line 229 def display(msg = "", new_line = true) super if $stdout.tty? end
# File lib/heroku/command/certs.rb, line 202 def display_certificate_info(endpoint) data = { 'Common Name(s)' => endpoint['ssl_cert']['cert_domains'], 'Expires At' => format_date(endpoint['ssl_cert']['expires_at']), 'Issuer' => endpoint['ssl_cert']['issuer'], 'Starts At' => format_date(endpoint['ssl_cert']['starts_at']), 'Subject' => endpoint['ssl_cert']['subject'] } styled_hash(data) if endpoint["ssl_cert"]["ca_signed?"] display "SSL certificate is verified by a root authority." elsif endpoint["issuer"] == endpoint["subject"] display "SSL certificate is self signed." else display "SSL certificate is not trusted." end end
# File lib/heroku/command/certs.rb, line 221 def display_warnings(endpoint) if endpoint["warnings"] endpoint["warnings"].each do |field, warning| display "WARNING: #{field} #{warning}" end end end
# File lib/heroku/command/certs.rb, line 313 def explain_step_after_generate(result) if result.csr_file.nil? display "Your key and self-signed certificate have been generated." display "Next, run:" else display "Your key and certificate signing request have been generated." display "Submit the CSR in '#{result.csr_file}' to your preferred certificate authority." display "When you've received your certificate, run:" end needs_addon = false command = "add" begin command = "update" if all_endpoint_domains.include? result.request.domain rescue RestClient::Forbidden needs_addon = true end display "$ heroku addons:add ssl:endpoint" if needs_addon display "$ heroku certs:#{command} #{result.crt_file || "CERTFILE"} #{result.key_file}" end
# File lib/heroku/command/certs.rb, line 233 def post_to_ssl_doctor(path, action_text = nil) raise UsageError if args.size < 1 action_text ||= "Resolving trust chain" action(action_text) do input = args.map { |arg| begin certbody=File.read(arg) rescue => e error("Unable to read #{arg} file: #{e}") end certbody }.join("\n") SSL_DOCTOR.post(:path => path, :body => input, :headers => {'Content-Type' => 'application/octet-stream'}, :expects => 200).body end rescue Excon::Errors::BadRequest, Excon::Errors::UnprocessableEntity => e error(e.response.body) end
# File lib/heroku/command/certs.rb, line 279 def prompt(question) display("#{question}: ", false) ask end
# File lib/heroku/command/certs.rb, line 268 def read_crt_and_key options[:bypass] ? read_crt_and_key_bypassing_ssl_doctor : read_crt_and_key_through_ssl_doctor end
# File lib/heroku/command/certs.rb, line 261 def read_crt_and_key_bypassing_ssl_doctor raise UsageError if args.size != 2 crt = File.read(args[0]) rescue error("Unable to read #{args[0]} CRT") key = File.read(args[1]) rescue error("Unable to read #{args[1]} KEY") [crt, key] end
# File lib/heroku/command/certs.rb, line 252 def read_crt_and_key_through_ssl_doctor(action_text = nil) crt_and_key = post_to_ssl_doctor("resolve-chain-and-key", action_text) MultiJson.load(crt_and_key).values_at("pem", "key") end
# File lib/heroku/command/certs.rb, line 257 def read_crt_through_ssl_doctor(action_text = nil) post_to_ssl_doctor("resolve-chain", action_text) end
# File lib/heroku/command/certs.rb, line 284 def val_empty?(val) val.nil? or val.empty? end