class Potluck::Nginx::SSL

SSL-specific configuration for Nginx. Provides self-signed certificate generation for use in developemnt.

Constants

CERT_DAYS
CERT_RENEW_DAYS
DEFAULT_CONFIG

Reference: ssl-config.mozilla.org/#server=nginx&config=intermediate&guideline=5.6

Attributes

config[R]
crt_file[R]
csr_file[R]
dhparam_file[R]
key_file[R]

Public Class Methods

new(nginx, dir, host, crt_file: nil, key_file: nil, dhparam_file: nil, config: {}) click to toggle source

Creates a new instance. Providing no SSL files will cue generation of a self-signed certificate.

  • nginx - Nginx instance.

  • dir - Directory where SSL files are located or should be written to.

  • host - Name of the host for determining file names and generating a self-signed certificate.

  • crt_file - Path to the CRT file (optional).

  • key_file - Path to the KEY file (optional).

  • dhparam_file - Path to the DH parameters file (optional).

  • config - Nginx configuration hash (optional).

# File lib/potluck/nginx/ssl.rb, line 44
def initialize(nginx, dir, host, crt_file: nil, key_file: nil, dhparam_file: nil, config: {})
  @nginx = nginx
  @dir = dir
  @host = host

  @auto_generated = !crt_file && !key_file && !dhparam_file

  if !@auto_generated && (!crt_file || !key_file || !dhparam_file)
    raise(ArgumentError, 'Must supply values for all three or none: crt_file, key_file, dhparam_file')
  end

  @csr_file = File.join(@dir, "#{@host}.csr").freeze
  @crt_file = crt_file || File.join(@dir, "#{@host}.crt").freeze
  @key_file = key_file || File.join(@dir, "#{@host}.key").freeze
  @dhparam_file = dhparam_file || File.join(@dir, 'dhparam.pem').freeze

  @config = Util.deep_merge({
    'ssl_certificate' => @crt_file,
    'ssl_certificate_key' => @key_file,
    'ssl_dhparam' => @dhparam_file,
    'ssl_stapling' => ('on' unless @auto_generated),
    'ssl_stapling_verify' => ('on' unless @auto_generated),
  }, DEFAULT_CONFIG, config)
end

Public Instance Methods

ensure_files() click to toggle source

If SSL files were passed to SSL.new, does nothing. Otherwise checks if auto-generated SSL files exist and generates them if not. If they do exist, the expiration for the certificate is checked and the certificate regenerated if the expiration date is soon or in the past.

# File lib/potluck/nginx/ssl.rb, line 74
def ensure_files
  return if !@auto_generated || (
    File.exists?(@csr_file) &&
    File.exists?(@key_file) &&
    File.exists?(@crt_file) &&
    File.exists?(@dhparam_file) &&
    (Time.parse(
      @nginx.run("openssl x509 -enddate -noout -in #{@crt_file}").sub('notAfter=', '')
    ) - Time.now) >= CERT_RENEW_DAYS * 24 * 60 * 60
  )

  @nginx.log('Generating SSL files...')

  @nginx.run("openssl genrsa -out #{@key_file} 4096", capture_stderr: false)
  @nginx.run("openssl req -out #{@csr_file} -key #{@key_file} -new -sha256 -config /dev/stdin <<< "\
    "'#{openssl_config}'", capture_stderr: false)
  @nginx.run("openssl x509 -in #{@csr_file} -out #{@crt_file} -signkey #{@key_file} -days "\
    "#{CERT_DAYS} -req -sha256 -extensions req_ext -extfile /dev/stdin <<< '#{openssl_config}'",
    capture_stderr: false)
  @nginx.run("openssl dhparam -out #{@dhparam_file} 2048", capture_stderr: false)

  if IS_MACOS
    @nginx.log('Adding cert to keychain...')

    @nginx.run(
      "sudo security delete-certificate -t -c #{@host} 2>&1 || "\
      "sudo security delete-certificate -c #{@host} 2>&1 || :"
    )

    @nginx.run("sudo security add-trusted-cert -d -r trustRoot -k "\
      "/Library/Keychains/System.keychain #{@crt_file}")
  end
end

Private Instance Methods

openssl_config() click to toggle source

OpenSSL configuration content used when auto-generating an SSL certificate.

# File lib/potluck/nginx/ssl.rb, line 113
      def openssl_config
        <<~EOS
          [ req ]
          prompt             = no
          default_bits       = 4096
          distinguished_name = req_distinguished_name
          req_extensions     = req_ext

          [ req_distinguished_name ]
          commonName          = #{@host}

          [ req_ext ]
          subjectAltName = @alt_names

          [alt_names]
          DNS.1 = #{@host}
          DNS.2 = *.#{@host}
        EOS
      end