pg-ldap-sync 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/History.txt +4 -0
- data/Manifest.txt +15 -0
- data/README.rdoc +102 -0
- data/Rakefile +18 -0
- data/bin/pg_ldap_sync +6 -0
- data/config/sample-config.yaml +54 -0
- data/config/sample-config2.yaml +54 -0
- data/config/schema.yaml +62 -0
- data/lib/pg_ldap_sync.rb +3 -0
- data/lib/pg_ldap_sync/application.rb +354 -0
- data/test/fixtures/config-ldapdb.yaml +32 -0
- data/test/fixtures/ldapdb.yaml +38 -0
- data/test/ldap_server.rb +41 -0
- data/test/test_pg_ldap_sync.rb +112 -0
- metadata +160 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
.autotest
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
bin/pg_ldap_sync
|
7
|
+
config/sample-config.yaml
|
8
|
+
config/sample-config2.yaml
|
9
|
+
config/schema.yaml
|
10
|
+
lib/pg_ldap_sync.rb
|
11
|
+
lib/pg_ldap_sync/application.rb
|
12
|
+
test/fixtures/config-ldapdb.yaml
|
13
|
+
test/fixtures/ldapdb.yaml
|
14
|
+
test/ldap_server.rb
|
15
|
+
test/test_pg_ldap_sync.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
= Use LDAP permissions in PostgreSQL
|
2
|
+
|
3
|
+
* http://github.com/larskanis/pg-ldap-sync
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
LDAP is often used to do a centralized user and role management
|
8
|
+
in an enterprise environment. PostgreSQL offers different
|
9
|
+
authentication methods, like LDAP, SSPI, GSSAPI or SSL.
|
10
|
+
However, for any method the user must already exist in the database,
|
11
|
+
before the authentication can be used. There is currently
|
12
|
+
no authorization of database users directly based on LDAP.
|
13
|
+
|
14
|
+
This program helps to solve the issue by synchronizing users,
|
15
|
+
groups and their memberships from LDAP to PostgreSQL.
|
16
|
+
Access to LDAP is read-only. <tt>pg_ldap_sync</tt> issues proper
|
17
|
+
CREATE ROLE, DROP ROLE, GRANT and REVOKE commands to synchronize
|
18
|
+
users and groups.
|
19
|
+
|
20
|
+
It is meant to be started as a cron job.
|
21
|
+
|
22
|
+
== FEATURES:
|
23
|
+
|
24
|
+
* Configurable per YAML config file
|
25
|
+
* Can use Active Directory as LDAP-Server
|
26
|
+
* Nested groups/roles supported
|
27
|
+
* Runs with pg.gem (C-library) or postgres-pr.gem (pure Ruby)
|
28
|
+
* Test mode which doesn't do any changes to the DBMS
|
29
|
+
|
30
|
+
== REQUIREMENTS:
|
31
|
+
|
32
|
+
* Installed Ruby and rubygems
|
33
|
+
* LDAP- and PostgreSQL-Server
|
34
|
+
|
35
|
+
== INSTALL:
|
36
|
+
|
37
|
+
Install Ruby and rubygems:
|
38
|
+
* on Windows: http://rubyinstaller.org
|
39
|
+
* on Debian/Ubuntu: <tt>apt-get install ruby rubygems</tt>
|
40
|
+
|
41
|
+
Install pg-ldap-sync and a database connector for PostgreSQL:
|
42
|
+
gem install pg-ldap-sync pg
|
43
|
+
You may also use the pure ruby postgres-connector which is less mature,
|
44
|
+
but doesn't need compilation:
|
45
|
+
gem install pg-ldap-sync postgres-pr
|
46
|
+
|
47
|
+
=== Install from Git:
|
48
|
+
git clone https://github.com/larskanis/pg-ldap-sync.git
|
49
|
+
cd pg-ldap-sync
|
50
|
+
gem install hoe
|
51
|
+
rake install_gem
|
52
|
+
|
53
|
+
== USAGE:
|
54
|
+
|
55
|
+
Create a config file based on <tt>config/sample-config.yaml</tt> .
|
56
|
+
Run in test-mode:
|
57
|
+
|
58
|
+
pg_ldap_sync -c my_config.yaml -vv -t
|
59
|
+
|
60
|
+
Run in modify-mode:
|
61
|
+
|
62
|
+
pg_ldap_sync -c my_config.yaml -vv
|
63
|
+
|
64
|
+
|
65
|
+
== TEST:
|
66
|
+
There is a small test suite in the <tt>test</tt> directory that runs
|
67
|
+
against an internal ruby-ldapserver and PostgreSQL server. Ensure gem
|
68
|
+
<tt>ruby-ldapserver</tt> is installed and <tt>pg_ctl</tt>, <tt>initdb</tt> and <tt>psql</tt>
|
69
|
+
commands are in the <tt>PATH</tt>. Then:
|
70
|
+
|
71
|
+
cd pg-ldap-sync
|
72
|
+
rake test
|
73
|
+
|
74
|
+
== ISSUES:
|
75
|
+
* There is currently no way to set certain user attributes in PG
|
76
|
+
based on individual attributes in LDAP (expiration date etc.)
|
77
|
+
|
78
|
+
|
79
|
+
== LICENSE:
|
80
|
+
|
81
|
+
(The MIT License)
|
82
|
+
|
83
|
+
Copyright (c) 2011 FIX
|
84
|
+
|
85
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
86
|
+
a copy of this software and associated documentation files (the
|
87
|
+
'Software'), to deal in the Software without restriction, including
|
88
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
89
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
90
|
+
permit persons to whom the Software is furnished to do so, subject to
|
91
|
+
the following conditions:
|
92
|
+
|
93
|
+
The above copyright notice and this permission notice shall be
|
94
|
+
included in all copies or substantial portions of the Software.
|
95
|
+
|
96
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
97
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
98
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
99
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
100
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
101
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
102
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
Hoe.spec 'pg-ldap-sync' do
|
7
|
+
developer('Lars Kanis', 'kanis@comcard.de')
|
8
|
+
|
9
|
+
extra_deps << ['net-ldap', '>= 0.2']
|
10
|
+
extra_deps << ['kwalify', '>= 0.7']
|
11
|
+
extra_dev_deps << ['ruby-ldapserver', '>= 0.3']
|
12
|
+
|
13
|
+
self.readme_file = 'README.rdoc'
|
14
|
+
spec_extras[:rdoc_options] = ['--main', readme_file, "--charset=UTF-8"]
|
15
|
+
self.extra_rdoc_files << self.readme_file
|
16
|
+
end
|
17
|
+
|
18
|
+
# vim: syntax=ruby
|
data/bin/pg_ldap_sync
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# With this sample config the distinction between PG groups and users is
|
2
|
+
# done by the LOGIN/NOLOGIN attribute. Any non-superuser account
|
3
|
+
# is considered as LDAP-synchronized.
|
4
|
+
|
5
|
+
# Connection parameters to LDAP server
|
6
|
+
# see also: http://net-ldap.rubyforge.org/Net/LDAP.html#method-c-new
|
7
|
+
ldap_connection:
|
8
|
+
host: localhost
|
9
|
+
port: 389
|
10
|
+
auth:
|
11
|
+
method: :simple
|
12
|
+
username: CN=username,OU=!Serviceaccounts,OU=company,DC=company,DC=de
|
13
|
+
password: secret
|
14
|
+
|
15
|
+
# Search parameters for LDAP users which should be synchronized
|
16
|
+
ldap_users:
|
17
|
+
base: OU=company,OU=company,DC=company,DC=de
|
18
|
+
# LDAP filter (according to RFC 2254)
|
19
|
+
# defines to users in LDAP to be synchronized
|
20
|
+
filter: (&(objectClass=person)(objectClass=organizationalPerson)(givenName=*)(sn=*))
|
21
|
+
# this attribute is used as PG role name
|
22
|
+
name_attribute: sAMAccountName
|
23
|
+
|
24
|
+
# Search parameters for LDAP groups which should be synchronized
|
25
|
+
ldap_groups:
|
26
|
+
base: OU=company,OU=company,DC=company,DC=de
|
27
|
+
filter: (|(cn=group1)(cn=group2)(cn=group3))
|
28
|
+
# this attribute is used as PG role name
|
29
|
+
name_attribute: cn
|
30
|
+
# this attribute must reference to all member DN's of the given group
|
31
|
+
member_attribute: member
|
32
|
+
|
33
|
+
# Connection parameters to PostgreSQL server
|
34
|
+
# see also: http://rubydoc.info/gems/pg/0.11.0/PGconn#initialize-instance_method
|
35
|
+
pg_connection:
|
36
|
+
host:
|
37
|
+
dbname: postgres
|
38
|
+
user: db-username
|
39
|
+
password:
|
40
|
+
|
41
|
+
pg_users:
|
42
|
+
# Filter for identifying LDAP generated users in the database.
|
43
|
+
# It's the WHERE-condition to "SELECT rolname, oid FROM pg_roles"
|
44
|
+
filter: rolcanlogin AND NOT rolsuper
|
45
|
+
# Options for CREATE RULE statements
|
46
|
+
create_options: LOGIN
|
47
|
+
|
48
|
+
pg_groups:
|
49
|
+
# Filter for identifying LDAP generated groups in the database.
|
50
|
+
# It's the WHERE-condition to "SELECT rolname, oid FROM pg_roles"
|
51
|
+
filter: NOT rolcanlogin AND NOT rolsuper
|
52
|
+
# Options for CREATE RULE statements
|
53
|
+
create_options: NOLOGIN
|
54
|
+
grant_options:
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# With this sample config the distinction between LDAP-synchronized
|
2
|
+
# groups/users from is done by the membership to ldap_user and
|
3
|
+
# ldap_group. These two roles has to be defined manally.
|
4
|
+
|
5
|
+
# Connection parameters to LDAP server
|
6
|
+
# see also: http://net-ldap.rubyforge.org/Net/LDAP.html#method-c-new
|
7
|
+
ldap_connection:
|
8
|
+
host: ldapserver
|
9
|
+
port: 389
|
10
|
+
auth:
|
11
|
+
method: :simple
|
12
|
+
username: CN=username,OU=!Serviceaccounts,OU=company,DC=company,DC=de
|
13
|
+
password: secret
|
14
|
+
|
15
|
+
# Search parameters for LDAP users which should be synchronized
|
16
|
+
ldap_users:
|
17
|
+
base: OU=company,DC=company,DC=prod
|
18
|
+
# LDAP filter (according to RFC 2254)
|
19
|
+
# defines to users in LDAP to be synchronized
|
20
|
+
filter: (&(objectClass=person)(objectClass=organizationalPerson)(givenName=*)(sn=*)(sAMAccountName=*))
|
21
|
+
# this attribute is used as PG role name
|
22
|
+
name_attribute: sAMAccountName
|
23
|
+
|
24
|
+
# Search parameters for LDAP groups which should be synchronized
|
25
|
+
ldap_groups:
|
26
|
+
base: OU=company,DC=company,DC=prod
|
27
|
+
filter: (cn=company.*)
|
28
|
+
# this attribute is used as PG role name
|
29
|
+
name_attribute: cn
|
30
|
+
# this attribute must reference to all member DN's of the given group
|
31
|
+
member_attribute: member
|
32
|
+
|
33
|
+
# Connection parameters to PostgreSQL server
|
34
|
+
# see also: http://rubydoc.info/gems/pg/0.11.0/PGconn#initialize-instance_method
|
35
|
+
pg_connection:
|
36
|
+
host:
|
37
|
+
dbname: postgres
|
38
|
+
user:
|
39
|
+
password:
|
40
|
+
|
41
|
+
pg_users:
|
42
|
+
# Filter for identifying LDAP generated users in the database.
|
43
|
+
# It's the WHERE-condition to "SELECT rolname, oid FROM pg_roles"
|
44
|
+
filter: oid IN (SELECT pam.member FROM pg_auth_members pam JOIN pg_roles pr ON pr.oid=pam.roleid WHERE pr.rolname='ldap_users')
|
45
|
+
# Options for CREATE RULE statements
|
46
|
+
create_options: LOGIN IN ROLE ldap_users
|
47
|
+
|
48
|
+
pg_groups:
|
49
|
+
# Filter for identifying LDAP generated groups in the database.
|
50
|
+
# It's the WHERE-condition to "SELECT rolname, oid FROM pg_roles"
|
51
|
+
filter: oid IN (SELECT pam.member FROM pg_auth_members pam JOIN pg_roles pr ON pr.oid=pam.roleid WHERE pr.rolname='ldap_groups')
|
52
|
+
# Options for CREATE RULE statements
|
53
|
+
create_options: NOLOGIN IN ROLE ldap_groups
|
54
|
+
grant_options:
|
data/config/schema.yaml
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
type: map
|
2
|
+
mapping:
|
3
|
+
"ldap_connection":
|
4
|
+
type: any
|
5
|
+
required: yes
|
6
|
+
|
7
|
+
"ldap_users":
|
8
|
+
type: map
|
9
|
+
required: yes
|
10
|
+
mapping:
|
11
|
+
"base":
|
12
|
+
type: str
|
13
|
+
required: yes
|
14
|
+
"filter":
|
15
|
+
type: str
|
16
|
+
required: yes
|
17
|
+
"name_attribute":
|
18
|
+
type: str
|
19
|
+
required: yes
|
20
|
+
|
21
|
+
"ldap_groups":
|
22
|
+
type: map
|
23
|
+
required: yes
|
24
|
+
mapping:
|
25
|
+
"base":
|
26
|
+
type: str
|
27
|
+
required: yes
|
28
|
+
"filter":
|
29
|
+
type: str
|
30
|
+
required: yes
|
31
|
+
"name_attribute":
|
32
|
+
type: str
|
33
|
+
required: yes
|
34
|
+
"member_attribute":
|
35
|
+
type: str
|
36
|
+
required: yes
|
37
|
+
|
38
|
+
"pg_connection":
|
39
|
+
type: any
|
40
|
+
required: yes
|
41
|
+
|
42
|
+
"pg_users":
|
43
|
+
type: map
|
44
|
+
required: yes
|
45
|
+
mapping:
|
46
|
+
"filter":
|
47
|
+
type: str
|
48
|
+
required: yes
|
49
|
+
"create_options":
|
50
|
+
type: str
|
51
|
+
|
52
|
+
"pg_groups":
|
53
|
+
type: map
|
54
|
+
required: yes
|
55
|
+
mapping:
|
56
|
+
"filter":
|
57
|
+
type: str
|
58
|
+
required: yes
|
59
|
+
"create_options":
|
60
|
+
type: str
|
61
|
+
"grant_options":
|
62
|
+
type: str
|
data/lib/pg_ldap_sync.rb
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'net/ldap'
|
5
|
+
require 'optparse'
|
6
|
+
require 'yaml'
|
7
|
+
require 'logger'
|
8
|
+
require 'kwalify'
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'pg'
|
12
|
+
rescue LoadError => e
|
13
|
+
begin
|
14
|
+
require 'postgres'
|
15
|
+
class PGconn
|
16
|
+
alias initialize_before_hash_change initialize
|
17
|
+
def initialize(*args)
|
18
|
+
arg = args.first
|
19
|
+
if args.length==1 && arg.kind_of?(Hash)
|
20
|
+
initialize_before_hash_change(arg[:host], arg[:port], nil, nil, arg[:dbname], arg[:user], arg[:password])
|
21
|
+
else
|
22
|
+
initialize_before_hash_change(*args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
raise e
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'pg_ldap_sync'
|
32
|
+
|
33
|
+
module PgLdapSync
|
34
|
+
class Application
|
35
|
+
class LdapError < RuntimeError; end
|
36
|
+
attr_accessor :config_fname
|
37
|
+
attr_accessor :log
|
38
|
+
attr_accessor :test
|
39
|
+
|
40
|
+
def string_to_symbol(hash)
|
41
|
+
if hash.kind_of?(Hash)
|
42
|
+
return hash.inject({}){|h, v|
|
43
|
+
raise "expected String instead of #{h.inspect}" unless v[0].kind_of?(String)
|
44
|
+
h[v[0].intern] = string_to_symbol(v[1])
|
45
|
+
h
|
46
|
+
}
|
47
|
+
else
|
48
|
+
return hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def validate_config(config, schema, fname)
|
54
|
+
schema = YAML.load_file(schema)
|
55
|
+
validator = Kwalify::Validator.new(schema)
|
56
|
+
errors = validator.validate(config)
|
57
|
+
if errors && !errors.empty?
|
58
|
+
errors.each do |err|
|
59
|
+
log.fatal "error in #{fname}: [#{err.path}] #{err.message}"
|
60
|
+
end
|
61
|
+
exit(-1)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_config_file(fname)
|
66
|
+
raise "Config file #{fname.inspect} does not exist" unless File.exist?(fname)
|
67
|
+
config = YAML.load(File.read(fname))
|
68
|
+
|
69
|
+
schema_fname = File.join(File.dirname(__FILE__), '../../config/schema.yaml')
|
70
|
+
validate_config(config, schema_fname, fname)
|
71
|
+
|
72
|
+
@config = string_to_symbol(config)
|
73
|
+
end
|
74
|
+
|
75
|
+
LdapRole = Struct.new :name, :dn, :member_dns
|
76
|
+
|
77
|
+
def search_ldap_users
|
78
|
+
ldap_user_conf = @config[:ldap_users]
|
79
|
+
|
80
|
+
users = []
|
81
|
+
res = @ldap.search(:base => ldap_user_conf[:base], :filter => ldap_user_conf[:filter]) do |entry|
|
82
|
+
name = entry[ldap_user_conf[:name_attribute]].first
|
83
|
+
|
84
|
+
unless name
|
85
|
+
log.warn "user attribute #{ldap_user_conf[:name_attribute].inspect} not defined for #{entry.dn}"
|
86
|
+
next
|
87
|
+
end
|
88
|
+
|
89
|
+
log.info "found user-dn: #{entry.dn}"
|
90
|
+
user = LdapRole.new name, entry.dn
|
91
|
+
users << user
|
92
|
+
entry.each do |attribute, values|
|
93
|
+
log.debug " #{attribute}:"
|
94
|
+
values.each do |value|
|
95
|
+
log.debug " --->#{value.inspect}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
raise LdapError, "LDAP: #{@ldap.get_operation_result.message}" unless res
|
100
|
+
return users
|
101
|
+
end
|
102
|
+
|
103
|
+
def search_ldap_groups
|
104
|
+
ldap_group_conf = @config[:ldap_groups]
|
105
|
+
|
106
|
+
groups = []
|
107
|
+
res = @ldap.search(:base => ldap_group_conf[:base], :filter => ldap_group_conf[:filter]) do |entry|
|
108
|
+
name = entry[ldap_group_conf[:name_attribute]].first
|
109
|
+
|
110
|
+
unless name
|
111
|
+
log.warn "user attribute #{ldap_group_conf[:name_attribute].inspect} not defined for #{entry.dn}"
|
112
|
+
next
|
113
|
+
end
|
114
|
+
|
115
|
+
log.info "found group-dn: #{entry.dn}"
|
116
|
+
group = LdapRole.new name, entry.dn, entry[ldap_group_conf[:member_attribute]]
|
117
|
+
groups << group
|
118
|
+
entry.each do |attribute, values|
|
119
|
+
log.debug " #{attribute}:"
|
120
|
+
values.each do |value|
|
121
|
+
log.debug " --->#{value.inspect}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
raise LdapError, "LDAP: #{@ldap.get_operation_result.message}" unless res
|
126
|
+
return groups
|
127
|
+
end
|
128
|
+
|
129
|
+
PgRole = Struct.new :name, :member_names
|
130
|
+
|
131
|
+
def search_pg_users
|
132
|
+
pg_users_conf = @config[:pg_users]
|
133
|
+
|
134
|
+
users = []
|
135
|
+
res = pg_exec "SELECT rolname FROM pg_roles WHERE #{pg_users_conf[:filter]}"
|
136
|
+
res.each do |tuple|
|
137
|
+
user = PgRole.new tuple[0]
|
138
|
+
log.info{ "found pg-user: #{user.name.inspect}"}
|
139
|
+
users << user
|
140
|
+
end
|
141
|
+
return users
|
142
|
+
end
|
143
|
+
|
144
|
+
def search_pg_groups
|
145
|
+
pg_groups_conf = @config[:pg_groups]
|
146
|
+
|
147
|
+
groups = []
|
148
|
+
res = pg_exec "SELECT rolname, oid FROM pg_roles WHERE #{pg_groups_conf[:filter]}"
|
149
|
+
res.each do |tuple|
|
150
|
+
res2 = pg_exec "SELECT pr.rolname FROM pg_auth_members pam JOIN pg_roles pr ON pr.oid=pam.member WHERE pam.roleid=#{PGconn.escape(tuple[1])}"
|
151
|
+
member_names = res2.map{|row| row[0] }
|
152
|
+
group = PgRole.new tuple[0], member_names
|
153
|
+
log.info{ "found pg-group: #{group.name.inspect} with members: #{member_names.inspect}"}
|
154
|
+
groups << group
|
155
|
+
end
|
156
|
+
return groups
|
157
|
+
end
|
158
|
+
|
159
|
+
def uniq_names(list)
|
160
|
+
names = {}
|
161
|
+
new_list = list.select do |entry|
|
162
|
+
name = entry.name
|
163
|
+
if names[name]
|
164
|
+
log.warn{ "duplicated group/user #{name.inspect} (#{entry.inspect})" }
|
165
|
+
next false
|
166
|
+
else
|
167
|
+
names[name] = true
|
168
|
+
next true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
return new_list
|
172
|
+
end
|
173
|
+
|
174
|
+
MatchedRole = Struct.new :ldap, :pg, :name, :state, :type
|
175
|
+
|
176
|
+
def match_roles(ldaps, pgs, type)
|
177
|
+
ldap_by_name = ldaps.inject({}){|h,u| h[u.name] = u; h }
|
178
|
+
pg_by_name = pgs.inject({}){|h,u| h[u.name] = u; h }
|
179
|
+
|
180
|
+
roles = []
|
181
|
+
ldaps.each do |ld|
|
182
|
+
pg = pg_by_name[ld.name]
|
183
|
+
role = MatchedRole.new ld, pg, ld.name
|
184
|
+
roles << role
|
185
|
+
end
|
186
|
+
pgs.each do |pg|
|
187
|
+
ld = ldap_by_name[pg.name]
|
188
|
+
next if ld
|
189
|
+
role = MatchedRole.new ld, pg, pg.name
|
190
|
+
roles << role
|
191
|
+
end
|
192
|
+
|
193
|
+
roles.each do |r|
|
194
|
+
r.state = case
|
195
|
+
when r.ldap && !r.pg then :create
|
196
|
+
when !r.ldap && r.pg then :drop
|
197
|
+
when r.pg && r.ldap then :keep
|
198
|
+
else raise "invalid user #{r.inspect}"
|
199
|
+
end
|
200
|
+
r.type = type
|
201
|
+
end
|
202
|
+
|
203
|
+
log.info{
|
204
|
+
roles.each do |role|
|
205
|
+
log.debug{ "#{role.state} #{role.type}: #{role.name}" }
|
206
|
+
end
|
207
|
+
"#{type} stat: create: #{roles.count{|r| r.state==:create }} drop: #{roles.count{|r| r.state==:drop }} keep: #{roles.count{|r| r.state==:keep }}"
|
208
|
+
}
|
209
|
+
return roles
|
210
|
+
end
|
211
|
+
|
212
|
+
def pg_exec_modify(sql)
|
213
|
+
log.info{ "SQL: #{sql}" }
|
214
|
+
unless self.test
|
215
|
+
res = @pgconn.exec sql
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def pg_exec(sql)
|
220
|
+
res = @pgconn.exec sql
|
221
|
+
(0...res.num_tuples).map{|t| (0...res.num_fields).map{|i| res.getvalue(t, i) } }
|
222
|
+
end
|
223
|
+
|
224
|
+
def create_pg_role(role)
|
225
|
+
pg_conf = @config[role.type==:user ? :pg_users : :pg_groups]
|
226
|
+
pg_exec_modify "CREATE ROLE \"#{role.name}\" #{pg_conf[:create_options]}"
|
227
|
+
end
|
228
|
+
|
229
|
+
def drop_pg_role(role)
|
230
|
+
pg_exec_modify "DROP ROLE \"#{role.name}\""
|
231
|
+
end
|
232
|
+
|
233
|
+
def sync_roles_to_pg(roles, for_state)
|
234
|
+
roles.sort{|a,b| a.name<=>b.name }.each do |role|
|
235
|
+
create_pg_role(role) if role.state==:create && for_state==:create
|
236
|
+
drop_pg_role(role) if role.state==:drop && for_state==:drop
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
MatchedMembership = Struct.new :role_name, :has_member, :state
|
241
|
+
|
242
|
+
def match_memberships(ldap_roles, pg_roles)
|
243
|
+
ldap_by_dn = ldap_roles.inject({}){|h,r| h[r.dn] = r; h }
|
244
|
+
ldap_by_m2m = ldap_roles.inject([]){|a,r|
|
245
|
+
next a unless r.member_dns
|
246
|
+
a + r.member_dns.map{|dn|
|
247
|
+
if has_member=ldap_by_dn[dn]
|
248
|
+
[r.name, has_member.name]
|
249
|
+
else
|
250
|
+
log.warn{"ldap member with dn #{dn} is unknown"}
|
251
|
+
nil
|
252
|
+
end
|
253
|
+
}.compact
|
254
|
+
}
|
255
|
+
|
256
|
+
pg_by_name = pg_roles.inject({}){|h,r| h[r.name] = r; h }
|
257
|
+
pg_by_m2m = pg_roles.inject([]){|a,r|
|
258
|
+
next a unless r.member_names
|
259
|
+
a + r.member_names.map{|name|
|
260
|
+
if has_member=pg_by_name[name]
|
261
|
+
[r.name, has_member.name]
|
262
|
+
else
|
263
|
+
log.warn{"pg member with name #{name} is unknown"}
|
264
|
+
nil
|
265
|
+
end
|
266
|
+
}.compact
|
267
|
+
}
|
268
|
+
|
269
|
+
memberships = (ldap_by_m2m & pg_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :keep }
|
270
|
+
memberships += (ldap_by_m2m - pg_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :grant }
|
271
|
+
memberships += (pg_by_m2m - ldap_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :revoke }
|
272
|
+
|
273
|
+
log.info{
|
274
|
+
memberships.each do |membership|
|
275
|
+
log.debug{ "#{membership.state} #{membership.role_name} to #{membership.has_member}" }
|
276
|
+
end
|
277
|
+
"membership stat: grant: #{memberships.count{|u| u.state==:grant }} revoke: #{memberships.count{|u| u.state==:revoke }} keep: #{memberships.count{|u| u.state==:keep }}"
|
278
|
+
}
|
279
|
+
return memberships
|
280
|
+
end
|
281
|
+
|
282
|
+
def grant_membership(role_name, add_members)
|
283
|
+
pg_conf = @config[:pg_groups]
|
284
|
+
add_members_escaped = add_members.map{|m| "\"#{m}\"" }.join(",")
|
285
|
+
pg_exec_modify "GRANT \"#{role_name}\" TO #{add_members_escaped} #{pg_conf[:grant_options]}"
|
286
|
+
end
|
287
|
+
|
288
|
+
def revoke_membership(role_name, rm_members)
|
289
|
+
rm_members_escaped = rm_members.map{|m| "\"#{m}\"" }.join(",")
|
290
|
+
pg_exec_modify "REVOKE \"#{role_name}\" FROM #{rm_members_escaped}"
|
291
|
+
end
|
292
|
+
|
293
|
+
def sync_membership_to_pg(memberships, for_state)
|
294
|
+
grants = {}
|
295
|
+
memberships.select{|ms| ms.state==for_state }.each do |ms|
|
296
|
+
grants[ms.role_name] ||= []
|
297
|
+
grants[ms.role_name] << ms.has_member
|
298
|
+
end
|
299
|
+
|
300
|
+
grants.each do |role_name, members|
|
301
|
+
grant_membership(role_name, members) if for_state==:grant
|
302
|
+
revoke_membership(role_name, members) if for_state==:revoke
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def start!
|
307
|
+
read_config_file(@config_fname)
|
308
|
+
|
309
|
+
# gather LDAP users and groups
|
310
|
+
@ldap = Net::LDAP.new @config[:ldap_connection]
|
311
|
+
ldap_users = uniq_names search_ldap_users
|
312
|
+
ldap_groups = uniq_names search_ldap_groups
|
313
|
+
|
314
|
+
# gather PGs users and groups
|
315
|
+
@pgconn = PGconn.connect @config[:pg_connection]
|
316
|
+
pg_users = uniq_names search_pg_users
|
317
|
+
pg_groups = uniq_names search_pg_groups
|
318
|
+
|
319
|
+
# compare LDAP to PG users and groups
|
320
|
+
mroles = match_roles(ldap_users, pg_users, :user)
|
321
|
+
mroles += match_roles(ldap_groups, pg_groups, :group)
|
322
|
+
|
323
|
+
# compare LDAP to PG memberships
|
324
|
+
mmemberships = match_memberships(ldap_users+ldap_groups, pg_users+pg_groups)
|
325
|
+
|
326
|
+
# drop/revoke roles/memberships first
|
327
|
+
sync_membership_to_pg(mmemberships, :revoke)
|
328
|
+
sync_roles_to_pg(mroles, :drop)
|
329
|
+
# create/grant roles/memberships
|
330
|
+
sync_roles_to_pg(mroles, :create)
|
331
|
+
sync_membership_to_pg(mmemberships, :grant)
|
332
|
+
|
333
|
+
@pgconn.close
|
334
|
+
end
|
335
|
+
|
336
|
+
def self.run(argv)
|
337
|
+
s = self.new
|
338
|
+
s.config_fname = '/etc/pg_ldap_sync.yaml'
|
339
|
+
s.log = Logger.new(STDOUT)
|
340
|
+
s.log.level = Logger::ERROR
|
341
|
+
|
342
|
+
OptionParser.new do |opts|
|
343
|
+
opts.banner = "Usage: #{$0} [options]"
|
344
|
+
opts.on("-v", "--[no-]verbose", "Increase verbose level"){ s.log.level-=1 }
|
345
|
+
opts.on("-c", "--config FILE", "Config file [#{s.config_fname}]", &s.method(:config_fname=))
|
346
|
+
opts.on("-t", "--[no-]test", "Don't do any change in the database", &s.method(:test=))
|
347
|
+
|
348
|
+
opts.parse!(argv)
|
349
|
+
end
|
350
|
+
|
351
|
+
s.start!
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
---
|
2
|
+
ldap_connection:
|
3
|
+
host: localhost
|
4
|
+
port: 1389
|
5
|
+
|
6
|
+
ldap_users:
|
7
|
+
base: dc=example,dc=com
|
8
|
+
filter: (&(cn=*)(sAMAccountName=*))
|
9
|
+
name_attribute: sAMAccountName
|
10
|
+
|
11
|
+
ldap_groups:
|
12
|
+
base: dc=example,dc=com
|
13
|
+
filter: (member=*)
|
14
|
+
name_attribute: cn
|
15
|
+
member_attribute: member
|
16
|
+
|
17
|
+
pg_connection:
|
18
|
+
dbname: postgres
|
19
|
+
# needed for postgres-pr:
|
20
|
+
# host: localhost
|
21
|
+
# port: 54321
|
22
|
+
# user: insert_your_username_here
|
23
|
+
# password:
|
24
|
+
|
25
|
+
pg_users:
|
26
|
+
filter: rolcanlogin AND NOT rolsuper
|
27
|
+
create_options: LOGIN
|
28
|
+
|
29
|
+
pg_groups:
|
30
|
+
filter: NOT rolcanlogin
|
31
|
+
create_options: NOLOGIN
|
32
|
+
grant_options:
|
@@ -0,0 +1,38 @@
|
|
1
|
+
---
|
2
|
+
dc=example,dc=com:
|
3
|
+
cn:
|
4
|
+
- Top object
|
5
|
+
cn=Fred Flintstone,dc=example,dc=com:
|
6
|
+
cn:
|
7
|
+
- Fred Flintstone
|
8
|
+
mail:
|
9
|
+
- fred@bedrock.org
|
10
|
+
- fred.flintstone@bedrock.org
|
11
|
+
sn:
|
12
|
+
- Flintstone
|
13
|
+
sAMAccountName:
|
14
|
+
- fred
|
15
|
+
cn=Wilma Flintstone,dc=example,dc=com:
|
16
|
+
cn:
|
17
|
+
- Wilma Flintstone
|
18
|
+
mail:
|
19
|
+
- wilma@bedrock.org
|
20
|
+
sAMAccountName:
|
21
|
+
- wilma
|
22
|
+
cn=Flintstones,dc=example,dc=com:
|
23
|
+
cn:
|
24
|
+
- Flintstones
|
25
|
+
member:
|
26
|
+
- cn=Fred Flintstone,dc=example,dc=com
|
27
|
+
- cn=Wilma Flintstone,dc=example,dc=com
|
28
|
+
cn=Wilmas,dc=example,dc=com:
|
29
|
+
cn:
|
30
|
+
- Wilmas
|
31
|
+
member:
|
32
|
+
- cn=Wilma Flintstone,dc=example,dc=com
|
33
|
+
cn=All Users,dc=example,dc=com:
|
34
|
+
cn:
|
35
|
+
- All Users
|
36
|
+
member:
|
37
|
+
- cn=Wilmas,dc=example,dc=com
|
38
|
+
- cn=Fred Flintstone,dc=example,dc=com
|
data/test/ldap_server.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# This is a trivial LDAP server which just stores directory entries in RAM.
|
4
|
+
# It does no validation or authentication. This is intended just to
|
5
|
+
# demonstrate the API, it's not for real-world use!!
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'ldap/server'
|
9
|
+
|
10
|
+
# We subclass the Operation class, overriding the methods to do what we need
|
11
|
+
|
12
|
+
class HashOperation < LDAP::Server::Operation
|
13
|
+
def initialize(connection, messageID, hash)
|
14
|
+
super(connection, messageID)
|
15
|
+
@hash = hash # an object reference to our directory data
|
16
|
+
end
|
17
|
+
|
18
|
+
def search(basedn, scope, deref, filter)
|
19
|
+
basedn.downcase!
|
20
|
+
|
21
|
+
case scope
|
22
|
+
when LDAP::Server::BaseObject
|
23
|
+
# client asked for single object by DN
|
24
|
+
obj = @hash[basedn]
|
25
|
+
raise LDAP::ResultError::NoSuchObject unless obj
|
26
|
+
send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj)
|
27
|
+
|
28
|
+
when LDAP::Server::WholeSubtree
|
29
|
+
@hash.each do |dn, av|
|
30
|
+
next unless dn.index(basedn, -basedn.length) # under basedn?
|
31
|
+
next unless LDAP::Server::Filter.run(filter, av) # attribute filter?
|
32
|
+
send_SearchResultEntry(dn, av)
|
33
|
+
end
|
34
|
+
|
35
|
+
else
|
36
|
+
raise LDAP::ResultError::UnwillingToPerform, "OneLevel not implemented"
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
require "pg_ldap_sync/application"
|
5
|
+
require 'yaml'
|
6
|
+
require 'test/ldap_server'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
class TestPgLdapSync < Test::Unit::TestCase
|
10
|
+
def log_and_run( *cmd )
|
11
|
+
puts cmd.join(' ')
|
12
|
+
system( *cmd )
|
13
|
+
raise "Command failed: [%s]" % [cmd.join(' ')] unless $?.success?
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_ldap_server
|
17
|
+
yaml_fname = File.join(File.dirname(__FILE__), "fixtures/ldapdb.yaml")
|
18
|
+
@directory = File.open(yaml_fname){|f| YAML::load(f.read) }
|
19
|
+
|
20
|
+
# Listen for incoming LDAP connections. For each one, create a Connection
|
21
|
+
# object, which will invoke a HashOperation object for each request.
|
22
|
+
|
23
|
+
@ldap_server = LDAP::Server.new(
|
24
|
+
:port => 1389,
|
25
|
+
:nodelay => true,
|
26
|
+
:listen => 10,
|
27
|
+
# :ssl_key_file => "key.pem",
|
28
|
+
# :ssl_cert_file => "cert.pem",
|
29
|
+
# :ssl_on_connect => true,
|
30
|
+
:operation_class => HashOperation,
|
31
|
+
:operation_args => [@directory]
|
32
|
+
)
|
33
|
+
@ldap_server.run_tcpserver
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop_ldap_server
|
37
|
+
@ldap_server.stop
|
38
|
+
end
|
39
|
+
|
40
|
+
def start_pg_server
|
41
|
+
@port = 54321
|
42
|
+
ENV['PGPORT'] = @port.to_s
|
43
|
+
ENV['PGHOST'] = 'localhost'
|
44
|
+
unless File.exist?('temp/pg_data')
|
45
|
+
FileUtils.mkdir_p 'temp/pg_data'
|
46
|
+
log_and_run 'initdb', '-D', 'temp/pg_data'
|
47
|
+
end
|
48
|
+
log_and_run 'pg_ctl', '-w', '-o', "-k.", '-D', 'temp/pg_data', 'start'
|
49
|
+
log_and_run 'psql', '-e', '-c', "DROP ROLE IF EXISTS fred, wilma, \"Flintstones\", \"Wilmas\", \"All Users\"", 'postgres'
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop_pg_server
|
53
|
+
log_and_run 'pg_ctl', '-w', '-o', "-k.", '-D', 'temp/pg_data', 'stop'
|
54
|
+
end
|
55
|
+
|
56
|
+
def setup
|
57
|
+
start_ldap_server
|
58
|
+
start_pg_server
|
59
|
+
end
|
60
|
+
|
61
|
+
def teardown
|
62
|
+
stop_ldap_server
|
63
|
+
stop_pg_server
|
64
|
+
end
|
65
|
+
|
66
|
+
def psqlre(*args)
|
67
|
+
/^\s*#{args[0]}[ |]*#{args[1]}[ |\{"]*#{args[2..-1].join('[", ]+')}["\}\s]*$/
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_sanity
|
71
|
+
PgLdapSync::Application.run(%w[-c test/fixtures/config-ldapdb.yaml -vv])
|
72
|
+
|
73
|
+
ENV['LC_MESSAGES'] = 'C'
|
74
|
+
psql_du = `psql -c \\\\du postgres`
|
75
|
+
puts psql_du
|
76
|
+
|
77
|
+
assert_match(psqlre('All Users','Cannot login'), psql_du)
|
78
|
+
assert_match(psqlre('Flintstones','Cannot login'), psql_du)
|
79
|
+
assert_match(psqlre('Wilmas','Cannot login','All Users'), psql_du)
|
80
|
+
assert_match(psqlre('fred','','All Users','Flintstones'), psql_du)
|
81
|
+
assert_match(psqlre('wilma','','Flintstones','Wilmas'), psql_du)
|
82
|
+
|
83
|
+
# revoke membership of 'wilma' to 'Flintstones'
|
84
|
+
@directory['cn=Flintstones,dc=example,dc=com']['member'].pop
|
85
|
+
|
86
|
+
PgLdapSync::Application.run(%w[-c test/fixtures/config-ldapdb.yaml -vv])
|
87
|
+
psql_du = `psql -c \\\\du postgres`
|
88
|
+
puts psql_du
|
89
|
+
|
90
|
+
assert_match(psqlre('All Users','Cannot login'), psql_du)
|
91
|
+
assert_match(psqlre('Flintstones','Cannot login'), psql_du)
|
92
|
+
assert_match(psqlre('Wilmas','Cannot login','All Users'), psql_du)
|
93
|
+
assert_match(psqlre('fred','','All Users','Flintstones'), psql_du)
|
94
|
+
assert_match(psqlre('wilma','','Wilmas'), psql_du)
|
95
|
+
|
96
|
+
# rename role 'wilma'
|
97
|
+
@directory['cn=Wilma Flintstone,dc=example,dc=com']['sAMAccountName'] = ['Wilma Flintstone']
|
98
|
+
# re-add 'Wilma' to 'Flintstones'
|
99
|
+
@directory['cn=Flintstones,dc=example,dc=com']['member'] << 'cn=Wilma Flintstone,dc=example,dc=com'
|
100
|
+
|
101
|
+
PgLdapSync::Application.run(%w[-c test/fixtures/config-ldapdb.yaml -vv])
|
102
|
+
psql_du = `psql -c \\\\du postgres`
|
103
|
+
puts psql_du
|
104
|
+
|
105
|
+
assert_match(psqlre('All Users','Cannot login'), psql_du)
|
106
|
+
assert_match(psqlre('Flintstones','Cannot login'), psql_du)
|
107
|
+
assert_match(psqlre('Wilmas','Cannot login','All Users'), psql_du)
|
108
|
+
assert_match(psqlre('fred','','All Users','Flintstones'), psql_du)
|
109
|
+
assert_no_match(/wilma/, psql_du)
|
110
|
+
assert_match(psqlre('Wilma Flintstone','','Flintstones','Wilmas'), psql_du)
|
111
|
+
end
|
112
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pg-ldap-sync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Lars Kanis
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-13 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: net-ldap
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 15
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 2
|
33
|
+
version: "0.2"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: kwalify
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 5
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 7
|
48
|
+
version: "0.7"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: ruby-ldapserver
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 13
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
- 3
|
63
|
+
version: "0.3"
|
64
|
+
type: :development
|
65
|
+
version_requirements: *id003
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: hoe
|
68
|
+
prerelease: false
|
69
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 47
|
75
|
+
segments:
|
76
|
+
- 2
|
77
|
+
- 8
|
78
|
+
- 0
|
79
|
+
version: 2.8.0
|
80
|
+
type: :development
|
81
|
+
version_requirements: *id004
|
82
|
+
description: |-
|
83
|
+
LDAP is often used to do a centralized user and role management
|
84
|
+
in an enterprise environment. PostgreSQL offers different
|
85
|
+
authentication methods, like LDAP, SSPI, GSSAPI or SSL.
|
86
|
+
However, for any method the user must already exist in the database,
|
87
|
+
before the authentication can be used. There is currently
|
88
|
+
no authorization of database users directly based on LDAP.
|
89
|
+
|
90
|
+
This program helps to solve the issue by synchronizing users,
|
91
|
+
groups and their memberships from LDAP to PostgreSQL.
|
92
|
+
Access to LDAP is read-only. <tt>pg_ldap_sync</tt> issues proper
|
93
|
+
CREATE ROLE, DROP ROLE, GRANT and REVOKE commands to synchronize
|
94
|
+
users and groups.
|
95
|
+
|
96
|
+
It is meant to be started as a cron job.
|
97
|
+
email:
|
98
|
+
- kanis@comcard.de
|
99
|
+
executables:
|
100
|
+
- pg_ldap_sync
|
101
|
+
extensions: []
|
102
|
+
|
103
|
+
extra_rdoc_files:
|
104
|
+
- History.txt
|
105
|
+
- Manifest.txt
|
106
|
+
- README.rdoc
|
107
|
+
files:
|
108
|
+
- .autotest
|
109
|
+
- History.txt
|
110
|
+
- Manifest.txt
|
111
|
+
- README.rdoc
|
112
|
+
- Rakefile
|
113
|
+
- bin/pg_ldap_sync
|
114
|
+
- config/sample-config.yaml
|
115
|
+
- config/sample-config2.yaml
|
116
|
+
- config/schema.yaml
|
117
|
+
- lib/pg_ldap_sync.rb
|
118
|
+
- lib/pg_ldap_sync/application.rb
|
119
|
+
- test/fixtures/config-ldapdb.yaml
|
120
|
+
- test/fixtures/ldapdb.yaml
|
121
|
+
- test/ldap_server.rb
|
122
|
+
- test/test_pg_ldap_sync.rb
|
123
|
+
has_rdoc: true
|
124
|
+
homepage: http://github.com/larskanis/pg-ldap-sync
|
125
|
+
licenses: []
|
126
|
+
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options:
|
129
|
+
- --main
|
130
|
+
- README.rdoc
|
131
|
+
- --charset=UTF-8
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
hash: 3
|
140
|
+
segments:
|
141
|
+
- 0
|
142
|
+
version: "0"
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
hash: 3
|
149
|
+
segments:
|
150
|
+
- 0
|
151
|
+
version: "0"
|
152
|
+
requirements: []
|
153
|
+
|
154
|
+
rubyforge_project: pg-ldap-sync
|
155
|
+
rubygems_version: 1.3.7
|
156
|
+
signing_key:
|
157
|
+
specification_version: 3
|
158
|
+
summary: LDAP is often used to do a centralized user and role management in an enterprise environment
|
159
|
+
test_files:
|
160
|
+
- test/test_pg_ldap_sync.rb
|