crypto_env_var 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4c377692700f1b2c90560712d95d1e75759e6081
4
+ data.tar.gz: 5bbbbbb5acaaac302b139763eb1a6c33623e666b
5
+ SHA512:
6
+ metadata.gz: afe175462500360433166c587d568d75623f8a68d2dbe284dd274a5db678ed1710e4ca1f0e8ef20c83298192029701226c270cc28ae6ae39a8085bc7d490a931
7
+ data.tar.gz: c2b1d0bd66ce7980fb91d1752f25cd71b6b0d3e03f4097186c68e21e8cee7aa1a3e7d3e822b97ccb82867654d91f57687b182145e910254c7fad7222dc006d05
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,12 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4
5
+ - 2.3
6
+ - 2.2
7
+ script: "rspec"
8
+ before_install: gem install bundler -v 1.15.4
9
+ notifications:
10
+ email:
11
+ on_success: never
12
+ on_failure: never
@@ -0,0 +1,7 @@
1
+ # CryptoEnvVar Changelog
2
+
3
+ ## v1.0.0
4
+
5
+ * Initial release
6
+ * Asymmetric encryption and decryption functions
7
+ * Helper to seed the ENV of the current Ruby process with an encrypted payload.
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 crypto_env_var.gemspec
6
+ gemspec
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2017 Tommaso Pavese
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,159 @@
1
+ # CryptoEnvVar
2
+
3
+ [![Build Status](https://travis-ci.org/deliveroo/crypto_env_var.svg?branch=master)](https://travis-ci.org/deliveroo/crypto_env_var)
4
+
5
+ **CryptoEnvVar** provides:
6
+
7
+ * [Asymmetric encryption](#asymmetric-encryption) of JSON-serializable Ruby objects using a RSA keypair.
8
+ * [Secrets management for the Ruby ENV](#secrets-management-for-the-ruby-env).
9
+
10
+ ## Asymmetric Encryption
11
+
12
+ The gem provides two functions that can be used on their own, even without the ENV management functionality.
13
+
14
+ Any Ruby object that can be serialized and deserialized with `JSON.dump` nd `JSON.load` is a valid input. This means that you can use a Hash, but that symbols won't be preserved. The output is a base64-encoded string, safe to be stored and transferred as text.
15
+
16
+ ```ruby
17
+ rsa_private_key_string = File.read("path/to/rsa_key.pem")
18
+ rsa_public_key_string = File.read("path/to/rsa_key.pub.pem")
19
+
20
+ data = { "foo" => 42, "bar" => [1, 3, 3, 7], "baz" => true }
21
+
22
+ ciphertext = CryptoEnvVar.encrypt(data, rsa_private_key_string)
23
+ plaintext = CryptoEnvVar.decrypt(ciphertext, rsa_public_key_string)
24
+
25
+ data == plaintext # true
26
+ ```
27
+
28
+ While the public key can only be used to decrypt, the private key can be used to both encrypt and decrypt:
29
+
30
+ ```ruby
31
+ a = CryptoEnvVar.decrypt(encrypted, rsa_private_key_string)
32
+ b = CryptoEnvVar.decrypt(encrypted, rsa_public_key_string)
33
+
34
+ a == b && a == data # true
35
+ ```
36
+
37
+ ## Secrets management for the Ruby ENV
38
+
39
+ A common practice when deploying applications is to customize their runtime behaviour by providing configuration in the system ENV.
40
+
41
+ Often this means that something, at some point, needs to get the raw configuration values and set them on the machine (or container, or Heroku dyno) that will run the application. Since the configuration usually contains sensitive values, for example database passwords and other auth credentials, this is less than ideal.
42
+
43
+ A solution is to encrypt the configuration data, set it in the ENV encrypted, and then allow the application to decrypt it when it boots. This library aims to make this pattern easier to adopt.
44
+
45
+ First off, the app configuration needs to be encrypted. This should ideally be done by an automated tool. For example:
46
+
47
+ ```ruby
48
+ app_config = {
49
+ "DB_URL" => "postgres://user:password123@thedb.hostname.com:1234/db_name",
50
+ "CACHE_URL" => "redis://user:password456@another.url.com:5678",
51
+ "PAYMENTS_GATEWAY_API_TOKEN" => "SECRET_TOKEN_ZOMG",
52
+ }
53
+
54
+ private_key = File.read("path/to/rsa_key.pem")
55
+
56
+ File.write("encrypted_env.txt", CryptoEnvVar.encrypt(app_config, private_key))
57
+ ```
58
+
59
+ Done that, the encrypted configuration and the public key need to be passed to the starting application. By default, `CryptoEnvVar` will try to read them from two ENV variables:
60
+
61
+ ```
62
+ export CRYPTO_ENV='the base64 encrypted configuration generated above'
63
+ export CRYPTO_ENV_DECRYPT_KEY='the public key'
64
+
65
+ ruby my_app.rb
66
+ ```
67
+
68
+ If you choose the default names, then all you need to do is call `CryptoEnvVar.bootstrap!` early in the application initialization process (for example in `config.ru` for a Rack app), and it will load and decrypt the encrypted env, then copy its data into the `ENV` of the current process.
69
+
70
+ ```ruby
71
+ ENV["DB_URL"]
72
+ # => nil
73
+
74
+ CryptoEnvVar.bootstrap!
75
+
76
+ ENV["DB_URL"]
77
+ # => "postgres://user:password123@thedb.hostname.com:1234/db_name"
78
+ ```
79
+
80
+
81
+ The sources of both the encrypted configuration and the public key can be configured with the `:read_from` and `:decrypt_with` options, respectively. Valid values are strings or callable objects (you can use a proc or lambda or you can implement your own loaders). For example:
82
+
83
+ ```ruby
84
+ CryptoEnvVar.bootstrap!(
85
+ read_from: -> { File.read(ENV["secrets_file_path"]).chomp },
86
+ decrypt_with: SecurePublicKeyFetcher.new
87
+ )
88
+
89
+ class SecurePublicKeyFetcher
90
+ def call
91
+ # ...
92
+ end
93
+ end
94
+ ```
95
+
96
+
97
+ By default `CryptoEnvVar.bootstrap!` will copy all the decrypted configuration variables into the ENV, overriding any preset value. If you want to disable this, for example because you want any explicitly set ENV variable to have the precence, you can do so with this option:
98
+
99
+ ```ruby
100
+ CryptoEnvVar.bootstrap!(override_env: false)
101
+ ```
102
+
103
+
104
+ ## A note on the RSA keypairs
105
+
106
+ Only keypairs without passphrase are supported at this stage.
107
+
108
+ You can create an RSA keypair in a shell with:
109
+
110
+ ```
111
+ openssl genrsa -out private_key.pem 2048
112
+ openssl rsa -pubout -in private_key.pem -out public_key.pem
113
+ ```
114
+
115
+ Please note that the public key is **NOT** the same thing as the SSH public key file [normally generated with `ssh-keygen`](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/). You can still use a private RSA key generated with `ssh-keygen`, but then you have to extract the public key with the `openssl` command shown above.
116
+
117
+ Alternatively, you can use the [OpenSSL classes](http://ruby-doc.org/stdlib-2.4.1/libdoc/openssl/rdoc/OpenSSL/PKey/RSA.html) from the Ruby standard library:
118
+
119
+ ```ruby
120
+ require "openssl"
121
+
122
+ new_keypair = OpenSSL::PKey::RSA.generate(2048)
123
+ new_private_key = new_keypair.to_s
124
+ new_public_key = new_keypair.public_key.to_s
125
+
126
+ imported_keypair = OpenSSL::PKey::RSA.new(File.read("private_rsa_key.pem"))
127
+ imported_private_key = imported_keypair.to_s
128
+ imported_public_key = imported_keypair.public_key.to_s
129
+
130
+ imported_private_key == File.read("private_rsa_key.pem") # true
131
+ ```
132
+
133
+
134
+ ## Installation
135
+
136
+ Add this line to your application's Gemfile:
137
+
138
+ ```ruby
139
+ gem 'crypto_env_var'
140
+ ```
141
+
142
+ And then execute:
143
+
144
+ $ bundle
145
+
146
+ Or install it yourself as:
147
+
148
+ $ gem install crypto_env_var
149
+
150
+
151
+ ## Development
152
+
153
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
154
+
155
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
156
+
157
+ ## Contributing
158
+
159
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/crypto_env_var.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "pry"
5
+
6
+ require "crypto_env_var"
7
+
8
+ require File.expand_path("../../spec/support/shared_helpers.rb", __FILE__)
9
+ include SharedHelpers
10
+
11
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "crypto_env_var/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "crypto_env_var"
8
+ spec.version = CryptoEnvVar::VERSION
9
+ spec.authors = ["Tommaso Pavese"]
10
+ spec.email = ["tommaso.pavese@deliveroo.co.uk"]
11
+
12
+ spec.summary = %q{Utilities to protect the application env}
13
+ spec.description = <<-EOS
14
+ Utilities to protect the application env.
15
+ Use an keypair to encrypt an env Hash to a blob and populate ENV from the encryped blob.
16
+ EOS
17
+ spec.homepage = "https://github.com/deliveroo/crypto_env_var"
18
+
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
21
+ f.match(%r{^(test|spec|features)/})
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_dependency 'msgpack', '~> 1.1'
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.15"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+
33
+ spec.add_development_dependency 'pry', '~> 0.10.4'
34
+ end
@@ -0,0 +1,49 @@
1
+ require "crypto_env_var/version"
2
+ require "crypto_env_var/cipher"
3
+ require "crypto_env_var/utils"
4
+
5
+
6
+ module CryptoEnvVar
7
+ CRYPTO_ENV_VAR = "CRYPTO_ENV"
8
+ DECRYPT_KEY_VAR = "CRYPTO_ENV_DECRYPT_KEY"
9
+ CRYPTO_ENV = lambda { ENV.fetch(CRYPTO_ENV_VAR) }
10
+ DECRYPT_KEY = lambda { ENV.fetch(DECRYPT_KEY_VAR) }
11
+
12
+
13
+ class << self
14
+ def bootstrap!(read_from: CRYPTO_ENV, decrypt_with: DECRYPT_KEY, override_env: true)
15
+ data = read_value(read_from)
16
+ key = read_value(decrypt_with)
17
+ hash = decrypt(data, key)
18
+
19
+ hash.each_pair do |key, value|
20
+ next if (!override_env && ENV.member?(key))
21
+ ENV[key] = value
22
+ end
23
+ end
24
+
25
+
26
+ def encrypt(data, private_key_string)
27
+ json = Utils.serialize(data)
28
+ cipher = Cipher.new(private_key_string)
29
+ encrypted_data = cipher.encrypt(json)
30
+ Utils.encode(encrypted_data)
31
+ end
32
+
33
+
34
+ def decrypt(string, public_key_string)
35
+ encrypted_data = Utils.decode(string)
36
+ cipher = Cipher.new(public_key_string)
37
+ json = cipher.decrypt(encrypted_data)
38
+ Utils.deserialize(json)
39
+ end
40
+
41
+
42
+ private
43
+
44
+
45
+ def read_value(source)
46
+ source.respond_to?(:call) ? source.call() : source
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,132 @@
1
+ require "openssl"
2
+ require "msgpack"
3
+
4
+ module CryptoEnvVar
5
+ class Cipher
6
+ VERSION = "v1".freeze # To support multiple versions in the future
7
+
8
+
9
+ # Can be initialized with a private or public
10
+ # RSA key.
11
+ # - private: encrypt and decrypt.
12
+ # - public: decrypt only.
13
+ #
14
+ def initialize(key_string)
15
+ @key = OpenSSL::PKey::RSA.new(key_string)
16
+ end
17
+
18
+
19
+ # Encrypt the plaintext with symmetric AES.
20
+ #
21
+ # Encrypt the AES key with the private RSA key.
22
+ #
23
+ # Join the encrypted text, the encrypted key and
24
+ # the initialization vector in a payload string.
25
+ #
26
+ # Get a digest of the the payload, then encrypt
27
+ # it with the private RSA key and append it to
28
+ # the payload.
29
+ #
30
+ def encrypt(plaintext)
31
+ ciphertext, key, iv = aes_encrypt(plaintext)
32
+
33
+ key = rsa_encrypt(key)
34
+ payload = [VERSION, key, iv, ciphertext].to_msgpack
35
+ digest = rsa_encrypt(sha2_digest(payload))
36
+
37
+ [payload, digest].to_msgpack
38
+ end
39
+
40
+
41
+ # Extract the digest, decrypt it with the RSA
42
+ # public key, then validate the integrity of the
43
+ # rest of the payload.
44
+ #
45
+ # Decrypt the AES key with the RSA public key.
46
+ #
47
+ # Decrypt the ciphertext with symmetric AES.
48
+ #
49
+ def decrypt(data)
50
+ payload, digest = MessagePack.unpack(data)
51
+
52
+ digest = rsa_decrypt(digest)
53
+ validate_digest!(payload, digest)
54
+
55
+ _version, key, iv, ciphertext = MessagePack.unpack(payload)
56
+
57
+ key = rsa_decrypt(key)
58
+
59
+ aes_decrypt(ciphertext, key, iv)
60
+ end
61
+
62
+
63
+ private
64
+
65
+
66
+ # Plain RSA private key encryption.
67
+ # It can only encrypt data smaller than the key size.
68
+ #
69
+ def rsa_encrypt(plaintext)
70
+ @key.private_encrypt(plaintext)
71
+ end
72
+
73
+
74
+ # Plain RDS public key decryption.
75
+ # It can only decrypt data encrypted with the private
76
+ # key from the keypair.
77
+ #
78
+ def rsa_decrypt(ciphertext)
79
+ @key.public_decrypt(ciphertext)
80
+ end
81
+
82
+
83
+ # AES symmetric encryption.
84
+ #
85
+ def aes_encrypt(plaintext)
86
+ cipher = build_aes_ciper
87
+ cipher.encrypt
88
+ key = cipher.random_key
89
+ iv = cipher.random_iv
90
+
91
+ ciphertext = cipher.update(plaintext) + cipher.final
92
+
93
+ [ciphertext, key, iv]
94
+ end
95
+
96
+
97
+ # AES symmetric decryption.
98
+ #
99
+ def aes_decrypt(ciphertext, key, iv)
100
+ cipher = build_aes_ciper
101
+ cipher.decrypt
102
+ cipher.key = key
103
+ cipher.iv = iv
104
+
105
+ cipher.update(ciphertext) + cipher.final
106
+ end
107
+
108
+
109
+ def build_aes_ciper
110
+ OpenSSL::Cipher::AES256.new(:CBC)
111
+ end
112
+
113
+
114
+ def sha2_digest(data)
115
+ OpenSSL::Digest::SHA512.new.digest(data)
116
+ end
117
+
118
+
119
+ def validate_digest!(plaintext, digest)
120
+ unless sha2_digest(plaintext) == digest
121
+ raise DigestVerificationError
122
+ end
123
+ end
124
+
125
+
126
+ class DigestVerificationError < StandardError
127
+ def message
128
+ "The payload has been tampered with."
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,24 @@
1
+ require "json"
2
+ require "base64"
3
+
4
+ module CryptoEnvVar
5
+ module Utils
6
+ class << self
7
+ def serialize(data)
8
+ JSON.dump(data)
9
+ end
10
+
11
+ def deserialize(string)
12
+ JSON.load(string)
13
+ end
14
+
15
+ def encode(string)
16
+ Base64.strict_encode64(string)
17
+ end
18
+
19
+ def decode(string)
20
+ Base64.strict_decode64(string)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module CryptoEnvVar
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crypto_env_var
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tommaso Pavese
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-10-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: msgpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.4
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.10.4
83
+ description: |2
84
+ Utilities to protect the application env.
85
+ Use an keypair to encrypt an env Hash to a blob and populate ENV from the encryped blob.
86
+ email:
87
+ - tommaso.pavese@deliveroo.co.uk
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".gitignore"
93
+ - ".rspec"
94
+ - ".travis.yml"
95
+ - CHANGELOG.md
96
+ - Gemfile
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - bin/console
101
+ - bin/setup
102
+ - crypto_env_var.gemspec
103
+ - lib/crypto_env_var.rb
104
+ - lib/crypto_env_var/cipher.rb
105
+ - lib/crypto_env_var/utils.rb
106
+ - lib/crypto_env_var/version.rb
107
+ homepage: https://github.com/deliveroo/crypto_env_var
108
+ licenses: []
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.6.13
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Utilities to protect the application env
130
+ test_files: []
131
+ has_rdoc: