pg-ldap-sync 0.1.0
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.
- 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
|