net-ldap 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of net-ldap might be problematic. Click here for more details.

@@ -1,3 +1,9 @@
1
+ === Net::LDAP 0.16.0
2
+
3
+ * Sasl fix {#281}[https://github.com/ruby-ldap/ruby-net-ldap/pull/281]
4
+ * enable TLS hostname validation {#279}[https://github.com/ruby-ldap/ruby-net-ldap/pull/279]
5
+ * update rubocop to 0.42.0 {#278}[https://github.com/ruby-ldap/ruby-net-ldap/pull/278]
6
+
1
7
  === Net::LDAP 0.15.0
2
8
 
3
9
  * Respect connect_timeout when establishing SSL connections {#273}[https://github.com/ruby-ldap/ruby-net-ldap/pull/273]
@@ -52,12 +52,10 @@ This task will run the test suite and the
52
52
 
53
53
  rake rubotest
54
54
 
55
- To run the integration tests against an LDAP server:
56
-
57
- cd test/support/vm/openldap
58
- vagrant up
59
- cd ../../../..
60
- INTEGRATION=openldap bundle exec rake rubotest
55
+ CI takes too long? If your local box supports
56
+ {Vagrant}[https://www.vagrantup.com/], you can run most of the tests
57
+ in a VM on your local box. For more details and setup instructions, see
58
+ {test/support/vm/openldap/README.md}[https://github.com/ruby-ldap/ruby-net-ldap/tree/master/test/support/vm/openldap/README.md]
61
59
 
62
60
  == Release
63
61
 
@@ -327,11 +327,10 @@ class Net::BER::BerIdentifiedString < String
327
327
  # Check the encoding of the newly created String and set the encoding
328
328
  # to 'UTF-8' (NOTE: we do NOT change the bytes, but only set the
329
329
  # encoding to 'UTF-8').
330
+ return unless encoding == Encoding::BINARY
330
331
  current_encoding = encoding
331
- if current_encoding == Encoding::BINARY
332
- force_encoding('UTF-8')
333
- force_encoding(current_encoding) unless valid_encoding?
334
- end
332
+ force_encoding('UTF-8')
333
+ force_encoding(current_encoding) unless valid_encoding?
335
334
  end
336
335
  end
337
336
 
@@ -172,10 +172,10 @@ module Net::BER::BERParser
172
172
  yield id, content_length if block_given?
173
173
 
174
174
  if -1 == content_length
175
- raise Net::BER::BerError, "Indeterminite BER content length not implemented."
176
- else
177
- data = read(content_length)
175
+ raise Net::BER::BerError,
176
+ "Indeterminite BER content length not implemented."
178
177
  end
178
+ data = read(content_length)
179
179
 
180
180
  parse_ber_object(syntax, id, data)
181
181
  end
@@ -476,61 +476,73 @@ class Net::LDAP
476
476
  # specify a treebase. If you give a treebase value in any particular
477
477
  # call to #search, that value will override any treebase value you give
478
478
  # here.
479
+ # * :force_no_page => Set to true to prevent paged results even if your
480
+ # server says it supports them. This is a fix for MS Active Directory
481
+ # * :instrumentation_service => An object responsible for instrumenting
482
+ # operations, compatible with ActiveSupport::Notifications' public API.
479
483
  # * :encryption => specifies the encryption to be used in communicating
480
484
  # with the LDAP server. The value must be a Hash containing additional
481
485
  # parameters, which consists of two keys:
482
486
  # method: - :simple_tls or :start_tls
483
- # options: - Hash of options for that method
487
+ # tls_options: - Hash of options for that method
484
488
  # The :simple_tls encryption method encrypts <i>all</i> communications
485
489
  # with the LDAP server. It completely establishes SSL/TLS encryption with
486
490
  # the LDAP server before any LDAP-protocol data is exchanged. There is no
487
491
  # plaintext negotiation and no special encryption-request controls are
488
492
  # sent to the server. <i>The :simple_tls option is the simplest, easiest
489
493
  # way to encrypt communications between Net::LDAP and LDAP servers.</i>
490
- # It's intended for cases where you have an implicit level of trust in the
491
- # authenticity of the LDAP server. No validation of the LDAP server's SSL
492
- # certificate is performed. This means that :simple_tls will not produce
493
- # errors if the LDAP server's encryption certificate is not signed by a
494
- # well-known Certification Authority. If you get communications or
495
- # protocol errors when using this option, check with your LDAP server
496
- # administrator. Pay particular attention to the TCP port you are
497
- # connecting to. It's impossible for an LDAP server to support plaintext
498
- # LDAP communications and <i>simple TLS</i> connections on the same port.
499
- # The standard TCP port for unencrypted LDAP connections is 389, but the
500
- # standard port for simple-TLS encrypted connections is 636. Be sure you
501
- # are using the correct port.
502
- #
494
+ # If you get communications or protocol errors when using this option,
495
+ # check with your LDAP server administrator. Pay particular attention
496
+ # to the TCP port you are connecting to. It's impossible for an LDAP
497
+ # server to support plaintext LDAP communications and <i>simple TLS</i>
498
+ # connections on the same port. The standard TCP port for unencrypted
499
+ # LDAP connections is 389, but the standard port for simple-TLS
500
+ # encrypted connections is 636. Be sure you are using the correct port.
503
501
  # The :start_tls like the :simple_tls encryption method also encrypts all
504
502
  # communcations with the LDAP server. With the exception that it operates
505
503
  # over the standard TCP port.
506
504
  #
507
- # In order to verify certificates and enable other TLS options, the
508
- # :tls_options hash can be passed alongside :simple_tls or :start_tls.
509
- # This hash contains any options that can be passed to
510
- # OpenSSL::SSL::SSLContext#set_params(). The most common options passed
511
- # should be OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, or the :ca_file option,
512
- # which contains a path to a Certificate Authority file (PEM-encoded).
513
- #
514
- # Example for a default setup without custom settings:
515
- # {
516
- # :method => :simple_tls,
517
- # :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
518
- # }
519
- #
520
- # Example for specifying a CA-File and only allowing TLSv1.1 connections:
505
+ # To validate the LDAP server's certificate (a security must if you're
506
+ # talking over the public internet), you need to set :tls_options
507
+ # something like this...
521
508
  #
522
- # {
523
- # :method => :start_tls,
524
- # :tls_options => { :ca_file => "/etc/cafile.pem", :ssl_version => "TLSv1_1" }
509
+ # Net::LDAP.new(
510
+ # # ... set host, bind dn, etc ...
511
+ # encryption: {
512
+ # method: :simple_tls,
513
+ # tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
525
514
  # }
526
- # * :force_no_page => Set to true to prevent paged results even if your
527
- # server says it supports them. This is a fix for MS Active Directory
528
- # * :instrumentation_service => An object responsible for instrumenting
529
- # operations, compatible with ActiveSupport::Notifications' public API.
515
+ # )
516
+ #
517
+ # The above will use the operating system-provided store of CA
518
+ # certificates to validate your LDAP server's cert.
519
+ # If cert validation fails, it'll happen during the #bind
520
+ # whenever you first try to open a connection to the server.
521
+ # Those methods will throw Net::LDAP::ConnectionError with
522
+ # a message about certificate verify failing. If your
523
+ # LDAP server's certificate is signed by DigiCert, Comodo, etc.,
524
+ # you're probably good. If you've got a self-signed cert but it's
525
+ # been added to the host's OS-maintained CA store (e.g. on Debian
526
+ # add foobar.crt to /usr/local/share/ca-certificates/ and run
527
+ # `update-ca-certificates`), then the cert should pass validation.
528
+ # To ignore the OS's CA store, put your CA in a PEM-encoded file and...
529
+ #
530
+ # encryption: {
531
+ # method: :simple_tls,
532
+ # tls_options: { ca_file: '/path/to/my-little-ca.pem',
533
+ # ssl_version: 'TLSv1_1' },
534
+ # }
535
+ #
536
+ # As you might guess, the above example also fails the connection
537
+ # if the client can't negotiate TLS v1.1.
538
+ # tls_options is ultimately passed to OpenSSL::SSL::SSLContext#set_params
539
+ # For more details, see
540
+ # http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
530
541
  #
531
542
  # Instantiating a Net::LDAP object does <i>not</i> result in network
532
543
  # traffic to the LDAP server. It simply stores the connection and binding
533
- # parameters in the object.
544
+ # parameters in the object. That's why Net::LDAP.new doesn't throw
545
+ # cert validation errors itself; #bind does instead.
534
546
  def initialize(args = {})
535
547
  @host = args[:host] || DefaultHost
536
548
  @port = args[:port] || DefaultPort
@@ -1286,11 +1298,9 @@ class Net::LDAP
1286
1298
  else
1287
1299
  begin
1288
1300
  conn = new_connection
1289
- if (result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess
1290
- yield conn
1291
- else
1292
- return result
1293
- end
1301
+ result = conn.bind(args[:auth] || @auth)
1302
+ return result unless result.result_code == Net::LDAP::ResultCodeSuccess
1303
+ yield conn
1294
1304
  ensure
1295
1305
  conn.close if conn
1296
1306
  end
@@ -4,6 +4,8 @@ module Net
4
4
  class LDAP
5
5
  class AuthAdapter
6
6
  class Sasl < Net::LDAP::AuthAdapter
7
+ MAX_SASL_CHALLENGES = 10
8
+
7
9
  #--
8
10
  # Required parameters: :mechanism, :initial_credential and
9
11
  # :challenge_response
@@ -47,7 +49,7 @@ module Net
47
49
  end
48
50
 
49
51
  return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress
50
- raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
52
+ raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MAX_SASL_CHALLENGES)
51
53
 
52
54
  cred = chall.call(pdu.result_server_sasl_creds)
53
55
  end
@@ -7,7 +7,6 @@ class Net::LDAP::Connection #:nodoc:
7
7
  DefaultConnectTimeout = 5
8
8
 
9
9
  LdapVersion = 3
10
- MaxSaslChallenges = 10
11
10
 
12
11
  # Initialize a connection to an LDAP server
13
12
  #
@@ -52,6 +51,15 @@ class Net::LDAP::Connection #:nodoc:
52
51
  hosts.each do |host, port|
53
52
  begin
54
53
  prepare_socket(server.merge(socket: @socket_class.new(host, port, socket_opts)), timeout)
54
+ if encryption
55
+ if encryption[:tls_options] &&
56
+ encryption[:tls_options][:verify_mode] &&
57
+ encryption[:tls_options][:verify_mode] == OpenSSL::SSL::VERIFY_NONE
58
+ warn "not verifying SSL hostname of LDAPS server '#{host}:#{port}'"
59
+ else
60
+ @conn.post_connection_check(host)
61
+ end
62
+ end
55
63
  return
56
64
  rescue Net::LDAP::Error, SocketError, SystemCallError,
57
65
  OpenSSL::SSL::SSLError => e
@@ -95,17 +103,13 @@ class Net::LDAP::Connection #:nodoc:
95
103
  conn.connect
96
104
  end
97
105
  rescue IO::WaitReadable
98
- if IO.select([conn], nil, nil, timeout)
99
- retry
100
- else
101
- raise Errno::ETIMEDOUT, "OpenSSL connection read timeout"
102
- end
106
+ raise Errno::ETIMEDOUT, "OpenSSL connection read timeout" unless
107
+ IO.select([conn], nil, nil, timeout)
108
+ retry
103
109
  rescue IO::WaitWritable
104
- if IO.select(nil, [conn], nil, timeout)
105
- retry
106
- else
107
- raise Errno::ETIMEDOUT, "OpenSSL connection write timeout"
108
- end
110
+ raise Errno::ETIMEDOUT, "OpenSSL connection write timeout" unless
111
+ IO.select(nil, [conn], nil, timeout)
112
+ retry
109
113
  end
110
114
 
111
115
  # Doesn't work:
@@ -163,11 +167,9 @@ class Net::LDAP::Connection #:nodoc:
163
167
  raise Net::LDAP::NoStartTLSResultError, "no start_tls result"
164
168
  end
165
169
 
166
- if pdu.result_code.zero?
167
- @conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout)
168
- else
169
- raise Net::LDAP::StartTLSError, "start_tls failed: #{pdu.result_code}"
170
- end
170
+ raise Net::LDAP::StartTLSError,
171
+ "start_tls failed: #{pdu.result_code}" unless pdu.result_code.zero?
172
+ @conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout)
171
173
  else
172
174
  raise Net::LDAP::EncMethodUnsupportedError, "unsupported encryption method #{args[:method]}"
173
175
  end
@@ -197,12 +199,10 @@ class Net::LDAP::Connection #:nodoc:
197
199
 
198
200
  # read messages until we have a match for the given message_id
199
201
  while pdu = read
200
- if pdu.message_id == message_id
201
- return pdu
202
- else
203
- message_queue[pdu.message_id].push pdu
204
- next
205
- end
202
+ return pdu if pdu.message_id == message_id
203
+
204
+ message_queue[pdu.message_id].push pdu
205
+ next
206
206
  end
207
207
 
208
208
  pdu
@@ -400,12 +400,11 @@ class Net::LDAP::Connection #:nodoc:
400
400
  # should collect this into a private helper to clarify the structure
401
401
  query_limit = 0
402
402
  if size > 0
403
- if paged
404
- query_limit = (((size - n_results) < 126) ? (size -
405
- n_results) : 0)
406
- else
407
- query_limit = size
408
- end
403
+ query_limit = if paged
404
+ (((size - n_results) < 126) ? (size - n_results) : 0)
405
+ else
406
+ size
407
+ end
409
408
  end
410
409
 
411
410
  request = [
@@ -169,11 +169,10 @@ class Net::LDAP::DN
169
169
  end
170
170
 
171
171
  # Last pair
172
- if [:value, :value_normal, :value_hexstring, :value_end].include? state
173
- yield key.string.strip, value.string.rstrip
174
- else
175
- raise "DN badly formed"
176
- end
172
+ raise "DN badly formed" unless
173
+ [:value, :value_normal, :value_hexstring, :value_end].include? state
174
+
175
+ yield key.string.strip, value.string.rstrip
177
176
  end
178
177
 
179
178
  ##
@@ -140,11 +140,10 @@ class Net::LDAP::Entry
140
140
  # arguments to the block: a Symbol giving the name of the attribute, and a
141
141
  # (possibly empty) \Array of data values.
142
142
  def each # :yields: attribute-name, data-values-array
143
- if block_given?
144
- attribute_names.each do|a|
145
- attr_name, values = a, self[a]
146
- yield attr_name, values
147
- end
143
+ return unless block_given?
144
+ attribute_names.each do|a|
145
+ attr_name, values = a, self[a]
146
+ yield attr_name, values
148
147
  end
149
148
  end
150
149
  alias_method :each_attribute, :each
@@ -1,5 +1,5 @@
1
1
  module Net
2
2
  class LDAP
3
- VERSION = "0.15.0"
3
+ VERSION = "0.16.0"
4
4
  end
5
5
  end
@@ -31,7 +31,7 @@ the most recent LDAP RFCs (4510-4519, plutions of 4520-4532).}
31
31
 
32
32
  s.add_development_dependency("flexmock", "~> 1.3")
33
33
  s.add_development_dependency("rake", "~> 10.0")
34
- s.add_development_dependency("rubocop", "~> 0.28.0")
34
+ s.add_development_dependency("rubocop", "~> 0.42.0")
35
35
  s.add_development_dependency("test-unit")
36
36
  s.add_development_dependency("byebug")
37
37
  end
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+
3
+ BASE_PATH=$( cd "`dirname $0`/../test/fixtures/ca" && pwd )
4
+ cd "${BASE_PATH}" || exit 4
5
+
6
+ USAGE=$( cat << EOS
7
+ Usage:
8
+ $0 --regenerate
9
+
10
+ Generates a new self-signed CA, for integration testing. This should only need
11
+ to be run if you are writing new TLS/SSL tests, and need to generate
12
+ additional fixtuer CAs.
13
+
14
+ This script uses the GnuTLS certtool CLI. If you are on macOS,
15
+ 'brew install gnutls', and it will be installed as 'gnutls-certtool'.
16
+ Apple unfortunately ships with an incompatible /usr/bin/certtool that does
17
+ different things.
18
+ EOS
19
+ )
20
+
21
+ if [ "x$1" != 'x--regenerate' ]; then
22
+ echo "${USAGE}"
23
+ exit 1
24
+ fi
25
+
26
+ TOOL=`type -p certtool`
27
+ if [ "$(uname)" = "Darwin" ]; then
28
+ TOOL=`type -p gnutls-certtool`
29
+ if [ ! -x "${TOOL}" ]; then
30
+ echo "Sorry, Darwin requires gnutls-certtool; try `brew install gnutls`"
31
+ exit 2
32
+ fi
33
+ fi
34
+
35
+ if [ ! -x "${TOOL}" ]; then
36
+ echo "Sorry, no certtool found!"
37
+ exit 3
38
+ fi
39
+ export TOOL
40
+
41
+
42
+ ${TOOL} --generate-privkey > ./cakey.pem
43
+ ${TOOL} --generate-self-signed \
44
+ --load-privkey ./cakey.pem \
45
+ --template ./ca.info \
46
+ --outfile ./cacert.pem
47
+
48
+ echo "cert and private key generated! Don't forget to check them in"
@@ -2,8 +2,8 @@
2
2
  set -e
3
3
  set -x
4
4
 
5
- BASE_PATH="$( cd `dirname $0`/../test/fixtures/openldap && pwd )"
6
- SEED_PATH="$( cd `dirname $0`/../test/fixtures && pwd )"
5
+ BASE_PATH=$( cd "`dirname $0`/../test/fixtures/openldap" && pwd )
6
+ SEED_PATH=$( cd "`dirname $0`/../test/fixtures" && pwd )
7
7
 
8
8
  dpkg -s slapd time ldap-utils gnutls-bin ssl-cert > /dev/null ||\
9
9
  DEBIAN_FRONTEND=noninteractive apt-get update -y --force-yes && \
@@ -48,47 +48,58 @@ chown -R openldap.openldap /var/lib/ldap
48
48
  rm -rf $TMPDIR
49
49
 
50
50
  # SSL
51
+ export CA_CERT="/usr/local/share/ca-certificates/rubyldap-ca.crt"
52
+ export CA_KEY="/etc/ssl/private/rubyldap-ca.key"
51
53
 
52
- sh -c "certtool --generate-privkey > /etc/ssl/private/cakey.pem"
54
+ # The self-signed fixture CA cert & key are generated by
55
+ # `script/generate-fiuxture-ca` and checked into version control.
56
+ # You shouldn't need to muck with these unless you're writing more
57
+ # TLS/SSL integration tests, and need special magic values in the cert.
53
58
 
54
- sh -c "cat > /etc/ssl/ca.info <<EOF
55
- cn = rubyldap
56
- ca
57
- cert_signing_key
58
- EOF"
59
+ cp "${SEED_PATH}/ca/cacert.pem" "${CA_CERT}"
60
+ cp "${SEED_PATH}/ca/cakey.pem" "${CA_KEY}"
59
61
 
60
- # Create the self-signed CA certificate:
61
- certtool --generate-self-signed \
62
- --load-privkey /etc/ssl/private/cakey.pem \
63
- --template /etc/ssl/ca.info \
64
- --outfile /etc/ssl/certs/cacert.pem
62
+ # actually add the fixture CA to the system store
63
+ update-ca-certificates
65
64
 
66
65
  # Make a private key for the server:
67
66
  certtool --generate-privkey \
68
- --bits 1024 \
69
- --outfile /etc/ssl/private/ldap01_slapd_key.pem
67
+ --bits 1024 \
68
+ --outfile /etc/ssl/private/ldap01_slapd_key.pem
70
69
 
71
70
  sh -c "cat > /etc/ssl/ldap01.info <<EOF
72
71
  organization = Example Company
73
72
  cn = ldap01.example.com
73
+ dns_name = ldap01.example.com
74
+ dns_name = ldap02.example.com
75
+ dns_name = localhost
74
76
  tls_www_server
75
77
  encryption_key
76
78
  signing_key
77
79
  expiration_days = 3650
78
80
  EOF"
79
81
 
82
+ # The integration server may be accessed by IP address, in which case
83
+ # we want some of the IPs included in the cert. We skip loopback (127.0.0.1)
84
+ # because that's the IP we use in the integration test for cert name mismatches.
85
+ ADDRS=$(ifconfig -a | grep 'inet addr:' | cut -f 2 -d : | cut -f 1 -d ' ')
86
+ for ip in $ADDRS; do
87
+ if [ "x$ip" = 'x127.0.0.1' ]; then continue; fi
88
+ echo "ip_address = $ip" >> /etc/ssl/ldap01.info
89
+ done
90
+
80
91
  # Create the server certificate
81
92
  certtool --generate-certificate \
82
93
  --load-privkey /etc/ssl/private/ldap01_slapd_key.pem \
83
- --load-ca-certificate /etc/ssl/certs/cacert.pem \
84
- --load-ca-privkey /etc/ssl/private/cakey.pem \
94
+ --load-ca-certificate "${CA_CERT}" \
95
+ --load-ca-privkey "${CA_KEY}" \
85
96
  --template /etc/ssl/ldap01.info \
86
97
  --outfile /etc/ssl/certs/ldap01_slapd_cert.pem
87
98
 
88
99
  ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF | true
89
100
  dn: cn=config
90
101
  add: olcTLSCACertificateFile
91
- olcTLSCACertificateFile: /etc/ssl/certs/cacert.pem
102
+ olcTLSCACertificateFile: ${CA_CERT}
92
103
  -
93
104
  add: olcTLSCertificateFile
94
105
  olcTLSCertificateFile: /etc/ssl/certs/ldap01_slapd_cert.pem
@@ -110,6 +121,14 @@ chmod g+r /etc/ssl/private/ldap01_slapd_key.pem
110
121
  chmod o-r /etc/ssl/private/ldap01_slapd_key.pem
111
122
 
112
123
  # Drop packets on a secondary port used to specific timeout tests
113
- iptables -A OUTPUT -p tcp -j DROP --dport 8389
124
+ iptables -A INPUT -p tcp -j DROP --dport 8389
125
+
126
+ # Forward a port for Vagrant
127
+ iptables -t nat -A PREROUTING -p tcp --dport 9389 -j REDIRECT --to-port 389
128
+
129
+ # fix up /etc/hosts for cert validation
130
+ grep ldap01 /etc/hosts || echo "127.0.0.1 ldap01.example.com" >> /etc/hosts
131
+ grep ldap02 /etc/hosts || echo "127.0.0.1 ldap02.example.com" >> /etc/hosts
132
+ grep bogus /etc/hosts || echo "127.0.0.1 bogus.example.com" >> /etc/hosts
114
133
 
115
134
  service slapd restart