lib/pg_ldap_sync/application.rb in pg-ldap-sync-0.1.1 vs lib/pg_ldap_sync/application.rb in pg-ldap-sync-0.2.0

- old
+ new

@@ -1,40 +1,16 @@ #!/usr/bin/env ruby -require 'rubygems' require 'net/ldap' require 'optparse' require 'yaml' -require 'logger' require 'kwalify' +require 'pg' +require "pg_ldap_sync/logger" -begin - require 'pg' -rescue LoadError => e - begin - require 'postgres' - class PGconn - alias initialize_before_hash_change initialize - def initialize(*args) - arg = args.first - if args.length==1 && arg.kind_of?(Hash) - initialize_before_hash_change(arg[:host], arg[:port], nil, nil, arg[:dbname], arg[:user], arg[:password]) - else - initialize_before_hash_change(*args) - end - end - end - rescue LoadError - raise e - end -end - -require 'pg_ldap_sync' - module PgLdapSync class Application - class LdapError < RuntimeError; end attr_accessor :config_fname attr_accessor :log attr_accessor :test def string_to_symbol(hash) @@ -56,11 +32,11 @@ errors = validator.validate(config) if errors && !errors.empty? errors.each do |err| log.fatal "error in #{fname}: [#{err.path}] #{err.message}" end - exit(-1) + raise InvalidConfig, 78 # EX_CONFIG end end def read_config_file(fname) raise "Config file #{fname.inspect} does not exist" unless File.exist?(fname) @@ -128,17 +104,21 @@ return groups end PgRole = Struct.new :name, :member_names + # List of default roles taken from https://www.postgresql.org/docs/current/static/default-roles.html + PG_BUILTIN_ROLES = %w[ pg_signal_backend pg_monitor pg_read_all_settings pg_read_all_stats pg_stat_scan_tables] + def search_pg_users pg_users_conf = @config[:pg_users] users = [] res = pg_exec "SELECT rolname FROM pg_roles WHERE #{pg_users_conf[:filter]}" res.each do |tuple| user = PgRole.new tuple[0] + next if PG_BUILTIN_ROLES.include?(user.name) log.info{ "found pg-user: #{user.name.inspect}"} users << user end return users end @@ -147,13 +127,14 @@ pg_groups_conf = @config[:pg_groups] groups = [] res = pg_exec "SELECT rolname, oid FROM pg_roles WHERE #{pg_groups_conf[:filter]}" res.each do |tuple| - 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])}" + 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_string(tuple[1])}" member_names = res2.map{|row| row[0] } group = PgRole.new tuple[0], member_names + next if PG_BUILTIN_ROLES.include?(group.name) log.info{ "found pg-group: #{group.name.inspect} with members: #{member_names.inspect}"} groups << group end return groups end @@ -209,14 +190,25 @@ "#{type} stat: create: #{roles.count{|r| r.state==:create }} drop: #{roles.count{|r| r.state==:drop }} keep: #{roles.count{|r| r.state==:keep }}" } return roles end + def try_sql(text) + begin + @pgconn.exec "SAVEPOINT try_sql;" + @pgconn.exec text + rescue PG::Error => err + @pgconn.exec "ROLLBACK TO try_sql;" + + log.error{ "#{err} (#{err.class})" } + end + end + def pg_exec_modify(sql) log.info{ "SQL: #{sql}" } unless self.test - res = @pgconn.exec sql + try_sql sql end end def pg_exec(sql) res = @pgconn.exec sql @@ -312,39 +304,49 @@ @ldap = Net::LDAP.new @config[:ldap_connection] ldap_users = uniq_names search_ldap_users ldap_groups = uniq_names search_ldap_groups # gather PGs users and groups - @pgconn = PGconn.connect @config[:pg_connection] - pg_users = uniq_names search_pg_users - pg_groups = uniq_names search_pg_groups + @pgconn = PG.connect @config[:pg_connection] + begin + @pgconn.transaction do + pg_users = uniq_names search_pg_users + pg_groups = uniq_names search_pg_groups - # compare LDAP to PG users and groups - mroles = match_roles(ldap_users, pg_users, :user) - mroles += match_roles(ldap_groups, pg_groups, :group) + # compare LDAP to PG users and groups + mroles = match_roles(ldap_users, pg_users, :user) + mroles += match_roles(ldap_groups, pg_groups, :group) - # compare LDAP to PG memberships - mmemberships = match_memberships(ldap_users+ldap_groups, pg_users+pg_groups) + # compare LDAP to PG memberships + mmemberships = match_memberships(ldap_users+ldap_groups, pg_users+pg_groups) - # drop/revoke roles/memberships first - sync_membership_to_pg(mmemberships, :revoke) - sync_roles_to_pg(mroles, :drop) - # create/grant roles/memberships - sync_roles_to_pg(mroles, :create) - sync_membership_to_pg(mmemberships, :grant) + # drop/revoke roles/memberships first + sync_membership_to_pg(mmemberships, :revoke) + sync_roles_to_pg(mroles, :drop) + # create/grant roles/memberships + sync_roles_to_pg(mroles, :create) + sync_membership_to_pg(mmemberships, :grant) + end + ensure + @pgconn.close + end - @pgconn.close + # Determine exitcode + if log.had_errors? + raise ErrorExit, 1 + end end def self.run(argv) s = self.new s.config_fname = '/etc/pg_ldap_sync.yaml' - s.log = Logger.new(STDOUT) + s.log = Logger.new($stdout, @error_counters) s.log.level = Logger::ERROR OptionParser.new do |opts| + opts.version = VERSION opts.banner = "Usage: #{$0} [options]" - opts.on("-v", "--[no-]verbose", "Increase verbose level"){ s.log.level-=1 } + opts.on("-v", "--[no-]verbose", "Increase verbose level"){|v| s.log.level += v ? -1 : 1 } opts.on("-c", "--config FILE", "Config file [#{s.config_fname}]", &s.method(:config_fname=)) opts.on("-t", "--[no-]test", "Don't do any change in the database", &s.method(:test=)) opts.parse!(argv) end