Skip to content

Instantly share code, notes, and snippets.

@aschmidt75
Created January 27, 2015 15:21
Show Gist options
  • Save aschmidt75/163c36450c9c24f21285 to your computer and use it in GitHub Desktop.
Save aschmidt75/163c36450c9c24f21285 to your computer and use it in GitHub Desktop.
#serverspec example for testing a docker TLS-only setup. Checks key, certificates, configuration, runtime
require 'spec_helper.rb'
# The MIT License (MIT) andreas@de-wiring.net
# This serverspec ensures that
# - tls is configured in docker defaults file
# - certs and keys are present and valid (using openssl verify)
# - dockerd is listing on TLS port
# - there is no activity on docker socket (tls only)
# - that a connection is TLS-secure (using openssl s_client)
# - that docker client can connect using TLS (using docker client)
# dependencies
# - openssl, lsof, netstat, docker client
# This is a sample specification, it has to be adapted to
# local paths and settings.
# Configuration ---------------
#
docker_tls_config = {
:host_ip => '10.0.2.15',
:host_name => 'docker-server.local',
:cert_path => '/etc/docker-tls/certs', # where certificates are
:key_path => '/etc/docker-tls/private', # where keys are
:ca_file => '/etc/docker-tls/cacert.pem',
# file names of certs in CERT_PATH/
:client_cert_file => 'client-cert.pem',
:server_cert_file => 'server-cert.pem',
# file names of keys in KEY_PATH/
:client_key_file => 'client-key.pem',
:server_key_file => 'server-key.pem',
# certificate details to check for
:cert_issuer => /C=DE\/L=Berlin\/O=YourOrg.com/,
:server_cert_subject => /^subject=.*\/CN=docker-server.local/,
:client_cert_subject => /^subject=.*\/CN=client/,
# FLAG: should local socket be allowed or not
:allow_socket => false,
# ensure key strength
:server_key_bits => 4096
}
# -----------------------------
describe 'This spec needs to run as root' do
describe command "id -u" do
its(:stdout) { should match /^0$/ }
end
end
# check all files needed for TLS, client and server - keys and certs.
# run openssl to check validity
describe 'keys and certs should be present and valid' do
[ docker_tls_config[:server_key_file], docker_tls_config[:client_key_file]].each do |n|
describe file "#{docker_tls_config[:key_path]}/#{n}" do
it { should be_file }
it { should be_owned_by 'root' }
it { should be_grouped_into 'root' }
it { should be_mode 640 }
end
describe command "openssl rsa -in #{docker_tls_config[:key_path]}/#{n} -check -noout" do
its(:stdout) { should match /^RSA key ok/ }
its(:exit_status) { should be 0 }
end
end
[ docker_tls_config[:server_cert_file], docker_tls_config[:client_cert_file] ].each do |n|
describe file "#{docker_tls_config[:cert_path]}/#{n}" do
it { should be_file }
it { should be_owned_by 'root' }
it { should be_grouped_into 'root' }
it { should be_mode 644 }
end
describe command "openssl x509 -in #{docker_tls_config[:cert_path]}/#{n} -issuer -noout" do
its(:stdout) { should match docker_tls_config[:cert_issuer] }
its(:exit_status) { should be 0 }
end
describe command "openssl verify -CAfile #{docker_tls_config[:ca_file]} #{docker_tls_config[:cert_path]}/#{n}" do
its(:stdout) { should match /.*OK$/ }
its(:exit_status) { should be 0 }
end
end
describe 'Server key should match server cert' do
describe command "(openssl x509 -noout -modulus -in #{docker_tls_config[:cert_path]}/#{docker_tls_config[:server_cert_file]} | openssl md5 ; \
openssl rsa -noout -modulus -in #{docker_tls_config[:key_path]}/#{docker_tls_config[:server_key_file]} | openssl md5 ) | \
uniq | wc -l" do
its(:stdout) { should match /^1$/ }
end
end
describe 'Client key should match client cert' do
describe command "(openssl x509 -noout -modulus -in #{docker_tls_config[:cert_path]}/#{docker_tls_config[:client_cert_file]} | openssl md5 ; \
openssl rsa -noout -modulus -in #{docker_tls_config[:key_path]}/#{docker_tls_config[:client_key_file]} | openssl md5 ) | \
uniq | wc -l" do
its(:stdout) { should match /^1$/ }
end
end
describe 'Key subjects should be valid' do
describe command "openssl x509 -in #{docker_tls_config[:cert_path]}/#{docker_tls_config[:server_cert_file]} -subject -noout" do
its(:stdout) { should match docker_tls_config[:server_cert_subject]}
its(:exit_status) { should be 0 }
end
describe command "openssl x509 -in #{docker_tls_config[:cert_path]}/#{docker_tls_config[:client_cert_file]} -subject -noout" do
its(:stdout) { should match docker_tls_config[:client_cert_subject] }
its(:exit_status) { should be 0 }
end
end
end
# check defaults configuration file for settings to be present
# does not mean that they're effective, but at least thats needed.
describe 'it should have TLS configured in defaults file' do
describe file('/etc/default/docker') do
its(:content) { should match '--tlsverify' }
its(:content) { should match '--tlscacert=[a-zA-Z_0-9\/]+' }
its(:content) { should match '--tlscert=[a-zA-Z_0-9\/]+' }
its(:content) { should match '--tlskey=[a-zA-Z_0-9\/]+' }
its(:content) { should match "-H=#{docker_tls_config[:host_ip]}:2376" }
its(:content) { should_not match '-H=0\.0\.0\.0' }
end
end
# basic check for port to be listening, NOT on 0.0.0.0
describe 'it should be running on specific port/ip' do
describe port 2376 do
it { should be_listening }
end
# netstat-based
describe command 'netstat -nltp' do
its(:stdout) { should match '[0-9\.]+:2376.*docker' }
its(:stdout) { should_not match '0\.0\.0\.0:2376.*docker' }
end
# lsof based
describe command 'lsof -n -i :2376' do
its(:stdout) { should match 'docker.*[0-9\.]+:2376.*LISTEN' }
its(:stdout) { should_not match 'docker.*0\.0\.0\.0:2376.*LISTEN' }
end
end
if docker_tls_config[:allow_socket]== false then
# use lsof to ensure no activity on socket even if there
# is a socket.
describe 'it should NOT be running on docker socket' do
# extra check
describe command 'test -S /var/run/docker.sock && lsof /var/run/docker.sock' do
its(:stdout) { should_not match /^docker.*docker.sock$/ }
its(:stdout) { should match /^$/ }
end
end
else
# ensure that socket exists and is active
describe 'it should be running on docker socket' do
describe file '/var/run/docker.sock' do
it { should be_socket }
end
# extra check
describe command 'test -S /var/run/docker.sock && lsof /var/run/docker.sock' do
its(:stdout) { should match /^docker.*docker.sock$/ }
its(:stdout) { should_not match /^$/ }
end
end
end
# use openssl s_tunnel to check that TLS port is enabled with our certificate
describe 'it should be running on secured TLS port' do
describe command "echo '' | \
openssl s_client \
-showcerts \
-host #{docker_tls_config[:host_ip]} \
-port 2376 \
-state \
-cert #{docker_tls_config[:cert_path]}/#{docker_tls_config[:client_cert_file]} \
-key #{docker_tls_config[:key_path]}/#{docker_tls_config[:client_key_file]} \
-CAfile #{docker_tls_config[:ca_file]}" do
its(:exit_status) { should be 0 }
its(:stdout) { should match docker_tls_config[:server_cert_subject] }
its(:stdout) { should match docker_tls_config[:cert_issuer] }
# ensure key strength
its(:stdout) { should match /^Server public key is #{docker_tls_config[:server_key_bits]} bit$/ }
# ensure that out cert is written to server by looking at -state output
its(:stdout) { should match /write client certificate/ }
its(:stdout) { should match /write client key exchange/ }
# look for recent cipher suites
its(:stdout) { should match /Protocol.*:.*TLSv1.2/ }
end
end
# finally ensure that docker client is able to call API successfully
describe 'it should respond to docker client using TLS' do
describe command "DOCKER_HOST=tcp://#{docker_tls_config[:host_name]}:2376 docker \
--tlsverify \
--tlscacert=#{docker_tls_config[:ca_file]} \
--tlscert #{docker_tls_config[:cert_path]}/#{docker_tls_config[:client_cert_file]} \
--tlskey #{docker_tls_config[:key_path]}/#{docker_tls_config[:client_key_file]} \
version" do
its(:exit_status) { should eq 0 }
its(:stdout) { should match /Server API version/ }
its(:stdout) { should match /Server version/ }
end
end
@aschmidt75
Copy link
Author

This can be used as a base to spec a docker installation with TLS-only setup. Needs to be adapted and configured, see configuration section at top.

Sample output:

$ sudo rake spec
/usr/bin/ruby1.9.1 -I/var/lib/gems/1.9.1/gems/rspec-support-3.1.2/lib:/var/lib/gems/1.9.1/gems/rspec-core-3.1.7/lib /var/lib/gems/1.9.1/gems/rspec-core-3.1.7/exe/rspec --pattern spec/localhost/\*_spec.rb

This spec needs to run as root
  Command "id -u"
    stdout
      should match /^0$/

keys and certs should be present and valid
  File "/etc/docker-tls/private/server-key.pem"
    should be file
    should be owned by "root"
    should be grouped into "root"
    should be mode 640
  Command "openssl rsa -in /etc/docker-tls/private/server-key.pem -check -noout"
    stdout
      should match /^RSA key ok/
    exit_status
      should equal 0
  File "/etc/docker-tls/private/client-key.pem"
    should be file
    should be owned by "root"
    should be grouped into "root"
    should be mode 640
  Command "openssl rsa -in /etc/docker-tls/private/client-key.pem -check -noout"
    stdout
      should match /^RSA key ok/
    exit_status
      should equal 0
  File "/etc/docker-tls/certs/server-cert.pem"
    should be file
    should be owned by "root"
    should be grouped into "root"
    should be mode 644
  Command "openssl x509 -in /etc/docker-tls/certs/server-cert.pem -issuer -noout"
    stdout
      should match /C=DE\/L=Berlin\/O=YourOrg.com/
    exit_status
      should equal 0
  Command "openssl verify -CAfile /etc/docker-tls/cacert.pem /etc/docker-tls/certs/server-cert.pem"
    stdout
      should match /.*OK$/
    exit_status
      should equal 0
  File "/etc/docker-tls/certs/client-cert.pem"
    should be file
    should be owned by "root"
    should be grouped into "root"
    should be mode 644
  Command "openssl x509 -in /etc/docker-tls/certs/client-cert.pem -issuer -noout"
    stdout
      should match /C=DE\/L=Berlin\/O=YourOrg.com/
    exit_status
      should equal 0
  Command "openssl verify -CAfile /etc/docker-tls/cacert.pem /etc/docker-tls/certs/client-cert.pem"
    stdout
      should match /.*OK$/
    exit_status
      should equal 0
  Server key should match server cert
    Command "(openssl x509 -noout -modulus -in /etc/docker-tls/certs/server-cert.pem | openssl md5 ;                   openssl rsa -noout -modulus -in /etc/docker-tls/private/server-key.pem | openssl md5 ) |                     uniq | wc -l"
      stdout
        should match /^1$/
  Client key should match client cert
    Command "(openssl x509 -noout -modulus -in /etc/docker-tls/certs/client-cert.pem | openssl md5 ;                   openssl rsa -noout -modulus -in /etc/docker-tls/private/client-key.pem | openssl md5 ) |                     uniq | wc -l"
      stdout
        should match /^1$/
  Key subjects should be valid
    Command "openssl x509 -in /etc/docker-tls/certs/server-cert.pem -subject -noout"
      stdout
        should match /^subject=.*\/CN=docker-server.local/
      exit_status
        should equal 0
    Command "openssl x509 -in /etc/docker-tls/certs/client-cert.pem -subject -noout"
      stdout
        should match /^subject=.*\/CN=client/
      exit_status
        should equal 0

it should have TLS configured in defaults file
  File "/etc/default/docker"
    content
      should match "--tlsverify"
    content
      should match "--tlscacert=[a-zA-Z_0-9\\/]+"
    content
      should match "--tlscert=[a-zA-Z_0-9\\/]+"
    content
      should match "--tlskey=[a-zA-Z_0-9\\/]+"
    content
      should match "-H=10.0.2.15:2376"
    content
      should not match "-H=0\\.0\\.0\\.0"

it should be running on specific port/ip
  Port "2376"
    should be listening
  Command "netstat -nltp"
    stdout
      should match "[0-9\\.]+:2376.*docker"
    stdout
      should not match "0\\.0\\.0\\.0:2376.*docker"
  Command "lsof -n -i :2376"
    stdout
      should match "docker.*[0-9\\.]+:2376.*LISTEN"
    stdout
      should not match "docker.*0\\.0\\.0\\.0:2376.*LISTEN"

it should NOT be running on docker socket
  Command "test -S /var/run/docker.sock && lsof /var/run/docker.sock"
    stdout
      should not match /^docker.*docker.sock$/
    stdout
      should match /^$/

it should be running on secured TLS port
  Command "echo '' |        openssl s_client        -showcerts          -host 10.0.2.15         -port 2376      -state      -cert /etc/docker-tls/certs/client-cert.pem         -key /etc/docker-tls/private/client-key.pem         -CAfile /etc/docker-tls/cacert.pem"
    exit_status
      should equal 0
    stdout
      should match /^subject=.*\/CN=docker-server.local/
    stdout
      should match /C=DE\/L=Berlin\/O=YourOrg.com/
    stdout
      should match /^Server public key is 4096 bit$/
    stdout
      should match /write client certificate/
    stdout
      should match /write client key exchange/
    stdout
      should match /Protocol.*:.*TLSv1.2/

it should respond to docker client using TLS
  Command "DOCKER_HOST=tcp://docker-server.local:2376 docker            --tlsverify             --tlscacert=/etc/docker-tls/cacert.pem          --tlscert /etc/docker-tls/certs/client-cert.pem             --tlskey /etc/docker-tls/private/client-key.pem             version"
    exit_status
      should eq 0
    stdout
      should match /Server API version/
    stdout
      should match /Server version/

Finished in 1.46 seconds (files took 0.48905 seconds to load)
58 examples, 0 failures

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment