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
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rubocop/rake_task'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
RuboCop::RakeTask.new do |task|
|
10
|
+
task.options = %w[-a]
|
11
|
+
end
|
12
|
+
|
13
|
+
task(:default).clear
|
14
|
+
task default: %i[rubocop spec]
|
data/bin/console
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'awesome_print'
|
6
|
+
require 'grantinee'
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
+
# require "pry"
|
13
|
+
# Pry.start
|
14
|
+
|
15
|
+
require 'irb'
|
16
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config/grantinee.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This is a sample configuration file for you to play with
|
4
|
+
Grantinee.configure do |c|
|
5
|
+
c.engine = :postgresql
|
6
|
+
|
7
|
+
case c.engine
|
8
|
+
when :mysql
|
9
|
+
c.username = 'root'
|
10
|
+
c.password = 'mysql'
|
11
|
+
c.hostname = ENV['MYSQL_HOST'] || 'localhost'
|
12
|
+
c.port = 3306
|
13
|
+
c.database = 'grantinee_development'
|
14
|
+
|
15
|
+
when :mysql_url
|
16
|
+
c.url = 'mysql://root:mysql@localhost:3306/grantinee_development'
|
17
|
+
|
18
|
+
when :postgresql
|
19
|
+
c.username = 'postgres'
|
20
|
+
c.password = 'postgres'
|
21
|
+
c.hostname = ENV['POSTGRES_HOST'] || 'localhost'
|
22
|
+
c.port = 5432
|
23
|
+
c.database = 'grantinee_development'
|
24
|
+
|
25
|
+
when :postgresql_url
|
26
|
+
c.url = 'postgres://postgres:postgres@localhost:5432/grantinee_development'
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
version: '3'
|
2
|
+
services:
|
3
|
+
|
4
|
+
mysql:
|
5
|
+
image: mysql:5.7.15
|
6
|
+
volumes:
|
7
|
+
- ./mysql:/var/lib/mysql
|
8
|
+
ports:
|
9
|
+
- "3306:3306"
|
10
|
+
environment:
|
11
|
+
MYSQL_ROOT_PASSWORD: mysql
|
12
|
+
|
13
|
+
postgres:
|
14
|
+
image: postgres:9.6
|
15
|
+
volumes:
|
16
|
+
- ./postgres/data:/var/lib/postgresql/data
|
17
|
+
ports:
|
18
|
+
- "5432:5432"
|
19
|
+
environment:
|
20
|
+
- POSTGRES_PASSWORD=postgres
|
21
|
+
|
22
|
+
rspec:
|
23
|
+
environment:
|
24
|
+
MYSQL_HOST: 'mysql'
|
25
|
+
POSTGRES_HOST: 'postgres'
|
26
|
+
tty: true
|
27
|
+
stdin_open: true
|
28
|
+
build: .
|
29
|
+
command: bundle exec rspec
|
30
|
+
volumes:
|
31
|
+
- .:/myapp
|
32
|
+
depends_on:
|
33
|
+
- mysql
|
34
|
+
- postgres
|
data/exe/grantinee
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'grantinee'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
# Autodetect plug-and-play connection
|
9
|
+
Bundler.require if File.exist? 'Gemfile.lock'
|
10
|
+
|
11
|
+
# Logger setup
|
12
|
+
logger = Logger.new(STDOUT)
|
13
|
+
logger.level = Logger::INFO
|
14
|
+
|
15
|
+
Grantinee::CLI.new(ARGV, logger).run!
|
data/grantinee.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib"))
|
4
|
+
|
5
|
+
require "grantinee/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "grantinee"
|
9
|
+
spec.version = Grantinee::VERSION
|
10
|
+
spec.authors = ["Paweł Komarnicki"]
|
11
|
+
spec.email = ["pawel@blinkist.com"]
|
12
|
+
|
13
|
+
spec.summary = '"Your permissions, freshly baked!" | A library to manage your database permissions for MySQL and Postgres'
|
14
|
+
spec.description = "A Ruby library to manage your database permissions for MySQL and PostgreSQL. Supports per-table, and per-column permissions for granular access and security."
|
15
|
+
spec.homepage = "https://github.com/blinkist/grantinee"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
spec.files = Dir.chdir(File.dirname(__FILE__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
28
|
+
spec.add_development_dependency "byebug"
|
29
|
+
spec.add_development_dependency "method_source"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
spec.add_development_dependency "rubocop"
|
33
|
+
end
|
data/lib/grantinee.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# Grantinee module is where the magic at ;-)
|
6
|
+
module Grantinee
|
7
|
+
class << self
|
8
|
+
# Allow configuration using a block
|
9
|
+
def configure
|
10
|
+
yield configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns configuration
|
14
|
+
def configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def logger
|
19
|
+
configuration.logger
|
20
|
+
end
|
21
|
+
|
22
|
+
def logger=(logger)
|
23
|
+
configuration.logger = logger
|
24
|
+
end
|
25
|
+
|
26
|
+
extend Gem::Deprecate
|
27
|
+
deprecate :logger=, "Please provide logger via configure block", 2018, 7
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Load internal stuffs
|
32
|
+
require 'grantinee/configuration'
|
33
|
+
require 'grantinee/engine'
|
34
|
+
require 'grantinee/cli'
|
35
|
+
require 'grantinee/dsl'
|
36
|
+
require 'grantinee/executor'
|
37
|
+
require 'grantinee/engine/abstract_engine'
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grantinee
|
4
|
+
class CLI
|
5
|
+
attr_accessor :options
|
6
|
+
attr_accessor :dsl
|
7
|
+
attr_accessor :engine
|
8
|
+
|
9
|
+
def initialize(args = ARGV, logger = ::Logger.new($stderr))
|
10
|
+
@args = args
|
11
|
+
@logger = logger
|
12
|
+
|
13
|
+
@options = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def run!
|
17
|
+
parse_command_line_parameters
|
18
|
+
process_command_line_parameters
|
19
|
+
|
20
|
+
@dsl = build_dsl
|
21
|
+
@engine = build_engine
|
22
|
+
@executor = build_executor
|
23
|
+
@executor.run!
|
24
|
+
|
25
|
+
[@dsl, @engine, @executor]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_command_line_parameters # rubocop:disable Metrics/MethodLength
|
31
|
+
parser = OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
|
32
|
+
opts.banner = "Usage: grantinee [options]"
|
33
|
+
|
34
|
+
# Help
|
35
|
+
opts.on(
|
36
|
+
'-h', '--help',
|
37
|
+
"Displays help"
|
38
|
+
) do
|
39
|
+
puts opts
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
# Verbose mode
|
44
|
+
opts.on(
|
45
|
+
'-vLEVEL', '--verbosity=LEVEL',
|
46
|
+
"Set verbosity level to debug, info, warn, error, fatal, or unknown (default: warning)"
|
47
|
+
) do |level|
|
48
|
+
@options[:verbose] = level || 'warning'
|
49
|
+
end
|
50
|
+
|
51
|
+
# App boot file
|
52
|
+
opts.on(
|
53
|
+
'-rFILE', '--require=FILE',
|
54
|
+
"Application boot file path (default: ./config/environment.rb)"
|
55
|
+
) do |file_path|
|
56
|
+
@options[:require] = file_path
|
57
|
+
end
|
58
|
+
|
59
|
+
# Grantinee file
|
60
|
+
opts.on(
|
61
|
+
'-fFILE', '--file=FILE',
|
62
|
+
"Permission definitions file path (default: ./Grantinee)"
|
63
|
+
) do |file_path|
|
64
|
+
@options[:file] = file_path
|
65
|
+
end
|
66
|
+
|
67
|
+
# Database configuration file
|
68
|
+
opts.on(
|
69
|
+
'-cFILE', '--config=FILE',
|
70
|
+
"Database configuration file path"
|
71
|
+
) do |file_path|
|
72
|
+
@options[:config] = file_path
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
parser.parse! @args
|
77
|
+
end
|
78
|
+
|
79
|
+
# Process parsed parameters
|
80
|
+
def process_command_line_parameters
|
81
|
+
process_require_param
|
82
|
+
process_database_param
|
83
|
+
process_grantinee_param
|
84
|
+
process_verbosity_param
|
85
|
+
end
|
86
|
+
|
87
|
+
def build_dsl
|
88
|
+
Grantinee.configuration.logger = @logger
|
89
|
+
Grantinee::Dsl.eval(File.read(@options[:file]))
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_engine
|
93
|
+
Grantinee::Engine.for Grantinee.configuration.engine
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_executor
|
97
|
+
Grantinee::Executor.new(@dsl, @engine)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Application boot file
|
101
|
+
def process_require_param
|
102
|
+
if @options[:require]
|
103
|
+
require @options[:require]
|
104
|
+
elsif defined?(Rails)
|
105
|
+
require './config/environment'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Database configuration file
|
110
|
+
def process_database_param
|
111
|
+
require options[:config] if options[:config]
|
112
|
+
|
113
|
+
Grantinee::Engine.detect_active_record_connection! unless Grantinee.configuration.configured?
|
114
|
+
end
|
115
|
+
|
116
|
+
# Grantinee file
|
117
|
+
def process_grantinee_param
|
118
|
+
@options[:file] ||= "Grantinee"
|
119
|
+
end
|
120
|
+
|
121
|
+
# Explicit verbose mode, overrides configuration value
|
122
|
+
def process_verbosity_param
|
123
|
+
return unless @options[:verbose]
|
124
|
+
log_levels = %w[debug info warn error fatal unknown]
|
125
|
+
@logger.level = log_levels.index(@options[:verbose])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Grantinee
|
6
|
+
class Configuration
|
7
|
+
# Which engine is used by the library?
|
8
|
+
attr_accessor :engine
|
9
|
+
|
10
|
+
attr_accessor :logger
|
11
|
+
|
12
|
+
# Connection parameters
|
13
|
+
attr_accessor :username
|
14
|
+
attr_accessor :password
|
15
|
+
attr_accessor :hostname
|
16
|
+
attr_accessor :port
|
17
|
+
attr_accessor :database
|
18
|
+
attr_reader :url
|
19
|
+
|
20
|
+
# Allow verbose mode
|
21
|
+
attr_accessor :verbose
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@logger = ::Logger.new(nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
def configured?
|
28
|
+
username && password && hostname && port && database
|
29
|
+
end
|
30
|
+
|
31
|
+
# Handle url -> fields conversion
|
32
|
+
def url=(url)
|
33
|
+
uri = URI.parse url
|
34
|
+
|
35
|
+
case uri.scheme
|
36
|
+
when /^mysql/
|
37
|
+
default_port = 3306
|
38
|
+
@engine = :mysql
|
39
|
+
when /^postgres/
|
40
|
+
default_port = 5432
|
41
|
+
@engine = :postgres
|
42
|
+
end
|
43
|
+
|
44
|
+
@username = uri.user
|
45
|
+
@password = uri.password
|
46
|
+
@hostname = uri.host
|
47
|
+
@port = uri.port || default_port
|
48
|
+
@database = (uri.path || '').split('/').last
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grantinee
|
4
|
+
class Dsl
|
5
|
+
attr_accessor :permissions
|
6
|
+
|
7
|
+
# Allow evaluation of the code coming from the Grantinee file
|
8
|
+
def self.eval(commands)
|
9
|
+
new.tap do |x|
|
10
|
+
x.eval(commands)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Initialize defaults
|
15
|
+
def initialize
|
16
|
+
@permissions = []
|
17
|
+
@data = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval(commands)
|
21
|
+
instance_eval(commands)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Define database and mode
|
25
|
+
def on(database, &block)
|
26
|
+
logger.debug "Got database: #{database}"
|
27
|
+
|
28
|
+
@data[:database] = database
|
29
|
+
|
30
|
+
instance_eval(&block) if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Define user and host
|
34
|
+
# Note: revokes all permissions for given user first
|
35
|
+
def user(user, &block)
|
36
|
+
logger.debug "Got user: #{user}"
|
37
|
+
|
38
|
+
@data[:user], @data[:host] = user.to_s.split '@'
|
39
|
+
@data[:host] ||= '%'
|
40
|
+
|
41
|
+
instance_eval(&block) if block_given?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define permission grants
|
45
|
+
Engine::WHITELISTED_KINDS.each do |kind|
|
46
|
+
define_method(kind.to_sym) do |table, fields = []|
|
47
|
+
logger.debug "Got table: #{table}, fields: #{fields}"
|
48
|
+
|
49
|
+
@permissions << @data.merge(
|
50
|
+
kind: kind,
|
51
|
+
table: table,
|
52
|
+
fields: fields
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def logger
|
60
|
+
Grantinee.logger
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grantinee
|
4
|
+
module Engine
|
5
|
+
SUPPORTED_ENGINES = %w[mysql postgresql].freeze
|
6
|
+
|
7
|
+
WHITELISTED_KINDS = %w[all usage select update insert].freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Get appropriate engine class for the engine name
|
11
|
+
def for(engine)
|
12
|
+
logger.debug "Using engine: #{engine}"
|
13
|
+
unless SUPPORTED_ENGINES.include?(engine.to_s)
|
14
|
+
raise "Engine '#{engine}' is not supported, supported engines: #{SUPPORTED_ENGINES}"
|
15
|
+
end
|
16
|
+
|
17
|
+
case engine.to_s
|
18
|
+
when 'mysql'
|
19
|
+
require 'grantinee/engine/mysql'
|
20
|
+
Mysql.new
|
21
|
+
when 'postgresql'
|
22
|
+
require 'grantinee/engine/postgresql'
|
23
|
+
Postgresql.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def detect_active_record_connection!
|
28
|
+
return unless defined?(ActiveRecord::Base)
|
29
|
+
|
30
|
+
configure_for_active_record(ActiveRecord::Base.connection_config)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def logger
|
36
|
+
Grantinee.logger
|
37
|
+
end
|
38
|
+
|
39
|
+
def configure_for_active_record(ar_config)
|
40
|
+
if ar_config[:url]
|
41
|
+
configure_for_active_record_url(ar_config)
|
42
|
+
else
|
43
|
+
configure_for_active_record_fields(ar_config)
|
44
|
+
end
|
45
|
+
|
46
|
+
Grantinee.configuration.engine = case ar_config[:adapter]
|
47
|
+
when 'mysql', 'mysql2'
|
48
|
+
:mysql
|
49
|
+
when 'postgresql', 'pg'
|
50
|
+
:postgresql
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def configure_for_active_record_url(ar_config)
|
55
|
+
Grantinee.configuration.url = ar_config[:url]
|
56
|
+
end
|
57
|
+
|
58
|
+
def configure_for_active_record_fields(ar_config)
|
59
|
+
Grantinee.configuration.username = ar_config[:username]
|
60
|
+
Grantinee.configuration.password = ar_config[:password]
|
61
|
+
Grantinee.configuration.hostname = ar_config[:host]
|
62
|
+
Grantinee.configuration.port = ar_config[:port]
|
63
|
+
Grantinee.configuration.database = ar_config[:database]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|