confidant 0.1.0
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 +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
|
+
[](https://rubygems.org/gems/confidant)
|
6
|
+
[](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: []
|