grantinee 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +68 -0
- data/.circleci/setup-rubygems.sh +3 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +37 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +15 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +59 -0
- data/Grantinee +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/Rakefile +14 -0
- data/bin/console +16 -0
- data/bin/setup +10 -0
- data/config/grantinee.rb +29 -0
- data/docker-compose.yml +34 -0
- data/exe/grantinee +15 -0
- data/grantinee.gemspec +33 -0
- data/lib/grantinee.rb +37 -0
- data/lib/grantinee/cli.rb +128 -0
- data/lib/grantinee/configuration.rb +51 -0
- data/lib/grantinee/dsl.rb +63 -0
- data/lib/grantinee/engine.rb +67 -0
- data/lib/grantinee/engine/abstract_engine.rb +48 -0
- data/lib/grantinee/engine/mysql.rb +87 -0
- data/lib/grantinee/engine/postgresql.rb +80 -0
- data/lib/grantinee/executor.rb +34 -0
- data/lib/grantinee/version.rb +5 -0
- metadata +161 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grantinee
|
4
|
+
module Engine
|
5
|
+
class AbstractEngine
|
6
|
+
NOT_IMPLEMENTED = "Not implemented".freeze
|
7
|
+
|
8
|
+
def logger
|
9
|
+
Grantinee.logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
raise NOT_IMPLEMENTED
|
14
|
+
end
|
15
|
+
|
16
|
+
def flush_permissions!
|
17
|
+
raise NOT_IMPLEMENTED
|
18
|
+
end
|
19
|
+
|
20
|
+
def revoke_permissions!(_data)
|
21
|
+
raise NOT_IMPLEMENTED
|
22
|
+
end
|
23
|
+
|
24
|
+
def grant_permission!(_data)
|
25
|
+
raise NOT_IMPLEMENTED
|
26
|
+
end
|
27
|
+
|
28
|
+
def run!(_query, _data = {})
|
29
|
+
raise NOT_IMPLEMENTED
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sanitize one value piece
|
33
|
+
def sanitize_value(_value)
|
34
|
+
raise NOT_IMPLEMENTED
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sanitize column name
|
38
|
+
def sanitize_column_name(_name)
|
39
|
+
raise NOT_IMPLEMENTED
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sanitize table name
|
43
|
+
def sanitize_table_name(_name)
|
44
|
+
raise NOT_IMPLEMENTED
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem 'mysql2', '>= 0.4.4', '< 0.6.0'
|
4
|
+
require 'mysql2'
|
5
|
+
|
6
|
+
module Grantinee
|
7
|
+
module Engine
|
8
|
+
class Mysql < AbstractEngine
|
9
|
+
def initialize
|
10
|
+
configuration = Grantinee.configuration
|
11
|
+
|
12
|
+
@connection = Mysql2::Client.new(
|
13
|
+
username: configuration.username,
|
14
|
+
password: configuration.password,
|
15
|
+
host: configuration.hostname,
|
16
|
+
port: configuration.port,
|
17
|
+
database: configuration.database
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def flush_permissions!
|
22
|
+
query = "FLUSH PRIVILEGES;"
|
23
|
+
|
24
|
+
run! query
|
25
|
+
end
|
26
|
+
|
27
|
+
def revoke_permissions!(data)
|
28
|
+
database = sanitize_column_name(data[:database])
|
29
|
+
user = sanitize_value(data[:user])
|
30
|
+
host = sanitize_value(data[:host])
|
31
|
+
|
32
|
+
query = "REVOKE ALL PRIVILEGES ON #{database}.* FROM '#{user}'@'#{host}';"
|
33
|
+
run! query, data
|
34
|
+
end
|
35
|
+
|
36
|
+
def grant_permission!(data) # rubocop:disable Metrics/AbcSize
|
37
|
+
raise "Invalid permission kind" unless WHITELISTED_KINDS.include?(data[:kind])
|
38
|
+
|
39
|
+
database = sanitize_column_name(data[:database])
|
40
|
+
kind = data[:kind]
|
41
|
+
table = sanitize_table_name(data[:table])
|
42
|
+
user = sanitize_value(data[:user])
|
43
|
+
host = sanitize_value(data[:host])
|
44
|
+
fields = data[:fields].map { |v| sanitize_column_name(v.to_s) }.join(', ')
|
45
|
+
|
46
|
+
query = if data[:fields].empty?
|
47
|
+
"GRANT #{kind} ON #{database}.#{table} TO '#{user}'@'#{host}';"
|
48
|
+
else
|
49
|
+
"GRANT #{kind}(#{fields}) ON #{database}.#{table} TO '#{user}'@'#{host}';"
|
50
|
+
end
|
51
|
+
run! query, data
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def sanitize_value(value)
|
57
|
+
@connection.escape value
|
58
|
+
end
|
59
|
+
|
60
|
+
def sanitize_column_name(name)
|
61
|
+
"`#{name.to_s.gsub('`', '``')}`"
|
62
|
+
end
|
63
|
+
|
64
|
+
def sanitize_table_name(name)
|
65
|
+
sanitize_column_name(name).gsub('.', '`.`')
|
66
|
+
end
|
67
|
+
|
68
|
+
def run!(query, data = {})
|
69
|
+
logger.info query
|
70
|
+
|
71
|
+
begin
|
72
|
+
@connection.query query
|
73
|
+
rescue ::Mysql2::Error => e
|
74
|
+
case e.error_number
|
75
|
+
when 1141, 1269 # Can't revoke all privileges for one or more of the requested users
|
76
|
+
logger.debug format("User %{user}@%{host} doesn't have any grants yet", data)
|
77
|
+
when 1133 # Can't find any matching row in the user table
|
78
|
+
logger.fatal format("User %{user}@%{host} doesn't exist yet, create it with \"CREATE USER '%{user}'@'%{host}';\" first", data) # rubocop:disable Metrics/LineLength
|
79
|
+
else
|
80
|
+
logger.debug e.error_number
|
81
|
+
raise e
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem 'pg', '>= 0.18', '< 2.0'
|
4
|
+
require 'pg'
|
5
|
+
|
6
|
+
module Grantinee
|
7
|
+
module Engine
|
8
|
+
class Postgresql < AbstractEngine
|
9
|
+
def initialize
|
10
|
+
configuration = Grantinee.configuration
|
11
|
+
|
12
|
+
@connection = PG::Connection.open(
|
13
|
+
user: configuration.username,
|
14
|
+
password: configuration.password,
|
15
|
+
host: configuration.hostname,
|
16
|
+
port: configuration.port,
|
17
|
+
dbname: configuration.database
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def flush_permissions!
|
22
|
+
# Postgres doesn't need to flush privileges
|
23
|
+
end
|
24
|
+
|
25
|
+
def revoke_permissions!(data)
|
26
|
+
database = sanitize_column_name(data[:database])
|
27
|
+
user = sanitize_column_name(data[:database])
|
28
|
+
|
29
|
+
query = "REVOKE ALL PRIVILEGES ON DATABASE #{database} FROM #{user};"
|
30
|
+
run! query, data
|
31
|
+
end
|
32
|
+
|
33
|
+
def grant_permission!(data)
|
34
|
+
raise "Invalid permission kind" unless WHITELISTED_KINDS.include?(data[:kind])
|
35
|
+
|
36
|
+
kind = data[:kind]
|
37
|
+
table = sanitize_table_name(data[:table])
|
38
|
+
user = sanitize_column_name(data[:user])
|
39
|
+
fields = data[:fields].map { |v| sanitize_column_name(v.to_s) }.join(', ')
|
40
|
+
|
41
|
+
query = if data[:fields].empty?
|
42
|
+
"GRANT #{kind} ON #{table} TO #{user};"
|
43
|
+
else
|
44
|
+
"GRANT #{kind}(#{fields}) ON TABLE #{table} TO #{user};"
|
45
|
+
end
|
46
|
+
run! query, data
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def sanitize_value(value)
|
52
|
+
@connection.escape_string value.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def sanitize_column_name(name)
|
56
|
+
@connection.quote_ident name.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def sanitize_table_name(name)
|
60
|
+
@connection.quote_ident name.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def run!(query, data = {})
|
64
|
+
logger.info query
|
65
|
+
|
66
|
+
begin
|
67
|
+
@connection.exec query
|
68
|
+
rescue PG::Error => e
|
69
|
+
case e
|
70
|
+
when PG::UndefinedObject
|
71
|
+
logger.fatal format("User %{user}@%{host} doesn't exist yet, create it with \"CREATE ROLE %{user};\" first", data) # rubocop:disable Metrics/LineLength
|
72
|
+
else
|
73
|
+
logger.debug e.class
|
74
|
+
raise e
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grantinee
|
4
|
+
class Executor
|
5
|
+
def initialize(dsl, engine = Executor.default_engine)
|
6
|
+
@dsl = dsl
|
7
|
+
@engine = engine
|
8
|
+
end
|
9
|
+
|
10
|
+
def run!
|
11
|
+
revoke_permissions
|
12
|
+
grant_permissions
|
13
|
+
flush_permissions
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.default_engine
|
17
|
+
Grantinee::Engine.for(Grantinee.configuration.engine)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def revoke_permissions
|
23
|
+
@dsl.permissions.each { |data| @engine.revoke_permissions!(data) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def grant_permissions
|
27
|
+
@dsl.permissions.each { |data| @engine.grant_permission!(data) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def flush_permissions
|
31
|
+
@engine.flush_permissions!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grantinee
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paweł Komarnicki
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: byebug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: method_source
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A Ruby library to manage your database permissions for MySQL and PostgreSQL.
|
98
|
+
Supports per-table, and per-column permissions for granular access and security.
|
99
|
+
email:
|
100
|
+
- pawel@blinkist.com
|
101
|
+
executables:
|
102
|
+
- grantinee
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".circleci/config.yml"
|
107
|
+
- ".circleci/setup-rubygems.sh"
|
108
|
+
- ".gitignore"
|
109
|
+
- ".rspec"
|
110
|
+
- ".rubocop.yml"
|
111
|
+
- ".travis.yml"
|
112
|
+
- CODE_OF_CONDUCT.md
|
113
|
+
- Dockerfile
|
114
|
+
- Gemfile
|
115
|
+
- Gemfile.lock
|
116
|
+
- Grantinee
|
117
|
+
- LICENSE.txt
|
118
|
+
- README.md
|
119
|
+
- Rakefile
|
120
|
+
- bin/console
|
121
|
+
- bin/setup
|
122
|
+
- config/grantinee.rb
|
123
|
+
- docker-compose.yml
|
124
|
+
- exe/grantinee
|
125
|
+
- grantinee.gemspec
|
126
|
+
- lib/grantinee.rb
|
127
|
+
- lib/grantinee/cli.rb
|
128
|
+
- lib/grantinee/configuration.rb
|
129
|
+
- lib/grantinee/dsl.rb
|
130
|
+
- lib/grantinee/engine.rb
|
131
|
+
- lib/grantinee/engine/abstract_engine.rb
|
132
|
+
- lib/grantinee/engine/mysql.rb
|
133
|
+
- lib/grantinee/engine/postgresql.rb
|
134
|
+
- lib/grantinee/executor.rb
|
135
|
+
- lib/grantinee/version.rb
|
136
|
+
homepage: https://github.com/blinkist/grantinee
|
137
|
+
licenses:
|
138
|
+
- MIT
|
139
|
+
metadata: {}
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 2.5.2.2
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: '"Your permissions, freshly baked!" | A library to manage your database permissions
|
160
|
+
for MySQL and Postgres'
|
161
|
+
test_files: []
|