diff --git a/README.rdoc b/README.rdoc index 53e2d468..f1b1ea36 100644 --- a/README.rdoc +++ b/README.rdoc @@ -52,12 +52,10 @@ This task will run the test suite and the rake rubotest -To run the integration tests against an LDAP server: - - cd test/support/vm/openldap - vagrant up - cd ../../../.. - INTEGRATION=openldap bundle exec rake rubotest +CI takes too long? If your local box supports +{Vagrant}[https://www.vagrantup.com/], you can run most of the tests +in a VM on your local box. For more details and setup instructions, see +{test/support/vm/openldap/README.md}[https://github.com/ruby-ldap/ruby-net-ldap/tree/master/test/support/vm/openldap/README.md] == Release diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index a79d6c55..f7a98ef5 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -476,61 +476,73 @@ def self.result2string(code) #:nodoc: # specify a treebase. If you give a treebase value in any particular # call to #search, that value will override any treebase value you give # here. + # * :force_no_page => Set to true to prevent paged results even if your + # server says it supports them. This is a fix for MS Active Directory + # * :instrumentation_service => An object responsible for instrumenting + # operations, compatible with ActiveSupport::Notifications' public API. # * :encryption => specifies the encryption to be used in communicating # with the LDAP server. The value must be a Hash containing additional # parameters, which consists of two keys: # method: - :simple_tls or :start_tls - # options: - Hash of options for that method + # tls_options: - Hash of options for that method # The :simple_tls encryption method encrypts all communications # with the LDAP server. It completely establishes SSL/TLS encryption with # the LDAP server before any LDAP-protocol data is exchanged. There is no # plaintext negotiation and no special encryption-request controls are # sent to the server. The :simple_tls option is the simplest, easiest # way to encrypt communications between Net::LDAP and LDAP servers. - # It's intended for cases where you have an implicit level of trust in the - # authenticity of the LDAP server. No validation of the LDAP server's SSL - # certificate is performed. This means that :simple_tls will not produce - # errors if the LDAP server's encryption certificate is not signed by a - # well-known Certification Authority. If you get communications or - # protocol errors when using this option, check with your LDAP server - # administrator. Pay particular attention to the TCP port you are - # connecting to. It's impossible for an LDAP server to support plaintext - # LDAP communications and simple TLS connections on the same port. - # The standard TCP port for unencrypted LDAP connections is 389, but the - # standard port for simple-TLS encrypted connections is 636. Be sure you - # are using the correct port. - # + # If you get communications or protocol errors when using this option, + # check with your LDAP server administrator. Pay particular attention + # to the TCP port you are connecting to. It's impossible for an LDAP + # server to support plaintext LDAP communications and simple TLS + # connections on the same port. The standard TCP port for unencrypted + # LDAP connections is 389, but the standard port for simple-TLS + # encrypted connections is 636. Be sure you are using the correct port. # The :start_tls like the :simple_tls encryption method also encrypts all # communcations with the LDAP server. With the exception that it operates # over the standard TCP port. # - # In order to verify certificates and enable other TLS options, the - # :tls_options hash can be passed alongside :simple_tls or :start_tls. - # This hash contains any options that can be passed to - # OpenSSL::SSL::SSLContext#set_params(). The most common options passed - # should be OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, or the :ca_file option, - # which contains a path to a Certificate Authority file (PEM-encoded). - # - # Example for a default setup without custom settings: - # { - # :method => :simple_tls, - # :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS - # } + # To validate the LDAP server's certificate (a security must if you're + # talking over the public internet), you need to set :tls_options + # something like this... # - # Example for specifying a CA-File and only allowing TLSv1.1 connections: - # - # { - # :method => :start_tls, - # :tls_options => { :ca_file => "/etc/cafile.pem", :ssl_version => "TLSv1_1" } + # Net::LDAP.new( + # # ... set host, bind dn, etc ... + # encryption: { + # method: :simple_tls, + # tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, # } - # * :force_no_page => Set to true to prevent paged results even if your - # server says it supports them. This is a fix for MS Active Directory - # * :instrumentation_service => An object responsible for instrumenting - # operations, compatible with ActiveSupport::Notifications' public API. + # ) + # + # The above will use the operating system-provided store of CA + # certificates to validate your LDAP server's cert. + # If cert validation fails, it'll happen during the #bind + # whenever you first try to open a connection to the server. + # Those methods will throw Net::LDAP::ConnectionError with + # a message about certificate verify failing. If your + # LDAP server's certificate is signed by DigiCert, Comodo, etc., + # you're probably good. If you've got a self-signed cert but it's + # been added to the host's OS-maintained CA store (e.g. on Debian + # add foobar.crt to /usr/local/share/ca-certificates/ and run + # `update-ca-certificates`), then the cert should pass validation. + # To ignore the OS's CA store, put your CA in a PEM-encoded file and... + # + # encryption: { + # method: :simple_tls, + # tls_options: { ca_file: '/path/to/my-little-ca.pem', + # ssl_version: 'TLSv1_1' }, + # } + # + # As you might guess, the above example also fails the connection + # if the client can't negotiate TLS v1.1. + # tls_options is ultimately passed to OpenSSL::SSL::SSLContext#set_params + # For more details, see + # http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html # # Instantiating a Net::LDAP object does not result in network # traffic to the LDAP server. It simply stores the connection and binding - # parameters in the object. + # parameters in the object. That's why Net::LDAP.new doesn't throw + # cert validation errors itself; #bind does instead. def initialize(args = {}) @host = args[:host] || DefaultHost @port = args[:port] || DefaultPort diff --git a/lib/net/ldap/connection.rb b/lib/net/ldap/connection.rb index 05f676cc..4f311748 100644 --- a/lib/net/ldap/connection.rb +++ b/lib/net/ldap/connection.rb @@ -52,6 +52,15 @@ def open_connection(server) hosts.each do |host, port| begin prepare_socket(server.merge(socket: @socket_class.new(host, port, socket_opts)), timeout) + if encryption + if encryption[:tls_options] && + encryption[:tls_options][:verify_mode] && + encryption[:tls_options][:verify_mode] == OpenSSL::SSL::VERIFY_NONE + warn "not verifying SSL hostname of LDAPS server '#{host}:#{port}'" + else + @conn.post_connection_check(host) + end + end return rescue Net::LDAP::Error, SocketError, SystemCallError, OpenSSL::SSL::SSLError => e @@ -392,12 +401,11 @@ def search(args = nil) # should collect this into a private helper to clarify the structure query_limit = 0 if size > 0 - if paged - query_limit = (((size - n_results) < 126) ? (size - - n_results) : 0) - else - query_limit = size - end + query_limit = if paged + (((size - n_results) < 126) ? (size - n_results) : 0) + else + size + end end request = [ diff --git a/script/generate-fixture-ca b/script/generate-fixture-ca new file mode 100755 index 00000000..89eb3d8d --- /dev/null +++ b/script/generate-fixture-ca @@ -0,0 +1,48 @@ +#!/bin/bash + +BASE_PATH=$( cd "`dirname $0`/../test/fixtures/ca" && pwd ) +cd "${BASE_PATH}" || exit 4 + +USAGE=$( cat << EOS +Usage: + $0 --regenerate + +Generates a new self-signed CA, for integration testing. This should only need +to be run if you are writing new TLS/SSL tests, and need to generate +additional fixtuer CAs. + +This script uses the GnuTLS certtool CLI. If you are on macOS, +'brew install gnutls', and it will be installed as 'gnutls-certtool'. +Apple unfortunately ships with an incompatible /usr/bin/certtool that does +different things. +EOS +) + +if [ "x$1" != 'x--regenerate' ]; then + echo "${USAGE}" + exit 1 +fi + +TOOL=`type -p certtool` +if [ "$(uname)" = "Darwin" ]; then + TOOL=`type -p gnutls-certtool` + if [ ! -x "${TOOL}" ]; then + echo "Sorry, Darwin requires gnutls-certtool; try `brew install gnutls`" + exit 2 + fi +fi + +if [ ! -x "${TOOL}" ]; then + echo "Sorry, no certtool found!" + exit 3 +fi +export TOOL + + +${TOOL} --generate-privkey > ./cakey.pem +${TOOL} --generate-self-signed \ + --load-privkey ./cakey.pem \ + --template ./ca.info \ + --outfile ./cacert.pem + +echo "cert and private key generated! Don't forget to check them in" diff --git a/script/install-openldap b/script/install-openldap index efb0cbaa..3e391d87 100755 --- a/script/install-openldap +++ b/script/install-openldap @@ -2,8 +2,8 @@ set -e set -x -BASE_PATH="$( cd `dirname $0`/../test/fixtures/openldap && pwd )" -SEED_PATH="$( cd `dirname $0`/../test/fixtures && pwd )" +BASE_PATH=$( cd "`dirname $0`/../test/fixtures/openldap" && pwd ) +SEED_PATH=$( cd "`dirname $0`/../test/fixtures" && pwd ) dpkg -s slapd time ldap-utils gnutls-bin ssl-cert > /dev/null ||\ DEBIAN_FRONTEND=noninteractive apt-get update -y --force-yes && \ @@ -48,47 +48,58 @@ chown -R openldap.openldap /var/lib/ldap rm -rf $TMPDIR # SSL +export CA_CERT="/usr/local/share/ca-certificates/rubyldap-ca.crt" +export CA_KEY="/etc/ssl/private/rubyldap-ca.key" -sh -c "certtool --generate-privkey > /etc/ssl/private/cakey.pem" +# The self-signed fixture CA cert & key are generated by +# `script/generate-fiuxture-ca` and checked into version control. +# You shouldn't need to muck with these unless you're writing more +# TLS/SSL integration tests, and need special magic values in the cert. -sh -c "cat > /etc/ssl/ca.info < /etc/ssl/ldap01.info <> /etc/ssl/ldap01.info +done + # Create the server certificate certtool --generate-certificate \ --load-privkey /etc/ssl/private/ldap01_slapd_key.pem \ - --load-ca-certificate /etc/ssl/certs/cacert.pem \ - --load-ca-privkey /etc/ssl/private/cakey.pem \ + --load-ca-certificate "${CA_CERT}" \ + --load-ca-privkey "${CA_KEY}" \ --template /etc/ssl/ldap01.info \ --outfile /etc/ssl/certs/ldap01_slapd_cert.pem ldapmodify -Y EXTERNAL -H ldapi:/// <> /etc/hosts +grep ldap02 /etc/hosts || echo "127.0.0.1 ldap02.example.com" >> /etc/hosts +grep bogus /etc/hosts || echo "127.0.0.1 bogus.example.com" >> /etc/hosts service slapd restart diff --git a/test/fixtures/ca/ca.info b/test/fixtures/ca/ca.info new file mode 100644 index 00000000..c0fd3629 --- /dev/null +++ b/test/fixtures/ca/ca.info @@ -0,0 +1,4 @@ +cn = rubyldap +ca +cert_signing_key +expiration_days = 7200 diff --git a/test/fixtures/ca/cacert.pem b/test/fixtures/ca/cacert.pem new file mode 100644 index 00000000..0218dd8a --- /dev/null +++ b/test/fixtures/ca/cacert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID7zCCAlegAwIBAgIMV7zWei6SNfABx6jMMA0GCSqGSIb3DQEBCwUAMBMxETAP +BgNVBAMTCHJ1YnlsZGFwMB4XDTE2MDgyMzIzMDQyNloXDTM2MDUxMDIzMDQyNlow +EzERMA8GA1UEAxMIcnVieWxkYXAwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGK +AoIBgQDGe9wziGHZJhIf+IEKSk1tpT9Mu7YgsUwjrlutvkoO1Q6K+amTAVDXizPf +1DVSDpZP5+CfBOznhgLMsPvrQ02w4qx5/6X9L+zJcMk8jTNYSKj5uIKpK52E7Uok +aygMXeaqroPONGkoJIZiVGgdbWfTvcffTm8FOhztXUbMrMXJNinFsocGHEoMNN8b +vqgAyG4+DFHoK4L0c6eQjE4nZBChieZdShUhaBpV7r2qSNbPw67cvAKuEzml58mV +1ZF1F73Ua8gPWXHEfUe2GEfG0NnRq6sGbsDYe/DIKxC7AZ89udZF3WZXNrPhvXKj +ZT7njwcMQemns4dNPQ0k2V4vAQ8pD8r8Qvb65FiSopUhVaGQswAnIMS1DnFq88AQ +KJTKIXbBuMwuaNNSs6R/qTS2RDk1w+CGpRXAg7+1SX5NKdrEsu1IaABA/tQ/zKKk +OLLJaD0giX1weBVmNeFcKxIoT34VS59eEt5APmPcguJnx+aBrA9TLzSO788apBN0 +4lGAmR0CAwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwQA +MB0GA1UdDgQWBBRTvXSkge03oqLu7UUjFI+oLYwnujANBgkqhkiG9w0BAQsFAAOC +AYEATSZQWH+uSN5GvOUvJ8LHWkeVovn0UhboK0K7GzmMeGz+dp/Xrj6eQ4ONK0zI +RCJyoo/nCR7CfQ5ujVXr03XD2SUgyD565ulXuhw336DasL5//fucmQYDeqhwbKML +FTzsF9H9dO4J5TjxJs7e5dRJ0wrP/XEY+WFhXXdSHTl8vGCI6QqWc7TvDpmbS4iX +uTzjJswu9Murt9JUJNMN2DlDi/vBBeruaj4c2cMMnKMvkfj14kd8wMocmzj+gVQl +r+fRQbKAJNec65lA4/Zeb6sD9SAi0ZIVgxA4a7g8/sdNWHIAxPicpJkIJf30TsyY +F+8+Hd5mBtCbvFfAVkT6bHBP1OiAgNke+Rh/j/sQbyWbKCKw0+jpFJgO9KUNGfC0 +O/CqX+J4G7HqL8VJqrLnBvOdhfetAvNQtf1gcw5ZwpeEFM+Kvx/lsILaIYdAUSjX +ePOc5gI2Bi9WXq+T9AuhSf+TWUR874m/rdTWe5fM8mXCNl7C4I5zCqLltEDkSoMP +jDj/ +-----END CERTIFICATE----- diff --git a/test/fixtures/ca/cakey.pem b/test/fixtures/ca/cakey.pem new file mode 100644 index 00000000..d75ab299 --- /dev/null +++ b/test/fixtures/ca/cakey.pem @@ -0,0 +1,190 @@ +Public Key Info: + Public Key Algorithm: RSA + Key Security Level: High (3072 bits) + +modulus: + 00:c6:7b:dc:33:88:61:d9:26:12:1f:f8:81:0a:4a:4d + 6d:a5:3f:4c:bb:b6:20:b1:4c:23:ae:5b:ad:be:4a:0e + d5:0e:8a:f9:a9:93:01:50:d7:8b:33:df:d4:35:52:0e + 96:4f:e7:e0:9f:04:ec:e7:86:02:cc:b0:fb:eb:43:4d + b0:e2:ac:79:ff:a5:fd:2f:ec:c9:70:c9:3c:8d:33:58 + 48:a8:f9:b8:82:a9:2b:9d:84:ed:4a:24:6b:28:0c:5d + e6:aa:ae:83:ce:34:69:28:24:86:62:54:68:1d:6d:67 + d3:bd:c7:df:4e:6f:05:3a:1c:ed:5d:46:cc:ac:c5:c9 + 36:29:c5:b2:87:06:1c:4a:0c:34:df:1b:be:a8:00:c8 + 6e:3e:0c:51:e8:2b:82:f4:73:a7:90:8c:4e:27:64:10 + a1:89:e6:5d:4a:15:21:68:1a:55:ee:bd:aa:48:d6:cf + c3:ae:dc:bc:02:ae:13:39:a5:e7:c9:95:d5:91:75:17 + bd:d4:6b:c8:0f:59:71:c4:7d:47:b6:18:47:c6:d0:d9 + d1:ab:ab:06:6e:c0:d8:7b:f0:c8:2b:10:bb:01:9f:3d + b9:d6:45:dd:66:57:36:b3:e1:bd:72:a3:65:3e:e7:8f + 07:0c:41:e9:a7:b3:87:4d:3d:0d:24:d9:5e:2f:01:0f + 29:0f:ca:fc:42:f6:fa:e4:58:92:a2:95:21:55:a1:90 + b3:00:27:20:c4:b5:0e:71:6a:f3:c0:10:28:94:ca:21 + 76:c1:b8:cc:2e:68:d3:52:b3:a4:7f:a9:34:b6:44:39 + 35:c3:e0:86:a5:15:c0:83:bf:b5:49:7e:4d:29:da:c4 + b2:ed:48:68:00:40:fe:d4:3f:cc:a2:a4:38:b2:c9:68 + 3d:20:89:7d:70:78:15:66:35:e1:5c:2b:12:28:4f:7e + 15:4b:9f:5e:12:de:40:3e:63:dc:82:e2:67:c7:e6:81 + ac:0f:53:2f:34:8e:ef:cf:1a:a4:13:74:e2:51:80:99 + 1d: + +public exponent: + 01:00:01: + +private exponent: + 1d:0d:9a:50:ec:c0:ad:e1:75:bb:ba:4b:61:2f:39:20 + 38:95:08:6d:5d:9e:71:75:5c:af:b3:f9:bd:a5:e7:7f + e6:4e:0f:77:73:ee:38:60:24:9f:26:3f:50:c2:bf:21 + df:76:68:99:be:45:d3:29:f9:94:ee:bf:21:53:cb:b6 + 7d:a7:93:80:09:53:03:45:dc:c2:a6:a2:37:64:f1:a2 + 49:21:ac:91:6b:a3:d7:bd:d2:62:0c:ec:a6:83:10:e7 + a7:ca:3d:be:dc:4b:1c:36:24:79:96:33:5b:43:5d:74 + 50:0e:46:b0:9b:6d:9f:71:06:89:a5:c8:65:ed:d9:a3 + 15:00:3c:3e:a9:75:50:9d:72:cb:c9:aa:e1:ba:a3:9c + 07:77:14:32:30:d4:4d:65:f4:7c:23:1d:79:84:9b:2e + 9a:19:df:43:ed:cd:e3:08:1f:d5:ff:6b:42:98:36:f7 + 44:cc:48:b4:f7:b8:16:b3:23:37:8d:b8:22:3f:8a:86 + db:71:b3:85:2d:6d:42:44:b7:dc:c1:36:e0:c4:0f:fe + cb:76:84:81:e2:83:f5:82:76:a9:7b:35:d5:44:00:d1 + 1a:fc:ef:b9:a4:2b:62:aa:f8:56:eb:60:e5:16:33:f1 + 28:e1:da:91:50:e3:a4:c7:d6:30:21:cf:04:07:cd:8c + b6:9e:b0:a7:6c:96:57:2e:09:5b:39:26:d0:60:be:e3 + 90:59:a3:8e:e7:6e:3f:62:7e:b4:2a:e1:8f:00:37:7a + 83:9e:7a:9c:d2:ae:ba:50:84:73:65:3a:64:95:d8:48 + f9:fd:0e:c3:5b:6e:08:3b:c5:c9:1c:29:55:bb:67:e8 + fa:50:40:30:2a:d1:b7:cf:54:a8:f0:f0:76:89:ad:19 + e7:a0:3a:56:6c:75:c5:bc:d8:46:ce:1e:66:f2:61:96 + 11:e4:57:cc:52:ff:e4:ed:6b:2c:ce:78:15:ba:b7:ed + 31:f2:68:88:79:bf:7c:29:3c:2f:66:71:0b:09:b7:41 + + +prime1: + 00:fd:c2:37:b9:6f:77:88:51:a2:f7:4f:c2:3c:a4:57 + bf:ba:71:14:f3:61:f4:39:78:22:3d:bc:d8:d2:4e:c0 + 4b:9e:c2:6d:38:a8:21:e2:70:1a:96:48:95:18:85:01 + 46:fb:62:a4:81:09:f8:2a:3a:87:78:07:5d:93:54:ce + 2a:51:b3:51:6f:61:0a:2e:9d:b0:51:37:e3:13:bd:81 + 23:2b:61:53:fa:ac:08:dc:a0:e6:63:a3:b0:cc:cf:73 + 1d:65:b7:11:bc:29:70:fb:72:ea:63:9d:67:02:d6:35 + 24:13:1d:bc:72:fb:9e:3d:ab:0b:57:6e:bd:a1:51:56 + f9:bc:96:15:74:a3:31:16:c6:b8:98:1b:0a:a2:59:7c + c8:b7:14:b8:5b:f3:2e:26:b4:f0:46:c4:3d:27:dd:41 + 31:52:a7:15:a8:af:6a:98:a5:9c:20:17:f9:1d:54:54 + ff:10:91:a3:a5:ca:ac:63:e7:16:2b:71:3c:3a:cd:4f + ed: + +prime2: + 00:c8:3c:a8:9f:8a:db:42:b5:8d:cf:2a:a1:2f:e5:73 + 05:de:30:d8:17:b9:5c:9d:08:60:02:c9:66:9d:88:50 + ac:cd:0f:b5:47:b4:a8:73:3b:7d:65:79:bf:4c:6f:d0 + e2:03:ed:d4:28:4e:00:07:23:00:01:4f:05:de:9b:44 + 1a:84:ae:09:4a:d6:ed:61:5d:77:e2:fa:13:99:4c:b7 + 76:72:3d:f8:53:93:69:78:e8:bd:26:cb:b0:f9:01:f4 + 1d:20:4f:60:f5:ab:3c:19:85:73:34:f3:ec:d2:67:ef + 56:b8:5d:93:73:8e:d9:3e:28:ff:87:f5:4a:26:fa:b1 + ae:c6:d3:9d:03:e3:fd:c2:24:48:af:85:2a:8e:3b:5b + 93:07:38:91:21:ae:49:cb:6d:e3:30:81:15:ed:65:eb + dc:01:df:3b:9d:43:fd:a6:e1:df:ef:ad:22:42:34:f1 + 3f:81:5e:57:0a:e0:56:94:f2:2a:00:d0:cc:c5:50:67 + f1: + +coefficient: + 00:bd:23:8c:2e:a7:7b:6b:1e:85:77:db:7d:77:f6:e5 + b0:15:c6:e1:9e:35:57:72:df:35:6d:93:89:7f:83:9f + 63:7f:08:0a:b3:d4:ba:63:9b:10:7f:0f:d3:55:e9:38 + cf:90:37:3d:85:3d:a7:97:8c:33:f2:c2:b1:38:2b:db + 39:ca:a8:d0:23:d7:89:cc:8d:02:7d:61:9b:b6:04:69 + 14:e8:c9:84:34:36:6c:fb:84:58:cc:9a:53:74:a4:42 + bd:1d:25:1b:ba:82:c0:fb:23:2c:90:bb:35:4b:5b:b0 + 98:d0:ab:9d:61:6e:ea:e8:84:e7:a7:6c:ae:1b:2c:00 + cb:0f:1a:f8:e2:7c:fd:42:1a:e2:13:52:c7:50:fa:65 + c9:5f:ed:40:a8:7f:46:0e:ce:f6:56:83:6f:0e:8e:39 + f8:33:5f:83:de:be:be:ef:8c:66:ad:16:c8:ec:98:d4 + b2:b2:55:66:a2:9e:27:6a:84:f1:31:07:e8:bf:a7:a7 + bd: + +exp1: + 00:b6:50:0c:53:19:07:8b:14:03:fe:a4:fa:0b:31:93 + ad:b7:18:b9:91:a6:c5:9d:68:77:49:5d:dd:75:33:89 + 2a:8b:54:6a:be:32:e5:ad:57:17:72:f3:90:d2:fd:f4 + 0d:f8:5c:45:8e:44:08:5c:e6:92:1f:a5:43:10:af:f4 + 33:29:61:a8:d7:59:a3:c4:1c:1c:ea:2d:39:e3:1b:da + a4:d6:ec:e5:36:0a:d5:8f:15:b6:90:cd:b1:1f:64:c7 + f2:cd:fa:3a:2e:b2:a3:6e:b4:80:3b:b3:81:a7:e3:18 + 68:e3:a7:10:96:97:ba:77:d9:e4:9b:1b:7f:f8:5f:85 + 1a:85:e8:5a:5f:e3:43:48:76:db:76:c4:ae:de:37:66 + d4:99:dc:b4:1b:b3:da:6b:8a:c1:ba:46:11:1e:0b:f3 + 63:a9:5b:4b:cf:56:c0:42:0d:71:df:08:fa:3c:9d:33 + 37:d1:c2:a1:0d:63:50:79:b2:34:16:60:13:82:b7:b1 + 7d: + +exp2: + 00:98:38:2c:c4:24:4e:2c:b7:52:17:a4:43:a6:e2:99 + ff:62:fa:e4:bb:9c:49:40:83:66:61:97:f3:af:5c:3a + 60:32:ff:77:03:0c:de:65:c3:5a:bf:72:bf:2f:7f:6d + 5e:f4:37:af:69:f8:69:e3:03:03:74:fb:3a:ee:10:40 + c4:9c:0a:a5:bb:c4:09:ef:53:9b:d8:eb:dd:4c:53:da + c0:6b:76:9a:ba:06:3d:4f:12:37:01:30:25:d8:16:59 + 1a:6f:3e:88:ea:19:83:75:af:52:76:75:dc:99:d3:33 + 4a:4c:9b:ae:85:51:99:ea:bc:46:0d:78:36:27:cd:ba + 97:b0:44:9c:7f:a1:a9:7e:16:11:3f:85:4f:65:92:d0 + 39:c4:6a:87:42:00:79:ce:f1:39:9d:dc:f3:eb:65:e8 + d8:76:7f:da:94:e2:64:08:a2:7b:97:7b:99:a8:95:10 + b5:03:46:d1:8a:ce:22:63:d6:78:81:e8:39:52:e2:9e + 31: + + +Public Key ID: 53:BD:74:A4:81:ED:37:A2:A2:EE:ED:45:23:14:8F:A8:2D:8C:27:BA +Public key's random art: ++--[ RSA 3072]----+ +| . o. . | +| . +...+ | +| . o o.+ . | +| o o . . .ooo | +| o = . S o..o . | +| . o . .+.. | +|. . .. | +| . .. . | +|E oo.o | ++-----------------+ + +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/cacert.pem b/test/fixtures/cacert.pem deleted file mode 100644 index f8b134e1..00000000 --- a/test/fixtures/cacert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDRzCCAf+gAwIBAgIEVHpbmjANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhy -dWJ5bGRhcDAeFw0xNDExMjkyMzQ5NDZaFw0xNTExMjkyMzQ5NDZaMBMxETAPBgNV -BAMTCHJ1YnlsZGFwMIIBUjANBgkqhkiG9w0BAQEFAAOCAT8AMIIBOgKCATEA4pKe -cDCNuL53fkpO/WSAS+gmMTsOs+oOK71kZlk2QT/MBz8TxC6m358qCADjnXcMVVxa -ySQbQlVKZMkIvLNciZbiLDgC5II0NbHACNa8rqenoKRjS4J9W3OhA8EmnXn/Me+8 -uMCI9tfnKNRZYdkQZlra4I+Idn+xYfl/5q5b/7ZjPS2zY/585hFEYE+5vfOZVBSU -3HMNSeuJvTehLv7dD7aQfXNM4cRgHXequkJQ/HLLFAO4AgJ+LJrFWpj7GWz3crgr -9G5px4T78wJH3NQiOsG6UBXPw8c4T+Z6GAWX2l1zs1gZsaiCVbAraqK3404lL7yp -+ThbsW3ifzgNPhmjScXBLdbEDrrAKosW7kkTOGzxiMCBmNlj2SKhcztoduAtfF1f -Fs2Jk8MRTHwO8ThD7wIDAQABo0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1UdDwEB -/wQFAwMHBAAwHQYDVR0OBBYEFJDm67ekyFu4/Z7VcO6Vk/5pinGcMA0GCSqGSIb3 -DQEBCwUAA4IBMQDHeEPzfYRtjynpUKyrtxx/6ZVOfCLuz4eHkBZggz/pJacDCv/a -I//W03XCk8RWq/fWVVUzvxXgPwnYcw992PLM7XW81zp6ruRUDWooYnjHZZz3bRhe -kC4QvM2mZhcsMVmhmWWKZn81qXgVdUY1XNRhk87cuXjF/UTpEieFvWAsCUkFZkqB -AmySCuI/FuPaauT1YAltkIlYAEIGNJGZDMf2BTVUQpXhTXeS9/AZWLNDBwiq+fwo -YYnsr9MnBXCEmg1gVSR/Ay2AZmbYfiYtb5kU8uq2lSWAUb4LX6HZl82wo3OilrJ2 -WXl6Qf+Fcy4qqkRt4AKHjtzizpEDCOVYuuG0Zoy+QnxNXRsEzpb8ymnJFrcgYfk/ -6Lv2gWAFl5FqCZp7gBWg55eL2coT4C+mbNTF ------END CERTIFICATE----- diff --git a/test/integration/test_bind.rb b/test/integration/test_bind.rb index b7fa35bc..bd1281e2 100644 --- a/test/integration/test_bind.rb +++ b/test/integration/test_bind.rb @@ -2,19 +2,23 @@ class TestBindIntegration < LDAPIntegrationTestCase def test_bind_success - assert @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1"), @ldap.get_operation_result.inspect + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect end def test_bind_timeout @ldap.port = 8389 error = assert_raise Net::LDAP::Error do - @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1") + @ldap.bind BIND_CREDS end - assert_equal('Connection timed out - user specified timeout', error.message) + msgs = ['Operation timed out - user specified timeout', + 'Connection timed out - user specified timeout'] + assert_send([msgs, :include?, error.message]) end def test_bind_anonymous_fail - refute @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: ""), @ldap.get_operation_result.inspect + refute @ldap.bind(BIND_CREDS.merge(password: '')), + @ldap.get_operation_result.inspect result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeUnwillingToPerform, result.code @@ -25,18 +29,216 @@ def test_bind_anonymous_fail end def test_bind_fail - refute @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "not my password"), @ldap.get_operation_result.inspect + refute @ldap.bind(BIND_CREDS.merge(password: "not my password")), + @ldap.get_operation_result.inspect end def test_bind_tls_with_cafile - tls_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge(:ca_file => CA_FILE) - @ldap.encryption(method: :start_tls, tls_options: tls_options) - assert @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1"), @ldap.get_operation_result.inspect + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(ca_file: CA_FILE), + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect end - def test_bind_tls_with_verify_none - tls_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge(:verify_mode => OpenSSL::SSL::VERIFY_NONE) - @ldap.encryption(method: :start_tls, tls_options: tls_options) - assert @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1"), @ldap.get_operation_result.inspect + def test_bind_tls_with_bad_hostname_verify_none_no_ca_passes + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_NONE }, + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + def test_bind_tls_with_bad_hostname_verify_none_no_ca_opt_merge_passes + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_NONE), + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + def test_bind_tls_with_bad_hostname_verify_peer_ca_fails + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER, + ca_file: CA_FILE }, + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError do + @ldap.bind BIND_CREDS + end + assert_equal( + "hostname \"#{@ldap.host}\" does not match the server certificate", + error.message, + ) + end + + def test_bind_tls_with_bad_hostname_ca_default_opt_merge_fails + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(ca_file: CA_FILE), + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError do + @ldap.bind BIND_CREDS + end + assert_equal( + "hostname \"#{@ldap.host}\" does not match the server certificate", + error.message, + ) + end + + def test_bind_tls_with_bad_hostname_ca_no_opt_merge_fails + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: { ca_file: CA_FILE }, + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError do + @ldap.bind BIND_CREDS + end + assert_equal( + "hostname \"#{@ldap.host}\" does not match the server certificate", + error.message, + ) + end + + def test_bind_tls_with_valid_hostname_default_opts_passes + @ldap.host = 'localhost' + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_PEER, + ca_file: CA_FILE), + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + def test_bind_tls_with_valid_hostname_just_verify_peer_ca_passes + @ldap.host = 'localhost' + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER, + ca_file: CA_FILE }, + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + def test_bind_tls_with_bogus_hostname_system_ca_fails + @ldap.host = '127.0.0.1' + @ldap.encryption(method: :start_tls, tls_options: {}) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError do + @ldap.bind BIND_CREDS + end + assert_equal( + "hostname \"#{@ldap.host}\" does not match the server certificate", + error.message, + ) + end + + # The following depend on /etc/hosts hacking. + # We can do that on CI, but it's less than cool on people's dev boxes + def test_bind_tls_with_multiple_hosts + omit_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['ldap01.example.com', 389], ['ldap02.example.com', 389]] + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_PEER, + ca_file: CA_FILE), + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + def test_bind_tls_with_multiple_bogus_hosts + omit_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['127.0.0.1', 389], ['bogus.example.com', 389]] + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_PEER, + ca_file: CA_FILE), + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionError do + @ldap.bind BIND_CREDS + end + assert_equal("Unable to connect to any given server: ", + error.message.split("\n").shift) + end + + def test_bind_tls_with_multiple_bogus_hosts_no_verification + omit_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['127.0.0.1', 389], ['bogus.example.com', 389]] + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_NONE), + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + def test_bind_tls_with_multiple_bogus_hosts_ca_check_only_fails + omit_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['127.0.0.1', 389], ['bogus.example.com', 389]] + @ldap.encryption( + method: :start_tls, + tls_options: { ca_file: CA_FILE }, + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionError do + @ldap.bind BIND_CREDS + end + assert_equal("Unable to connect to any given server: ", + error.message.split("\n").shift) + end + + # This test is CI-only because we can't add the fixture CA + # to the system CA store on people's dev boxes. + def test_bind_tls_valid_hostname_system_ca_on_travis_passes + omit_unless ENV['TRAVIS'] == 'true' + + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER }, + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + # Inverse of the above! Don't run this on Travis, only on Vagrant. + # Since Vagrant's hypervisor *won't* have the CA in the system + # x509 store, we can assume validation will fail + def test_bind_tls_valid_hostname_system_on_vagrant_fails + omit_if ENV['TRAVIS'] == 'true' + + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER }, + ) + error = assert_raise Net::LDAP::Error do + @ldap.bind BIND_CREDS + end + assert_equal( + "SSL_connect returned=1 errno=0 state=error: certificate verify failed", + error.message, + ) end end diff --git a/test/support/vm/openldap/README.md b/test/support/vm/openldap/README.md index a2769567..f79f4dc6 100644 --- a/test/support/vm/openldap/README.md +++ b/test/support/vm/openldap/README.md @@ -1,8 +1,31 @@ # Local OpenLDAP Integration Testing -Set up a [Vagrant](http://www.vagrantup.com/) VM to run integration tests against OpenLDAP locally. +Set up a [Vagrant](http://www.vagrantup.com/) VM to run integration +tests against OpenLDAP locally. *NOTE*: To support some of the SSL tests, +Vagrant forwards localhost port 9389 to VM host port 9389. The port mapping +goes away when you run `vagrant destroy`. -To run integration tests locally: +## Install Vagrant + +*NOTE*: The Vagrant gem (`gem install vagrant`) is +[no longer supported](https://www.vagrantup.com/docs/installation/). If you've +previously installed it, run `gem uninstall vagrant`. If you're an rbenv +user, you probably want to follow that up with `rbenv rehash; hash -r`. + +If you use Homebrew on macOS: +``` bash +$ brew update +$ brew cask install virtualbox +$ brew cask install vagrant +$ brew cask install vagrant-manager +$ vagrant plugin install vagrant-vbguest +``` + +Installing Vagrant and virtualbox on other operating systems is left +as an exercise to the reader. Note the `vagrant-vbguest` plugin is required +to update the VirtualBox guest extensions in the guest VM image. + +## Run the tests ``` bash # start VM (from the correct directory) @@ -15,6 +38,9 @@ $ ip=$(vagrant ssh -- "ifconfig eth1 | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+\.[0-9] # change back to root project directory $ cd ../../../.. +# set the TCP port for testing +$ export INTEGRATION_PORT=9389 + # run all tests, including integration tests $ time INTEGRATION=openldap INTEGRATION_HOST=$ip bundle exec rake @@ -27,6 +53,12 @@ $ export INTEGRATION_HOST=$ip # now run tests without having to set ENV variables $ time bundle exec rake + +# Once you're all done +$ cd test/support/vm/openldap +$ vagrant destroy ``` -You may need to `gem install vagrant` first in order to provision the VM. +If at any point your VM appears to have broken itself, `vagrant destroy` +from the `test/support/vm/openldap` directory will blow it away. You can +then do `vagrant up` and start over. diff --git a/test/support/vm/openldap/Vagrantfile b/test/support/vm/openldap/Vagrantfile index 96233e92..1f375e76 100644 --- a/test/support/vm/openldap/Vagrantfile +++ b/test/support/vm/openldap/Vagrantfile @@ -10,6 +10,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" config.vm.network "private_network", type: :dhcp + config.vm.network "forwarded_port", guest: 389, host: 9389 config.ssh.forward_agent = true diff --git a/test/test_helper.rb b/test/test_helper.rb index cd34017c..0a976be4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -14,10 +14,18 @@ if File.exist?("/etc/ssl/certs/cacert.pem") "/etc/ssl/certs/cacert.pem" else - File.expand_path("fixtures/cacert.pem", File.dirname(__FILE__)) + File.expand_path("fixtures/ca/cacert.pem", File.dirname(__FILE__)) end end +BIND_CREDS = { + method: :simple, + username: "uid=user1,ou=People,dc=rubyldap,dc=com", + password: "passworD1", +}.freeze + +TLS_OPTS = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge({}).freeze + if RUBY_VERSION < "2.0" class String def b