bcdatabase 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gemspec
2
+ pkg
3
+ coverage
@@ -0,0 +1,29 @@
1
+ 1.0.0
2
+ =====
3
+ - Split out from NUBIC internal `bcdatabase` project.
4
+ (Changelog entries below reflect the relevant changes & version numbers from that project.)
5
+
6
+ 0.4.1
7
+ =====
8
+ - Fix `bcdatabase encrypt` so that it doesn't re-encrypt already encrypted
9
+ epassword entries.
10
+
11
+ 0.4.0
12
+ =====
13
+ - Use the YAML entry name as the "database" value if no other value is
14
+ provided. This is to DRY up PostgreSQL configurations where the username
15
+ (already defaulted) and the database name are the same.
16
+
17
+ 0.2.0
18
+ =====
19
+ - Change default encrypted secret password location
20
+
21
+ 0.1.0
22
+ =====
23
+ - Support encrypted passwords
24
+ - Command-line utility (also called bcdatabase) for creating encrypted passwords
25
+ - Gem distribution
26
+
27
+ 0.0.0
28
+ =====
29
+ Original release.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Rhett Sutphin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,142 @@
1
+ bcdatabase
2
+ ==========
3
+
4
+ *bcdatabase* is a library and utility which provides database configuration parameter management for Ruby on Rails applications. It provides a simple mechanism for separating database configuration attributes from application source code so that there's no temptation to check passwords into the version control system. And it centralizes the parameters for a single server so that they can be easily shared among multiple applications and easily updated by a single administrator.
5
+
6
+ ## Installing bcdatabase
7
+
8
+ Ensure that [gemcutter](http://gemcutter.org) is in your gem sources list, then:
9
+
10
+ $ gem install bcdatabase
11
+
12
+ ## Using bcdatabase to configure the database for a Rails application
13
+
14
+ A bog-standard rails application's `config/database.yml` file looks like this:
15
+
16
+ development:
17
+ adapter: oracle-enhanced
18
+ database: //localhost/XE
19
+ username: cfg_animal
20
+ password: not-important
21
+
22
+ test:
23
+ adapter: oracle-enhanced
24
+ database: //localhost/XE
25
+ username: cfg_animal_test
26
+ password: who-cares
27
+
28
+ production:
29
+ adapter: oracle-enhanced
30
+ database: //super/prod
31
+ username: cfg_animal
32
+ password: very-secret
33
+
34
+ Rails allows this file to contain [ERB][]. `bcdatabase` uses ERB to replace an entire configuration block. If you wanted to replace, say, just the production block in this example, you would transform it like so:
35
+
36
+ <%
37
+ require 'bcdatabase'
38
+ bcdb = Bcdatabase.load
39
+ %>
40
+
41
+ development:
42
+ adapter: oracle-enhanced
43
+ database: //localhost/XE
44
+ username: cfg_animal
45
+ password: not-important
46
+
47
+ test:
48
+ adapter: oracle-enhanced
49
+ database: //localhost/XE
50
+ username: cfg_animal_test
51
+ password: who-cares
52
+
53
+ <%= bcdb.production :prod, :cfg_animal %>
54
+
55
+ This means "create a YAML block for the *production* environment from the configuration entry named *cfg_animal* in /etc/nubic/db/*prod*.yml." The method called can be anything:
56
+
57
+ <%= bcdb.development :local, :cfg_animal %>
58
+ <%= bcdb.staging 'stage', 'cfg_animal' %>
59
+ <%= bcdb.automated :dev, :cfg_animal_hudson %>
60
+
61
+ [ERB]: http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/
62
+
63
+ ## Directly accessing configuration parameters from bcdatabase
64
+
65
+ More rarely, you might need to access the actual configuration hash, instead of the YAMLized version. You can access it by invoking `Bcdatabase.load` as shown earlier, then using the bracket operator to specify the configuration you want:
66
+
67
+ bcdb[:local, :cfg_animal]
68
+
69
+ The resulting hash is suitable for passing to `ActiveRecord::Base.establish_connection`, for instance.
70
+
71
+ ## Central configuration files
72
+
73
+ The database configuration properties for all the applications on a server are stored in one or more files under `/etc/nubic/db` (by default; see "File locations" below). Each one is a standard YAML file, similar to rails' `database.yml` but with a few enhancements:
74
+
75
+ * Each file can have a defaults entry which provides attributes which are shared across all configurations in the file
76
+ * Each entry defaults its "username" attribute to the name of the entry (useful for Oracle)
77
+ * Each entry defaults its "database" attribute to the name of the entry (useful for PostgreSQL)
78
+
79
+ Since each file can define a set of default properties which are shared by all the contained configurations, it makes sense to group databases which have some shared configuration elements.
80
+
81
+ ### Example
82
+
83
+ If you have an `/etc/nubic/db/stage.yml` file that looks like this:
84
+
85
+ defaults:
86
+ adapter: oracle-enhanced
87
+ database: //mondo/stage
88
+ cfg_animal:
89
+ password: secret
90
+ personnel:
91
+ username: pers
92
+ password: more-secret
93
+
94
+ You have defined two configuration entries. `:stage, :cfg_animal`:
95
+
96
+ adapter: oracle-enhanced
97
+ username: cfg_animal
98
+ password: secret
99
+ database: //mondo/stage
100
+
101
+ and `:bcstage, :personnel`:
102
+
103
+ adapter: oracle-enhanced
104
+ username: pers
105
+ password: more-secret
106
+ database: //mondo/stage
107
+
108
+ ## Obscuring passwords
109
+
110
+ bcdatabase supports storing encrypted passwords instead of the plaintext ones shown in the previous example. Encrypted passwords are defined with the key `epassword` instead of `password`. The library will decrypt the `epassword` value and expose it to the calling code (usually rails) unencrypted under the `password` key. The `bcdatabase` command line utility handles encrypting passwords; see the next section.
111
+
112
+ While the passwords are technically encrypted, the master key must be stored on the same machine so that they can be decrypted on demand. That means this feature only obscures passwords &mdash; it will not deter a determined attacker.
113
+
114
+ ## `bcdatabase` command line utility
115
+
116
+ The gem includes a command line utility (also called `bcdatabase`) which assists with creating `epassword` entries. It has online help; after installing the gem, try `bcdatabase help` to read it:
117
+
118
+ $ bcdatabase help
119
+ usage: bcdatabase <command> [args]
120
+ Command-line utility for bcdatabase 1.0.0
121
+ encrypt Encrypts all the password entries in a bcdatabase YAML file
122
+ epass Generate epasswords from individual database passwords
123
+ gen-key Generate a key for bcdatabase to use
124
+ help List commands or display help for one
125
+
126
+ ## File locations
127
+
128
+ `/etc/nubic/db` is the default place the library will look for the central configuration files. It may be overridden with the environment variable `BCDATABASE_PATH`. For instance, if you wanted to keep these files in your home directory on your development machine &mdash; perhaps so that editing them doesn't require elevated privileges &mdash; you could add this to `~/.bashrc`:
129
+
130
+ export BCDATABASE_PATH=${HOME}/nubic/db
131
+
132
+ Similarly, the file containing the encryption password has a sensible default location, but that location can be overridden by setting `BCDATABASE_PASS`.
133
+
134
+ ## Credits
135
+
136
+ `bcdatabase` was developed at and for the [Northwestern University Biomedical Informatics Center][NUBIC].
137
+
138
+ [NUBIC]: http://www.nucats.northwestern.edu/centers/nubic/index.html
139
+
140
+ ### Copyright
141
+
142
+ Copyright (c) 2009 Rhett Sutphin. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "bcdatabase"
8
+ gem.summary = %Q{Server-central database configuration for rails and other ruby apps}
9
+ gem.description = %Q{bcdatabase is a tool for storing passwords and other database configuration information outside of your application source tree.}
10
+ gem.email = "rhett@detailedbalance.net"
11
+ gem.homepage = "http://github.com/rsutphin/bcdatabase"
12
+ gem.authors = ["Rhett Sutphin"]
13
+ gem.add_development_dependency 'rspec', ">= 1.2"
14
+ gem.add_dependency 'highline', '>= 1.4'
15
+ gem.add_dependency 'activesupport', '>= 2.0'
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ # rcov can't tell that /Library/Ruby is a system path
33
+ spec.rcov_opts = ['--exclude', "spec/*,/Library/Ruby/*"]
34
+ end
35
+
36
+ task :spec => :check_dependencies
37
+
38
+ task :default => :spec
39
+
40
+ require 'rake/rdoctask'
41
+ Rake::RDocTask.new do |rdoc|
42
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "schema_qualified_tables #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
49
+
50
+ # Disable github release since I don't want to commit the gemspec
51
+ Rake::Task[:release].prerequisites.delete 'github:release'
52
+
53
+ task :build => [:gemspec]
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 0
4
+ :build:
5
+ :patch: 0
data/bin/bcdatabase ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby -W
2
+
3
+ require 'rubygems'
4
+ require 'bcdatabase/commands'
5
+
6
+ module Bcdatabase::Commands
7
+ UTILITY_NAME = File.basename(__FILE__)
8
+ end
9
+
10
+ ###### MAIN
11
+
12
+ command = ARGV.shift
13
+ unless command
14
+ $stderr.puts "Please specify a command."
15
+ $stderr.puts Bcdatabase::Commands.help
16
+ exit(1)
17
+ end
18
+
19
+ klass = Bcdatabase::Commands[command]
20
+ unless klass
21
+ $stderr.puts "Unknown command #{command}."
22
+ $stderr.puts Bcdatabase::Commands.help
23
+ exit(2)
24
+ end
25
+ klass.new(ARGV).main
@@ -0,0 +1,238 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'highline'
4
+ require 'active_support'
5
+ require 'bcdatabase'
6
+
7
+ HL = HighLine.new
8
+
9
+ module Bcdatabase::Commands
10
+ class Base
11
+ protected
12
+
13
+ def self.usage(use)
14
+ "usage: #{UTILITY_NAME} #{use}"
15
+ end
16
+
17
+ def self.help_message(use)
18
+ msg = [ "#{command_name}: #{summary}", usage(use), "" ]
19
+ yield msg if block_given?
20
+ msg.join("\n")
21
+ end
22
+ end
23
+
24
+ class Epass < Base
25
+ def initialize(argv)
26
+ @streaming = argv[-1] == '-'
27
+ end
28
+
29
+ def self.summary
30
+ "Generate epasswords from individual database passwords"
31
+ end
32
+
33
+ def self.help
34
+ help_message("epass [-]") do |msg|
35
+ msg << "With no arguments, interactively prompts for passwords and"
36
+ msg << " prints the corresponding epassword entry."
37
+ msg << ""
38
+ msg << "If the last argument is -, reads a newline-separated list"
39
+ msg << " of passwords from standard in and prints the corresponding "
40
+ msg << " epasswords to standard out."
41
+ end
42
+ end
43
+
44
+ def main
45
+ @streaming ? streamed : interactive
46
+ end
47
+
48
+ private
49
+
50
+ def streamed
51
+ $stdin.readlines.each do |line|
52
+ puts Bcdatabase.encrypt(line.chomp)
53
+ end
54
+ end
55
+
56
+ def interactive
57
+ begin
58
+ loop do
59
+ pass = HL.ask("Password (^C to end): ") do |q|
60
+ q.echo = false
61
+ end
62
+ puts " epassword: #{Bcdatabase.encrypt(pass)}"
63
+ end
64
+ rescue Interrupt
65
+ puts "\nQuit"
66
+ exit(0)
67
+ end
68
+ end
69
+ end
70
+
71
+ class Encrypt < Base
72
+ def initialize(argv)
73
+ @input = argv.shift
74
+ @output = argv.shift
75
+ end
76
+
77
+ def self.summary
78
+ "Encrypts all the password entries in a bcdatabase YAML file"
79
+ end
80
+
81
+ def self.help
82
+ help_message("encrypt [inputfile [outputfile]]") do |msg|
83
+ msg << "Specifically, this command finds all the keys named 'password'"
84
+ msg << " in the input YAML and substitutes appropriate 'epassword'"
85
+ msg << " keys."
86
+ msg << ""
87
+ msg << "If inputfile is specified, the source will be that file."
88
+ msg << " If not, the source will be standard in."
89
+ msg << ""
90
+ msg << "If inputfile and outputfile are specified, the new file"
91
+ msg << " will be written to the output file. Otherwise the output"
92
+ msg << " will go to standard out. Input and output may be the same"
93
+ msg << " file."
94
+ msg << ""
95
+ msg << "You can't read from standard in and write to a file directly; "
96
+ msg << " use shell file redirection if you need to do that."
97
+ end
98
+ end
99
+
100
+ def main
101
+ inio =
102
+ if @input
103
+ open(@input, "r")
104
+ else
105
+ $stdin
106
+ end
107
+ # try to preserve the order by replacing everything using regexes
108
+ contents = inio.read
109
+ contents.gsub!(/\bpassword:(\s*)(\S+)\s*?$/) { "epassword:#{$1}#{Bcdatabase.encrypt($2)}" }
110
+ outio =
111
+ if @output
112
+ open(@output, "w")
113
+ else
114
+ $stdout
115
+ end
116
+ outio.write(contents)
117
+ outio.close
118
+ end
119
+ end
120
+
121
+ class Help < Base
122
+ def initialize(argv)
123
+ @cmd = argv.shift
124
+ end
125
+
126
+ def self.summary
127
+ "List commands or display help for one; e.g. #{UTILITY_NAME} help epass"
128
+ end
129
+
130
+ def self.help
131
+ help_message "help [command name]"
132
+ end
133
+
134
+ def main
135
+ if @cmd
136
+ klass = Bcdatabase::Commands[@cmd]
137
+ if klass
138
+ msg = klass.respond_to?(:help) ? klass.help : klass.summary
139
+ $stderr.puts msg
140
+ exit(0)
141
+ else
142
+ $stderr.puts "Unknown command #{@cmd}"
143
+ exit(1)
144
+ end
145
+ else
146
+ $stderr.puts Bcdatabase::Commands.help
147
+ exit(0)
148
+ end
149
+ end
150
+ end
151
+
152
+ class GenKey < Base
153
+ def initialize(argv)
154
+ @stream = argv[-1] == '-'
155
+ end
156
+
157
+ def self.summary
158
+ "Generate a key for bcdatabase to use"
159
+ end
160
+
161
+ def self.help
162
+ help_message("gen-key [-]") do |msg|
163
+ msg << "By default, the key will be generated in "
164
+ msg << " #{Bcdatabase.pass_file}. If the last argument to this"
165
+ msg << " command is -, the key will be generated to standard out"
166
+ msg << " instead."
167
+ msg << ""
168
+ msg << "CAUTION: writing to #{Bcdatabase.pass_file} may overwrite"
169
+ msg << " an existing bcdatabase key. If that happens, you will"
170
+ msg << " need to reencrypt all the epasswords on this machine."
171
+ end
172
+ end
173
+
174
+ def main
175
+ key = random_key(128)
176
+ outio =
177
+ if @stream
178
+ $stdout
179
+ else
180
+ file = Bcdatabase.pass_file
181
+ if File.exist?(file)
182
+ sure = HL.ask("This operation will overwrite the existing pass file.\n Are you sure you want to do that? ", %w{yes no}) do |q|
183
+ q.case = :down
184
+ end
185
+ unless sure == 'yes'
186
+ exit(0)
187
+ end
188
+ end
189
+ open(file, "w")
190
+ end
191
+ outio.write key
192
+ outio.close
193
+ end
194
+
195
+ private
196
+
197
+ def random_key(length)
198
+ k = ""
199
+ # This is probably not going to work in ruby 1.9
200
+ until k.size == length; k << rand(126 - 32) + 32; end
201
+ k
202
+ end
203
+ end
204
+
205
+ class << self
206
+ def help
207
+ all_help = commands.collect { |c| [c.command_name, c.summary] }.sort_by { |p| p[0] }
208
+ max_name_length = all_help.collect { |a| a[0].size }.max
209
+ msg = Base.usage "<command> [args]\n"
210
+ msg << "Utility for bcdatabase #{Bcdatabase::VERSION}\n"
211
+ msg << "Commands:\n"
212
+ msg << all_help.collect { |name, help| " %#{max_name_length + 1}s %s" % [name, help] }.join("\n")
213
+ end
214
+
215
+ # Lists all the commands
216
+ def commands
217
+ constants.reject { |cs| cs == "Base" }.collect { |cs| const_get(cs) }.select { |c| c.kind_of? Class }
218
+ end
219
+
220
+ # Locates the command class for a user-entered command name.
221
+ # Returns nil if the name is invalid.
222
+ def command(command_name)
223
+ begin
224
+ klassname = command_name.gsub('-', '_').camelize
225
+ Bcdatabase::Commands.const_get "#{klassname}"
226
+ rescue NameError
227
+ nil
228
+ end
229
+ end
230
+ alias :[] :command
231
+ end
232
+ end
233
+
234
+ class Class
235
+ def command_name
236
+ name.gsub(Bcdatabase::Commands.name + "::", '').underscore.gsub("_", '-')
237
+ end
238
+ end
data/lib/bcdatabase.rb ADDED
@@ -0,0 +1,116 @@
1
+ require 'yaml'
2
+ require 'openssl'
3
+ require 'digest/sha2'
4
+ require 'base64'
5
+
6
+ module Bcdatabase
7
+ VERSION = begin
8
+ config = YAML.load(File.read(File.expand_path('../VERSION.yml', File.dirname(__FILE__))))
9
+ [config[:major], config[:minor], config[:patch]].join('.')
10
+ end
11
+ DEFAULT_BASE_PATH = File.join('/', 'etc', 'nubic', 'db')
12
+ DEFAULT_PASS_FILE = File.join('/', 'var', 'lib', 'nubic', 'db.pass')
13
+ CIPHER = 'aes-256-ecb'
14
+
15
+ class << self
16
+ def load(path=nil)
17
+ path ||= base_path
18
+ files = Dir.glob(File.join(path, "*.yml")) + Dir.glob(File.join(path, "*.yaml"))
19
+ DatabaseConfigurations.new(files)
20
+ end
21
+
22
+ def encrypt(s)
23
+ Base64.encode64(encipher(:encrypt, s)).strip
24
+ end
25
+
26
+ def decrypt(s)
27
+ encipher(:decrypt, Base64.decode64(s))
28
+ end
29
+
30
+ def pass_file
31
+ ENV["BCDATABASE_PASS"] || DEFAULT_PASS_FILE
32
+ end
33
+
34
+ private
35
+
36
+ # based on http://snippets.dzone.com/posts/show/576
37
+ def encipher(direction, s)
38
+ # the order of operations here is very important
39
+ c = OpenSSL::Cipher::Cipher.new(CIPHER)
40
+ c.send direction
41
+ c.key = pass
42
+ t = c.update(s)
43
+ t << c.final
44
+ end
45
+
46
+ def pass
47
+ return @pass if instance_variable_defined? :@pass
48
+
49
+ contents = open(pass_file).read.chomp
50
+ # This code may not work correctly on Ruby 1.9
51
+ if contents.size == 32
52
+ @pass = contents
53
+ else
54
+ @pass = Digest::SHA256.digest(contents)
55
+ end
56
+ end
57
+
58
+ def base_path
59
+ ENV["BCDATABASE_PATH"] || DEFAULT_BASE_PATH
60
+ end
61
+ end
62
+
63
+ class DatabaseConfigurations
64
+ def initialize(files)
65
+ @files = files
66
+ @map = { }
67
+ files.each do |filename|
68
+ name = File.basename(filename).gsub(/\.ya?ml/, '')
69
+ @map[name] = YAML.load(File.open(filename))
70
+ end
71
+ end
72
+
73
+ def [](groupname, dbname)
74
+ create_entry(groupname.to_s, dbname.to_s)
75
+ end
76
+
77
+ def method_missing(name, *args)
78
+ groupname = (args[0] or raise "Database configuration group not specified for #{name}")
79
+ dbname = (args[1] or raise "Database entry name not specified for #{name}")
80
+ n = name.to_s
81
+ begin
82
+ unseparated_yaml({ n, self[groupname, dbname] })
83
+ rescue Bcdatabase::Error => e
84
+ if defined?(RAILS_ENV) and RAILS_ENV == n
85
+ raise e
86
+ else
87
+ # Not using that configuration right now, so return a dummy instead
88
+ # of throwing an exception
89
+ unseparated_yaml({ n, { 'error' => e.message } })
90
+ end
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def create_entry(groupname, dbname)
97
+ group = @map[groupname] or raise Error.new("No databasease configuration group named #{groupname.inspect} found. (Found #{@map.keys.inspect}.)")
98
+ db = group[dbname] or raise Error.new("No database entry for #{dbname.inspect} in #{groupname}")
99
+ merged = { 'username' => dbname, 'database' => dbname } \
100
+ .merge(group['defaults'] || {}) \
101
+ .merge(group['default'] || {}) \
102
+ .merge(db)
103
+ # include the decrypted password if an encrypted one was provided
104
+ if merged['epassword']
105
+ merged['password'] = Bcdatabase.decrypt(merged['epassword'])
106
+ end
107
+ merged
108
+ end
109
+
110
+ def unseparated_yaml(arg)
111
+ arg.to_yaml.gsub(/^---.*\n/, '')
112
+ end
113
+ end
114
+
115
+ class Error < Exception; end
116
+ end unless defined?(Bcdatabase)
@@ -0,0 +1,55 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+
3
+ require "bcdatabase/commands"
4
+
5
+ describe "CLI: bcdatabase" do
6
+ before(:each) do
7
+ ENV["BCDATABASE_PATH"] = "/tmp/bcdb_specs"
8
+ FileUtils.mkdir_p ENV["BCDATABASE_PATH"]
9
+ end
10
+
11
+ after(:each) do
12
+ FileUtils.rm_rf ENV["BCDATABASE_PATH"]
13
+ ENV["BCDATABASE_PATH"] = nil
14
+ end
15
+
16
+ describe "encrypt" do
17
+ before do
18
+ enable_fake_cipherment
19
+ end
20
+
21
+ after do
22
+ disable_fake_cipherment
23
+ end
24
+
25
+ def bcdatabase_encrypt(infile)
26
+ StringIO.open("", "w") do |io|
27
+ $stdout = io
28
+ Bcdatabase::Commands::Encrypt.new([File.join(ENV["BCDATABASE_PATH"], infile)]).main
29
+ $stdout = STDOUT
30
+ YAML::load(io.string)
31
+ end
32
+ end
33
+
34
+ it "replaces password: clauses with epasswords" do
35
+ temporary_yaml "plain", {
36
+ "single" => {
37
+ "password" => 'zanzibar'
38
+ }
39
+ }
40
+
41
+ bcdatabase_encrypt('plain.yaml')['single']['epassword'].should == 'rabiznaz'
42
+ bcdatabase_encrypt('plain.yaml')['single']['password'].should be_nil
43
+ end
44
+
45
+ it "leaves existing epasswords alone" do
46
+ temporary_yaml "plain", {
47
+ "single" => {
48
+ "epassword" => 'etalocohc'
49
+ }
50
+ }
51
+
52
+ bcdatabase_encrypt('plain.yaml')['single']['epassword'].should == 'etalocohc'
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,200 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe Bcdatabase, "cipherment" do
4
+ before(:all) do
5
+ keyfile = "/tmp/bcdb-spec-key"
6
+ open(keyfile, 'w') { |f| f.write "01234567890123456789012345678901" }
7
+ ENV["BCDATABASE_PASS"] = keyfile
8
+ end
9
+
10
+ after(:all) do
11
+ FileUtils.rm ENV["BCDATABASE_PASS"]
12
+ ENV["BCDATABASE_PASS"] = nil
13
+ end
14
+
15
+ it "should be reversible" do
16
+ e = Bcdatabase.encrypt("riboflavin")
17
+ Bcdatabase.decrypt(e).should == "riboflavin"
18
+ end
19
+
20
+ it "should permute the input" do
21
+ Bcdatabase.encrypt("zanzibar").should_not == "zanzibar"
22
+ end
23
+
24
+ it "should do more than just encode" do
25
+ Bcdatabase.encrypt("zanzibar").should_not == Base64.encode64("zanzibar")
26
+ end
27
+ end
28
+
29
+ describe Bcdatabase, "module " do
30
+ it "should have a d.d.d version" do
31
+ Bcdatabase::VERSION.should =~ /^\d+\.\d+\.\d+$/
32
+ end
33
+ end
34
+
35
+ describe Bcdatabase, "loading" do
36
+ before(:each) do
37
+ ENV["BCDATABASE_PATH"] = "/tmp/bcdb_specs"
38
+ FileUtils.mkdir_p ENV["BCDATABASE_PATH"]
39
+ end
40
+
41
+ after(:each) do
42
+ FileUtils.rm_rf ENV["BCDATABASE_PATH"]
43
+ ENV["BCDATABASE_PATH"] = nil
44
+ end
45
+
46
+ it "should read simple YAML" do
47
+ temporary_yaml "simple", {
48
+ "single" => {
49
+ "adapter" => "foo", "username" => "baz"
50
+ }
51
+ }
52
+ bcdb = Bcdatabase.load
53
+ bcdb[:simple, :single]['adapter'].should == "foo"
54
+ bcdb[:simple, :single]['username'].should == "baz"
55
+ end
56
+
57
+ it "should read and expose multiple groups from multiple files" do
58
+ temporary_yaml "one", {
59
+ "first" => { "dc" => "etc" }
60
+ }
61
+ temporary_yaml "two", {
62
+ "fourth" => { "dc" => "etc" }
63
+ }
64
+ bcdb = Bcdatabase.load
65
+ bcdb['one', 'first'].should_not be_nil
66
+ bcdb['two', 'fourth'].should_not be_nil
67
+ end
68
+
69
+ it "should merge defaults from 'defaults'" do
70
+ temporary_yaml "defaulted", {
71
+ "defaults" => {
72
+ "database" => "postgresql"
73
+ },
74
+ "real" => {
75
+ "password" => "frood"
76
+ }
77
+ }
78
+ bcdb = Bcdatabase.load
79
+ bcdb['defaulted', 'real']['password'].should == 'frood'
80
+ bcdb['defaulted', 'real']['database'].should == 'postgresql'
81
+ end
82
+
83
+ it "should merge defaults from 'default'" do
84
+ temporary_yaml "singular", {
85
+ "default" => {
86
+ "adapter" => "three-eighths"
87
+ },
88
+ "real" => {
89
+ "password" => "frood"
90
+ }
91
+ }
92
+ bcdb = Bcdatabase.load
93
+ bcdb['singular', 'real']['adapter'].should == 'three-eighths'
94
+ end
95
+
96
+ it "should preserve values overridden from defaults" do
97
+ temporary_yaml "jam", {
98
+ "default" => {
99
+ "adapter" => "three-eighths"
100
+ },
101
+ "standard" => {
102
+ "password" => "frood"
103
+ },
104
+ "custom" => {
105
+ "adapter" => "five-sixteenths",
106
+ "password" => "lazlo"
107
+ }
108
+ }
109
+ bcdb = Bcdatabase.load
110
+ bcdb['jam', 'standard']['adapter'].should == 'three-eighths'
111
+ bcdb['jam', 'custom']['adapter'].should == 'five-sixteenths'
112
+ end
113
+
114
+ it "should default the username to the entry name" do
115
+ temporary_yaml "scran", {
116
+ "jim" => { "password" => "leather" }
117
+ }
118
+ bcdb = Bcdatabase.load
119
+ bcdb['scran', 'jim']['username'].should == 'jim'
120
+ bcdb['scran', 'jim']['password'].should == 'leather'
121
+ end
122
+
123
+ it "should default the database name to the entry name" do
124
+ temporary_yaml "scran", {
125
+ "jim" => { "password" => "leather" }
126
+ }
127
+ bcdb = Bcdatabase.load
128
+ bcdb['scran', 'jim']['database'].should == 'jim'
129
+ bcdb['scran', 'jim']['password'].should == 'leather'
130
+ end
131
+
132
+ it "should not default the database name if there's an explicit database name" do
133
+ temporary_yaml "scran", {
134
+ "jim" => {
135
+ "password" => "leather",
136
+ "database" => "james"
137
+ }
138
+ }
139
+ bcdb = Bcdatabase.load
140
+ bcdb['scran', 'jim']['database'].should == 'james'
141
+ bcdb['scran', 'jim']['password'].should == 'leather'
142
+ end
143
+
144
+ it "should not default the database name to the entry name if there's a default database name" do
145
+ temporary_yaml "scran", {
146
+ "default" => {
147
+ "database" => "//localhost:345/etc"
148
+ },
149
+ "jim" => {
150
+ "password" => "leather",
151
+ }
152
+ }
153
+ bcdb = Bcdatabase.load
154
+ bcdb['scran', 'jim']['database'].should == '//localhost:345/etc'
155
+ bcdb['scran', 'jim']['password'].should == 'leather'
156
+ end
157
+
158
+ it "should use an explicit username instead of the entry name if provided" do
159
+ temporary_yaml "scran", {
160
+ "jim" => {
161
+ "username" => "james",
162
+ "password" => "earldom"
163
+ }
164
+ }
165
+ bcdb = Bcdatabase.load
166
+ bcdb['scran', 'jim']['username'].should == 'james'
167
+ bcdb['scran', 'jim']['password'].should == 'earldom'
168
+ end
169
+
170
+ describe "with encrypted passwords" do
171
+ before do
172
+ enable_fake_cipherment
173
+ end
174
+
175
+ after do
176
+ disable_fake_cipherment
177
+ end
178
+
179
+ it "should decrypt and expose the password" do
180
+ temporary_yaml "secure", {
181
+ "safe" => {
182
+ "epassword" => "moof"
183
+ }
184
+ }
185
+ bcdb = Bcdatabase.load
186
+ bcdb['secure', 'safe']['password'].should == "foom"
187
+ end
188
+
189
+ it "should prefer the decrypted version of an epassword" do
190
+ temporary_yaml "secure", {
191
+ "safe" => {
192
+ "password" => "fake",
193
+ "epassword" => "moof"
194
+ }
195
+ }
196
+ bcdb = Bcdatabase.load
197
+ bcdb['secure', 'safe']['password'].should == "foom" # not "fake"
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,32 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'bcdatabase'
4
+ require 'rubygems'
5
+ require 'fileutils'
6
+
7
+ def temporary_yaml(name, hash)
8
+ filename = "/#{ENV['BCDATABASE_PATH']}/#{name}.yaml"
9
+ open(filename, "w") { |f| YAML.dump(hash, f) }
10
+ filename
11
+ end
12
+
13
+ def enable_fake_cipherment
14
+ # replace real encryption methods with something predictable
15
+ Bcdatabase.module_eval do
16
+ class << self
17
+ alias :encrypt_original :encrypt
18
+ alias :decrypt_original :decrypt
19
+ def encrypt(s); s.reverse; end
20
+ def decrypt(s); s.reverse; end
21
+ end
22
+ end
23
+ end
24
+
25
+ def disable_fake_cipherment
26
+ Bcdatabase.module_eval do
27
+ class << self
28
+ alias :encrypt :encrypt_original
29
+ alias :decrypt :decrypt_original
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bcdatabase
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rhett Sutphin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-08 00:00:00 -06:00
13
+ default_executable: bcdatabase
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "1.2"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: highline
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "1.4"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: activesupport
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "2.0"
44
+ version:
45
+ description: bcdatabase is a tool for storing passwords and other database configuration information outside of your application source tree.
46
+ email: rhett@detailedbalance.net
47
+ executables:
48
+ - bcdatabase
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.markdown
54
+ files:
55
+ - .gitignore
56
+ - CHANGELOG.markdown
57
+ - LICENSE
58
+ - README.markdown
59
+ - Rakefile
60
+ - VERSION.yml
61
+ - bin/bcdatabase
62
+ - lib/bcdatabase.rb
63
+ - lib/bcdatabase/commands.rb
64
+ - spec/bcdatabase/commands_spec.rb
65
+ - spec/bcdatabase_spec.rb
66
+ - spec/spec_helper.rb
67
+ has_rdoc: true
68
+ homepage: http://github.com/rsutphin/bcdatabase
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --charset=UTF-8
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.3.5
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Server-central database configuration for rails and other ruby apps
95
+ test_files:
96
+ - spec/bcdatabase/commands_spec.rb
97
+ - spec/bcdatabase_spec.rb
98
+ - spec/spec_helper.rb