forty 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 97efc5c8cb7262d1dfb61406f68497e31d49d90d
4
- data.tar.gz: 393e67307c31c8273709d61bff8b6428186925ce
3
+ metadata.gz: 1c69cf32507f1a087a044f2571606170c3c28459
4
+ data.tar.gz: 7d13e1d39898bc33077e627463abff4040f0bb97
5
5
  SHA512:
6
- metadata.gz: de46378f836e770d1d02f9e7319691ffc08dd59165255fd09440b86cba41f23e786674d2b84f18d2aa007ccdafaada6410b867bcb4bb7e41f3aeb011b29529cd
7
- data.tar.gz: f866784cdcbce84f15f36943e0d188eaf731bdea969f0043ea2839040431c39fa518781399a24ca493bb18f4330af4f326d55101243e974f6c0f565813f9a489
6
+ metadata.gz: a77cdb8ce81e28740ece0fd5d6fc02809cb9c36d855b638224480476f07f18b63b4437882453e036d496fb31d63ddfd9c1d68d4b5beb69b7cf3bd9a5e40cc0e7
7
+ data.tar.gz: 7d5afa71ef584d53e72ae07b3a72741028fdabec327246381a8eb7d2792dd64d40a82f74c950822528d61eb73fcfd55566cdad1406836e10cda82831633a92c4
@@ -1,3 +1,5 @@
1
+ require 'logger'
2
+
1
3
  module Forty
2
4
  class Configuration
3
5
  attr_accessor :logger
@@ -5,6 +7,14 @@ module Forty
5
7
  attr_accessor :schemas
6
8
  attr_accessor :acl_file
7
9
  attr_accessor :generate_passwords
10
+
11
+ def initialize
12
+ @logger = ::Logger.new(STDOUT)
13
+ @logger.level = ::Logger::INFO
14
+ @logger.formatter = proc do |severity, _, _, message|
15
+ "[Forty] [#{severity}] #{message}\n"
16
+ end
17
+ end
8
18
  end
9
19
 
10
20
  class Database
@@ -19,7 +19,5 @@ module Forty
19
19
 
20
20
  @db.exec(statement)
21
21
  end
22
-
23
- alias_method :dwh, :execute
24
22
  end
25
23
  end
@@ -0,0 +1,69 @@
1
+ module Forty
2
+ module Privilege
3
+ class Base
4
+ PRIVILEGES = self.constants.map { |const| self.const_get(const) }
5
+
6
+ def self.get_privilege_name_by_acronym(acronym)
7
+ privilege = self.constants.select do |constant|
8
+ self.const_get(constant).eql?(acronym)
9
+ end[0]
10
+ privilege.nil? ? nil : privilege.to_s.downcase
11
+ end
12
+
13
+ def self.parse_privileges_from_string(privileges_string)
14
+ privileges = []
15
+ self.constants.each do |constant|
16
+ acronym = self.const_get(constant)
17
+ unless privileges_string.slice!(acronym).nil?
18
+ privileges << self.get_privilege_name_by_acronym(acronym)
19
+ end
20
+ break if privileges_string.empty?
21
+ end
22
+ privileges
23
+ end
24
+ end
25
+
26
+ # https://www.postgresql.org/docs/9.6/static/sql-grant.html
27
+ # r -- SELECT ("read")
28
+ # w -- UPDATE ("write")
29
+ # a -- INSERT ("append")
30
+ # d -- DELETE
31
+ # D -- TRUNCATE
32
+ # x -- REFERENCES
33
+ # t -- TRIGGER
34
+ # X -- EXECUTE
35
+ # U -- USAGE
36
+ # C -- CREATE
37
+ # c -- CONNECT
38
+ # T -- TEMPORARY
39
+ # arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects)
40
+ # * -- grant option for preceding privilege
41
+ #
42
+ # /yyyy -- role that granted this privilege
43
+
44
+ class Table < Base
45
+ ALL = 'arwdDxt'
46
+ SELECT = 'r'
47
+ UPDATE = 'W'
48
+ INSERT = 'a'
49
+ DELETE = 'd'
50
+ TRUNCATE = 'D'
51
+ REFERENCES = 'x'
52
+ TRIGGER = 't'
53
+ EXECUTE = 'X'
54
+ end
55
+
56
+ class Schema < Base
57
+ ALL = 'UC'
58
+ USAGE = 'U'
59
+ CREATE = 'C'
60
+ end
61
+
62
+ class Database < Base
63
+ ALL = 'CTc'
64
+ CREATE = 'C'
65
+ CONNECT = 'c'
66
+ TEMPORARY = 'T'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require_relative '../../forty'
3
+
4
+ module Forty
5
+ module Rake
6
+ class Task
7
+ include ::Rake::DSL if defined? ::Rake::DSL
8
+
9
+ def install_tasks
10
+ namespace :acl do
11
+ namespace :sync do
12
+ desc 'syncs entire acl config with database'
13
+ task :all, [:disable_dry_run] do
14
+ Forty.sync
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Forty::Rake::Task.new.install_tasks
data/lib/forty/sync.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # require_relative 'configuration'
2
2
 
3
3
  module Forty
4
+
4
5
  def self.sync
5
6
  Forty::Sync.new(
6
7
  Forty.configuration.logger,
@@ -19,6 +20,8 @@ module Forty
19
20
  @logger = logger or raise Error, 'No logger provided'
20
21
  @master_username = master_username or raise Error, 'No master username provided'
21
22
  @production_schemas = production_schemas or raise Error, 'No production schemas provided'
23
+ @system_groups = ["pg_signal_backend"]
24
+ @system_users = ["postgres"]
22
25
  @acl_config = acl_config or raise Error, 'No acl config provided'
23
26
  @acl_config['users'] ||= {}
24
27
  @acl_config['groups'] ||= {}
@@ -30,6 +33,7 @@ module Forty
30
33
  end
31
34
 
32
35
  def run
36
+ banner()
33
37
  sync_users()
34
38
  sync_groups()
35
39
  sync_user_groups()
@@ -37,13 +41,39 @@ module Forty
37
41
  sync_acl()
38
42
  end
39
43
 
44
+ def banner
45
+ @logger.info(<<-BANNER)
46
+ Starting sync...
47
+ ____ __
48
+ / __/___ _____/ /___ __
49
+ / /_/ __ \\/ ___/ __/ / / /
50
+ / __/ /_/ / / / /_/ /_/ /
51
+ /_/ \\____/_/ \\__/\\__, / Database ACL Sync
52
+ /____/ v0.2.0
53
+
54
+ ===============================================================================
55
+
56
+ Running in #{@dry_run ? 'DRY-MODE (not enforcing state)' : 'PRODUCTION-MODE (enforcing state)'}
57
+
58
+ Configuration:
59
+ Master user: #{@master_username}
60
+ Synced schemas: #{@production_schemas.join(', ')}
61
+ System users: #{@system_users.join(', ')}
62
+ System groups: #{@system_groups.join(', ')}
63
+
64
+ ===============================================================================
65
+ BANNER
66
+ end
67
+
40
68
  def sync_users
41
69
  current_users = _get_current_dwh_users.keys
42
70
  defined_users = @acl_config['users'].keys
43
71
 
44
- undefined_users = (current_users - defined_users).uniq.compact
72
+ undefined_users = (current_users - defined_users - @system_users).uniq.compact
45
73
  missing_users = (defined_users - current_users).uniq.compact
46
74
 
75
+ @logger.debug("Undefined users: #{undefined_users}")
76
+ @logger.debug("Missing users: #{missing_users}")
47
77
  undefined_users.each { |user| _delete_user(user) }
48
78
 
49
79
  missing_users.each do |user|
@@ -61,7 +91,7 @@ module Forty
61
91
  current_groups = _get_current_dwh_groups().keys
62
92
  defined_groups = @acl_config['groups'].keys
63
93
 
64
- undefined_groups = (current_groups - defined_groups).uniq.compact
94
+ undefined_groups = (current_groups - defined_groups - @system_groups).uniq.compact
65
95
  missing_groups = (defined_groups - current_groups).uniq.compact
66
96
 
67
97
  undefined_groups.each { |group| _delete_group(group) }
@@ -108,8 +138,8 @@ module Forty
108
138
  unless schemas_owned_by_user.empty?
109
139
  tables_owned_by_user = _get_currently_owned_tables(user)
110
140
  schemas_owned_by_user.each do |schema|
111
- @executor.dwh("set search_path=#{schema}")
112
- tables = @executor.dwh("select tablename from pg_tables where schemaname='#{schema}'").map { |row| "#{schema}.#{row['tablename']}" }
141
+ @executor.execute("set search_path=#{schema}")
142
+ tables = @executor.execute("select tablename from pg_tables where schemaname='#{schema}'").map { |row| "#{schema}.#{row['tablename']}" }
113
143
  nonowned_tables_by_user = tables.uniq - tables_owned_by_user
114
144
  nonowned_tables_by_user.each { |table| _execute_statement("alter table #{table} owner to #{user};") }
115
145
  end
@@ -126,7 +156,7 @@ module Forty
126
156
  diverged = 0
127
157
 
128
158
  users.each do |user|
129
- next if user.eql?(@master_username)
159
+ next if user.eql?(@master_username) or @system_users.include?(user)
130
160
 
131
161
  raise Error, "Users are not in sync #{user}" if current_user_roles[user].nil? or defined_user_roles[user].nil?
132
162
 
@@ -206,7 +236,7 @@ module Forty
206
236
  end
207
237
 
208
238
  def _get_current_user_roles
209
- Hash[@executor.dwh(<<-SQL).map { |row| [row['usename'], row['user_roles'].split(',').select { |e| not e.empty? }.compact] }]
239
+ Hash[@executor.execute(<<-SQL).map { |row| [row['usename'], row['user_roles'].split(',').select { |e| not e.empty? }.compact] }]
210
240
  select
211
241
  usename
212
242
  , case when usecreatedb is true then 'createdb' else '' end
@@ -221,11 +251,13 @@ module Forty
221
251
  end
222
252
 
223
253
  def _check_group_unknown(current_groups, defined_groups)
224
- raise Error, 'Groups are out of sync!' if _mismatch?(current_groups, defined_groups)
254
+ @logger.debug("Check whether groups are in sync. Current: #{current_groups}; Defined: #{defined_groups}; System: #{@system_groups}")
255
+ raise Error, 'Groups are out of sync!' if _mismatch?(current_groups - @system_groups, defined_groups)
225
256
  end
226
257
 
227
258
  def _check_user_unknown(current_users, defined_users)
228
- raise Error, 'Users are out of sync!' if _mismatch?(current_users, defined_users)
259
+ @logger.debug("Check whether users are in sync. Current: #{current_users}; Defined: #{defined_users}; System: #{@system_users}")
260
+ raise Error, 'Users are out of sync!' if _mismatch?(current_users - @system_users, defined_users)
229
261
  end
230
262
 
231
263
  def _mismatch?(current, defined)
@@ -243,7 +275,7 @@ module Forty
243
275
  @logger.info("Retrying to execute statement in #{attempts*10} seconds...") if attempts > 0
244
276
  sleep (attempts*10)
245
277
  attempts += 1
246
- @executor.dwh(statement)
278
+ @executor.execute(statement)
247
279
  rescue PG::UndefinedTable => e
248
280
  @logger.error("#{e.class}: #{e.message}" )
249
281
  retry unless attempts > 3
@@ -353,7 +385,7 @@ module Forty
353
385
  ;
354
386
  SQL
355
387
 
356
- raw_dwh_users = @executor.dwh(query)
388
+ raw_dwh_users = @executor.execute(query)
357
389
 
358
390
  Hash[raw_dwh_users.map do |row|
359
391
  name = row['name']
@@ -375,7 +407,7 @@ module Forty
375
407
  from pg_group
376
408
  ;
377
409
  SQL
378
- raw_dwh_groups = @executor.dwh(query)
410
+ raw_dwh_groups = @executor.execute(query)
379
411
 
380
412
  Hash[raw_dwh_groups.map do |row|
381
413
  name = row['name']
@@ -398,7 +430,7 @@ module Forty
398
430
  ;
399
431
  SQL
400
432
 
401
- raw_schema_acl = @executor.dwh(query)
433
+ raw_schema_acl = @executor.execute(query)
402
434
  _parse_current_acl('schema', raw_schema_acl)
403
435
  end
404
436
 
@@ -414,7 +446,7 @@ module Forty
414
446
  ;
415
447
  SQL
416
448
 
417
- raw_database_acl = @executor.dwh(query)
449
+ raw_database_acl = @executor.execute(query)
418
450
  _parse_current_acl('database', raw_database_acl)
419
451
  end
420
452
 
@@ -437,7 +469,7 @@ module Forty
437
469
  ;
438
470
  SQL
439
471
 
440
- raw_table_acl = @executor.dwh(query)
472
+ raw_table_acl = @executor.execute(query)
441
473
  _parse_current_acl('table', raw_table_acl)
442
474
  end
443
475
 
@@ -459,7 +491,7 @@ module Forty
459
491
  .compact
460
492
 
461
493
  if grantees.any? { |grantee| !known_grantees.include?(grantee) }
462
- raise Error, 'Users or groups not in sync!'
494
+ raise Error, "Users or groups not in sync! Could not find #{grantees.select { |grantee| !known_grantees.include?(grantee) }.join(', ')}"
463
495
  end
464
496
 
465
497
  grantees.each do |grantee|
@@ -613,14 +645,14 @@ module Forty
613
645
 
614
646
  if schema.eql?('*')
615
647
  @production_schemas.each do |prod_schema|
616
- tables = @executor.dwh(<<-SQL).map { |row| "#{prod_schema}.#{row['tablename']}" }
648
+ tables = @executor.execute(<<-SQL).map { |row| "#{prod_schema}.#{row['tablename']}" }
617
649
  select tablename from pg_tables where schemaname='#{prod_schema}'
618
650
  SQL
619
651
 
620
652
  tables_to_grant_privileges_on.concat(tables)
621
653
  end
622
654
  else
623
- tables_to_grant_privileges_on = @executor.dwh(<<-SQL).map { |row| "#{schema}.#{row['tablename']}" }
655
+ tables_to_grant_privileges_on = @executor.execute(<<-SQL).map { |row| "#{schema}.#{row['tablename']}" }
624
656
  select tablename from pg_tables where schemaname='#{schema}'
625
657
  SQL
626
658
  end
@@ -651,9 +683,14 @@ module Forty
651
683
  parsed_acls = {}
652
684
  raw_acl.each do |row|
653
685
  name = row['name']
686
+ @logger.debug("Current ACL: [#{identifier_type}] '#{name}': #{row['acls']}")
654
687
  parsed_acl = _parse_current_permissions(identifier_type, row['acls'])
655
688
  parsed_acl.each do |grantee, privileges|
656
689
  unless grantee.empty?
690
+ if _get_current_dwh_groups().keys.include?(grantee)
691
+ @logger.debug("Grantee '#{grantee}' has been identified as a group")
692
+ grantee = "group #{grantee}"
693
+ end
657
694
  parsed_acls[grantee] ||= {}
658
695
  parsed_acls[grantee][name] ||= []
659
696
  parsed_acls[grantee][name].concat(privileges)
@@ -667,7 +704,7 @@ module Forty
667
704
  # http://www.postgresql.org/docs/8.1/static/sql-grant.html
668
705
  # Typical ACL string:
669
706
  #
670
- # admin=arwdRxt/admin,jimdo=r/admin,"group selfservice=r/admin"
707
+ # admin=arwdRxt/admin,someone=r/admin,"group selfservice=r/admin"
671
708
  #
672
709
  # =xxxx -- privileges granted to PUBLIC
673
710
  # uname=xxxx -- privileges granted to a user
@@ -708,7 +745,7 @@ module Forty
708
745
  where pg_user.usename = '#{user}'
709
746
  ;
710
747
  SQL
711
- @executor.dwh(query).map { |row| row['schemaname'] }
748
+ @executor.execute(query).map { |row| row['schemaname'] }
712
749
  end
713
750
 
714
751
  def _get_currently_owned_tables(user)
@@ -718,7 +755,7 @@ module Forty
718
755
  where tableowner = '#{user}'
719
756
  ;
720
757
  SQL
721
- @executor.dwh(query).map { |row| row['tablename'] }
758
+ @executor.execute(query).map { |row| row['tablename'] }
722
759
  end
723
760
  end
724
761
  end
metadata CHANGED
@@ -1,15 +1,115 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefanie Grunwald
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-25 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2017-04-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.16'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0.16'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: cucumber
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '3.0'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '2.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '3.0'
53
+ - !ruby/object:Gem::Dependency
54
+ name: rake
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '10.1'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '12.0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '10.1'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '12.0'
73
+ - !ruby/object:Gem::Dependency
74
+ name: rspec
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '3.1'
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.1'
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '4.0'
93
+ - !ruby/object:Gem::Dependency
94
+ name: rspec-collection_matchers
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 1.1.2
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 1.1.2
110
+ - - "<"
111
+ - !ruby/object:Gem::Version
112
+ version: '2.0'
13
113
  description:
14
114
  email: steffi@physics.org
15
115
  executables: []
@@ -20,8 +120,8 @@ files:
20
120
  - lib/forty/acl.rb
21
121
  - lib/forty/configuration.rb
22
122
  - lib/forty/database.rb
23
- - lib/forty/dbms.rb
24
- - lib/forty/redshift/privilege.rb
123
+ - lib/forty/privilege.rb
124
+ - lib/forty/rake/task.rb
25
125
  - lib/forty/sync.rb
26
126
  homepage: https://github.com/moertel/forty
27
127
  licenses:
data/lib/forty/dbms.rb DELETED
@@ -1 +0,0 @@
1
- require_relative 'redshift/privilege'
@@ -1,49 +0,0 @@
1
- module Forty
2
- module Redshift
3
- module Privilege
4
- class Base
5
- PRIVILEGES = self.constants.map { |const| self.const_get(const) }
6
-
7
- def self.get_privilege_name_by_acronym(acronym)
8
- privilege = self.constants.select do |constant|
9
- self.const_get(constant).eql?(acronym)
10
- end[0]
11
- privilege.nil? ? nil : privilege.to_s.downcase
12
- end
13
-
14
- def self.parse_privileges_from_string(privileges_string)
15
- privileges = []
16
- self.constants.each do |constant|
17
- acronym = self.const_get(constant)
18
- unless privileges_string.slice!(acronym).nil?
19
- privileges << self.get_privilege_name_by_acronym(acronym)
20
- end
21
- break if privileges_string.empty?
22
- end
23
- privileges
24
- end
25
- end
26
-
27
- class Table < Base
28
- ALL = 'arwdRxt'
29
- SELECT = 'r'
30
- UPDATE = 'w'
31
- INSERT = 'a'
32
- DELETE = 'd'
33
- REFERENCES = 'x'
34
- end
35
-
36
- class Schema < Base
37
- ALL = 'UC'
38
- USAGE = 'U'
39
- CREATE = 'C'
40
- end
41
-
42
- class Database < Base
43
- ALL = 'CT'
44
- CREATE = 'C'
45
- TEMPORARY = 'T'
46
- end
47
- end
48
- end
49
- end