hypershield 0.1.0

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.
@@ -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: []