vagrant-zanzibar 0.1.1
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.
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +13 -0
- data/README.md +74 -0
- data/Rakefile +11 -0
- data/bin/zamioculcas +2 -0
- data/bin/zanzibar +72 -0
- data/lib/vagrant-zanzibar.rb +213 -0
- data/lib/vagrant-zanzibar/version.rb +3 -0
- data/spec/responses/attachment_response.xml +12 -0
- data/spec/responses/authenticate_response.xml +12 -0
- data/spec/responses/download_private_key_response.xml +12 -0
- data/spec/responses/download_public_key_response.xml +12 -0
- data/spec/responses/get_secret_response.xml +57 -0
- data/spec/responses/get_secret_with_attachment_response.xml +57 -0
- data/spec/responses/get_secret_with_keys_response.xml +47 -0
- data/spec/scrt.wsdl +629 -0
- data/spec/spec/spec_helper.rb +95 -0
- data/spec/zanzibar_spec.rb +113 -0
- data/zanzibar.gemspec +26 -0
- metadata +167 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9.3
|
4
|
+
- 2.0.0
|
5
|
+
deploy:
|
6
|
+
provider: rubygems
|
7
|
+
api_key:
|
8
|
+
secure: "CFXaOgtLypVc3/Nn3NFyeTYJ3rR/KNua2FPFHc02h5K/TPm8PMlHsYEB3e9OpithnC5cLqPUUlyZL4Gz7QC4zxX0G4luNVz+ZXdueMk1GBstK9QMsrjOQMKnQTCPaK/3x2/53kuPWMjYSyn5+ICkf/Omq1EVD4YEplhSvIRA9QQ="
|
9
|
+
gem: vagrant-zanzibar
|
10
|
+
on:
|
11
|
+
tags: true
|
12
|
+
all_branches: true
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'savon', '0.9.5'
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
gem 'rake'
|
7
|
+
gem 'savon_spec'
|
8
|
+
gem 'rspec'
|
9
|
+
gem 'webmock'
|
10
|
+
gem 'codeclimate-test-reporter'
|
11
|
+
gem 'vagrant-zanzibar', path: '.'
|
12
|
+
gem 'rubocop'
|
13
|
+
end
|
14
|
+
|
15
|
+
# Specify your gem's dependencies in zanzibar.gemspec
|
16
|
+
gemspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2015 Cimpress
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Zanzibar
|
2
|
+
[](http://badge.fury.io/rb/zanzibar)
|
3
|
+
|
4
|
+
Zanzibar is a utility to retrieve secrets from a Secret Server installation. It supports retrieval of a password, public/private key, or secret attachment.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'zanzibar'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install zanzibar
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
In your ruby project, rakefile, etc., create a new Zanzibar object. The constructor takes a hash of optional parameters for the WSDL location, the domain of the Secret Server, a hash of global variables to pass to savon (necessary for windows environments with self-signed certs) and a password for the current user (intended to be passed in through some encryption method, unless you really want a plaintext password there.). All of these parameters are optional and the user will be prompted to enter them if they are missing.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
my_object = Zanzibar::Zanzibar.new(:domain => 'my.domain.net', :wsdl => 'my.scrt.srvr.com/webservices/sswebservice.asmx?wdsl', :pwd => get_encrypted_password_from_somewhere)
|
28
|
+
```
|
29
|
+
|
30
|
+
Example:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'zanzibar'
|
34
|
+
|
35
|
+
## Constructor takes hash as argument, all optional :domain, :wsdl, :pwd, :globals
|
36
|
+
secrets = Zanzibar::Zanzibar.new(:domain => 'mydomain.net', :wsdl => "https://my.scrt.server/webservices/sswebservice.asmx?wsdl")
|
37
|
+
# On windows with self-signed certs,
|
38
|
+
# Zanzibar::Zanzibar.new(:domain => 'mydomain.net', :wsdl => "https://my.scrt.server/webservices/sswebservice.asmx?wsdl", :globals => {:ssl_verify_mode => :none})
|
39
|
+
|
40
|
+
## Simple password -> takes secret id as argument
|
41
|
+
secrets.get_password(1234)
|
42
|
+
|
43
|
+
## Private Key -> takes hash as argument, requires :scrt_id, :type, optional :scrt_item_id, :path
|
44
|
+
secrets.download_secret_file(:scrt_id => 2345, :path => 'secrets/', :type => "Private Key")
|
45
|
+
|
46
|
+
## Public Key -> takes hash as argument, requires :scrt_id, :type, optional :scrt_item_id, :path
|
47
|
+
secrets.download_secret_file(:scrt_id => 2345, :path => 'secrets/', :type => "Public Key")
|
48
|
+
|
49
|
+
## Attachment; only supports secrets with single attachment -> takes hash as argument, requires :scrt_id, :path, optional :scrt_item_id, :path
|
50
|
+
secrets.download_secret_file(:scrt_id => 2345, :path => 'secrets/', :type => "Attachment")
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+
### Command Line
|
55
|
+
|
56
|
+
Zanzibar comes bundled with the `zanzibar` command-line utility that can be used for fetching passwords and downloading keys from outside of Ruby.
|
57
|
+
|
58
|
+
`zanzibar` supports most actions provided by Zanzibar itself. Because it operates on the command-line, it can be used as part of a pipeline or within a bash script.
|
59
|
+
|
60
|
+
```bash
|
61
|
+
# if you don't pipe in a password, you will be prompted to enter one.
|
62
|
+
# this will download the private key from secret 1984 to the current directory
|
63
|
+
cat ./local-password | zanzibar 1984 -s server.example.com -d example.com -t privatekey
|
64
|
+
|
65
|
+
ssh user@someremote -i ./private_key
|
66
|
+
```
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
|
70
|
+
1. Fork it ( https://github.com/Cimpress-MCP/zanzibar/fork )
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'bundler/setup' # load up our gem environment (incl. local zanzibar)
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'vagrant-zanzibar/version'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
7
|
+
task default: [:test]
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:test)
|
10
|
+
|
11
|
+
RuboCop::RakeTask.new
|
data/bin/zamioculcas
ADDED
data/bin/zanzibar
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#! ruby
|
2
|
+
|
3
|
+
require 'vagrant-zanzibar'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {
|
7
|
+
:domain => 'local'
|
8
|
+
}
|
9
|
+
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: zamioculcas -d domain [-w wsdl] [-k] [-p] [secret_id]"
|
12
|
+
|
13
|
+
opts.on("-d", "--domain DOMAIN", "Specify domain") do |v|
|
14
|
+
options[:domain] = v
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on("-w", "--wsdl WSDL", "Specify WSDL location") do |v|
|
18
|
+
options[:wsdl] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-s", "--server SERVER", "Secret server hostname or IP") do |v|
|
22
|
+
options[:server] = v
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-k", "--no-check-certificate", "Don't run SSL certificate checks") do |v|
|
26
|
+
options[:globals] = {:ssl_verify_mode => :none}
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("-p", "--password PASSWORD", "Specify password") do |v|
|
30
|
+
options[:pwd] = v
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-t", "--type TYPE", "Specify the type of secret") do |v|
|
34
|
+
options[:type] = v
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-u", "--user USER", "Specify the username") do |v|
|
38
|
+
options[:username] = v
|
39
|
+
end
|
40
|
+
|
41
|
+
end.parse!
|
42
|
+
|
43
|
+
raise OptionParser::MissingArgument if options[:server].nil?
|
44
|
+
options[:type] = "password" if options[:type].nil?
|
45
|
+
|
46
|
+
unless STDIN.tty? || options[:pwd]
|
47
|
+
options[:pwd] = $stdin.read.strip
|
48
|
+
end
|
49
|
+
|
50
|
+
secret_id = Integer(ARGV.pop)
|
51
|
+
if(!secret_id)
|
52
|
+
fail "no secret!"
|
53
|
+
end
|
54
|
+
|
55
|
+
unless options[:wsdl] || options[:server].nil?
|
56
|
+
options[:wsdl] = "https://#{options[:server]}/webservices/sswebservice.asmx?wsdl"
|
57
|
+
end
|
58
|
+
|
59
|
+
scrt = Zanzibar::Zanzibar.new(options)
|
60
|
+
|
61
|
+
case options[:type]
|
62
|
+
when "password"
|
63
|
+
$stdout.write "#{scrt.get_password(secret_id)}\n"
|
64
|
+
when "privatekey"
|
65
|
+
scrt.download_private_key(:scrt_id=>secret_id)
|
66
|
+
when "publickey"
|
67
|
+
scrt.download_public_key(:scrt_id=>secret_id)
|
68
|
+
when "attachment"
|
69
|
+
scrt.download_attachment(:scrt_id=>secret_id)
|
70
|
+
else
|
71
|
+
$stderr.write "#{options[:type]} is not a known type."
|
72
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'vagrant-zanzibar/version'
|
2
|
+
require 'savon'
|
3
|
+
require 'io/console'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module Zanzibar
|
7
|
+
##
|
8
|
+
# Class for interacting with Secret Server
|
9
|
+
class Zanzibar
|
10
|
+
##
|
11
|
+
# @param args{:domain, :wsdl, :pwd, :username, :globals{}}
|
12
|
+
|
13
|
+
def initialize(args = {})
|
14
|
+
|
15
|
+
## Array to store checked out secrets
|
16
|
+
@@secrets = []
|
17
|
+
|
18
|
+
if args[:username]
|
19
|
+
@@username = args[:username]
|
20
|
+
else
|
21
|
+
@@username = ENV['USER']
|
22
|
+
end
|
23
|
+
|
24
|
+
if args[:wsdl]
|
25
|
+
@@wsdl = args[:wsdl]
|
26
|
+
else
|
27
|
+
@@wsdl = get_wsdl_location
|
28
|
+
end
|
29
|
+
if args[:pwd]
|
30
|
+
@@password = args[:pwd]
|
31
|
+
else
|
32
|
+
@@password = prompt_for_password
|
33
|
+
end
|
34
|
+
if args[:domain]
|
35
|
+
@@domain = args[:domain]
|
36
|
+
else
|
37
|
+
@@domain = prompt_for_domain
|
38
|
+
end
|
39
|
+
args[:globals] = {} unless args[:globals]
|
40
|
+
init_client(args[:globals])
|
41
|
+
end
|
42
|
+
|
43
|
+
## Initializes the Savon client class variable with the wdsl document location and optional global variables
|
44
|
+
# @param globals{}, optional
|
45
|
+
|
46
|
+
def init_client(globals = {})
|
47
|
+
globals = {} if globals.nil?
|
48
|
+
Savon.configure do |config|
|
49
|
+
config.log = false
|
50
|
+
end
|
51
|
+
@@client = Savon::Client.new(@@wsdl) do
|
52
|
+
#wsdl.endpoint = "https://scrt.vistaprint.net/webservices/sswebservice.asmx"
|
53
|
+
#wsdl.namespace = "thesecretserver.com"
|
54
|
+
http.auth.ssl.verify_mode = :none
|
55
|
+
HTTPI.log = false
|
56
|
+
end
|
57
|
+
#@@client.log = false
|
58
|
+
#@@client.http.auth.ssl.verify_mode = :none
|
59
|
+
end
|
60
|
+
|
61
|
+
## Gets the user's password if none is provided in the constructor.
|
62
|
+
# @return [String] the password for the current user
|
63
|
+
|
64
|
+
def prompt_for_password
|
65
|
+
puts "Please enter password for #{@@username}:"
|
66
|
+
STDIN.noecho(&:gets).chomp
|
67
|
+
end
|
68
|
+
|
69
|
+
## Gets the wsdl document location if none is provided in the constructor
|
70
|
+
# @return [String] the location of the WDSL document
|
71
|
+
|
72
|
+
def prompt_for_wsdl_location
|
73
|
+
puts 'Enter the URL of the Secret Server WSDL:'
|
74
|
+
STDIN.gets.chomp
|
75
|
+
end
|
76
|
+
|
77
|
+
## Gets the domain of the Secret Server installation if none is provided in the constructor
|
78
|
+
# @return [String] the domain of the secret server installation
|
79
|
+
|
80
|
+
def prompt_for_domain
|
81
|
+
puts 'Enter the domain of your Secret Server:'
|
82
|
+
STDIN.gets.chomp
|
83
|
+
end
|
84
|
+
|
85
|
+
## Check in the secrets that are currently checked out
|
86
|
+
def check_in_secrets
|
87
|
+
failures = []
|
88
|
+
token = get_token
|
89
|
+
@@secrets.each do |secret_id|
|
90
|
+
begin
|
91
|
+
response = @@client.request(:wsdl, :check_in) { soap.body = { token: token, secretId: secret_id} }
|
92
|
+
.hash[:envelope][:body][:check_in_response][:check_in_result][:errors]
|
93
|
+
if !response.nil? and response[:string] != "Secret is not CheckedOut to current user."
|
94
|
+
failures << "Error checking in secret #{secret_id} : #{response[:string]}"
|
95
|
+
end
|
96
|
+
rescue Savon::Error => err
|
97
|
+
raise "There was an error checking in secret #{secret_id}: #{err}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
if !failures.empty?
|
101
|
+
failures.each do |message|
|
102
|
+
puts message
|
103
|
+
end
|
104
|
+
abort()
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
## Get an authentication token for interacting with Secret Server. These are only good for about 10 minutes so just get a new one each time.
|
109
|
+
# Will raise an error if there is an issue with the authentication.
|
110
|
+
# @return the authentication token for the current user.
|
111
|
+
|
112
|
+
def get_token
|
113
|
+
response = @@client.request( :wsdl, :authenticate) { soap.body = { username: @@username, password: @@password, organization: '', domain: @@domain }}
|
114
|
+
.hash[:envelope][:body][:authenticate_response][:authenticate_result]
|
115
|
+
abort("Error generating the authentication token for user #{@@username}: #{response[:errors][:string]}") if response[:errors]
|
116
|
+
response[:token]
|
117
|
+
rescue Savon::Error => err
|
118
|
+
raise "There was an error generating the authentiaton token for user #{@@username}: #{err}"
|
119
|
+
end
|
120
|
+
|
121
|
+
## Get a secret returned as a hash
|
122
|
+
# Will raise an error if there was an issue getting the secret
|
123
|
+
# @param [Integer] the secret id
|
124
|
+
# @return [Hash] the secret hash retrieved from the wsdl
|
125
|
+
|
126
|
+
def get_secret(scrt_id, token = nil)
|
127
|
+
token = get_token if token.nil?
|
128
|
+
secret = @@client.request(:wsdl, :get_secret) { soap.body = { token: token, secretId: scrt_id } }.hash[:envelope][:body][:get_secret_response][:get_secret_result]
|
129
|
+
abort("There was an error getting secret #{scrt_id}: #{secret[:errors][:string]}") if secret[:errors]
|
130
|
+
@@secrets << scrt_id
|
131
|
+
return secret
|
132
|
+
rescue Savon::Error => err
|
133
|
+
raise "There was an error getting the secret with id #{scrt_id}: #{err}"
|
134
|
+
end
|
135
|
+
|
136
|
+
## Retrieve a simple password from a secret
|
137
|
+
# Will raise an error if there are any issues
|
138
|
+
# @param [Integer] the secret id
|
139
|
+
# @return [String] the password for the given secret
|
140
|
+
|
141
|
+
def get_password(scrt_id)
|
142
|
+
secret = get_secret(scrt_id)
|
143
|
+
secret_items = secret[:secret][:items][:secret_item]
|
144
|
+
return get_secret_item_by_field_name(secret_items, 'Password')[:value]
|
145
|
+
rescue Savon::Error => err
|
146
|
+
raise "There was an error getting the password for secret #{scrt_id}: #{err}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def write_secret_to_file(path, secret_response)
|
150
|
+
File.open(File.join(path, secret_response[:file_name]), 'wb') do |file|
|
151
|
+
file.puts Base64.decode64(secret_response[:file_attachment])
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_secret_item_by_field_name(secret_items, field_name)
|
156
|
+
secret_items.each do |item|
|
157
|
+
return item if item[:field_name] == field_name
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
## Get the secret item id that relates to a key file or attachment.
|
162
|
+
# Will raise on error
|
163
|
+
# @param [Integer] the secret id
|
164
|
+
# @param [String] the type of secret item to get, one of privatekey, publickey, attachment
|
165
|
+
# @return [Integer] the secret item id
|
166
|
+
|
167
|
+
def get_scrt_item_id(scrt_id, type, token)
|
168
|
+
secret = get_secret(scrt_id, token)
|
169
|
+
secret_items = secret[:secret][:items][:secret_item]
|
170
|
+
begin
|
171
|
+
return get_secret_item_by_field_name(secret_items, type)[:id]
|
172
|
+
rescue
|
173
|
+
raise "Unknown type, #{type}."
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
## Downloads a file for a secret and places it where Zanzibar is running, or :path if specified
|
178
|
+
# Raise on error
|
179
|
+
# @param [Hash] args, :scrt_id, :type (one of "Private Key", "Public Key", "Attachment"), :scrt_item_id - optional, :path - optional
|
180
|
+
|
181
|
+
def download_secret_file(args = {})
|
182
|
+
token = get_token
|
183
|
+
scrt_item_id = get_scrt_item_id(args[:scrt_id], args[:type], token)
|
184
|
+
FileUtils.mkdir_p(args[:path]) if args[:path]
|
185
|
+
path = args[:path] ? args[:path] : '.' ## The File.join below doesn't handle nils well, so let's take that possibility away.
|
186
|
+
begin
|
187
|
+
response = @@client.request(:wsdl, :download_file_attachment_by_item_id){ soap.body =
|
188
|
+
{ token: token, secretId: args[:scrt_id], secretItemId: scrt_item_id }}
|
189
|
+
.hash[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result]
|
190
|
+
abort("There was an error getting the #{args[:type]} for secret #{args[:scrt_id]}: #{response[:errors][:string]}") if response[:errors]
|
191
|
+
write_secret_to_file(path, response)
|
192
|
+
rescue Savon::Error => err
|
193
|
+
raise "There was an error getting the #{args[:type]} for secret #{args[:scrt_id]}: #{err}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
## Methods to maintain backwards compatibility
|
198
|
+
def download_private_key(args = {})
|
199
|
+
args[:type] = 'Private Key'
|
200
|
+
download_secret_file(args)
|
201
|
+
end
|
202
|
+
|
203
|
+
def download_public_key(args = {})
|
204
|
+
args[:type] = 'Public Key'
|
205
|
+
download_secret_file(args)
|
206
|
+
end
|
207
|
+
|
208
|
+
def download_attachment(args = {})
|
209
|
+
args[:type] = 'Attachment'
|
210
|
+
download_secret_file(args)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|