diff --git a/.circleci/config.yml b/.circleci/config.yml index b5de027..3b5a3fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,11 +5,12 @@ jobs: working_directory: ~/repo docker: - - image: circleci/ruby:2.3.6 + - image: circleci/ruby:2.5.1 environment: COVALENCE_VERSION: 0.7.8 TERRAFORM_VERSION: 0.11.7 + SOPS_VERSION: 3.0.5 steps: - checkout @@ -21,9 +22,33 @@ jobs: wget -q "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip + # Install Sops + sudo wget -q "https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux" -O /usr/local/bin/sops + sudo chmod +x /usr/local/bin/sops + # Install gem bundle bundle install - run: name: Run tests command: bundle exec rake ci:covalence + + - run: + name: Setup gem credentials + command: | + mkdir -p ~/.gem/ + cat <~/.gem/credentials + --- + :rubygems_api_key: ${RUBYGEMS_API_KEY} + EOF + chmod 600 ~/.gem/credentials + + - deploy: + name: Publish gem to rubygems + command: | + if [ "${CIRCLE_BRANCH}" == "master" ]; then + gem signin + gem build covalence.gemspec + COVALENCE_VERSION=$(ruby -e "require \"#{Dir.pwd}/lib/covalence/version\"; puts Covalence::VERSION") + gem push covalence-${COVALENCE_VERSION}.gem + fi diff --git a/.env.sample b/.env.sample index c43da94..41d2686 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,6 @@ COVALENCE_WORKSPACE = $PWD +AWS_PROFILE = "" AWS_REGION = "us-west-2" ATLAS_TOKEN = "" +SOPS_KMS_ARN = "https://github.com/mozilla/sops#2usage" +CONSUL_KV_FILE = "<$WORKSPACE_DIR/consul-kv.yml>" diff --git a/.env.test b/.env.test index 4e86063..635e811 100644 --- a/.env.test +++ b/.env.test @@ -3,5 +3,8 @@ COVALENCE_CONFIG = "spec/fixtures/covalence_spec.yaml" COVALENCE_TERRAFORM_DIR = "spec/fixtures/terraform" COVALENCE_PACKER_DIR = "spec/fixtures" AWS_REGION = "us-west-2" +AWS_PROFILE = "unifio" ATLAS_TOKEN = "" RAKE_ENV = "test" +SOPS_KMS_ARN = "" +CONSUL_KV_FILE = "spec/fixtures/data/consul-kv.yml" diff --git a/.ruby-version b/.ruby-version index e75da3e..73462a5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.6 +2.5.1 diff --git a/Gemfile.lock b/Gemfile.lock index 2f7d24b..3f0aba2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,48 +1,55 @@ PATH remote: . specs: - covalence (0.7.8) - activemodel (~> 4.2.6) - activesupport (~> 4.2.6) - aws-sdk (~> 2.9.5) - deep_merge (~> 1.0.1) - hiera (~> 3.3.0) - highline (~> 1.6.0) - json (~> 1.8.3) + covalence (0.8.0) + activemodel (~> 5.2.0) + activesupport (~> 5.2.0) + aws-sdk-s3 (~> 1) + consul_loader (~> 1.0.0) + deep_merge (~> 1.2.1) + hiera (~> 3.4.3) + highline (~> 1.7.10) + json (~> 2.1.0) rake (>= 11.1.2) rest-client (~> 2.0.0.rc3) - semantic (~> 1.4.1) - slop (~> 4.4.1) + semantic (~> 1.6.1) + slop (~> 4.6.2) virtus (~> 1.0.5) GEM remote: https://rubygems.org/ specs: - activemodel (4.2.10) - activesupport (= 4.2.10) - builder (~> 3.1) - activesupport (4.2.10) - i18n (~> 0.7) + activemodel (5.2.0) + activesupport (= 5.2.0) + activesupport (5.2.0) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) - awesome_print (1.7.0) - aws-sdk (2.9.44) - aws-sdk-resources (= 2.9.44) - aws-sdk-core (2.9.44) + awesome_print (1.8.0) + aws-eventstream (1.0.0) + aws-partitions (1.87.0) + aws-sdk-core (3.21.2) + aws-eventstream (~> 1.0) + aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.9.44) - aws-sdk-core (= 2.9.44) + aws-sdk-kms (1.5.0) + aws-sdk-core (~> 3) + aws-sigv4 (~> 1.0) + aws-sdk-s3 (1.13.0) + aws-sdk-core (~> 3, >= 3.21.2) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.0) aws-sigv4 (1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) builder (3.2.3) - byebug (9.0.6) + byebug (10.0.2) ci_reporter (2.0.0) builder (>= 2.1.2) ci_reporter_rspec (1.0.0) @@ -51,47 +58,35 @@ GEM coercible (1.0.0) descendants_tracker (~> 0.0.1) concurrent-ruby (1.0.5) + consul_loader (1.0.0) + rest-client crack (0.4.3) safe_yaml (~> 1.0.0) - deep_merge (1.0.1) + deep_merge (1.2.1) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.3) - docile (1.1.5) + docile (1.3.0) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - dotenv (2.1.2) + dotenv (2.4.0) equalizer (0.0.11) - fabrication (2.15.2) - faraday (0.9.2) - multipart-post (>= 1.2, < 3) - gemfury (0.6.0) - faraday (>= 0.9.0, < 0.10.0.pre) - highline (~> 1.6.0) - multi_json (~> 1.10) - netrc (~> 0.10.0) - thor (>= 0.14.0, < 1.0.0.pre) + fabrication (2.20.1) hashdiff (0.3.7) - hiera (3.3.3) - highline (1.6.21) + hiera (3.4.3) + highline (1.7.10) http-cookie (1.0.3) domain_name (~> 0.5) - i18n (0.9.5) + i18n (1.0.1) concurrent-ruby (~> 1.0) ice_nine (0.11.2) jmespath (1.4.0) - json (1.8.6) + json (2.1.0) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) minitest (5.11.3) - multi_json (1.13.1) - multipart-post (2.0.0) - net-scp (1.2.1) - net-ssh (>= 2.6.5) - net-ssh (4.2.0) - net-telnet (0.1.1) - netrc (0.10.3) + netrc (0.11.0) public_suffix (3.0.2) rake (12.3.1) rest-client (2.0.2) @@ -107,33 +102,18 @@ GEM rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) - rspec-its (1.2.0) - rspec-core (>= 3.0.0) - rspec-expectations (>= 3.0.0) rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) rspec-support (3.7.1) safe_yaml (1.0.4) - semantic (1.4.1) - serverspec (2.36.1) - multi_json - rspec (~> 3.0) - rspec-its - specinfra (~> 2.53) - sfl (2.3) - simplecov (0.12.0) - docile (~> 1.1.0) + semantic (1.6.1) + simplecov (0.16.1) + docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - slop (4.4.3) - specinfra (2.73.3) - net-scp - net-ssh (>= 2.7, < 5.0) - net-telnet - sfl - thor (0.20.0) + slop (4.6.2) thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) @@ -145,7 +125,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - webmock (2.0.3) + webmock (3.4.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff @@ -154,18 +134,16 @@ PLATFORMS ruby DEPENDENCIES - awesome_print (~> 1.7.0) + awesome_print (~> 1.8.0) bundler (>= 1.9.0) - byebug (~> 9.0.5) + byebug (~> 10.0.2) ci_reporter_rspec (~> 1.0.0) covalence! - dotenv (~> 2.1.0) - fabrication (~> 2.15.2) - gemfury (~> 0.6.0) + dotenv (~> 2.4.0) + fabrication (~> 2.20.1) rspec (~> 3.5) - serverspec (~> 2.36.0) - simplecov (~> 0.12.0) - webmock (~> 2.0.3) + simplecov (~> 0.16.1) + webmock (~> 3.4.1) BUNDLED WITH 1.16.1 diff --git a/README.md b/README.md index 2713e90..1aa7f22 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Covalence [![CircleCI](https://circleci.com/gh/unifio/covalence.svg?style=svg)](https://circleci.com/gh/unifio/covalence) +[![Dependency Status](https://gemnasium.com/badges/github.com/unifio/covalence.svg)](https://gemnasium.com/github.com/unifio/covalence) A tool for the management and orchestration of data used by HashiCorp infrastructure tooling. @@ -503,8 +504,11 @@ vpc_id: Covalence is packaged as a Ruby Gem. +You will probably need the following packages installed locally +- Terraform +- Packer +- Sops + Execute the following to build the gem: `$ gem build covalence.gemspec` - -Gem artifacts are hosted at https://repo.fury.io/unifio/. diff --git a/covalence.gemspec b/covalence.gemspec index de2ac60..8ec8ca6 100644 --- a/covalence.gemspec +++ b/covalence.gemspec @@ -16,40 +16,33 @@ Gem::Specification.new do |spec| spec.homepage = "https://unif.io" spec.license = "MPL-2.0" - if spec.respond_to?(:metadata) - spec.metadata['allowed_push_host'] = "https://repo.fury.io/unifio/" - else - raise "RubyGems 2.0 or newer is required to protect against public gem pushes." - end - spec.files = Dir["*.md", "#{gem_root}/**/*"] spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.require_paths = [ gem_root ] spec.required_ruby_version = '>= 2.0.0' - spec.add_dependency "deep_merge", "~> 1.0.1" - spec.add_dependency "hiera", "~> 3.3.0" - spec.add_dependency "json", "~> 1.8.3" + spec.add_dependency "deep_merge", "~> 1.2.1" + spec.add_dependency "hiera", "~> 3.4.3" + spec.add_dependency "json", "~> 2.1.0" spec.add_dependency "rest-client", "~> 2.0.0.rc3" spec.add_dependency "rake", ">= 11.1.2" - spec.add_dependency "aws-sdk", "~> 2.9.5" + spec.add_dependency "aws-sdk-s3", "~> 1" spec.add_dependency "virtus", "~> 1.0.5" - spec.add_dependency "activesupport", "~> 4.2.6" - spec.add_dependency "activemodel", "~> 4.2.6" - spec.add_dependency "semantic", "~> 1.4.1" - spec.add_dependency "slop", "~> 4.4.1" - spec.add_dependency "highline", "~> 1.6.0" + spec.add_dependency "activesupport", "~> 5.2.0" + spec.add_dependency "activemodel", "~> 5.2.0" + spec.add_dependency "semantic", "~> 1.6.1" + spec.add_dependency "slop", "~> 4.6.2" + spec.add_dependency "highline", "~> 1.7.10" + spec.add_dependency "consul_loader", "~> 1.0.0" Covalence::Helpers::SpecDependencies.dependencies.each do |name, requirement| spec.add_development_dependency name, requirement end - spec.add_development_dependency "awesome_print", "~> 1.7.0" + spec.add_development_dependency "awesome_print", "~> 1.8.0" spec.add_development_dependency "bundler", ">= 1.9.0" - spec.add_development_dependency "dotenv", "~> 2.1.0" - spec.add_development_dependency "byebug", "~> 9.0.5" - spec.add_development_dependency "serverspec", "~> 2.36.0" - spec.add_development_dependency "webmock", "~> 2.0.3" - spec.add_development_dependency "gemfury", "~> 0.6.0" - spec.add_development_dependency "fabrication", "~> 2.15.2" - spec.add_development_dependency "simplecov", "~> 0.12.0" + spec.add_development_dependency "dotenv", "~> 2.4.0" + spec.add_development_dependency "byebug", "~> 10.0.2" + spec.add_development_dependency "webmock", "~> 3.4.1" + spec.add_development_dependency "fabrication", "~> 2.20.1" + spec.add_development_dependency "simplecov", "~> 0.16.1" end diff --git a/example/Rakefile b/example/Rakefile index e5ec741..65093cc 100644 --- a/example/Rakefile +++ b/example/Rakefile @@ -2,15 +2,5 @@ require 'rake' require 'rspec/core/rake_task' require 'covalence/environment_tasks' require 'covalence/spec_tasks' -require 'consul_loader' - -def load_yaml(filename) - consul_loader = ConsulLoader::Loader.new(ConsulLoader::ConfigParser.new) - consul_server = "http://#{ENV['CONSUL_HTTP_ADDR']}" - consul_loader.load_config(filename, consul_server) -end - -desc 'Load K/V data into Consul service' -task 'consul_load' do - load_yaml("#{ENV['CONSUL_KV_FILE']}") -end +require 'covalence/consul_tasks' +require 'covalence/sops_tasks' diff --git a/lib/covalence.rb b/lib/covalence.rb index 6593145..3d0be3f 100644 --- a/lib/covalence.rb +++ b/lib/covalence.rb @@ -26,6 +26,11 @@ module Covalence PACKER_CMD = ENV['PACKER_CMD'] || "packer" + SOPS_CMD = ENV['SOPS_CMD'] || "sops" + SOPS_VERSION = ENV['SOPS_VERSION'] || (`#{SOPS_CMD} --version`.gsub(/[^\d\.]/, '') rescue "0.0.0") + SOPS_ENCRYPTED_SUFFIX = ENV['SOPS_ENCRYPTED_SUFFIX'] || "-encrypted" + SOPS_DECRYPTED_SUFFIX = ENV['SOPS_DECRYPTED_SUFFIX'] || "-decrypted" + # No-op shell command. Should not need to modify for most unix shells. DRY_RUN_CMD = (ENV['COVALENCE_DRY_RUN_CMD'] || ":") DEBUG_CLI = (ENV['COVALENCE_DEBUG'] || 'false') =~ (/(true|t|yes|y|1)$/i) diff --git a/lib/covalence/consul_tasks.rb b/lib/covalence/consul_tasks.rb new file mode 100644 index 0000000..cabc172 --- /dev/null +++ b/lib/covalence/consul_tasks.rb @@ -0,0 +1,27 @@ +require 'rake' +require 'consul_loader' +require_relative '../covalence' + +module Covalence + class ConsulTasks + extend Rake::DSL + + def self.run + desc 'Load K/V data into Consul service' + task 'consul_load' do + load_yaml("#{ENV['CONSUL_KV_FILE']}") + end + end + + class << self + private + def load_yaml(filename) + consul_loader = ConsulLoader::Loader.new(ConsulLoader::ConfigParser.new) + consul_server = "http://#{ENV['CONSUL_HTTP_ADDR']}" + consul_loader.load_config(filename, consul_server) + end + end + end +end + +Covalence::ConsulTasks.run diff --git a/lib/covalence/core/cli_wrappers/sops_cli.rb b/lib/covalence/core/cli_wrappers/sops_cli.rb new file mode 100644 index 0000000..5b8eea8 --- /dev/null +++ b/lib/covalence/core/cli_wrappers/sops_cli.rb @@ -0,0 +1,82 @@ +require 'semantic' +require 'fileutils' +require 'yaml' +require 'active_support/core_ext/object/blank' + +module Covalence + class SopsCli + + DIRECTION = { + encrypt: { + sops_option: "--encrypt", + file_search_suffix: "-decrypted", + file_replace_suffix: "-encrypted" + }, + decrypt: { + sops_option: "--decrypt", + file_search_suffix: "-encrypted", + file_replace_suffix: "-decrypted" + } + } + + def self.encrypt_path(path=default_data_dir, extension=".yaml") + modify_files(DIRECTION[:encrypt], path, extension) + end + + def self.decrypt_path(path=default_data_dir, extension=".yaml") + modify_files(DIRECTION[:decrypt], path, extension) + end + + # Clean targets all extensions by default, sounds like a more secure way to avoid commiting something accidentally + def self.clean_decrypt_path(path, extension="*", dry_run: false, verbose: true) + file_path = File.expand_path(path) + + if File.file?(file_path) + files = [file_path] + else + files = Dir.glob(File.join(file_path, "**" , "*#{DIRECTION[:decrypt][:file_replace_suffix]}#{extension}")) + end + + unless files.blank? + FileUtils.rm_f(files, { + noop: dry_run, + verbose: verbose + }) + end + end + + def self.default_data_dir + @default_data_dir ||= File.join(WORKSPACE, YAML.load_file(CONFIG).fetch(:yaml, {}).fetch(:datadir, "")) + end + + class << self + private + + # Intentionally unified the logic so that encryption and decryption would follow the + # same path and avoid logic forking + def modify_files(direction_hash, path, extension=".yaml") + if Semantic::Version.new(Covalence::SOPS_VERSION) < Semantic::Version.new("3.0.0") + raise "Sops v3.0.0 or newer required" + end + + files = [] + file_path = File.expand_path(path) + cmd = [Covalence::SOPS_CMD, direction_hash[:sops_option]] + + if File.file?(file_path) + files = [file_path] + else + files = Dir.glob(File.join(file_path, "**" , "*#{direction_hash[:file_search_suffix]}#{extension}")) + end + + files.map do |file| + dirname, basename = File.split(file) + new_file = File.join(dirname, basename.gsub(direction_hash[:file_search_suffix],direction_hash[:file_replace_suffix])) + + break unless (PopenWrapper.run(cmd, file, "> #{new_file}") == 0) + new_file + end + end + end + end +end diff --git a/lib/covalence/core/state_stores/s3.rb b/lib/covalence/core/state_stores/s3.rb index 5c0dacd..55fca40 100644 --- a/lib/covalence/core/state_stores/s3.rb +++ b/lib/covalence/core/state_stores/s3.rb @@ -1,5 +1,5 @@ require 'json' -require 'aws-sdk' +require 'aws-sdk-s3' require_relative '../../helpers/shell_interpolation' diff --git a/lib/covalence/helpers/shell_interpolation.rb b/lib/covalence/helpers/shell_interpolation.rb index ba3e9ff..a394917 100644 --- a/lib/covalence/helpers/shell_interpolation.rb +++ b/lib/covalence/helpers/shell_interpolation.rb @@ -6,7 +6,7 @@ class ShellInterpolation def self.parse_shell(input) Covalence::LOGGER.info "Evaluating requested interpolation: \"#{input}\"" - matches = input.scan(/.?\$\([^)]*\)/) + matches = input.scan(/.?\$\([^)]*\)+/) Covalence::LOGGER.debug "matches: #{matches}" matches.each do |cmd| diff --git a/lib/covalence/sops_tasks.rb b/lib/covalence/sops_tasks.rb new file mode 100644 index 0000000..0148e9c --- /dev/null +++ b/lib/covalence/sops_tasks.rb @@ -0,0 +1,33 @@ +require 'rake' +require 'consul_loader' +require_relative '../covalence' +require_relative 'core/cli_wrappers/sops_cli' + +module Covalence + class SopsTasks + extend Rake::DSL + + def self.run + desc 'Decrypt files in [:path, :extension]' + task 'sops:decrypt_path', [:path, :extension] do |t, args| + # should have defaults in just one place but rake isn't a terribly great entrypoint to centralize on + SopsCli.decrypt_path(args[:path] || SopsCli.default_data_dir, + args[:extension] || ".yaml") + end + + desc 'Encrypt files in [:path, :extension]' + task 'sops:encrypt_path', [:path, :extension] do |t, args| + SopsCli.encrypt_path(args[:path] || SopsCli.default_data_dir, + args[:extension] || ".yaml") + end + + desc 'Clean decrypt files in [:path, :extension]' + task 'sops:clean_decrypt_path', [:path, :extension] do |t, args| + SopsCli.clean_decrypt_path(args[:path] || SopsCli.default_data_dir, + args[:extension] || "*") + end + end + end +end + +Covalence::SopsTasks.run diff --git a/lib/covalence/version.rb b/lib/covalence/version.rb index b3794a0..caaaf27 100644 --- a/lib/covalence/version.rb +++ b/lib/covalence/version.rb @@ -1,3 +1,3 @@ module Covalence - VERSION = "0.7.8" + VERSION = "0.8.0" end diff --git a/spec/core/cli_wrappers/sops_cli_spec.rb b/spec/core/cli_wrappers/sops_cli_spec.rb new file mode 100644 index 0000000..f0a4bdb --- /dev/null +++ b/spec/core/cli_wrappers/sops_cli_spec.rb @@ -0,0 +1,72 @@ +require 'fileutils' +require 'tmpdir' + +require 'spec_helper' +require_relative File.join(Covalence::GEM_ROOT, 'core/cli_wrappers/sops_cli') +require_relative File.join(Covalence::GEM_ROOT, 'core/cli_wrappers/popen_wrapper') + +module Covalence + RSpec.describe SopsCli do + before(:each) do + @tmp_dir = Dir.mktmpdir + + end + + after(:each) do + FileUtils.remove_entry(@tmp_dir) + end + + it "#encrypt_path on default file extensions" do + FileUtils.touch(File.join(@tmp_dir, "yaml_file-decrypted.yaml")) + FileUtils.touch(File.join(@tmp_dir, "json_file-decrypted.json")) + cmd = [Covalence::SOPS_CMD, Covalence::SopsCli::DIRECTION[:encrypt][:sops_option]] + expect(PopenWrapper).to receive(:run).with(cmd, File.join(@tmp_dir, "yaml_file-decrypted.yaml"), anything).and_return(0) + expect(PopenWrapper).to_not receive(:run).with(cmd, File.join(@tmp_dir, "json_file-decrypted.json"), anything) + + described_class.encrypt_path(@tmp_dir) + end + + it "#encrypt_path on all file extensions" do + FileUtils.touch(File.join(@tmp_dir, "yaml_file-decrypted.yaml")) + FileUtils.touch(File.join(@tmp_dir, "json_file-decrypted.json")) + FileUtils.touch(File.join(@tmp_dir, "rando_file-decrypted")) + cmd = [Covalence::SOPS_CMD, Covalence::SopsCli::DIRECTION[:encrypt][:sops_option]] + %w(yaml_file-decrypted.yaml json_file-decrypted.json rando_file-decrypted).each do |fn| + expect(PopenWrapper).to receive(:run).with(cmd, File.join(@tmp_dir, fn), anything).and_return(0) + end + + described_class.encrypt_path(@tmp_dir, "*") + end + + it "#encrypt_path on specific file" do + FileUtils.touch(File.join(@tmp_dir, "some_random_file")) + cmd = [Covalence::SOPS_CMD, Covalence::SopsCli::DIRECTION[:encrypt][:sops_option]] + + expect(PopenWrapper).to receive(:run).with(cmd, File.join(@tmp_dir, "some_random_file"), anything).and_return(0) + described_class.encrypt_path(File.join(@tmp_dir, "some_random_file")) + end + + it "#decrypt_path on default file extensions" do + FileUtils.touch(File.join(@tmp_dir, "yaml_file-encrypted.yaml")) + FileUtils.touch(File.join(@tmp_dir, "json_file-encrypted.json")) + cmd = [Covalence::SOPS_CMD, Covalence::SopsCli::DIRECTION[:decrypt][:sops_option]] + expect(PopenWrapper).to receive(:run).with(cmd, File.join(@tmp_dir, "yaml_file-encrypted.yaml"), anything).and_return(0) + expect(PopenWrapper).to_not receive(:run).with(cmd, File.join(@tmp_dir, "json_file-encrypted.json"), anything) + + described_class.decrypt_path(@tmp_dir) + end + + it "#clean_decrypt_path" do + FileUtils.touch(File.join(@tmp_dir, "yaml_file-decrypted.yaml")) + FileUtils.touch(File.join(@tmp_dir, "json_file-decrypted.json")) + FileUtils.touch(File.join(@tmp_dir, "random_file")) + FileUtils.touch(File.join(@tmp_dir, "random_file-decrypted")) + FileUtils.touch(File.join(@tmp_dir, "json_file-encrypted.json")) + + expect(Dir[File.join(@tmp_dir, "*")].length).to eq(5) + described_class.clean_decrypt_path(@tmp_dir, verbose: false) + expect(Dir[File.join(@tmp_dir, "*")].length).to eq(2) + expect(Dir[File.join(@tmp_dir, "*")].map{|f| File.basename(f)}).to include("random_file", "json_file-encrypted.json") + end + end +end diff --git a/spec/fixtures/covalence_spec.yaml b/spec/fixtures/covalence_spec.yaml index a8314ca..d9c5217 100644 --- a/spec/fixtures/covalence_spec.yaml +++ b/spec/fixtures/covalence_spec.yaml @@ -8,6 +8,7 @@ :hierarchy: - "stacks/%{environment}-%{stack}" + - "secure/contexts/%{environment}-%{stack}-decrypted" - "envs/%{environment}" - 'envs' - 'global' diff --git a/spec/fixtures/data/consul-kv.yml b/spec/fixtures/data/consul-kv.yml new file mode 100644 index 0000000..e69de29 diff --git a/spec/fixtures/data/secure/contexts/example-myapp-encrypted.yaml b/spec/fixtures/data/secure/contexts/example-myapp-encrypted.yaml new file mode 100644 index 0000000..cfb5875 --- /dev/null +++ b/spec/fixtures/data/secure/contexts/example-myapp-encrypted.yaml @@ -0,0 +1,13 @@ +myapp::vars: + some_secret: ENC[AES256_GCM,data:Or9AJCcuEL+u,iv:ZasjAjIKRnM5P2YmCvfYSkTq5+M+u7JjX6/pwEslbwo=,tag:t+6DKhlCy6DhknPxciB/7Q==,type:str] +sops: + kms: + - arn: arn:aws:kms:us-east-1:261363125332:key/0c9d2ef3-080f-49d9-b64b-4526d0ab0ebb + created_at: '2018-05-24T13:00:36Z' + enc: AQICAHjwKGLaMcnhX2xwWYTzol4tBXI+C7D3xdfj5BJ5MghHBQGRHCRospbL3i2rCdEQaVZqAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMtvzIF7WyT31DF0xWAgEQgDtCcU1sc4PQaTTsWf+ZRKs71OCLxlpiguYm53ivHWHMJ3adiN2bn0HgGtNXzBozBrkBm9BscJN570hI9g== + gcp_kms: [] + lastmodified: '2018-05-24T13:00:36Z' + mac: ENC[AES256_GCM,data:CDDbadJ2fqqMWCn2PMGebAlYHc2wHypWxcltV0p8/QczeeuxLSuA2pymWvTMpazx6xItrY9X9G48EGM7uJjy644uOc3KYrugYbK6TQKXmzbQmETr5R48v+OFMcXZYwlPyBuOAAwnDx6kkQPjkyYmMVJP7AK7/48mFX8fvBBvjYc=,iv:RcPF37OZynsPDHjmX05FjvNkVHm8NUvXLZSPYWfnE8c=,tag:zxbd3jgXgtWKCRaxWsldFw==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.0.5 diff --git a/spec/helpers/shell_interpolation_spec.rb b/spec/helpers/shell_interpolation_spec.rb new file mode 100644 index 0000000..7234b80 --- /dev/null +++ b/spec/helpers/shell_interpolation_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' +require_relative File.join(Covalence::GEM_ROOT, 'helpers/shell_interpolation') + +module Covalence + module Helpers + RSpec.describe ShellInterpolation do + describe "#parse_shell" do + let(:simple_shell_input) { "$(echo 'simple')" } + let(:nested_shell_input) { "$(echo $((20000 + 12345 % 65535)))" } + + it "should succeed with simple_shell_input" do + expect(Open3).to receive(:capture2e).with(anything, "echo \"#{simple_shell_input}\"").and_call_original + expect(described_class.parse_shell(simple_shell_input)).to eq("simple") + end + + + it "should succeed with nested_shell_input" do + expect(Open3).to receive(:capture2e).with(anything, "echo \"#{nested_shell_input}\"").and_call_original + expect(described_class.parse_shell(nested_shell_input)).to eq("32345") + end + end + end + end +end diff --git a/spec/rake/consul_tasks_spec.rb b/spec/rake/consul_tasks_spec.rb new file mode 100644 index 0000000..d3217ee --- /dev/null +++ b/spec/rake/consul_tasks_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +require_relative File.join(Covalence::GEM_ROOT, 'consul_tasks') +require_relative '../shared_contexts/rake.rb' + +module Covalence + describe ConsulTasks do + let(:task_files) { 'consul_tasks.rb' } + + # TODO: this test can use better bite + describe "consul_load" do + include_context "rake" + + it "cleans the workspace" do + expect_any_instance_of(ConsulLoader::Loader).to receive(:load_config).with("spec/fixtures/data/consul-kv.yml", "http://") + subject.invoke + end + end + end +end diff --git a/spec/rake/sops_tasks_spec.rb b/spec/rake/sops_tasks_spec.rb new file mode 100644 index 0000000..b828f87 --- /dev/null +++ b/spec/rake/sops_tasks_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require 'fileutils' + +require_relative File.join(Covalence::GEM_ROOT, 'sops_tasks') +require_relative File.join(Covalence::GEM_ROOT, 'core/cli_wrappers/popen_wrapper') +require_relative '../shared_contexts/rake.rb' + + +module Covalence + describe SopsTasks do + let(:task_files) { 'sops_tasks.rb' } + + describe "sops:decrypt_path" do + include_context "rake" + + it "sops:decrypt_path decrypts *.yaml files in the data directory by default" do + cmd = [Covalence::SOPS_CMD, Covalence::SopsCli::DIRECTION[:decrypt][:sops_option]] + + expect(PopenWrapper).to receive(:run).with(cmd, File.expand_path("./spec/fixtures/data/secure/contexts/example-myapp-encrypted.yaml"), anything).and_return(0) + Rake::Task["sops:decrypt_path"].invoke + end + + it "sops:decrypt_path decrypts specific file" do + cmd = [Covalence::SOPS_CMD, Covalence::SopsCli::DIRECTION[:decrypt][:sops_option]] + + expect(PopenWrapper).to receive(:run).with(cmd, File.expand_path("./spec/fixtures/data/consul-kv.yml"), anything).and_return(0) + Rake::Task["sops:decrypt_path"].invoke("spec/fixtures/data/consul-kv.yml") + end + + it "sops:encrypt_path encrypts *.yaml files in the data directory by default" do + expect(PopenWrapper).to_not receive(:run) + # there's no -decrypted.yaml files in the data path + Rake::Task["sops:encrypt_path"].invoke + end + + it "sops:clean_decrypt_path should remove -decrypted* files in the data directory" do + expect(FileUtils).to_not receive(:rm_f) + # there's no -decrypted.yaml files in the data path + Rake::Task["sops:clean_decrypt_path"].invoke + end + end + end +end