├── .ruby-version ├── spec ├── rails-2-test │ ├── config.ru │ ├── script │ │ └── console │ └── config │ │ └── application.rb ├── rails-3-test │ ├── config.ru │ ├── script │ │ └── rails │ ├── config │ │ └── application.rb │ ├── .chamber.pub.pem │ └── .chamber.pem ├── rails-4-test │ ├── bin │ │ └── rails │ ├── config.ru │ └── config │ │ └── application.rb ├── rails-engine-test │ └── spec │ │ └── dummy │ │ ├── config.ru │ │ ├── script │ │ └── rails │ │ ├── config │ │ └── application.rb │ │ └── .chamber.pub.pem ├── fixtures │ ├── keys │ │ ├── .chamber.pem │ │ ├── .chamber.pub.pem │ │ ├── .chamber.test.pem │ │ ├── .chamber.test.pub.pem │ │ ├── .chamber.enc │ │ ├── .chamber.test.enc │ │ ├── .foo.development.pem │ │ ├── .chamber.development.pem │ │ ├── .chamber.production.pem │ │ ├── .chamber.production.pub.pem │ │ ├── .foo.development.pub.pem │ │ ├── .chamber.development.pub.pem │ │ ├── .foo.development.enc │ │ ├── .chamber.development.enc │ │ ├── .chamber.examplehostcom.pem │ │ ├── .chamber.examplehostcom.pub.pem │ │ ├── .chamber.production.enc │ │ ├── real │ │ │ ├── .chamber.signature.enc.pass │ │ │ ├── .chamber.pub.pem │ │ │ ├── .chamber.test.pub.pem │ │ │ ├── .chamber.production.pub.pem │ │ │ ├── .chamber.signature.pub.pem │ │ │ ├── .chamber.development.pub.pem │ │ │ ├── .chamber.pem │ │ │ ├── .chamber.test.pem │ │ │ ├── .chamber.development.pem │ │ │ ├── .chamber.production.pem │ │ │ ├── .chamber.signature.pem │ │ │ ├── .chamber.enc │ │ │ ├── .chamber.test.enc │ │ │ ├── .chamber.signature.enc │ │ │ ├── .chamber.development.enc │ │ │ └── .chamber.production.enc │ │ └── .chamber.examplehostcom.enc │ └── settings.yml ├── lib │ └── chamber │ │ ├── keys │ │ └── base_spec.rb │ │ ├── commands │ │ ├── files_spec.rb │ │ ├── secure_spec.rb │ │ ├── unsecure_spec.rb │ │ ├── sign_spec.rb │ │ ├── show_spec.rb │ │ └── verify_spec.rb │ │ ├── filters │ │ ├── translate_secure_keys_filter_spec.rb │ │ ├── secure_filter_spec.rb │ │ ├── failed_decryption_filter_spec.rb │ │ ├── namespace_filter_spec.rb │ │ └── insecure_filter_spec.rb │ │ ├── types │ │ └── secured_spec.rb │ │ ├── files │ │ └── signature_spec.rb │ │ ├── key_pair_spec.rb │ │ └── namespace_set_spec.rb ├── spec_key.pub └── spec_key ├── .markdownlintignore ├── .github ├── FUNDING.yml ├── workflows │ ├── labels.yml │ ├── testing.yml │ └── locking.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug.md ├── contributing.md ├── labels.yml └── pull_request_template.md ├── checksum ├── chamber-2.10.0.gem.md5 ├── chamber-2.10.1.gem.md5 ├── chamber-2.10.2.gem.md5 ├── chamber-2.11.0.gem.md5 ├── chamber-2.12.0.gem.md5 ├── chamber-2.12.1.gem.md5 ├── chamber-2.12.2.gem.md5 ├── chamber-2.12.3.gem.md5 ├── chamber-2.12.5.gem.md5 ├── chamber-2.13.0.gem.md5 ├── chamber-2.13.1.gem.md5 ├── chamber-2.14.0.gem.md5 ├── chamber-2.14.1.gem.md5 ├── chamber-2.14.2.gem.md5 ├── chamber-2.14.3.gem.md5 ├── chamber-2.9.0.gem.md5 ├── chamber-2.9.1.gem.md5 ├── chamber-3.0.0.gem.md5 ├── chamber-3.0.1.gem.md5 ├── chamber-3.1.0.gem.md5 ├── chamber-3.1.1.gem.md5 ├── chamber-3.0.0rc2.gem.md5 ├── chamber-2.10.0.gem.sha256 ├── chamber-2.10.1.gem.sha256 ├── chamber-2.10.2.gem.sha256 ├── chamber-2.11.0.gem.sha256 ├── chamber-2.12.0.gem.sha256 ├── chamber-2.12.1.gem.sha256 ├── chamber-2.12.2.gem.sha256 ├── chamber-2.12.3.gem.sha256 ├── chamber-2.12.5.gem.sha256 ├── chamber-2.13.0.gem.sha256 ├── chamber-2.13.1.gem.sha256 ├── chamber-2.14.0.gem.sha256 ├── chamber-2.14.1.gem.sha256 ├── chamber-2.14.2.gem.sha256 ├── chamber-2.14.3.gem.sha256 ├── chamber-2.9.0.gem.sha256 ├── chamber-2.9.1.gem.sha256 ├── chamber-3.0.0.gem.sha256 ├── chamber-3.0.1.gem.sha256 ├── chamber-3.1.0.gem.sha256 ├── chamber-3.1.1.gem.sha256 ├── chamber-3.0.0rc2.gem.sha256 ├── chamber-2.10.0.gem.sha512 ├── chamber-2.10.1.gem.sha512 ├── chamber-2.10.2.gem.sha512 ├── chamber-2.11.0.gem.sha512 ├── chamber-2.12.0.gem.sha512 ├── chamber-2.12.1.gem.sha512 ├── chamber-2.12.2.gem.sha512 ├── chamber-2.12.3.gem.sha512 ├── chamber-2.12.5.gem.sha512 ├── chamber-2.13.0.gem.sha512 ├── chamber-2.13.1.gem.sha512 ├── chamber-2.14.0.gem.sha512 ├── chamber-2.14.1.gem.sha512 ├── chamber-2.14.2.gem.sha512 ├── chamber-2.14.3.gem.sha512 ├── chamber-2.9.0.gem.sha512 ├── chamber-2.9.1.gem.sha512 ├── chamber-3.0.0.gem.sha512 ├── chamber-3.0.1.gem.sha512 ├── chamber-3.1.0.gem.sha512 ├── chamber-3.1.1.gem.sha512 └── chamber-3.0.0rc2.gem.sha512 ├── lib ├── chamber │ ├── version.rb │ ├── rails.rb │ ├── errors │ │ ├── invalid_key_type.rb │ │ ├── decryption_failure.rb │ │ ├── disallowed_class.rb │ │ ├── non_conforming_key.rb │ │ ├── environment_conversion.rb │ │ ├── missing_setting.rb │ │ └── missing_index.rb │ ├── commands │ │ ├── files.rb │ │ ├── travis.rb │ │ ├── sign.rb │ │ ├── verify.rb │ │ ├── base.rb │ │ ├── secure.rb │ │ ├── unsecure.rb │ │ ├── comparable.rb │ │ ├── show.rb │ │ ├── compare.rb │ │ └── securable.rb │ ├── rubinius_fix.rb │ ├── integrations │ │ ├── rails.rb │ │ └── sinatra.rb │ ├── refinements │ │ ├── enumerable.rb │ │ ├── hash.rb │ │ └── deep_dup.rb │ ├── encryption_methods │ │ ├── none.rb │ │ ├── public_key.rb │ │ └── ssl.rb │ ├── keys │ │ ├── decryption.rb │ │ ├── encryption.rb │ │ └── base.rb │ ├── filters │ │ ├── translate_secure_keys_filter.rb │ │ ├── namespace_filter.rb │ │ ├── secure_filter.rb │ │ ├── failed_decryption_filter.rb │ │ ├── insecure_filter.rb │ │ ├── encryption_filter.rb │ │ ├── decryption_filter.rb │ │ └── environment_filter.rb │ ├── configuration.rb │ ├── types │ │ └── secured.rb │ ├── instance.rb │ ├── key_pair.rb │ ├── files │ │ └── signature.rb │ ├── context_resolver.rb │ └── namespace_set.rb └── chamber.rb ├── bin ├── chamber ├── console └── setup ├── Rakefile ├── Gemfile ├── .rubocop.yml ├── .rubocop_local.yml ├── .chamber.pub.pem ├── templates └── settings.yml ├── .rubocop_rspec_rails.yml ├── .rubocop_thread_safety.yml ├── LICENSE.txt ├── .rubocop_capybara.yml ├── .rubocop_factory_bot.yml ├── settings.yml ├── certs └── thekompanee.pem ├── Gemfile.lock ├── .yamllint ├── CODE-OF-CONDUCT.md ├── chamber.gemspec ├── .remarkrc ├── .gitignore ├── README.md └── .rubocop_performance.yml /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.6 2 | -------------------------------------------------------------------------------- /spec/rails-2-test/config.ru: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails-3-test/config.ru: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails-4-test/bin/rails: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails-4-test/config.ru: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails-2-test/script/console: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails-3-test/script/rails: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | -------------------------------------------------------------------------------- /spec/rails-engine-test/spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails-engine-test/spec/dummy/script/rails: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | github: jfelchner 4 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.pem: -------------------------------------------------------------------------------- 1 | default private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.pub.pem: -------------------------------------------------------------------------------- 1 | default public key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.test.pem: -------------------------------------------------------------------------------- 1 | test private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.test.pub.pem: -------------------------------------------------------------------------------- 1 | test public key 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.0.gem.md5: -------------------------------------------------------------------------------- 1 | b4757ff6d5c92bb8a57a0497db4c6bf2 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.1.gem.md5: -------------------------------------------------------------------------------- 1 | 5682d163e76b325fcc0c78886f775250 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.2.gem.md5: -------------------------------------------------------------------------------- 1 | 4798d76c55ae589ef54d048c42cdb02b 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.11.0.gem.md5: -------------------------------------------------------------------------------- 1 | 3db72ef220795d2082995f3f7e68eba2 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.0.gem.md5: -------------------------------------------------------------------------------- 1 | 3a5919a165c36db43155866f4bc404d3 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.1.gem.md5: -------------------------------------------------------------------------------- 1 | 727733f14bec4c37670036d0bf9db78d 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.2.gem.md5: -------------------------------------------------------------------------------- 1 | bf8e4ce48900300c0158698fd7c17752 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.3.gem.md5: -------------------------------------------------------------------------------- 1 | 88d594974970251221e3372dfb1579b9 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.5.gem.md5: -------------------------------------------------------------------------------- 1 | b103797c83c85d29db111863f1da297c 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.13.0.gem.md5: -------------------------------------------------------------------------------- 1 | 3e824f80daed76051fbbcc32cc2d7e4e 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.13.1.gem.md5: -------------------------------------------------------------------------------- 1 | 35ac537abdb89292523d1634b06c602a 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.0.gem.md5: -------------------------------------------------------------------------------- 1 | 30993bcaa0d9b7747704221c7d9be5cc 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.1.gem.md5: -------------------------------------------------------------------------------- 1 | a6d9f2ad100fb1a809e53bd4848bd07e 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.2.gem.md5: -------------------------------------------------------------------------------- 1 | 6cff165d8184f70f0d12f62d82883613 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.3.gem.md5: -------------------------------------------------------------------------------- 1 | 94b6b130a25554864f048ec8f45bc022 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.9.0.gem.md5: -------------------------------------------------------------------------------- 1 | effd465546681de2cc2fbb733aaa4834 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.9.1.gem.md5: -------------------------------------------------------------------------------- 1 | f0b43df74b32ead7ea131a4c9ac76557 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.0.gem.md5: -------------------------------------------------------------------------------- 1 | 2e3ea499a573c0baa886f0b5b2e4f7ad 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.1.gem.md5: -------------------------------------------------------------------------------- 1 | 532d318178a7595ca8a3fe871ce6055f 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.1.0.gem.md5: -------------------------------------------------------------------------------- 1 | 56f9f52d72b80df72a2590a3f0b73cb9 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.1.1.gem.md5: -------------------------------------------------------------------------------- 1 | 9fe51b09004bd9adf3a78a491c470cac 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.enc: -------------------------------------------------------------------------------- 1 | default encrypted private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.test.enc: -------------------------------------------------------------------------------- 1 | test encrypted private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.foo.development.pem: -------------------------------------------------------------------------------- 1 | non-standard private key 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.0rc2.gem.md5: -------------------------------------------------------------------------------- 1 | 310a131316eeaca13bf7090432992783 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.development.pem: -------------------------------------------------------------------------------- 1 | development private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.production.pem: -------------------------------------------------------------------------------- 1 | production private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.production.pub.pem: -------------------------------------------------------------------------------- 1 | production public key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.foo.development.pub.pem: -------------------------------------------------------------------------------- 1 | non-standard public key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.development.pub.pem: -------------------------------------------------------------------------------- 1 | development public key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.foo.development.enc: -------------------------------------------------------------------------------- 1 | non-standard encrypted private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.development.enc: -------------------------------------------------------------------------------- 1 | development encrypted private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.examplehostcom.pem: -------------------------------------------------------------------------------- 1 | example-host.com private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.examplehostcom.pub.pem: -------------------------------------------------------------------------------- 1 | example-host.com public key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.production.enc: -------------------------------------------------------------------------------- 1 | production encrypted private key 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.signature.enc.pass: -------------------------------------------------------------------------------- 1 | 5768856f-cc84-4a07-97e9-3131c89eb1c8 -------------------------------------------------------------------------------- /spec/fixtures/keys/.chamber.examplehostcom.enc: -------------------------------------------------------------------------------- 1 | example-host.com encrypted private key 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.0.gem.sha256: -------------------------------------------------------------------------------- 1 | 09d282dfa6662a8fc378eeee8e68fe4cd69957b8584b6e7b24c20c5166a41c36 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.1.gem.sha256: -------------------------------------------------------------------------------- 1 | 6a02a102f6d616ed1b946c3f5a8f5d6c0b490db6971a1a0e0bb1770567587c50 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.2.gem.sha256: -------------------------------------------------------------------------------- 1 | 96a81e4c15a24c97b31e85652e161f994b13750bf7d8e4d06f39640c4e9c116d 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.11.0.gem.sha256: -------------------------------------------------------------------------------- 1 | 477b87cce2b8a94538ef8dd8100405127b2b88b6b9ef9b44ce9ced9e6ec70e57 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.0.gem.sha256: -------------------------------------------------------------------------------- 1 | e2c2e9180164fbf29588375ada28124a083dd487e4b44d41b273662bc9aadfa2 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.1.gem.sha256: -------------------------------------------------------------------------------- 1 | 27780edc01256c716bf8c7875eed93fbc6cfd0c2528ffb239a2aa3590e95f853 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.2.gem.sha256: -------------------------------------------------------------------------------- 1 | 3558098349ca0d4aff24c20d3cdc39aed6ce6ee49b49d365079c16c5b38f927f 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.3.gem.sha256: -------------------------------------------------------------------------------- 1 | 38568f4ff536539390570aa35d8e693399f610d446fee18e7e50f6068447857c 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.5.gem.sha256: -------------------------------------------------------------------------------- 1 | cd079d8779964f18f7d61b759c156cbb4553c66b23b8c5262817685d94e57574 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.13.0.gem.sha256: -------------------------------------------------------------------------------- 1 | afc18297ba6f5c49d81daaa41a4fbccf06df9a70da622c72bb7a47cde9f856a5 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.13.1.gem.sha256: -------------------------------------------------------------------------------- 1 | 531267d4d0f95bc86269a2a5114e35ba2cc8d8106e1f0b13a9ca3ed739e02aad 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.0.gem.sha256: -------------------------------------------------------------------------------- 1 | d4b6d866cd729ba7d88c22ea426b68aed731856f250e17c22e78fbecc371c814 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.1.gem.sha256: -------------------------------------------------------------------------------- 1 | 83f2582f8519553e1e2824917f5221f18069effd2f62ee815c90fd9c9d16dfc9 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.2.gem.sha256: -------------------------------------------------------------------------------- 1 | 6e28e769ea18b08e543e14869ecd3429d1c6631ba826717c562254974ae8325d 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.3.gem.sha256: -------------------------------------------------------------------------------- 1 | 20869220fee54910905731a527c13b275f9fa836c94e19ce6beb10c5feecbdb1 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.9.0.gem.sha256: -------------------------------------------------------------------------------- 1 | 8a4fc2646e5057ecf30a6985e92001f1b4209329f86af0b26492b2ee6774a8cd 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.9.1.gem.sha256: -------------------------------------------------------------------------------- 1 | 0deb7b68351eb9162c22575a2e8c36152cce2f45b7c273e276c943a62cc65e9c 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.0.gem.sha256: -------------------------------------------------------------------------------- 1 | fb993f76325d402ed7243f056f67fc3a69361e283c7c17b9c87b1efbdc1537ad 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.1.gem.sha256: -------------------------------------------------------------------------------- 1 | ee0f7b092ad14d41810cc4fe55abe58820c51237e139bf06cc1ef82b4d4aa38f 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.1.0.gem.sha256: -------------------------------------------------------------------------------- 1 | 5a39455fdaf86ba9959adf7a7f6c8d6b39fd8bc1c623fa2624b15fe4b13da38d 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.1.1.gem.sha256: -------------------------------------------------------------------------------- 1 | bee30cf3909547590e6e93147cf51eb27b05436320f040ced16ed812eea3f38b 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.0rc2.gem.sha256: -------------------------------------------------------------------------------- 1 | 11447ea996a3f85aaa732bcb478d1c93afb52e7f43219d7da60dcb10ae6eca5d 2 | -------------------------------------------------------------------------------- /lib/chamber/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | VERSION = '3.1.1' 5 | end 6 | -------------------------------------------------------------------------------- /lib/chamber/rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/integrations/rails' if defined?(Rails) 4 | -------------------------------------------------------------------------------- /spec/rails-engine-test/spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | class Rails 2 | def self.env 3 | 'development' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/rails-2-test/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Rails 4 | def self.env 5 | 'development' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/rails-3-test/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Rails 4 | def self.env 5 | 'development' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/rails-4-test/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Rails 4 | def self.env 5 | 'development' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.0.gem.sha512: -------------------------------------------------------------------------------- 1 | f32e104c8e5fca9a580c02978ef882a226085be60be48892bdc0df547956f85cdffd9bfa60ceda3d12f3c5199c1beda3f4e6dce75dc4434c1e687a48d541b97c 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.1.gem.sha512: -------------------------------------------------------------------------------- 1 | faf1c446a5e8c5a9b9f7d8f99ce77101dd5ac5ffb47f6f3ca2ffd5d853b47fdf327dfca55a78406f9ee1fac082391c4aa286aa6607010994de1b139217625a85 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.10.2.gem.sha512: -------------------------------------------------------------------------------- 1 | b4ee3c67b7c2a51c3ba71408cee3f3252b75a36a4ee6dc1b696aaea74dc5b2e6b5ec370ab62580be1293c47ba5494f5f54e77243f18f69358d496648516a7bea 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.11.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 15eab2eb4e1e6e9b4967fcac87171171334ff200819d78d445cded5f6c0297782eb083fc60aca44b6b58c54974b8b14bffc03cfa50387644415f37e8216178d6 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 77fc39dc30a5eb721597d0399267f273e499a88d0ba2319c01d324f211d1b3d7c234cb27fd48803de4ae0ca37d554471c09053f05f8cf60c1d4b5dc10cd0c879 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.1.gem.sha512: -------------------------------------------------------------------------------- 1 | 984e6f74e4ad463c4b587932d0a99d1b3ff8f0b6c0b9f1c9a603925c99eb826914ace80a50e97aa3fcb5fcf8da07c3839414d097f5b142594227ac680cf59e37 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.2.gem.sha512: -------------------------------------------------------------------------------- 1 | bbd1b6c860979aa8500fbf4bbead1e8dd36c041d7e1342900c7f6441bb1bef7a191d6cc35676eb7f31a8d8bb7092d26b3113804266ad298610651b0732060096 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.3.gem.sha512: -------------------------------------------------------------------------------- 1 | 865a1a47c7727824aeac2785d455d54e6f0441ce3afbcace7ef42e09b872afcc774d58c522d70f5f24356409fd1b76c0c33dcc8bb2163159dddd909610b8c2e5 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.12.5.gem.sha512: -------------------------------------------------------------------------------- 1 | ff7ceab7f8f89004945179913161f71cdb1a4376b12357c43bc60e690753cff467220003985d7998dca50d92c573fb723360261250610774319b7c81d84ca981 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.13.0.gem.sha512: -------------------------------------------------------------------------------- 1 | ddb9a51261dd91517a9b5390a8f96d33a52d29f846209c2ee712d07505808ebb2ffcb433cdcfb89be9ed8ae4b8c961771c9f80151c166a4ffcc3d405aee8e1f2 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.13.1.gem.sha512: -------------------------------------------------------------------------------- 1 | be97d528a4f554538561ca710f7b3f08caae5c0423d7c6ffb95383b20120d42347b38a68bed515dfa38acf7da9d495f168f40ae374965d9ef0552044c4336cdf 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 22b877981acb9f8160dc14e7f999f05cd87532951db408968220e322150a77fc7f52843943193090bd95a06f5bc53dea49e2d5ef57ee5c14d4a32949fc198db5 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.1.gem.sha512: -------------------------------------------------------------------------------- 1 | 82ebecfb4f95e930833ee2b55f86f88ccbdbc18ce38134b4e98f59b19d920945b868b01da22237abb690f821c7b7cca3fd2e8111b65f8b3a860aba50e0ec64ee 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.2.gem.sha512: -------------------------------------------------------------------------------- 1 | ec78ef882f2a52cf3e372f7ff7aec8b53e8bb886166123452a6eb7c4e01e6a9a1d043170cb8a241ef9477c9c046cbdc5ef4ed9637e2107ad5484f738428333c3 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.14.3.gem.sha512: -------------------------------------------------------------------------------- 1 | 568ad7e7876ba081cf01dd58b864f05b7ae337d90058bc89c381378552dca745c8377905f4df79ac3a98f677a84aa8815a44c2cf315c7d2632421a7d5f737d49 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.9.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 7cbc6ec258f32fb8dd9c5db021fcb751268c26232ba375b534d948988ee4a1fe9a5b28255a59843382a76d82828a1c23753e3872c98579f026dc05dbf7a70ddd 2 | -------------------------------------------------------------------------------- /checksum/chamber-2.9.1.gem.sha512: -------------------------------------------------------------------------------- 1 | acc91b373e482584c012dd9ba5d5b7a9b36effc210f8464935ade1629cddb31dd2362dc4adc954d0896a7d188bce6461d7f4a76511f47958ba54363c935b4bb4 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 563d4720facada5c9800c06c758a2c4f2ae3e075bf5894f800e63a3587b5889b2a9652990ee4bd45b8204db6bf8ab77ee672a6033d1e576b72bae3cec3df23b2 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.1.gem.sha512: -------------------------------------------------------------------------------- 1 | 22ce53fbbba9f178d57fd4380678ad1f012f3cf0660d048d77ddb268b0370dfee4d472be205706e9f43a9ab92f929ff4bb639dd03f0b6c55a9199cf417bd781d 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.1.0.gem.sha512: -------------------------------------------------------------------------------- 1 | a2b1b06363c46926e85231a5c351bbec395f1c3ea8b10a65d72ead253585a9da0a449dacccdc8583d0dd00aad1fca7761624426c5423b24592616f15a327a071 2 | -------------------------------------------------------------------------------- /checksum/chamber-3.1.1.gem.sha512: -------------------------------------------------------------------------------- 1 | 2345f9afb13785db04a5bd690713aefa752863048d1ab02e2c4d4a9d30c549d5f1b64aa3119f8b32372a98e03b28e76374ada235d2e6e850205c5ff3439f01e7 2 | -------------------------------------------------------------------------------- /bin/chamber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'chamber' 5 | require 'chamber/binary/runner' 6 | 7 | Chamber::Binary::Runner.start 8 | -------------------------------------------------------------------------------- /checksum/chamber-3.0.0rc2.gem.sha512: -------------------------------------------------------------------------------- 1 | bd8e65b7cd32752c562e649720c0743a806f542dc30164476559b273ba7fd5bc5cbe9c140e796184c3f00d9acb8c51ed7fcebc644be3c843a6b72643d9285540 2 | -------------------------------------------------------------------------------- /lib/chamber/errors/invalid_key_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Errors 5 | class InvalidKeyType < ::ArgumentError 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /lib/chamber/errors/decryption_failure.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Errors 5 | class DecryptionFailure < RuntimeError 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/chamber/errors/disallowed_class.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Errors 5 | class DisallowedClass < ::ArgumentError 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/chamber/errors/non_conforming_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Errors 5 | class NonConformingKey < ::ArgumentError 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | 6 | Bundler.require(:default, :console) 7 | 8 | require 'irb' 9 | IRB.start 10 | -------------------------------------------------------------------------------- /lib/chamber/errors/environment_conversion.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Errors 5 | class EnvironmentConversion < ArgumentError 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/lib/chamber/keys/base_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | 5 | module Chamber 6 | module Keys 7 | describe Base do 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | group :console do 6 | gem 'awesome_print', '~> 1.6' 7 | end 8 | 9 | # Specify your gem's dependencies in chamber.gemspec 10 | gemspec 11 | -------------------------------------------------------------------------------- /lib/chamber/commands/files.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/commands/base' 4 | 5 | module Chamber 6 | module Commands 7 | class Files < Chamber::Commands::Base 8 | def call 9 | chamber.filenames 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/chamber/rubinius_fix.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | 5 | unless Pathname.instance_methods.include?(:write) 6 | class Pathname 7 | def write(*args) 8 | IO.write @path, *args # rubocop:disable Security/IoMethods 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/chamber/commands/travis.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler' 4 | 5 | module Chamber 6 | module Commands 7 | module Travis 8 | protected 9 | 10 | def travis_encrypt(command) 11 | Bundler.with_clean_env { `travis encrypt --add 'env.global' #{command}` } 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | inherit_from: 4 | - '.rubocop_core.yml' 5 | - '.rubocop_capybara.yml' 6 | - '.rubocop_factory_bot.yml' 7 | - '.rubocop_performance.yml' 8 | - '.rubocop_rspec.yml' 9 | - '.rubocop_local.yml' 10 | # - '.rubocop_thread_safety.yml' 11 | 12 | # Put your project-specific Rubocop configuration in .rubocop_local.yml 13 | -------------------------------------------------------------------------------- /lib/chamber/commands/sign.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/commands/base' 4 | 5 | module Chamber 6 | module Commands 7 | class Sign < Chamber::Commands::Base 8 | def initialize(**args) 9 | super(**args.merge(namespaces: ['*'])) 10 | end 11 | 12 | def call 13 | chamber.sign 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/chamber/errors/missing_setting.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Errors 5 | class MissingSetting < ::KeyError 6 | def initialize(missing_key, all_keys) 7 | super(<<~HEREDOC.chomp) 8 | You attempted to access setting '#{all_keys.join(':')}' but '#{missing_key}' did not exist. 9 | HEREDOC 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/chamber/errors/missing_index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Errors 5 | class MissingIndex < ::IndexError 6 | def initialize(missing_index, all_keys) 7 | super(<<~HEREDOC.chomp) 8 | You attempted to access setting '#{all_keys.join(':')}' but the index '#{missing_index}' in the array did not exist. 9 | HEREDOC 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /.rubocop_local.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | Lint/EmptyFile: 4 | Exclude: 5 | - 'spec/rails-2-test/config.ru' 6 | - 'spec/rails-3-test/config.ru' 7 | - 'spec/rails-4-test/config.ru' 8 | 9 | # Rubocop Thinks Pathnames Are Strings 10 | Style/StringConcatenation: 11 | Enabled: false 12 | 13 | # We Use To Call Private Methods Internally 14 | Style/SendWithLiteralMethodName: 15 | AllowSend: true 16 | -------------------------------------------------------------------------------- /.chamber.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx+GjXcKrJCRmrXqFME4s 3 | 4cl5QzwWD3+m/wIYAiV2xY+vy07jkgI9Tfqu2MZwiSfSx0/1P4zdIGTAMeT227m9 4 | 60z1/fAYZmWwIJYDlbKCevHk3INEzJI079a6DCkqNwgwaiw3vUYllmoGovGgStiA 5 | Z1JiV9z/vgZ/YLAIfXKMa7jx4Xxf+7v1Muejq1oojU3hu/OOuR2QJ33GWZwfKJty 6 | 4AfTeE/GACw193jGt/hdxBfnet0drCADL3v8Yz6FjrkYIXZAOHvKkLoUxYblD+xI 7 | isIgzEopuRmlE1bThT6V4oGUrssFAPi3WWvrgYpAfJmYTCKFSiHcj6LEWeHVBzaJ 8 | SwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spec/spec_key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYcqkBjBLhSaTKOCMoq+ 3 | ZxcuxTaA5UQpf/vDXbOiI871+6x7yKbTr+Xr9oFDvFldyvUFiK6LX0rj/jgLnaTB 4 | sLyXjH46dOmiPUO3k/QvDmRKN8zvl4x9T7YZKuoEkxZwE3T3MxKPmBorKGv/22Vb 5 | KocqkGGgx9gKIvSfxVXfTMfcvTDrFllm1bCaXEVGcRAknJg94ul2yMgqmYA2KJcP 6 | y2naped90yzv0A7c/UI5zjBcJPgkum79aDTSv095yl+Pk+5JM2jD85x3ph3ij++L 7 | dAXJ1fBJrV1H39UJ4A6yOupEG3+QsZTPDXkBBnX8+mWXYCClI/GF6iA/G3njeMqU 8 | fQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spec/rails-3-test/.chamber.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYcqkBjBLhSaTKOCMoq+ 3 | ZxcuxTaA5UQpf/vDXbOiI871+6x7yKbTr+Xr9oFDvFldyvUFiK6LX0rj/jgLnaTB 4 | sLyXjH46dOmiPUO3k/QvDmRKN8zvl4x9T7YZKuoEkxZwE3T3MxKPmBorKGv/22Vb 5 | KocqkGGgx9gKIvSfxVXfTMfcvTDrFllm1bCaXEVGcRAknJg94ul2yMgqmYA2KJcP 6 | y2naped90yzv0A7c/UI5zjBcJPgkum79aDTSv095yl+Pk+5JM2jD85x3ph3ij++L 7 | dAXJ1fBJrV1H39UJ4A6yOupEG3+QsZTPDXkBBnX8+mWXYCClI/GF6iA/G3njeMqU 8 | fQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzoh4Dj7hY1pc7yM1tNHZ 3 | gB3sU+wdDrKn9hl/TaJ2m28HckKzpUbChlFzfjp6LUTzwmyZ/RFMOLHrigEiCg4u 4 | B+RA8/pCwTGBbG+xxTOnrxEcFtxep/8ova8SR9D/rOoQed4bvnwN1xj3Gm7ynuvH 5 | FXbTwl5Ugcf4rP4ftgAOHJDSzQSUkjJKceI7zEMXBq9LAA40/xrzIxQyquZgOy0f 6 | n/UwRfh9Wcv8Cmv2jl2EzGaJB6rz7qjN2MD7Lx0bqaMuLAQ+WzmB/A1cAEtPdfB1 7 | yCWotMQV+YGDCSRcx6WK2DY6OtYQCC8qHix1m2rkoinb/rVI9LpiEuVD/WLbXxwE 8 | dwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.test.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqFa5xERTK2y8c/gcCtOJ 3 | xkGzj3oFXfm7fdtsRWs+ewZIVNqfKgQmvKviLEALVL1+MYwhELZ0bkC7zvJwZWf6 4 | lbxgZDh9eCENM95CdPEhGe70N8kHF/A7w9CEH0R+mchyoIN7Kj9km3OLCJhQW1fP 5 | BP30ATDBWaiQO0+ChMnV6qij1q+l09UQ+yJUXB5p2tlo8qB9vcBzQKOqvTZZ3M1l 6 | mIovSKKgYeXgZD3aLm/ufhSbXtnYMNcVad4QTrX5Rg2PHqz9m4hWVp5TuDs+hpXB 7 | 7GCwtwQFGH4B5yPwrdzZQCIT7vYqfDuXtU9sbFYout/OT+THXcpUyN4ak6KtsHwU 8 | YQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /lib/chamber/integrations/rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'socket' 4 | 5 | module Chamber 6 | module Integrations 7 | class Rails < ::Rails::Railtie 8 | initializer 'chamber.load', before: :load_environment_config do 9 | Chamber.load(basepath: ::Rails.root.join('config'), 10 | namespaces: { 11 | environment: -> { ::Rails.env }, 12 | hostname: -> { ::Socket.gethostname }, 13 | }) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.production.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlXcpv9ILEtmn2JXsX2mY 3 | cxROZ+KDD8k6/ywTzVoZLSRfBglPLVJjW06hYRDV9ZX08nBxc5aTJrXA05aE9Y+w 4 | Y427wPh/Pasm99QSBWs6FUu8GbOhCExkK09UenntzAKc8BhSOi/UiJYSxiOgWM5L 5 | 0FzHtCVXJpMHZq2JMTJ8nP2ra7OWxe4qj6h53rqVUmFSuVnn6o4+HIKXxdkQRs8b 6 | 3UTPPUcked29tbFPnCYFTWGk7sn7KePn93qpWjrOB2QRpcyopaS+EKFlR4XpJrt4 7 | pfZbVbw1kS61Z3N6Cf8r/c3MdxzmTxDoBckhLicnbOxSwZNrKvvpd46Volfz2gcH 8 | EwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.signature.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwHYvjoZ/MCLq5fQDrh6W 3 | DLVnNkhvUe1VEcz01frC8u2faSrw2WzBQQTjUktFUChHBpEJJgqZoRLIrmPU1zE3 4 | BQ7LaKH20EADNyLabRqbG7POws7tnjxzK4tzOoUUhyfE7ZIVJo0e1dPvmKtMXQ4l 5 | DPDLiVs5neheqHhCyg/TCMgFyEMxKHr+h2yW/oG2C1Q9o1yJo2kdX50j0wPxmb0T 6 | Cd+UD6rXYlpzqhzxi8a9D30HDk7+cSvbgAfaKr0T6rAcX+V/WJ7sgYT/Jpi7+Rdb 7 | TPoQVRLLZwdIWvZOgLKua+WXYzhskKHFM3kqsmEltoPQFu9nkhJAj9Sq2eUq6tfb 8 | 0wIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spec/rails-engine-test/spec/dummy/.chamber.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYcqkBjBLhSaTKOCMoq+ 3 | ZxcuxTaA5UQpf/vDXbOiI871+6x7yKbTr+Xr9oFDvFldyvUFiK6LX0rj/jgLnaTB 4 | sLyXjH46dOmiPUO3k/QvDmRKN8zvl4x9T7YZKuoEkxZwE3T3MxKPmBorKGv/22Vb 5 | KocqkGGgx9gKIvSfxVXfTMfcvTDrFllm1bCaXEVGcRAknJg94ul2yMgqmYA2KJcP 6 | y2naped90yzv0A7c/UI5zjBcJPgkum79aDTSv095yl+Pk+5JM2jD85x3ph3ij++L 7 | dAXJ1fBJrV1H39UJ4A6yOupEG3+QsZTPDXkBBnX8+mWXYCClI/GF6iA/G3njeMqU 8 | fQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.development.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqMVYSeBJCEPPiKJPrWkM 3 | n+/9onHDHFNUu+nX+Wh7JC/0I/eDF5QrczAT53Q0YQvEaP82zhbnjzR4FJu2bcEZ 4 | HmePUoPGME6ITI7f7DYRRlpTgT0HnYdnEyO72LPnRRzITNpNbJAbOkdfoKbKW9+D 5 | VWIlbspM+lSHJUv2WeMWL9pQ4+aTPYBxHOIEcorvj2ccIL4I1b6x55j2qE5z92xK 6 | cAqRmdY6xiGLNq42bX4kxvt2pQY4smzjetLQ7oiEykDMqFzOoWtFOQwq+8ub3G8y 7 | Pe2a14wM8qybm6WkoobhMv9XXCIvGIq+b6iGMxOzCIc5wYXEXXuSN4pz2bIiE1Ex 8 | 4wIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /templates/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | development: 4 | setting: development_value 5 | # The following will become 'secure_setting' 6 | _secure_secure_setting: secure_development_value 7 | 8 | test: 9 | setting: test_value 10 | # The following will become 'secure_setting' 11 | _secure_secure_setting: secure_test_value 12 | 13 | production: 14 | setting: production_value 15 | # The following will become 'secure_setting' 16 | _secure_secure_setting: secure_production_value 17 | -------------------------------------------------------------------------------- /lib/chamber/commands/verify.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/commands/base' 4 | 5 | module Chamber 6 | module Commands 7 | class Verify < Chamber::Commands::Base 8 | def initialize(**args) 9 | super(**args.merge(namespaces: ['*'])) 10 | end 11 | 12 | def call 13 | verification_results = chamber.verify 14 | 15 | verification_results.each_pair do |filename, result| 16 | unless result 17 | shell.say("The signature for '#{filename}' failed verification.", :yellow) 18 | end 19 | end 20 | 21 | verification_results 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/chamber/refinements/enumerable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/errors/non_conforming_key' 4 | 5 | module Chamber 6 | module Refinements 7 | class Enumerable 8 | def self.deep_validate_keys(object, &block) 9 | case object 10 | when ::Hash 11 | object.each do |(key, value)| 12 | fail ::Chamber::Errors::NonConformingKey unless key == yield(key) 13 | 14 | deep_validate_keys(value, &block) 15 | end 16 | when ::Array 17 | object.map { |v| deep_validate_keys(v, &block) } 18 | else 19 | object 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/chamber/encryption_methods/none.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module EncryptionMethods 5 | class None 6 | def self.encrypt(_key, value, _encryption_keys) 7 | value 8 | end 9 | 10 | def self.decrypt(key, value, _decryption_keys) 11 | return value if value.nil? 12 | 13 | warn "WARNING: It appears that you would like to keep your information for #{key} " \ 14 | "secure, however the value for that setting does not appear to be encrypted. " \ 15 | "Make sure you run 'chamber secure' before committing." 16 | 17 | value 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/chamber/refinements/hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Refinements 5 | module Hash 6 | refine ::Hash do 7 | def deep_merge(other_hash, &block) 8 | dup.deep_merge!(other_hash, &block) 9 | end 10 | 11 | def deep_merge!(other_hash, &block) 12 | merge!(other_hash) do |key, this_val, other_val| 13 | if this_val.is_a?(::Hash) && other_val.is_a?(::Hash) 14 | this_val.deep_merge(other_val, &block) 15 | elsif block 16 | yield(key, this_val, other_val) 17 | else 18 | other_val 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/lib/chamber/commands/files_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/commands/files' 5 | 6 | module Chamber 7 | module Commands 8 | describe Files do 9 | let(:rootpath) { ::File.expand_path('./spec/fixtures') } 10 | let(:options) do 11 | { 12 | basepath: rootpath, 13 | rootpath: rootpath, 14 | } 15 | end 16 | 17 | it 'can return values formatted as environment variables' do 18 | files = Files.call(**options) 19 | 20 | expect(files.size).to be 1 21 | expect(files.first).to include 'spec/fixtures/settings.yml' 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/chamber/commands/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'chamber/instance' 5 | 6 | module Chamber 7 | module Commands 8 | class Base 9 | attr_accessor :chamber, 10 | :dry_run, 11 | :rootpath, 12 | :shell 13 | 14 | def self.call(**args) 15 | new(**args).call 16 | end 17 | 18 | def initialize(shell: nil, rootpath: nil, dry_run: nil, **args) 19 | self.chamber = Chamber::Instance.new(rootpath: rootpath, **args) 20 | self.shell = shell 21 | self.rootpath = chamber.configuration.rootpath 22 | self.dry_run = dry_run 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/chamber/refinements/deep_dup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Refinements 5 | module DeepDup 6 | refine ::Array do 7 | def deep_dup 8 | map { |i| i.respond_to?(:deep_dup) ? i.deep_dup : i.dup } 9 | end 10 | end 11 | 12 | refine ::Object do 13 | def deep_dup 14 | dup 15 | end 16 | end 17 | 18 | refine ::Hash do 19 | def deep_dup 20 | dup.tap do |hash| 21 | each_pair do |key, value| 22 | if key.frozen? && key.is_a?(::String) 23 | hash[key] = value.deep_dup 24 | else 25 | hash.delete(key) 26 | hash[key.deep_dup] = value.deep_dup 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/chamber/integrations/sinatra.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'socket' 4 | 5 | module Chamber 6 | module Integrations 7 | module Sinatra 8 | def self.registered(app) 9 | app.configure do |inner_app| 10 | env = inner_app.environment || ENV.fetch('RACK_ENV', nil) 11 | root = inner_app.root 12 | 13 | if defined?(Padrino) 14 | env = Padrino.env if Padrino.respond_to?(:env) 15 | root = Padrino.root if Padrino.respond_to?(:root) 16 | end 17 | 18 | Chamber.load( 19 | basepath: root, 20 | namespaces: { 21 | environment: -> { env }, 22 | hostname: -> { Socket.gethostname }, 23 | }, 24 | ) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | on: 4 | - "issues" 5 | - "label" 6 | - "workflow_dispatch" 7 | 8 | name: "Labels" 9 | jobs: 10 | synchronize-labels: 11 | name: "Synchronize Labels" 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - name: "Checkout Code" 15 | uses: "actions/checkout@v3" 16 | timeout-minutes: 5 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: "Synchronize Labels" 21 | uses: "micnncim/action-label-syncer@v1" 22 | with: 23 | manifest: ".github/labels.yml" 24 | env: 25 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 26 | -------------------------------------------------------------------------------- /.rubocop_rspec_rails.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Last updated to 2.29.0 4 | 5 | require: 6 | - 'rubocop-rspec_rails' 7 | 8 | RSpecRails/AvoidSetupHook: 9 | Enabled: true 10 | 11 | RSpecRails/HaveHttpStatus: 12 | Enabled: true 13 | ResponseMethods: 14 | - 'response' 15 | - 'last_response' 16 | 17 | RSpecRails/HttpStatus: 18 | Enabled: true 19 | EnforcedStyle: 'numeric' 20 | 21 | RSpecRails/InferredSpecType: 22 | Enabled: true 23 | 24 | RSpecRails/MinitestAssertions: 25 | Enabled: true 26 | 27 | RSpecRails/NegationBeValid: 28 | Enabled: true 29 | EnforcedStyle: 'not_to' 30 | 31 | RSpecRails/TravelAround: 32 | Enabled: true 33 | -------------------------------------------------------------------------------- /lib/chamber/commands/secure.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/commands/base' 4 | require 'chamber/commands/securable' 5 | 6 | module Chamber 7 | module Commands 8 | class Secure < Chamber::Commands::Base 9 | include Chamber::Commands::Securable 10 | 11 | def initialize(**args) 12 | super(**args.merge(namespaces: ['*'])) 13 | end 14 | 15 | def call 16 | disable_warnings do 17 | insecure_environment_variables.each_key do |key| 18 | color = dry_run ? :blue : :green 19 | 20 | shell.say_status 'encrypt', key, color 21 | end 22 | end 23 | 24 | chamber.secure unless dry_run 25 | end 26 | 27 | private 28 | 29 | def disable_warnings 30 | $stderr = ::File.open('/dev/null', 'w') 31 | 32 | yield 33 | 34 | $stderr = STDERR 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/chamber/commands/unsecure.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/commands/base' 4 | require 'chamber/commands/securable' 5 | 6 | module Chamber 7 | module Commands 8 | class Unsecure < Chamber::Commands::Base 9 | include Chamber::Commands::Securable 10 | 11 | def initialize(**args) 12 | super(**args.merge(namespaces: ['*'])) 13 | end 14 | 15 | def call 16 | disable_warnings do 17 | current_settings.secure.to_environment.each_key do |key| 18 | color = dry_run ? :blue : :green 19 | 20 | shell.say_status 'decrypt', key, color 21 | end 22 | end 23 | 24 | chamber.unsecure unless dry_run 25 | end 26 | 27 | private 28 | 29 | def disable_warnings 30 | $stderr = ::File.open('/dev/null', 'w') 31 | 32 | yield 33 | 34 | $stderr = STDERR 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/chamber/commands/comparable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'tempfile' 4 | 5 | module Chamber 6 | module Commands 7 | module Comparable 8 | def initialize(keys_only:, **args) 9 | super 10 | 11 | self.keys_only = keys_only 12 | end 13 | 14 | def call 15 | system("git diff --no-index #{first_settings_file} #{second_settings_file}") 16 | end 17 | 18 | protected 19 | 20 | attr_accessor :keys_only 21 | 22 | def first_settings_file 23 | create_comparable_settings_file 'first', first_settings_data 24 | end 25 | 26 | def second_settings_file 27 | create_comparable_settings_file 'second', second_settings_data 28 | end 29 | 30 | def create_comparable_settings_file(name, config) 31 | Tempfile.open(name) do |file| 32 | file.write config 33 | file.to_path 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /.rubocop_thread_safety.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Last updated to 0.5.1 4 | 5 | require: 6 | - 'rubocop-thread_safety' 7 | 8 | ThreadSafety/ClassAndModuleAttributes: 9 | Enabled: true 10 | 11 | ThreadSafety/DirChdir: 12 | Enabled: true 13 | 14 | ThreadSafety/InstanceVariableInClassMethod: 15 | Enabled: true 16 | 17 | ThreadSafety/MutableClassInstanceVariable: 18 | Enabled: true 19 | EnforcedStyle: 'literals' 20 | 21 | ThreadSafety/NewThread: 22 | Enabled: true 23 | 24 | ThreadSafety/RackMiddlewareInstanceVariable: 25 | Enabled: true 26 | Include: 27 | - 'app/middleware/**/*.rb' 28 | - 'lib/middleware/**/*.rb' 29 | - 'app/middlewares/**/*.rb' 30 | - 'lib/middlewares/**/*.rb' 31 | AllowedIdentifiers: [] 32 | -------------------------------------------------------------------------------- /lib/chamber/keys/decryption.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'chamber/keys/base' 5 | 6 | module Chamber 7 | module Keys 8 | class Decryption < Chamber::Keys::Base 9 | NAMESPACE_PATTERN = / 10 | \A # Beginning of Filename 11 | \.chamber # Initial Chamber Prefix 12 | \. # Pre-Namespace Dot 13 | (\w+) # Namespace 14 | \.pem # Extension 15 | \z # End of Filename 16 | /x.freeze 17 | 18 | private 19 | 20 | def environment_variable_from_path(path) 21 | [ 22 | 'CHAMBER', 23 | namespace_from_path(path), 24 | 'KEY', 25 | ] 26 | .compact 27 | .join('_') 28 | end 29 | 30 | def key_filename_extension 31 | '.pem' 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/chamber/keys/encryption.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'chamber/keys/base' 5 | 6 | module Chamber 7 | module Keys 8 | class Encryption < Chamber::Keys::Base 9 | NAMESPACE_PATTERN = / 10 | \A # Beginning of Filename 11 | \.chamber # Initial Chamber Prefix 12 | \. # Pre-Namespace Dot 13 | (\w+) # Namespace 14 | \.pub\.pem # Extension 15 | \z # End of Filename 16 | /x.freeze 17 | 18 | private 19 | 20 | def environment_variable_from_path(path) 21 | [ 22 | 'CHAMBER', 23 | namespace_from_path(path), 24 | 'PUBLIC_KEY', 25 | ] 26 | .compact 27 | .join('_') 28 | end 29 | 30 | def key_filename_extension 31 | '.pub.pem' 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/chamber/commands/show.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pp' 4 | require 'chamber/commands/base' 5 | 6 | module Chamber 7 | module Commands 8 | class Show < Chamber::Commands::Base 9 | attr_accessor :as_env, 10 | :only_sensitive 11 | 12 | def initialize(as_env: nil, only_sensitive: nil, **args) 13 | super(**args) 14 | 15 | self.as_env = as_env 16 | self.only_sensitive = only_sensitive 17 | end 18 | 19 | def call 20 | if as_env 21 | settings.to_s(pair_separator: "\n") 22 | else 23 | PP 24 | .pp(settings.to_hash, StringIO.new, 60) 25 | .string 26 | .chomp 27 | end 28 | end 29 | 30 | protected 31 | 32 | def settings 33 | @settings ||= if only_sensitive 34 | chamber.settings.securable 35 | else 36 | chamber.settings 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/fixtures/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | development: 4 | my_setting: my_dev_value 5 | my_boolean: 'false' 6 | my_dynamic_setting: 3 7 | another_level: 8 | setting_one: 1 9 | setting_two: 4 10 | level_three: 11 | an_array: 12 | - item 8 13 | - item 2 14 | - item 0 15 | a_scalar: hello 16 | test: 17 | my_setting: my_value 18 | _secure_my_secure_settings: 'M3yI2fIHsfD+zznsvO3FB/ryCwvvdQQ9ZXPQlTIR6Y9vtzNFAeRAxZpSyYUdOpeMDkWQSo5ZVLseM20iTh1YpNCjzd7D0bT4O9aBskYBE92b4ioYPAPSZ3NcvA1pGa6A/hWGo3iJZK1t96mGrfxy2mSFFqGHQbj4ix6D7PpCfVkjuUMp3NG3XjgGhmynK88XENWXBQfgxdfwylZZSQTm058BubkuM5MXgf4WGL3qWo+wWk9AOwjohAGq3UAf5Q341g/OlPGbCV3rBPTnlm866N8aAsHtppg5HwbknaySpLMPcv0KhUGC/bEPgbm3tuG7JZKsoqvDmWr/I+LjVi/LKg==' 19 | my_boolean: 'false' 20 | my_dynamic_setting: 2 21 | another_level: 22 | setting_one: 1 23 | setting_two: 2 24 | level_three: 25 | an_array: 26 | - item 1 27 | - item 2 28 | - item 3 29 | a_scalar: hello 30 | -------------------------------------------------------------------------------- /lib/chamber/filters/translate_secure_keys_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/refinements/deep_dup' 4 | 5 | module Chamber 6 | module Filters 7 | class TranslateSecureKeysFilter 8 | using ::Chamber::Refinements::DeepDup 9 | 10 | attr_accessor :data, 11 | :secure_key_token 12 | 13 | def self.execute(**args) 14 | new(**args).__send__(:execute) 15 | end 16 | 17 | def initialize(data:, secure_key_prefix:, **_args) 18 | self.data = data.deep_dup 19 | self.secure_key_token = /\A#{Regexp.escape(secure_key_prefix)}/ 20 | end 21 | 22 | protected 23 | 24 | def execute(raw_data = data) 25 | settings = {} 26 | 27 | raw_data.each_pair do |key, value| 28 | value = execute(value) if value.respond_to? :each_pair 29 | key = key.to_s 30 | key = key.sub(secure_key_token, '') if key.match(secure_key_token) 31 | 32 | settings[key] = value 33 | end 34 | 35 | settings 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/chamber/filters/namespace_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/refinements/deep_dup' 4 | require 'chamber/refinements/hash' 5 | 6 | module Chamber 7 | module Filters 8 | class NamespaceFilter 9 | using ::Chamber::Refinements::DeepDup 10 | using ::Chamber::Refinements::Hash 11 | 12 | attr_accessor :data, 13 | :namespaces 14 | 15 | def self.execute(**args) 16 | new(**args).__send__(:execute) 17 | end 18 | 19 | def initialize(data:, namespaces:, **_args) 20 | self.data = data.deep_dup 21 | self.namespaces = namespaces 22 | end 23 | 24 | protected 25 | 26 | def execute 27 | return data unless data_is_namespaced? 28 | 29 | namespaces.each_with_object({}) do |namespace, filtered_data| 30 | filtered_data.deep_merge!(data[namespace]) if data[namespace] 31 | end 32 | end 33 | 34 | private 35 | 36 | def data_is_namespaced? 37 | @data_is_namespaced ||= data.keys.any? { |key| namespaces.include?(key.to_s) } 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/chamber/filters/secure_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/refinements/deep_dup' 4 | 5 | module Chamber 6 | module Filters 7 | class SecureFilter 8 | using ::Chamber::Refinements::DeepDup 9 | 10 | attr_accessor :data, 11 | :secure_key_token 12 | 13 | def self.execute(**args) 14 | new(**args).__send__(:execute) 15 | end 16 | 17 | def initialize(data:, secure_key_prefix:, **_args) 18 | self.data = data.deep_dup 19 | self.secure_key_token = /\A#{Regexp.escape(secure_key_prefix)}/ 20 | end 21 | 22 | protected 23 | 24 | def execute(raw_data = data) 25 | settings = {} 26 | 27 | raw_data.each_pair do |key, value| 28 | secure_value = if value.respond_to? :each_pair 29 | execute(value) 30 | elsif key.respond_to? :match 31 | value if key.match(secure_key_token) 32 | end 33 | 34 | settings[key] = secure_value unless secure_value.nil? 35 | end 36 | 37 | settings 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2016 The Kompanee, Ltd 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 11 | all 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 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: "Build" 4 | 5 | on: 6 | - "push" 7 | - "pull_request" 8 | 9 | jobs: 10 | test: 11 | name: "Testing" 12 | runs-on: "ubuntu-latest" 13 | 14 | strategy: 15 | matrix: 16 | ruby: 17 | - "2.7" 18 | - "3.0" 19 | - "3.1" 20 | - "3.2" 21 | - "ruby-head" 22 | 23 | continue-on-error: ${{ endsWith(matrix.ruby, 'head') }} 24 | 25 | steps: 26 | - name: "Checkout Code" 27 | uses: "actions/checkout@v3" 28 | timeout-minutes: 5 29 | with: 30 | fetch-depth: 0 31 | 32 | - name: "Build Ruby" 33 | uses: "ruby/setup-ruby@v1" 34 | with: 35 | ruby-version: "${{ matrix.ruby }}" 36 | bundler: "2" 37 | bundler-cache: true 38 | 39 | - name: "Run RSpec" 40 | run: | 41 | bundle exec rspec 42 | -------------------------------------------------------------------------------- /lib/chamber/commands/compare.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/instance' 4 | require 'chamber/commands/base' 5 | require 'chamber/commands/comparable' 6 | 7 | module Chamber 8 | module Commands 9 | class Compare < Chamber::Commands::Base 10 | include Chamber::Commands::Comparable 11 | 12 | attr_accessor :first_settings_instance, 13 | :second_settings_instance 14 | 15 | def self.call(**args) 16 | new(**args).call 17 | end 18 | 19 | def initialize(first:, second:, **args) 20 | super(**args) 21 | 22 | self.first_settings_instance = Chamber::Instance.new(args.merge(namespaces: first)) 23 | self.second_settings_instance = Chamber::Instance.new(args.merge(namespaces: second)) 24 | end 25 | 26 | protected 27 | 28 | def first_settings_data 29 | settings_data(first_settings_instance) 30 | end 31 | 32 | def second_settings_data 33 | settings_data(second_settings_instance) 34 | end 35 | 36 | def settings_data(instance) 37 | if keys_only 38 | instance.to_environment.keys.join("\n") 39 | else 40 | instance.to_s(pair_separator: "\n") 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/chamber/configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/context_resolver' 4 | 5 | module Chamber 6 | class Configuration 7 | attr_accessor :basepath, 8 | :decryption_keys, 9 | :encryption_keys, 10 | :files, 11 | :namespaces, 12 | :rootpath, 13 | :signature_name 14 | 15 | def initialize(**args) 16 | options = ContextResolver.resolve(**args) 17 | 18 | self.basepath = options.fetch(:basepath) 19 | self.namespaces = options.fetch(:namespaces) 20 | self.decryption_keys = options.fetch(:decryption_keys) 21 | self.encryption_keys = options.fetch(:encryption_keys) 22 | self.files = options.fetch(:files) 23 | self.rootpath = options.fetch(:rootpath) 24 | self.signature_name = options.fetch(:signature_name) 25 | end 26 | 27 | def to_hash 28 | { 29 | basepath: basepath, 30 | decryption_keys: decryption_keys, 31 | encryption_keys: encryption_keys, 32 | files: files, 33 | namespaces: namespaces, 34 | signature_name: signature_name, 35 | } 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /.rubocop_capybara.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Last updated to 2.21.0 4 | 5 | require: 6 | - 'rubocop-capybara' 7 | 8 | Capybara/ClickLinkOrButtonStyle: 9 | Enabled: true 10 | EnforcedStyle: 'link_or_button' 11 | 12 | Capybara/CurrentPathExpectation: 13 | Enabled: true 14 | 15 | Capybara/MatchStyle: 16 | Enabled: true 17 | 18 | Capybara/NegationMatcher: 19 | Enabled: true 20 | EnforcedStyle: 'have_no' 21 | 22 | Capybara/RedundantWithinFind: 23 | Enabled: true 24 | 25 | Capybara/RSpec/HaveSelector: 26 | Enabled: true 27 | DefaultSelector: 'css' 28 | 29 | Capybara/RSpec/PredicateMatcher: 30 | Enabled: true 31 | AllowedExplicitMatchers: [] 32 | EnforcedStyle: 'inflected' 33 | 34 | Capybara/SpecificActions: 35 | Enabled: true 36 | 37 | Capybara/SpecificFinders: 38 | Enabled: true 39 | 40 | Capybara/SpecificMatcher: 41 | Enabled: true 42 | 43 | Capybara/VisibilityMatcher: 44 | Enabled: true 45 | -------------------------------------------------------------------------------- /lib/chamber/filters/failed_decryption_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/errors/decryption_failure' 4 | require 'chamber/refinements/deep_dup' 5 | 6 | module Chamber 7 | module Filters 8 | class FailedDecryptionFilter 9 | using ::Chamber::Refinements::DeepDup 10 | 11 | BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9+/]{342}==\z}.freeze 12 | 13 | attr_accessor :data, 14 | :secure_key_token 15 | 16 | def self.execute(**args) 17 | new(**args).__send__(:execute) 18 | end 19 | 20 | def initialize(data:, secure_key_prefix:, **_args) 21 | self.data = data.deep_dup 22 | self.secure_key_token = /\A#{Regexp.escape(secure_key_prefix)}/ 23 | end 24 | 25 | protected 26 | 27 | def execute(raw_data = data) 28 | settings = raw_data 29 | 30 | raw_data.each_pair do |key, value| 31 | if value.respond_to? :each_pair 32 | execute(value) 33 | elsif key.match(secure_key_token) && 34 | value.respond_to?(:match) && 35 | value.match(BASE64_STRING_PATTERN) 36 | 37 | fail Chamber::Errors::DecryptionFailure, 38 | "Failed to decrypt #{key} (with an encrypted value of '#{value}') " \ 39 | "in your settings." 40 | end 41 | end 42 | 43 | settings 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/lib/chamber/commands/secure_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/commands/secure' 5 | require 'fileutils' 6 | 7 | module Chamber 8 | module Commands 9 | describe Secure do # rubocop:disable RSpec/MultipleMemoizedHelpers 10 | let(:rootpath) { Pathname.new(::File.expand_path('./spec/fixtures')) } 11 | let(:settings_directory) { rootpath + 'settings' } 12 | let(:settings_filename) { settings_directory + 'unencrypted.yml' } 13 | let(:options) do 14 | { 15 | basepath: rootpath, 16 | rootpath: rootpath, 17 | encryption_keys: rootpath + '../spec_key', 18 | shell: double.as_null_object, # rubocop:disable RSpec/VerifiedDoubles 19 | } 20 | end 21 | 22 | before(:each) do 23 | ::FileUtils.mkdir_p settings_directory unless ::File.exist? settings_directory 24 | end 25 | 26 | after(:each) do 27 | ::FileUtils.rm_rf(settings_directory) if ::File.exist? settings_directory 28 | end 29 | 30 | it 'can return values formatted as environment variables' do 31 | settings_filename.write <<~HEREDOC 32 | test: 33 | _secure_my_unencrpyted_setting: hello 34 | HEREDOC 35 | 36 | Secure.call(**options) 37 | 38 | expect(settings_filename.read) 39 | .to match %r{_secure_my_unencrpyted_setting: [A-Za-z0-9+/]{342}==} 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/chamber/filters/insecure_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/filters/secure_filter' 4 | 5 | module Chamber 6 | module Filters 7 | class InsecureFilter < SecureFilter 8 | BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9+/]{342}==\z}.freeze 9 | BASE64_SUBSTRING_PATTERN = %r{[A-Za-z0-9+/#]*={0,2}}.freeze 10 | LARGE_DATA_STRING_PATTERN = / 11 | \A 12 | (#{BASE64_SUBSTRING_PATTERN}) 13 | \# 14 | (#{BASE64_SUBSTRING_PATTERN}) 15 | \# 16 | (#{BASE64_SUBSTRING_PATTERN}) 17 | \z 18 | /x.freeze 19 | 20 | protected 21 | 22 | def execute(raw_data = data) # rubocop:disable Metrics/CyclomaticComplexity 23 | securable_settings = super 24 | settings = {} 25 | 26 | securable_settings.each_pair do |key, value| 27 | value = if value.respond_to? :each_pair 28 | execute(value) 29 | elsif value.respond_to? :match 30 | value unless value.match(BASE64_STRING_PATTERN) || 31 | value.match(LARGE_DATA_STRING_PATTERN) 32 | end 33 | 34 | settings[key] = value unless value.nil? 35 | end 36 | 37 | settings 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /.rubocop_factory_bot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Last updated to 2.26.1 4 | 5 | require: 6 | - 'rubocop-factory_bot' 7 | 8 | FactoryBot/AssociationStyle: 9 | Enabled: true 10 | EnforcedStyle: 'explicit' 11 | 12 | FactoryBot/AttributeDefinedStatically: 13 | Enabled: true 14 | 15 | FactoryBot/ConsistentParenthesesStyle: 16 | Enabled: true 17 | ExplicitOnly: false 18 | 19 | FactoryBot/CreateList: 20 | Enabled: true 21 | ExplicitOnly: false 22 | 23 | FactoryBot/ExcessiveCreateList: 24 | Enabled: true 25 | 26 | FactoryBot/FactoryAssociationWithStrategy: 27 | Enabled: true 28 | Include: 29 | - '**/*_spec.rb' 30 | - '**/spec/**/*' 31 | - '**/test/**/*' 32 | - '**/features/support/factories/**/*.rb' 33 | 34 | FactoryBot/FactoryClassName: 35 | Enabled: true 36 | 37 | FactoryBot/FactoryNameStyle: 38 | Enabled: true 39 | ExplicitOnly: false 40 | 41 | FactoryBot/IdSequence: 42 | Enabled: true 43 | 44 | FactoryBot/RedundantFactoryOption: 45 | Enabled: true 46 | Include: 47 | - '**/*_spec.rb' 48 | - '**/spec/**/*' 49 | - '**/test/**/*' 50 | - '**/features/support/factories/**/*.rb' 51 | 52 | FactoryBot/SyntaxMethods: 53 | Enabled: true 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | Current Problem 8 | -------------------------------------------------------------------------------- 9 | 10 | 16 | 17 | Potential Solution or New Behavior 18 | -------------------------------------------------------------------------------- 19 | 20 | 21 | 22 | ### Mockups 23 | 24 | 28 | 29 | 51 | -------------------------------------------------------------------------------- /settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | rubygems: 4 | _secure_api_key: JL5hAVux4TERpv49QPWxy9H0VC2Rnk7V8/e8+1XOwPcXcoH/a7Lh253UY/v9m8nI/Onb+ZG9nZ082J4M/BmLa+f7jwMEwufIqbUhUah9eKIW8xcxlppBYpl7JVGf2HJF5TfCN44gMQNgGNzboCQXKqRyeGFm4u772Sg9V2gEx/q7qJ6F4jg7v/cltCFLmJfXA2SHA5Dai4p9L4IvMVVJGm34k5j7KOegNqpVWs2RY99cagjPuzc9VM2XSUsXgqcUJdmH8YtPW8Kqkyg0oYlRh6VQWABlWXwTZz74QjTTjqtqfoELIoFTMBDh+cCvuUTAE5m06LhlqauVrB4UnBsd5g== 5 | 6 | circle_ci: 7 | _secure_api_token: eKTv02Fah+GibSsxGCnX/6K/mx5e5R3+A+z3fu2sDpJp5dyrLyRcC0oAFka7Knba0vfGSucccW6Hi1SBRDIzv40AvIO1g21LyitQDaRf6xZyB/YcUM8/qRGQls2+FtsrKPOyVAeC6Yd8RE0+7nI18civGui0XedOmKWEc8xuomeri6UtN21fO4qyiLBTOwY1oTTwtT3ixVGSd4ER5yKpLSdxW2EwqEC6ir0bYi/7WHEs0DZ6/EfnBuRhTH7e4FjVeCkzMBVpOrzx5zE8BXs4SXrFMzU4M3SeAUQ5jlTPAo0C3nq/IXEw3ctzvPNHA5lAlWfSXhIOZgB7qiTqu6a0dA== 8 | 9 | heroku: 10 | _secure_test_app_name: nzDOat47NCv1jRmP3EC6MLAFo7xTlBHoiVhmleg54+nsicYoX24NfZZfl0sEvpHq9yHfn8lawNXs0NknaoXmxIBlMYxSZpNJ1BJSS+0mxrVUIGJisufTpSlIoiZXSnjoQ3ef/PkZSxvSYL/VXLAQP6zY8dD2HtW2Eawh2oZ8sAY8Aer62IF+tqp1cuwlK9vL2+mFt283huBBekKHpYFuJZRUaLRtdbGnEmNtuauWS6Y9ml/ItsQuUaySAKvIqjRVduIdCc2ZitWRJT3Ibn/fhcawYeM8J6ize1OXKVeN5jXdbMXOR/7r15VocyZMn2w7zODrMKBZUW2BMrF+BKQxkw== 11 | _secure_api_token: Re/7f/OQoP9zYUXjjmiUoiGeB8PtSnkcIFPlIw7dNh3rLO4pJQaNXVALMvPEMaLPVqzg1Qzm/k6IjEErYzA/PWSpt1WK+gHE9cPTGotnaW13hoi0o5HQ/7PqczItNqJY/i96X06EUtdx08/24XaAAOzy4qLNe2rJQ1vO0Fjbc9TD8AUKtsC1qTf+LTQpu0n9ESIUC25LI0AC2oM3HDtzBvHkdPHsqBm6wUrxdhKUPbLbehpAnNRe9BEdgFJArVlEpzVWrqv9QTA3H18jETDqSMQ+0yckUgY9+KFzPnFGJ+D00PAR3LfUcKO5J12temtoORNmxWV8rh9ofIHoYXcwRQ== 12 | -------------------------------------------------------------------------------- /lib/chamber/encryption_methods/public_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'base64' 4 | 5 | require 'chamber/errors/disallowed_class' 6 | 7 | module Chamber 8 | module EncryptionMethods 9 | class PublicKey 10 | def self.encrypt(_settings_key, value, encryption_key) 11 | value = YAML.dump(value) 12 | encrypted_string = encryption_key.public_encrypt(value) 13 | 14 | Base64.strict_encode64(encrypted_string) 15 | end 16 | 17 | def self.decrypt(_settings_key, value, decryption_key) 18 | return value if decryption_key.nil? 19 | 20 | decoded_string = ::Base64.strict_decode64(value) 21 | unencrypted_value = decryption_key.private_decrypt(decoded_string) 22 | 23 | ::YAML.safe_load(unencrypted_value, 24 | aliases: true, 25 | permitted_classes: [ 26 | ::Date, 27 | ::Time, 28 | ::Regexp, 29 | ]) 30 | rescue ::Psych::DisallowedClass => error 31 | raise ::Chamber::Errors::DisallowedClass, <<~HEREDOC 32 | #{error.message} 33 | 34 | You attempted to load a class instance via your Chamber settings that is not allowed. 35 | 36 | See https://github.com/thekompanee/chamber/wiki/Upgrading-To-Chamber-3.0#limiting-complex-classes for full details. 37 | HEREDOC 38 | rescue ::TypeError 39 | unencrypted_value 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/lib/chamber/filters/translate_secure_keys_filter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/filters/translate_secure_keys_filter' 5 | 6 | module Chamber 7 | module Filters 8 | describe TranslateSecureKeysFilter do 9 | it 'translates keys if they start with "_secure_"' do 10 | filtered_settings = TranslateSecureKeysFilter.execute( 11 | secure_key_prefix: '_secure_', 12 | data: { 13 | '_secure_my_secure_setting' => 'hello', 14 | }, 15 | ) 16 | 17 | expect(filtered_settings['my_secure_setting']).to eql 'hello' 18 | end 19 | 20 | it 'does not translate keys if they do not start with "_secure_"' do 21 | filtered_settings = TranslateSecureKeysFilter.execute( 22 | secure_key_prefix: '_secure_', 23 | data: { 24 | 'my_secure_setting' => 'hello', 25 | }, 26 | ) 27 | 28 | expect(filtered_settings['my_secure_setting']).to eql 'hello' 29 | end 30 | 31 | it 'does not translate the key if it starts with "secure"' do 32 | filtered_settings = TranslateSecureKeysFilter.execute( 33 | secure_key_prefix: '_secure_', 34 | data: { 35 | 'secure_setting' => 'hello', 36 | }, 37 | ) 38 | 39 | expect(filtered_settings['secure_setting']).to eql 'hello' 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /certs/thekompanee.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEdjCCAt6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAyMTAwLgYDVQQDDCdhY2Nv 3 | dW50c19ydWJ5Z2Vtcy9EQz10aGVrb21wYW5lZS9EQz1jb20wHhcNMjMwMzA2MDM0 4 | ODUxWhcNMjYwMzA1MDM0ODUxWjAyMTAwLgYDVQQDDCdhY2NvdW50c19ydWJ5Z2Vt 5 | cy9EQz10aGVrb21wYW5lZS9EQz1jb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAw 6 | ggGKAoIBgQD0Z84PxtE0iiWCMTQbnit6D4w55GGBQZnhpWUCJwC0SpQ/jnT0Fsma 7 | g8oAIdDclLvLC9jzqSAmkOujlpkJMb5NabgkhKFwHi6cVW/gz/cVnISAv8LQTIM5 8 | c1wyhwX/YhVFaNYNzMizB//kZOeXnv6x0tqV9NY7urHcT6mCwyLeNJIgf44i1Ut+ 9 | mKtXxEyXNbfWQLVY95bVoVg3GOCkycerZN4Oh3zSJM1s+cZRBFlg7GR1AE3Xqs6k 10 | RhE7yp8ufeghC3bbxgnQrkk8W8Fkkl0/+2JAENpdE4OUepC6dFzDlLZ3UvSk7VHf 11 | NBRKSuO15kpPo2G55N0HLy8abUzbu5cqjhSbIk9hzD6AmdGCT4DqlsdHI5gOrGP0 12 | BO6VxGpRuRETKoZ4epPCsXC2XAwk3TJXkuuqYkgdcv8ZR4rPW2CiPvRqgG1YVwWj 13 | SrIy5Dt/dlMvxdIMiTj6ytAQP1kfdKPFWrJTIA2tspl/eNB+LiYsVdj8d0UU/KTY 14 | y7jqKMpOE1UCAwEAAaOBljCBkzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNV 15 | HQ4EFgQU7+XQuN042fZGvzLhYbIwDfsxZV8wLAYDVR0RBCUwI4EhYWNjb3VudHMr 16 | cnVieWdlbXNAdGhla29tcGFuZWUuY29tMCwGA1UdEgQlMCOBIWFjY291bnRzK3J1 17 | YnlnZW1zQHRoZWtvbXBhbmVlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAIaR7p1yv 18 | +nE+JX/7nItZ0YKE71L6JdAuAlUfvTuHq8S1dpKdewrm3ewNPGt2nK9svy8vxNUQ 19 | AqT3KWIaGVwkslL/y/MRxBQHOOZz9XCMmUYXIencNXqNzkWLubcCK7uF1h8mRS46 20 | jvGfiXfYDwi8QzqJA8IFEK/oyKCCKlNiIbz8Q4yHM4wTqLwjYQqlHiBEgCF2UHc6 21 | 0tCTD8eixdZUeuexdB7M7F/M086y1wjWyYCeijqbKmTPqKb8n6AmLo0fqkNBVibJ 22 | nYVEOECVzKt/f3PoG0Z2oMnZ6FKqpFiDpPqgAlRDmVznjpMdZR204/t79NoEVlLf 23 | lxz9Q1u83TUBUPvDRzKg6Kq9P28JV1Ipnst0ecQZOsnoLjl1BmxUt29hQPJKjd11 24 | Z6HMkN0PHJ6eG0Zl3/H4H8Xb+KreWlEx3sXXfZj6UscrdHAVffRQnM1E98PCqnRX 25 | l5EwT4ShG/HorJMQSTY1EoBLZf54NrD5WlWcfM0CLrcvT7QM77dIqmue 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /spec/spec_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAvYcqkBjBLhSaTKOCMoq+ZxcuxTaA5UQpf/vDXbOiI871+6x7 3 | yKbTr+Xr9oFDvFldyvUFiK6LX0rj/jgLnaTBsLyXjH46dOmiPUO3k/QvDmRKN8zv 4 | l4x9T7YZKuoEkxZwE3T3MxKPmBorKGv/22VbKocqkGGgx9gKIvSfxVXfTMfcvTDr 5 | Fllm1bCaXEVGcRAknJg94ul2yMgqmYA2KJcPy2naped90yzv0A7c/UI5zjBcJPgk 6 | um79aDTSv095yl+Pk+5JM2jD85x3ph3ij++LdAXJ1fBJrV1H39UJ4A6yOupEG3+Q 7 | sZTPDXkBBnX8+mWXYCClI/GF6iA/G3njeMqUfQIDAQABAoIBAQCNfvwn2FNhM8B+ 8 | txPc5/h+vrAMTESu5dSV2EBjFFlYoorC+mLvyp0AFYe04YzQcWe0vnkDMkybOC4+ 9 | /ViuuPJiIGqEWTkLT+HwGz5TF1qKRBWk0zuMyTZE7I6dm1kO9xF1dT2Yw6Kwy9is 10 | tPSvt1v4xDDi2gBPMttefYNKsHGlv8dYWwwTUd1JA4Cx4QOTimH1OK0OJJZMa2Fo 11 | wW/fcHHQd1IkwkHo4gHLs5vIQLjQIyFazIwSmaCeiyXZm0gTEP1Zc5+NQUuascnD 12 | k6CIOM51hmK8x+8DNU0edOs9tm8kIkqxwapjpfzJZtJy19yNam7psX+d8T4YY/Vq 13 | uuv2XyOBAoGBAOrmlJ9ES7Nb+ssQ0stPqsz9pWp3HVfxkR9jWoye793EDvPx+/7u 14 | ggiKt/vMsNQSrO7LiKXrTETjLeprHDun4ekIF6QoyiA8A0El2SHVyFDUVrQ70MWo 15 | 5G+t/HvMcbfuwHEmHKZxx/G+5d571v5TnVr/SO+X/aUsCpnvI+n3unGDAoGBAM6N 16 | Q4kGTWeoFYdbAlu2q4zdmGbkFkTCHC7EyEZqLLbLs8ruFCVtb+mLC6ogg1Pljzkf 17 | Q8KfY2pe9XjLvTIsEhMnSrXHpLRQTtnM9wtBTcldxmXIx7iIN3YlHFAylDRBsQeY 18 | EDmVRWlddQTwiCTMP/egdrf6M1q/lKbwJKGuNgH/AoGAbOSZbz+szCfOmkZhQcjb 19 | yA2pmjI/0x30BZX8SwX+8lJKEEJUbHjKpQURh1P7gURrEIQgA7KRd7nM9kfTHcCU 20 | 1H9g3kPed5T6eT//aVT+eKIG06SLAqZpx2/jhMxgx0CRy6I8U9iZNPMnAr1sDBhc 21 | 8Os5N4DBH8nL3FJk1rh1oNsCgYBdlgfph99+fdvzTjsNTwD/fE/XHrUwRjr6vdJ4 22 | hRbpY/Z8lc6u/pzcVlHcrYqTcsmn+hB7mDg1M/1I1BdPKkSuRPlGz8RCprhzWrj8 23 | coRKHqEL8SL860slzr67X49i9fi+FhoT3RxsFxo0OL+W3KyHCD+CMG0gSvmRgfE3 24 | CHYUBQKBgDd591aYkAyuSSAsLhSjQKjd64Rj2vkaQw1xVkEUyfdeORmGensy4enH 25 | /jlhH3NVDYTGKiWRGkOnf9Vi1e4NVTQSoR4UyjoU4iMR1w9RVNccOtpLbqBn0377 26 | 0fQlf1ILp+IT7NeWtrpEwb9LvmBbvYpyEAHtwqWknYpCr6/FBrEQ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/rails-3-test/.chamber.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAvYcqkBjBLhSaTKOCMoq+ZxcuxTaA5UQpf/vDXbOiI871+6x7 3 | yKbTr+Xr9oFDvFldyvUFiK6LX0rj/jgLnaTBsLyXjH46dOmiPUO3k/QvDmRKN8zv 4 | l4x9T7YZKuoEkxZwE3T3MxKPmBorKGv/22VbKocqkGGgx9gKIvSfxVXfTMfcvTDr 5 | Fllm1bCaXEVGcRAknJg94ul2yMgqmYA2KJcPy2naped90yzv0A7c/UI5zjBcJPgk 6 | um79aDTSv095yl+Pk+5JM2jD85x3ph3ij++LdAXJ1fBJrV1H39UJ4A6yOupEG3+Q 7 | sZTPDXkBBnX8+mWXYCClI/GF6iA/G3njeMqUfQIDAQABAoIBAQCNfvwn2FNhM8B+ 8 | txPc5/h+vrAMTESu5dSV2EBjFFlYoorC+mLvyp0AFYe04YzQcWe0vnkDMkybOC4+ 9 | /ViuuPJiIGqEWTkLT+HwGz5TF1qKRBWk0zuMyTZE7I6dm1kO9xF1dT2Yw6Kwy9is 10 | tPSvt1v4xDDi2gBPMttefYNKsHGlv8dYWwwTUd1JA4Cx4QOTimH1OK0OJJZMa2Fo 11 | wW/fcHHQd1IkwkHo4gHLs5vIQLjQIyFazIwSmaCeiyXZm0gTEP1Zc5+NQUuascnD 12 | k6CIOM51hmK8x+8DNU0edOs9tm8kIkqxwapjpfzJZtJy19yNam7psX+d8T4YY/Vq 13 | uuv2XyOBAoGBAOrmlJ9ES7Nb+ssQ0stPqsz9pWp3HVfxkR9jWoye793EDvPx+/7u 14 | ggiKt/vMsNQSrO7LiKXrTETjLeprHDun4ekIF6QoyiA8A0El2SHVyFDUVrQ70MWo 15 | 5G+t/HvMcbfuwHEmHKZxx/G+5d571v5TnVr/SO+X/aUsCpnvI+n3unGDAoGBAM6N 16 | Q4kGTWeoFYdbAlu2q4zdmGbkFkTCHC7EyEZqLLbLs8ruFCVtb+mLC6ogg1Pljzkf 17 | Q8KfY2pe9XjLvTIsEhMnSrXHpLRQTtnM9wtBTcldxmXIx7iIN3YlHFAylDRBsQeY 18 | EDmVRWlddQTwiCTMP/egdrf6M1q/lKbwJKGuNgH/AoGAbOSZbz+szCfOmkZhQcjb 19 | yA2pmjI/0x30BZX8SwX+8lJKEEJUbHjKpQURh1P7gURrEIQgA7KRd7nM9kfTHcCU 20 | 1H9g3kPed5T6eT//aVT+eKIG06SLAqZpx2/jhMxgx0CRy6I8U9iZNPMnAr1sDBhc 21 | 8Os5N4DBH8nL3FJk1rh1oNsCgYBdlgfph99+fdvzTjsNTwD/fE/XHrUwRjr6vdJ4 22 | hRbpY/Z8lc6u/pzcVlHcrYqTcsmn+hB7mDg1M/1I1BdPKkSuRPlGz8RCprhzWrj8 23 | coRKHqEL8SL860slzr67X49i9fi+FhoT3RxsFxo0OL+W3KyHCD+CMG0gSvmRgfE3 24 | CHYUBQKBgDd591aYkAyuSSAsLhSjQKjd64Rj2vkaQw1xVkEUyfdeORmGensy4enH 25 | /jlhH3NVDYTGKiWRGkOnf9Vi1e4NVTQSoR4UyjoU4iMR1w9RVNccOtpLbqBn0377 26 | 0fQlf1ILp+IT7NeWtrpEwb9LvmBbvYpyEAHtwqWknYpCr6/FBrEQ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAzoh4Dj7hY1pc7yM1tNHZgB3sU+wdDrKn9hl/TaJ2m28HckKz 3 | pUbChlFzfjp6LUTzwmyZ/RFMOLHrigEiCg4uB+RA8/pCwTGBbG+xxTOnrxEcFtxe 4 | p/8ova8SR9D/rOoQed4bvnwN1xj3Gm7ynuvHFXbTwl5Ugcf4rP4ftgAOHJDSzQSU 5 | kjJKceI7zEMXBq9LAA40/xrzIxQyquZgOy0fn/UwRfh9Wcv8Cmv2jl2EzGaJB6rz 6 | 7qjN2MD7Lx0bqaMuLAQ+WzmB/A1cAEtPdfB1yCWotMQV+YGDCSRcx6WK2DY6OtYQ 7 | CC8qHix1m2rkoinb/rVI9LpiEuVD/WLbXxwEdwIDAQABAoIBAQC3Fv/nERNZslfE 8 | n2ATBh1cmCpTU3OG/hYr8vqJ5kZjEJB3WG5NTGdSD47lDg4VZ5T4fLIeSGFbuU5n 9 | Lw3wABYKZOy33NmkzrA/Yt0jRuGWLwLJuV4oa+jADx2TkNG3hmBIyP+rY+oZD0av 10 | pj9rZvU8L/UVz+32h0IKidWu0GmyI5C+YGreb/4bLVjTZnR/Gq7UI/utHTUvfzjS 11 | DPPShfsEi94QLzZHJ6V9SttAEc0w8bq9VLOhtuOp1RSD80Wqsz1TEazn2nnhcp+N 12 | G8cEEqPuGOX7exbpL0/X9nhqWwkJxMKJYj0sPfoyVW16gPLSlhdKZ6sGFYtAaThd 13 | mOamweWxAoGBAPDGXFpVxt4ccGTgG8Za56R5h0g6g65HyQXFkTSTWvbYT8/RO+Hu 14 | H79XlJ/hulOTth+6uJujxwLXS9piRQs9bxCnckg0zJT27FCs1jc0YABhF2fVGDtR 15 | t6nTY9kS8NViuz/Hpwk4xsS7IBRUTIDOMVJ9chnofv1okPemyHn6S5EzAoGBANuX 16 | zlG8V0Zj1FSJDJYwABiaKr6wuSKE7WytMCGexWsMjG8lg4i366sFRgqQWBW9e8rI 17 | LVLzPwr5T7L2h2u9bRtdvu7NaULNm1wsw4k3yKF2eWgY7DPkPhnc9DMDYRZ7r3cj 18 | 3/TmkkEhTqt/pnhCBQ99sGbr3olqG+3EAdBfo4etAoGAdbYEYIWe++ebazXFXmwC 19 | 434mjUFOL14ZASFdo/6Gcje+jTMVu5bXHoyKtL9+gG2ulESNDEo9eT4Yb2OrrZsT 20 | OWkjjfmCKzGsxqQ49jQ82vKcpr6fYiDJUdVu8yk3ZJsoQD4zg2amhneVb1rnK/Gi 21 | W1nI9rGbo/gDJ4/6YufjGdECgYEA2eahjOnfkLGvGNOF8eJftgoFMIQcsexFzPlu 22 | MW5jZ/5jvEb1Zs1axbHN9t2VCr2kAWzxzMYjYLhUJboMVxT5F5bGyCB8uxVbfPPO 23 | dHMuW0o83tccS8HI/dELYTORO8YwL4Eh0tqZdpeegP+CKQMaEm0ehoK3qJPqct11 24 | D1hX5WECgYBhd06qglbMiWQ7zHfciEFmKtcJjqnoOQ98ZhjoMOccKhzpT3fZA6oz 25 | C/Gp60g8dI67C71ZCfMKMOrC9ra6L9XFLaEnTaecvxDAjq0LMPZjBMb8qtQK55rQ 26 | fdgW6jtD3AJl7BsmPvr23YTQP2MKEOPWaB63fG5oqFd+gtzklzkTDg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAqFa5xERTK2y8c/gcCtOJxkGzj3oFXfm7fdtsRWs+ewZIVNqf 3 | KgQmvKviLEALVL1+MYwhELZ0bkC7zvJwZWf6lbxgZDh9eCENM95CdPEhGe70N8kH 4 | F/A7w9CEH0R+mchyoIN7Kj9km3OLCJhQW1fPBP30ATDBWaiQO0+ChMnV6qij1q+l 5 | 09UQ+yJUXB5p2tlo8qB9vcBzQKOqvTZZ3M1lmIovSKKgYeXgZD3aLm/ufhSbXtnY 6 | MNcVad4QTrX5Rg2PHqz9m4hWVp5TuDs+hpXB7GCwtwQFGH4B5yPwrdzZQCIT7vYq 7 | fDuXtU9sbFYout/OT+THXcpUyN4ak6KtsHwUYQIDAQABAoIBAB12QED3bE73UuxB 8 | Cjdi2oQWT6TWyEWwRX47fvGPWXoSmWowcRbbbyQqOQVhmBoWqBVn3wmbxTjen4AS 9 | slUtI5A9Tel8cYkqz7K6PYtWAGMXjSqORm2MIAmfXuBSDo4/pMAvAslcGvGSAU1Z 10 | ywaRkavf/mqO6+gTNlsLCV7QLF5LKNRA66CMkJtJzofDnom6ti23PhKcdB2xR+pr 11 | 45iMeQ0ktmGwDkBQ7FT6zWc58Azf3y7PGZO5Z1bFP0uIqljiwBF5UalZ/zWG5BX1 12 | 5J2h94QrmCw7xzLM8bQ4vgXHMXrhxhdefv6UBLSDLUekpVR4qBVpXPto9NJxTe6n 13 | SqHoyKECgYEA2TVrWeqTatyNNPJc6m5ZFPvnl3+4ObRMrXbsC+H7SbtS96bbTKEy 14 | TYanGM4vkxyVXBSLzpFYn/wEnT9OZXOWaE7Ii5gReqiH7JShgCkySukAxW85jpWd 15 | 66pspZxHeNsvWJq7G7bFI3H7x00wKm/AeJcfEwCuO8lVusCZdgXksH0CgYEAxmcF 16 | AJLglGbA09mpEuxFw8XbOxpPUTQDqJpCUGFl01XsMO1izaa9juUbgDQecrVfuzw8 17 | zHNHwNGxI8PsHGFfVpHnUk4yPJmkF9e3U2wbK4bmDJWYqM44rrH4daRTpBvY9Cwt 18 | WH1ZdJBJHXKrCb2TNAS21Mxk5bjpixRnVKeGPLUCgYEAvF77jqtE1bQYMgbOVfEF 19 | CJG+M0Dsf/7BUN6Fr3h3+WWfem8WWMqjWWChoX2O/MIZNhYLeUXL2lMxnT29u3YW 20 | xrcsbQOghQOLfb0YopEXsKeJp6/h9qoCntLaDnYN9d4AnZLzGgHTzf2YISvrzaHF 21 | Bc/pfw1kDS16T8+gRx6tSfECgYEAqn8XZmHSCtWueZ+/0tomLdIWztiSNucrCbXe 22 | rPK2LYHWIkmtsarLlUO/eVHQpKTG6sEeQlgC3mmU/3Y7rjvjUSK7FhjKxRcHU9H1 23 | 4gOx6ZrYkRHxiYcGYOHh8T7Q/eNMcrCQG05FFI11t4+1+f3mxToT4BTx1BQu5m0X 24 | S0FXc00CgYEAoT5bpqPk16cIRp/jep20Lc4tzLrAOTTRgN4a1hj86bcFTqp4wEPY 25 | PGuea97foTRz/jVrfTPCNU1QZZbRlw6/Wd0+FNa5o+4Rr6MOB3K1i/5U4ux97oDJ 26 | hu1Ca260FrJ7q6aPcDf1gYE6DDLJ4ZVvB3vJEZM6Ah0/Xsc0pIYzf8g= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | chamber (3.1.1) 5 | thor (>= 0.20.3, < 2.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activemodel (4.2.11.1) 11 | activesupport (= 4.2.11.1) 12 | builder (~> 3.1) 13 | activerecord (4.2.11.1) 14 | activemodel (= 4.2.11.1) 15 | activesupport (= 4.2.11.1) 16 | arel (~> 6.0) 17 | activesupport (4.2.11.1) 18 | i18n (~> 0.7) 19 | minitest (~> 5.1) 20 | thread_safe (~> 0.3, >= 0.3.4) 21 | tzinfo (~> 1.1) 22 | arel (6.0.4) 23 | awesome_print (1.9.2) 24 | builder (3.2.4) 25 | concurrent-ruby (1.2.2) 26 | diff-lcs (1.5.0) 27 | fuubar (2.3.2) 28 | rspec-core (~> 3.0) 29 | ruby-progressbar (~> 1.4) 30 | i18n (0.9.5) 31 | concurrent-ruby (~> 1.0) 32 | minitest (5.11.3) 33 | rspec (3.12.0) 34 | rspec-core (~> 3.12.0) 35 | rspec-expectations (~> 3.12.0) 36 | rspec-mocks (~> 3.12.0) 37 | rspec-core (3.12.1) 38 | rspec-support (~> 3.12.0) 39 | rspec-expectations (3.12.2) 40 | diff-lcs (>= 1.2.0, < 2.0) 41 | rspec-support (~> 3.12.0) 42 | rspec-mocks (3.12.3) 43 | diff-lcs (>= 1.2.0, < 2.0) 44 | rspec-support (~> 3.12.0) 45 | rspec-support (3.12.0) 46 | rspectacular (0.70.8) 47 | fuubar (~> 2.0) 48 | rspec (~> 3.1) 49 | ruby-progressbar (1.13.0) 50 | thor (1.2.1) 51 | thread_safe (0.3.6) 52 | timecop (0.9.6) 53 | tzinfo (1.2.5) 54 | thread_safe (~> 0.1) 55 | 56 | PLATFORMS 57 | ruby 58 | 59 | DEPENDENCIES 60 | activerecord (~> 4.0) 61 | activesupport (~> 4.0) 62 | awesome_print (~> 1.6) 63 | chamber! 64 | rspec (~> 3.5) 65 | rspectacular (~> 0.46) 66 | timecop (~> 0.0) 67 | 68 | BUNDLED WITH 69 | 2.3.24 70 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.development.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAqMVYSeBJCEPPiKJPrWkMn+/9onHDHFNUu+nX+Wh7JC/0I/eD 3 | F5QrczAT53Q0YQvEaP82zhbnjzR4FJu2bcEZHmePUoPGME6ITI7f7DYRRlpTgT0H 4 | nYdnEyO72LPnRRzITNpNbJAbOkdfoKbKW9+DVWIlbspM+lSHJUv2WeMWL9pQ4+aT 5 | PYBxHOIEcorvj2ccIL4I1b6x55j2qE5z92xKcAqRmdY6xiGLNq42bX4kxvt2pQY4 6 | smzjetLQ7oiEykDMqFzOoWtFOQwq+8ub3G8yPe2a14wM8qybm6WkoobhMv9XXCIv 7 | GIq+b6iGMxOzCIc5wYXEXXuSN4pz2bIiE1Ex4wIDAQABAoIBAE4ADyLtnrNMK9F5 8 | OpvpriUJFM8MBuHDfajvlZq2eBsscohg71xpZX9yG/Df2wlzF85zDZBIM7MSy+Cy 9 | aqjlEsfjkvDO6D0RPpsreUBBaNB8FJXpb+iVWMUWwc2Qr/VYLRaf2iKvo3XW4NJt 10 | 4558ecVve99tbhBQGrnZFr1KX8mzOy8HvisXAZ/swoLk9kgq5HxTtwpuznoSs6qh 11 | KTQ7LKjHcdYXq8ONx3jnTiP8dLhLGZwSjIjki3JbUEG77NzxKVLHu64tRrA8RCp8 12 | T4JRySYLIyh6XTBkzF6sknBFQ6hgEqBxrOa2r4u4ODbZDtWIDUd6mq10mX6cvXyq 13 | HcbmkJkCgYEA3K4YBMFhKnk/7V7pt2J8h1I22t6VnJeNC2NeuImTiy35HT1xgl2d 14 | wRmJkYNMB0LHfIoZf1dTSV9xJpWRJkidb5ONT/uNBziqkcdGxKByOTVmN+nU5US9 15 | nNKiNSuDGRBp4oSPUazdfzoh4lXoAD4E3tdfoxj7Y66Drrs+RIrmZccCgYEAw8hh 16 | bH4fsUcOXSmxltxz4Ii2qvFSTdK8qunirONzq+3PZ/UZK37+B4C1ZCuzlY13xUVT 17 | nM0zPgHeATluP6zI8m9/w5DKGA54kT7cKn0iDkqUAOGVIWKCQUOTOhNTtCQS2Vbk 18 | CMASlUnUVKTCpNCnmrSH7yWw7s5Y2H7q6k/hIwUCgYA1wh9NwUIHU8Qz8gQjTVWi 19 | rW95f/GYVP7iAFIJyFHt/MQL0HT/WgjdCniNvxtdRXJNL2Mot0AtxpdM2/ChD91y 20 | WdJ54oCnerFqk830hlVtBEojYs4fD9DVDCbBpxjnmS0CJmK3ddH7dVDdzymWLYWI 21 | N5UvqR1MWi3v+eMgVWns7wKBgGkNtqkerCbV9/EplC9dL5iGA26Kex1jPPWA9q7K 22 | haD1xFsF+3GJfQnNmIedo4lWFbR21LzIpvxYyCVkCbKFJnZgOfBYWvi5mxxk3IK4 23 | DnQYtH4cjYcScs5OUkvRXxLEYJDavRWs6svMh/C7T+5cP/MVd/NtCBm8A5R31dnQ 24 | 7v95AoGAIqimHVj63/E38hC2sn9jGSI5T0c2a+XRjrn1mqD2HBf++M6kGbr5cb0G 25 | 5W/C7oCxLvKgKGcNmgast3JMH1otOve4HgGxg9as/AFxW6689hjR2eZUH2nMGVZi 26 | TtbFaLZTqK4HQ2WihDv30d1GlWXJsl/8pX27GLm+Nepu4VDQBE0= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.production.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAlXcpv9ILEtmn2JXsX2mYcxROZ+KDD8k6/ywTzVoZLSRfBglP 3 | LVJjW06hYRDV9ZX08nBxc5aTJrXA05aE9Y+wY427wPh/Pasm99QSBWs6FUu8GbOh 4 | CExkK09UenntzAKc8BhSOi/UiJYSxiOgWM5L0FzHtCVXJpMHZq2JMTJ8nP2ra7OW 5 | xe4qj6h53rqVUmFSuVnn6o4+HIKXxdkQRs8b3UTPPUcked29tbFPnCYFTWGk7sn7 6 | KePn93qpWjrOB2QRpcyopaS+EKFlR4XpJrt4pfZbVbw1kS61Z3N6Cf8r/c3Mdxzm 7 | TxDoBckhLicnbOxSwZNrKvvpd46Volfz2gcHEwIDAQABAoIBAQCRHvJaXQaDdvGR 8 | SYAH4+8xTTYTh8rZOPkfaRyQ9jobqoTYx5Eo4CW4MdBe/om/uanq+2IsaLbXjwF6 9 | b1sJUkphXuw9oQlqRxO+7RnSSRpMMwyfp9lixUk9A/lyMZDB1IesVvD/rhEdzRjN 10 | aSn3D9ZsqKj+MIM94OP+vd/G9M4pXmeHM0wHVIZP5duCMW2H1x4sFilbdK8ZY0lo 11 | Xxg2AyFVQjNE7Ss5ILaMFR9+kP4CP3FPbcXM8zfHYks6WkVUm5Vqff2oCyqaJCXY 12 | scyJSQGoJOBIY1uYlh0fM+/UiuOYlLoxFJMlINrcsvksuNnLGYadREbhKA7mxAHQ 13 | XyBkYM+BAoGBAMRWq2z8ywI0srSksPVRxAKZWIotIhF26dif126SyYYTH4LAwW0z 14 | oJs6gtdjm0S9Ur6RyvnqrPvF8/px4g2SPpi0ELiNpjkUles47no2201e2P/diKar 15 | igMJn6InqmK5SSJi3JavUN9Iq70TQhR8slsMsPCPqdt9MGgPj88b1Yb1AoGBAMLi 16 | M0c5dgLwP/F7IMyjUtAD/KUvLCXlUEExjwzsXUrDwkNhrZ/ycfvfOuks9BNUZbiJ 17 | SVTSEiX0hu4h75KAeMqbA4yNoJfCX808ZG5goTxEUgrjntjf6FOoGzvRGetONGI3 18 | +0oMlXxK/sql3J2C6aB1LcZ6LJdIwk2tK3asCUDnAoGBAJ4JZVME6COOZ4ogRpAw 19 | kKXG6Q3P5mxIW1KS1sWaQbw6CFMm9IsB03UfU9Iryv85pTDgCZHA5ByE6i3dHSfv 20 | X5Nq3UqwOewYNdRwrwSQw1Uz01eg6i2+RgjL5kKPywZMPiH/ka94zo/r3tECf14e 21 | QTIotQPtn7ucuSoJ3KfIr8dBAoGAHG2SvVkMdfFF4pvrdO5+Lgnm2NHAxuZkhp4M 22 | F/6CvV1BAUNGaIadSfiOR2VfVVKCZCDiqZTbQN4isKoSoHRNKoDiS6Z9PqYEA5RD 23 | sjY2TuxIa344tboz0cA7+MXP2htWWN2BspOTWsY11KnNPMzT96wgOUupJYhfj6Ur 24 | Kex+jRECgYEApEwAtGIpUi2RZD7+ghyIfiz8jbgep2In00WL+4sV3XHNJoMGLcj6 25 | eiSuZTREINaoYz0R869A0hgTS5eR0XBQwnGYTwa56/3MtYN7HgkHJllzGOChoMys 26 | wB86BXvzxrn3y60cK6oZ3oyk1do+JvQZCZznm3gdgdxcnkOxLafsMW4= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.signature.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAwHYvjoZ/MCLq5fQDrh6WDLVnNkhvUe1VEcz01frC8u2faSrw 3 | 2WzBQQTjUktFUChHBpEJJgqZoRLIrmPU1zE3BQ7LaKH20EADNyLabRqbG7POws7t 4 | njxzK4tzOoUUhyfE7ZIVJo0e1dPvmKtMXQ4lDPDLiVs5neheqHhCyg/TCMgFyEMx 5 | KHr+h2yW/oG2C1Q9o1yJo2kdX50j0wPxmb0TCd+UD6rXYlpzqhzxi8a9D30HDk7+ 6 | cSvbgAfaKr0T6rAcX+V/WJ7sgYT/Jpi7+RdbTPoQVRLLZwdIWvZOgLKua+WXYzhs 7 | kKHFM3kqsmEltoPQFu9nkhJAj9Sq2eUq6tfb0wIDAQABAoIBACOxR1yNQOUydJG0 8 | 0UHnVjiBI+UDRfHRq+cjNXzBhon624dreUM5UhW2zFmkr/QPpCxplWLCsH8YtrLw 9 | vLYz4FpFxh9feiBg3rfI9O0q8/0NOZkTOyoPV+SJdhMyPv5Zt8G/R8DghwlsxpBv 10 | pDfVubVOqtCMZIEOaQ+hTfGfQans7A31EPLf41zyXUdVwxFG53I0u+m3BPrXu3dY 11 | K6xbTPuakYb0F4JbgbH7agRI898M/yyuQ1+0N4wEYja7DbSZvBu8yrvibQWZqGwx 12 | 4H61Fp55AihVvO72B1CKzGQDaLCFCDBT0WInsqYURnjmqsTyKHTG9s0SBB90kxEk 13 | DMWtgGECgYEA55jfn1fcVhX9J1h4pvNLZYcjPG62+jpZBTuFNPQmd3/sLUhgK64H 14 | Sv43LL40N8to6OgckfSpK2wCayJV+KchQ41wK3JPgrBxmm59CxZIRfl5FBUhf73t 15 | XwS79LhC2E+qudRYyJiU4UzOJgRhE07ZvwexJ7/27y5Q5cUKScdHjJkCgYEA1L2q 16 | ySCvCj4BUFrx4ZNsHrt03YUt2MvGfjzDYYRfWiPjicuYuHWJHwkOlsP9Q8KFz6zX 17 | +l4apvlbXbIPTn8YCVzIAuCDtv23DXqunLH+XOhX0J77Kx/gXIof9cz9nisbNsCf 18 | 01x/iliD6eell5LS8QYn+Td8pWQyM+vM+sym40sCgYEAy3CFl3JVkaVVMJfNvFHh 19 | fDj+YdqbjylsA2WqeL6zf6t2ary2BgikTWMMUphuREjBSj6Cwk+ZvxPs9/E8deVS 20 | GgYHb5LFd3bJBDBkVTXonNctXc/GEnFjL2F4PdsSlPM+dGis6ODnfxW5zLlyEMTm 21 | D6GRI+rD3ozf8UZp6C9OkZkCgYEAhJgebTM4xep1iS7V2WG4HsN5iGPselYvxhh7 22 | Mjl98MGznPvPmRBwwW40sJVBMk14d0LkbkDe3blHT/OeBK/EPHmek+R5U8F4qScQ 23 | 08tAnYmWygl8xJkejq7sUphMbpuxX4PZWbQqLgETXuMpW3qAjQboPTVvM+pqO+A5 24 | JwwM7FcCgYEAmHcnfyeAI/7sDetuvwepdQbK922nnCxvyg9rxydbDGHDpWMB7mHA 25 | plh3CoTLLB2iCrtuT4CwxXnIYZm8dtoC3vORkPmNnE9iDR0yNJRIUYJegL0umuO5 26 | 66Z0PPRLxfR44d/kvjCjHfryjFx3njWwMlmUv7ZIlmbhgeYsol0C27Y= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.enc: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,E60BDF017291C114135298FE6FBA8F0F 4 | 5 | PzSvvOCJG2VVQS8kZ6EjvYMVARSrLwDUYVsx9ZYc9GHN1mWRkHXeW78iseTu2CJE 6 | KlpmBlrAQm4AYm9OBSljMVhsyx7o/af8g3Fyl8v1BwbEuE8MzcjUzoxmtKWxhbMG 7 | 9wlDOVd39M+YbqtdRG5OCjhAYYJf2fAogsQ44+pMNNGMVKurjN0m4I20UTpEQeN+ 8 | ZxNs4UnTA/7TTXOEM8XC4rZ+CfeC8NHrahwLahPE8e96eNx9Sg6tl5FeIhjoPAHA 9 | E2bOCE2FxGMIk1JTHLRNTGg8vDJlNQMiqRGnStAt8ptk8MEkYqTBMJumvPsH/2a5 10 | lw5s7qTajIqM2XcDVuhbaQL6cJTqSVn0sHiP0E1AKitcSenXzw3QSUbzKkPMoYNA 11 | m3tvrlnxF77yzEe+cqLkbou/jBCpVJhU3dOBaCuNI4eO9aM0FDAky9SF8RKJz4RQ 12 | /QzVETQEgiiA7Ghl0u6L31Ag3V+7sxz5Nrv964fM8BBev6Na77HwvaZX++WKO9fK 13 | 91/cJEBISJmB9ncYzMnmGHcc2AMYWFZvV/PkCrbXuryg+4tdWKfRH+QjCN/Rh9K/ 14 | C3M1fKbjEdeDI0hsv9Lr5ffAygQH/lP3bDd7hG03Vks/x4xRtbDGV+FbxZSeUjv1 15 | gn7Cge08Opr+gX0Z8LoPrfbmsZu0NXTVKnFe28jtkGLuxmDvJHWjnpaFpVuPM1c2 16 | nv45Qudsy2RQ7B1MZFcnYX6jyY2rTQGRPL7aayBUQhTyIK9ZMEqB9+oFuZLvhAgb 17 | dVbKPbijpLfCR2WzUbmbqMgpD2/kpCeFMg9Ns1y0a0P7y+cPhEeGd7BNxH3MamFQ 18 | dGADxKaX/y+PECoMKcOZ1qrim0IGz4iB11FAJ+6KdZpXkz6F4Qo1lqeaGEa3cGfB 19 | C1zGK5+SkLSkB5OidCVFvfUYLaxFRecifcMx8eNhNrcmXbOpb9TdGnEb/097WhpN 20 | aEg9635BC0g3ab8APH4yQvm2ipq256T5hTpprf85aFiGq85DAc5gCzMN0IQmTf8G 21 | sXIyd9ecmEKhTcTtUPeIaea1UQxE+Zvi8PA0teIycltUxFc5g9ofjTBl5yszaNAO 22 | XRCEAfXVJefTe+k6jS2b2lRwQ0GSTmieyGa2b3g8Ya3bj+6JCXTSNtRONXXIn4tt 23 | 5G2HBCdtrZB9o6lrjBwOas93rzWWJxicIC51l0mfIkfJGnEgoEZyN5x0JNnBzS/q 24 | 7wEvAEOCCLa2b81J6fObdtrspu00UTIisAYpqm0oLOiFHevWSqRUkNPMc4m/hwga 25 | xoAfuUjzS0N0lD+VICQ4V9P56wQS75BC8s426vJBNwIlYkVOYwB2lLmAiJ4nej7e 26 | bCCAbGGdvOeHS8hUDEw0Jqs5f94y4vAGLkXAL+v/jwokUbdgupyYYLcOVPWXQXcg 27 | +W4tXIt7dmhnfj3DYyZu0w2tk9dRmStuu+C1q9tk1dEYhdBbl9rlv+BvEwsy9gC2 28 | XhagI8KtYkzaJs2IU/gk5Fef72lvbiKVyFlN/iMPRxkuscAeLm/C/15ITujZbdJV 29 | mc7pJprImyiWl7HvbuoMWgJwK0Q5gnmSOFAzKK9770OE/o53/Hnx+8TqJp7ESQJW 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.test.enc: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,E26A48DF348B1FB5FA27721B32F8FD2B 4 | 5 | 6X93Pczu0oBZiILEZZ1kaHWhleLukVtOu4sPHV4k3F0tpjLjbuVdkf5ebzrm2GyZ 6 | TKfSWHtvx0P2zSpqMZotwBC5PWwbNpjg+0gzuNvDQ1ro25W0g/A+4SWD5rBZJMGf 7 | itccIn30szAVFgtuhui7TliDojZVFkuN1d+hc6vOzFWueWfzyI6ebi5HdpNv/6lD 8 | svRCBV+mhwgdLN3ft7PkEQrska/ZKW+G2Z1VkcXR8CQnUfHlP4D3BGzhe3Hs902A 9 | 1Xpt7ir7RTJPyWxiPH2q2kVMSSAuVIDg/Nutte5R1j8FlnwtRj8p1JbjufoTDZUh 10 | cbpc+Qq/OoW16rMZkI4qpWf9FFG0JkBCzTM+T6H0fEjuyyM44jE5zSWBNva6luYq 11 | x5s/b+xvsrPTV9bsMHn5kW24pI/sylY/NlaumfAYeRbR4mOQfS07u9kiYZtudVcL 12 | Y0x87tjZNv5eXM0rQbC9nno3dr0vMp1XX+cXB4ye3saRastd2nonM3GhtKNw1nbP 13 | lEEZJVTMVKT2FiyOml3IGckgQgXYaDdfXXqplrRZvRpcrixzDbmzL9RBjNaGDOhZ 14 | DHd8xY5bxNuZ9mYYKirQwyn5x/Xwuq1OUte6efeZHxDPgC512lDhIZNSwW6H1Lzl 15 | Ifb+P6hHO6jRpPIVIqPWooI+RM1RtpwFgEL0AJhrbSVyYndLyeJB+coe+xgEM01a 16 | eo4O5lTEtQUZRG5jgNXp2ydiSqIujHJyHekS5lOhBCiTwMRI44V6bIoacKQnLGJx 17 | 7yP4HUxO1c9FalIZQ9Vna5c9Uzm5FWvOMwCoRkP0pRApQX/uTF2ayt931oRIVrmg 18 | pLx14Ae75wiA8in/6XddrvYGDXEen/EsDmVL5EwK9TN7Z1tUsm+X65Kdiqw6QJyM 19 | dGlpZlAb06z0exVfdZA+nHP8yxlgD1zs5t3C0z2fVtXxvkQCd7ixKtwSBbHX2Qsb 20 | iqOts/RszDjH3G2/EBaOTwDErwt7H+NHTFw9Sjc4Bf7TFjfe24W5Zu/AV4ZEIz32 21 | s0wYthxAJ9Tq+lAHb8JwLOSMQe9qG5/x9j1UELIudV32HnjHq74C9376AftpKUkg 22 | xl2RVHAnpmHvKqrjUTNl59c33RETk9NLnHwyNBbk1VStAgEFfyuHfaMiM7bB/HEI 23 | JT1DM5OWs9wPraZL1q1EcdcQZ0MziHgmZFA+fz2QLn8WgeBGDoaeTrYuNwoNCNgy 24 | GfACLsoujyiU49bLDtG+6bS99jE1jrMHZGBtrTIaQGnTkPo149arYrqOuxdu3oHI 25 | J4g7La/yZyCtvDuoKmm8+8FpdXOFfVbWrroNvNW+onLBYrfEP5iUOxmxEbowkHhJ 26 | 4D77vXQfPrBGJoK7DrYSmuO/egPaXcpUnmwZ7M4m/fCtZqM3l9xnDwnxHgatWjGW 27 | XJZpAAA4go8Bmw2EFiylop5OcXf3AUCnt+JnbN9wGbV8zYRRSO2QRwvNc8d5jNeF 28 | 7YJ481p7WJ7067Ph+wmEYPTU91Pt15A2REU6mW3zQKM2BYpbTymrN7ktUXp7VPCP 29 | j4vAwoPweCu/IatNsLv3xyjKFxDB84n05A/MYjojID2k8KJbjdUwq71J44WYbCzB 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.signature.enc: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,885A430F988B134A48EC0EFFDDE7C9CC 4 | 5 | H5ovCAPTSIhm91/0f+puhrOVXPsNRne9xeJNEGQx9AevmZ/gIQd9yPqQd2mX0cnQ 6 | wIq/1nljZtf0uZSCjxCfKSABLlAZV5D5Q/ByPQwmjBa4bcAh9pyZDOY3JDmbi/87 7 | TeqigtI0/t7ByH6+XP69m5Q574o/+wuTOgtikjdCqqXn/HBxxMK31vWbEeUZtupi 8 | LVD/RTRgXJlsHavufovbF3hOMTcRauTAp3bXN+PhrdiBLVBtDbdF51QrBBFe529N 9 | YuLrvrGw9CTtG5+vpGtPp7FlU5Pz3rmEgMJBKE0QbDiXdwt2KoqxZPUTKP6mypHV 10 | VGtqUlPg1bbnfrgZhTY0IA/qopCkCNA3g1yHV2jBx1Z9o9zP6ZlEJYHvV7Isztgl 11 | YCTapc3F8cxfnDxQsfRoOu/5Z4KiBpWZPhLDS1qowZ0gG6CyQH+u15J+zNrVBi9k 12 | 2Shs02zojTmaIQI/yt7GdonBiazr0OMdW3/rvN9mlqntId91To6ukaneOfkca9/v 13 | tmDq3uAPMdSqLevFaOwks0oZEqZVOSigKHqxjrlA2U6rTIg8h6dteFcuBHGblh85 14 | 5EiYPvI88ea/FyidYQDlQT/hYzVgokJKLhSWGjamvmHQL5GiFQCEh2d/GXku0hg8 15 | 2HWq+H+KSyFx2iU+R/jbXpdnGVjgJ1kIX/EpNOO/lNuGb3SODd6YgYT+GVToHhib 16 | 2QcxtKemrpaVPl5sR4dS87O+aFN9YsoELINsUiiCslNLtqKJ7B85g4YzWyKCDtoZ 17 | z9KFgBjT6GY3g1JvZYIBsrlJ73gCwYS8mNaOZ0rXhMn7k6HHlrBmUTUhIrt7gmrC 18 | OQ6i4bNIeXlRNzVcklQqc7hBRApQEwyO85vy0u0d2ODq0JCfrufW03q18VwmtUUK 19 | c2Yg4IFz+HLihVpTcp3tPAyC37dERwP951MgNLmXEepQCFf0Dh+YXXWvFXnhcESf 20 | atjHJRKAoUUl4AL/bx52gj6SXDVVbXniZvgL+kcgGQwIrqsIoAC/XrV9EXRHPXCR 21 | Uegz7jftEg57+3dwLgbjxGagw1gFWRdBEvd2IiM7OvXanAHlg8GM9P5939sNuv6t 22 | YM8QgklHNPHD4fb5ucp4XKKYGt59GbnXum4gDnv3IWUEzGv/H0oqtvHTynVFgQ8D 23 | iaL3O/AqFra1lJttjhVN9mJdMx9uBIQ/IyUkF71FzpYHX9luXC5LRgv85RUs5Dzo 24 | gvSX+wRymDXKxlS8Vi8RYUVf/PiSFLVCePMxkxBj59wRdSdfDdAb1aDMCgMwDMl1 25 | UqkH3zwNdX6YhVFYucs7nMn1IC3jMfnf/PeDpOLhbtLpAi3TGwsOYxe3E/q0up4+ 26 | v8iXuzl0b+dIzFQ9iwokN2fltOp+Uv8B3ETFqFIaG71AgauSvgUtUuVQgq9iLtIU 27 | 97x1pHpudmPK7esJhuIETc0XExMasqaucT7TRnE+x36c7traPH1ULXiQvdHvEbiW 28 | h++7HAfiWYTLiyZlPSKP48hJWVEfXPua2nDYicLOHn7XkocXYp5/ws9kdgP9wIPj 29 | ARiWDFARQsz35dbm9SMaAYQsl+n0q+vqUxUQCgNEF2hUuiogQE3pAUUnBQhRBDq7 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /spec/lib/chamber/commands/unsecure_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/commands/unsecure' 5 | require 'fileutils' 6 | 7 | module Chamber 8 | module Commands 9 | describe Unsecure do # rubocop:disable RSpec/MultipleMemoizedHelpers 10 | let(:rootpath) { Pathname.new(::File.expand_path('./spec/fixtures')) } 11 | let(:settings_directory) { rootpath + 'settings' } 12 | let(:settings_filename) { settings_directory + 'encrypted.yml' } 13 | let(:options) do 14 | { 15 | basepath: rootpath, 16 | rootpath: rootpath, 17 | decryption_keys: rootpath + '../spec_key', 18 | shell: double.as_null_object, # rubocop:disable RSpec/VerifiedDoubles 19 | } 20 | end 21 | 22 | before(:each) do 23 | ::FileUtils.mkdir_p settings_directory unless ::File.exist? settings_directory 24 | end 25 | 26 | after(:each) do 27 | ::FileUtils.rm_rf(settings_directory) if ::File.exist? settings_directory 28 | end 29 | 30 | it 'can return values formatted as environment variables' do 31 | settings_filename.write <<~HEREDOC 32 | test: 33 | _secure_my_encrypted_setting: YDCCivOY8rcqYmM7OMzqSQL3hRZlySGVklad7Ouk3a4r4aJk4nLa/u+vE316CNvtJF5uP+FJ6lCf4s5w4hd9/hmdgzZQ+CVGgzB3iSP4IqZuL+hsLW994BuUSk3iv1X2Bv1t/3I5BOLtbgMeZrgzUFjWYEbPcfCi09RrbXFYekiAghP6tPybcPp6yTc8sS3cVty4cAcjfKB3POZQ95htyqtM97sQ78wtJcftCBz/9XkT9aFHtOicXwurV4uaSan6LBV5D419/a/Aqij2PMe6FJ+65Xo1wf6V61AIqg9600M28r5/36tYwVEJdk0x4ka2Ijs3JeOcwN8cekemx/2lsA== 34 | _secure_my_unencrypted_setting: goodbye 35 | HEREDOC 36 | 37 | Unsecure.call(**options) 38 | 39 | expect(settings_filename.read) 40 | .to eql <<~HEREDOC 41 | test: 42 | _secure_my_encrypted_setting: hello 43 | _secure_my_unencrypted_setting: goodbye 44 | HEREDOC 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.development.enc: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,C3D6EACCB614682EE65CE6C34A300A23 4 | 5 | spovgs6dVoi9rCGVRFlrvrsjWKeyunak3btfGgmYAeD3kkVin2Tsv/WTtoU8BRgZ 6 | +Jzt0u8ux4IgMHPD+PHGRLz+MKGHK8st2w8cF0YHwuoLtw3+r8kDAav/Quiftpre 7 | BjAcG+T+W4FO8MKmK86fUwDnjKPACNNwiZoeEQ9VeyhIWCpPBcB3NMLK6nLB0Cu1 8 | B8Bap79TWhE4bv3APAmBy+azPjHNLLXqsD7vTmvINtzyKggglvusXyvqg+XDBu78 9 | qm/sBf5cM6IG7d3+0b6GW0ZIZ1Xp7aDqV3xo0+T2S8C17doHVNwVzoCJGBhyzb5F 10 | jk7mWNev97cyJSgUTaUHhywMsWebVz1My01p+mzB7ESA7CAxh3uvYiA9kW2APTVe 11 | ZWSMiXO6DNDSCjcOISpmC+WiMk6o+FSCjXZmfKgPY9DfAbjfZTKynvqU/hh6TeqU 12 | HTs4Fs6h/IuV4e2O4MfIrOVw5UHDu3E6eFB1oT4AyofQPEePjZ5RvhMl/AAzCvaV 13 | 17lziDLaBKxXYawIMs9vE9lnzNVOpVOA5y5cXAAgzWtjNnSa2MWI/M2p6POOC0mP 14 | uf6zm9/NSn9iaHlHWC45MxoQ4O7eJ2tbUuo1wXUwYK2IP/nU0odMEYbvZquLtjm/ 15 | Myey21E5PmBHdrYNmfTE2FBvPpQuUAmpIi8FwegnkOFA9ttJhEcEUxie0DaL71ry 16 | DdNTM2IpdJKOeHdg5iYR2R2kBE+jn5jSjU1OwWIikzwpypV7albn1sHcDgSZ4Nsh 17 | 73yJFYXxb7OwAHliDF7QqNTbS8C+p1bRw7TxZQavsEG1xaEzgHWthF+evJQlThkv 18 | MzcpXRRgA0pQRGugiIt357l3V7WksTtWDmCGdsJ9Byyhumd8urHVjNwaPmQP3Lc/ 19 | NX3p/XnQTnlXE2aKjHa9zDu7/JZ4b2N1IG10dzJply4gQYnLXfNfqtWxmhhApuWD 20 | WfTby7p1A2DYEF0b6uV2hVMDlhNFr/HtXMAUuBguq4+wRZgMR/0463xIevVPpjxy 21 | gQstUZu9teZQV4wzf+8LxeKVDiWtFiEwL7afDHkhAYobeJsCoCRLxFzdQcev11Hg 22 | hOCncuBr43xTQ5OMIFpKOMZBSu7+vbwV7MnzrFlvBQ4lQJMcH/Quu0OiXsSiRs12 23 | AO2NT4O4UoiGisn23ITwm2kFlK2DfMaCaxn0DnHxrA79aHGSA0W1GtVjQ/VwODt9 24 | /403r7ZYVJMVnadh6tj8sp4pX+blkcWQ4XjRNusBrozxBaU2WgjWkaBwIg5JqAOt 25 | 3dWPyDt2BGLGXN7kusuuwJn2xE/h0KXn5FtsJjz2tfZ9+G/gJ3pxmubKfpF+RzFY 26 | CmrojqkgTQ0eLxoqFfk7ImiCkO7qrJypQF84VCjou/zYOom9smhauZF0QFoHqp/u 27 | ktGc5mWYx2EnT4mKhc0COLYY1Ksoq2ucRV+qO3LGN3rvuNbZ7ZTjznUHr1fGBEF9 28 | BTmbv/A0WhGi/fGGzXUvXVVd605uHFBwlQyF+mqmOkh16AnqVkxD8neC7yYQn6i6 29 | P2RY8NynHR2H9bxxuvXltbTIbCmY2fnJFAQE7LgXJNW7mIkWhSrHH9afIr0nwcFD 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /spec/fixtures/keys/real/.chamber.production.enc: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,1EBCEC96C8D28B08FEAEF6AAEE7F9FAD 4 | 5 | wl2T+NaBqCGSB7YR/6QrYIM4R4TscfcexhoxClDjfl2PijTgpTPd22os47+0x2/S 6 | TAV+wAiKBA1lMEwavFDsDWsQZW9+/f34RCIEyzp1sRIZZFYLrW32jeCupMj9x2DK 7 | GmHK1b6kR2tjXchM4kQtmRNq1vzH8DegmEFXzHRuuTkwUZEVoCveBvBPH4isZ2yB 8 | t/ij7NOvM6h8eBSyRNR0gnCGC+QUUdy9A4dQvcz8DV5efivFvebI117ZDmIgalz6 9 | mPBbhnyLKwd1v3cMwkfxLIpsbCokd+c3TFzXTf0Lu+Vl3vX23TQO/NdJs9H6cMKU 10 | YaByGnwrZr16Tw51D15D7MslZ7HnWm5WFdVMujDNhpmGqDwaNlBmpWNjAXIitKnA 11 | EqoMs8JHC6HlJC1IYoHqv4JgCQ0T6akN+M9/f/C+/07SBu/YkmQkGFxYGrEu3zFc 12 | XLGj5Q6soJFnls1GDLNCPFct0Cr1/FLAh0lSKowL0ArWssfm0MCPlfPxxVuZC3f1 13 | 9DSQFye3wCV4d3j7h7V8f8WH5dsIoe9naamUMkgGbL/RYMn5h7jNdjJQGD0rn7wF 14 | cTJs2bTicpZin5nLmbnKBE6zIb2mPuEunh1zJon8v3nAyZozgxfdVg4XFzZapcHO 15 | NEY9nHBIv9CqVtumjj+sXn9HzJawod5g3iAwMg3fv6uO5cCuTdPnF2Jzuf3FY0b4 16 | Nl1RGj6BngswgIWvCVGui5kt8SZit73MfIH8ct5XQA6WJfmNnij0wR1vELDEYG8v 17 | Og/bgSWfBNSywuwafc0MZcieV8FMroRN5cAjYBmructTZqP5fQCwm/gWpg0jxqNi 18 | zJ06mSxHA0DEHEVCUbvrdx3fK0Vw2nwaEvpgIBxrCtBUzHoOq9Fi5qSSo8aMIa2A 19 | y9ehosjK3jkWbq2RHXCELJdYRgXuiGp3q+lFdil9WBkAoooFkZIgowz9drOhqCUO 20 | yJiTFb+/MzbSqt1TMjaIu1COXXQ3YzkJiRtZCnqmfrV/80BTr5OlD9mbKu80uDxr 21 | Y5UvMUqxx5WPDZSHzUaABC61qfPvpuu9Z+td1HV+XUe8cxM7rJBIslg3JctZ0W0D 22 | bw/+5OFZ+UH0D/UijLl7orU4qNy25MQvafEWT8RYKbC50mky+S8ivzSSdWum+iu2 23 | GR68ksSrACSdP6QkA8RHVR9ZMG3khBkGbyKEUJJz8fbP2qtsfVWPS1cJVkFvhJu4 24 | njyI8KBcEssn99vOstmJSQPipYGz+kb1IB4/ZfmpiND8j7thjtH6VQSJnrCXl4hX 25 | CaELLzkSnidfoeZ7Jni6Val5YDIujZ5TnO+xneFkBy8awB0ptIg8PSFjEMOXUpWq 26 | 96map124oOwJABry9PO6/2MtYW6MWpldKCStcwztj0731FZU+haybGSh9rogN360 27 | QQcORwF/ZGHOgC9W2Ex8lOAha9and+4aEAkUnkQX8IuVyrpF/Sb/JazRMK02cJCd 28 | xe2vQimGtRfjXA/Xoz4sWdgiXKzRURL13f0GyGZZApN2dMSkRUUp5fuFJKTdeyQt 29 | uuvu717zCjQ+vVpWNRz2jFdTAlVvt5ptvUp5wEe5A4Que+jKsYgkc8g/YOjeREZ1 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /lib/chamber/commands/securable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'shellwords' 4 | require 'chamber/instance' 5 | 6 | module Chamber 7 | module Commands 8 | module Securable 9 | def initialize(only_sensitive: nil, **args) 10 | super(**args) 11 | 12 | ignored_settings_options = args 13 | .merge(files: ignored_settings_filepaths) 14 | .reject { |k, _v| k == 'basepath' } 15 | self.ignored_settings_instance = Chamber::Instance.new(**ignored_settings_options) 16 | self.current_settings_instance = Chamber::Instance.new(**args) 17 | self.only_sensitive = only_sensitive 18 | end 19 | 20 | protected 21 | 22 | attr_accessor :only_sensitive, 23 | :ignored_settings_instance, 24 | :current_settings_instance 25 | 26 | def securable_environment_variables 27 | if only_sensitive 28 | securable_settings.to_environment 29 | else 30 | current_settings.to_environment 31 | end 32 | end 33 | 34 | def insecure_environment_variables 35 | securable_settings.insecure.to_environment 36 | end 37 | 38 | def securable_settings 39 | ignored_settings.merge(current_settings.securable) 40 | end 41 | 42 | def current_settings 43 | current_settings_instance.settings 44 | end 45 | 46 | def ignored_settings 47 | ignored_settings_instance.settings 48 | end 49 | 50 | def ignored_settings_filepaths 51 | shell_escaped_chamber_filenames = chamber.filenames.map do |filename| 52 | Shellwords.escape(filename) 53 | end 54 | 55 | `git ls-files --other --ignored --exclude-per-directory=.gitignore` 56 | .split("\n") 57 | .map { |filename| "#{Shellwords.escape(rootpath.to_s)}/#{filename}" } 58 | .select { |filename| shell_escaped_chamber_filenames.include?(filename) } 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ================================================================================ 3 | 4 | We love pull requests from everyone. By participating in this project, you 5 | agree to abide by the code of conduct. 6 | 7 | Here are some ways **you** can contribute: 8 | 9 | * by using alpha, beta, and prerelease versions 10 | * by reporting bugs 11 | * by suggesting new features 12 | * by writing or editing documentation 13 | * by writing specifications 14 | * by writing code 15 | * by fixing styling inconsistencies 16 | * by refactoring code 17 | * by closing issues 18 | * by reviewing patches 19 | 20 | Submitting an Issue 21 | -------------------------------------------------------------------------------- 22 | 23 | We use the GitHub issue tracker to track bugs and features. Before submitting 24 | a bug report or feature request, check to make sure it hasn't already been 25 | submitted. When submitting a bug report, please include a [Gist][gist] that 26 | includes a stack trace and any details that may be necessary to reproduce the 27 | bug. Ideally, a bug report should include a pull request with failing specs. 28 | 29 | Submitting a Pull Request 30 | -------------------------------------------------------------------------------- 31 | 32 | 1. [Fork][fork] the official repository. 33 | 2. [Create a topic branch.][branch] 34 | 3. Implement your feature or bug fix. 35 | 4. Add, commit, and push your changes. 36 | 5. [Submit a pull request.][pr] 37 | 38 | Notes 39 | -------------------------------------------------------------------------------- 40 | 41 | * Please add tests if you changed code. Contributions without tests won't be 42 | accepted. 43 | 44 | Inspired by 45 | 46 | [branch]: https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/ 47 | [fork]: https://help.github.com/articles/fork-a-repo/ 48 | [gist]: https://gist.github.com/ 49 | [pr]: https://help.github.com/articles/using-pull-requests/ 50 | -------------------------------------------------------------------------------- /.github/workflows/locking.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: {} 7 | 8 | name: "Issue Locking" 9 | jobs: 10 | lock-issues: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - name: "Lock Issues" 14 | uses: "dessant/lock-threads@v4" 15 | with: 16 | github-token: "${{ secrets.GITHUB_TOKEN }}" 17 | issue-lock-inactive-days: "180" 18 | issue-exclude-created-before: "" 19 | issue-exclude-labels: "outdated,on-hold,in-progress,watchlist" 20 | issue-lock-labels: "outdated" 21 | issue-lock-comment: > 22 | This issue has been automatically locked since there has not been 23 | any recent activity after it was closed. Please open a new issue 24 | for related bugs. 25 | issue-lock-reason: "resolved" 26 | process-only: "issues" 27 | 28 | lock-pull-requests: 29 | runs-on: "ubuntu-latest" 30 | steps: 31 | - name: "Lock Pull Requests" 32 | uses: "dessant/lock-threads@v4" 33 | with: 34 | github-token: "${{ secrets.GITHUB_TOKEN }}" 35 | pr-lock-inactive-days: "180" 36 | pr-exclude-created-before: "" 37 | pr-exclude-labels: "outdated,on-hold,in-progress,watchlist" 38 | pr-lock-labels: "outdated" 39 | pr-lock-comment: > 40 | This issue has been automatically locked since there has not been 41 | any recent activity after it was closed. Please open a new issue 42 | for related bugs. 43 | pr-lock-reason: "resolved" 44 | process-only: "prs" 45 | -------------------------------------------------------------------------------- /spec/lib/chamber/commands/sign_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'securerandom' 5 | require 'fileutils' 6 | require 'chamber/commands/sign' 7 | 8 | module Chamber 9 | module Commands 10 | describe Sign do # rubocop:disable RSpec/MultipleMemoizedHelpers 11 | let(:rootpath) { Pathname.new(::File.expand_path("./tmp/fixtures-#{SecureRandom.uuid}")) } 12 | let(:settings_directory) { rootpath + 'settings' } 13 | let(:settings_filename) { settings_directory + 'settings.yml' } 14 | let(:signature_filename) { settings_directory + 'settings.sig' } 15 | let(:options) do 16 | { 17 | basepath: rootpath, 18 | rootpath: rootpath, 19 | decryption_keys: rootpath + '../../spec/fixtures/keys/real/.chamber.signature.pem', 20 | shell: double.as_null_object, # rubocop:disable RSpec/VerifiedDoubles 21 | signature_name: 'Suzy Q Robinson', 22 | } 23 | end 24 | 25 | before(:each) do 26 | ::FileUtils.mkdir_p settings_directory unless ::File.exist? settings_directory 27 | end 28 | 29 | after(:each) do 30 | ::FileUtils.rm_rf(settings_directory) if ::File.exist? settings_directory 31 | end 32 | 33 | it 'can generate signature files', :time_mock do 34 | settings_filename.write <<~HEREDOC 35 | test: 36 | my_setting: hello 37 | HEREDOC 38 | 39 | Sign.call(**options) 40 | 41 | expect(signature_filename.read).to eql(<<~HEREDOC) 42 | Signed By: Suzy Q Robinson 43 | Signed At: 2012-07-26T18:00:00Z 44 | 45 | -----BEGIN CHAMBER SIGNATURE----- 46 | QhGPAea/1RQZnh8ES+Esmr3ZssBtZJvxp+yW7wUMHc2D5Mq9SzLymuwSxLtOGuJsqlxMWW0FaOIK1F0AcQRnw9+RXfdGvBNsm/5LJr1TYJ9EfAKFY/PPDpnMId6gJV/Tz+y5sOt97oyUXVqDbd6jbwmJvYWNfYYTmI1NunkRRNtLuS83hce+qJLPhmYqnHEkWvbcczkjml/axfh5l5VS8aob9zfXnHryMoaCu2E/yfZOsXDEXVLVAGid33eq719Wm/nK2R4hhgRMrm7+4kfGSQyluOAobgvU3jspKJZO7tLH3uXYxqTVG9ZldEc8tRlP79QjSwJdWLoLmwL+bnAjIQ== 47 | -----END CHAMBER SIGNATURE----- 48 | HEREDOC 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck disable=SC2037 3 | 4 | ### 5 | # Install Bundler Source Credentials 6 | # 7 | declare private_url="$(command chamber show --as-env --files="config/settings/**/*.yml" | grep 'GEMFURY_URL' | perl -p -e 's/GEMFURY_URL="(.*)"/\1/')" 8 | declare private_token="$(command chamber show --as-env --files="config/settings/**/*.yml" | grep 'GEMFURY_TOKEN' | perl -p -e 's/GEMFURY_TOKEN="(.*)"/\1/')" 9 | 10 | if [ -n "${private_url}" ] && [ -n "${private_token}" ]; then 11 | bundle config --local "${private_url}" "${private_token}" 12 | else 13 | bundle config --delete "${private_url}" 14 | fi 15 | 16 | ### 17 | # Install Dependencies 18 | # 19 | echo 'Installing Ruby Packages...' 20 | gem install bundler --conservative 21 | bundle check || bundle install 22 | 23 | gem install rubocop --conservative 24 | 25 | echo 'Installing NPM Packages...' 26 | command npm install -g "jsonlint" 2> /dev/null 27 | command npm update -g "jsonlint" 2> /dev/null 28 | 29 | nodenv rehash 2> /dev/null 30 | 31 | ### 32 | # Install Project bin Executables 33 | # 34 | echo 'Installing project binaries...' 35 | if ! [ -d ".git/shellwreck-verification-dir" ]; then 36 | mkdir .git/shellwreck-verification-dir 37 | fi 38 | 39 | ### 40 | # Add Project Information to the Git Repository 41 | # 42 | echo 'Installing project configuration into git repo...' 43 | command git config --replace-all --local project.application-name 'chamber' 44 | 45 | command git config --replace-all --local deployment.pipeline 'rubygems' 46 | command git config --replace-all --local deployment.profile 'thekompanee' 47 | 48 | command git config --replace-all --local workflow.issue-tracker 'github' 49 | 50 | ### 51 | # Setup Git Hook Templates 52 | # 53 | gem install overcommit --conservative 54 | 55 | if [ -d "${HOME}/.shellwreck/plugins/git/symlinks/hooks" ]; then 56 | rm --recursive --force .git/hooks 57 | ln -s "${HOME}/.shellwreck/plugins/git/symlinks/hooks" .git/hooks 58 | fi 59 | -------------------------------------------------------------------------------- /spec/lib/chamber/commands/show_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/commands/show' 5 | 6 | module Chamber 7 | module Commands 8 | describe Show do 9 | let(:rootpath) { ::File.expand_path('./spec/fixtures') } 10 | let(:options) do 11 | { 12 | basepath: rootpath, 13 | rootpath: rootpath, 14 | namespaces: %w{test}, 15 | decryption_keys: './spec/spec_key', 16 | } 17 | end 18 | 19 | it 'can return values formatted as environment variables' do 20 | expect(Show.call(**options.merge(as_env: true))) 21 | .to eql( 22 | <<~HEREDOC.chomp) 23 | ANOTHER_LEVEL_LEVEL_THREE_A_SCALAR="hello" 24 | ANOTHER_LEVEL_LEVEL_THREE_AN_ARRAY="["item 1", "item 2", "item 3"]" 25 | ANOTHER_LEVEL_SETTING_ONE="1" 26 | ANOTHER_LEVEL_SETTING_TWO="2" 27 | MY_BOOLEAN="false" 28 | MY_DYNAMIC_SETTING="2" 29 | MY_SECURE_SETTINGS="my_secure_value" 30 | MY_SETTING="my_value" 31 | HEREDOC 32 | end 33 | 34 | it 'can return values filtered by whether or not they are secure' do 35 | expect(Show.call(**options.merge(as_env: true, only_sensitive: true))) 36 | .to eql( 37 | <<~HEREDOC.chomp) 38 | MY_SECURE_SETTINGS="my_secure_value" 39 | HEREDOC 40 | end 41 | 42 | it 'can return values formatted as a hash' do 43 | expect(Show.call(**options)) 44 | .to eql( 45 | <<~HEREDOC.chomp) 46 | {"my_setting"=>"my_value", 47 | "my_secure_settings"=>"my_secure_value", 48 | "my_boolean"=>"false", 49 | "my_dynamic_setting"=>2, 50 | "another_level"=> 51 | {"setting_one"=>1, 52 | "setting_two"=>2, 53 | "level_three"=> 54 | {"an_array"=>["item 1", "item 2", "item 3"], 55 | "a_scalar"=>"hello"}}} 56 | HEREDOC 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | rules: 4 | braces: 5 | min-spaces-inside: 1 6 | max-spaces-inside: 1 7 | min-spaces-inside-empty: 0 8 | max-spaces-inside-empty: 0 9 | brackets: 10 | min-spaces-inside: 0 11 | max-spaces-inside: 0 12 | min-spaces-inside-empty: 0 13 | max-spaces-inside-empty: 0 14 | colons: 15 | max-spaces-before: 0 16 | max-spaces-after: -1 17 | commas: 18 | max-spaces-before: 0 19 | min-spaces-after: 1 20 | max-spaces-after: 1 21 | comments: 22 | require-starting-space: true 23 | min-spaces-from-content: 1 24 | comments-indentation: "enable" 25 | document-end: 26 | present: false 27 | document-start: 28 | present: true 29 | empty-lines: 30 | max: 1 31 | max-start: 0 32 | max-end: 0 33 | empty-values: 34 | forbid-in-block-mappings: true 35 | forbid-in-flow-mappings: true 36 | hyphens: 37 | max-spaces-after: 1 38 | indentation: 39 | spaces: 2 40 | indent-sequences: true 41 | check-multi-line-strings: false 42 | key-duplicates: "enable" 43 | key-ordering: "disable" 44 | line-length: 45 | max: 90 46 | allow-non-breakable-words: true 47 | allow-non-breakable-inline-mappings: true 48 | new-line-at-end-of-file: "enable" 49 | new-lines: 50 | type: "unix" 51 | octal-values: 52 | forbid-implicit-octal: true 53 | forbid-explicit-octal: true 54 | trailing-spaces: "enable" 55 | -------------------------------------------------------------------------------- /lib/chamber/keys/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chamber 4 | module Keys 5 | class Base 6 | attr_accessor :rootpath 7 | attr_reader :filenames, 8 | :namespaces 9 | 10 | def self.resolve(**args) 11 | new(**args).resolve 12 | end 13 | 14 | def initialize(rootpath:, namespaces:, filenames: nil) 15 | self.rootpath = Pathname.new(rootpath) 16 | self.namespaces = namespaces 17 | self.filenames = filenames 18 | end 19 | 20 | def resolve 21 | key_paths.each_with_object({}) do |path, memo| 22 | namespace = namespace_from_path(path) || '__default' 23 | value = if path.readable? 24 | path.read 25 | else 26 | ENV.fetch(environment_variable_from_path(path), nil) 27 | end 28 | 29 | memo[namespace.downcase.to_sym] = value if value 30 | end 31 | end 32 | 33 | def as_environment_variables 34 | key_paths.select(&:readable?).each_with_object({}) do |path, memo| 35 | memo[environment_variable_from_path(path)] = path.read 36 | end 37 | end 38 | 39 | private 40 | 41 | def key_paths 42 | @key_paths = (filenames.any? ? filenames : [default_key_file_path]) + 43 | namespaces.map { |n| namespace_to_key_path(n) } 44 | end 45 | 46 | # rubocop:disable Performance/MapCompact 47 | def filenames=(other) 48 | @filenames = Array(other) 49 | .map { |o| Pathname.new(o) } 50 | .compact 51 | end 52 | # rubocop:enable Performance/MapCompact 53 | 54 | def namespaces=(other) 55 | @namespaces = other + %w{signature} 56 | end 57 | 58 | def namespace_from_path(path) 59 | path 60 | .basename 61 | .to_s 62 | .match(self.class::NAMESPACE_PATTERN) { |m| m[1].upcase } 63 | end 64 | 65 | def namespace_to_key_path(namespace) 66 | rootpath + ".chamber.#{namespace.to_s.tr('.-', '')}#{key_filename_extension}" 67 | end 68 | 69 | def default_key_file_path 70 | Pathname.new(rootpath + ".chamber#{key_filename_extension}") 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/chamber.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/rubinius_fix' 4 | require 'chamber/instance' 5 | require 'chamber/rails' 6 | 7 | module Chamber 8 | attr_writer :instance 9 | 10 | def load(**args) 11 | self.instance = Instance.new(**args) 12 | end 13 | 14 | def instance 15 | @instance ||= Instance.new 16 | end 17 | 18 | def [](key) 19 | instance.[](key) 20 | end 21 | 22 | def dig!(*args) 23 | instance.dig!(*args) 24 | end 25 | 26 | def dig(*args) 27 | instance.dig(*args) 28 | end 29 | 30 | def configuration 31 | instance.configuration 32 | end 33 | 34 | def decrypt(value, **args) 35 | instance.decrypt(value, **args) 36 | end 37 | 38 | def encrypt(value, **args) 39 | instance.encrypt(value, **args) 40 | end 41 | 42 | def files 43 | instance.files 44 | end 45 | 46 | def filenames 47 | instance.filenames 48 | end 49 | 50 | def namespaces 51 | instance.namespaces 52 | end 53 | 54 | def secure 55 | instance.secure 56 | end 57 | 58 | def sign 59 | instance.sign 60 | end 61 | 62 | def verify 63 | instance.verify 64 | end 65 | 66 | def to_environment 67 | instance.to_environment 68 | end 69 | 70 | def to_hash 71 | instance.to_hash 72 | end 73 | 74 | def to_s(**args) 75 | return '' unless @instance 76 | 77 | instance.to_s(**args) 78 | end 79 | 80 | module_function :[], 81 | :configuration, 82 | :decrypt, 83 | :dig!, 84 | :dig, 85 | :encrypt, 86 | :filenames, 87 | :files, 88 | :instance, 89 | :instance=, 90 | :load, 91 | :namespaces, 92 | :respond_to_missing?, 93 | :secure, 94 | :sign, 95 | :to_environment, 96 | :to_hash, 97 | :to_s, 98 | :verify 99 | end 100 | -------------------------------------------------------------------------------- /spec/lib/chamber/commands/verify_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'securerandom' 5 | require 'fileutils' 6 | require 'chamber/commands/verify' 7 | 8 | module Chamber 9 | module Commands 10 | describe Verify do # rubocop:disable RSpec/MultipleMemoizedHelpers 11 | let(:rootpath) { Pathname.new(::File.expand_path("./tmp/fixtures-#{SecureRandom.uuid}")) } 12 | let(:settings_directory) { rootpath + 'settings' } 13 | let(:settings_filename) { settings_directory + 'settings.yml' } 14 | let(:signature_filename) { settings_directory + 'settings.sig' } 15 | let(:options) do 16 | { 17 | basepath: rootpath, 18 | rootpath: rootpath, 19 | encryption_keys: rootpath + 20 | '../../spec/fixtures/keys/real/.chamber.signature.pub.pem', 21 | shell: double.as_null_object, # rubocop:disable RSpec/VerifiedDoubles 22 | } 23 | end 24 | 25 | before(:each) do 26 | ::FileUtils.mkdir_p settings_directory unless ::File.exist? settings_directory 27 | end 28 | 29 | after(:each) do 30 | ::FileUtils.rm_rf(settings_directory) if ::File.exist? settings_directory 31 | end 32 | 33 | it 'can generate signature files', :time_mock do 34 | settings_filename.write <<~HEREDOC 35 | test: 36 | my_setting: hello 37 | HEREDOC 38 | 39 | signature_filename.write <<~HEREDOC 40 | Signed By: Suzy Q Robinson 41 | Signed At: 2012-07-26T18:00:00Z 42 | 43 | -----BEGIN CHAMBER SIGNATURE----- 44 | QhGPAea/1RQZnh8ES+Esmr3ZssBtZJvxp+yW7wUMHc2D5Mq9SzLymuwSxLtOGuJsqlxMWW0FaOIK1F0AcQRnw9+RXfdGvBNsm/5LJr1TYJ9EfAKFY/PPDpnMId6gJV/Tz+y5sOt97oyUXVqDbd6jbwmJvYWNfYYTmI1NunkRRNtLuS83hce+qJLPhmYqnHEkWvbcczkjml/axfh5l5VS8aob9zfXnHryMoaCu2E/yfZOsXDEXVLVAGid33eq719Wm/nK2R4hhgRMrm7+4kfGSQyluOAobgvU3jspKJZO7tLH3uXYxqTVG9ZldEc8tRlP79QjSwJdWLoLmwL+bnAjIQ== 45 | -----END CHAMBER SIGNATURE----- 46 | HEREDOC 47 | 48 | result = Verify.call(**options) 49 | 50 | expect(result).to eql( 51 | 'settings/settings.yml' => true, 52 | ) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/chamber/types/secured.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/json' 4 | require 'chamber' 5 | 6 | module Chamber 7 | begin 8 | require 'active_record/type/value' 9 | 10 | CHAMBER_TYPE_VALUE_SUPERCLASS = ActiveRecord::Type::Value 11 | rescue LoadError # rubocop:disable Lint/SuppressedException 12 | end 13 | 14 | begin 15 | require 'active_model/type/value' 16 | 17 | CHAMBER_TYPE_VALUE_SUPERCLASS = ActiveModel::Type::Value 18 | rescue LoadError # rubocop:disable Lint/SuppressedException 19 | end 20 | 21 | module Types 22 | class Secured < CHAMBER_TYPE_VALUE_SUPERCLASS 23 | attr_accessor :decryption_keys, 24 | :encryption_keys 25 | 26 | def initialize(decryption_keys: ::Chamber.configuration.decryption_keys, 27 | encryption_keys: ::Chamber.configuration.encryption_keys) 28 | self.decryption_keys = decryption_keys 29 | self.encryption_keys = encryption_keys 30 | 31 | super() 32 | end 33 | 34 | def type 35 | :jsonb 36 | end 37 | 38 | def cast(value) 39 | case value 40 | when Hash 41 | value 42 | when String 43 | ::JSON.parse(value) 44 | when NilClass 45 | nil 46 | else 47 | fail ArgumentError, 'Any attributes encrypted with Chamber must be either a Hash or a valid JSON string' 48 | end 49 | end 50 | alias type_cast_from_user cast 51 | 52 | def deserialize(value) 53 | value = cast(value) 54 | 55 | return if value.nil? 56 | 57 | Chamber.decrypt(value, 58 | decryption_keys: decryption_keys, 59 | encryption_keys: encryption_keys) 60 | end 61 | alias type_cast_from_database deserialize 62 | 63 | def serialize(value) 64 | fail ArgumentError, 'Any attributes encrypted with Chamber must be a Hash' unless value.is_a?(Hash) 65 | 66 | ::JSON.dump( 67 | ::Chamber.encrypt(value, 68 | decryption_keys: decryption_keys, 69 | encryption_keys: encryption_keys), 70 | ) 71 | end 72 | alias type_cast_for_database serialize 73 | 74 | def changed_in_place?(raw_old_value, new_value) 75 | deserialize(raw_old_value) == new_value 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | Current Behavior 8 | -------------------------------------------------------------------------------- 9 | 10 | 11 | ### Steps to Reproduce 12 | 13 | 14 | 1. Sign in as "marty@hillvalley.net" 15 | 2. Click "Current Orders" 16 | 3. Try to remove "Flux Capacitor" from the cart 17 | 18 | ### Screenshot 19 | 20 | 24 | 25 | Expected Behavior 26 | -------------------------------------------------------------------------------- 27 | 28 | 29 | Environment 30 | -------------------------------------------------------------------------------- 31 | 32 | * Device: 33 | 34 | * OS: 35 | 36 | * [ ] Windows 37 | * [ ] macOS 38 | * [ ] iOS 39 | * [ ] Android 40 | * [ ] Linux 41 | * [ ] Other 42 | 43 | * OS Version: 44 | 45 | * Browser: 46 | 47 | * [ ] Chrome 48 | * [ ] Safari 49 | * [ ] Firefox 50 | * [ ] Internet Explorer 51 | * [ ] Edge 52 | * [ ] Opera 53 | 54 | * Browser Version: 55 | 56 | 78 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Code of Conduct 2 | ================================================================================ 3 | 4 | As contributors and maintainers of this project, and in the interest of 5 | fostering an open and welcoming community, we pledge to respect all people who 6 | contribute through reporting issues, posting feature requests, updating 7 | documentation, submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project a harassment-free 10 | experience for everyone, regardless of level of experience, gender, gender 11 | identity and expression, sexual orientation, disability, personal appearance, 12 | body size, race, ethnicity, age, religion, or nationality. 13 | 14 | Examples of unacceptable behavior by participants include: 15 | 16 | * The use of sexualized language or imagery 17 | 18 | * Personal attacks 19 | 20 | * Trolling or insulting/derogatory comments 21 | 22 | * Public or private harassment 23 | 24 | * Publishing other's private information, such as physical or electronic 25 | addresses, without explicit permission 26 | 27 | * Other unethical or unprofessional conduct. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject 30 | comments, commits, code, wiki edits, issues, and other contributions that are 31 | not aligned to this Code of Conduct. By adopting this Code of Conduct, project 32 | maintainers commit themselves to fairly and consistently applying these 33 | principles to every aspect of managing this project. Project maintainers who do 34 | not follow or enforce the Code of Conduct may be permanently removed from the 35 | project team. 36 | 37 | This code of conduct applies both within project spaces and in public spaces 38 | when an individual is representing the project or its community. 39 | 40 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 41 | reported by opening an issue or contacting one or more of the project 42 | maintainers. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][1], version 45 | 1.2.0, available at [here][2]. 46 | 47 | [1]: http://contributor-covenant.org 48 | [2]: http://contributor-covenant.org/version/1/2/0/ 49 | -------------------------------------------------------------------------------- /lib/chamber/instance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chamber/configuration' 4 | require 'chamber/file_set' 5 | require 'chamber/settings' 6 | 7 | module Chamber 8 | class Instance 9 | attr_accessor :configuration, 10 | :files 11 | 12 | def initialize(**args) 13 | self.configuration = Configuration.new(**args) 14 | self.files = FileSet.new(**configuration.to_hash) 15 | end 16 | 17 | def settings 18 | @settings ||= files.to_settings { |settings| @settings = settings } 19 | end 20 | 21 | def [](key) 22 | settings.[](key) 23 | end 24 | 25 | def dig!(*args) 26 | settings.dig!(*args) 27 | end 28 | 29 | def dig(*args) 30 | settings.dig(*args) 31 | end 32 | 33 | def filenames 34 | files.filenames 35 | end 36 | 37 | def secure 38 | files.secure 39 | end 40 | 41 | def unsecure 42 | files.unsecure 43 | end 44 | 45 | def sign 46 | files.sign 47 | end 48 | 49 | def verify 50 | files.verify 51 | end 52 | 53 | def to_environment 54 | settings.to_environment 55 | end 56 | 57 | def to_s(**args) 58 | settings.to_s(**args) 59 | end 60 | 61 | def to_hash 62 | settings.to_hash 63 | end 64 | 65 | def namespaces 66 | settings.namespaces 67 | end 68 | 69 | def encrypt(data, **args) 70 | config = configuration.to_hash.merge(**args) 71 | 72 | Settings 73 | .new( 74 | **config.merge( 75 | settings: data, 76 | pre_filters: [Filters::EncryptionFilter], 77 | post_filters: [], 78 | ), 79 | ) 80 | .to_hash 81 | end 82 | 83 | def decrypt(data, **args) 84 | config = configuration.to_hash.merge(**args) 85 | 86 | Settings 87 | .new( 88 | **config.merge( 89 | settings: data, 90 | pre_filters: [Filters::NamespaceFilter], 91 | post_filters: [ 92 | Filters::DecryptionFilter, 93 | Filters::FailedDecryptionFilter, 94 | ], 95 | ), 96 | ) 97 | .to_hash 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /chamber.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'chamber/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'chamber' 8 | spec.version = Chamber::VERSION 9 | spec.authors = ['thekompanee', 'jfelchner', 'stevenhallen', 'm5rk'] 10 | spec.email = ['hello@thekompanee.com'] 11 | spec.summary = %q{A surprisingly configurable convention-based approach to managing your application's custom configuration settings.} 12 | spec.description = %q{Chamber lets you source your Settings from an arbitrary number of YAML files and provides a simple mechanism for overriding settings from the ENV, which is friendly to how Heroku addons work.} 13 | spec.homepage = 'https://github.com/thekompanee/chamber' 14 | spec.licenses = ['MIT'] 15 | 16 | spec.cert_chain = ['certs/thekompanee.pem'] 17 | spec.signing_key = File.expand_path('~/.gem/certs/thekompanee-private_key.pem') if $0 =~ /gem\z/ 18 | 19 | spec.executables = ['chamber'] 20 | spec.files = Dir['{app,config,db,lib,templates}/**/*'] + %w{README.md LICENSE.txt} 21 | 22 | spec.metadata = { 23 | 'allowed_push_host' => 'https://rubygems.org', 24 | 'bug_tracker_uri' => 'https://github.com/thekompanee/chamber/issues', 25 | 'changelog_uri' => 'https://github.com/thekompanee/chamber/blob/master/CHANGELOG.md', 26 | 'documentation_uri' => 'https://github.com/thekompanee/chamber/tree/releases/v2.13.0', 27 | 'homepage_uri' => 'https://github.com/thekompanee/chamber', 28 | 'source_code_uri' => 'https://github.com/thekompanee/chamber', 29 | 'wiki_uri' => 'https://github.com/thekompanee/chamber/wiki', 30 | } 31 | 32 | spec.required_ruby_version = '>= 2.7.5' 33 | 34 | spec.add_dependency 'thor', [">= 0.20.3", "< 2.0"] 35 | 36 | spec.add_development_dependency 'rspec', ["~> 3.5"] 37 | spec.add_development_dependency 'rspectacular', ["~> 0.46"] 38 | spec.add_development_dependency 'activerecord', ["~> 4.0"] 39 | spec.add_development_dependency 'activesupport', ["~> 4.0"] 40 | spec.add_development_dependency 'timecop', ["~> 0.0"] 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib/chamber/filters/secure_filter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/filters/secure_filter' 5 | 6 | module Chamber 7 | module Filters 8 | describe SecureFilter do 9 | it 'returns values which are marked as "secure"' do 10 | filtered_settings = SecureFilter.execute(secure_key_prefix: '_secure_', 11 | data: { 12 | '_secure_my_secure_setting' => 'hello', 13 | }) 14 | 15 | expect(filtered_settings['_secure_my_secure_setting']).to match 'hello' 16 | end 17 | 18 | it 'does not return values which are not marked as "secure"' do 19 | filtered_settings = SecureFilter.execute(secure_key_prefix: '_secure_', 20 | data: { 21 | 'my_secure_setting' => 'hello', 22 | }) 23 | 24 | expect(filtered_settings['my_secure_setting']).to be nil 25 | end 26 | 27 | it 'properly returns values even if they are mixed and deeply nested' do 28 | filtered_settings = SecureFilter.execute(secure_key_prefix: '_secure_', 29 | data: { 30 | '_secure_setting' => 'hello', 31 | 'secure_setting' => 'goodbye', 32 | 'secure_group' => { 33 | '_secure_nested_setting' => 'movie', 34 | 'insecure_nested_setting' => 'dinner', 35 | }, 36 | }) 37 | 38 | expect(filtered_settings['_secure_setting']).to eql 'hello' 39 | expect(filtered_settings['secure_setting']).to be(nil) 40 | expect(filtered_settings['secure_group']['_secure_nested_setting']).to eql 'movie' 41 | expect(filtered_settings['secure_group']['insecure_nested_setting']).to be(nil) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Problem 4 | 5 | - name: "bug" 6 | color: "cb2431" 7 | description: "Problem - Bug" 8 | 9 | - name: "security" 10 | color: "86181d" 11 | description: "Problem - Security" 12 | 13 | # Miscellaneous 14 | 15 | - name: "chore" 16 | color: "fff5b1" 17 | description: "Miscellaneous - Chore" 18 | 19 | - name: "legal" 20 | color: "fff5b1" 21 | description: "Miscellaneous - Legal" 22 | 23 | # Experience 24 | 25 | - name: "copy" 26 | color: "ffab70" 27 | description: "Experience - Copy" 28 | 29 | - name: "design" 30 | color: "fb8532" 31 | description: "Experience - Design" 32 | 33 | - name: "ux" 34 | color: "f66a0a" 35 | description: "Experience - UX" 36 | 37 | # Environment 38 | 39 | - name: "test" 40 | color: "d1bcf9" 41 | description: "Environment - Test" 42 | 43 | - name: "staging" 44 | color: "8a63d2" 45 | description: "Environment - Staging" 46 | 47 | - name: "production" 48 | color: "4c2889" 49 | description: "Environment - Production" 50 | 51 | # Improvements 52 | 53 | - name: "enhancement" 54 | color: "bef5cb" 55 | description: "Improvements - Enhancement" 56 | 57 | - name: "optimization" 58 | color: "34d058" 59 | description: "Improvements - Optimization" 60 | 61 | - name: "feature" 62 | color: "176f2c" 63 | description: "Improvements - Feature" 64 | 65 | # Pending 66 | 67 | - name: "in-progress" 68 | color: "79b8ff" 69 | description: "Pending - In-Progress" 70 | 71 | - name: "watchlist" 72 | color: "0366d6" 73 | description: "Pending - Watchlist" 74 | 75 | # Inactive 76 | 77 | - name: "invalid" 78 | color: "f6f8fa" 79 | description: "Inactive - Invalid" 80 | 81 | - name: "wontfix" 82 | color: "e1e4e8" 83 | description: "Inactive - Won't Fix" 84 | 85 | - name: "duplicate" 86 | color: "959da5" 87 | description: "Inactive - Duplicate" 88 | 89 | - name: "on-hold" 90 | color: "586069" 91 | description: "Inactive - On Hold" 92 | 93 | - name: "outdated" 94 | color: "2f363d" 95 | description: "Inactive - Outdated" 96 | -------------------------------------------------------------------------------- /lib/chamber/key_pair.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'securerandom' 5 | 6 | module Chamber 7 | class KeyPair 8 | attr_accessor :key_file_path, 9 | :namespace, 10 | :passphrase 11 | 12 | def initialize(key_file_path:, namespace: nil, passphrase: ::SecureRandom.uuid) 13 | self.namespace = namespace 14 | self.passphrase = passphrase 15 | self.key_file_path = Pathname.new(key_file_path) 16 | end 17 | 18 | def encrypted_private_key_passphrase_filepath 19 | key_file_path + "#{encrypted_private_key_filename}.pass" 20 | end 21 | 22 | def encrypted_private_key_filepath 23 | key_file_path + encrypted_private_key_filename 24 | end 25 | 26 | def unencrypted_private_key_filepath 27 | key_file_path + unencrypted_private_key_filename 28 | end 29 | 30 | def public_key_filepath 31 | key_file_path + public_key_filename 32 | end 33 | 34 | def encrypted_private_key_pem 35 | encrypted_private_key 36 | end 37 | 38 | def unencrypted_private_key_pem 39 | unencrypted_private_key.to_pem 40 | end 41 | 42 | def public_key_pem 43 | public_key.to_pem 44 | end 45 | 46 | def encrypted_private_key_filename 47 | "#{base_key_filename}.enc" 48 | end 49 | 50 | def unencrypted_private_key_filename 51 | "#{base_key_filename}.pem" 52 | end 53 | 54 | def public_key_filename 55 | "#{base_key_filename}.pub.pem" 56 | end 57 | 58 | private 59 | 60 | def encrypted_private_key 61 | @encrypted_private_key ||= 62 | unencrypted_private_key.export(encryption_cipher, passphrase) 63 | end 64 | 65 | def unencrypted_private_key 66 | @unencrypted_private_key ||= OpenSSL::PKey::RSA.new(2048) 67 | end 68 | 69 | def public_key 70 | @public_key ||= unencrypted_private_key.public_key 71 | end 72 | 73 | def encryption_cipher 74 | @encryption_cipher ||= OpenSSL::Cipher.new('AES-128-CBC') 75 | end 76 | 77 | def base_key_filename 78 | @base_key_filename ||= [ 79 | '.chamber', 80 | namespace ? namespace.tr('-.', '') : nil, 81 | ] 82 | .compact 83 | .join('.') 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/lib/chamber/filters/failed_decryption_filter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/filters/failed_decryption_filter' 5 | 6 | module Chamber 7 | module Filters 8 | describe FailedDecryptionFilter do 9 | it 'raises an exception if any of the settings are not decrypted' do 10 | expect { 11 | FailedDecryptionFilter.execute( 12 | secure_key_prefix: '_secure_', 13 | data: { 14 | _secure_my_secure_setting: 'cJbFe0NI5wknmsp2fVgpC/YeBD2pvcdVD+p0pUdnMoYThaV4m' \ 15 | 'psspg/ZTBtmjx7kMwcF6cjXFLDVw3FxptTHwzJUd4akun6EZ5' \ 16 | '7m+QzCMJYnfY95gB2/emEAQLSz4/YwsE4LDGydkEjY1ZprfXz' \ 17 | 'nf+rU31YGDJUTf34ESz7fsQGSc9DjkBb9ao8Mv4cI7pCXkQZD' \ 18 | 'wS5kLAZDf6agy1GzeL71Z8lrmQzk8QQuf/1kQzxsWVlzpKNXW' \ 19 | 'S7u2CJ0sN5eINMngJBfv5ZFrZgfXc86wdgUKc8aaoX8OQA1kK' \ 20 | 'TcdgbE9NcAhNr1+WfNxMnz84XzmUp2Y0H1jPgGkBKQJKArfQ==', 21 | }, 22 | ) 23 | } 24 | .to raise_error Chamber::Errors::DecryptionFailure 25 | end 26 | 27 | it 'does not raise an exception if it is not a secure key' do 28 | expect { 29 | FailedDecryptionFilter.execute( 30 | secure_key_prefix: '_secure_', 31 | data: { 32 | my_secure_setting: 'cJbFe0NI5wknmsp2fVgpC/YeBD2pvcdVD+p0pUdnMoYThaV4m' \ 33 | 'psspg/ZTBtmjx7kMwcF6cjXFLDVw3FxptTHwzJUd4akun6EZ5' \ 34 | '7m+QzCMJYnfY95gB2/emEAQLSz4/YwsE4LDGydkEjY1ZprfXz' \ 35 | 'nf+rU31YGDJUTf34ESz7fsQGSc9DjkBb9ao8Mv4cI7pCXkQZD' \ 36 | 'wS5kLAZDf6agy1GzeL71Z8lrmQzk8QQuf/1kQzxsWVlzpKNXW' \ 37 | 'S7u2CJ0sN5eINMngJBfv5ZFrZgfXc86wdgUKc8aaoX8OQA1kK' \ 38 | 'TcdgbE9NcAhNr1+WfNxMnz84XzmUp2Y0H1jPgGkBKQJKArfQ==', 39 | }, 40 | ) 41 | } 42 | .not_to raise_error 43 | end 44 | 45 | it 'does not raise an exception if it is not a secure value' do 46 | expect { 47 | FailedDecryptionFilter.execute( 48 | secure_key_prefix: '_secure_', 49 | data: { 50 | _secure_my_secure_setting: 'hello', 51 | }, 52 | ) 53 | } 54 | .not_to raise_error 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/lib/chamber/types/secured_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'active_support/hash_with_indifferent_access' 5 | require 'chamber/types/secured' 6 | 7 | module Chamber 8 | module Types 9 | describe Secured do 10 | subject(:secured_type) do 11 | Secured.new(decryption_keys: { __default: './spec/spec_key' }, 12 | encryption_keys: { __default: './spec/spec_key.pub' }) 13 | end 14 | 15 | let(:base64_string_pattern) { %r{[A-Za-z0-9+/]{342}==} } 16 | 17 | it 'allows strings to be cast from the user' do 18 | json_string = '{ "hello": "there", "whatever": 3 }' 19 | secured = secured_type.cast(json_string) 20 | 21 | expect(secured).to eql('hello' => 'there', 'whatever' => 3) 22 | end 23 | 24 | it 'allows hashes to be cast from a user' do 25 | json_hash = { 'hello' => 'there', 'whatever' => 3 } 26 | secured = secured_type.cast(json_hash) 27 | 28 | expect(secured).to eql('hello' => 'there', 'whatever' => 3) 29 | end 30 | 31 | it 'allows nils to be cast from a user' do 32 | secured = secured_type.cast(nil) 33 | 34 | expect(secured).to be(nil) 35 | end 36 | 37 | it 'fails if passed something that it cannot be cast' do 38 | expect { secured_type.cast(3) } 39 | .to \ 40 | raise_error(ArgumentError) 41 | .with_message('Any attributes encrypted with Chamber must ' \ 42 | 'be either a Hash or a valid JSON string') 43 | end 44 | 45 | it 'can deserialize a hash' do 46 | json_string = '{' \ 47 | '"_secure_hello":"cpsTajQ/28E0YLQBpJ2tORnLSc6wliCqrmMzU0QfQZJlUWf' \ 48 | 'Q1yuev2xLsX56o5QkuJiqaspH9W68qXDC17UqcV0pB0y75d' \ 49 | '6ttQZbk3p9QbYgWGZOVlHEA8eJIqDUzisShrrOo+nSin6QK' \ 50 | 'UqizSjqhQC3Ii7CjTpMOK5RVc2y34vsVvYoJaqz5IYUEatA' \ 51 | 'XxzHsQ5tkcqy++a9LTJVFOt+ug+mTCstNJHW2sUK9L1XrbD' \ 52 | '2+KwUNkImCbhl6qeA+4CeVXMFgcpxjaawg5cQCgfSPj8gSy' \ 53 | 'pisbID59P0QVXRDQTdncrRv7q16RLmTqKI0xhNGevreFkNG' \ 54 | 'LAtSQjFRYfAQA==",' \ 55 | '"whatever":3' \ 56 | '}' 57 | secured = secured_type.deserialize(json_string) 58 | 59 | expect(secured).to eql('_secure_hello' => 'there', 'whatever' => 3) 60 | end 61 | 62 | it 'can serialize a hash' do 63 | json_hash = { '_secure_hello' => 'there', 'whatever' => 3 } 64 | secured = secured_type.serialize(json_hash) 65 | 66 | expect(secured).to be_a String 67 | expect(secured).to match(/{"_secure_hello":"#{base64_string_pattern}","whatever":3}/) 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Pull Request 4 | about: Describe the changes you would like to make 5 | 6 | --- 7 | 8 | 9 | 10 | Why This Change Is Necessary 11 | -------------------------------------------------------------------------------- 12 | 13 | 14 | 15 | * [] Bug Fix 16 | * [] New Feature 17 | 18 | 22 | 23 | How These Changes Address the Issue 24 | -------------------------------------------------------------------------------- 25 | 26 | 30 | 31 | Side Effects Caused By This Change 32 | -------------------------------------------------------------------------------- 33 | 34 | * [] This Causes a Breaking Change 35 | * [] This Does Not Cause Any Known Side Effects 36 | 37 | 43 | 44 | Screenshots 45 | -------------------------------------------------------------------------------- 46 | 47 | 51 | 52 | Checklist 53 | -------------------------------------------------------------------------------- 54 | 55 | 58 | 59 | * [] I have run `rubocop` against the codebase 60 | * [] I have added tests to cover my changes 61 | * [] All new and existing tests passed 62 | 63 | 85 | -------------------------------------------------------------------------------- /lib/chamber/files/signature.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'base64' 4 | require 'pathname' 5 | require 'time' 6 | 7 | module Chamber 8 | module Files 9 | class Signature 10 | SIGNATURE_HEADER = '-----BEGIN CHAMBER SIGNATURE-----' 11 | SIGNATURE_HEADER_PATTERN = /-----BEGIN\sCHAMBER\sSIGNATURE-----/.freeze 12 | SIGNATURE_FOOTER = '-----END CHAMBER SIGNATURE-----' 13 | SIGNATURE_FOOTER_PATTERN = /-----END\sCHAMBER\sSIGNATURE-----/.freeze 14 | SIGNATURE_IN_FILE_PATTERN = / 15 | #{SIGNATURE_HEADER_PATTERN}\n # Header 16 | (.*)\n # Signature Body 17 | #{SIGNATURE_FOOTER_PATTERN} # Footer 18 | /x.freeze 19 | 20 | attr_accessor :settings_content, 21 | :settings_filename, 22 | :signature_name 23 | 24 | attr_reader :signature_key 25 | 26 | def initialize(settings_filename, settings_content, signature_key, signature_name) 27 | self.signature_key = signature_key 28 | self.settings_content = settings_content 29 | self.settings_filename = Pathname.new(settings_filename) 30 | self.signature_name = signature_name 31 | end 32 | 33 | def signature_key=(keyish) 34 | @signature_key = if keyish.is_a?(OpenSSL::PKey::RSA) 35 | keyish 36 | elsif ::File.readable?(::File.expand_path(keyish)) 37 | file_contents = ::File.read(::File.expand_path(keyish)) 38 | OpenSSL::PKey::RSA.new(file_contents) 39 | else 40 | OpenSSL::PKey::RSA.new(keyish) 41 | end 42 | end 43 | 44 | def write 45 | signature_filename.write(<<~HEREDOC, 0, mode: 'w+') 46 | Signed By: #{signature_name} 47 | Signed At: #{Time.now.utc.iso8601} 48 | 49 | #{SIGNATURE_HEADER} 50 | #{encoded_signature} 51 | #{SIGNATURE_FOOTER} 52 | HEREDOC 53 | end 54 | 55 | def verify 56 | signature_key.verify(digest, signature_content, settings_content) 57 | end 58 | 59 | private 60 | 61 | def encoded_signature 62 | @encoded_signature ||= Base64.strict_encode64(raw_signature) 63 | end 64 | 65 | def raw_signature 66 | @raw_signature ||= signature_key 67 | .sign(digest, settings_content) 68 | end 69 | 70 | def signature_filename 71 | @signature_filename ||= settings_filename 72 | .sub('.yml', '.sig') 73 | .sub('.erb', '') 74 | end 75 | 76 | def encoded_signature_content 77 | @encoded_signature_content ||= signature_filename 78 | .read 79 | .match(SIGNATURE_IN_FILE_PATTERN) do |match| 80 | match[1] 81 | end 82 | end 83 | 84 | def signature_content 85 | @signature_content ||= Base64.strict_decode64(encoded_signature_content) 86 | end 87 | 88 | def digest 89 | @digest ||= OpenSSL::Digest.new('SHA512') 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/chamber/encryption_methods/ssl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'base64' 4 | 5 | module Chamber 6 | module EncryptionMethods 7 | class Ssl 8 | BASE64_STRING_PATTERN = %r{[A-Za-z0-9+/#]*={0,2}}.freeze 9 | LARGE_DATA_STRING_PATTERN = / 10 | \A 11 | (#{BASE64_STRING_PATTERN}) 12 | \# 13 | (#{BASE64_STRING_PATTERN}) 14 | \# 15 | (#{BASE64_STRING_PATTERN}) 16 | \z 17 | /x.freeze 18 | 19 | def self.encrypt(_settings_key, value, encryption_keys) # rubocop:disable Metrics/AbcSize 20 | value = YAML.dump(value) 21 | cipher = OpenSSL::Cipher.new('AES-128-CBC') 22 | cipher.encrypt 23 | symmetric_key = cipher.random_key 24 | iv = cipher.random_iv 25 | 26 | # encrypt all data with this key and iv 27 | encrypted_data = cipher.update(value) + cipher.final 28 | 29 | # encrypt the key with the public key 30 | encrypted_key = encryption_keys.public_encrypt(symmetric_key) 31 | 32 | # assemble the resulting Base64 encoded data, the key 33 | Base64.strict_encode64(encrypted_key) + '#' + 34 | Base64.strict_encode64(iv) + '#' + 35 | Base64.strict_encode64(encrypted_data) 36 | end 37 | 38 | def self.decrypt(_settings_key, value, decryption_keys) # rubocop:disable Metrics/AbcSize 39 | return value if decryption_keys.nil? 40 | 41 | key, iv, decoded_string = value 42 | .match(LARGE_DATA_STRING_PATTERN) 43 | .captures 44 | .map do |part| 45 | ::Base64.strict_decode64(part) 46 | end 47 | key = decryption_keys.private_decrypt(key) 48 | 49 | cipher_dec = ::OpenSSL::Cipher.new('AES-128-CBC') 50 | 51 | cipher_dec.decrypt 52 | 53 | cipher_dec.key = key 54 | cipher_dec.iv = iv 55 | 56 | unencrypted_value = cipher_dec.update(decoded_string) + cipher_dec.final 57 | 58 | ::YAML.safe_load(unencrypted_value, 59 | aliases: true, 60 | permitted_classes: [ 61 | ::Date, 62 | ::Time, 63 | ::Regexp, 64 | ]) 65 | rescue ::OpenSSL::Cipher::CipherError 66 | raise ::Chamber::Errors::DecryptionFailure, 67 | 'A decryption error occurred. It was probably due to invalid key data.' 68 | rescue ::Psych::DisallowedClass => error 69 | raise ::Chamber::Errors::DisallowedClass, <<~HEREDOC 70 | #{error.message} 71 | 72 | You attempted to load a class instance via your Chamber settings that is not allowed. 73 | 74 | See https://github.com/thekompanee/chamber/wiki/Upgrading-To-Chamber-3.0#limiting-complex-classes for full details. 75 | HEREDOC 76 | rescue ::TypeError 77 | unencrypted_value 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/lib/chamber/files/signature_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'securerandom' 5 | require 'chamber/files/signature' 6 | 7 | module Chamber 8 | module Files 9 | describe Signature do 10 | it 'can write a signature file', :time_mock do 11 | seed = SecureRandom.uuid 12 | file = Signature.new("/tmp/settings-#{seed}.yml", 13 | 'my settings content', 14 | 'spec/spec_key', 15 | 'Suzy Q Robinson') 16 | 17 | file.write 18 | 19 | signature_contents = ::File.read("/tmp/settings-#{seed}.sig") 20 | 21 | expect(signature_contents).to eql(<<~HEREDOC) 22 | Signed By: Suzy Q Robinson 23 | Signed At: 2012-07-26T18:00:00Z 24 | 25 | -----BEGIN CHAMBER SIGNATURE----- 26 | HxXUpr8+UhpdOSgqM778KLZTHYjYnTnOfPzr5SiYCtFOgckdM2IxlmrYvYSP2a9Xw0gptXJLE1CxpT19YefhTzL8WzJF6ut6ByVkDeJKv+1UXqQcvtFhOsponN9vsvZALJoH36AL34GXmUiNXpjoqZuw5BFhq/j321ddy3TD7YTfzF+9vYQTGfB6coMpQAn1x7Vctg7PZeNHsG443EIifbIP4x/ql05sxvyg8i3L7LJ4cxZ1y5EVdOYjLxHvZZ19jq2/ELHVh1gKZ5AR/sHx9r/5Lq12u3qeBRIdsxNfFax+dYA2/7zdos/vxqLZe2wrQKK010kose8pTc8Rq+p7/Q== 27 | -----END CHAMBER SIGNATURE----- 28 | HEREDOC 29 | end 30 | 31 | it 'can write an ERB signature file', :time_mock do 32 | seed = SecureRandom.uuid 33 | file = Signature.new("/tmp/settings-#{seed}.yml.erb", 34 | 'my settings content', 35 | 'spec/spec_key', 36 | 'Suzy Q Robinson') 37 | 38 | file.write 39 | 40 | signature_contents = ::File.read("/tmp/settings-#{seed}.sig") 41 | 42 | expect(signature_contents).to eql(<<~HEREDOC) 43 | Signed By: Suzy Q Robinson 44 | Signed At: 2012-07-26T18:00:00Z 45 | 46 | -----BEGIN CHAMBER SIGNATURE----- 47 | HxXUpr8+UhpdOSgqM778KLZTHYjYnTnOfPzr5SiYCtFOgckdM2IxlmrYvYSP2a9Xw0gptXJLE1CxpT19YefhTzL8WzJF6ut6ByVkDeJKv+1UXqQcvtFhOsponN9vsvZALJoH36AL34GXmUiNXpjoqZuw5BFhq/j321ddy3TD7YTfzF+9vYQTGfB6coMpQAn1x7Vctg7PZeNHsG443EIifbIP4x/ql05sxvyg8i3L7LJ4cxZ1y5EVdOYjLxHvZZ19jq2/ELHVh1gKZ5AR/sHx9r/5Lq12u3qeBRIdsxNfFax+dYA2/7zdos/vxqLZe2wrQKK010kose8pTc8Rq+p7/Q== 48 | -----END CHAMBER SIGNATURE----- 49 | HEREDOC 50 | end 51 | 52 | it 'can verify a signature file', :time_mock do 53 | seed = SecureRandom.uuid 54 | settings_filename = "/tmp/settings-#{seed}.yml" 55 | signature_filename = "/tmp/settings-#{seed}.sig" 56 | 57 | ::File.write(signature_filename, <<~HEREDOC, mode: 'w+') 58 | Signed By: Suzy Q Robinson 59 | Signed At: 2012-07-26T18:00:00Z 60 | 61 | -----BEGIN CHAMBER SIGNATURE----- 62 | HxXUpr8+UhpdOSgqM778KLZTHYjYnTnOfPzr5SiYCtFOgckdM2IxlmrYvYSP2a9Xw0gptXJLE1CxpT19YefhTzL8WzJF6ut6ByVkDeJKv+1UXqQcvtFhOsponN9vsvZALJoH36AL34GXmUiNXpjoqZuw5BFhq/j321ddy3TD7YTfzF+9vYQTGfB6coMpQAn1x7Vctg7PZeNHsG443EIifbIP4x/ql05sxvyg8i3L7LJ4cxZ1y5EVdOYjLxHvZZ19jq2/ELHVh1gKZ5AR/sHx9r/5Lq12u3qeBRIdsxNfFax+dYA2/7zdos/vxqLZe2wrQKK010kose8pTc8Rq+p7/Q== 63 | -----END CHAMBER SIGNATURE----- 64 | HEREDOC 65 | 66 | file = Signature.new(settings_filename, 67 | 'my settings content', 68 | 'spec/spec_key.pub', 69 | 'Suzy Q Robinson') 70 | 71 | expect(file.verify).to be true 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/chamber/filters/encryption_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'openssl' 4 | require 'yaml' 5 | require 'chamber/encryption_methods/public_key' 6 | require 'chamber/encryption_methods/ssl' 7 | require 'chamber/encryption_methods/none' 8 | require 'chamber/refinements/deep_dup' 9 | 10 | module Chamber 11 | module Filters 12 | class EncryptionFilter 13 | using ::Chamber::Refinements::DeepDup 14 | 15 | BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9+/]{342}==\z}.freeze 16 | BASE64_SUBSTRING_PATTERN = %r{[A-Za-z0-9+/#]*={0,2}}.freeze 17 | LARGE_DATA_STRING_PATTERN = / 18 | \A 19 | (#{BASE64_SUBSTRING_PATTERN}) 20 | \# 21 | (#{BASE64_SUBSTRING_PATTERN}) 22 | \# 23 | (#{BASE64_SUBSTRING_PATTERN}) 24 | \z 25 | /x.freeze 26 | 27 | attr_accessor :data, 28 | :secure_key_token 29 | attr_reader :encryption_keys 30 | 31 | def self.execute(**args) 32 | new(**args).__send__(:execute) 33 | end 34 | 35 | def initialize(data:, secure_key_prefix:, encryption_keys: {}, **_args) 36 | self.encryption_keys = (encryption_keys || {}).transform_keys(&:to_s) 37 | self.data = data.deep_dup 38 | self.secure_key_token = /\A#{Regexp.escape(secure_key_prefix)}/ 39 | end 40 | 41 | protected 42 | 43 | def execute(raw_data = data, namespace = nil) 44 | raw_data.each_with_object({}) do |(key, value), settings| 45 | settings[key] = if value.respond_to? :each_pair 46 | execute(value, namespace || key) 47 | elsif key.match(secure_key_token) 48 | encrypt(namespace, key, value) 49 | else 50 | value 51 | end 52 | end 53 | end 54 | 55 | def encryption_keys=(other) 56 | @encryption_keys = other.each_with_object({}) do |(namespace, keyish), memo| # rubocop:disable Style/HashTransformValues 57 | memo[namespace] = if keyish.is_a?(OpenSSL::PKey::RSA) 58 | keyish 59 | elsif ::File.readable?(::File.expand_path(keyish)) 60 | file_contents = ::File.read(::File.expand_path(keyish)) 61 | OpenSSL::PKey::RSA.new(file_contents) 62 | else 63 | OpenSSL::PKey::RSA.new(keyish) 64 | end 65 | end 66 | end 67 | 68 | private 69 | 70 | def encrypt(namespace, key, value) 71 | method = encryption_method(value) 72 | encryption_key = encryption_keys[namespace] || encryption_keys['__default'] 73 | 74 | return value unless encryption_key 75 | 76 | method.encrypt(key, value, encryption_key) 77 | end 78 | 79 | def encryption_method(value) 80 | value_is_encrypted = value.is_a?(::String) && 81 | (value.match(BASE64_STRING_PATTERN) || 82 | value.match(LARGE_DATA_STRING_PATTERN)) 83 | 84 | if value_is_encrypted 85 | EncryptionMethods::None 86 | else 87 | serialized_value = YAML.dump(value) 88 | 89 | if serialized_value.length <= 128 90 | EncryptionMethods::PublicKey 91 | else 92 | EncryptionMethods::Ssl 93 | end 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/lib/chamber/key_pair_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/key_pair' 5 | 6 | module Chamber 7 | describe KeyPair do 8 | it 'can generate a private key' do 9 | key_pair = KeyPair.new(key_file_path: './tmp/') 10 | 11 | expect(key_pair.unencrypted_private_key_pem) 12 | .to match( 13 | / 14 | -----BEGIN\sRSA\sPRIVATE\sKEY-----\n # Private Key Header 15 | .* # Any Key Contents 16 | -----END\sRSA\sPRIVATE\sKEY-----\n # Private Key Footer 17 | /xm, 18 | ) 19 | end 20 | 21 | it 'can generate a encrypted private key' do 22 | key_pair = KeyPair.new(key_file_path: './tmp/') 23 | 24 | expect(key_pair.encrypted_private_key_pem) 25 | .to match( 26 | / 27 | -----BEGIN\sRSA\sPRIVATE\sKEY-----\n # Private Key Header 28 | Proc-Type:\s4,ENCRYPTED\n # Encryption Header 29 | .* # Any Key Contents 30 | -----END\sRSA\sPRIVATE\sKEY-----\n # Private Key Footer 31 | /xm, 32 | ) 33 | end 34 | 35 | it 'can generate a public key' do 36 | key_pair = KeyPair.new(key_file_path: './tmp/') 37 | 38 | expect(key_pair.public_key_pem) 39 | .to match( 40 | / 41 | -----BEGIN\sPUBLIC\sKEY----- # Public Key Header 42 | .* # Any Key Contents 43 | -----END\sPUBLIC\sKEY-----\n # Public Key Footer 44 | /xm, 45 | ) 46 | end 47 | 48 | it 'can construct a default private key filepath' do 49 | key_pair = KeyPair.new(key_file_path: './tmp/') 50 | 51 | expect(key_pair.unencrypted_private_key_filepath.to_s).to eql './tmp/.chamber.pem' 52 | end 53 | 54 | it 'can construct a default encrypted private key filepath' do 55 | key_pair = KeyPair.new(key_file_path: './tmp/') 56 | 57 | expect(key_pair.encrypted_private_key_filepath.to_s).to eql './tmp/.chamber.enc' 58 | end 59 | 60 | it 'can construct a default public key filepath' do 61 | key_pair = KeyPair.new(key_file_path: './tmp/') 62 | 63 | expect(key_pair.public_key_filepath.to_s).to eql './tmp/.chamber.pub.pem' 64 | end 65 | 66 | it 'can construct a namespaced private key filepath' do 67 | key_pair = KeyPair.new(namespace: 'mynamespace', 68 | key_file_path: './tmp/') 69 | 70 | expect(key_pair.unencrypted_private_key_filepath.to_s).to eql './tmp/.chamber.mynamespace.pem' 71 | end 72 | 73 | it 'can construct a namespaced encrypted private key filepath' do 74 | key_pair = KeyPair.new(namespace: 'mynamespace', 75 | key_file_path: './tmp/') 76 | 77 | expect(key_pair.encrypted_private_key_filepath.to_s).to eql './tmp/.chamber.mynamespace.enc' 78 | end 79 | 80 | it 'can construct a namespaced public key filepath' do 81 | key_pair = KeyPair.new(namespace: 'mynamespace', 82 | key_file_path: './tmp/') 83 | 84 | expect(key_pair.public_key_filepath.to_s).to eql './tmp/.chamber.mynamespace.pub.pem' 85 | end 86 | 87 | it 'knows to remove special characters from the namespace before adding it to the file' do 88 | key_pair = KeyPair.new(namespace: 'my-name.space', 89 | key_file_path: './tmp/') 90 | 91 | expect(key_pair.unencrypted_private_key_filepath.to_s).to eql './tmp/.chamber.mynamespace.pem' 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/chamber/filters/decryption_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'openssl' 4 | require 'base64' 5 | require 'yaml' 6 | require 'chamber/encryption_methods/public_key' 7 | require 'chamber/encryption_methods/ssl' 8 | require 'chamber/encryption_methods/none' 9 | require 'chamber/errors/decryption_failure' 10 | require 'chamber/refinements/deep_dup' 11 | 12 | module Chamber 13 | module Filters 14 | class DecryptionFilter 15 | using ::Chamber::Refinements::DeepDup 16 | 17 | BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9+/]{342}==\z}.freeze 18 | LARGE_DATA_STRING_PATTERN = %r{ 19 | \A # Beginning of String 20 | ( 21 | [A-Za-z0-9+/#]*={0,2} # Base64 Encoded Key 22 | ) 23 | \# # Separator 24 | ( 25 | [A-Za-z0-9+/#]*={0,2} # Base64 Encoded IV 26 | ) 27 | \# # Separator 28 | ( 29 | [A-Za-z0-9+/#]*={0,2} # Base64 Encoded Data 30 | ) 31 | \z # End of String 32 | }x.freeze 33 | 34 | attr_accessor :data, 35 | :secure_key_token 36 | attr_reader :decryption_keys 37 | 38 | def self.execute(**args) 39 | new(**args).__send__(:execute) 40 | end 41 | 42 | def initialize(data:, secure_key_prefix:, decryption_keys: {}, **_args) 43 | self.decryption_keys = (decryption_keys || {}).transform_keys(&:to_s) 44 | self.data = data.deep_dup 45 | self.secure_key_token = /\A#{Regexp.escape(secure_key_prefix)}/ 46 | end 47 | 48 | protected 49 | 50 | def execute(raw_data = data) 51 | settings = {} 52 | 53 | raw_data.each_pair do |key, value| 54 | settings[key] = if value.respond_to? :each_pair 55 | execute(value) 56 | elsif key.match(secure_key_token) 57 | decrypt(key, value) 58 | else 59 | value 60 | end 61 | end 62 | 63 | settings 64 | end 65 | 66 | def decryption_keys=(other) 67 | @decryption_keys = other.each_value.map do |keyish| 68 | content = if ::File.readable?(::File.expand_path(keyish)) 69 | ::File.read(::File.expand_path(keyish)) 70 | else 71 | keyish 72 | end 73 | 74 | OpenSSL::PKey::RSA.new(content) 75 | end 76 | end 77 | 78 | private 79 | 80 | def decrypt(key, value) 81 | method = decryption_method(value) 82 | 83 | decryption_keys.each do |decryption_key| 84 | return method.decrypt(key, value, decryption_key) 85 | rescue OpenSSL::PKey::RSAError 86 | next 87 | end 88 | 89 | value 90 | end 91 | 92 | def decryption_method(value) 93 | if value.is_a?(::String) 94 | if value.match(BASE64_STRING_PATTERN) 95 | EncryptionMethods::PublicKey 96 | elsif value.match(LARGE_DATA_STRING_PATTERN) 97 | EncryptionMethods::Ssl 98 | else 99 | EncryptionMethods::None 100 | end 101 | else 102 | EncryptionMethods::None 103 | end 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/chamber/context_resolver.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'socket' 5 | 6 | require 'chamber/keys/decryption' 7 | require 'chamber/keys/encryption' 8 | 9 | module Chamber 10 | class ContextResolver 11 | attr_accessor :options 12 | 13 | def self.resolve(**args) 14 | new(**args).resolve 15 | end 16 | 17 | def initialize(**args) 18 | self.options = args 19 | end 20 | 21 | # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Layout/LineLength, Lint/SelfAssignment 22 | def resolve 23 | options[:rootpath] ||= Pathname.pwd 24 | options[:rootpath] = Pathname.new(options[:rootpath]) 25 | options[:namespaces] ||= [] 26 | options[:preset] ||= resolve_preset 27 | 28 | if %w{rails rails-engine}.include?(options[:preset]) 29 | options[:rootpath] = detect_engine_root if options[:preset] == 'rails-engine' 30 | options[:namespaces] = load_rails_default_namespaces(options[:rootpath]) if options[:namespaces] == [] 31 | options[:basepath] ||= options[:rootpath] + 'config' 32 | else 33 | options[:basepath] ||= options[:rootpath] 34 | end 35 | 36 | options[:namespaces] = resolve_namespaces(options[:namespaces]) 37 | options[:encryption_keys] = Keys::Encryption.resolve(filenames: options[:encryption_keys], 38 | namespaces: options[:namespaces], 39 | rootpath: options[:rootpath]) 40 | options[:decryption_keys] = Keys::Decryption.resolve(filenames: options[:decryption_keys], 41 | namespaces: options[:namespaces], 42 | rootpath: options[:rootpath]) 43 | options[:basepath] = Pathname.new(options[:basepath]) 44 | options[:files] ||= [ 45 | options[:basepath] + 'settings*.yml', 46 | options[:basepath] + 'settings', 47 | ] 48 | 49 | options[:signature_name] = options[:signature_name] 50 | 51 | options 52 | end 53 | # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Layout/LineLength, Lint/SelfAssignment 54 | 55 | protected 56 | 57 | def resolve_namespaces(other) 58 | (other.respond_to?(:values) ? other.values : other) 59 | .map do |namespace| 60 | namespace.respond_to?(:call) ? namespace.call : namespace 61 | end 62 | end 63 | 64 | def resolve_preset 65 | if in_a_rails_project? 66 | 'rails' 67 | elsif in_a_rails_engine? 68 | 'rails-engine' 69 | end 70 | end 71 | 72 | def in_a_rails_project? 73 | (options[:rootpath] + 'config.ru').exist? && 74 | rails_executable_exists? 75 | end 76 | 77 | def in_a_rails_engine? 78 | (options[:rootpath] + 'spec' + 'dummy' + 'config.ru').exist? || 79 | (options[:rootpath] + 'test' + 'dummy' + 'config.ru').exist? 80 | end 81 | 82 | def rails_executable_exists? 83 | options[:rootpath].join('bin', 'rails').exist? || 84 | options[:rootpath].join('script', 'rails').exist? || 85 | options[:rootpath].join('script', 'console').exist? 86 | end 87 | 88 | private 89 | 90 | def detect_engine_root 91 | engine_spec_dummy_directory = options[:rootpath] + 'spec' + 'dummy' 92 | engine_test_dummy_directory = options[:rootpath] + 'test' + 'dummy' 93 | 94 | if (engine_spec_dummy_directory + 'config.ru').exist? 95 | engine_spec_dummy_directory 96 | elsif (engine_test_dummy_directory + 'config.ru').exist? 97 | engine_test_dummy_directory 98 | end 99 | end 100 | 101 | def load_rails_default_namespaces(root) 102 | require root.join('config', 'application').to_s 103 | 104 | [ 105 | ::Rails.env, 106 | Socket.gethostname, 107 | ] 108 | rescue LoadError 109 | [] 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "settings": {}, 3 | "plugins": { 4 | "lint-blockquote-indentation": ["error", 2], 5 | "lint-checkbox-character-style": ["error", { 6 | "checked": "x", 7 | "unchecked": " " 8 | }], 9 | "lint-code-block-style": ["error", "fenced"], 10 | "lint-definition-case": ["error", true], 11 | "lint-definition-spacing": ["error", true], 12 | "lint-emphasis-marker": ["error", "_"], 13 | "lint-fenced-code-flag": ["error", true], 14 | "lint-fenced-code-marker": ["error", "`"], 15 | "lint-file-extension": ["error", "md"], 16 | "lint-final-definition": ["error", true], 17 | "lint-final-newline": ["error", true], 18 | "lint-first-heading-level": ["warn", 1], 19 | "lint-hard-break-spaces": ["error", true], 20 | "lint-heading-increment": ["error", true], 21 | "lint-heading-style": ["error", "setext"], 22 | "lint-heading-whitespace": ["error", true], 23 | "lint-linebreak-style": ["error", "unix"], 24 | "lint-link-title-style": ["error", "\""], 25 | "lint-list-item-bullet-indent": ["error", true], 26 | "lint-list-item-content-indent": ["error", true], 27 | "lint-list-item-indent": ["error", "space"], 28 | "lint-list-item-spacing": ["error", true], 29 | "lint-maximum-heading-length": false, 30 | "lint-maximum-line-length": ["error", 80], 31 | "lint-no-auto-link-without-protocol": ["error", true], 32 | "lint-no-blockquote-without-marker": ["error", true], 33 | "lint-no-consecutive-blank-lines": ["error", true], 34 | "lint-no-duplicate-definitions": ["error", true], 35 | "lint-no-duplicate-headings": false, 36 | "lint-no-duplicate-headings-in-section": ["error", true], 37 | "lint-no-emphasis-as-heading": ["error", true], 38 | "lint-no-empty-sections": ["error", true], 39 | "lint-no-empty-url": ["error", true], 40 | "lint-no-file-name-articles": ["error", true], 41 | "lint-no-file-name-consecutive-dashes": ["error", true], 42 | "lint-no-file-name-irregular-characters": ["error", "\\.a-zA-Z0-9-_"], 43 | "lint-no-file-name-mixed-case": ["error", true], 44 | "lint-no-file-name-outer-dashes": ["error", true], 45 | "lint-no-heading-content-indent": ["error", true], 46 | "lint-no-heading-indent": ["error", true], 47 | "lint-no-heading-like-paragraph": ["error", true], 48 | "lint-no-heading-punctuation": ["error", ".,;:!?"], 49 | "lint-no-html": false, 50 | "lint-no-inline-padding": ["error", true], 51 | "lint-no-literal-urls": ["error", true], 52 | "lint-no-missing-blank-lines": ["error", { 53 | "exceptTightLists": true 54 | }], 55 | "lint-no-multiple-toplevel-headings": false, 56 | "lint-no-paragraph-content-indent": ["error", true], 57 | "lint-no-reference-like-url": ["error", true], 58 | "lint-no-shell-dollars": ["error", true], 59 | "lint-no-shortcut-reference-image": ["error", true], 60 | "lint-no-shortcut-reference-link": ["error", true], 61 | "lint-no-table-indentation": ["error", true], 62 | "lint-no-tabs": ["error", true], 63 | "lint-no-undefined-references": ["error", true], 64 | "lint-no-unused-definitions": ["error", true], 65 | "lint-no-url-trailing-slash": ["error", true], 66 | "lint-ordered-list-marker-style": ["error", "."], 67 | "lint-ordered-list-marker-value": ["error", "ordered"], 68 | "lint-strong-marker": ["error", "*"], 69 | "lint-table-cell-padding": ["error", "padded"], 70 | "lint-table-pipe-alignment": ["error", true], 71 | "lint-table-pipes": ["error", true], 72 | "lint-unordered-list-marker-style": ["error", "*"] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /spec/lib/chamber/filters/namespace_filter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/filters/namespace_filter' 5 | require 'chamber/namespace_set' 6 | 7 | module Chamber 8 | module Filters 9 | describe NamespaceFilter do 10 | it 'can filter settings data based on the settings namespaces' do 11 | filtered_settings = NamespaceFilter.execute( 12 | data: { 13 | 'namespace_value' => { 14 | 'namespace_setting' => 'value 1', 15 | }, 16 | 'other_namespace_value' => { 17 | 'other_namespace_setting' => 'value 2', 18 | }, 19 | }, 20 | namespaces: %w{namespace_value other_namespace_value}, 21 | ) 22 | 23 | expect(filtered_settings['namespace_setting']).to eql 'value 1' 24 | expect(filtered_settings['other_namespace_setting']).to eql 'value 2' 25 | end 26 | 27 | it 'ignores data which is not part of a namespace' do 28 | filtered_settings = NamespaceFilter.execute( 29 | data: { 30 | 'namespace_value' => { 31 | 'namespace_setting' => 'value 1', 32 | }, 33 | 'non_namespaced_value' => { 34 | 'non_namespaced_setting' => 'value 2', 35 | }, 36 | }, 37 | namespaces: %w{ 38 | namespace_value 39 | }, 40 | ) 41 | 42 | expect(filtered_settings['namespace_setting']).to eql 'value 1' 43 | expect(filtered_settings['non_namespaced_setting']).to be(nil) 44 | end 45 | 46 | it 'ignores namespaces which do not exist in the data' do 47 | filtered_settings = NamespaceFilter.execute( 48 | data: { 49 | 'namespace_value' => { 50 | 'namespace_setting' => 'value 1', 51 | }, 52 | }, 53 | namespaces: %w{namespace_value other_namespace_value}, 54 | ) 55 | 56 | expect(filtered_settings['namespace_setting']).to eql 'value 1' 57 | end 58 | 59 | it 'does not filter data if it does not include any namespaces' do 60 | filtered_settings = NamespaceFilter.execute( 61 | data: { 62 | 'non_namespaced_setting' => 'value 1', 63 | }, 64 | namespaces: [], 65 | ) 66 | 67 | expect(filtered_settings['non_namespaced_setting']).to eql 'value 1' 68 | end 69 | 70 | it 'can filter if it is given NamespaceSets' do 71 | filtered_settings = NamespaceFilter.execute( 72 | data: { 73 | 'namespace_value' => { 74 | 'namespace_setting' => 'value 1', 75 | 'another_namespace_setting' => 'value 2', 76 | }, 77 | 'other_namespace_value' => { 78 | 'namespace_setting_1' => 'value 1', 79 | 'another_namespace_setting_2' => 'value 2', 80 | }, 81 | 'non_namespaced_value' => 'value 3', 82 | }, 83 | namespaces: NamespaceSet.new( 84 | %w{ 85 | namespace_value 86 | other_namespace_value 87 | }, 88 | ), 89 | ) 90 | 91 | expect(filtered_settings.to_hash).to eql('namespace_setting' => 'value 1', 92 | 'another_namespace_setting' => 'value 2', 93 | 'namespace_setting_1' => 'value 1', 94 | 'another_namespace_setting_2' => 'value 2') 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Vendored Dependencies 2 | Pods 3 | bower_components 4 | node_modules 5 | web/app/mu-plugins 6 | web/app/plugins 7 | web/app/upgrade 8 | web/app/uploads 9 | web/wp 10 | vendor 11 | 12 | # OS Files 13 | .AppleDB 14 | .AppleDesktop 15 | .AppleDouble 16 | .DS_Store 17 | .LSOverride 18 | .apdisk 19 | Thumbs.db 20 | 21 | # Testing Files 22 | *.gemfile.lock 23 | capybara* 24 | pickle-email-*.html 25 | public/uploads 26 | rerun.txt 27 | site/web/app/uploads 28 | test/data 29 | 30 | # Cache Files 31 | *.stTheme.cache 32 | *.tfstate 33 | *.tmPreferences.cache 34 | *.tmlanguage.cache 35 | .TAGS 36 | .byebug_history 37 | .divshot-cache 38 | .dropbox 39 | .dropbox.attr 40 | .dropbox.cache 41 | .rake_tasks 42 | .repl_history 43 | .sass-cache 44 | .tags 45 | .yardoc 46 | /variables.tf.json 47 | TAGS 48 | _yardoc 49 | disk_image_for_*.json 50 | disk_image_settings.json 51 | lambdas/*.js 52 | lambdas/*.py 53 | lambdas/*.zip 54 | packer_cache 55 | public/packs* 56 | public/system 57 | site/web/app/upgrade 58 | spec/reports 59 | tags 60 | terraform.tfvars 61 | test/tmp 62 | test/version_tmp 63 | tmtags 64 | variables.sls 65 | 66 | !TAGS/ 67 | !tags/ 68 | 69 | # Local Compiled Files 70 | **/cordova/platforms/android/CordovaLib/ant-build 71 | **/cordova/platforms/android/CordovaLib/ant-gen 72 | **/cordova/platforms/android/CordovaLib/bin 73 | **/cordova/platforms/android/CordovaLib/gen 74 | **/cordova/platforms/android/ant-build 75 | **/cordova/platforms/android/ant-gen 76 | **/cordova/platforms/android/assets/www 77 | **/cordova/platforms/android/bin 78 | **/cordova/platforms/android/gen 79 | **/cordova/platforms/browser/build 80 | **/cordova/platforms/browser/www 81 | **/cordova/platforms/ios/build/ 82 | **/cordova/platforms/ios/www/ 83 | **/cordova/platforms/wp8/*.csproj.user 84 | **/cordova/platforms/wp8/*.suo 85 | **/cordova/platforms/wp8/.staging 86 | **/cordova/platforms/wp8/bin 87 | **/cordova/platforms/wp8/obj 88 | **/cordova/platforms/wp8/www 89 | **/public/stylesheets/*.css 90 | *.a 91 | *.css.map 92 | *.elc 93 | *.gem 94 | *.hmap 95 | *.ipa 96 | *.o 97 | *.rbc 98 | *.sass.map 99 | *.scss.map 100 | *.so 101 | *.xccheckout 102 | *.xcuserdatad 103 | DerivedData 104 | build 105 | build-iPhoneOS 106 | build-iPhoneSimulator 107 | coverage 108 | coverage.data 109 | dist 110 | files/scripts/**/*.js 111 | files/scripts/**/*.py 112 | files/scripts/**/*.zip 113 | out 114 | pillars/files 115 | pkg 116 | profile 117 | rdoc 118 | target 119 | 120 | # Local Configuration Files 121 | **/cordova/platforms/android/CordovaLib/local.properties 122 | **/cordova/platforms/android/local.properties 123 | **/settings-local.yml 124 | **/settings/*-local.yml 125 | *.bundle 126 | *.iml 127 | *.ipr 128 | *.iws 129 | *.mode1v3 130 | *.mode2v3 131 | *.pbxuser 132 | *.perspectivev3 133 | *.project 134 | *.sublime-project 135 | *.sublime-settings 136 | *.sublime-workspace 137 | *.tmproj 138 | .Rhistory 139 | .build 140 | .bundle 141 | .chamber*.enc 142 | .chamber*.enc.pass 143 | .chamber*.pem 144 | .emacs.desktop 145 | .emacs.desktop.lock 146 | .env 147 | .env.* 148 | .env.*.local 149 | .env.local 150 | .env.yml 151 | .foreman 152 | .htaccess 153 | .idea 154 | .idea_modules 155 | .powenv 156 | .powrc 157 | .pryrc 158 | .railsrc 159 | .rspec 160 | .rvmrc 161 | .secrets 162 | .shellwreck.sh 163 | .tern-port 164 | .terraform 165 | Gemfile.local 166 | Procfile.local 167 | atlassian-ide-plugin.xml 168 | com_crashlytics_export_strings.xml 169 | config.yml 170 | config/application.yml 171 | config/database.yml 172 | config/initializers/secret_token.rb 173 | config/master.key 174 | config/secrets.yml 175 | crashlytics-build.properties 176 | crashlytics.properties 177 | nbproject 178 | xcuserdata 179 | 180 | !.circleci/config.yml 181 | !.env.example 182 | !default.mode1v3 183 | !default.mode2v3 184 | !default.pbxuser 185 | !default.perspectivev3 186 | 187 | # Temporary files 188 | *.backup 189 | *.bak 190 | *.gho 191 | *.lck 192 | *.lock 193 | *.log* 194 | *.moved-aside 195 | *.old 196 | *.ori 197 | *.orig 198 | *.rs.bk 199 | *.sqlite3 200 | *.sqlite3-journal 201 | *.swo 202 | *.swp 203 | *.tmp 204 | *~ 205 | .\#* 206 | .netrwhist 207 | \#* 208 | db-sync 209 | dump.rdb 210 | log 211 | solr/* 212 | spec/examples.txt 213 | sql-dump-*.sql 214 | tmp 215 | 216 | # Vagrant 217 | .vagrant 218 | 219 | # Always keep chamber public keys 220 | !.chamber*.pub.pem 221 | 222 | # Always keep lock files 223 | !yarn.lock 224 | !Gemfile.lock 225 | !Cargo.lock 226 | !composer.lock 227 | 228 | # Always Version .keep and .gitkeep files 229 | !.gitkeep 230 | !.keep 231 | -------------------------------------------------------------------------------- /spec/lib/chamber/namespace_set_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/namespace_set' 5 | 6 | module Chamber 7 | describe NamespaceSet do 8 | it 'can create a set from from a hash' do 9 | namespace_set = NamespaceSet.new(environment: :development, 10 | hostname: 'my host') 11 | 12 | expect(namespace_set).to eq ['development', 'my host'] 13 | end 14 | 15 | it 'can create a set from an array' do 16 | namespace_set = NamespaceSet.new([ 17 | :development, 18 | 'my host', 19 | ]) 20 | 21 | expect(namespace_set).to eq ['development', 'my host'] 22 | end 23 | 24 | it 'can create a set from a set' do 25 | original_set = Set[:development, 'my host'] 26 | namespace_set = NamespaceSet.new(original_set) 27 | 28 | expect(namespace_set).to eq ['development', 'my host'] 29 | end 30 | 31 | it 'can create itself using square-bracket notation' do 32 | namespace_set = NamespaceSet[:development, 'my host'] 33 | 34 | expect(namespace_set).to eq ['development', 'my host'] 35 | end 36 | 37 | it 'can create itself from another NamespaceSet' do 38 | original_set = NamespaceSet[:development, 'my host'] 39 | namespace_set = NamespaceSet.new(original_set) 40 | 41 | expect(namespace_set).to eq ['development', 'my host'] 42 | end 43 | 44 | it 'can create itself from a single value' do 45 | namespace_set = NamespaceSet.new(:development) 46 | 47 | expect(namespace_set).to eq %w{development} 48 | end 49 | 50 | it 'when creating itself from another NamespaceSet, it creates a new NamespaceSet' do 51 | original_set = NamespaceSet[:development, 'my host'] 52 | namespace_set = NamespaceSet.new(original_set) 53 | 54 | expect(namespace_set.object_id).not_to eq original_set.object_id 55 | end 56 | 57 | it 'when creating itself from another NamespaceSet, it does not nest the ' \ 58 | 'NamespaceSets' do 59 | 60 | original_set = NamespaceSet[:development, 'my host'] 61 | namespace_set = NamespaceSet.new(original_set) 62 | 63 | expect(namespace_set.__send__(:raw_namespaces)).not_to be_a NamespaceSet 64 | end 65 | 66 | it 'can turn itself into an array' do 67 | namespace_set = NamespaceSet[:development, 'my host'] 68 | 69 | expect(namespace_set.to_ary).to eq ['development', 'my host'] 70 | expect(namespace_set.to_a).to eq ['development', 'my host'] 71 | end 72 | 73 | it 'can combine itself with an array' do 74 | namespace_set = NamespaceSet[:development, 'my host'] 75 | other_set = Set['other value', 3] 76 | 77 | combined_set = namespace_set + other_set 78 | 79 | expect(combined_set).to eq ['development', 'my host', 'other value', '3'] 80 | end 81 | 82 | it 'can combine itself with another NamespaceSet' do 83 | namespace_set = NamespaceSet[:development, 'my host'] 84 | other_set = NamespaceSet['other value', 3] 85 | 86 | combined_set = namespace_set + other_set 87 | 88 | expect(combined_set).to eq ['development', 'my host', 'other value', '3'] 89 | end 90 | 91 | it 'does not modify the set in place if combining with another array' do 92 | namespace_set = NamespaceSet[:development, 'my host'] 93 | other_set = Set['other value', 3] 94 | combined_set = namespace_set + other_set 95 | 96 | expect(combined_set.object_id).not_to eq namespace_set.object_id 97 | end 98 | 99 | it 'can combine itself with something that can be converted to an array' do 100 | namespace_set = NamespaceSet[:development, 'my host'] 101 | other_set = (1..3) 102 | combined_set = namespace_set + other_set 103 | 104 | expect(combined_set).to eq ['development', 'my host', '1', '2', '3'] 105 | end 106 | 107 | it 'does not allow duplicate items' do 108 | namespace_set = NamespaceSet[:development, :development] 109 | 110 | expect(namespace_set).to eq %w{development} 111 | end 112 | 113 | it 'processes a value by executing it if it is a callable' do 114 | namespace_set = NamespaceSet[-> { 'callable' }] 115 | 116 | expect(namespace_set).to eq %w{callable} 117 | 118 | namespace_set = NamespaceSet.new(my_namespace: -> { 'callable' }) 119 | 120 | expect(namespace_set).to eq %w{callable} 121 | end 122 | 123 | it 'can compare itself to another NamespaceSet' do 124 | namespace_set = NamespaceSet[:development, :development] 125 | other_namespace_set = NamespaceSet[:development, :development] 126 | 127 | expect(namespace_set).to eql other_namespace_set 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chamber 2 | ================================================================================ 3 | 4 |
5 | 6 | RubyGems Version 7 | 8 | 9 | 10 | RubyGems Rank Overall 11 | 12 | 13 | 14 | RubyGems Rank Daily 15 | 16 | 17 | 18 | RubyGems Downloads 19 | 20 | 21 | 22 | Build Status 23 | 24 | 25 | 26 | Maintainability 27 | 28 |
29 | 30 |
31 | 32 | Chamber is the auto-encrypting, extremely organizable, Heroku-loving, 33 | CLI-having, non-extra-repo-needing, non-Rails-specific-ing, CI-serving 34 | configuration management library. 35 | 36 | We looked at all of the options out there and thought something was still 37 | missing, so we wrote Chamber. We made it with lots of ❤ and we hope you like it 38 | as much as we do. 39 | 40 | What Sets Chamber Apart 41 | -------------------------------------------------------------------------------- 42 | 43 | For an idea of how Chamber compares to other popular libraries, check out our 44 | [Gem Comparison][comparison]. 45 | 46 | Basic Usage 47 | -------------------------------------------------------------------------------- 48 | 49 | Before starting this guide, make sure you [install chamber][installation]. 50 | 51 | Once your app is initialized, you should have a `settings.yml` file somewhere. 52 | A lot of times it's the root of your project and sometimes it's in a framework 53 | specific location. 54 | 55 | Inside of here you can define any settings you'd like like so: 56 | 57 | ```yaml 58 | # settings.yml 59 | 60 | smtp_username: 'my_username' 61 | smtp_password: 'my_password' 62 | ``` 63 | 64 | From there you can access your settings by using the special `Chamber.dig` 65 | constant. 66 | 67 | ```ruby 68 | Chamber.dig('smtp_password') 69 | # => 'my_password' 70 | ``` 71 | 72 | If you want to encrypt a setting, prefix the setting name with `_secure_` like 73 | so: 74 | 75 | ```ruby 76 | # settings.yml 77 | 78 | smtp_username: 'my_username' 79 | _secure_smtp_password: 'my_password' 80 | ``` 81 | 82 | And then run `chamber secure`. Your settings file will have an encrypted value: 83 | 84 | ```ruby 85 | # settings.yml 86 | 87 | smtp_username: 'my_username' 88 | _secure_smtp_password: JL5hAVux4tERpv49QPWxy9H0VC2Rnk7V8/e8+1XOwPcXcoH/a7Lh253UY/v9m8nI/Onb+ZG9nZ082J4M/BmLa+f7jwMEwufIqbUhUah9eKIW8xcxlppBYpl7JVGf2HJF5TfCN44gMQNgGNzboCQXKqRyeGFm4u772Sg9V2gEx/q7qJ6F4jg7v/cltCFLmJfXA2SHA5Dai4p9L4IvMVVJGm34k5j7KOegNqpVWs2RY99cagjPuzc9VM2XSUsXgqcUJdmH8YtPW8Kqkyg0oYlRh6VQWABlWXwTZz74QjTTjqtqfoELIoFTMBDh+cCvuUTAE5m06LhlqauVrB4UnBsd5g== 89 | ``` 90 | 91 | which you still access the same way because Chamber handles the decryption for 92 | you: 93 | 94 | ```ruby 95 | Chamber.dig('smtp_password') 96 | # => 'my_password' 97 | ``` 98 | 99 | Full Reference 100 | -------------------------------------------------------------------------------- 101 | 102 | There's so much to Chamber, we couldn't put it all in the README. For the full 103 | Chamber guide, visit the [wiki][wiki]. 104 | 105 | Credits 106 | -------------------------------------------------------------------------------- 107 | 108 | Chamber was written by [Jeff Felchner][jeff-profile] and 109 | [Mark McEahern][mark-profile] 110 | 111 | ![The Kompanee][kompanee-logo] 112 | 113 | Chamber is maintained and funded by [The Kompanee, Ltd.][kompanee-site] 114 | 115 | The names and logos for The Kompanee are trademarks of The Kompanee, Ltd. 116 | 117 | License 118 | -------------------------------------------------------------------------------- 119 | 120 | Chamber is Copyright © 2014-2023 Jeff Felchner and Mark McEahern. It is free 121 | software, and may be redistributed under the terms specified in the 122 | [LICENSE][license] file. 123 | 124 | [comparison]: https://github.com/thekompanee/chamber/wiki/Gem-Comparison 125 | [jeff-profile]: https://github.com/jfelchner 126 | [kompanee-logo]: https://kompanee-public-assets.s3.amazonaws.com/readmes/kompanee-horizontal-black.png 127 | [kompanee-site]: http://www.thekompanee.com 128 | [license]: https://github.com/thekompanee/chamber/blob/master/LICENSE.txt 129 | [mark-profile]: https://github.com/m5rk 130 | [wiki]: https://github.com/thekompanee/chamber/wiki 131 | [installation]: https://github.com/thekompanee/chamber/wiki/Installation 132 | -------------------------------------------------------------------------------- /lib/chamber/namespace_set.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'set' 4 | 5 | ### 6 | # Internal: Respresents a set of namespaces which will be processed by Chamber 7 | # at various stages when settings are loaded. 8 | # 9 | # The main function that this class provides is the ability to create 10 | # a NamespaceSet from either an array-like or hash-like object and the ability 11 | # to allow callables to be passed which will then be executed. 12 | # 13 | module Chamber 14 | class NamespaceSet 15 | include Enumerable 16 | 17 | attr_reader :raw_namespaces 18 | 19 | ### 20 | # Internal: Allows for more compact NamespaceSet creation by giving a list of 21 | # namespace values. 22 | # 23 | # Examples: 24 | # 25 | # NamespaceSet['development', -> { ENV['HOST'] }] 26 | # 27 | # Returns a new NamespaceSet 28 | # 29 | def self.[](*namespace_values) 30 | new(namespace_values) 31 | end 32 | 33 | ### 34 | # Internal: Creates a new NamespaceSet from arrays, hashes and sets. 35 | # 36 | def initialize(raw_namespaces = {}) 37 | self.raw_namespaces = raw_namespaces 38 | end 39 | 40 | ### 41 | # Internal: Allows a NamespaceSet to be combined with some other array-like 42 | # object. 43 | # 44 | # It does not mutate the source NamespaceSet but rather creates a new one and 45 | # returns it. 46 | # 47 | # Examples: 48 | # 49 | # # Can be an Array 50 | # namespace_set = NamespaceSet.new ['value_1', 'value_2'] 51 | # namespace_set + ['value_3'] 52 | # # => 53 | # 54 | # # Can be a Set 55 | # namespace_set = NamespaceSet.new ['value_1', 'value_2'] 56 | # namespace_set + Set['value_3'] 57 | # # => 58 | # 59 | # # Can be a object which is convertable to an Array 60 | # namespace_set = NamespaceSet.new ['value_1', 'value_2'] 61 | # namespace_set + (1..3) 62 | # # => 63 | # 64 | # Returns a NamespaceSet 65 | # 66 | def +(other) 67 | NamespaceSet.new namespaces + other.to_a 68 | end 69 | 70 | ### 71 | # Internal: Iterates over each namespace value and allows it to be used in 72 | # a block. 73 | # 74 | def each(&block) 75 | namespaces.each(&block) 76 | end 77 | 78 | ### 79 | # Internal: Converts a NamespaceSet into an Array consisting of the namespace 80 | # values stored in the set. 81 | # 82 | # Returns an Array 83 | # 84 | def to_ary 85 | namespaces.to_a 86 | end 87 | 88 | alias to_a to_ary 89 | 90 | ### 91 | # Internal: Determines whether a NamespaceSet is equal to another array-like 92 | # object. 93 | # 94 | # Returns a Boolean 95 | # 96 | def ==(other) 97 | to_a.eql? other.to_a 98 | end 99 | 100 | ### 101 | # Internal: Determines whether a NamespaceSet is equal to another 102 | # NamespaceSet. 103 | # 104 | # Returns a Boolean 105 | # 106 | def eql?(other) 107 | other.is_a?(NamespaceSet) && 108 | namespaces == other.namespaces 109 | end 110 | 111 | protected 112 | 113 | ### 114 | # Internal: Sets the namespaces for the set from a variety of objects and 115 | # processes them by checking to see if they can be 'called'. 116 | # 117 | # Examples: 118 | # 119 | # namespace_set = NamespaceSet.new 120 | # 121 | # # Can be set to an array 122 | # namespace_set.namespaces = %w{namespace_value_1 namespace_value_2} 123 | # namespace_set.namespaces 124 | # # => ['namespace_value_1', 'namespace_value_2'] 125 | # 126 | # # Can be set to a hash 127 | # namespace_set.namespaces = { environment: 'development', 128 | # hostname: 'my host' } 129 | # namespace_set.namespaces 130 | # # => ['development', 'my host'] 131 | # 132 | # # Can be set to a NamespaceSet 133 | # namespace_set.namespaces = NamespaceSet.new('development') 134 | # namespace_set.namespaces 135 | # # => ['development'] 136 | # 137 | # # Can be set to a single value 138 | # namespace_set.namespaces = 'development' 139 | # namespace_set.namespaces 140 | # # => ['development'] 141 | # 142 | # # Can be set to a callable 143 | # namespace_set.namespaces = { environment: -> { 'called' } } 144 | # namespace_set.namespaces 145 | # # => ['called'] 146 | # 147 | # # Does not allow duplicate items 148 | # namespace_set.namespaces = %w{namespace_value namespace_value} 149 | # namespace_set.namespaces 150 | # # => ['namespace_value'] 151 | # 152 | def namespaces 153 | @namespaces ||= Set.new namespace_values.map do |value| 154 | (value.respond_to?(:call) ? value.call : value).to_s 155 | end 156 | end 157 | 158 | def raw_namespaces=(raw_namespaces) 159 | @raw_namespaces = if raw_namespaces.is_a? NamespaceSet 160 | raw_namespaces.to_ary 161 | else 162 | raw_namespaces 163 | end 164 | end 165 | 166 | private 167 | 168 | def namespace_values 169 | if raw_namespaces.respond_to? :map 170 | if raw_namespaces.respond_to? :values 171 | raw_namespaces.values 172 | else 173 | raw_namespaces 174 | end 175 | else 176 | [raw_namespaces] 177 | end 178 | end 179 | end 180 | end 181 | -------------------------------------------------------------------------------- /.rubocop_performance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Last updated to 1.22.1 4 | 5 | require: 6 | - 'rubocop-performance' 7 | 8 | ################################################################################ 9 | # PERFORMANCE 10 | ################################################################################ 11 | 12 | Performance/AncestorsInclude: 13 | Enabled: true 14 | 15 | # Mistakenly created: https://github.com/rubocop-hq/rubocop-performance/pull/199/files 16 | Performance/ArraySemiInfiniteRangeSlice: 17 | Enabled: false 18 | 19 | Performance/BigDecimalWithNumericArgument: 20 | Enabled: true 21 | 22 | Performance/BindCall: 23 | Enabled: true 24 | 25 | Performance/BlockGivenWithExplicitBlock: 26 | Enabled: true 27 | 28 | Performance/Caller: 29 | Enabled: true 30 | 31 | Performance/Casecmp: 32 | Enabled: true 33 | 34 | Performance/CaseWhenSplat: 35 | Enabled: true 36 | 37 | Performance/ChainArrayAllocation: 38 | # Disallows: Most compact chaining where performance impact is minimal 39 | Enabled: false 40 | 41 | Performance/CollectionLiteralInLoop: 42 | Enabled: true 43 | MinSize: 1 44 | Exclude: 45 | - '**/spec/**/*.rb' 46 | 47 | Performance/CompareWithBlock: 48 | Enabled: true 49 | 50 | Performance/ConcurrentMonotonicTime: 51 | Enabled: true 52 | 53 | Performance/ConstantRegexp: 54 | Enabled: true 55 | 56 | Performance/Count: 57 | Enabled: true 58 | 59 | Performance/DeletePrefix: 60 | Enabled: true 61 | 62 | Performance/DeleteSuffix: 63 | Enabled: true 64 | 65 | Performance/Detect: 66 | Enabled: true 67 | 68 | Performance/DoubleStartEndWith: 69 | Enabled: true 70 | IncludeActiveSupportAliases: true 71 | 72 | Performance/EndWith: 73 | Enabled: true 74 | 75 | Performance/FixedSize: 76 | Enabled: true 77 | 78 | Performance/FlatMap: 79 | Enabled: true 80 | 81 | Performance/InefficientHashSearch: 82 | Enabled: true 83 | 84 | Performance/IoReadlines: 85 | Enabled: true 86 | 87 | Performance/MapCompact: 88 | Enabled: true 89 | 90 | Performance/MapMethodChain: 91 | Enabled: true 92 | 93 | Performance/MethodObjectAsBlock: 94 | Enabled: true 95 | 96 | Performance/OpenStruct: 97 | Enabled: true 98 | Exclude: 99 | - '**/spec/**/*.rb' 100 | 101 | Performance/RangeInclude: 102 | Enabled: true 103 | 104 | Performance/RedundantBlockCall: 105 | Enabled: true 106 | 107 | Performance/RedundantEqualityComparisonBlock: 108 | Enabled: true 109 | AllowRegexpMatch: false 110 | 111 | Performance/RedundantMatch: 112 | Enabled: true 113 | 114 | Performance/RedundantMerge: 115 | Enabled: true 116 | 117 | Performance/RedundantSortBlock: 118 | Enabled: true 119 | 120 | Performance/RedundantSplitRegexpArgument: 121 | Enabled: true 122 | 123 | Performance/RedundantStringChars: 124 | Enabled: true 125 | 126 | Performance/RegexpMatch: 127 | Enabled: true 128 | 129 | Performance/ReverseEach: 130 | Enabled: true 131 | 132 | Performance/ReverseFirst: 133 | Enabled: true 134 | 135 | # filter_map excludes `false` unlike `compact` which does not. 136 | Performance/SelectMap: 137 | Enabled: false 138 | 139 | Performance/Size: 140 | Enabled: true 141 | 142 | Performance/SortReverse: 143 | Enabled: true 144 | 145 | Performance/Squeeze: 146 | Enabled: true 147 | 148 | Performance/StartWith: 149 | Enabled: true 150 | 151 | Performance/StringIdentifierArgument: 152 | Enabled: true 153 | 154 | Performance/StringInclude: 155 | Enabled: true 156 | 157 | Performance/StringReplacement: 158 | Enabled: true 159 | 160 | Performance/Sum: 161 | Enabled: true 162 | OnlySumOrWithInitialValue: false 163 | 164 | Performance/TimesMap: 165 | Enabled: true 166 | AutoCorrect: true 167 | 168 | Performance/UnfreezeString: 169 | Enabled: true 170 | 171 | Performance/UriDefaultParser: 172 | Enabled: true 173 | -------------------------------------------------------------------------------- /spec/lib/chamber/filters/insecure_filter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspectacular' 4 | require 'chamber/filters/insecure_filter' 5 | 6 | module Chamber 7 | module Filters 8 | describe InsecureFilter do 9 | it 'returns values which are marked as "secure" if they are unencrypted' do 10 | filtered_settings = InsecureFilter.execute(secure_key_prefix: '_secure_', 11 | data: { 12 | '_secure_my_secure_setting' => 'hello', 13 | }) 14 | 15 | expect(filtered_settings['_secure_my_secure_setting']).to match 'hello' 16 | end 17 | 18 | it 'does not return values which are not marked as "secure"' do 19 | filtered_settings = InsecureFilter.execute(secure_key_prefix: '_secure_', 20 | data: { 21 | 'my_secure_setting' => 'hello', 22 | }) 23 | 24 | expect(filtered_settings['my_secure_setting']).to be(nil) 25 | end 26 | 27 | it 'properly returns values even if they are mixed and deeply nested' do 28 | filtered_settings = InsecureFilter.execute(secure_key_prefix: '_secure_', 29 | data: { 30 | '_secure_setting' => 'hello', 31 | 'secure_setting' => 'goodbye', 32 | 'secure_group' => { 33 | '_secure_nested_setting' => 'movie', 34 | 'insecure_nested_setting' => 'dinner', 35 | }, 36 | }) 37 | 38 | expect(filtered_settings['_secure_setting']).to eql 'hello' 39 | expect(filtered_settings['secure_setting']).to be(nil) 40 | expect(filtered_settings['secure_group']['_secure_nested_setting']).to eql 'movie' 41 | expect(filtered_settings['secure_group']['insecure_nested_setting']).to be(nil) 42 | end 43 | 44 | it 'does not return values which are encrypted' do 45 | filtered_settings = \ 46 | InsecureFilter 47 | .execute( 48 | secure_key_prefix: '_secure_', 49 | data: { 50 | '_secure_setting' => 'cJbFe0NI5wknmsp2fVgpC/YeBD2pvcdVD+p0pUdnMoYTha' \ 51 | 'V4mpsspg/ZTBtmjx7kMwcF6cjXFLDVw3FxptTHwzJUd4ak' \ 52 | 'un6EZ57m+QzCMJYnfY95gB2/emEAQLSz4/YwsE4LDGydkE' \ 53 | 'jY1ZprfXznf+rU31YGDJUTf34ESz7fsQGSc9DjkBb9ao8M' \ 54 | 'v4cI7pCXkQZDwS5kLAZDf6agy1GzeL71Z8lrmQzk8QQuf/' \ 55 | '1kQzxsWVlzpKNXWS7u2CJ0sN5eINMngJBfv5ZFrZgfXc86' \ 56 | 'wdgUKc8aaoX8OQA1kKTcdgbE9NcAhNr1+WfNxMnz84XzmU' \ 57 | 'p2Y0H1jPgGkBKQJKArfQ==', 58 | 'secure_setting' => 'cJbFe0NI5wknmsp2fVgpC/YeBD2pvcdVD+p0pUdnMoYTha' \ 59 | 'V4mpsspg/ZTBtmjx7kMwcF6cjXFLDVw3FxptTHwzJUd4ak' \ 60 | 'un6EZ57m+QzCMJYnfY95gB2/emEAQLSz4/YwsE4LDGydkE' \ 61 | 'jY1ZprfXznf+rU31YGDJUTf34ESz7fsQGSc9DjkBb9ao8M' \ 62 | 'v4cI7pCXkQZDwS5kLAZDf6agy1GzeL71Z8lrmQzk8QQuf/' \ 63 | '1kQzxsWVlzpKNXWS7u2CJ0sN5eINMngJBfv5ZFrZgfXc86' \ 64 | 'wdgUKc8aaoX8OQA1kKTcdgbE9NcAhNr1+WfNxMnz84XzmU' \ 65 | 'p2Y0H1jPgGkBKQJKArfQ==', 66 | '_secure_other_setting' => 'hello', 67 | 'secure_group' => { 68 | '_secure_nested_setting' => 'cJbFe0NI5wknmsp2fVgpC/YeBD2pvc' \ 69 | 'dVD+p0pUdnMoYThaV4mpsspg/ZTBtm' \ 70 | 'jx7kMwcF6cjXFLDVw3FxptTHwzJUd4' \ 71 | 'akun6EZ57m+QzCMJYnfY95gB2/emEA' \ 72 | 'QLSz4/YwsE4LDGydkEjY1ZprfXznf+' \ 73 | 'rU31YGDJUTf34ESz7fsQGSc9DjkBb9' \ 74 | 'ao8Mv4cI7pCXkQZDwS5kLAZDf6agy1' \ 75 | 'GzeL71Z8lrmQzk8QQuf/1kQzxsWVlz' \ 76 | 'pKNXWS7u2CJ0sN5eINMngJBfv5ZFrZ' \ 77 | 'gfXc86wdgUKc8aaoX8OQA1kKTcdgbE' \ 78 | '9NcAhNr1+WfNxMnz84XzmUp2Y0H1jP' \ 79 | 'gGkBKQJKArfQ==', 80 | '_secure_other_nested_setting' => 'goodbye', 81 | 'insecure_nested_setting' => 'dinner', 82 | }, 83 | }, 84 | ) 85 | 86 | expect(filtered_settings['_secure_setting']).to be nil 87 | expect(filtered_settings['secure_setting']).to be nil 88 | expect(filtered_settings['_secure_other_setting']).to eql 'hello' 89 | expect(filtered_settings['secure_group']['_secure_nested_setting']).to be nil 90 | expect(filtered_settings['secure_group']['_secure_other_nested_setting']).to eql 'goodbye' 91 | expect(filtered_settings['secure_group']['insecure_nested_setting']).to be nil 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/chamber/filters/environment_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | 5 | require 'chamber/errors/environment_conversion' 6 | 7 | module Chamber 8 | module Filters 9 | class EnvironmentFilter 10 | attr_accessor :data, 11 | :secure_key_token 12 | 13 | ### 14 | # Internal: Allows the existing environment to be injected into the passed in 15 | # hash. The hash that is passed in is *not* modified, instead a new hash is 16 | # returned. 17 | # 18 | # This filter will also do basic value conversions from the environment 19 | # variable string to the data type defined in the YAML. For example if the 20 | # YAML value is `true`, then the conversion knows it's a Boolean. If there's 21 | # an environment varible which should override that value, it will look to see 22 | # if it is a `String` of 'true', 'false', 't', 'f', 'yes', or 'no' and perform 23 | # the appropriate conversion of that value into a Boolean. 24 | # 25 | # This will work for: 26 | # 27 | # * Booleans 28 | # * Integers 29 | # * Floats 30 | # * Arrays 31 | # 32 | # For the Arrays, it will convert the environment value by parsing the string 33 | # as YAML. Whatever the parsed value ends up being, *must* be an Array. 34 | # 35 | # Examples: 36 | # 37 | # ### 38 | # # Injects the current environment variables 39 | # # 40 | # ENV['LEVEL_ONE_1_LEVEL_TWO_1'] = 'env value 1' 41 | # ENV['LEVEL_ONE_1_LEVEL_TWO_2_LEVEL_THREE_1'] = 'env value 2' 42 | # 43 | # EnvironmentFilter.execute( 44 | # level_one_1: { 45 | # level_two_1: 'value 1', 46 | # level_two_2: { 47 | # level_three_1: 'value 2' } } ) 48 | # 49 | # # => { 50 | # 'level_one_1' => { 51 | # 'level_two_1' => 'env value 1', 52 | # 'level_two_2' => { 53 | # 'level_three_1' => 'env value 2', 54 | # } 55 | # 56 | # ### 57 | # # Can do basic value conversions based on the raw data 58 | # # 59 | # ENV['LEVEL_ONE_1_LEVEL_TWO_1'] = '1' 60 | # ENV['LEVEL_ONE_1_LEVEL_TWO_2_LEVEL_THREE_1'] = '[1, 2, 3]' 61 | # 62 | # EnvironmentFilter.execute( 63 | # level_one_1: { 64 | # level_two_1: 4, 65 | # level_two_2: { 66 | # level_three_1: [4, 5, 6] } } ) 67 | # 68 | # # => { 69 | # 'level_one_1' => { 70 | # 'level_two_1' => 1, 71 | # 'level_two_2' => { 72 | # 'level_three_1' => [1, 2, 3], 73 | # } 74 | # 75 | # ### 76 | # # Can inject environment variables if said variables are prefixed 77 | # # 78 | # ENV['PREFIX_LEVEL_TWO_1'] = 'env value 1' 79 | # ENV['PREFIX_LEVEL_TWO_2'] = 'env value 2' 80 | # 81 | # EnvironmentFilter.execute({ 82 | # level_two_1: 'value 1', 83 | # level_two_2: 'value 2' 84 | # }, 85 | # ['prefix']) 86 | # 87 | # # => { 88 | # 'level_two_1' => 'env value 1', 89 | # 'level_two_2' => 'env value 2', 90 | # } 91 | # 92 | # 93 | def self.execute(**args) 94 | new(**args).__send__(:execute) 95 | end 96 | 97 | def initialize(data:, secure_key_prefix:, **_args) 98 | self.data = data 99 | self.secure_key_token = /\A#{Regexp.escape(secure_key_prefix)}/ 100 | end 101 | 102 | protected 103 | 104 | def execute(settings = data, parent_keys = []) 105 | with_environment( 106 | settings, 107 | parent_keys, 108 | lambda do |key, value, environment_keys| 109 | { key => execute(value, environment_keys) } 110 | end, 111 | lambda do |key, value, environment_key| 112 | { 113 | key => convert_environment_value(environment_key, 114 | ENV.fetch(environment_key, nil), 115 | value), 116 | } 117 | end, 118 | ) 119 | end 120 | 121 | private 122 | 123 | def with_environment(settings, parent_keys, hash_block, value_block) 124 | environment_hash = {} 125 | 126 | settings.each_pair do |key, value| 127 | environment_key = key.to_s.gsub(secure_key_token, '') 128 | environment_keys = parent_keys.dup.push(environment_key) 129 | 130 | if value.respond_to? :each_pair 131 | environment_hash.merge!(hash_block.call(key, value, environment_keys)) 132 | else 133 | environment_key = environment_keys.join('_').upcase 134 | 135 | environment_hash.merge!(value_block.call(key, value, environment_key)) 136 | end 137 | end 138 | 139 | environment_hash 140 | end 141 | 142 | def convert_environment_value(environment_key, environment_value, settings_value) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize 143 | return settings_value unless environment_value 144 | return if %w{___nil___ ___null___}.include?(environment_value) 145 | 146 | case settings_value.class.name 147 | when 'TrueClass', 'FalseClass' 148 | case environment_value.downcase 149 | when 'false', 'f', 'no', 'off', '0' 150 | false 151 | when 'true', 't', 'yes', 'on', '1' 152 | true 153 | else 154 | fail ArgumentError, "Invalid value for Boolean: #{environment_value}" 155 | end 156 | when 'Float' 157 | Float(environment_value) 158 | when 'Time' 159 | Time.iso8601(environment_value) 160 | when 'Array' 161 | YAML.safe_load(environment_value).tap do |parsed_value| 162 | unless parsed_value.is_a?(Array) 163 | fail ArgumentError, "Invalid value for Array: #{environment_value}" 164 | end 165 | end 166 | when 'Fixnum', 'Integer' 167 | Integer(environment_value) 168 | else 169 | environment_value 170 | end 171 | rescue ArgumentError 172 | raise Chamber::Errors::EnvironmentConversion, <<~HEREDOC 173 | We attempted to convert '#{environment_key}' from '#{environment_value}' to a '#{settings_value.class.name}'. 174 | 175 | Unfortunately, this did not go as planned. Please either verify that your value is convertable 176 | or change the original YAML value to be something more generic (like a String). 177 | 178 | For more information, see https://github.com/thekompanee/chamber/wiki/Environment-Variable-Coercions 179 | HEREDOC 180 | end 181 | end 182 | end 183 | end 184 | --------------------------------------------------------------------------------