hypershield 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1b167b3edd07f94a46e64afb6bca7f78f84af7cf722335ce5a518a78e8fa5258
4
+ data.tar.gz: dbc93219926d4d02b719fae8592ad5ffba3240240b7db58562f1a05f1d6427f2
5
+ SHA512:
6
+ metadata.gz: 0c92349ce9da647caec2ee487d815136340c25f1b42190b2589c0173b610ed7865648421db72a5a950aee9b87dbd421fcc02d0d248d945db9b9202507b425cdc
7
+ data.tar.gz: 3da557fabb3e16e852f8a1762d82de60078e10a6863b340fdef76c3708f3e979555aff123623fc3c566088a8d9de8d7addd1c7ce4ebb21446cb548050ce39246
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - First release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in hypershield.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Andrew
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,118 @@
1
+ # Hypershield
2
+
3
+ :zap: Shield sensitive data in Postgres and MySQL
4
+
5
+ Great for business intelligence tools like [Blazer](https://github.com/ankane/blazer)
6
+
7
+ ## How It Works
8
+
9
+ Hypershield creates *shielded views* (in the `hypershield` schema by default) that hide sensitive tables and columns. By default, it hides columns with:
10
+
11
+ - `encrypted`
12
+ - `password`
13
+ - `token`
14
+ - `secret`
15
+
16
+ Give database users access to these views instead of the original tables. You can set the `search_path` so queries don’t need to be schema-qualified. The advantage of this approach over column-level privileges is you can use `SELECT *`.
17
+
18
+ ## Database Setup
19
+
20
+ ### Postgres
21
+
22
+ Create a new schema in your database
23
+
24
+ ```sql
25
+ CREATE SCHEMA hypershield;
26
+ ```
27
+
28
+ Grant privileges
29
+
30
+ ```sql
31
+ GRANT USAGE ON SCHEMA hypershield TO myuser;
32
+
33
+ -- replace migrations with the user who manages your schema
34
+ ALTER DEFAULT PRIVILEGES FOR ROLE migrations IN SCHEMA hypershield
35
+ GRANT SELECT ON TABLES TO myuser;
36
+
37
+ -- keep public in search path for functions
38
+ ALTER ROLE myuser SET search_path TO hypershield, public;
39
+ ```
40
+
41
+ And connect as the user and make sure there’s no access the original tables
42
+
43
+ ```sql
44
+ SELECT * FROM public.users LIMIT 1;
45
+ ```
46
+
47
+ ### MySQL
48
+
49
+ Create a new schema in your database
50
+
51
+ ```sql
52
+ CREATE SCHEMA hypershield;
53
+ ```
54
+
55
+ Grant privileges
56
+
57
+ ```sql
58
+ GRANT SELECT, SHOW VIEW ON hypershield.* TO myuser;
59
+ FLUSH PRIVILEGES;
60
+ ```
61
+
62
+ And connect as the user and make sure there’s no access the original tables
63
+
64
+ ```sql
65
+ SELECT * FROM mydb.users LIMIT 1;
66
+ ```
67
+
68
+ ## Installation
69
+
70
+ Add this line to your application’s Gemfile:
71
+
72
+ ```ruby
73
+ gem 'hypershield', group: :production
74
+ ```
75
+
76
+ Refresh the schema
77
+
78
+ ```sh
79
+ rake hypershield:refresh
80
+ ```
81
+
82
+ And query away on your shielded views
83
+
84
+ ```sql
85
+ SELECT * FROM users LIMIT 1;
86
+ ```
87
+
88
+ When you run database migrations, the schema is automatically refreshed.
89
+
90
+ ## Configuration
91
+
92
+ Specify the schema to use and columns to show and hide
93
+
94
+ ```ruby
95
+ Hypershield.schemas = {
96
+ hypershield: {
97
+ hide: %w(encrypted password token secret),
98
+ show: %w(ahoy_visits.visit_token)
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## TODO
104
+
105
+ - Create CLI
106
+
107
+ ## History
108
+
109
+ View the [changelog](CHANGELOG.md)
110
+
111
+ ## Contributing
112
+
113
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
114
+
115
+ - [Report bugs](https://github.com/ankane/hypershield/issues)
116
+ - Fix bugs and [submit pull requests](https://github.com/ankane/hypershield/pulls)
117
+ - Write, clarify, or fix documentation
118
+ - Suggest or add new features
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,28 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "hypershield/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hypershield"
8
+ spec.version = Hypershield::VERSION
9
+ spec.authors = ["Andrew Kane"]
10
+ spec.email = ["andrew@chartkick.com"]
11
+
12
+ spec.summary = "Shield sensitive data in Postgres and MySQL"
13
+ spec.homepage = "https://github.com/ankane/hypershield"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "activerecord"
24
+
25
+ spec.add_development_dependency "bundler"
26
+ spec.add_development_dependency "minitest"
27
+ spec.add_development_dependency "rake"
28
+ end
@@ -0,0 +1,139 @@
1
+ require "active_support"
2
+
3
+ require "hypershield/migration"
4
+ require "hypershield/railtie" if defined?(Rails)
5
+ require "hypershield/version"
6
+
7
+ module Hypershield
8
+ class << self
9
+ attr_accessor :schemas
10
+ end
11
+ self.schemas = {
12
+ hypershield: {
13
+ hide: %w(encrypted password token secret),
14
+ show: []
15
+ }
16
+ }
17
+
18
+ class << self
19
+ def drop_view(view)
20
+ schemas.each do |schema, _|
21
+ execute("DROP VIEW IF EXISTS #{quote_ident(schema)}.#{quote_ident(view)}")
22
+ end
23
+ end
24
+
25
+ def refresh
26
+ if adapter_name =~ /sqlite/i
27
+ raise "Adapter not supported: #{adapter_name}"
28
+ end
29
+
30
+ quiet_logging do
31
+ statements = []
32
+
33
+ schemas.each do |schema, config|
34
+ hide = config[:hide].to_a
35
+ show = config[:show].to_a
36
+
37
+ tables.sort_by { |k, _| k }.each do |table, columns|
38
+ next if table == "pg_stat_statements"
39
+
40
+ statements << "DROP VIEW IF EXISTS #{quote_ident(schema)}.#{quote_ident(table)} CASCADE"
41
+
42
+ columns.reject! do |column|
43
+ hide.any? { |m| "#{table}.#{column}".include?(m) } &&
44
+ !show.any? { |m| "#{table}.#{column}".include?(m) }
45
+ end
46
+
47
+ if columns.any?
48
+ statements << "CREATE VIEW #{quote_ident(schema)}.#{quote_ident(table)} AS SELECT #{columns.map { |c| quote_ident(c) }.join(", ")} FROM #{quote_ident(table)}"
49
+ end
50
+ end
51
+ end
52
+
53
+ if statements.any?
54
+ connection.transaction do
55
+ if mysql?
56
+ statements.each do |statement|
57
+ execute(statement)
58
+ end
59
+ else
60
+ execute(statements.join(";\n"))
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def quiet_logging
70
+ if ActiveRecord::Base.logger
71
+ previous_level = ActiveRecord::Base.logger.level
72
+ begin
73
+ ActiveRecord::Base.logger.level = Logger::INFO
74
+ yield
75
+ ensure
76
+ ActiveRecord::Base.logger.level = previous_level
77
+ end
78
+ else
79
+ yield
80
+ end
81
+ end
82
+
83
+ def connection
84
+ ActiveRecord::Base.connection
85
+ end
86
+
87
+ def adapter_name
88
+ connection.adapter_name
89
+ end
90
+
91
+ def mysql?
92
+ adapter_name =~ /mysql/i
93
+ end
94
+
95
+ def tables
96
+ # TODO make schema configurable
97
+ schema =
98
+ if mysql?
99
+ "database()"
100
+ else
101
+ "'public'"
102
+ end
103
+
104
+ query = <<-SQL
105
+ SELECT
106
+ table_name,
107
+ column_name,
108
+ ordinal_position,
109
+ data_type
110
+ FROM
111
+ information_schema.columns
112
+ WHERE
113
+ table_schema = #{schema}
114
+ SQL
115
+
116
+ Hash[
117
+ select_all(query)
118
+ .group_by { |c| c["table_name"] }
119
+ .map { |t, cs| [t, cs.sort_by { |c| c["ordinal_position"].to_i }.map { |c| c["column_name"] }] }
120
+ ]
121
+ end
122
+
123
+ def select_all(sql)
124
+ connection.select_all(sql).to_a
125
+ end
126
+
127
+ def execute(sql)
128
+ connection.execute(sql)
129
+ end
130
+
131
+ def quote_ident(ident)
132
+ connection.quote_table_name(ident.to_s)
133
+ end
134
+ end
135
+ end
136
+
137
+ ActiveSupport.on_load(:active_record) do
138
+ ActiveRecord::Migration.prepend(Hypershield::Migration)
139
+ end
@@ -0,0 +1,14 @@
1
+ module Hypershield
2
+ module Migration
3
+ def method_missing(method, *args)
4
+ if [
5
+ :change_column, :drop_table, :remove_column, :remove_columns,
6
+ :remove_timestamps, :rename_column, :rename_table
7
+ ].include?(method)
8
+ Hypershield.drop_view(args[0])
9
+ end
10
+
11
+ super
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module Hypershield
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ namespace :hypershield do
5
+ task refresh: :environment do
6
+ $stderr.puts "[hypershield] Refreshing schemas"
7
+ Hypershield.refresh
8
+ $stderr.puts "[hypershield] Success!"
9
+ end
10
+ end
11
+
12
+ Rake::Task["db:migrate"].enhance do
13
+ Rake::Task["hypershield:refresh"].invoke
14
+ end
15
+
16
+ Rake::Task["db:rollback"].enhance do
17
+ Rake::Task["hypershield:refresh"].invoke
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Hypershield
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hypershield
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
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: minitest
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: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - andrew@chartkick.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - CHANGELOG.md
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - hypershield.gemspec
83
+ - lib/hypershield.rb
84
+ - lib/hypershield/migration.rb
85
+ - lib/hypershield/railtie.rb
86
+ - lib/hypershield/version.rb
87
+ homepage: https://github.com/ankane/hypershield
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.7.6
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Shield sensitive data in Postgres and MySQL
111
+ test_files: []