confidant 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +211 -0
- data/bin/confidant +6 -0
- data/lib/confidant.rb +40 -0
- data/lib/confidant/cli.rb +135 -0
- data/lib/confidant/client.rb +137 -0
- data/lib/confidant/configurator.rb +148 -0
- data/lib/confidant/version.rb +3 -0
- data/spec/confidant/client_spec.rb +92 -0
- data/spec/confidant_spec.rb +27 -0
- data/spec/spec_helper.rb +9 -0
- metadata +225 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 20203303d07ffce5c33fb5fcdd92f479eef85e0a
|
4
|
+
data.tar.gz: 72ba74d4b41018265a3ad9d535ec3f479802d5ce
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff62a6debd26773bdddce93ab885a4b4ad0cd2ed0c3ff5bbf4000e07eef0cbfc1291851fc24fde7e2968b1ce1148095cd6de7364d7b59fccf3fb698be4ab6f91
|
7
|
+
data.tar.gz: 5dd2f41cc9336023c6188dac971a3bf69578dc75cb7f77c277c8bbdbb78f8a55f901a2cbcb16d7fa9258dbbd287e751ed1746b8aa00ab075427789623c60c0bb
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
### Added
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
|
12
|
+
## [0.1.0] - 2016-12-03
|
13
|
+
### Added
|
14
|
+
- Initial public release of Confidant, with Confidant::Client library and CLI
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Matt Greensmith
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# Confidant
|
2
|
+
|
3
|
+
This is a client for [Confidant](https://lyft.github.io/confidant), an open source secret management service.
|
4
|
+
|
5
|
+
[![Gem](https://img.shields.io/gem/v/confidant.svg)](https://rubygems.org/gems/confidant)
|
6
|
+
[![Travis](https://img.shields.io/mgreensmith/confidant-client-ruby.svg)](https://travis-ci.org/mgreensmith/confidant-client-ruby)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
$ gem install confidant
|
11
|
+
|
12
|
+
## Configuration
|
13
|
+
|
14
|
+
This client is compatible with the config file format of the [official Python client](https://lyft.github.io/confidant/basics/client/); it should be a drop-in replacement.
|
15
|
+
|
16
|
+
The client will automatically look in `~/.confidant` and `/etc/confidant/config` for its configuration. Alternate config files can be specified via the `--config-files` (Ruby `:config_files`) option. Config files can be YAML or JSON format.
|
17
|
+
|
18
|
+
The configuration file supports profiles, which let you specify multiple environments in the same file. The default profile is `default`, an alternate profile can be specified with the `--profile` (Ruby: `:profile`) option.
|
19
|
+
|
20
|
+
The client does not merge config from multiple files; it expects to find a configuration block for the specified profile in the first file it finds.
|
21
|
+
|
22
|
+
The following configuration is supported, with the listed defaults. Some defaults differ from the official Python client, namely `user_type` and `region`. Additionally, this client supports per-command configuration: options provided within config keys named for a CLI command (or `Confidant::Client` method) will be used as to configure that command.
|
23
|
+
|
24
|
+
```yaml
|
25
|
+
default:
|
26
|
+
|
27
|
+
# URL of the confidant server.
|
28
|
+
url: nil
|
29
|
+
|
30
|
+
# The KMS auth key to use. i.e. alias/authnz-production
|
31
|
+
auth_key: nil
|
32
|
+
|
33
|
+
# Note: unlike the official Python client, this client configures
|
34
|
+
# encryption-context-related options in the top-level config.
|
35
|
+
|
36
|
+
# The IAM role or user to authenticate with. i.e. myservice-production or myuser
|
37
|
+
from: nil
|
38
|
+
|
39
|
+
# The IAM role name of confidant. i.e. confidant-production
|
40
|
+
to: nil
|
41
|
+
|
42
|
+
# The confidant user-type to authenticate as. i.e. user or service
|
43
|
+
user_type: service
|
44
|
+
|
45
|
+
# Provided for compatibility with the official Python client.
|
46
|
+
# Any configured options in auth_context will be flattened into
|
47
|
+
# the top-level config, and will override provided top-level values.
|
48
|
+
auth_context:
|
49
|
+
from: nil
|
50
|
+
to: nil
|
51
|
+
user_type: service
|
52
|
+
|
53
|
+
# The token lifetime, in minutes.
|
54
|
+
token_lifetime: 10
|
55
|
+
|
56
|
+
# The version of the KMS auth token.
|
57
|
+
token_version: 2
|
58
|
+
|
59
|
+
# Use the specified AWS region for authentication.
|
60
|
+
region: us-east-1
|
61
|
+
|
62
|
+
# Example of per-command configuration for the get_service command
|
63
|
+
# get_service:
|
64
|
+
# service: my-service
|
65
|
+
|
66
|
+
# Not yet implemented in this client, will be ignored if provided:
|
67
|
+
# retries: 0
|
68
|
+
# backoff: 1
|
69
|
+
# token_cache_file: '/run/confidant/confidant_token'
|
70
|
+
# assume_role: nil
|
71
|
+
```
|
72
|
+
|
73
|
+
When using the CLI, CLI-provided option flags are merged with config file options, with CLI options taking precedence.
|
74
|
+
|
75
|
+
When using the client as a Ruby library, options passed as parameters to `Confidant.configure` are merged with config file options, with parameter options taking precedence.
|
76
|
+
|
77
|
+
## CLI Usage
|
78
|
+
|
79
|
+
```
|
80
|
+
NAME
|
81
|
+
confidant - Client for Confidant, an open source secret management system
|
82
|
+
|
83
|
+
SYNOPSIS
|
84
|
+
confidant [global options] command [command options] [arguments...]
|
85
|
+
|
86
|
+
VERSION
|
87
|
+
0.1.0
|
88
|
+
|
89
|
+
GLOBAL OPTIONS
|
90
|
+
--config-files=arg - Comma separated list of configuration files to use (default: ~/.confidant,/etc/confidant/config)
|
91
|
+
--from=arg - The IAM role or user to authenticate with. i.e. myservice-production or myuser (default: none)
|
92
|
+
--help - Show this message
|
93
|
+
-k, --auth-key=arg - The KMS auth key to use. i.e. alias/authnz-production (default: none)
|
94
|
+
-l, --token-lifetime=arg - The token lifetime, in minutes. (default: none)
|
95
|
+
--log-level=arg - Logging verbosity. (default: info)
|
96
|
+
--profile=arg - Configuration profile to use. (default: default)
|
97
|
+
--region=arg - Use the specified region for authentication. (default: none)
|
98
|
+
--to=arg - The IAM role name of confidant. i.e. confidant-production (default: none)
|
99
|
+
--token-version=arg - The version of the KMS auth token. (default: none)
|
100
|
+
-u, --url=arg - URL of the confidant server. (default: none)
|
101
|
+
--user-type=arg - The confidant user-type to authenticate as. i.e. user or service (default: none)
|
102
|
+
--version - Display the program version
|
103
|
+
|
104
|
+
COMMANDS
|
105
|
+
get_service - Get credentials for a service
|
106
|
+
help - Shows a list of commands or help for one command
|
107
|
+
show_config - Show the current config
|
108
|
+
```
|
109
|
+
|
110
|
+
The CLI returns JSON to `STDOUT`, for drop-in compatibility with the official Python client.
|
111
|
+
|
112
|
+
Logs are written to `STDERR` by default. TO write logs to a file, redirect `STDERR` output:
|
113
|
+
|
114
|
+
```
|
115
|
+
confidant 2>/some/log/file
|
116
|
+
```
|
117
|
+
|
118
|
+
## Library Usage
|
119
|
+
|
120
|
+
Require the client.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
require 'confidant'
|
124
|
+
```
|
125
|
+
|
126
|
+
### Configuration
|
127
|
+
|
128
|
+
Configure the library via `Confidant.configure`.
|
129
|
+
|
130
|
+
An insufficiently-specified config, or any errors during configuration, will raise `Confidant::ConfigurationError`
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# Configure Confidant using options from config file only:
|
134
|
+
Confidant.configure
|
135
|
+
|
136
|
+
# Or provide options as parameters, which will be merged onto
|
137
|
+
# the options from the config file, with parameter options
|
138
|
+
# taking precedence:
|
139
|
+
Confidant.configure(
|
140
|
+
auth_key: 'alias/authnz-production',
|
141
|
+
from: 'myservice-production',
|
142
|
+
to: 'confidant-production',
|
143
|
+
get_service: {
|
144
|
+
service: 'myservice-production'
|
145
|
+
}
|
146
|
+
)
|
147
|
+
```
|
148
|
+
|
149
|
+
### Get Service
|
150
|
+
|
151
|
+
Get credentials for a service from the Confidant server via `Confidant.get_service`
|
152
|
+
|
153
|
+
JSON responses from the server are returned as Ruby `Hash`es.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# If a service name was provided in config,
|
157
|
+
# i.e. `get_service: { service: 'my-service' }`,
|
158
|
+
# `get_service` will get that service's credentials:
|
159
|
+
Confidant.get_service
|
160
|
+
=> {"account"=>nil,
|
161
|
+
"blind_credentials"=>[],
|
162
|
+
"credentials"=>
|
163
|
+
[{"credential_pairs"=>{"my_fancy_secret"=>"I love cats!", "something_is"=>"A super secret!"},
|
164
|
+
<SNIP>
|
165
|
+
|
166
|
+
# Or, provide a service name via parameter:
|
167
|
+
Confidant.get_service('my-service')
|
168
|
+
```
|
169
|
+
|
170
|
+
### Multiple Clients
|
171
|
+
|
172
|
+
Use multiple client instances with different configurations simultaneously by instantiating `Confidant::Configurator` and `Confidant::Client` directly:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
default_config = Confidant::Configurator.new
|
176
|
+
default_client = Confidant::Client.new(default_config)
|
177
|
+
default_client.get_service('my-service')
|
178
|
+
|
179
|
+
production_config = Confidant::Configurator.new(profile: 'production')
|
180
|
+
production_client = Confidant::Client.new(production_config)
|
181
|
+
production_client.get_service('my-service-production')
|
182
|
+
```
|
183
|
+
|
184
|
+
## WARNING
|
185
|
+
|
186
|
+
This client is pre-alpha, and does not have feature parity with the official Python client!
|
187
|
+
|
188
|
+
#### Things that work
|
189
|
+
|
190
|
+
- The `get_service` CLI command (`Client.get_service`) can fetch service credentials from a Confidant server using a v2 KMS authentication token.
|
191
|
+
- All currently-available CLI options are correctly configurable.
|
192
|
+
|
193
|
+
#### Things that have not been implemented yet
|
194
|
+
|
195
|
+
- Any other CLI command, notably everything to do with server-blinded credentials
|
196
|
+
- API retries/backoff
|
197
|
+
- The `confidant-format` formatter
|
198
|
+
- KMS v1 auth tokens
|
199
|
+
- Token caching
|
200
|
+
- Assuming an IAM role
|
201
|
+
- MFA tokens
|
202
|
+
|
203
|
+
## Contributing
|
204
|
+
|
205
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mgreensmith/confidant-client-ruby.
|
206
|
+
|
207
|
+
|
208
|
+
## License
|
209
|
+
|
210
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
211
|
+
|
data/bin/confidant
ADDED
data/lib/confidant.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'logging'
|
2
|
+
|
3
|
+
include Logging.globally(:log)
|
4
|
+
|
5
|
+
require 'confidant/version'
|
6
|
+
|
7
|
+
# This is a set of client libs for Confidant
|
8
|
+
module Confidant
|
9
|
+
Logging.logger.root.appenders = Logging.appenders.stderr
|
10
|
+
Logging.logger.root.level = :info
|
11
|
+
|
12
|
+
# An invalid configuration was provided
|
13
|
+
class ConfigurationError < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'confidant/configurator'
|
17
|
+
require 'confidant/client'
|
18
|
+
|
19
|
+
module_function
|
20
|
+
|
21
|
+
### Wrap common workflow into module methods for end-user simplicity.
|
22
|
+
|
23
|
+
def configure(config = {})
|
24
|
+
@configurator = Configurator.new(config)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_service(service = nil)
|
28
|
+
unless @configurator
|
29
|
+
raise ConfigurationError, 'Not configured, run Confidant.configure'
|
30
|
+
end
|
31
|
+
Client.new(@configurator).get_service(service)
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_exception(klass, ex)
|
35
|
+
klass.log.error("#{ex.class} : #{ex.message}")
|
36
|
+
ex.backtrace.each do |frame|
|
37
|
+
klass.log.debug("\t#{frame}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'gli'
|
3
|
+
|
4
|
+
module Confidant
|
5
|
+
# Creates a CLI that fronts the Confidant client
|
6
|
+
class CLI
|
7
|
+
extend GLI::App
|
8
|
+
|
9
|
+
program_desc 'Client for Confidant, an open source secret management system'
|
10
|
+
version Confidant::VERSION
|
11
|
+
|
12
|
+
### Global configuration options
|
13
|
+
|
14
|
+
# 'flag' options take params
|
15
|
+
|
16
|
+
desc 'Comma separated list of configuration files to use'
|
17
|
+
flag 'config-files', default_value:
|
18
|
+
Confidant::Configurator::DEFAULT_OPTS[:config_files].join(',')
|
19
|
+
|
20
|
+
desc 'Configuration profile to use.'
|
21
|
+
flag 'profile', default_value:
|
22
|
+
Confidant::Configurator::DEFAULT_OPTS[:profile]
|
23
|
+
|
24
|
+
desc 'Logging verbosity.'
|
25
|
+
flag 'log-level', default_value:
|
26
|
+
Confidant::Configurator::DEFAULT_OPTS[:log_level]
|
27
|
+
|
28
|
+
desc 'URL of the confidant server.'
|
29
|
+
flag %w(u url)
|
30
|
+
|
31
|
+
desc 'The KMS auth key to use. i.e. alias/authnz-production'
|
32
|
+
flag ['k', 'auth-key']
|
33
|
+
|
34
|
+
desc 'The token lifetime, in minutes.'
|
35
|
+
flag ['l', 'token-lifetime']
|
36
|
+
|
37
|
+
desc 'The version of the KMS auth token.'
|
38
|
+
flag 'token-version'
|
39
|
+
|
40
|
+
desc 'The IAM role or user to authenticate with. ' \
|
41
|
+
'i.e. myservice-production or myuser'
|
42
|
+
flag 'from'
|
43
|
+
|
44
|
+
desc 'The IAM role name of confidant. i.e. confidant-production'
|
45
|
+
flag 'to'
|
46
|
+
|
47
|
+
desc 'The confidant user-type to authenticate as. i.e. user or service'
|
48
|
+
flag 'user-type'
|
49
|
+
|
50
|
+
desc 'Use the specified region for authentication.'
|
51
|
+
flag 'region'
|
52
|
+
|
53
|
+
# TODO: Implement support for these.
|
54
|
+
|
55
|
+
# desc 'Assume the specified IAM role.'
|
56
|
+
# flag 'assume-role'
|
57
|
+
|
58
|
+
# desc 'Number of retries that should be attempted on ' \
|
59
|
+
# 'confidant server errors.'
|
60
|
+
# flag 'retries'
|
61
|
+
|
62
|
+
# 'switch' options are booleans
|
63
|
+
|
64
|
+
# desc 'Prompt for an MFA token.'
|
65
|
+
# switch 'mfa'
|
66
|
+
|
67
|
+
### Commands
|
68
|
+
|
69
|
+
desc 'Get credentials for a service'
|
70
|
+
command :get_service do |c|
|
71
|
+
c.desc 'The service to get.'
|
72
|
+
c.flag 'service'
|
73
|
+
|
74
|
+
c.action do |_global_options, _options, _|
|
75
|
+
log.debug 'Running get_service command'
|
76
|
+
client = Confidant::Client.new(@configurator)
|
77
|
+
client.suppress_errors
|
78
|
+
puts JSON.pretty_generate(client.get_service)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
desc 'Show the current config'
|
83
|
+
command :show_config do |c|
|
84
|
+
c.action do |_global_options, _options, _|
|
85
|
+
puts @configurator.config.to_yaml
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
### Hooks
|
90
|
+
|
91
|
+
pre do |global_options, command, options, _|
|
92
|
+
Logging.logger.root.level = global_options['log-level'].to_sym
|
93
|
+
|
94
|
+
opts = clean_opts(global_options)
|
95
|
+
opts[command.name] = clean_opts(options) if options
|
96
|
+
|
97
|
+
log.debug "Parsed CLI options: #{opts}"
|
98
|
+
@configurator = Confidant::Configurator.new(opts, command.name)
|
99
|
+
end
|
100
|
+
|
101
|
+
on_error do |ex|
|
102
|
+
Confidant.log_exception(self, ex)
|
103
|
+
false # return false to suppress standard message
|
104
|
+
end
|
105
|
+
|
106
|
+
### Helper methods
|
107
|
+
|
108
|
+
# Try and clean up GLI's output into something useable.
|
109
|
+
def self::clean_opts(gli_opts)
|
110
|
+
# GLI provides String and Symbol keys for each flag/switch.
|
111
|
+
# We want the String keys (because some of our flags have dashes,
|
112
|
+
# and GLI makes :"dash-keys" symbols which need extra work to clean)
|
113
|
+
string_opts = gli_opts.select { |k, _| k.is_a?(String) }
|
114
|
+
|
115
|
+
# Convert the dashes in key names to underscores and symbolize the keys.
|
116
|
+
opts = {}
|
117
|
+
string_opts.each_pair { |k, v| opts[k.tr('-', '_').to_sym] = v }
|
118
|
+
|
119
|
+
# Convert :config_files into an array.
|
120
|
+
if opts[:config_files]
|
121
|
+
opts[:config_files] = opts[:config_files].split(',')
|
122
|
+
end
|
123
|
+
|
124
|
+
# Remove unneeded hash pairs:
|
125
|
+
# - nils: GLI returns 'nil' default for non-specified flag-type opts
|
126
|
+
# - false: GLI returns 'false' default for non-specified switch-type opts
|
127
|
+
# Removing false values also removes GLI's :help and :version keys
|
128
|
+
# - single-letter keys: these opts all have longer-form doppelgangers
|
129
|
+
opts.delete_if { |k, v| v.nil? || v == false || k.length == 1 }
|
130
|
+
|
131
|
+
# Now, only defaulted and explicitly-specified options remain.
|
132
|
+
opts
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'base64'
|
3
|
+
require 'aws-sdk-core'
|
4
|
+
require 'rest-client'
|
5
|
+
|
6
|
+
require 'confidant/configurator'
|
7
|
+
|
8
|
+
module Confidant
|
9
|
+
# The Confidant Client implementation
|
10
|
+
class Client
|
11
|
+
TOKEN_SKEW_SECONDS = 3 * 60
|
12
|
+
TIME_FORMAT = '%Y%m%dT%H%M%SZ'.freeze
|
13
|
+
|
14
|
+
attr_accessor :config
|
15
|
+
|
16
|
+
# Initialize with a +configurator+, which is
|
17
|
+
# an instance of Confidant::Configurator
|
18
|
+
def initialize(configurator)
|
19
|
+
@config = configurator.config
|
20
|
+
@kms = Aws::KMS::Client.new(region: config[:region])
|
21
|
+
@suppress_errors = false
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return a Hash of credentials from the confidant API
|
25
|
+
# for +service+, either explicitly-provided, or from config.
|
26
|
+
def get_service(service = nil)
|
27
|
+
log.debug "Requesting #{api_service_url(service_name(service))} " \
|
28
|
+
"as user #{api_user}"
|
29
|
+
response = RestClient::Request.execute(
|
30
|
+
method: :get,
|
31
|
+
url: api_service_url(service_name(service)),
|
32
|
+
user: api_user,
|
33
|
+
password: generate_token,
|
34
|
+
headers: {
|
35
|
+
user_agent: RestClient::Platform.default_user_agent.prepend(
|
36
|
+
"confidant-client/#{Confidant::VERSION} "
|
37
|
+
)
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
JSON.parse(response.body)
|
42
|
+
rescue => ex
|
43
|
+
Confidant.log_exception(self, ex)
|
44
|
+
@suppress_errors ? api_error_response : raise
|
45
|
+
end
|
46
|
+
|
47
|
+
# The Python client suppresses API errors,
|
48
|
+
# returning { result: false } instead.
|
49
|
+
# Mimic this behavior based on the truthiness of +enable+.
|
50
|
+
# This is generally only called from Confidant::CLI
|
51
|
+
def suppress_errors(enable = true)
|
52
|
+
@suppress_errors = enable
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Return the name of the service for which we
|
59
|
+
# should fetch credentials from the confidant API.
|
60
|
+
# Returns +service+ if provided, or the config
|
61
|
+
# value at @config[:get_service][:service]
|
62
|
+
# Raises +ConfigurationError+ if no service was
|
63
|
+
# provided or configured.
|
64
|
+
def service_name(service = nil)
|
65
|
+
return service unless service.nil?
|
66
|
+
if @config[:get_service] && @config[:get_service][:service]
|
67
|
+
return @config[:get_service][:service]
|
68
|
+
end
|
69
|
+
raise ConfigurationError,
|
70
|
+
'Service name must be specified, or provided in config as ' \
|
71
|
+
'{get_service: { service: \'my-service\' }'
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return the name of the user that will connect to the confidant API
|
75
|
+
# TODO(v1-auth-support): Support v1-style user names.
|
76
|
+
def api_user
|
77
|
+
format(
|
78
|
+
'%s/%s/%s',
|
79
|
+
@config[:token_version],
|
80
|
+
@config[:user_type],
|
81
|
+
@config[:from]
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
# The URL to get credentials for +service+ from the Confidant server.
|
86
|
+
def api_service_url(service)
|
87
|
+
format('%s/v1/services/%s', @config[:url], service)
|
88
|
+
end
|
89
|
+
|
90
|
+
# The falsey response to return when
|
91
|
+
# @suppress_errors is true,
|
92
|
+
# rather than raising exceptions.
|
93
|
+
def api_error_response
|
94
|
+
{ 'result' => 'false' }
|
95
|
+
end
|
96
|
+
|
97
|
+
# The content of a confidant auth token payload,
|
98
|
+
# to be encrypted by KMS.
|
99
|
+
def token_payload
|
100
|
+
now = Time.now.utc
|
101
|
+
|
102
|
+
start_time = (now - TOKEN_SKEW_SECONDS)
|
103
|
+
|
104
|
+
end_time = (
|
105
|
+
now - TOKEN_SKEW_SECONDS +
|
106
|
+
(@config[:token_lifetime].to_i * 60)
|
107
|
+
)
|
108
|
+
|
109
|
+
{ not_before: start_time.strftime(TIME_FORMAT),
|
110
|
+
not_after: end_time.strftime(TIME_FORMAT) }.to_json
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return an auth token for the confidant service,
|
114
|
+
# encrypted via KMS.
|
115
|
+
def generate_token
|
116
|
+
# TODO(v1-auth-support): Handle the different encryption_context
|
117
|
+
if @config[:token_version].to_i != 2
|
118
|
+
raise ConfigurationError,
|
119
|
+
'This client only supports KMS v2 auth tokens.'
|
120
|
+
end
|
121
|
+
|
122
|
+
encrypt_params = {
|
123
|
+
key_id: @config[:auth_key],
|
124
|
+
plaintext: token_payload,
|
125
|
+
encryption_context: {
|
126
|
+
to: @config[:to],
|
127
|
+
from: @config[:from],
|
128
|
+
user_type: @config[:user_type]
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
log.debug "Asking KMS to encrypt: #{encrypt_params}"
|
133
|
+
resp = @kms.encrypt(encrypt_params)
|
134
|
+
Base64.strict_encode64(resp.ciphertext_blob)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'active_support/hash_with_indifferent_access'
|
3
|
+
|
4
|
+
module Confidant
|
5
|
+
# Builds configuration for the Confidant client
|
6
|
+
class Configurator
|
7
|
+
# Default configration options for the Confidant module
|
8
|
+
# and this Configurator class, and not for the Client.
|
9
|
+
# Pass these through to the CLI for use in the `pre` hook,
|
10
|
+
# and strip them out of the final config hash used by the Client.
|
11
|
+
DEFAULT_OPTS = {
|
12
|
+
config_files: %w(~/.confidant /etc/confidant/config),
|
13
|
+
profile: 'default',
|
14
|
+
log_level: 'info'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# Default configuration options for the Client.
|
18
|
+
DEFAULTS = {
|
19
|
+
token_lifetime: 10,
|
20
|
+
token_version: 2,
|
21
|
+
user_type: 'service',
|
22
|
+
region: 'us-east-1'
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
# Keys that must exist in the final config in order for
|
26
|
+
# the Client to be able to function.
|
27
|
+
MANDATORY_CONFIG_KEYS = {
|
28
|
+
global: [:url, :auth_key, :from, :to],
|
29
|
+
get_service: [:service]
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
attr_reader :config
|
33
|
+
|
34
|
+
# Instantiate with a hash of configuration +opts+,and optionally
|
35
|
+
# the name of a +command+ that may have mandatory config options
|
36
|
+
# that should be validated along with the global config.
|
37
|
+
def initialize(opts = {}, command = nil)
|
38
|
+
configure(opts, command)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Given a hash of configuration +opts+, and optionally the name
|
42
|
+
# of a +command+ that may have mandatory config options
|
43
|
+
# that should be validated along with the global config,
|
44
|
+
# load configuration from files, merge config keys together,
|
45
|
+
# and validate the presence of sufficient top-level config keys
|
46
|
+
# and command-specific config keys to be able to use the client.
|
47
|
+
#
|
48
|
+
# Saves and returns the final merged config.
|
49
|
+
def configure(opts, command = nil)
|
50
|
+
# Merge 'opts' onto DEFAULT_OPTS so that we can self-configure.
|
51
|
+
# This is a noop if we were called from CLI,
|
52
|
+
# as those keys are defaults in GLI and guaranteed to exist in 'opts',
|
53
|
+
# but this is necessary if we were invoked as a lib.
|
54
|
+
config = DEFAULT_OPTS.dup.merge(opts)
|
55
|
+
log.debug "Local config: #{config}"
|
56
|
+
|
57
|
+
# Merge local config over the profile config from file.
|
58
|
+
config = profile_config(
|
59
|
+
config[:config_files],
|
60
|
+
config[:profile]
|
61
|
+
).dup.merge(config)
|
62
|
+
|
63
|
+
# We don't need any of the internal DEFAULT_OPTS any longer
|
64
|
+
DEFAULT_OPTS.keys.each { |k| config.delete(k) }
|
65
|
+
|
66
|
+
# Merge config onto local DEFAULTS
|
67
|
+
# to backfill any keys that are needed for KMS.
|
68
|
+
config = DEFAULTS.dup.merge(config)
|
69
|
+
|
70
|
+
@config = config
|
71
|
+
validate_config(command)
|
72
|
+
log.debug "Authoritative config: #{config}"
|
73
|
+
@config
|
74
|
+
end
|
75
|
+
|
76
|
+
# Validate the instance's @config for the presence of
|
77
|
+
# all global mandatory config keys. If +command+ is provided,
|
78
|
+
# validate the presence of all mandatory config keys specific
|
79
|
+
# to that command, otherwise validate that mandatory config keys
|
80
|
+
# exist for any command keys that exist in the top-level hash.
|
81
|
+
# Raises +ConfigurationError+ if mandatory config options are missing.
|
82
|
+
def validate_config(command = nil)
|
83
|
+
missing_keys = MANDATORY_CONFIG_KEYS[:global] - @config.keys
|
84
|
+
|
85
|
+
commands_to_verify = if command
|
86
|
+
[command.to_sym]
|
87
|
+
else
|
88
|
+
(MANDATORY_CONFIG_KEYS.keys & @config.keys)
|
89
|
+
end
|
90
|
+
|
91
|
+
commands_to_verify.each do |cmd|
|
92
|
+
missing = missing_keys_for_command(cmd)
|
93
|
+
next if missing.empty?
|
94
|
+
missing_keys << "#{cmd}[#{missing.join(',')}]"
|
95
|
+
end
|
96
|
+
|
97
|
+
return true if missing_keys.empty?
|
98
|
+
raise ConfigurationError,
|
99
|
+
"Missing required config keys: #{missing_keys.join(', ')}"
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Return a hash of the config for the provided +profile+ from
|
105
|
+
# the first-existing file in the provided array of +config_files+.
|
106
|
+
def profile_config(config_files, profile)
|
107
|
+
config = nil
|
108
|
+
config_files.each do |config_file|
|
109
|
+
next unless File.exist?(File.expand_path(config_file))
|
110
|
+
log.debug "found config file: #{config_file}"
|
111
|
+
config = profile_from_file(config_file, profile)
|
112
|
+
break
|
113
|
+
end
|
114
|
+
log.debug "Profile config: #{config}"
|
115
|
+
config || {}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Given the pathname of a YAML or JSON +config_file+ and the name
|
119
|
+
# of a config +profile+ within that file, load the file and
|
120
|
+
# return a +Hash+ of the contents of that profile key.
|
121
|
+
def profile_from_file(config_file, profile)
|
122
|
+
content = YAML.load_file(File.expand_path(config_file))
|
123
|
+
|
124
|
+
# Fetch options from file for the specified profile
|
125
|
+
unless content.key?(profile)
|
126
|
+
raise ConfigurationError,
|
127
|
+
"Profile '#{profile}' not found in '#{config_file}"
|
128
|
+
end
|
129
|
+
profile_config = content[profile].symbolize_keys!
|
130
|
+
|
131
|
+
# Merge the :auth_context keys into the top-level hash.
|
132
|
+
profile_config.merge!(profile_config[:auth_context].symbolize_keys!)
|
133
|
+
profile_config.delete_if { |k, _| k == :auth_context }
|
134
|
+
profile_config
|
135
|
+
end
|
136
|
+
|
137
|
+
# Given a +command+, return an +array+ of
|
138
|
+
# the key names from MANDATORY_CONFIG_KEYS for that command,
|
139
|
+
# that are not present in the current @config
|
140
|
+
def missing_keys_for_command(command)
|
141
|
+
mandatory_keys = MANDATORY_CONFIG_KEYS[command] || []
|
142
|
+
return [] if mandatory_keys.empty?
|
143
|
+
return mandatory_keys unless @config[command] &&
|
144
|
+
@config[command].is_a?(Hash)
|
145
|
+
mandatory_keys - @config[command].keys
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Confidant::Client do
|
4
|
+
before do
|
5
|
+
Logging.logger.root.appenders = nil
|
6
|
+
|
7
|
+
config = {
|
8
|
+
user_type: 'philosopher',
|
9
|
+
from: 'hypatia',
|
10
|
+
to: 'aedesia',
|
11
|
+
region: 'alexandria',
|
12
|
+
auth_key: 'astrolabe',
|
13
|
+
url: 'athens',
|
14
|
+
token_version: 2
|
15
|
+
}
|
16
|
+
|
17
|
+
@client = Confidant::Client.new(Confidant::Configurator.new(config))
|
18
|
+
|
19
|
+
allow(RestClient::Platform)
|
20
|
+
.to receive(:default_user_agent).and_return('sextant')
|
21
|
+
|
22
|
+
stub_const('Confidant::VERSION', '999')
|
23
|
+
end
|
24
|
+
|
25
|
+
context '#get_service' do
|
26
|
+
before do
|
27
|
+
allow_any_instance_of(Confidant::Client)
|
28
|
+
.to receive(:generate_token).and_return('tympan')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can get credentials for a service from the API' do
|
32
|
+
allow_any_instance_of(Confidant::Client)
|
33
|
+
.to receive(:generate_token).and_return('tympan')
|
34
|
+
|
35
|
+
api_response = double
|
36
|
+
expect(api_response).to receive(:body).and_return('{ "hello": "world" }')
|
37
|
+
|
38
|
+
expect(RestClient::Request).to receive(:execute).with(
|
39
|
+
method: :get,
|
40
|
+
url: 'athens/v1/services/oracle',
|
41
|
+
user: '2/philosopher/hypatia',
|
42
|
+
password: 'tympan',
|
43
|
+
headers: { user_agent: 'confidant-client/999 sextant' }
|
44
|
+
).and_return(api_response)
|
45
|
+
|
46
|
+
expect(@client.get_service('oracle')).to eq('hello' => 'world')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'does not suppress errors by default' do
|
50
|
+
expect(RestClient::Request).to receive(:execute).and_raise(StandardError)
|
51
|
+
expect { @client.get_service('oracle') }.to raise_error(StandardError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'suppresses errors if @suppress_errors is true' do
|
55
|
+
expect(RestClient::Request).to receive(:execute).and_raise(StandardError)
|
56
|
+
@client.suppress_errors
|
57
|
+
expect(@client.get_service('oracle')).to eq('result' => 'false')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context '#generate_token' do
|
62
|
+
it 'can generate a v2 auth token' do
|
63
|
+
kms_response = double
|
64
|
+
expect(kms_response).to receive(:ciphertext_blob).and_return('12345')
|
65
|
+
|
66
|
+
allow_any_instance_of(Aws::KMS::Client)
|
67
|
+
.to receive(:encrypt).and_return(kms_response)
|
68
|
+
|
69
|
+
# Expect Bas64-encoded :ciphertext_blob
|
70
|
+
expect(@client.send(:generate_token)).to eq('MTIzNDU=')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'raises if a v1 token is requested' do
|
74
|
+
@client.config[:token_version] = 1
|
75
|
+
expect { @client.send(:generate_token) }
|
76
|
+
.to raise_error(Confidant::ConfigurationError)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context '#service_name' do
|
81
|
+
it 'returns a provided service name from parameter' do
|
82
|
+
@client.config[:get_service] = { service: 'aristotle' }
|
83
|
+
expect(@client.send(:service_name, 'plato')).to eq('plato')
|
84
|
+
expect(@client.send(:service_name)).to eq('aristotle')
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'raises if a service name was not provided or configured' do
|
88
|
+
expect { @client.send(:service_name) }
|
89
|
+
.to raise_error(Confidant::ConfigurationError)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Confidant do
|
4
|
+
it 'has a version number' do
|
5
|
+
expect(Confidant::VERSION).not_to be nil
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'module workflow' do
|
9
|
+
it 'exposes #configure' do
|
10
|
+
expect(Confidant).to respond_to(:configure).with(0..1).arguments
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'exposes #get_service' do
|
14
|
+
expect(Confidant).to respond_to(:configure).with(0..1).arguments
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a #log_exception helper' do
|
19
|
+
expect(Confidant).to respond_to(:log_exception).with(2).arguments
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Confidant::ConfigurationError do
|
24
|
+
it 'subclasses StandardError' do
|
25
|
+
expect(Confidant::ConfigurationError).to be_kind_of(StandardError.class)
|
26
|
+
end
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: confidant
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Greensmith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gli
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.14'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: logging
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: aws-sdk-core
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.6'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rest-client
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '10.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '10.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.12'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.12'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.46'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.46'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: pry
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0.10'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0.10'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: gem-release
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0.7'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0.7'
|
181
|
+
description:
|
182
|
+
email:
|
183
|
+
- matt@mattgreensmith.net
|
184
|
+
executables:
|
185
|
+
- confidant
|
186
|
+
extensions: []
|
187
|
+
extra_rdoc_files: []
|
188
|
+
files:
|
189
|
+
- CHANGELOG.md
|
190
|
+
- LICENSE.txt
|
191
|
+
- README.md
|
192
|
+
- bin/confidant
|
193
|
+
- lib/confidant.rb
|
194
|
+
- lib/confidant/cli.rb
|
195
|
+
- lib/confidant/client.rb
|
196
|
+
- lib/confidant/configurator.rb
|
197
|
+
- lib/confidant/version.rb
|
198
|
+
- spec/confidant/client_spec.rb
|
199
|
+
- spec/confidant_spec.rb
|
200
|
+
- spec/spec_helper.rb
|
201
|
+
homepage: https://github.com/mgreensmith/confidant-client-ruby
|
202
|
+
licenses:
|
203
|
+
- MIT
|
204
|
+
metadata: {}
|
205
|
+
post_install_message:
|
206
|
+
rdoc_options: []
|
207
|
+
require_paths:
|
208
|
+
- lib
|
209
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
210
|
+
requirements:
|
211
|
+
- - ">="
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
|
+
requirements:
|
216
|
+
- - ">="
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
version: '0'
|
219
|
+
requirements: []
|
220
|
+
rubyforge_project:
|
221
|
+
rubygems_version: 2.4.5.1
|
222
|
+
signing_key:
|
223
|
+
specification_version: 4
|
224
|
+
summary: A CLI and client library for the Confidant secret management service.
|
225
|
+
test_files: []
|