aws-s3crets 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +109 -0
- data/Rakefile +11 -0
- data/aws-s3crets.gemspec +30 -0
- data/bin/s3crets +4 -0
- data/lib/s3crets.rb +4 -0
- data/lib/s3crets/actions.rb +3 -0
- data/lib/s3crets/actions/base.rb +27 -0
- data/lib/s3crets/actions/bundle.rb +120 -0
- data/lib/s3crets/actions/get.rb +32 -0
- data/lib/s3crets/actions/init.rb +40 -0
- data/lib/s3crets/cli.rb +118 -0
- data/lib/s3crets/default_tasks.rb +18 -0
- data/lib/s3crets/defaults.rb +16 -0
- data/lib/s3crets/error.rb +6 -0
- data/lib/s3crets/ui.rb +42 -0
- data/lib/s3crets/version.rb +3 -0
- data/spec/files/Secretfile +8 -0
- data/spec/lib/s3crets/actions/bundle_spec.rb +55 -0
- data/spec/lib/s3crets/actions/init_spec.rb +51 -0
- data/spec/spec_helper.rb +14 -0
- data/templates/Secretfile.erb +9 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZTQ2MjViNjUxY2Q4MmVlZmE4MGYzNDEzOWYyODczNmNkYWRiMDgyYQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OGRkMzRkOWNkOTAyZWQzNDcyMjYyNTZhOGZjNTVjYjQ2ODJhN2I2Zg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OWFjNWMwNWQwMWIwMWExOTM5YmVhY2M0M2NmNmJlNWVhNzNlODlhOGMxNDIw
|
10
|
+
YjdjZTJjZjMxZWFkODczYTk1NGFjMWUwZTgwNGJmZTlhNDkzN2U3MjdkNWYx
|
11
|
+
NDgzODNhZDMxMWY1OWM4YTBkYjg5ZmIzMDRjMDIwOThiZTQ1NzM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OTZhZWM0MzEyZGE1ZTdhNDk1NGVhMTNiYWM5ODBkMzRiZDVlZGVhZmJhNWZm
|
14
|
+
MTdkYjg0MmRiZjE2MDFjZWNkNTllY2NmMmIxOGVjZDRhNTA3NGU3N2I3MTIw
|
15
|
+
ODI4YWUwNjNjOWQwMDA1MDgyNDI0YTgwYmFmZDczNWU1NmY4NGM=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.1.0
|
4
|
+
- 2.0.0
|
5
|
+
deploy:
|
6
|
+
provider: rubygems
|
7
|
+
gem: aws-s3crets
|
8
|
+
on:
|
9
|
+
tags: true
|
10
|
+
all_branches: true
|
11
|
+
api_key:
|
12
|
+
secure: crwL3RJgh7X9hJK3+3gJQ3ucn9uJjJNytEGqgmnVHvAw2WNcg3oHLnro/529wfMc961ZtwMSQ75Zc4jAeDLlxR6AdMTrUJ7SOmvaIkrvmJCUaadu3C8JXY/EoIx/8tJ4V+4u5hkDvcZUoZ9g9kI68r1NtBopJMaSCgrBbYCoCZA=
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Norm MacLennan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# S3crets
|
2
|
+
|
3
|
+
[](https://travis-ci.org/maclennann/s3crets)
|
4
|
+
|
5
|
+
`s3crets` is a gem that allows you to fetch secret files (password, certs, keys,
|
6
|
+
etc) from an S3 bucket via the command-line, rake, or ruby script.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'aws-s3crets'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install s3crets
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Setup
|
27
|
+
|
28
|
+
The most-common use-case for `s3crets` involves the use of a `Secretfile`.
|
29
|
+
|
30
|
+
This is a yaml file that contains S3 location information (region/bucket)
|
31
|
+
as well as the key/path to all of your required secrets.
|
32
|
+
|
33
|
+
You can generate a sample `Secretfile` by running `s3crets init`. Now you can
|
34
|
+
fill in your secrets. A completed `Secretfile` looks something like:
|
35
|
+
|
36
|
+
```yaml
|
37
|
+
---
|
38
|
+
settings:
|
39
|
+
bucket: 'secrets_bucket'
|
40
|
+
region: 'us-west-2'
|
41
|
+
secret_dir: secrets/dev
|
42
|
+
secrets:
|
43
|
+
aws_key: 'AWS/Keys/ec2-myteam-write'
|
44
|
+
ssh_key: 'SSH/myteam/myserver/server-priv'
|
45
|
+
cloud_config: 'AWS/cloudinit/myserver.yaml'
|
46
|
+
```
|
47
|
+
|
48
|
+
This `Secretfile` describes 3 secrets stored in the `secrets_bucket` bucket.
|
49
|
+
In this example, the files are 3 secrets required to provision a new EC2 instance -
|
50
|
+
an AWS credential file, an SSH private key, and a cloud-init config.
|
51
|
+
|
52
|
+
It will download these secrets to `secrets/dev/[filename]`.
|
53
|
+
|
54
|
+
### Fetching Secrets
|
55
|
+
|
56
|
+
Once you have your `Secretfile` ready, there are two ways you can actually fetch
|
57
|
+
the secrets. Both ways, assume you have you [AWS credentials set up](http://docs.aws.amazon.com/sdkforruby/api/#Credentials).
|
58
|
+
|
59
|
+
#### Command Line
|
60
|
+
|
61
|
+
Just type `s3crets bundle` to download all of the secrets. Secrets that already exist
|
62
|
+
in the target directory will not be re-downloaded.
|
63
|
+
|
64
|
+
#### Rake
|
65
|
+
|
66
|
+
`s3crets` comes with default rake tasks. Simply `require 's3crets/default_tasks'`
|
67
|
+
somewhere in your Rakefile and it will construct tasks based on your folder
|
68
|
+
structure and the location of your `Secretfile`(s).
|
69
|
+
|
70
|
+
For example, the following directory hierarchy:
|
71
|
+
|
72
|
+
```
|
73
|
+
Rakefile
|
74
|
+
secrets/
|
75
|
+
production/
|
76
|
+
Secretfile
|
77
|
+
development/
|
78
|
+
Secretfile
|
79
|
+
```
|
80
|
+
|
81
|
+
Will create the following rake tasks:
|
82
|
+
|
83
|
+
```
|
84
|
+
rake secrets:development # Fetch secrets for development
|
85
|
+
rake secrets:production # Fetch secrets for production
|
86
|
+
```
|
87
|
+
|
88
|
+
The following configuration can be applied to the default tasks:
|
89
|
+
|
90
|
+
* `ENV['S3CRETS_ENVIRONMENT_GLOB']` - The directory glob that is used to identify
|
91
|
+
your environments (default: `secrets/**/Secretfile`)
|
92
|
+
|
93
|
+
### `Secretfile.resolved`
|
94
|
+
|
95
|
+
Once you have fetched your secrets, a `Secretsfile.resolved` will be created in
|
96
|
+
the directory. This file contains the name and hash of the files that were
|
97
|
+
downloaded.
|
98
|
+
|
99
|
+
If you have a file locally that doesn't match the hash in your `resolved` file,
|
100
|
+
it will be redownloaded the next time you fetch secrets. Then the resolved file
|
101
|
+
will be updated.
|
102
|
+
|
103
|
+
## Contributing
|
104
|
+
|
105
|
+
1. Fork it ( https://github.com/maclennann/s3crets/fork )
|
106
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
107
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
108
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
109
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
task default: [:rubocop, :spec]
|
6
|
+
|
7
|
+
# Ensure default rake tasks load
|
8
|
+
require 's3crets/default_tasks'
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec)
|
11
|
+
RuboCop::RakeTask.new(:rubocop)
|
data/aws-s3crets.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 's3crets/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'aws-s3crets'
|
8
|
+
spec.version = S3crets::VERSION
|
9
|
+
spec.authors = ['Norm MacLennan']
|
10
|
+
spec.email = ['norm.maclennan@gmail.com']
|
11
|
+
spec.summary = 'Fetch secret files from AWS S3 buckets.'
|
12
|
+
spec.description = 'Fetch secret files from AWS S3 buckets.'
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rubocop', '~> 0.29'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
25
|
+
spec.add_development_dependency 'fakefs', '~> 0.6'
|
26
|
+
spec.add_development_dependency 'gem-path', '~> 0.6'
|
27
|
+
|
28
|
+
spec.add_dependency 'aws-sdk', '~> 2.0'
|
29
|
+
spec.add_dependency 'thor'
|
30
|
+
end
|
data/bin/s3crets
ADDED
data/lib/s3crets.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module S3crets
|
2
|
+
module Actions
|
3
|
+
# Basic plumbing for all actions
|
4
|
+
class Base
|
5
|
+
attr_accessor :options
|
6
|
+
private :options=
|
7
|
+
|
8
|
+
attr_accessor :logger
|
9
|
+
private :logger=
|
10
|
+
|
11
|
+
def initialize(logger, options = {})
|
12
|
+
self.logger = logger
|
13
|
+
self.options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def debug(*args, &block)
|
19
|
+
logger.debug(*args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def source_root
|
23
|
+
@source_root ||= Pathname.new(File.expand_path('../../../../', __FILE__))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 's3crets/actions/base'
|
2
|
+
require 's3crets/error'
|
3
|
+
require 'aws-sdk'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module S3crets
|
7
|
+
module Actions
|
8
|
+
# Download or verify the secrets in a Secretfile
|
9
|
+
class Bundle < Base
|
10
|
+
attr_accessor :settings
|
11
|
+
attr_accessor :remote_secrets
|
12
|
+
attr_accessor :local_secrets
|
13
|
+
attr_accessor :update
|
14
|
+
|
15
|
+
def initialize(ui, options, args = {})
|
16
|
+
super(ui, options)
|
17
|
+
@update = args[:update]
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
ensure_secretfile
|
22
|
+
load_required_secrets
|
23
|
+
ensure_secrets_path
|
24
|
+
validate_environment
|
25
|
+
load_resolved_secrets if resolved_file?
|
26
|
+
validate_local_secrets unless @update
|
27
|
+
run!
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def run!
|
33
|
+
if need_secrets?
|
34
|
+
new_secrets = download_remote_secrets
|
35
|
+
update_resolved_file new_secrets
|
36
|
+
else
|
37
|
+
debug { 'No secrets to download...' }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def ensure_secretfile
|
42
|
+
fail Error, NO_SECRETFILE_ERROR unless File.exist? SECRETFILE_NAME
|
43
|
+
debug { "#{SECRETFILE_NAME} located..." }
|
44
|
+
end
|
45
|
+
|
46
|
+
def ensure_secrets_path
|
47
|
+
FileUtils.mkdir_p(@settings['secret_dir']) unless @settings['secret_dir'].nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def resolved_file?
|
51
|
+
File.exist? RESOLVED_NAME
|
52
|
+
end
|
53
|
+
|
54
|
+
def load_required_secrets
|
55
|
+
secretfile = YAML.load_file(SECRETFILE_NAME)
|
56
|
+
@settings = secretfile['settings'] || {}
|
57
|
+
@remote_secrets = secretfile['secrets'] || {}
|
58
|
+
@local_secrets = {}
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_environment
|
62
|
+
return unless @settings.empty? || @remote_secrets.empty?
|
63
|
+
fail Error, INVALID_SECRETFILE_ERROR
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_resolved_secrets
|
67
|
+
@local_secrets = YAML.load_file RESOLVED_NAME
|
68
|
+
end
|
69
|
+
|
70
|
+
def need_secrets?
|
71
|
+
!@remote_secrets.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_local_secrets
|
75
|
+
@local_secrets.each do |key, secret|
|
76
|
+
if File.exist?(secret[:path]) && secret[:hash] == Digest::MD5.file(secret[:path]).hexdigest
|
77
|
+
debug { "#{key} found locally, skipping download..." }
|
78
|
+
@remote_secrets.delete key
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def download_remote_secrets
|
84
|
+
downloaded_secrets = {}
|
85
|
+
remote_secrets.each do |key, secret|
|
86
|
+
downloaded_secrets[key] = download_one_secret(secret,
|
87
|
+
@settings['secret_dir'] || '.')
|
88
|
+
|
89
|
+
debug { "Downloaded secret: #{key} to #{@settings['secret_dir']}..." }
|
90
|
+
end
|
91
|
+
|
92
|
+
downloaded_secrets
|
93
|
+
end
|
94
|
+
|
95
|
+
def download_one_secret(remote_path, local_path)
|
96
|
+
path = File.join(local_path, File.basename(remote_path))
|
97
|
+
|
98
|
+
resp = s3.get_object(bucket: @settings['bucket'],
|
99
|
+
key: remote_path)
|
100
|
+
|
101
|
+
File.write(path, resp.body.read)
|
102
|
+
{ path: path, hash: Digest::MD5.file(path).hexdigest }
|
103
|
+
end
|
104
|
+
|
105
|
+
def update_resolved_file(new_secrets)
|
106
|
+
@local_secrets.merge! new_secrets
|
107
|
+
|
108
|
+
File.open(RESOLVED_NAME, 'w') do |out|
|
109
|
+
YAML.dump(@local_secrets, out)
|
110
|
+
end
|
111
|
+
|
112
|
+
debug { 'Updated resolved file...' }
|
113
|
+
end
|
114
|
+
|
115
|
+
def s3
|
116
|
+
@s3 ||= ::Aws::S3::Client.new(region: @settings['region'])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 's3crets/actions/base'
|
2
|
+
require 's3crets/error'
|
3
|
+
require 'aws-sdk'
|
4
|
+
require 's3crets/defaults'
|
5
|
+
|
6
|
+
module S3crets
|
7
|
+
module Actions
|
8
|
+
# Fetch a single secret
|
9
|
+
class Get < Base
|
10
|
+
attr_accessor :path
|
11
|
+
|
12
|
+
def initialize(ui, options, path)
|
13
|
+
super(ui, options)
|
14
|
+
@path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
fetch_secret(@path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_secret(path)
|
22
|
+
filename = File.basename(path)
|
23
|
+
|
24
|
+
s3 = ::Aws::S3::Client.new(region: options['region'])
|
25
|
+
resp = s3.get_object(bucket: options['bucket'],
|
26
|
+
key: path)
|
27
|
+
|
28
|
+
File.write(filename, resp.body.read)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 's3crets/actions/base'
|
2
|
+
require 's3crets/error'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'erb'
|
5
|
+
require 's3crets/defaults'
|
6
|
+
|
7
|
+
module S3crets
|
8
|
+
module Actions
|
9
|
+
# Create a new Zanzifile
|
10
|
+
class Init < Base
|
11
|
+
def run
|
12
|
+
check_for_secretfile
|
13
|
+
write_template
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def check_for_secretfile
|
19
|
+
return unless File.exist?(SECRETFILE_NAME) && !options['force']
|
20
|
+
fail Error, ALREADY_EXISTS_ERROR
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_template
|
24
|
+
template = TemplateRenderer.new(options)
|
25
|
+
|
26
|
+
File.open(SECRETFILE_NAME, 'w') do |f|
|
27
|
+
f.write template.render(File.read(source_root.join(TEMPLATE_NAME)))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Allows us to easily feed our options hash
|
32
|
+
# to an ERB
|
33
|
+
class TemplateRenderer < OpenStruct
|
34
|
+
def render(template)
|
35
|
+
ERB.new(template).result(binding)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/s3crets/cli.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/actions'
|
3
|
+
require 's3crets/version'
|
4
|
+
require 's3crets/ui'
|
5
|
+
require 's3crets/actions'
|
6
|
+
require 's3crets/error'
|
7
|
+
require 's3crets/defaults'
|
8
|
+
|
9
|
+
module S3crets
|
10
|
+
# The `s3crets` binay/thor application main class
|
11
|
+
class Cli < Thor
|
12
|
+
include Thor::Actions
|
13
|
+
|
14
|
+
attr_accessor :ui
|
15
|
+
|
16
|
+
def initialize(*)
|
17
|
+
super
|
18
|
+
the_shell = (options['no-color'] ? Thor::Shell::Basic.new : shell)
|
19
|
+
@ui = Shell.new(the_shell)
|
20
|
+
@ui.be_quiet! if options['quiet']
|
21
|
+
@ui.debug! if options['verbose']
|
22
|
+
|
23
|
+
debug_header
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'version', 'Display your Zanzibar verion'
|
27
|
+
def version
|
28
|
+
say "#{APPLICATION_NAME} Version: #{VERSION}"
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'init', "Create an empty #{SECRETFILE_NAME} in the current directory."
|
32
|
+
option 'verbose', type: :boolean, default: false, aliases: :v
|
33
|
+
option 'force', type: :boolean, default: false
|
34
|
+
option 'bucket', type: :string, aliases: :b, default: 'my_bucket',
|
35
|
+
desc: 'The S3 bucket to connect to.'
|
36
|
+
option 'region', type: :string, aliases: :r, default: 'us-west-2',
|
37
|
+
desc: 'The AWS region to use.'
|
38
|
+
option 'credential_file', type: :string, aliases: :f,
|
39
|
+
default: '~/.aws/credential',
|
40
|
+
desc: 'The AWS credential file'
|
41
|
+
def init
|
42
|
+
run_action { init! }
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'bundle', "Fetch secrets declared in your #{SECRETFILE_NAME}"
|
46
|
+
option 'verbose', type: :boolean, default: false, aliases: :v
|
47
|
+
def bundle
|
48
|
+
run_action { bundle! }
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'plunder', "Alias to `#{APPLICATION_NAME} bundle`", hide: true
|
52
|
+
option 'verbose', type: :boolean, default: false, aliases: :v
|
53
|
+
alias_method :plunder, :bundle
|
54
|
+
|
55
|
+
desc 'install', "Alias to `#{APPLICATION_NAME} bundle`"
|
56
|
+
alias_method :install, :bundle
|
57
|
+
|
58
|
+
desc 'update', "Redownload all secrets in your #{SECRETFILE_NAME}"
|
59
|
+
option 'verbose', type: :boolean, default: false, aliases: :v
|
60
|
+
def update
|
61
|
+
run_action { update! }
|
62
|
+
end
|
63
|
+
|
64
|
+
desc 'get KEY', 'Fetch a single KEY from S3'
|
65
|
+
option 'bucket', type: :string, aliases: :b,
|
66
|
+
desc: 'The S3 bucket to connect to.'
|
67
|
+
option 'region', type: :string, aliases: :r,
|
68
|
+
desc: 'The AWS region to use.'
|
69
|
+
option 'credential_file', type: :string, aliases: :f,
|
70
|
+
default: '~/.aws/credential',
|
71
|
+
desc: 'The AWS credential file'
|
72
|
+
def get(key)
|
73
|
+
run_action { get! key }
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def debug_header
|
79
|
+
@ui.debug { "Running #{APPLICATION_NAME} in debug mode..." }
|
80
|
+
@ui.debug { "Ruby Version: #{RUBY_VERSION}" }
|
81
|
+
@ui.debug { "Ruby Platform: #{RUBY_PLATFORM}" }
|
82
|
+
@ui.debug { "#{APPLICATION_NAME} Version: #{VERSION}" }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Run the specified action and rescue errors we
|
86
|
+
# explicitly send back to format them
|
87
|
+
def run_action(&_block)
|
88
|
+
yield
|
89
|
+
rescue ::S3crets::Error => e
|
90
|
+
@ui.error e
|
91
|
+
abort "Fatal error: #{e.message}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def init!
|
95
|
+
say "Initializing a new #{SECRETFILE_NAME} in the current directory..."
|
96
|
+
Actions::Init.new(@ui, options).run
|
97
|
+
say "Your #{SECRETFILE_NAME} has been created!"
|
98
|
+
say 'You should check the settings and add your secrets.'
|
99
|
+
say "Then run `#{APPLICATION_NAME} bundle` to fetch them."
|
100
|
+
end
|
101
|
+
|
102
|
+
def bundle!
|
103
|
+
say "Checking for secrets declared in your #{SECRETFILE_NAME}..."
|
104
|
+
Actions::Bundle.new(@ui, options).run
|
105
|
+
say 'Finished downloading secrets!'
|
106
|
+
end
|
107
|
+
|
108
|
+
def update!
|
109
|
+
say "Redownloading all secrets declared in your #{SECRETFILE_NAME}..."
|
110
|
+
Actions::Bundle.new(@ui, options, update: true).run
|
111
|
+
say 'Finished downloading secrets!'
|
112
|
+
end
|
113
|
+
|
114
|
+
def get!(path)
|
115
|
+
say Actions::Get.new(@ui, options, path).run
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 's3crets/cli'
|
2
|
+
|
3
|
+
namespace :secrets do
|
4
|
+
env_glob = ENV['S3CRETS_ENVIRONMENT_GLOB'] || 'secrets/**/Secretfile'
|
5
|
+
environments = (Dir.glob env_glob).map { |f| File.dirname f }.uniq
|
6
|
+
|
7
|
+
environments.each do |env|
|
8
|
+
short_name = File.basename env
|
9
|
+
|
10
|
+
desc "Fetch secrets for #{short_name}"
|
11
|
+
task "#{short_name}" do
|
12
|
+
puts "CHDIR #{env}"
|
13
|
+
Dir.chdir env do
|
14
|
+
(S3crets::Cli.new).install
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
# Definitions for various strings used throughout the gem
|
4
|
+
module S3crets
|
5
|
+
APPLICATION_NAME = Pathname.new($PROGRAM_NAME).basename
|
6
|
+
SECRETFILE_NAME = 'Secretfile'
|
7
|
+
RESOLVED_NAME = 'Secretfile.resolved'
|
8
|
+
TEMPLATE_NAME = 'templates/Secretfile.erb'
|
9
|
+
DEFAULT_REGION = 'us-west-2'
|
10
|
+
DEFAULT_BUCKET = 'bucketname'
|
11
|
+
|
12
|
+
ALREADY_EXISTS_ERROR = "#{SECRETFILE_NAME} already exists! Aborting..."
|
13
|
+
NO_BUCKET_ERROR = 'Could not identify S3 bucket.'
|
14
|
+
NO_SECRETFILE_ERROR = "You don't have a #{SECRETFILE_NAME}! Run `#{APPLICATION_NAME} init` first!"
|
15
|
+
INVALID_SECRETFILE_ERROR = "Unable to load your #{SECRETFILE_NAME}. Please ensure it is valid YAML."
|
16
|
+
end
|
data/lib/s3crets/ui.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems/user_interaction'
|
2
|
+
|
3
|
+
module S3crets
|
4
|
+
# Prints messages out to stdout
|
5
|
+
class Shell
|
6
|
+
attr_writer :shell
|
7
|
+
|
8
|
+
def initialize(shell)
|
9
|
+
@shell = shell
|
10
|
+
@quiet = false
|
11
|
+
@debug = ENV['DEBUG']
|
12
|
+
end
|
13
|
+
|
14
|
+
def debug(message = nil)
|
15
|
+
@shell.say(message || yield) if @debug && !@quiet
|
16
|
+
end
|
17
|
+
|
18
|
+
def info(message = nil)
|
19
|
+
@shell.say(message || yield) unless @quiet
|
20
|
+
end
|
21
|
+
|
22
|
+
def confirm(message = nil)
|
23
|
+
@shell.say(message || yield, :green) unless @quiet
|
24
|
+
end
|
25
|
+
|
26
|
+
def warn(message = nil)
|
27
|
+
@shell.say(message || yield, :yellow)
|
28
|
+
end
|
29
|
+
|
30
|
+
def error(message = nil)
|
31
|
+
@shell.say(message || yield, :red)
|
32
|
+
end
|
33
|
+
|
34
|
+
def be_quiet!
|
35
|
+
@quiet = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def debug!
|
39
|
+
@debug = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 's3crets/cli'
|
2
|
+
require 's3crets/defaults'
|
3
|
+
require 'rspec'
|
4
|
+
require 'fakefs/spec_helpers'
|
5
|
+
require 'aws-sdk'
|
6
|
+
|
7
|
+
describe S3crets::Cli do
|
8
|
+
include FakeFS::SpecHelpers
|
9
|
+
|
10
|
+
describe '#bundle' do
|
11
|
+
context 'when Secretfile already exists' do
|
12
|
+
before(:each) do
|
13
|
+
spec_root = File.join(source_root, 'spec')
|
14
|
+
files = File.join(spec_root, 'files')
|
15
|
+
FakeFS::FileSystem.clone files
|
16
|
+
|
17
|
+
path = `gem path aws-sdk`.chomp
|
18
|
+
FakeFS::FileSystem.clone path, path
|
19
|
+
Dir.chdir File.join(source_root, 'spec', 'files')
|
20
|
+
end
|
21
|
+
|
22
|
+
before(:all) do
|
23
|
+
Aws.config[:stub_responses] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should have a Secretfile' do
|
27
|
+
expect(FakeFS::FileTest.file? S3crets::SECRETFILE_NAME).to be(true)
|
28
|
+
expect(File.read(S3crets::SECRETFILE_NAME)).to include('testbucket')
|
29
|
+
end
|
30
|
+
|
31
|
+
xit 'should download a file' do
|
32
|
+
expect(FakeFS::FileTest.file? File.join('secrets', '1.txt')).to be(false)
|
33
|
+
expect { subject.bundle }.to output(/Finished downloading secrets/).to_stdout
|
34
|
+
expect(FakeFS::FileTest.file? File.join('secrets', '1.txt')).to be(true)
|
35
|
+
end
|
36
|
+
|
37
|
+
xit 'should create a resolved file' do
|
38
|
+
expect(FakeFS::FileTest.file? S3crets::RESOLVED_NAME).to be(false)
|
39
|
+
expect { subject.bundle }.to output(/Finished downloading secrets/).to_stdout
|
40
|
+
expect(FakeFS::FileTest.file? S3crets::RESOLVED_NAME).to be(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should reject a malformed Secretfile' do
|
44
|
+
File.write('Secretfile', 'broken YAML')
|
45
|
+
expect { subject.bundle }.to raise_error.with_message(/#{S3crets::INVALID_SECRETFILE_ERROR}/)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when Secretfile does not exist' do
|
50
|
+
it 'should return an error' do
|
51
|
+
expect { subject.bundle }.to raise_error.with_message(/#{S3crets::NO_SECRETFILE_ERROR}/)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 's3crets/cli'
|
2
|
+
require 's3crets/defaults'
|
3
|
+
require 'rspec'
|
4
|
+
require 'fakefs/spec_helpers'
|
5
|
+
|
6
|
+
describe S3crets::Cli do
|
7
|
+
include FakeFS::SpecHelpers
|
8
|
+
|
9
|
+
describe '#init' do
|
10
|
+
before(:each) do
|
11
|
+
templates_root = File.join(source_root, 'templates')
|
12
|
+
FakeFS::FileSystem.clone templates_root
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when a file does not yet exist' do
|
16
|
+
it 'should create a template file' do
|
17
|
+
expect { subject.init }.to output(/has been created/).to_stdout
|
18
|
+
expect(FakeFS::FileTest.file? S3crets::SECRETFILE_NAME).to be(true)
|
19
|
+
expect(File.read S3crets::SECRETFILE_NAME).to match(/Fill in secrets/)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should accept settings as options' do
|
23
|
+
subject.options = { 'region' => 'us-east-1',
|
24
|
+
'bucket' => 'an_example_bucket',
|
25
|
+
'secretdir' => '.' }
|
26
|
+
|
27
|
+
expect { subject.init }.to output(/has been created/).to_stdout
|
28
|
+
contents = File.read S3crets::SECRETFILE_NAME
|
29
|
+
expect(contents).to include('region: us-east-1')
|
30
|
+
expect(contents).to include('bucket: an_example_bucket')
|
31
|
+
expect(contents).to include('secret_dir: .')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when a file already exists' do
|
36
|
+
before(:each) { File.write(S3crets::SECRETFILE_NAME, 'test value') }
|
37
|
+
|
38
|
+
it 'should not overwrite an existing file' do
|
39
|
+
expect { subject.init }.to raise_error.with_message(/#{S3crets::ALREADY_EXISTS_ERROR}/)
|
40
|
+
expect(File.read S3crets::SECRETFILE_NAME).to eq('test value')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should obey the force flag' do
|
44
|
+
subject.options = { 'force' => true }
|
45
|
+
|
46
|
+
expect { subject.init }.to output(/has been created/).to_stdout
|
47
|
+
expect(File.read S3crets::SECRETFILE_NAME).to match('Fill in secrets')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
RSpec.configure do |config|
|
3
|
+
config.expect_with :rspec do |expectations|
|
4
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
5
|
+
end
|
6
|
+
|
7
|
+
config.mock_with :rspec do |mocks|
|
8
|
+
mocks.verify_partial_doubles = true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def source_root
|
13
|
+
File.expand_path('../../', __FILE__)
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aws-s3crets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Norm MacLennan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.29'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.29'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: fakefs
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: gem-path
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: aws-sdk
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: thor
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Fetch secret files from AWS S3 buckets.
|
126
|
+
email:
|
127
|
+
- norm.maclennan@gmail.com
|
128
|
+
executables:
|
129
|
+
- s3crets
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .rspec
|
135
|
+
- .rubocop.yml
|
136
|
+
- .travis.yml
|
137
|
+
- Gemfile
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- aws-s3crets.gemspec
|
142
|
+
- bin/s3crets
|
143
|
+
- lib/s3crets.rb
|
144
|
+
- lib/s3crets/actions.rb
|
145
|
+
- lib/s3crets/actions/base.rb
|
146
|
+
- lib/s3crets/actions/bundle.rb
|
147
|
+
- lib/s3crets/actions/get.rb
|
148
|
+
- lib/s3crets/actions/init.rb
|
149
|
+
- lib/s3crets/cli.rb
|
150
|
+
- lib/s3crets/default_tasks.rb
|
151
|
+
- lib/s3crets/defaults.rb
|
152
|
+
- lib/s3crets/error.rb
|
153
|
+
- lib/s3crets/ui.rb
|
154
|
+
- lib/s3crets/version.rb
|
155
|
+
- spec/files/Secretfile
|
156
|
+
- spec/lib/s3crets/actions/bundle_spec.rb
|
157
|
+
- spec/lib/s3crets/actions/init_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
- templates/Secretfile.erb
|
160
|
+
homepage: ''
|
161
|
+
licenses:
|
162
|
+
- MIT
|
163
|
+
metadata: {}
|
164
|
+
post_install_message:
|
165
|
+
rdoc_options: []
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ! '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - ! '>='
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 2.4.5
|
181
|
+
signing_key:
|
182
|
+
specification_version: 4
|
183
|
+
summary: Fetch secret files from AWS S3 buckets.
|
184
|
+
test_files:
|
185
|
+
- spec/files/Secretfile
|
186
|
+
- spec/lib/s3crets/actions/bundle_spec.rb
|
187
|
+
- spec/lib/s3crets/actions/init_spec.rb
|
188
|
+
- spec/spec_helper.rb
|