bcdatabase 1.0.6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.markdown CHANGED
@@ -1,55 +1,74 @@
1
+ Bcdatabase history
2
+ ==================
3
+
4
+ 1.1.0
5
+ -----
6
+
7
+ - Introduce "transforms" -- a way to attach behavior to modify entries
8
+ on load. See {Bcdatabase.load} for details.
9
+ - Add `:datamapper` built-in transform to support sharing one set of
10
+ entries between ActiveRecord and DataMapper. (#10)
11
+ - Rework command-line interface for better testability. It's now
12
+ compatible with MRI 1.9. (#11, #7)
13
+ - Provide a better message when working with encrypted passwords and
14
+ the keyfile is not readable. (#1)
15
+ - Improve `bcdatabase encrypt` so that it will work with a wider
16
+ variety of input passwords and YAML files. The remaining limitations
17
+ are documented in its online help. (#12)
18
+ - Interpret empty stanzas as entries made up entirely of defaults. (#13)
19
+
1
20
  1.0.6
2
- =====
21
+ -----
3
22
  - Use `ENV['RAILS_ENV']` instead of the unreliable `RAILS_ENV` constant.
4
23
 
5
24
  1.0.5
6
- =====
25
+ -----
7
26
  - Loosen highline dependency so that bcdatabase can be used in buildr buildfiles.
8
27
 
9
28
  1.0.4
10
- =====
29
+ -----
11
30
  - Fix command line utilities that were broken in 1.0.3 due to
12
31
  inadequate test coverage. (GH-4)
13
32
 
14
33
  1.0.3
15
- =====
34
+ -----
16
35
  - Support ActiveSupport 3. ActiveSupport 2 continues to work.
17
36
 
18
37
  1.0.2
19
- =====
38
+ -----
20
39
  - Tighten up gemspec gem deps. Bcdatabase does not currently work
21
40
  with ActiveSupport 3.
22
41
 
23
42
  1.0.1
24
- =====
43
+ -----
25
44
  - Update some old syntax for ruby 1.9 compatibility (David Yip)
26
45
 
27
46
  1.0.0
28
- =====
47
+ -----
29
48
  - Split out from NUBIC internal `bcdatabase` project.
30
49
  (Changelog entries below reflect the relevant changes & version numbers from that project.)
31
50
 
32
51
  0.4.1
33
- =====
52
+ -----
34
53
  - Fix `bcdatabase encrypt` so that it doesn't re-encrypt already encrypted
35
54
  epassword entries.
36
55
 
37
56
  0.4.0
38
- =====
57
+ -----
39
58
  - Use the YAML entry name as the "database" value if no other value is
40
59
  provided. This is to DRY up PostgreSQL configurations where the username
41
60
  (already defaulted) and the database name are the same.
42
61
 
43
62
  0.2.0
44
- =====
63
+ -----
45
64
  - Change default encrypted secret password location
46
65
 
47
66
  0.1.0
48
- =====
67
+ -----
49
68
  - Support encrypted passwords
50
69
  - Command-line utility (also called bcdatabase) for creating encrypted passwords
51
70
  - Gem distribution
52
71
 
53
72
  0.0.0
54
- =====
73
+ -----
55
74
  Original release.
data/README.markdown CHANGED
@@ -1,7 +1,14 @@
1
- bcdatabase
1
+ Bcdatabase
2
2
  ==========
3
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.
4
+ *Bcdatabase* is a library and utility which provides database
5
+ configuration parameter management for Ruby on Rails applications. It
6
+ provides a simple mechanism for separating database configuration
7
+ attributes from application source code so that there's no temptation
8
+ to check passwords into the version control system. And it
9
+ centralizes the parameters for a single server so that they can be
10
+ easily shared among multiple applications and easily updated by a
11
+ single administrator.
5
12
 
6
13
  ## Installing bcdatabase
7
14
 
@@ -9,7 +16,7 @@ bcdatabase
9
16
 
10
17
  ## Using bcdatabase to configure the database for a Rails application
11
18
 
12
- A bog-standard rails application's `config/database.yml` file looks like this:
19
+ A bog-standard Rails application's `config/database.yml` file looks like this:
13
20
 
14
21
  development:
15
22
  adapter: oracle_enhanced
@@ -29,7 +36,10 @@ A bog-standard rails application's `config/database.yml` file looks like this:
29
36
  username: cfg_animal
30
37
  password: very-secret
31
38
 
32
- 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:
39
+ Rails allows this file to contain [ERB][]. `bcdatabase` uses ERB to
40
+ replace an entire configuration block. If you wanted to replace, say,
41
+ just the production block in this example, you would transform it like
42
+ so:
33
43
 
34
44
  <%
35
45
  require 'bcdatabase'
@@ -50,7 +60,9 @@ Rails allows this file to contain [ERB][]. `bcdatabase` uses ERB to replace an
50
60
 
51
61
  <%= bcdb.production :prod, :cfg_animal %>
52
62
 
53
- 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:
63
+ This means "create a YAML block for the *production* environment from
64
+ the configuration entry named *cfg_animal* in
65
+ /etc/nubic/db/*prod*.yml." The method called can be anything:
54
66
 
55
67
  <%= bcdb.development :local, :cfg_animal %>
56
68
  <%= bcdb.staging 'stage', 'cfg_animal' %>
@@ -60,21 +72,33 @@ This means "create a YAML block for the *production* environment from the config
60
72
 
61
73
  ## Directly accessing configuration parameters from bcdatabase
62
74
 
63
- 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:
75
+ More rarely, you might need to access the actual configuration hash,
76
+ instead of the YAMLized version. You can access it by invoking
77
+ `Bcdatabase.load` as shown earlier, then using the bracket operator to
78
+ specify the configuration you want:
64
79
 
65
80
  bcdb[:local, :cfg_animal]
66
81
 
67
- The resulting hash is suitable for passing to `ActiveRecord::Base.establish_connection`, for instance.
82
+ The resulting hash is suitable for passing to
83
+ `ActiveRecord::Base.establish_connection`, for instance.
68
84
 
69
85
  ## Central configuration files
70
86
 
71
- 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:
87
+ The database configuration properties for all the applications on a
88
+ server are stored in one or more files under `/etc/nubic/db` (by
89
+ default; see "File locations" below). Each one is a standard YAML
90
+ file, similar to Rails' `database.yml` but with a few enhancements:
72
91
 
73
- * Each file can have a defaults entry which provides attributes which are shared across all configurations in the file
74
- * Each entry defaults its "username" attribute to the name of the entry (useful for Oracle)
75
- * Each entry defaults its "database" attribute to the name of the entry (useful for PostgreSQL)
92
+ * Each file can have a defaults entry which provides attributes which
93
+ are shared across all configurations in the file
94
+ * Each entry defaults its "username" attribute to the name of the
95
+ entry (useful for Oracle)
96
+ * Each entry defaults its "database" attribute to the name of the
97
+ entry (useful for PostgreSQL)
76
98
 
77
- 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.
99
+ Since each file can define a set of default properties which are
100
+ shared by all the contained configurations, it makes sense to group
101
+ databases which have some shared configuration elements.
78
102
 
79
103
  ### Example
80
104
 
@@ -96,7 +120,7 @@ You have defined two configuration entries. `:stage, :cfg_animal`:
96
120
  password: secret
97
121
  database: //mondo/stage
98
122
 
99
- and `:bcstage, :personnel`:
123
+ and `:stage, :personnel`:
100
124
 
101
125
  adapter: oracle_enhanced
102
126
  username: pers
@@ -105,33 +129,104 @@ and `:bcstage, :personnel`:
105
129
 
106
130
  ## Obscuring passwords
107
131
 
108
- 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.
132
+ Bcdatabase supports storing encrypted passwords instead of the
133
+ plaintext ones shown in the previous example. Encrypted passwords are
134
+ defined with the key `epassword` instead of `password`. The library
135
+ will decrypt the `epassword` value and expose it to the calling code
136
+ (usually Rails) unencrypted under the `password` key. The
137
+ `bcdatabase` command line utility handles encrypting passwords; see
138
+ the next section.
109
139
 
110
- 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.
140
+ While the passwords are technically encrypted, the master key must be
141
+ stored on the same machine so that they can be decrypted on demand.
142
+ That means this feature only obscures passwords &mdash; it will not
143
+ deter a determined attacker.
111
144
 
112
145
  ## `bcdatabase` command line utility
113
146
 
114
- 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:
147
+ The gem includes a command line utility (also called `bcdatabase`)
148
+ which assists with creating `epassword` entries. It has online help;
149
+ after installing the gem, try `bcdatabase help` to read it:
115
150
 
116
151
  $ bcdatabase help
117
- usage: bcdatabase <command> [args]
118
- Command-line utility for bcdatabase 1.0.0
119
- encrypt Encrypts all the password entries in a bcdatabase YAML file
120
- epass Generate epasswords from individual database passwords
121
- gen-key Generate a key for bcdatabase to use
122
- help List commands or display help for one
152
+ Tasks:
153
+ bcdatabase encrypt [INPUT [OUTPUT]] # Encrypt every password in a bcdatabase YAML file
154
+ bcdatabase epass [-] # Generate epasswords from database passwords
155
+ bcdatabase gen-key [-] # Generate the bcdatabase shared key
156
+ bcdatabase help [TASK] # Describe available tasks or one specific task
123
157
 
124
158
  ## File locations
125
159
 
126
- `/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`:
160
+ `/etc/nubic/db` is the default place the library will look for the
161
+ central configuration files. It may be overridden with the
162
+ environment variable `BCDATABASE_PATH`. For instance, if you wanted
163
+ to keep these files in your home directory on your development machine
164
+ &mdash; perhaps so that editing them doesn't require elevated
165
+ privileges &mdash; you could add this to `~/.bashrc`:
127
166
 
128
167
  export BCDATABASE_PATH=${HOME}/nubic/db
129
168
 
130
- Similarly, the file containing the encryption password has a sensible default location, but that location can be overridden by setting `BCDATABASE_PASS`.
169
+ Similarly, the file containing the encryption password has a sensible
170
+ default location, but that location can be overridden by setting
171
+ `BCDATABASE_PASS`.
172
+
173
+ ## DataMapper
174
+
175
+ Bcdatabase was originally designed for use with ActiveRecord in Rails
176
+ applications. Since [DataMapper][dm]'s programmatic configuration mechanism
177
+ (`Datamapper.setup`) accepts hashes which are very similar to
178
+ ActiveRecord configuration hashes, Bcdatabase can easily be used with
179
+ DataMapper as well. Example:
180
+
181
+ bcdb = Bcdatabase.load(:transforms => [:datamapper]))
182
+ DataMapper.setup(:default, bcdb[:stage, :personnel])
183
+
184
+ The `:datamapper` transform passed to `Bcdatabase.load` in this
185
+ example permits sharing of one set of Bcdatabase configurations
186
+ between ActiveRecord and DataMapper-based apps. Specifically, for
187
+ those cases where the ActiveRecord adapter and the DataMapper adapter
188
+ have different names, it allows you to specify a separate
189
+ `datamapper_adapter` in your Bcdatabase configuration. For example,
190
+ say you had these contents in `stage.yml`:
191
+
192
+ defaults:
193
+ adapter: postgresql
194
+ datamapper_adapter: postgres
195
+ personnel:
196
+ password: foo
197
+
198
+ When loaded without the `:datamapper` transform, the effective
199
+ database configuration hash for `:stage, :personnel` would be
200
+
201
+ adapter: postgresql
202
+ datamapper_adapter: postgres # ignored by AR
203
+ database: personnel
204
+ username: personnel
205
+
206
+ With the `:datamapper` transform, the result would be instead:
207
+
208
+ adapter: postgres
209
+ database: personnel
210
+ username: personnel
211
+
212
+ And so your DM and AR apps can live side-by-side and neither needs to
213
+ embed its own database credentials.
214
+
215
+ [dm]: http://datamapper.org/
216
+
217
+ ## Platforms
218
+
219
+ Bcdatabase works on MRI 1.8.7 and MRI 1.9.2. It will also work on
220
+ JRuby (tested on 1.6+), provided that `jruby-openssl` is also
221
+ installed. It is [continuously tested][ci] on all three of these
222
+ platforms.
223
+
224
+ [ci]: https://public-ci.nubic.northwestern.edu/job/bcdatabase/
131
225
 
132
226
  ## Credits
133
227
 
134
- `bcdatabase` was developed at and for the [Northwestern University Biomedical Informatics Center][NUBIC].
228
+ `bcdatabase` was developed at and for the [Northwestern University
229
+ Biomedical Informatics Center][NUBIC].
135
230
 
136
231
  [NUBIC]: http://www.nucats.northwestern.edu/centers/nubic/index.html
137
232
 
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require 'bcdatabase/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'bcdatabase'
8
+ s.version = Bcdatabase::VERSION
9
+ s.summary = %Q{Server-central database configuration for rails and other ruby apps}
10
+ s.description = %Q{bcdatabase is a tool for storing passwords and other configuration information outside of your application source tree.}
11
+ s.email = "rhett@detailedbalance.net"
12
+ s.homepage = "http://github.com/NUBIC/bcdatabase"
13
+ s.authors = ["Rhett Sutphin"]
14
+
15
+ s.require_paths = ["lib"]
16
+
17
+ s.executables = ['bcdatabase']
18
+ s.files = Dir.glob("{CHANGELOG.markdown,LICENSE,README.markdown,bcdatabase.gemspec,{bin,lib}/**/*}")
19
+
20
+ s.add_dependency "activesupport", ">= 2.0"
21
+ s.add_dependency "highline", "~> 1.5"
22
+ s.add_dependency "i18n"
23
+ s.add_dependency 'thor', '~> 0.14.6'
24
+
25
+ s.add_development_dependency 'bundler', '~> 1.0.15'
26
+ s.add_development_dependency 'rake', '~> 0.9.2'
27
+ s.add_development_dependency 'rspec','~> 2.6'
28
+ s.add_development_dependency "ci_reporter", "~> 1.6"
29
+ s.add_development_dependency 'yard', '~> 0.7.2'
30
+ end
data/bin/bcdatabase CHANGED
@@ -1,26 +1,20 @@
1
- #!/usr/bin/env ruby -W
1
+ #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
- require 'bcdatabase/commands'
5
-
6
- module Bcdatabase::Commands
7
- UTILITY_NAME = File.basename(__FILE__)
3
+ # Allow this executable to be run directly from the source as well as
4
+ # from an installed gem.
5
+ begin
6
+ lib = File.expand_path('../../lib', __FILE__)
7
+ unless $LOAD_PATH.include?(lib)
8
+ $LOAD_PATH << lib
9
+ require 'rubygems'
10
+ end
8
11
  end
9
12
 
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
13
+ require 'bcdatabase'
18
14
 
19
- klass = Bcdatabase::Commands[command]
20
- unless klass
21
- $stderr.puts "Unknown command #{command}."
22
- $stderr.puts Bcdatabase::Commands.help
23
- exit(2)
15
+ begin
16
+ Bcdatabase::CLI.start
17
+ rescue Interrupt => e
18
+ $stderr.puts "Interrupted"
19
+ exit 1
24
20
  end
25
-
26
- exit(klass.new(ARGV).main)
@@ -0,0 +1,81 @@
1
+ require 'bcdatabase'
2
+ require 'thor'
3
+
4
+ module Bcdatabase
5
+ class CLI < Thor
6
+ desc "encrypt [INPUT [OUTPUT]]",
7
+ "Encrypt every password in a bcdatabase YAML file"
8
+ long_desc <<-DESC
9
+ This command finds all the keys named 'password' in the input
10
+ YAML and substitutes appropriate 'epassword' keys.
11
+
12
+ If inputfile is specified, the source will be that file. If not,
13
+ the source will be standard in.
14
+
15
+ If inputfile and outputfile are specified, the new file will be
16
+ written to the output file. Otherwise the output will go to
17
+ standard out. Input and output may be the same file.
18
+
19
+ You can't read from standard in and write to a file directly;
20
+ use shell file redirection if you need to do that.
21
+
22
+ N.b.: this command works with a subset of legal YAML
23
+ files. Specifically, it will only work if the 'password' key and
24
+ value are entirely on the same line. If for some reason you
25
+ can't arrange for your input files to meet this restriction, you
26
+ can use the epass subcommand to encrypt one password at a time.
27
+ DESC
28
+ def encrypt(inputfile=nil, outputfile=nil)
29
+ Commands::Encrypt.new(inputfile, outputfile).run
30
+ end
31
+
32
+ desc 'epass [-]', 'Generate epasswords from database passwords'
33
+ long_desc <<-DESC
34
+ With no arguments, interactively prompts for passwords and
35
+ prints the corresponding epassword entry.
36
+
37
+ If the last argument is -, reads a newline-separated list of
38
+ passwords from standard in and prints the corresponding
39
+ epasswords to standard out.
40
+ DESC
41
+ def epass(arg=nil)
42
+ Commands::Epass.new(arg == '-').run
43
+ end
44
+
45
+ desc 'gen-key [-]', 'Generate the bcdatabase shared key'
46
+ long_desc <<-DESC
47
+ Generates the key that is used to obscure epasswords. By
48
+ default, the key will be generated in
49
+ #{Bcdatabase.pass_file}. If the last argument to this command
50
+ is -, the key will be generated to standard out instead.
51
+
52
+ CAUTION: writing to #{Bcdatabase.pass_file} may overwrite an
53
+ existing bcdatabase key. If that happens, you will need to
54
+ reencrypt all the epasswords on this machine.
55
+ DESC
56
+ def gen_key(arg=nil)
57
+ Commands::GenKey.new(arg == '-').run
58
+ end
59
+
60
+ no_tasks do
61
+ # Add uniform exception handling
62
+ [:encrypt, :epass, :gen_key].each do |original|
63
+ alias_method "#{original}_without_rescue".to_sym, original
64
+
65
+ class_eval <<-RUBY
66
+ def #{original}(*args)
67
+ #{original}_without_rescue(*args)
68
+ rescue SystemCallError => e
69
+ shell.say("\#{e.class}: \#{e}", :RED)
70
+ exit(8)
71
+ rescue Bcdatabase::Error => e
72
+ shell.say(e.message, :RED)
73
+ exit(4)
74
+ rescue Commands::ForcedExit => e
75
+ exit(e.code)
76
+ end
77
+ RUBY
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,47 @@
1
+ require 'bcdatabase/commands'
2
+
3
+ module Bcdatabase::Commands
4
+ class Encrypt
5
+ def initialize(inputfile=nil, outputfile=nil)
6
+ @input = (Pathname.new(inputfile) if inputfile)
7
+ @output = (Pathname.new(outputfile) if outputfile)
8
+ end
9
+
10
+ def run
11
+ begin
12
+ # try to preserve the order by replacing everything using regexes
13
+ contents = inio.read
14
+ contents.gsub!(/\bpassword:.*?$/) { |line|
15
+ "epassword: #{Bcdatabase.encrypt(YAML.load(line)['password'])}"
16
+ }
17
+ outio.write(contents)
18
+ ensure
19
+ @inio.close if @close_in && @inio
20
+ @outio.close if @close_out && @outio
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def inio
27
+ @inio ||=
28
+ if @input
29
+ @close_in = true
30
+ @input.open('r')
31
+ else
32
+ $stdin
33
+ end
34
+ end
35
+
36
+ def outio
37
+ @outio ||=
38
+ if @output
39
+ @output.dirname.mkpath
40
+ @close_out = true
41
+ @output.open('w')
42
+ else
43
+ $stdout
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ require 'bcdatabase/commands'
2
+
3
+ require 'highline'
4
+
5
+ module Bcdatabase::Commands
6
+ class Epass
7
+ def initialize(streaming, opts={})
8
+ @streaming = streaming
9
+ @echo = opts[:echo].nil? ? false : opts[:echo]
10
+ @hl = HighLine.new($stdin, $stderr)
11
+ end
12
+
13
+ def run
14
+ @streaming ? streamed : interactive
15
+ end
16
+
17
+ protected
18
+
19
+ def streamed
20
+ $stdin.readlines.each do |line|
21
+ puts Bcdatabase.encrypt(line.chomp)
22
+ end
23
+ end
24
+
25
+ def interactive
26
+ loop do
27
+ pass = @hl.ask("Password (^C to end): ") do |q|
28
+ # this is configurable because having it false hangs the
29
+ # unit tests.
30
+ q.echo = @echo
31
+ end
32
+ puts " epassword: #{Bcdatabase.encrypt(pass)}"
33
+ end
34
+ rescue Interrupt
35
+ $stderr.puts "Quit"
36
+ rescue EOFError
37
+ $stderr.puts "Quit"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ require 'bcdatabase/commands'
2
+ require 'highline'
3
+ require 'pathname'
4
+
5
+ module Bcdatabase::Commands
6
+ class GenKey
7
+ def initialize(streaming)
8
+ @streaming = streaming
9
+ @hl = HighLine.new($stdin, $stderr)
10
+ end
11
+
12
+ def run
13
+ begin
14
+ key = random_key(128)
15
+ outio.write key
16
+ ensure
17
+ @outio.close if @close_out && @outio
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def random_key(length)
24
+ (1..length).collect { rand(255) }.pack('C*')
25
+ end
26
+
27
+ def outio
28
+ @outio ||=
29
+ if @streaming
30
+ $stdout
31
+ else
32
+ open_key_file
33
+ end
34
+ end
35
+
36
+ def open_key_file
37
+ filename = Pathname.new(Bcdatabase.pass_file)
38
+ if filename.exist?
39
+ unless @hl.agree("This operation will overwrite the existing pass file.\n Are you sure you want to do that? (y/n) ")
40
+ raise ForcedExit.new(1)
41
+ end
42
+ end
43
+ @close_out = true
44
+ filename.dirname.mkpath
45
+ filename.open('w')
46
+ end
47
+ end
48
+ end
@@ -1,241 +1,15 @@
1
- require 'rubygems'
2
- require 'fileutils'
3
- require 'highline'
4
- require 'active_support'
5
- require 'active_support/core_ext/string/inflections'
6
1
  require 'bcdatabase'
7
2
 
8
- HL = HighLine.new
9
-
10
3
  module Bcdatabase::Commands
11
- class Base
12
- protected
13
-
14
- def self.usage(use)
15
- "usage: #{UTILITY_NAME} #{use}"
16
- end
17
-
18
- def self.help_message(use)
19
- msg = [ "#{command_name}: #{summary}", usage(use), "" ]
20
- yield msg if block_given?
21
- msg.join("\n")
22
- end
23
- end
24
-
25
- class Epass < Base
26
- def initialize(argv)
27
- @streaming = argv[-1] == '-'
28
- end
29
-
30
- def self.summary
31
- "Generate epasswords from individual database passwords"
32
- end
33
-
34
- def self.help
35
- help_message("epass [-]") do |msg|
36
- msg << "With no arguments, interactively prompts for passwords and"
37
- msg << " prints the corresponding epassword entry."
38
- msg << ""
39
- msg << "If the last argument is -, reads a newline-separated list"
40
- msg << " of passwords from standard in and prints the corresponding "
41
- msg << " epasswords to standard out."
42
- end
43
- end
44
-
45
- def main
46
- @streaming ? streamed : interactive
47
- end
48
-
49
- private
50
-
51
- def streamed
52
- $stdin.readlines.each do |line|
53
- puts Bcdatabase.encrypt(line.chomp)
54
- end
55
- 0
56
- end
57
-
58
- def interactive
59
- begin
60
- loop do
61
- pass = HL.ask("Password (^C to end): ") do |q|
62
- q.echo = false
63
- end
64
- puts " epassword: #{Bcdatabase.encrypt(pass)}"
65
- end
66
- rescue Interrupt
67
- puts "\nQuit"
68
- end
69
- 0
70
- end
71
- end
72
-
73
- class Encrypt < Base
74
- def initialize(argv)
75
- @input = argv.shift
76
- @output = argv.shift
77
- end
78
-
79
- def self.summary
80
- "Encrypts all the password entries in a bcdatabase YAML file"
81
- end
82
-
83
- def self.help
84
- help_message("encrypt [inputfile [outputfile]]") do |msg|
85
- msg << "Specifically, this command finds all the keys named 'password'"
86
- msg << " in the input YAML and substitutes appropriate 'epassword'"
87
- msg << " keys."
88
- msg << ""
89
- msg << "If inputfile is specified, the source will be that file."
90
- msg << " If not, the source will be standard in."
91
- msg << ""
92
- msg << "If inputfile and outputfile are specified, the new file"
93
- msg << " will be written to the output file. Otherwise the output"
94
- msg << " will go to standard out. Input and output may be the same"
95
- msg << " file."
96
- msg << ""
97
- msg << "You can't read from standard in and write to a file directly; "
98
- msg << " use shell file redirection if you need to do that."
99
- end
100
- end
101
-
102
- def main
103
- inio =
104
- if @input
105
- open(@input, "r")
106
- else
107
- $stdin
108
- end
109
- # try to preserve the order by replacing everything using regexes
110
- contents = inio.read
111
- contents.gsub!(/\bpassword:(\s*)(\S+)\s*?$/) { "epassword:#{$1}#{Bcdatabase.encrypt($2)}" }
112
- outio =
113
- if @output
114
- open(@output, "w")
115
- else
116
- $stdout
117
- end
118
- outio.write(contents)
119
- outio.close
120
- 0
121
- end
122
- end
123
-
124
- class Help < Base
125
- def initialize(argv)
126
- @cmd = argv.shift
127
- end
128
-
129
- def self.summary
130
- "List commands or display help for one; e.g. #{UTILITY_NAME} help epass"
131
- end
132
-
133
- def self.help
134
- help_message "help [command name]"
135
- end
136
-
137
- def main
138
- if @cmd
139
- klass = Bcdatabase::Commands[@cmd]
140
- if klass
141
- msg = klass.respond_to?(:help) ? klass.help : klass.summary
142
- $stderr.puts msg
143
- else
144
- $stderr.puts "Unknown command #{@cmd}"
145
- return 1
146
- end
147
- else
148
- $stderr.puts Bcdatabase::Commands.help
149
- end
150
- 0
151
- end
152
- end
153
-
154
- class GenKey < Base
155
- def initialize(argv)
156
- @stream = argv[-1] == '-'
157
- end
158
-
159
- def self.summary
160
- "Generate a key for bcdatabase to use"
161
- end
4
+ autoload :Encrypt, 'bcdatabase/commands/encrypt'
5
+ autoload :Epass, 'bcdatabase/commands/epass'
6
+ autoload :GenKey, 'bcdatabase/commands/gen_key'
162
7
 
163
- def self.help
164
- help_message("gen-key [-]") do |msg|
165
- msg << "By default, the key will be generated in "
166
- msg << " #{Bcdatabase.pass_file}. If the last argument to this"
167
- msg << " command is -, the key will be generated to standard out"
168
- msg << " instead."
169
- msg << ""
170
- msg << "CAUTION: writing to #{Bcdatabase.pass_file} may overwrite"
171
- msg << " an existing bcdatabase key. If that happens, you will"
172
- msg << " need to reencrypt all the epasswords on this machine."
173
- end
174
- end
175
-
176
- def main
177
- key = random_key(128)
178
- outio =
179
- if @stream
180
- $stdout
181
- else
182
- file = Bcdatabase.pass_file
183
- if File.exist?(file)
184
- 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|
185
- q.case = :down
186
- end
187
- unless sure == 'yes'
188
- exit(0)
189
- end
190
- end
191
- open(file, "w")
192
- end
193
- outio.write key
194
- outio.close
195
- 0
196
- end
197
-
198
- private
199
-
200
- def random_key(length)
201
- k = ""
202
- # This is probably not going to work in ruby 1.9
203
- until k.size == length; k << rand(126 - 32) + 32; end
204
- k
205
- end
206
- end
8
+ class ForcedExit < StandardError
9
+ attr_reader :code
207
10
 
208
- class << self
209
- def help
210
- all_help = commands.collect { |c| [c.command_name, c.summary] }.sort_by { |p| p[0] }
211
- max_name_length = all_help.collect { |a| a[0].size }.max
212
- msg = Base.usage "<command> [args]\n"
213
- msg << "Utility for bcdatabase #{Bcdatabase::VERSION}\n"
214
- msg << "Commands:\n"
215
- msg << all_help.collect { |name, help| " %#{max_name_length + 1}s %s" % [name, help] }.join("\n")
11
+ def initialize(code)
12
+ @code = code
216
13
  end
217
-
218
- # Lists all the commands
219
- def commands
220
- constants.reject { |cs| cs == "Base" }.collect { |cs| const_get(cs) }.select { |c| c.kind_of? Class }
221
- end
222
-
223
- # Locates the command class for a user-entered command name.
224
- # Returns nil if the name is invalid.
225
- def command(command_name)
226
- begin
227
- klassname = command_name.gsub('-', '_').camelize
228
- Bcdatabase::Commands.const_get "#{klassname}"
229
- rescue NameError
230
- nil
231
- end
232
- end
233
- alias :[] :command
234
- end
235
- end
236
-
237
- class Class
238
- def command_name
239
- name.gsub(Bcdatabase::Commands.name + "::", '').underscore.gsub("_", '-')
240
14
  end
241
15
  end
@@ -1,3 +1,3 @@
1
1
  module Bcdatabase
2
- VERSION = '1.0.6'
2
+ VERSION = '1.1.0'
3
3
  end
data/lib/bcdatabase.rb CHANGED
@@ -2,9 +2,14 @@ require 'yaml'
2
2
  require 'openssl'
3
3
  require 'digest/sha2'
4
4
  require 'base64'
5
+ require 'pathname'
6
+
7
+ # Requiring just extract_options doesn't work on AS 2.3.
8
+ require 'active_support/core_ext/array'
5
9
 
6
10
  module Bcdatabase
7
11
  autoload :VERSION, 'bcdatabase/version'
12
+ autoload :CLI, 'bcdatabase/cli'
8
13
  autoload :Commands, 'bcdatabase/commands'
9
14
 
10
15
  DEFAULT_BASE_PATH = File.join('/', 'etc', 'nubic', 'db')
@@ -12,22 +17,51 @@ module Bcdatabase
12
17
  CIPHER = 'aes-256-ecb'
13
18
 
14
19
  class << self
15
- def load(path=nil)
16
- path ||= base_path
20
+ ##
21
+ # The main entry point for Bcdatabase.
22
+ #
23
+ # @overload load(options={})
24
+ # (See other alternative for option definitions.)
25
+ # @return [DatabaseConfigurations] a new instance using the
26
+ # default path.
27
+ # @overload load(path=nil, options={})
28
+ # @param [String,nil] path the directory to load from. If nil,
29
+ # will use the value in the environment variable
30
+ # `BCDATABASE_PATH`. If that's nil, too, it will use the
31
+ # default path.
32
+ # @param [Hash,nil] options additional options affecting the
33
+ # load behavior.
34
+ # @option options :transforms [Array<Symbol, #call>] ([]) Custom
35
+ # transforms. This can either be a symbol naming a
36
+ # {DatabaseConfigurations.BUILT_IN_TRANSFORMS built-in
37
+ # transform} or a callable which is the transform itself. A
38
+ # transform is a function that takes three arguments (the
39
+ # entry itself, the entry name, and the group name) and
40
+ # returns a new copy, modified as desired. It may also return
41
+ # nil to indicate that it doesn't wish to make any changes.
42
+ # @return [DatabaseConfigurations] a new instance reflecting
43
+ # the selected path.
44
+ def load(*args)
45
+ options = args.extract_options!
46
+ path ||= (args.first || base_path)
17
47
  files = Dir.glob(File.join(path, "*.yml")) + Dir.glob(File.join(path, "*.yaml"))
18
- DatabaseConfigurations.new(files)
48
+ DatabaseConfigurations.new(files, options[:transforms] || [])
19
49
  end
20
50
 
51
+ ##
52
+ # @private exposed for collaboration
21
53
  def encrypt(s)
22
54
  Base64.encode64(encipher(:encrypt, s)).strip
23
55
  end
24
56
 
57
+ ##
58
+ # @private exposed for collaboration
25
59
  def decrypt(s)
26
60
  encipher(:decrypt, Base64.decode64(s))
27
61
  end
28
62
 
29
63
  def pass_file
30
- ENV["BCDATABASE_PASS"] || DEFAULT_PASS_FILE
64
+ Pathname.new(ENV["BCDATABASE_PASS"] || DEFAULT_PASS_FILE)
31
65
  end
32
66
 
33
67
  private
@@ -43,15 +77,30 @@ module Bcdatabase
43
77
  end
44
78
 
45
79
  def pass
46
- return @pass if instance_variable_defined? :@pass
47
-
48
- contents = open(pass_file).read.chomp
49
- # This code may not work correctly on Ruby 1.9
50
- if contents.size == 32
51
- @pass = contents
52
- else
53
- @pass = Digest::SHA256.digest(contents)
80
+ @passes ||= { }
81
+ @passes[pass_file] ||=
82
+ begin
83
+ contents = read_pass_file
84
+ # This code may not work correctly on Ruby 1.9
85
+ if contents.size == 32
86
+ contents
87
+ else
88
+ Digest::SHA256.digest(contents)
89
+ end
90
+ end
91
+ end
92
+
93
+ def read_pass_file
94
+ unless pass_file.readable?
95
+ msg = [
96
+ "Bcdatabase keyfile #{pass_file} is not readable. Possible solutions:",
97
+ "* Use bcdatabase gen-key to generate a key",
98
+ "* Change the path by setting BCDATABASE_PASS in the environment",
99
+ "* Check the permissions on #{pass_file}"
100
+ ].compact.join("\n")
101
+ raise Bcdatabase::Error, msg
54
102
  end
103
+ pass_file.read
55
104
  end
56
105
 
57
106
  def base_path
@@ -59,8 +108,36 @@ module Bcdatabase
59
108
  end
60
109
  end
61
110
 
111
+ ##
112
+ # The set of groups and entries returned by one call to {Bcdatabase.load}.
62
113
  class DatabaseConfigurations
63
- def initialize(files)
114
+ BUILT_IN_TRANSFORMS = {
115
+ :key_defaults => lambda { |entry, name, group|
116
+ { 'username' => name, 'database' => name }.merge(entry)
117
+ },
118
+ :decrypt => lambda { |entry, name, group|
119
+ entry.merge({ 'password' => Bcdatabase.decrypt(entry['epassword']) }) if entry['epassword']
120
+ },
121
+ :datamapper => lambda { |entry, name, group|
122
+ entry.merge('adapter' => entry['datamapper_adapter']) if entry['datamapper_adapter']
123
+ }
124
+ }
125
+
126
+ ##
127
+ # Creates a configuration from a set of YAML files.
128
+ #
129
+ # General use of the library should not use this method, but
130
+ # instead should use {Bcdatabase.load}.
131
+ def initialize(files, transforms=[])
132
+ @transforms = ([:key_defaults, :decrypt] + transforms).collect do |t|
133
+ case t
134
+ when Symbol
135
+ BUILT_IN_TRANSFORMS[t] or fail "No built-in transform named #{t.inspect}"
136
+ else
137
+ fail 'Transforms must by callable' unless t.respond_to?(:call)
138
+ t
139
+ end
140
+ end
64
141
  @files = files
65
142
  @map = { }
66
143
  files.each do |filename|
@@ -69,10 +146,18 @@ module Bcdatabase
69
146
  end
70
147
  end
71
148
 
149
+ ##
150
+ # @return [Hash] the entry for the given group and name after all
151
+ # transformation is complete.
72
152
  def [](groupname, dbname)
73
153
  create_entry(groupname.to_s, dbname.to_s)
74
154
  end
75
155
 
156
+ ##
157
+ # This method implements the Rails database.yml integration
158
+ # described in full in the {file:README.markdown}.
159
+ #
160
+ # @return [String] a YAMLized view of a configuration entry.
76
161
  def method_missing(name, *args)
77
162
  groupname = (args[0] or raise "Database configuration group not specified for #{name}")
78
163
  dbname = (args[1] or raise "Database entry name not specified for #{name}")
@@ -94,16 +179,16 @@ module Bcdatabase
94
179
 
95
180
  def create_entry(groupname, dbname)
96
181
  group = @map[groupname] or raise Error.new("No database configuration group named #{groupname.inspect} found. (Found #{@map.keys.inspect}.)")
97
- db = group[dbname] or raise Error.new("No database entry for #{dbname.inspect} in #{groupname}")
98
- merged = { 'username' => dbname, 'database' => dbname } \
99
- .merge(group['defaults'] || {}) \
100
- .merge(group['default'] || {}) \
101
- .merge(db)
102
- # include the decrypted password if an encrypted one was provided
103
- if merged['epassword']
104
- merged['password'] = Bcdatabase.decrypt(merged['epassword'])
182
+ unless group.has_key?(dbname)
183
+ raise Error.new("No database entry for #{dbname.inspect} in #{groupname}.")
184
+ end
185
+ db = group[dbname] || {}
186
+ base = (group['defaults'] || {}).
187
+ merge(group['default'] || {}).
188
+ merge(db)
189
+ @transforms.inject(base) do |result, transform|
190
+ transform.call(result, dbname, groupname) || result
105
191
  end
106
- merged
107
192
  end
108
193
 
109
194
  def unseparated_yaml(arg)
@@ -111,5 +196,5 @@ module Bcdatabase
111
196
  end
112
197
  end
113
198
 
114
- class Error < Exception; end
199
+ class Error < StandardError; end
115
200
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bcdatabase
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 6
10
- version: 1.0.6
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Rhett Sutphin
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-06-28 00:00:00 -05:00
18
+ date: 2011-08-30 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -63,24 +63,72 @@ dependencies:
63
63
  type: :runtime
64
64
  version_requirements: *id003
65
65
  - !ruby/object:Gem::Dependency
66
- name: rspec
66
+ name: thor
67
67
  prerelease: false
68
68
  requirement: &id004 !ruby/object:Gem::Requirement
69
69
  none: false
70
70
  requirements:
71
71
  - - ~>
72
72
  - !ruby/object:Gem::Version
73
- hash: 11
73
+ hash: 43
74
+ segments:
75
+ - 0
76
+ - 14
77
+ - 6
78
+ version: 0.14.6
79
+ type: :runtime
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ name: bundler
83
+ prerelease: false
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ hash: 9
74
90
  segments:
75
91
  - 1
92
+ - 0
93
+ - 15
94
+ version: 1.0.15
95
+ type: :development
96
+ version_requirements: *id005
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ prerelease: false
100
+ requirement: &id006 !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ hash: 63
106
+ segments:
107
+ - 0
108
+ - 9
76
109
  - 2
77
- version: "1.2"
110
+ version: 0.9.2
78
111
  type: :development
79
- version_requirements: *id004
112
+ version_requirements: *id006
113
+ - !ruby/object:Gem::Dependency
114
+ name: rspec
115
+ prerelease: false
116
+ requirement: &id007 !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ~>
120
+ - !ruby/object:Gem::Version
121
+ hash: 15
122
+ segments:
123
+ - 2
124
+ - 6
125
+ version: "2.6"
126
+ type: :development
127
+ version_requirements: *id007
80
128
  - !ruby/object:Gem::Dependency
81
129
  name: ci_reporter
82
130
  prerelease: false
83
- requirement: &id005 !ruby/object:Gem::Requirement
131
+ requirement: &id008 !ruby/object:Gem::Requirement
84
132
  none: false
85
133
  requirements:
86
134
  - - ~>
@@ -91,8 +139,24 @@ dependencies:
91
139
  - 6
92
140
  version: "1.6"
93
141
  type: :development
94
- version_requirements: *id005
95
- description: bcdatabase is a tool for storing passwords and other database configuration information outside of your application source tree.
142
+ version_requirements: *id008
143
+ - !ruby/object:Gem::Dependency
144
+ name: yard
145
+ prerelease: false
146
+ requirement: &id009 !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ~>
150
+ - !ruby/object:Gem::Version
151
+ hash: 7
152
+ segments:
153
+ - 0
154
+ - 7
155
+ - 2
156
+ version: 0.7.2
157
+ type: :development
158
+ version_requirements: *id009
159
+ description: bcdatabase is a tool for storing passwords and other configuration information outside of your application source tree.
96
160
  email: rhett@detailedbalance.net
97
161
  executables:
98
162
  - bcdatabase
@@ -104,12 +168,17 @@ files:
104
168
  - CHANGELOG.markdown
105
169
  - LICENSE
106
170
  - README.markdown
171
+ - bcdatabase.gemspec
107
172
  - bin/bcdatabase
173
+ - lib/bcdatabase/cli.rb
174
+ - lib/bcdatabase/commands/encrypt.rb
175
+ - lib/bcdatabase/commands/epass.rb
176
+ - lib/bcdatabase/commands/gen_key.rb
108
177
  - lib/bcdatabase/commands.rb
109
178
  - lib/bcdatabase/version.rb
110
179
  - lib/bcdatabase.rb
111
180
  has_rdoc: true
112
- homepage: http://github.com/rsutphin/bcdatabase
181
+ homepage: http://github.com/NUBIC/bcdatabase
113
182
  licenses: []
114
183
 
115
184
  post_install_message: