epcim
4/22/2016 - 12:08 PM

Docker TLS certificate generator

Docker TLS certificate generator

#!/usr/bin/env ruby
# Generates necessary certificates to ~/.docker
#
# Prereq:
#   bundle install 
#   OR 
#   gem install certificate_authority
#   gem install fileutils
# Usage:
#   ruby certgen.rb <domain>

require 'certificate_authority'
require 'fileutils'

if ARGV.empty?
  puts "Usage: ruby certgen.rb <domain>"
  exit 1
end

$domain = ARGV[0]
$certs_path = ARGV[1].nil? ?  File.join(ENV['HOME'], '.docker') : ARGV[1]       

def certificate_authority
  cert_path = File.join($certs_path, 'ca', 'cert.pem')
  ca_path = File.join($certs_path, 'ca', 'key.pem')

  key_material = if File.exist?(ca_path)
    key = OpenSSL::PKey::RSA.new(File.read(ca_path))
    mem_key = CertificateAuthority::MemoryKeyMaterial.new
    mem_key.public_key = key.public_key
    mem_key.private_key = key
    mem_key
  else
    mem_key = CertificateAuthority::MemoryKeyMaterial.new
    mem_key.generate_key
    mem_key
  end

  if File.exist?(cert_path)
    raw_cert = File.read(cert_path)
    openssl = OpenSSL::X509::Certificate.new(raw_cert)
    cert = CertificateAuthority::Certificate.from_openssl(openssl)
    cert.key_material = key_material
    cert
  else
    root = CertificateAuthority::Certificate.new
    root.subject.common_name = $domain
    root.serial_number.number = 1
    root.signing_entity = true
    root.key_material = key_material

    ca_profile = {
      "extensions" => {
        "keyUsage" => {
          "usage" => [ "critical", "keyCertSign" ]
        }
      }
    }

    root.sign!(ca_profile)

    root
  end
end

def server_certificate(root)
  server = CertificateAuthority::Certificate.new
  server.subject.common_name = $domain
  server.serial_number.number = rand(3..100000)
  server.parent = root
  server.key_material.generate_key
  server.sign!
  server
end

def client_certificate(root)
  client = CertificateAuthority::Certificate.new
  client.subject.common_name = $domain
  client.serial_number.number = 2
  client.parent = root

  client.key_material.generate_key

  signing_profile = {
    "extensions" => {
      "extendedKeyUsage" => {
        "usage" => [ "clientAuth" ]
      }
    }
  }

  client.sign!(signing_profile)

  client
end

root = certificate_authority
server = server_certificate(root)
client = client_certificate(root)

[
  # You can reuse this file to generate more certs
  ['ca/key.pem', root.key_material.private_key],
  ['ca/cert.pem', root.to_pem],

  # Those are default filenames expected by Docker
  ['ca.pem', root.to_pem],
  ['key.pem', client.key_material.private_key],
  ['cert.pem', client.to_pem],

  # Those files are supposed to be uploaded to server
  ["#{$domain}/ca.pem", root.to_pem],
  ["#{$domain}/key.pem", server.key_material.private_key],
  ["#{$domain}/cert.pem", server.to_pem]
].each do |name, contents|
  path = File.join($certs_path, name)
  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, contents)
  File.chmod(0600, path)
end

puts "CA certificates are in #{$certs_path}/ca"
puts "Client certificates are in #{$certs_path}"
puts "Server certificates are in #{$certs_path}/#{$domain}"