Skip to content
Browse files

[finishes #44721035] SSL support on ELBs for aws_bootstrap

  • Loading branch information...
1 parent c2422df commit a6ad8f331d4fd68c61d2491f4b6625feb42d2a8f @oppegard oppegard committed with Gary Liu Apr 3, 2013
View
5 bosh_aws_bootstrap/lib/bosh/cli/commands/aws.rb
@@ -205,9 +205,12 @@ def create_vpc(config_file)
route53 = Bosh::Aws::Route53.new(config["aws"])
elbs = config["vpc"]["elbs"]
+ ssl_certs = config["ssl_certs"]
+
say "creating load balancers: #{elbs.keys.join(", ")}" if elbs
elbs.each do |name, settings|
- e = elb.create(name, vpc, settings)
+ settings["domain"] = config["vpc"]["domain"]
+ e = elb.create(name, vpc, settings, ssl_certs)
if settings["dns_record"]
say "adding CNAME record for #{settings["dns_record"]}.#{config["vpc"]["domain"]}"
route53.add_record(settings["dns_record"], config["vpc"]["domain"], [e.dns_name], {ttl: settings["ttl"], type: 'CNAME'})
View
1 bosh_aws_bootstrap/lib/bosh_aws_bootstrap.rb
@@ -22,3 +22,4 @@
require "bosh_aws_bootstrap/microbosh_manifest"
require "bosh_aws_bootstrap/bat_manifest"
require "bosh_aws_bootstrap/bosh_manifest"
+require "bosh_aws_bootstrap/server_certificate"
View
74 bosh_aws_bootstrap/lib/bosh_aws_bootstrap/elb.rb
@@ -2,15 +2,17 @@ module Bosh::Aws
class ELB
def initialize(credentials)
- @aws_elb = AWS::ELB.new(credentials)
+ @credentials = credentials
+ @aws_elb = AWS::ELB.new(@credentials)
+ @aws_iam = AWS::IAM.new(@credentials)
end
- def create(name, vpc, settings)
+ def create(name, vpc, settings, certs)
subnet_names = settings["subnets"]
subnet_ids = vpc.subnets.select { |k, v| subnet_names.include?(k) }.values
security_group_name = settings["security_group"]
security_group_id = vpc.security_group_by_name(security_group_name).id
- aws_elb.load_balancers.create(name, {
+ options = {
:listeners => [{
:port => 80,
:protocol => :http,
@@ -19,14 +21,44 @@ def create(name, vpc, settings)
}],
:subnets => subnet_ids,
:security_groups => [security_group_id]
- }).tap do |new_elb|
- new_elb.configure_health_check({
- :healthy_threshold => 5,
- :unhealthy_threshold => 2,
- :interval => 5,
- :timeout => 2,
- :target => "TCP:80"
- })
+ }
+
+ if settings["https"]
+ domain = settings["domain"]
+ cert_name = settings["ssl_cert"]
+ cert = certs[cert_name]
+ dns_record = settings["dns_record"]
+
+ certificate = Bosh::Aws::ServerCertificate.new(cert['private_key'],
+ cert['certificate'],
+ domain,
+ dns_record,
+ cert['certificate_chain']
+ ).load_or_create
+
+ uploaded_cert = upload_certificate(cert_name, certificate)
+
+ options[:listeners] << {
+ :port => 443,
+ :protocol => :https,
+ :instance_port => 80,
+ :instance_protocol => :http,
+ # passing through 'ssl_certificate_id' is undocumented, but we're
+ # working around a bug filed here: https://github.com/aws/aws-sdk-ruby/issues/216
+ :ssl_certificate_id => uploaded_cert.arn
+ }
+ end
+
+ Bosh::Common.retryable(tries: 10, on: AWS::ELB::Errors::CertificateNotFound) do
+ aws_elb.load_balancers.create(name, options).tap do |new_elb|
+ new_elb.configure_health_check({
+ :healthy_threshold => 5,
+ :unhealthy_threshold => 2,
+ :interval => 5,
+ :timeout => 2,
+ :target => "TCP:80"
+ })
+ end
end
end
@@ -36,12 +68,28 @@ def names
def delete_elbs
aws_elb.load_balancers.each(&:delete)
+ Bosh::Common.retryable(tries: 5, sleep: 2) do
+ aws_iam.server_certificates.each(&:delete)
+ aws_iam.server_certificates.to_a.empty?
+ end
end
private
- def aws_elb
- @aws_elb
+ attr_reader :aws_iam, :aws_elb
+
+ def upload_certificate(name, cert)
+ certificates = aws_iam.server_certificates
+ options = {
+ name: name,
+ certificate_body: cert.certificate,
+ private_key: cert.key
+ }
+
+ options[:certificate_chain] = cert.chain if cert.chain
+
+ certificates.upload(options)
end
+
end
end
View
95 bosh_aws_bootstrap/lib/bosh_aws_bootstrap/server_certificate.rb
@@ -0,0 +1,95 @@
+require 'openssl'
+
+module Bosh::Aws
+ class ServerCertificate
+ class SubjectsDoNotMatchException < RuntimeError; end
+
+ def initialize(key_path, certificate_path, domain, dns_record, chain_path = nil)
+ @key_path = key_path
+ @certificate_path = certificate_path
+ @chain_path = chain_path
+ @subject_string = subject_string(domain, dns_record)
+ end
+
+ def load_or_create
+ @key, @csr_cert = load_or_create_key_and_csr_cert
+ @chain = OpenSSL::X509::Certificate.new(File.read(@chain_path)) if @chain_path
+
+ self
+ end
+
+ def key
+ @key.to_pem
+ end
+
+ def certificate
+ @csr_cert.to_pem
+ end
+
+ def chain
+ @chain.to_pem if @chain
+ end
+
+ private
+
+ def load_or_create_key_and_csr_cert
+ File.exists?(@key_path) ? load_key_and_csr_cert : create_key_and_csr_cert
+ end
+
+ def load_key_and_csr_cert
+ key = OpenSSL::PKey::RSA.new(File.read(@key_path))
+ csr_cert = OpenSSL::X509::Certificate.new(File.read(@certificate_path))
+ subject = OpenSSL::X509::Name.parse(@subject_string)
+
+ raise SubjectsDoNotMatchException.new(
+ "The subject you provided is '#{subject}' but the certificate you loaded has a subject of '#{csr_cert.subject}'."
+ ) unless csr_cert.subject == subject
+
+ [key, csr_cert]
+ end
+
+ def create_key_and_csr_cert
+ subject = OpenSSL::X509::Name.parse(@subject_string)
+ key = OpenSSL::PKey::RSA.new(2048)
+ csr = new_csr(key, subject)
+ csr_cert = new_csr_certificate(key, csr)
+
+ File.write(@key_path, key.to_pem)
+ File.write(@certificate_path, csr_cert.to_pem)
+
+ [key, csr_cert]
+ end
+
+ def new_csr(key, subject)
+ csr = OpenSSL::X509::Request.new
+ csr.version = 0
+ csr.subject = subject
+ csr.public_key = key.public_key
+ csr.sign key, OpenSSL::Digest::SHA1.new
+
+ csr
+ end
+
+ def new_csr_certificate(key, csr)
+ csr_cert = OpenSSL::X509::Certificate.new
+ csr_cert.serial = 0
+ csr_cert.version = 2
+ csr_cert.not_before = Time.now
+ csr_cert.not_after = Time.now + 94608000
+
+ csr_cert.subject = csr.subject
+ csr_cert.public_key = csr.public_key
+ csr_cert.issuer = csr.subject
+
+ csr_cert.sign key, OpenSSL::Digest::SHA1.new
+
+ csr_cert
+ end
+
+ def subject_string(domain, dns_record)
+ "/C=US/O=Pivotal/CN=#{dns_record}.#{domain}"
+ end
+ end
+end
+
+
View
10 bosh_aws_bootstrap/spec/assets/ca/ca.csr
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBfDCB5gIBADA9MQswCQYDVQQGEwJVUzEQMA4GA1UEChMHUGl2b3RhbDEcMBoG
+A1UEAxMTbXlhcHAuZGV2MTAyLmNmLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAwJset1MW8pgZqjPIPAZ4cpm4svvq0NhgEBhk7p7/BG6TMuVFV63wYqI7
+AgpLidvR60EHpHjTnjWVMxhxMuXEgxXEzaBOiDs5AiWQl1dTOBzNZc0I3jPNpDAX
+6DJN/LjDCIu+13rWRXn558reo5oAFEFzk17MxefOlKo3XWSRbxcCAwEAAaAAMA0G
+CSqGSIb3DQEBBQUAA4GBADTBZQ5r+VgEGV6i35FRfU3dnMZ07ypVlFwDuCn3Aprp
+6hO1+Clgc2168I25bX9TpswyLmp693evv678fsVYTTmmAtDJraJtxTeqmt2XAnvN
+CA5IKSkpEq+t+3ttzIDU6gNxkNYEPdOJkpn5hAur0+mRj/g9ShFpPta7oT0/AGm7
+-----END CERTIFICATE REQUEST-----
View
15 bosh_aws_bootstrap/spec/assets/ca/ca.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDAmx63UxbymBmqM8g8Bnhymbiy++rQ2GAQGGTunv8EbpMy5UVX
+rfBiojsCCkuJ29HrQQekeNOeNZUzGHEy5cSDFcTNoE6IOzkCJZCXV1M4HM1lzQje
+M82kMBfoMk38uMMIi77XetZFefnnyt6jmgAUQXOTXszF586UqjddZJFvFwIDAQAB
+AoGAdMkZ9Nri3TIwza0cSpk5y3M+NPjfRscMaHYCLVYnF07c3k16oFGNoqDv2qrX
+Zr3tKRKDSc0hs5qV3yeA6/W6Expjcl39DzIcCH6x4yuR/WSv5koj6cUvhEsA9ElK
+YK5mrHPPvvmBHPsz3yJs+4XvtOmOF12qKFN4AkpuhGyc9CECQQD7zp/FxscrHud8
+/l7s033z45vj7D3tEiCC0ZkBGPOPnlUA4/M5Zw9tAFJ7nL7yvNn5jV3I56OFWmWO
+yBe69QeZAkEAw9Ajt54GDSFxwJXQ5LZ6yj5HPKCMybuRuvnBPkfSR/RAIj7RYoIS
+HTQqmWFr7v63Q6QUp5NU1FLuUlRw+PuaLwJBAN4nJghb3b1qT3qUBHVWBUoVZaRI
++T1df7dKaX1xYPg1DPNzKFNdXFujLCIIYOJQwNIM6U/lJPvyi6TTXTRBFwkCQQCm
+meDJyVm8skfmY5FoBxpSGMrjenZO7ZgATZ9tLg5ryhUHDgt4lNJ0pn3YIluC7JVg
+p6orKd9zB1xeEBpOvUNhAkEA3B0y9TMb/FCFZE1KI9mJT/5JBj4Fp0JzYUXj1RQt
+GT8S7QBRq5zGwyTrREEwjTy4P+2qi6I/cIZ6JlxGnAgO7g==
+-----END RSA PRIVATE KEY-----
View
13 bosh_aws_bootstrap/spec/assets/ca/ca.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB8TCCAVoCCQCTYw6RNn+xnzANBgkqhkiG9w0BAQUFADA9MQswCQYDVQQGEwJV
+UzEQMA4GA1UEChMHUGl2b3RhbDEcMBoGA1UEAxMTbXlhcHAuZGV2MTAyLmNmLmNv
+bTAeFw0xMzA0MDMwMDU0NDRaFw0yMzA0MDEwMDU0NDRaMD0xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdQaXZvdGFsMRwwGgYDVQQDExNteWFwcC5kZXYxMDIuY2YuY29t
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAmx63UxbymBmqM8g8Bnhymbiy
+++rQ2GAQGGTunv8EbpMy5UVXrfBiojsCCkuJ29HrQQekeNOeNZUzGHEy5cSDFcTN
+oE6IOzkCJZCXV1M4HM1lzQjeM82kMBfoMk38uMMIi77XetZFefnnyt6jmgAUQXOT
+XszF586UqjddZJFvFwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEQD/rtehZu7DQIp
+vkmeEGsTpQHINEcgZDiJg/A4zqaP1szv1/RmYzpY8jfl6cvjmcnJWdcfubgV0Bko
+xBw60zOFcTBZFvMGqA3n1jFqgdv6kU4NzCnyrB51TNKiET0WLEKb1j/dJvaHWsa8
+/wiMXR5sTqXg0V4AmoHGJ1IioKHb
+-----END CERTIFICATE-----
View
27 bosh_aws_bootstrap/spec/assets/ca/chain.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEWjCCA0KgAwIBAgILBAAAAAABL07hQUMwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIERvbWFpbiBWYWxpZGF0
+aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxo83A
+3zNAJuveWteUZtQBY8wzRIng4rjCRw2PrWmGHKhzQgvxcvstrLURcoMi9lbnLsVn
+cZ0AHDK84+0uCEWp5vrdyIyDBcFvS9AmSgv2G0XATX6TvA0nhO0wo+nGJibdLR/Y
+i8POGdBb/Aif5NjiNeSgaKb2DaN0YEKyl4IkjkGk8i5eto6nbtlsfw07JDVq0Ktb
+aveXAgA/UaanbnPKdw12fJu2MBoanPcfKHsOi0cf538FjMbJyLvP6dx6QS6hhtrU
+ObLiE0CmqDr6D1MeT+xumAkbypp3s1WFhekuFrWdXlTxSnpsObpuFwY0s7JC4ffz
+nJoLEUTeaniOsRNPAgMBAAGjggElMIIBITAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUlq36sFu5g2QqdsIcimnaQtz+/SgwRwYD
+VR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2Jh
+bHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9j
+cmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAvMC0GCCsG
+AQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEwHwYDVR0j
+BBgwFoAUYHtmGkUNl8qJUC99BM00qP/8/UswDQYJKoZIhvcNAQEFBQADggEBADrn
+/K6vBUOAJ3VBX6jwKI8fj4N+sri6rnUxJ4il5blOBEPSregTAKPbGQEwnmw8Un9c
+3qtnw4QEVFGZnmMvvdW3wNXaAw5J0+Gzkk/fkk59riJqzti8/Hyua7aK6kVikBHT
+C3GnXgYi/0046rk6bs1nGgJ/S/O/DnlvvtUpMllZHZYIm3CP9x5cRntO0J20U8gS
+AhsNuzLrWVO5PhtWjRXI8UI/d/4f5W2eZh+r2rKDV7QMItKGvNoy18DtcIV8k6rw
+l9w5EdLYieuNkKO2UCXLbNmmw2/7iFS45JJwh855O/DeNr8DBAA9+e+eqWek9IY+
+I5e4KnHi7f5piGe/Jlw=
+-----END CERTIFICATE-----
+
View
4 bosh_aws_bootstrap/spec/assets/ca/generate.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+openssl req -nodes -new -newkey rsa:1024 -out ca.csr -keyout ca.key -subj '/C=US/O=Pivotal/CN=myapp.dev102.cf.com'
+openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.pem
View
8 bosh_aws_bootstrap/spec/assets/config.yml
@@ -46,11 +46,19 @@ vpc:
sources: 0.0.0.0/0
elbs:
external-elb-1:
+ domain: dev102.cf.com
dns_record: "*"
ttl: 60
subnets:
- bosh
security_group: open
+ https: true
+ ssl_cert: my_cert_1
+ssl_certs:
+ my_cert_1:
+ private_key: /path/to/private_key
+ certificate: /path/to/certificate
+ certificate_chain: /path/to/certificate_chain
elastic_ips:
micro:
instances: 1
View
6 bosh_aws_bootstrap/spec/functional/aws_spec.rb
@@ -177,7 +177,7 @@ def make_fake_vpc!(overrides = {})
fake_ec2.stub(:force_add_key_pair)
fake_ec2.stub(:create_internet_gateway).and_return(fake_igw)
fake_ec2.stub(:elastic_ips).and_return(["1.2.3.4", "5.6.7.8"])
- fake_elb.stub(:create).and_return(mock("new elb", dns_name: 'elb-123.example.com'))
+ fake_elb.stub(:create).with("external-elb-1", fake_vpc, anything, hash_including('my_cert_1' => anything)).and_return(mock("new elb", dns_name: 'elb-123.example.com'))
fake_route53.stub(:create_zone)
fake_route53.stub(:add_record)
fake_vpc
@@ -1066,5 +1066,9 @@ def make_fake_rds!(opts = {})
aws.delete_all_elbs(config_file)
end
end
+
+ describe "aws create elb" do
+
+ end
end
end
View
156 bosh_aws_bootstrap/spec/unit/elb_spec.rb
@@ -1,73 +1,159 @@
require 'spec_helper'
describe Bosh::Aws::ELB do
- let(:elb) { described_class.new({"my" => "creds"}) }
+ let(:creds) { {'my' => 'creds'} }
+ let(:elb) { described_class.new(creds) }
let(:ec2) { Bosh::Aws::EC2.new({}) }
- let(:fake_aws_security_group) { mock("security_group", id: "sg_id", name: "security_group_name") }
- let(:fake_aws_vpc) { mock("vpc", security_groups: [fake_aws_security_group]) }
+ let(:fake_aws_security_group) { mock('security_group', id: 'sg_id', name: 'security_group_name') }
+ let(:fake_aws_vpc) { mock('vpc', security_groups: [fake_aws_security_group]) }
let(:vpc) { Bosh::Aws::VPC.new(ec2, fake_aws_vpc) }
- let(:fake_aws_elb) { double("aws_elb", load_balancers: double()) }
+ let(:fake_aws_elb) { double(AWS::ELB, load_balancers: double()) }
+ let(:certificates) { [] }
+ let(:fake_aws_iam) { double(AWS::IAM, server_certificates: certificates) }
- it "creates an underlying AWS ELB object with your credentials" do
- AWS::ELB.should_receive(:new).with({"my" => "creds"}).and_call_original
+ it 'creates an underlying AWS ELB object with your credentials' do
+ AWS::ELB.should_receive(:new).with(creds).and_call_original
elb.send(:aws_elb).should be_kind_of(AWS::ELB)
end
- describe "creation" do
- let(:new_elb) {mock("a new elb")}
+ describe 'creation' do
+ let(:new_elb) { mock('a new elb') }
+ let(:cert) { { 'certificate' => asset('ca/ca.pem'), 'private_key' => asset('ca/ca.key'), 'certificate_chain' => asset('ca/chain.pem') } }
+ let(:cert_name) { 'my-cert-name' }
+ let(:http_listener) { { port: 80, protocol: :http, instance_port: 80, instance_protocol: :http } }
+ let(:https_listener) { { port: 443, protocol: :https, instance_port: 80, instance_protocol: :http, ssl_certificate_id: 'certificate_arn' } }
+ let(:certs) { {} }
+
before do
elb.stub(:aws_elb).and_return(fake_aws_elb)
new_elb.should_receive(:configure_health_check).with({
:healthy_threshold => 5,
:unhealthy_threshold => 2,
:interval => 5,
:timeout => 2,
- :target => "TCP:80"
+ :target => 'TCP:80'
})
- vpc.should_receive(:subnets).and_return({"sub_name1" => "sub_id1", "sub_name2" => "sub_id2"})
- vpc.should_receive(:security_group_by_name).with("security_group_name").and_return(fake_aws_security_group)
+ vpc.stub(:subnets).and_return({'sub_name1' => 'sub_id1', 'sub_name2' => 'sub_id2'})
+ vpc.stub(:security_group_by_name).with('security_group_name').and_return(fake_aws_security_group)
end
- it "can create an ELB given a name and a vpc and a CIDR block" do
- fake_aws_elb.load_balancers.should_receive(:create).with("my elb name", {
- :listeners => [{
- :port => 80,
- :protocol => :http,
- :instance_port => 80,
- :instance_protocol => :http,
- }],
- :subnets => ["sub_id1", "sub_id2"],
- :security_groups => ["sg_id"]
+ it 'can create an ELB given a name and a vpc and a CIDR block' do
+ fake_aws_elb.load_balancers.should_receive(:create).with('my elb name', {
+ :listeners => [http_listener],
+ :subnets => %w[sub_id1 sub_id2],
+ :security_groups => %w[sg_id]
}).and_return(new_elb)
- elb.create("my elb name", vpc, "subnets" => %w(sub_name1 sub_name2), "security_group" => "security_group_name").should == new_elb
+ elb.create('my elb name', vpc, {'subnets' => %w(sub_name1 sub_name2), 'security_group' => 'security_group_name'}, certs).should == new_elb
+ end
+
+ describe 'creating a new ELB that allows HTTPS' do
+ let(:certs) { {cert_name => cert} }
+ let(:certificate) { double(AWS::IAM::ServerCertificate, arn: 'certificate_arn') }
+ let(:certificates) { double(AWS::IAM::ServerCertificateCollection) }
+
+ before do
+ elb.stub(:aws_iam).and_return(fake_aws_iam)
+
+ fake_aws_elb.load_balancers.should_receive(:create).with('my elb name', {
+ listeners: [http_listener, https_listener],
+ subnets: ['sub_id1', 'sub_id2'],
+ security_groups: ['sg_id'],
+ }).and_return(new_elb)
+ end
+
+ context 'if the certificate is self signed (has no certificate chain)' do
+ let(:cert) { { 'certificate' => asset('ca/ca.pem'), 'private_key' => asset('ca/ca.key') } }
+
+ before do
+ certificates.should_receive(:upload).with(anything) do |args|
+ args[:certificate_body].should match /BEGIN CERTIFICATE/
+ args[:private_key].should match /BEGIN RSA PRIVATE KEY/
+ args[:name].should == cert_name
+ args.should_not have_key :certificate_chain
+ end.and_return(certificate)
+ end
+
+ it 'can create a new ELB that is configured to allow HTTPS' do
+ elb.create('my elb name', vpc, {'subnets' => %w(sub_name1 sub_name2),
+ 'security_group' => 'security_group_name',
+ 'https' => true,
+ 'ssl_cert' => cert_name,
+ 'dns_record' => 'myapp',
+ 'domain' => 'dev102.cf.com'}, certs).should == new_elb
+ end
+ end
+
+ context 'if the certificate comes from a signing authority (has a certificate chain)' do
+ before do
+ certificates.should_receive(:upload).with(anything) do |args|
+ args[:certificate_chain].should match /BEGIN CERTIFICATE/
+ args[:certificate_body].should match /BEGIN CERTIFICATE/
+ args[:private_key].should match /BEGIN RSA PRIVATE KEY/
+ args[:name].should == cert_name
+ end.and_return(certificate)
+ end
+
+ it 'can create a new ELB that is configured to allow HTTPS' do
+ elb.create('my elb name', vpc, {'subnets' => %w(sub_name1 sub_name2),
+ 'security_group' => 'security_group_name',
+ 'https' => true,
+ 'ssl_cert' => cert_name,
+ 'dns_record' => 'myapp',
+ 'domain' => 'dev102.cf.com'}, certs).should == new_elb
+ end
+ end
end
end
- describe "deletion" do
+ describe 'deletion' do
+ let(:load_balancers) { [] }
+ let(:server_certificates) { [] }
+
before do
elb.stub(:aws_elb).and_return(fake_aws_elb)
+ elb.stub(:aws_iam).and_return(fake_aws_iam)
+
+ fake_aws_iam.should_receive(:server_certificates).and_return(server_certificates)
+ fake_aws_elb.should_receive(:load_balancers).and_return(load_balancers)
end
- it "should call delete on each ELB" do
- elb1 = mock("elb1")
- elb2 = mock("elb2")
- elb1.should_receive(:delete)
- elb2.should_receive(:delete)
- fake_aws_elb.should_receive(:load_balancers).and_return([elb1, elb2])
- elb.delete_elbs
+ describe 'deleting each load balancer' do
+ let(:elb1) { mock('elb1') }
+ let(:elb2) { mock('elb2') }
+ let(:load_balancers) { [elb1, elb2] }
+
+ it 'should call delete on each ELB' do
+ elb1.should_receive(:delete)
+ elb2.should_receive(:delete)
+
+ elb.delete_elbs
+ end
+ end
+
+ describe 'deleting the server certificates' do
+ let(:cert1) { mock('cert1') }
+ let(:cert2) { mock('cert2') }
+ let(:server_certificates) { [cert1, cert2] }
+
+ it 'deletes all of the uploaded server certificates' do
+ cert1.should_receive(:delete)
+ cert2.should_receive(:delete)
+
+ elb.delete_elbs
+ end
end
end
- describe "names" do
+ describe 'names' do
before do
elb.stub(:aws_elb).and_return(fake_aws_elb)
end
- it "returns the names of the running ELBs" do
- elb1 = mock("elb1", name: 'one')
- elb2 = mock("elb2", name: 'two')
+ it 'returns the names of the running ELBs' do
+ elb1 = mock('elb1', name: 'one')
+ elb2 = mock('elb2', name: 'two')
fake_aws_elb.should_receive(:load_balancers).and_return([elb1, elb2])
- elb.names.should == ["one", "two"]
+ elb.names.should == %w[one two]
end
end
end
View
126 bosh_aws_bootstrap/spec/unit/server_certificate_spec.rb
@@ -0,0 +1,126 @@
+require 'spec_helper'
+
+require 'tmpdir'
+require 'openssl'
+
+describe Bosh::Aws::ServerCertificate do
+ let(:subject_name) { '/C=US/O=Pivotal/CN=myapp.foo.com' }
+ let(:dns_record) { 'myapp' }
+ let(:domain) { 'foo.com' }
+ let(:server_certificate) { described_class.new(key_path, certificate_path, domain, dns_record) }
+
+ describe '#load_or_create' do
+ context 'when the paths given do not exist' do
+ let(:key_path) { File.join(Dir.tmpdir, 'ca.key') }
+ let(:certificate_path) { File.join(Dir.tmpdir, 'ca.pem') }
+
+ before do
+ FileUtils.rm_f(key_path)
+ FileUtils.rm_f(certificate_path)
+ end
+
+ it 'returns self so that it can be appended on to the constructor easily' do
+ server_certificate.load_or_create.should == server_certificate
+ end
+
+ it 'creates a new, valid certificate' do
+ server_certificate.load_or_create
+
+ key = OpenSSL::PKey::RSA.new(File.read(key_path))
+ certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
+
+ key.to_s.should include('BEGIN RSA PRIVATE KEY')
+ certificate.to_s.should include('BEGIN CERTIFICATE')
+
+ certificate.verify(key).should be_true
+ end
+
+ it 'sets the subject from the domain we ask for' do
+ server_certificate.load_or_create
+
+ certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
+ certificate.subject.to_s.should == subject_name
+ end
+
+ it 'has a sensible certificate lifetime' do
+ server_certificate.load_or_create
+
+ certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
+ start_time = certificate.not_before
+ end_time = certificate.not_after
+
+ (end_time - start_time).should == 3 * 365 * 24 * 60 * 60 # 3 Years
+ end
+ end
+
+ context 'when the paths given do exist' do
+ let(:key_path) { asset('ca/ca.key') }
+ let(:certificate_path) { asset('ca/ca.pem') }
+ let(:domain) { 'dev102.cf.com' }
+
+ it 'loads the key and certificate from the files' do
+ key_contents_before = File.read(key_path)
+ certificate_contents_before = File.read(certificate_path)
+
+ server_certificate.load_or_create
+
+ server_certificate.key.should == key_contents_before
+ server_certificate.certificate.should == certificate_contents_before
+ end
+
+ it 'does not write to the file unnecessarily' do
+ File.should_not_receive(:write).with(any_args)
+ server_certificate.load_or_create
+ end
+
+ it 'returns self so that it can be appended on to the constructor easily' do
+ server_certificate.load_or_create.should == server_certificate
+ end
+
+ context 'when the user has a certificate chain' do
+ let(:chain_path) { asset('ca/chain.pem') }
+ let(:server_certificate) { described_class.new(key_path, certificate_path, domain, dns_record, chain_path) }
+
+ it 'allows the user to read the contents of the chain file' do
+ server_certificate.load_or_create
+
+ server_certificate.chain.should include "BEGIN CERTIFICATE"
+ end
+ end
+
+ context 'when the user does not have a certificate chain' do
+ let(:server_certificate) { described_class.new(key_path, certificate_path, domain, dns_record) }
+
+ it 'the certificate chain should be nil' do
+ server_certificate.load_or_create
+
+ server_certificate.chain.should be_nil
+ end
+ end
+
+ describe 'verifying that the subject of the certificate we load matches the one was ask for' do
+ context 'when it does match' do
+ let(:domain) { 'dev102.cf.com' }
+
+ it 'does not raise an exception' do
+ expect {
+ server_certificate.load_or_create
+ }.to_not raise_error(Bosh::Aws::ServerCertificate::SubjectsDoNotMatchException)
+ end
+ end
+
+ context 'when it does not match' do
+ let(:domain) { 'bar.com' }
+
+ it 'raises an exception' do
+ expect {
+ server_certificate.load_or_create
+ }.to raise_error(Bosh::Aws::ServerCertificate::SubjectsDoNotMatchException,
+ "The subject you provided is '/C=US/O=Pivotal/CN=myapp.bar.com' but the certificate you loaded has a subject of '/C=US/O=Pivotal/CN=myapp.dev102.cf.com'."
+ )
+ end
+ end
+ end
+ end
+ end
+end
View
10 bosh_aws_bootstrap/templates/aws_configuration_template.yml.erb
@@ -84,6 +84,16 @@ vpc:
subnets:
- bosh
security_group: web
+ https: true
+ ssl_cert: cfrouter_cert
+
+ssl_certs:
+ cfrouter_cert:
+ private_key: <%= ENV["BOSH_SSL_KEY"] || "elb-cfrouter.key" %>
+ certificate: <%= ENV["BOSH_SSL_CERT"] || "elb-cfrouter.pem" %>
+ <% if ENV["BOSH_SSL_CHAIN"] %>
+ certificate_chain: <%= ENV["BOSH_SSL_CHAIN"] %>
+ <% end %>
elastic_ips:
# each NAT box automatically reserves 1 elastic IP, which is not listed below

0 comments on commit a6ad8f3

Please sign in to comment.
Something went wrong with that request. Please try again.