cs-ruby-ldap 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 19e574263bc68b0b3eadebc634b65cc0188fd5d4
4
+ data.tar.gz: 4ca1ff90e220d24d8a4db6cf1f703d48f3b5d1c7
5
+ SHA512:
6
+ metadata.gz: 8e7bd944ff3e8c3162e04212e241bf239d2ba725af6cd8ef9a28532c25c5c7d0b572619276777107b958036f09904966920f97eda5ae541ea994c43b404f9075
7
+ data.tar.gz: ca4fa065811f6a649448c5ed5189d63c01e12259fc1391db229f98f536ec028a47038972eabe47b9383566d283ff17a5710570750ccafa2ae93d9371e296e43d
data/README.rdoc ADDED
File without changes
@@ -0,0 +1,209 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # Utilities for manipulating slapd
3
+ #
4
+ # Author:: Benjamin Staffin <ben@cloudscaling.com>
5
+ #
6
+ # Copyright 2014, The Cloudscaling Group, Inc.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require 'base64'
22
+ require 'digest'
23
+ require 'mixlib/shellout'
24
+ require 'net-ldap'
25
+ require 'openssl'
26
+ require 'stringio'
27
+
28
+ module Cloudscaling
29
+ # Tools for manipulating slapd using a ldapi:/// connection
30
+ class LDAPConfigUtils
31
+ # Read directly from an on-disk database, even if slapd is not running.
32
+ def self.slapcat(subtree_dn, filter)
33
+ scat = Mixlib::ShellOut.new('slapcat', '-o', 'ldif-wrap=no',
34
+ '-H', "ldap:///#{subtree_dn}???#{filter}")
35
+ scat.run_command
36
+ scat.error!
37
+ scat.stdout
38
+ end
39
+
40
+ # Mimic Net::LDAP#search using ldapsearch subprocess so we can use
41
+ # things like -Y EXTERNAL -H ldapi:///
42
+ # Returns a Net::LDAP::Dataset object representing the result.
43
+ def self.ldapsearch(args)
44
+ cmdargs = %w(ldapsearch -LLLQ -Y EXTERNAL -H ldapi:/// -o ldif-wrap=no)
45
+ filter = attributes = nil
46
+ args.each do |arg, val|
47
+ case arg.to_s
48
+ when 'base'
49
+ cmdargs += ['-b', val]
50
+ when 'scope'
51
+ unless [:base, :one, :sub, :children].include? val
52
+ fail "Unrecognized scope: #{val}"
53
+ end
54
+ cmdargs += ['-s', String(val)]
55
+ when 'filter'
56
+ filter = val
57
+ when 'attributes'
58
+ unless val.is_a?(Array)
59
+ fail("attributes must be an Array, not #{val.class}")
60
+ end
61
+ attributes = val
62
+ else
63
+ fail "Unrecognized option: #{arg}"
64
+ end
65
+ end
66
+ cmdargs << filter if filter
67
+ cmdargs += attributes if attributes
68
+ lsearch = Mixlib::ShellOut.new(*cmdargs, returns: [0, 32])
69
+ # exit 32 --> noSuchObject
70
+ lsearch.run_command
71
+ lsearch.error!
72
+ Net::LDAP::Dataset.read_ldif(StringIO.new(lsearch.stdout))
73
+ end
74
+
75
+ # Use this to modify anything in cn=config
76
+ # ldif: String or Array of Strings
77
+ # op: :modify (default) or :add
78
+ def self.ldapmodify_ldif(ldif, op = :modify)
79
+ ldif = ldif.join("\n") if ldif.is_a?(Array)
80
+ extraargs = []
81
+ case op
82
+ when :add
83
+ extraargs << '-a'
84
+ end
85
+ # Chef::Log.info("applying ldif: #{ldif}")
86
+ lmod = Mixlib::ShellOut.new(
87
+ 'ldapmodify', *extraargs, '-Q', '-Y', 'EXTERNAL', '-H', 'ldapi:///',
88
+ input: ldif)
89
+ lmod.run_command
90
+ lmod.error!
91
+ end
92
+
93
+ # Check for existence of a cn=config entry
94
+ def self.dn_exists?(dn)
95
+ true unless ldapsearch(base: dn,
96
+ attributes: 'dn',
97
+ scope: :base
98
+ ).empty?
99
+ end
100
+
101
+ def self.ldapadd(dn, attrs)
102
+ ldif = ["dn: #{dn}"]
103
+ attrs.each do |attr, value|
104
+ ldif << "#{attr}: #{value}"
105
+ end
106
+ ldapmodify_ldif(ldif, :add)
107
+ end
108
+
109
+ # Add or update a cn=config entry
110
+ def self.add_or_update_entry(dn, attrs)
111
+ unless dn_exists?(dn)
112
+ ldapadd(dn, attrs)
113
+ return
114
+ end
115
+ ldif = ["dn: #{dn}",
116
+ 'changeType: modify']
117
+ attrs.each do |attr, value|
118
+ ldif << "replace: #{attr}"
119
+ case value
120
+ when Array
121
+ value.each { |vvv| ldif << "#{attr}: #{vvv}" }
122
+ else
123
+ ldif << "#{attr}: #{value}"
124
+ end
125
+ ldif << '-'
126
+ end
127
+ ldapmodify_ldif(ldif)
128
+ end
129
+ end
130
+
131
+ # Tools for interacting with LDAP using a TCP connection
132
+ class LDAPUtils
133
+ def initialize(server, port = 389, auth_dn = nil, password = nil,
134
+ tls_enable = true)
135
+ args = { host: server,
136
+ port: port }
137
+ auth_dn && args.update(auth: { method: :simple,
138
+ username: auth_dn,
139
+ password: password })
140
+ tls_enable && args.update(encryption: { method: :start_tls })
141
+ @ldap = Net::LDAP.new(args)
142
+ @ldap.bind || fail("LDAP bind failed: #{@ldap.get_operation_result}")
143
+ end
144
+
145
+ def self.openssl_extract_cert(rawcert)
146
+ OpenSSL::X509::Certificate.new(rawcert).to_s
147
+ end
148
+
149
+ def self.openssl_extract_key(rawcert, passphrase = nil)
150
+ OpenSSL::PKey.read(rawcert, passphrase).to_s
151
+ end
152
+
153
+ # Generate a rfc2307 SSHA-hashed password in the format that slapd likes
154
+ # http://www.openldap.org/faq/data/cache/347.html
155
+ def self.ssha_password(clear_password, salt = nil)
156
+ unless salt
157
+ len = 16
158
+ base = 16
159
+ # rubocop:disable Style/SingleLineBlockParams
160
+ salt = len.times.reduce('') { |a| a << rand(base).to_s(base) }
161
+ end
162
+ digest = Digest::SHA1.digest("#{clear_password}#{salt}")
163
+ hash = Base64.encode64("#{digest}#{salt}").chomp
164
+ "{SSHA}#{hash}"
165
+ end
166
+
167
+ def self.first_item(dn)
168
+ m = dn.match(/^([^=]+)=([^,]+)/)
169
+ { m[1] => m[2] }
170
+ end
171
+
172
+ def dn_exists?(dn)
173
+ @ldap.search(base: dn,
174
+ scope: Net::LDAP::SearchScope_BaseObject,
175
+ return_result: false)
176
+ end
177
+
178
+ # Add an entry to the directory
179
+ # dn: Distinguished name of the new entry
180
+ # attrs: hash of entry attributes
181
+ def add_entry(dn, attrs)
182
+ # Chef::Log.info "Add LDAP entry: #{dn} #{attrs}"
183
+ @ldap.add(dn: dn, attributes: attrs) ||
184
+ fail("LDAP add failure: #{@ldap.get_operation_result}")
185
+ end
186
+
187
+ # Add or update a directory entry.
188
+ # If the entry exists, any attributes provided to this function will be
189
+ # updated with a "replace" operation, which will add the attributes if
190
+ # necessary, and replace all values of the attribute otherwise.
191
+ def add_or_update_entry(dn, attrs)
192
+ ret = @ldap.search(base: dn,
193
+ scope: Net::LAP::SearchScope_BaseObject,
194
+ return_result: true)
195
+ entries && entries.size > 1 && fail("#{dn} matches more than one entry")
196
+ if ret.nil?
197
+ add_entry(dn, attrs)
198
+ else
199
+ entry = ret.first
200
+ ops = attrs.each_with_object(Array.new) do |(k, v), acc|
201
+ acc << [:replace, k, v] unless entry[k] == [v].flatten
202
+ end
203
+ # Chef::Log.info("Update LDAP entry: dn=#{dn}")
204
+ @ldap.modify(dn: dn, operations: ops) ||
205
+ fail("LDAP update failure: #{@ldap.get_operation_result}")
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,2 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+ require 'cloudscaling/ldap'
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cs-ruby-ldap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Benjamin Staffin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-ldap
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mixlib-shellout
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ description: Cloudscaling LDAP tools and helpers
42
+ email: ben@cloudscaling.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - README.rdoc
48
+ - lib/cloudscaling/ldap.rb
49
+ - lib/cs_ruby_ldap.rb
50
+ homepage: http://pd.cloudscaling.com
51
+ licenses:
52
+ - Apache 2.0
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '2.0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.2.2
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Cloudscaling LDAP tools and helpers
74
+ test_files: []