cici 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e0e0d27ce6125fb072e36aa5a3e197bd3ad70742d5171c2f2f360b771179d23a
4
+ data.tar.gz: bd919b3ef9990aa2952461ee646febdb9c803fbafe2c2f298d5168ea0b286731
5
+ SHA512:
6
+ metadata.gz: d1f5f125bbcfb71b5975f1cec0a5aeb7a442af524ceca952d6e8583da752bfb79627397958768bacd88dc89aa44c4333053f0f8fe76a5de58c6b5a381fddd735
7
+ data.tar.gz: 71614938679b2d015f7224e5d4d4f754d2d116a8a687fe3a5b36fed1aded3e429720fe3640b8475a7703d4e1acae5e73d17bf68d2cbb320c456d143a3d7e27be
@@ -0,0 +1,8 @@
1
+ ## [0.1.0] - 2019-12-17
2
+
3
+ ### Added
4
+ - CLI able to compress and encrypt directory.
5
+ - CLI able to decrypt, uncompress, and copy files to destinations.
6
+ - CLI reads from yml config file to control whole program.
7
+ - CLI adds entries to .gitignore so secrets do not get added to repo by accident.
8
+ - CLI able to encrypt/decrypt a default set of files or a separate collection of files.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019 Levi Bostian <levi.bostian@gmail.com>
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.
@@ -0,0 +1,212 @@
1
+ [![Gem](https://img.shields.io/gem/v/cici.svg)](https://rubygems.org/gems/cici)
2
+ [![Travis (.com)](https://travis-ci.com/levibostian/cici.svg?branch=master)](https://travis-ci.com/levibostian/cici)
3
+ [![GitHub](https://img.shields.io/github/license/levibostian/cici.svg)](https://github.com/levibostian/cici)
4
+
5
+ # cici
6
+
7
+ *Confidential Information for Continuous Integration (CICI)*
8
+
9
+ When environment variables are not enough and you need to store secrets within files, `cici` is your friend. Store secret files in your source code repository with ease.
10
+
11
+ *Note: Can be used without a CI server, but tool is primarily designed for your CI server to decrypt your secret files for deployment.*
12
+
13
+ # What is cici?
14
+
15
+ `cici` is a CLI program where you can encrypt a directory of confidential files on your local machine, then decrypt that directory of files on a CI server with great ease and flexibility. Store secrets in your source code, easily without checking those secrets into source control.
16
+
17
+ # Why use cici?
18
+
19
+ `cici` was inspired by Travis-CI's ability to encrypt files, but it's only limited to encrypting 1 file, per Travis repository. We can get around the 1 file limitation because we can just compress a directory of files into 1 compressed file using `zip` or `tar`. Well, that's great, but what about when we get to the CI server and we need to decrypt those secret files and then copy them from their original source to their final destination? It can start to get complex.
20
+
21
+ It would be awesome if we could simply write 1 command on the CI server: `cici decrypt` and automatically for us, the secret files our project depends on will be decrypted and then each secret file is copied to their destination in the source code. Nice!
22
+
23
+ But what if we have a production and a staging server? Easy. `cici decrypt --set production` or `cici decrypt --set staging`. `cici` can be configured with any number of sets of files.
24
+
25
+ Besides this simplicity and power, `cici` provides some nice features:
26
+ 1. Use `cici` with any CI service or git hosting service. It's not opinionated. You don't even need to use a CI service, really, if you just want to store private files in source code.
27
+ 2. `cici` will add entries to your `.gitignore` file for you to make sure you don't accidentally add secrets to your git repo.
28
+ 3. Full flexibility of where your secrets are stored with a configuration file you check into source control.
29
+
30
+ # Getting started
31
+
32
+ * Install this tool:
33
+
34
+ ```
35
+ gem install cici
36
+ ```
37
+
38
+ * Config. Let's use an example to explain the rest of the guide on getting started.
39
+
40
+ Let's say that you're building an app with the following secret files required to compile your project:
41
+ 1. `.env`
42
+ 2. `src/firebase/firebase-secrets.json`
43
+ 3. `App/GoogleService-Info.plist`
44
+
45
+ Let's also say that we have a production and a beta app. 2 separate environments that require the same 3 files for each environment.
46
+
47
+ All you need to do is...
48
+ 1. Create a `secrets/` directory in your project source code with this file structure:
49
+ ```
50
+ secrets/
51
+ .env
52
+ src/
53
+ firebase/
54
+ firebase-secrets.json
55
+ App/
56
+ GoogleService-Info.plist
57
+ beta/
58
+ .env
59
+ src/
60
+ firebase/
61
+ firebase-secrets.json
62
+ App/
63
+ GoogleService-Info.plist
64
+ ```
65
+
66
+ 2. Create a `.cici.yml` config file in the root of your project with the following:
67
+
68
+ ```yml
69
+ default:
70
+ secrets:
71
+ - ".env"
72
+ - "src/firebase/firebase-secrets.json"
73
+ - "App/GoogleService-Info.plist"
74
+ sets:
75
+ beta:
76
+ ```
77
+
78
+ This config file here defines a default set of files that are secrets and also states that we have a set of files besides the default for "beta". `cici` requires you state a default set of secret files. It's up to you to decide what that default is. In this example, we decided that production should be the default set. You can have a development environment be your default. Then all other sets you need, define those in `sets` in the config.
79
+
80
+ * Time to encrypt!
81
+
82
+ On your local development machine, run the command: `cici encrypt`. You will know the command ran successfully when you see "Success!" with further instructions of what to do next.
83
+
84
+ Make sure to follow the instructions printed out after the command so you can successfully decrypt. This includes setting *secret* environment variables on your CI machine (or whatever machine you're decrypting the data). Note: Make sure to keep these environment variables a secret. Follow the instructions for your given CI service to create environment variables that are not publicly viewable.
85
+
86
+ Here are some instructions for some CI providers. Add yours if you don't see it below:
87
+ * [Travis-CI](https://docs.travis-ci.com/user/environment-variables/#defining-encrypted-variables-in-travisyml)
88
+
89
+ * Now, it's time to decrypt. After you add the secret environment variables above, you need to run one of the following commands on the CI server:
90
+
91
+ ```
92
+ cici decrypt
93
+ ```
94
+
95
+ ...for the default production environment...
96
+
97
+ or,
98
+
99
+ ```
100
+ cici decrypt --set beta
101
+ ```
102
+
103
+ ...for the beta environment.
104
+
105
+ Done! What `cici` has done is (1) decrypted the encrypted file you made with the encryption step, (2) taken the production set of files or the beta set of files and copied them from the "secrets" directory into your project's source code where they belong.
106
+
107
+ So, if you have the following configuration file:
108
+
109
+ ```yml
110
+ default:
111
+ secrets:
112
+ - ".env"
113
+ - "src/firebase/firebase-secrets.json"
114
+ ```
115
+
116
+ and you run `cici decrypt`, `cici` will perform the following copy operations for you:
117
+
118
+ 1. `secrets/.env` -> `.env`
119
+ 2. `secrets/src/firebase/firebase-secrets.json` -> `src/firebase/firebase-secrets.json`
120
+
121
+ and if you run `cici decrypt --set beta`, `cici` will perform the following copy operations for you:
122
+
123
+ 1. `secrets/beta/.env` -> `.env`
124
+ 2. `secrets/beta/src/firebase/firebase-secrets.json` -> `src/firebase/firebase-secrets.json`
125
+
126
+ You're all done! I hope you enjoy `cici`.
127
+
128
+ # Advanced configuration
129
+
130
+ Here is a more advanced configuration file including all options the config file has to offer:
131
+
132
+ ```yml
133
+ path: "_secrets"
134
+ default:
135
+ secrets:
136
+ - "file.txt"
137
+ - "path/file2.txt"
138
+ sets:
139
+ production:
140
+ path: "_production"
141
+ staging:
142
+ secrets:
143
+ - "file3.txt"
144
+ output: "secrets_cici"
145
+ skip_gitignore: false
146
+ ```
147
+
148
+ Here is a breakdown of this file:
149
+
150
+ ```yml
151
+ path: (optional, default 'secrets') - the name of the directory your secrets are stored.
152
+ default: (required) - specifies a default set of files you want to encrypt/decrypt
153
+ secrets:
154
+ - "file.txt"
155
+ - "path/file2.txt"
156
+ sets: (optional) - specify a unique collection of files to encrypt/decrypt
157
+ production: name of a set used as CLI argument to decrypt
158
+ path: (optional, default name of set) - subdirectory within "path" to store files for this set
159
+ staging: another set
160
+ secrets: (optional, default is default secrets within subdirectory) set of files to encrypt/decrypt.
161
+ - "file3.txt"
162
+ output: "secrets_cici" (optional, default, "secrets") - output file name when secrets compressed
163
+ skip_gitignore: (optional, default true) - have cici add rules to .gitignore automatically or not for you.
164
+ ```
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ $> bundle install
170
+ ```
171
+
172
+ You're ready to start developing!
173
+
174
+ ##### Lint
175
+
176
+ ```
177
+ bundle exec rake lint
178
+ ```
179
+
180
+ ##### Build/install
181
+
182
+ ```
183
+ bundle exec rake install
184
+ bundle exec cici # You're running cici!
185
+ ```
186
+
187
+ Or,
188
+
189
+ ```
190
+ bundle exec rake build; gem install cici*.gem
191
+ cici # you have installed cici to your whole machine!
192
+ ```
193
+
194
+ ## Deployment
195
+
196
+ This gem is setup automatically to deploy to RubyGems on a git tag deployment.
197
+
198
+ * Add `RUBYGEMS_KEY` secret to Travis-CI's settings.
199
+ * Make a new git tag, push it up to GitHub. Travis will deploy for you.
200
+
201
+ ## Author
202
+
203
+ * Levi Bostian - [GitHub](https://github.com/levibostian), [Twitter](https://twitter.com/levibostian), [Website/blog](http://levibostian.com)
204
+
205
+ ![Levi Bostian image](https://gravatar.com/avatar/22355580305146b21508c74ff6b44bc5?s=250)
206
+
207
+ ## Contribute
208
+
209
+ cici is open for pull requests. Check out the [list of issues](https://github.com/levibostian/cici/issues) for tasks I am planning on working on. Check them out if you wish to contribute in that way.
210
+
211
+ **Want to add features?** Before you decide to take a bunch of time and add functionality to the library, please, [create an issue]
212
+ (https://github.com/levibostian/cici/issues/new) stating what you wish to add. This might save you some time in case your purpose does not fit well in the use cases of this project.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'cici'
5
+
6
+ CICI::CLI.new
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cici/cli'
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'optparse'
5
+ require 'set'
6
+ require 'pathname'
7
+ require_relative './ui'
8
+ require_relative './util'
9
+ require_relative './encrypt'
10
+ require_relative './decrypt'
11
+ require_relative './version'
12
+ require_relative './config'
13
+
14
+ module CICI
15
+ Options = Struct.new(:verbose, :debug, :help, :set)
16
+
17
+ class CLI
18
+ def initialize
19
+ @options = parse_options
20
+
21
+ @ui = CICI::UI.new(@options.verbose, @options.debug)
22
+ @ui.debug("Options: #{@options}")
23
+
24
+ @config = CICI::Config.new(@ui)
25
+ @config.load
26
+
27
+ run_command
28
+ end
29
+
30
+ def run_command
31
+ case ARGV[0]
32
+ when 'encrypt'
33
+ encrypt
34
+ when 'decrypt'
35
+ decrypt
36
+ else
37
+ @ui.fail('Command invalid.')
38
+ print_help(1)
39
+ end
40
+ end
41
+
42
+ def print_help(exit_code)
43
+ puts @options.help
44
+ exit exit_code
45
+ end
46
+
47
+ def parse_options
48
+ options = Options.new
49
+ options.verbose = false
50
+ options.debug = false
51
+
52
+ opt_parser = OptionParser.new do |opts|
53
+ opts.banner = 'Usage: cici encrypt|decrypt [options]'
54
+
55
+ opts.on('-v', '--version', 'Print version') do
56
+ puts CICI::Version.get
57
+ exit
58
+ end
59
+ opts.on('--verbose', 'Verbose output') do
60
+ options.verbose = true
61
+ end
62
+ opts.on('--debug', 'Debug output (also turns on verbose)') do
63
+ options.verbose = true
64
+ options.debug = true
65
+ end
66
+ opts.on('--set SET_NAME', 'Set to decrypt (Note: option ignored for encrypt command)') do |set_name|
67
+ options.set = set_name
68
+ end
69
+ opts.on('-h', '--help', 'Prints this help') do
70
+ puts opts
71
+ exit
72
+ end
73
+ end
74
+
75
+ help = opt_parser.help
76
+ options.help = help
77
+ abort(help) if ARGV.empty?
78
+
79
+ opt_parser.parse!(ARGV)
80
+
81
+ options
82
+ end
83
+
84
+ def encrypt
85
+ CICI::Encrypt.new(@ui, @config).start
86
+ end
87
+
88
+ def decrypt
89
+ CICI::Decrypt.new(@ui, @config).start(@options.set)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'set'
5
+
6
+ module CICI
7
+ class Config
8
+ def initialize(ui)
9
+ @ui = ui
10
+ end
11
+
12
+ def load
13
+ config_file_name = '.cici.yml'
14
+
15
+ @ui.fail("Cannot find config file, #{config_file_name} in current directory.") unless File.file?(config_file_name)
16
+
17
+ config_file_contents = File.read(config_file_name)
18
+ @config = YAML.safe_load(config_file_contents)
19
+
20
+ @ui.verbose("Loaded config from file, #{config_file_name}")
21
+ @ui.debug("Config: #{@config}")
22
+ end
23
+
24
+ # Functions below are to pull out parts from the config file
25
+
26
+ # Get "path", or default value
27
+ def base_path
28
+ @config['path'] || 'secrets'
29
+ end
30
+
31
+ # Gets default array of secrets. Each secrets includes 'base_path' so they each look like:
32
+ # "secrets/path_to_file/file.txt"
33
+ def default_secrets
34
+ default_secrets_without_base_path.map { |secret_path| Pathname.new(base_path).join(secret_path).to_s }
35
+ end
36
+
37
+ # Same as default_secrets(), but omit "base_path" inclusion. So you get raw entires from config file.
38
+ def default_secrets_without_base_path
39
+ secrets = []
40
+ return secrets unless @config.key? 'default'
41
+
42
+ return @config['default']['secrets'] if @config['default'].key? 'secrets'
43
+ end
44
+
45
+ # Get a Hash for the set from the config file
46
+ def set(name)
47
+ @ui.fail("Set, #{name}, does not exist in config file.") unless @config['sets'].key? name
48
+
49
+ set = @config['sets'][name]
50
+
51
+ set = {} if set.nil?
52
+
53
+ set
54
+ end
55
+
56
+ # Gets the "base_path" for where all secrets will be stored for a set.
57
+ # If set name is `production` and base path is `secrets/`, this function could return: "secrets/production/"
58
+ def path_for_set(set_name)
59
+ set = set(set_name)
60
+ path = Pathname.new(base_path)
61
+
62
+ directory = set.key?('path') ? set['path'] : set_name
63
+ path = path.join(directory)
64
+
65
+ path.to_s
66
+ end
67
+
68
+ # Same as secrets_for_set(), but omit "base_path" inclusion. So you get raw entires from config file.
69
+ def secrets_for_set_without_base_path(set_name)
70
+ set = set(set_name)
71
+ return set['secrets'] if set.key? 'secrets'
72
+
73
+ default_secrets_without_base_path
74
+ end
75
+
76
+ # Gets array of secrets for a set. Each secrets includes 'base_path' so they each look like:
77
+ # "secrets/name-of-set/path_to_file/file.txt"
78
+ def secrets_for_set(set_name)
79
+ secrets_for_set_without_base_path(set_name).map { |secret_path| Pathname.new(path_for_set(set_name)).join(secret_path).to_s }
80
+ end
81
+
82
+ # Should skip gitignore operation?
83
+ def skip_gitignore?
84
+ skip = false
85
+ return @config['skip_gitignore'] if @config.key? 'skip_gitignore'
86
+
87
+ skip
88
+ end
89
+
90
+ # Get array of all secrets, including their base paths. So, a collection of files in the secrets directory to compress.
91
+ def all_secrets
92
+ secrets = Set[]
93
+ secrets.merge(default_secrets)
94
+ sets.keys.each { |set_key| secrets.merge(secrets_for_set(set_key)) }
95
+
96
+ secrets.to_a
97
+ end
98
+
99
+ # Same as all_secrets(), but without base paths. So, a collection of files in their original source locations.
100
+ def all_secrets_original_paths
101
+ secrets = Set[]
102
+ secrets.merge(default_secrets_without_base_path)
103
+ sets.keys.each { |set_key| secrets.merge(secrets_for_set_without_base_path(set_key)) }
104
+
105
+ secrets.to_a
106
+ end
107
+
108
+ # Hash of all sets
109
+ def sets
110
+ sets = {}
111
+ sets = @config['sets'] if @config.key? 'sets'
112
+ sets
113
+ end
114
+
115
+ # outout file name. Includes file extension
116
+ def output_file
117
+ output_file = 'secrets.tar'
118
+ output_file = "#{@config['output']}.tar" unless @config['output'].nil?
119
+
120
+ output_file
121
+ end
122
+
123
+ # output file name plus encryption file extension
124
+ def output_file_encrypted
125
+ "#{output_file}.enc"
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CICI
4
+ DECRYPT_KEY_ENV_VAR = 'CICI_DECRYPT_KEY'
5
+ DECRYPT_IV_ENV_VAR = 'CICI_DECRYPT_IV'
6
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'optparse'
5
+ require 'set'
6
+ require 'pathname'
7
+ require_relative './ui'
8
+ require_relative './util'
9
+ require_relative './constants'
10
+ require 'base64'
11
+ require 'openssl'
12
+ require 'fileutils'
13
+
14
+ module CICI
15
+ class Decrypt
16
+ include CICI
17
+
18
+ def initialize(ui, config)
19
+ @ui = ui
20
+ @config = config
21
+ @util = CICI::Util.new(@ui)
22
+ end
23
+
24
+ def start(set)
25
+ @set = set
26
+
27
+ assert_encrypted_secret_exist
28
+ decrypt
29
+ decompress
30
+ copy_files
31
+
32
+ @ui.success('Files successfully decrypted and copied to their destination!')
33
+ end
34
+
35
+ private
36
+
37
+ def assert_encrypted_secret_exist
38
+ @ui.fail("Encrypted secrets file, #{@config.output_file_encrypted}, does not exist") unless File.file?(@config.output_file_encrypted)
39
+ end
40
+
41
+ def decrypt
42
+ @ui.verbose('Decrypting secrets encrypted file.')
43
+
44
+ decipher = OpenSSL::Cipher.new('AES-256-CBC')
45
+ decipher.decrypt
46
+ decipher.key = Base64.decode64(@util.get_env(CICI::DECRYPT_KEY_ENV_VAR))
47
+ decipher.iv = Base64.decode64(@util.get_env(CICI::DECRYPT_IV_ENV_VAR))
48
+
49
+ plain = decipher.update(File.read(@config.output_file_encrypted)) + decipher.final
50
+ File.write(@config.output_file, plain)
51
+ end
52
+
53
+ def decompress
54
+ @ui.verbose('Decompressing compressed file.')
55
+
56
+ @util.run_command("tar xvf #{@config.output_file}")
57
+ end
58
+
59
+ def copy_files
60
+ @ui.verbose('Copying files to their final destination')
61
+
62
+ copy_file = lambda { |path, secrets_path|
63
+ source = Pathname.new(secrets_path).join(path).to_s
64
+ destination = path
65
+
66
+ @ui.verbose("Copying file from #{source} to #{destination}")
67
+
68
+ parent_directory = Pathname.new(destination).expand_path.dirname.to_s
69
+
70
+ @ui.debug("mkdir -p for: #{parent_directory}")
71
+ FileUtils.mkdir_p(parent_directory)
72
+ @ui.debug("cp -r for, source: #{source}, destination: #{destination}")
73
+ FileUtils.cp_r(source, destination)
74
+ }
75
+
76
+ if @set.nil?
77
+ @config.default_secrets_without_base_path.each do |secret|
78
+ copy_file.call(secret, @config.base_path)
79
+ end
80
+ else
81
+ @config.secrets_for_set_without_base_path(@set).each do |secret|
82
+ copy_file.call(secret, @config.path_for_set(@set))
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'optparse'
5
+ require 'set'
6
+ require 'pathname'
7
+ require_relative './ui'
8
+ require_relative './util'
9
+ require_relative './constants'
10
+ require 'openssl'
11
+ require 'base64'
12
+
13
+ module CICI
14
+ class Encrypt
15
+ include CICI
16
+
17
+ def initialize(ui, config)
18
+ @ui = ui
19
+ @config = config
20
+ @util = CICI::Util.new(@ui)
21
+ end
22
+
23
+ def start
24
+ assert_secret_files_exist
25
+ compress
26
+ assert_files_in_gitignore
27
+ encrypt
28
+ end
29
+
30
+ private
31
+
32
+ def assert_secret_files_exist
33
+ @ui.verbose('Asserting secret files exist')
34
+
35
+ assert_file_exists = lambda { |file|
36
+ @ui.debug("Checking #{file} exists...")
37
+
38
+ @ui.fail("File or directory at path #{file} does not exist. Can't encrypt your secrets with missing secrets.") unless File.exist?(file)
39
+ }
40
+
41
+ @ui.verbose("Checking secrets exist in #{@config.base_path} directory.")
42
+
43
+ @config.all_secrets.each do |file|
44
+ assert_file_exists.call(file)
45
+ end
46
+ end
47
+
48
+ def compress
49
+ @ui.verbose('Compressing secrets...')
50
+
51
+ @util.run_command("tar cvf #{@config.output_file} #{@config.base_path}")
52
+ end
53
+
54
+ def encrypt
55
+ @ui.verbose("Encrypting #{@config.output_file} to file #{@config.output_file_encrypted}")
56
+
57
+ aes = OpenSSL::Cipher.new('AES-256-CBC')
58
+ data = File.binread(@config.output_file)
59
+ aes.encrypt
60
+ key = aes.random_key
61
+ iv = aes.random_iv
62
+ File.write(@config.output_file_encrypted, aes.update(data) + aes.final)
63
+
64
+ @ui.success('Success! Now, you need to follow these last few steps:')
65
+ @ui.success("1. Make sure to add #{@config.output_file_encrypted} to your source code repository")
66
+ @ui.success("2. Create a *secret* environment variable with key: #{CICI::DECRYPT_KEY_ENV_VAR} with value: #{Base64.encode64(key).strip}")
67
+ @ui.success("3. Create a *secret* environment variable with key: #{CICI::DECRYPT_IV_ENV_VAR} with value: #{Base64.encode64(iv).strip}")
68
+ end
69
+
70
+ def assert_files_in_gitignore
71
+ ignore_file_name = '.gitignore'
72
+
73
+ if @config.skip_gitignore? || !File.exist?(ignore_file_name)
74
+ @ui.verbose('Skipping adding entries to .gitignore file')
75
+ return
76
+ end
77
+
78
+ @ui.verbose("Adding entries to #{ignore_file_name} file")
79
+
80
+ current_gitignore_file_contents = Set[]
81
+ File.foreach(ignore_file_name).with_index do |line, _line_num|
82
+ line = line.strip
83
+ current_gitignore_file_contents = current_gitignore_file_contents.add(line)
84
+ end
85
+ @ui.debug("current contents of #{ignore_file_name}: #{current_gitignore_file_contents}")
86
+
87
+ new_gitignore_additions = current_gitignore_file_contents.clone
88
+ add_to_gitignore = lambda { |file|
89
+ new_gitignore_additions = new_gitignore_additions.add(file)
90
+ }
91
+
92
+ # Add all but the encrypted output file as that is required for decryption
93
+ add_to_gitignore.call(@config.output_file)
94
+ add_to_gitignore.call(@config.base_path)
95
+ @config.all_secrets_original_paths.each do |secret_file|
96
+ add_to_gitignore.call(secret_file)
97
+ end
98
+
99
+ new_gitignore_additions -= current_gitignore_file_contents
100
+ @ui.debug("additions to #{ignore_file_name}: #{new_gitignore_additions}")
101
+
102
+ new_gitignore_additions = new_gitignore_additions.to_a
103
+ unless new_gitignore_additions.empty? # only write if something to add
104
+ @ui.debug("writing new #{ignore_file_name} additions: #{new_gitignore_additions}")
105
+ gitignore_file_prepended_additions = new_gitignore_additions.join("\n") + "\n\n" + File.read(ignore_file_name)
106
+
107
+ File.write(ignore_file_name, gitignore_file_prepended_additions)
108
+ end
109
+
110
+ @ui.verbose("Done adding entries to #{ignore_file_name}")
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'optparse'
5
+
6
+ module CICI
7
+ class UI
8
+ def initialize(verbose, debug)
9
+ @verbose = verbose
10
+ @debug = debug
11
+ end
12
+
13
+ def success(message)
14
+ puts message.colorize(:green)
15
+ end
16
+
17
+ def fail(message)
18
+ abort(message.colorize(:red))
19
+ end
20
+
21
+ def warning(message)
22
+ puts message.to_s.colorize(:yellow)
23
+ end
24
+
25
+ def verbose(message)
26
+ puts message.to_s if @verbose
27
+ end
28
+
29
+ def debug(message)
30
+ puts message.to_s.colorize(:light_blue) if @debug
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CICI
4
+ class Util
5
+ def initialize(ui)
6
+ @ui = ui
7
+ end
8
+
9
+ def run_command(command)
10
+ @ui.warning("Running command: #{command}")
11
+ success = system(command)
12
+ @ui.fail("\nCommand failed. Fix issue and try again.") unless success
13
+ end
14
+
15
+ def get_env(name)
16
+ @ui.fail("Forgot to specify environment variable, #{name}") unless ENV[name]
17
+ ENV[name]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CICI
4
+ class Version
5
+ def self.get
6
+ '0.1.0'
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cici
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Levi Bostian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.8.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.8'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.8.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: rubocop
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.58'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.58.2
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.58'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.58.2
53
+ - !ruby/object:Gem::Dependency
54
+ name: rake
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '12.3'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 12.3.1
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '12.3'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 12.3.1
73
+ - !ruby/object:Gem::Dependency
74
+ name: rspec
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 3.8.0
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.8'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.8.0
90
+ - - "~>"
91
+ - !ruby/object:Gem::Version
92
+ version: '3.8'
93
+ - !ruby/object:Gem::Dependency
94
+ name: rspec_junit_formatter
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '0.4'
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 0.4.1
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.4'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 0.4.1
113
+ description: When environment variables are not enough and you need to store secrets
114
+ within files, cici is your friend. Store secret files in your source code repository
115
+ with ease. Can be used without a CI server, but tool is primarily designed for your
116
+ CI server to decrypt these secret files for deployment.
117
+ email: levi.bostian@gmail.com
118
+ executables:
119
+ - cici
120
+ extensions: []
121
+ extra_rdoc_files: []
122
+ files:
123
+ - CHANGELOG.md
124
+ - LICENSE
125
+ - README.md
126
+ - bin/cici
127
+ - lib/cici.rb
128
+ - lib/cici/cli.rb
129
+ - lib/cici/config.rb
130
+ - lib/cici/constants.rb
131
+ - lib/cici/decrypt.rb
132
+ - lib/cici/encrypt.rb
133
+ - lib/cici/ui.rb
134
+ - lib/cici/util.rb
135
+ - lib/cici/version.rb
136
+ homepage: https://github.com/levibostian/cici
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubygems_version: 3.0.6
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Confidential Information for Continuous Integration (CICI)
159
+ test_files: []