conjur-cli 4.8.0 → 4.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +1 -8
- data/bin/conjur +1 -0
- data/conjur.gemspec +4 -2
- data/lib/conjur/authn.rb +2 -0
- data/lib/conjur/command/assets.rb +4 -0
- data/lib/conjur/command/dsl_command.rb +1 -0
- data/lib/conjur/command/env.rb +170 -0
- data/lib/conjur/command/field.rb +2 -0
- data/lib/conjur/command/hosts.rb +9 -0
- data/lib/conjur/command/init.rb +31 -12
- data/lib/conjur/command/policy.rb +6 -3
- data/lib/conjur/command/roles.rb +1 -1
- data/lib/conjur/command/secrets.rb +3 -0
- data/lib/conjur/command/variables.rb +1 -0
- data/lib/conjur/config.rb +1 -1
- data/lib/conjur/conjurenv.rb +121 -0
- data/lib/conjur/dsl/runner.rb +5 -0
- data/lib/conjur/version.rb +1 -1
- data/spec/command/env_spec.rb +152 -0
- data/spec/command/init_spec.rb +65 -11
- data/spec/command/policy_spec.rb +24 -5
- data/spec/command/roles_spec.rb +4 -4
- data/spec/command/variables_spec.rb +0 -1
- data/spec/config_spec.rb +21 -0
- data/spec/dsl/runner_spec.rb +13 -4
- data/spec/env_spec.rb +180 -0
- metadata +32 -4
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -3,15 +3,8 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in conjur.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem 'conjur-api', git: 'https://github.com/conjurinc/api-ruby.git', branch: 'master'
|
6
|
+
gem 'conjur-api', '>=4.8', git: 'https://github.com/conjurinc/api-ruby.git', branch: 'master'
|
7
7
|
|
8
8
|
group :test, :development do
|
9
9
|
gem 'pry'
|
10
10
|
end
|
11
|
-
|
12
|
-
group :development do
|
13
|
-
gem 'conjur-asset-environment-api', git: 'git@github.com:inscitiv/conjur-asset-environment', branch: 'master'
|
14
|
-
gem 'conjur-asset-key-pair-api', git: 'git@github.com:conjurinc/conjur-asset-key-pair', branch: 'master'
|
15
|
-
gem 'conjur-asset-layer-api', git: 'git@github.com:conjurinc/conjur-asset-layer', branch: 'master'
|
16
|
-
gem 'conjur-asset-ui-api', git: 'git@github.com:conjurinc/conjur-asset-ui', branch: 'master'
|
17
|
-
end
|
data/bin/conjur
CHANGED
data/conjur.gemspec
CHANGED
@@ -14,8 +14,10 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.name = "conjur-cli"
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Conjur::VERSION
|
17
|
-
|
18
|
-
|
17
|
+
|
18
|
+
|
19
|
+
gem.add_dependency 'activesupport'
|
20
|
+
gem.add_dependency 'conjur-api', '>=4.8'
|
19
21
|
gem.add_dependency 'gli', '>=2.8.0'
|
20
22
|
gem.add_dependency 'highline'
|
21
23
|
gem.add_dependency 'netrc'
|
data/lib/conjur/authn.rb
CHANGED
@@ -27,6 +27,7 @@ class Conjur::Command::Assets < Conjur::Command
|
|
27
27
|
desc "Create an asset"
|
28
28
|
arg_name "kind:id"
|
29
29
|
command :create do |c|
|
30
|
+
def c.nodoc; true end
|
30
31
|
acting_as_option(c)
|
31
32
|
|
32
33
|
c.action do |global_options, options, args|
|
@@ -55,6 +56,7 @@ class Conjur::Command::Assets < Conjur::Command
|
|
55
56
|
desc "Show an asset"
|
56
57
|
arg_name "id"
|
57
58
|
command :show do |c|
|
59
|
+
def c.nodoc; true end
|
58
60
|
c.action do |global_options,options,args|
|
59
61
|
kind, id = get_kind_and_id_from_args(args, 'id')
|
60
62
|
display api.send(kind, id).attributes
|
@@ -64,6 +66,7 @@ class Conjur::Command::Assets < Conjur::Command
|
|
64
66
|
desc "Checks for the existance of an asset"
|
65
67
|
arg_name "id"
|
66
68
|
command :exists do |c|
|
69
|
+
def c.nodoc; true end
|
67
70
|
c.action do |global_options,options,args|
|
68
71
|
kind, id = get_kind_and_id_from_args(args, 'id')
|
69
72
|
puts api.send(kind, id).exists?
|
@@ -73,6 +76,7 @@ class Conjur::Command::Assets < Conjur::Command
|
|
73
76
|
desc "List an asset"
|
74
77
|
arg_name "kind"
|
75
78
|
command :list do |c|
|
79
|
+
def c.nodoc; true end
|
76
80
|
c.action do |global_options,options,args|
|
77
81
|
kind = require_arg(args, "kind").gsub('-', '_')
|
78
82
|
if api.respond_to?(kind.pluralize)
|
@@ -0,0 +1,170 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2014 Conjur Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
# this software and associated documentation files (the "Software"), to deal in
|
6
|
+
# the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
# subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
12
|
+
# copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
#
|
21
|
+
require 'conjur/authn'
|
22
|
+
require 'conjur/command'
|
23
|
+
require 'conjur/conjurenv'
|
24
|
+
require 'tempfile'
|
25
|
+
|
26
|
+
class Conjur::Command::Env < Conjur::Command
|
27
|
+
|
28
|
+
self.prefix = :env
|
29
|
+
|
30
|
+
def self.common_parameters c
|
31
|
+
c.desc "Environment configuration file"
|
32
|
+
c.default_value ".conjurenv"
|
33
|
+
c.flag ["c"]
|
34
|
+
|
35
|
+
c.desc "Environment configuration as inline yaml"
|
36
|
+
c.flag ["yaml"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.get_env_object options
|
40
|
+
if options[:yaml] and options[:c]!='.conjurenv'
|
41
|
+
exit_now! "Options -c and --yaml can not be provided together"
|
42
|
+
end
|
43
|
+
|
44
|
+
env = if options[:yaml]
|
45
|
+
Conjur::Env.new(yaml: options[:yaml])
|
46
|
+
else
|
47
|
+
Conjur::Env.new(file: (options[:c]||'.conjurenv'))
|
48
|
+
end
|
49
|
+
return env
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Execute external command with environment variables populated from Conjur"
|
53
|
+
long_desc <<'RUNLONGDESC'
|
54
|
+
Processes environment configuration (see env:help for details), and executes a command (with optional arguments) in the modified environment.
|
55
|
+
Local names are uppercased and used as names of environment variables.
|
56
|
+
|
57
|
+
Consider following environment configuration:
|
58
|
+
|
59
|
+
{ key_pair_name: jenkins_key, ssh_keypair_path: !tmp jenkins/private_key, api_key: !var jenkins/api_key }
|
60
|
+
|
61
|
+
Values of the variables "jenkins/private_key" and "jenkins/api_key" will be obtained from Conjur. Value of "jenkins/api_key" will be associated with local name 'api_key'. Value of "jenkins/private_key" will be stored in the temporary file, which name will be associated with local name "ssh_keypair_path".
|
62
|
+
|
63
|
+
Than, external command with appropriate arguments will be launched in the environment which has following variables:
|
64
|
+
|
65
|
+
KEY_PAIR_NAME="jenkins_key",
|
66
|
+
SSH_KEYPAIR_PATH="/dev/shm/temp_file_with_key_obtained_from_Conjur",
|
67
|
+
API_KEY="api key obtained from Conjur"
|
68
|
+
RUNLONGDESC
|
69
|
+
arg_name "-- command [arg1, arg2 ...] "
|
70
|
+
command :run do |c|
|
71
|
+
common_parameters(c)
|
72
|
+
|
73
|
+
c.action do |global_options,options,args|
|
74
|
+
if args.empty?
|
75
|
+
exit_now! "External command with optional arguments should be provided"
|
76
|
+
end
|
77
|
+
env = get_env_object(options)
|
78
|
+
runtime_environment = Hash[ env.obtain(api).map {|k,v| [k.upcase, v] } ]
|
79
|
+
if Conjur.log
|
80
|
+
Conjur.log << "Running command in the prepared environment: #{args}"
|
81
|
+
end
|
82
|
+
Kernel.system(runtime_environment, *args) or exit($?.to_i) # keep original exit code in case of failure
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "Check availability of Conjur variables"
|
87
|
+
long_desc "Checks availability of Conjur variables mentioned in an environment configuration (see env:help for details), and prints out each local name and appropriate status"
|
88
|
+
|
89
|
+
command :check do |c|
|
90
|
+
common_parameters(c)
|
91
|
+
c.action do |global_options,options,args|
|
92
|
+
env = get_env_object(options)
|
93
|
+
result = env.check(api)
|
94
|
+
result.each { |k,v| puts "#{k}: #{v}" }
|
95
|
+
raise "Some variables are not available" unless result.values.select {|v| v == :unavailable }.empty?
|
96
|
+
end
|
97
|
+
end # command
|
98
|
+
|
99
|
+
desc "Render ERB template with variables obtained from Conjur"
|
100
|
+
long_desc <<'TEMPLATEDESC'
|
101
|
+
Processes environment configuration (see env:help for details), and creates a temporary file, which contains result of ERB template rendering in appropriate context.
|
102
|
+
Template should refer to Conjur values by local name as "%<= conjurenv['local_name'] %>".
|
103
|
+
|
104
|
+
Consider following environment configuration:
|
105
|
+
|
106
|
+
{ key_pair_name: jenkins_key, ssh_keypair_path: !tmp jenkins/private_key, api_key: !var jenkins/api_key }
|
107
|
+
|
108
|
+
Values of the variables "jenkins/private_key" and "jenkins/api_key" will be obtained from Conjur. Value of "jenkins/api_key" will be associated with local name 'api_key'. Value of "jenkins/private_key" will be stored in the temporary file, which name will be associated with local name "ssh_keypair_path".
|
109
|
+
|
110
|
+
Than, following template
|
111
|
+
|
112
|
+
key_pair=<%= conjurenv["key_pair_name"] %>, path_to_ssh_key= <%= conjurenv["ssh_keypair_path"] %>, api_key = '<%= conjurenv["api_key"] %>'
|
113
|
+
|
114
|
+
will be rendered to
|
115
|
+
|
116
|
+
key_pair=jenkins_key, path_to_ssh_key=/dev/shm/temp_file_with_key_obtained_from_Conjur, api_key='api key obtained from Conjur'
|
117
|
+
|
118
|
+
Result of the rendering will be stored in temporary file, which location is than printed to stdout
|
119
|
+
TEMPLATEDESC
|
120
|
+
arg_name "template.erb"
|
121
|
+
command :template do |c|
|
122
|
+
common_parameters(c)
|
123
|
+
|
124
|
+
c.action do |global_options,options,args|
|
125
|
+
template_file = args.first
|
126
|
+
exit_now! "Location of readable ERB template should be provided" unless template_file and File.readable?(template_file)
|
127
|
+
template = File.read(template_file)
|
128
|
+
env = get_env_object(options)
|
129
|
+
conjurenv = env.obtain(api) # needed for binding
|
130
|
+
rendered = ERB.new(template).result(binding)
|
131
|
+
|
132
|
+
#
|
133
|
+
tempfile = if File.directory?("/dev/shm") and File.writable?("/dev/shm")
|
134
|
+
Tempfile.new("conjur","/dev/shm")
|
135
|
+
else
|
136
|
+
Tempfile.new("conjur")
|
137
|
+
end
|
138
|
+
tempfile.write(rendered)
|
139
|
+
tempfile.close()
|
140
|
+
old_path = tempfile.path
|
141
|
+
new_path = old_path+".saved"
|
142
|
+
FileUtils.copy(old_path, new_path) # prevent garbage collection
|
143
|
+
puts new_path
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
desc "Print description of environment configuration format"
|
148
|
+
command :help do |c|
|
149
|
+
c.action do |global_options,options,args|
|
150
|
+
puts """
|
151
|
+
Environment configuration (either stored in file referred by -f option or provided inline with --yaml option) should be a YAML document describing one-level Hash.
|
152
|
+
Keys of the hash are 'local names', used to refer to variable values in convenient manner. (See help for env:run and env:template for more details about how they are interpreted).
|
153
|
+
|
154
|
+
Values of the hash may take one of the following forms: a) string b) string preceeded with !var tag c) string preceeded with !tmp tag.
|
155
|
+
|
156
|
+
a) Plain string is just associated with local name without any calls to Conjur.
|
157
|
+
|
158
|
+
b) String preceeded by !var tag is interpreted as an ID of the Conjur variable, which value should be obtained and associated with appropriate local name.
|
159
|
+
|
160
|
+
c) String preceeded by !tmp tag is interpreted as an ID of the Conjur variable, which value should be stored in temporary file, which location should in turn be associated with appropriate local name.
|
161
|
+
|
162
|
+
Example of environment configuration:
|
163
|
+
|
164
|
+
{ local_variable_1: 'literal value', local_variable_2: !var id/of/Conjur/Variable , local_variable_3: !tmp id/of/another/Conjur/variable }
|
165
|
+
|
166
|
+
"""
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
data/lib/conjur/command/field.rb
CHANGED
@@ -25,6 +25,8 @@ class Conjur::Command::Field < Conjur::Command
|
|
25
25
|
|
26
26
|
desc "(Deprecated. See standalone jsonfield command instead.)"
|
27
27
|
command :select do |c|
|
28
|
+
def c.nodoc; true end
|
29
|
+
|
28
30
|
c.action do |global_options,options,args|
|
29
31
|
pattern = require_arg(args, 'pattern')
|
30
32
|
value = args.shift || STDIN.read
|
data/lib/conjur/command/hosts.rb
CHANGED
@@ -62,6 +62,15 @@ class Conjur::Command::Hosts < Conjur::Command
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
desc "List the layers to which the host belongs"
|
66
|
+
arg_name "id"
|
67
|
+
command :layers do |c|
|
68
|
+
c.action do |global_options, options, args|
|
69
|
+
id = require_arg(args, 'id')
|
70
|
+
display api.host(id).role.all.select{|r| r.kind == "layer"}.map(&:identifier), options
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
65
74
|
desc "Enroll a new host into conjur"
|
66
75
|
arg_name "host"
|
67
76
|
command :enroll do |c|
|
data/lib/conjur/command/init.rb
CHANGED
@@ -19,6 +19,8 @@
|
|
19
19
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
20
|
#
|
21
21
|
require 'conjur/command'
|
22
|
+
require 'openssl'
|
23
|
+
require 'socket'
|
22
24
|
|
23
25
|
class Conjur::Command::Init < Conjur::Command
|
24
26
|
desc "Initialize the Conjur configuration"
|
@@ -40,14 +42,14 @@ class Conjur::Command::Init < Conjur::Command
|
|
40
42
|
c.desc "Hostname of the Conjur endpoint (required for virtual appliance)"
|
41
43
|
c.flag ["h", "hostname"]
|
42
44
|
|
43
|
-
c.desc "Conjur account name (
|
45
|
+
c.desc "Conjur organization account name (not required for appliance)"
|
44
46
|
c.flag ["a", "account"]
|
45
47
|
|
46
48
|
c.desc "Conjur SSL certificate (will be obtained from host unless provided by this option)"
|
47
49
|
c.flag ["c", "certificate"]
|
48
50
|
|
49
51
|
c.desc "File to write the configuration to"
|
50
|
-
c.default_value File.
|
52
|
+
c.default_value File.expand_path('~/.conjurrc')
|
51
53
|
c.flag ["f","file"]
|
52
54
|
|
53
55
|
c.desc "Force overwrite of existing files"
|
@@ -57,17 +59,18 @@ class Conjur::Command::Init < Conjur::Command
|
|
57
59
|
hl = HighLine.new $stdin, $stderr
|
58
60
|
|
59
61
|
hostname = options[:hostname] || hl.ask("Enter the hostname (and optional port) of your Conjur endpoint: ").to_s
|
60
|
-
|
62
|
+
protocol, hostname = (hostname.scan %r(^(?:(.*)://)?(.*))).first
|
63
|
+
exit_now! "only https protocol supported" unless protocol.nil? || protocol == 'https'
|
61
64
|
if hostname
|
62
65
|
Conjur.configuration.core_url = "https://#{hostname}/api"
|
63
66
|
end
|
64
67
|
|
65
68
|
account = options[:account]
|
66
69
|
account ||= if hostname
|
67
|
-
account = Conjur::Core::API.info['account'] or raise "
|
70
|
+
account = Conjur::Core::API.info['account'] or raise "Expecting 'account' in Core info"
|
68
71
|
else
|
69
72
|
# using .to_s to overcome https://github.com/JEG2/highline/issues/69
|
70
|
-
hl.ask("Enter your account name: ").to_s
|
73
|
+
hl.ask("Enter your organization account name: ").to_s
|
71
74
|
end
|
72
75
|
|
73
76
|
if (certificate = options[:certificate]).blank?
|
@@ -77,13 +80,7 @@ class Conjur::Command::Init < Conjur::Command
|
|
77
80
|
else
|
78
81
|
hostname + ':443'
|
79
82
|
end
|
80
|
-
certificate =
|
81
|
-
`echo | openssl s_client -connect #{connect_hostname} 2>/dev/null | openssl x509 -fingerprint`
|
82
|
-
exit_now! "Unable to retrieve certificate from #{hostname}" if certificate.blank?
|
83
|
-
|
84
|
-
lines = certificate.split("\n")
|
85
|
-
fingerprint = lines[0]
|
86
|
-
certificate = lines[1..-1].join("\n")
|
83
|
+
fingerprint, certificate = get_certificate connect_hostname
|
87
84
|
|
88
85
|
puts
|
89
86
|
puts fingerprint
|
@@ -118,4 +115,26 @@ class Conjur::Command::Init < Conjur::Command
|
|
118
115
|
puts "Wrote configuration to #{options[:file]}"
|
119
116
|
end
|
120
117
|
end
|
118
|
+
|
119
|
+
def self.get_certificate connect_hostname
|
120
|
+
include OpenSSL::SSL
|
121
|
+
host, port = connect_hostname.split ':'
|
122
|
+
port ||= 443
|
123
|
+
|
124
|
+
sock = TCPSocket.new host, port.to_i
|
125
|
+
ssock = SSLSocket.new sock
|
126
|
+
ssock.connect
|
127
|
+
cert = ssock.peer_cert
|
128
|
+
fp = Digest::SHA1.digest cert.to_der
|
129
|
+
|
130
|
+
# convert to hex, then split into bytes with :
|
131
|
+
hexfp = (fp.unpack 'H*').first.upcase.scan(/../).join(':')
|
132
|
+
|
133
|
+
["SHA1 Fingerprint=#{hexfp}", cert.to_pem]
|
134
|
+
rescue
|
135
|
+
exit_now! "Unable to retrieve certificate from #{connect_hostname}"
|
136
|
+
ensure
|
137
|
+
ssock.close if ssock
|
138
|
+
sock.close if sock
|
139
|
+
end
|
121
140
|
end
|
@@ -20,16 +20,19 @@
|
|
20
20
|
#
|
21
21
|
require 'conjur/command/dsl_command'
|
22
22
|
|
23
|
+
require 'etc'
|
24
|
+
require 'socket'
|
25
|
+
|
23
26
|
class Conjur::Command::Policy < Conjur::DSLCommand
|
24
27
|
self.prefix = :policy
|
25
28
|
|
26
29
|
class << self
|
27
30
|
def default_collection_user
|
28
|
-
|
31
|
+
Etc.getlogin
|
29
32
|
end
|
30
33
|
|
31
34
|
def default_collection_hostname
|
32
|
-
|
35
|
+
Socket.gethostname
|
33
36
|
end
|
34
37
|
|
35
38
|
def default_collection_name
|
@@ -80,4 +83,4 @@ owner of the policy role is the logged-in user (you), as always.
|
|
80
83
|
end
|
81
84
|
end
|
82
85
|
end
|
83
|
-
end
|
86
|
+
end
|
data/lib/conjur/command/roles.rb
CHANGED
@@ -27,6 +27,8 @@ class Conjur::Command::Secrets < Conjur::Command
|
|
27
27
|
desc "Create and store a secret"
|
28
28
|
arg_name "secret"
|
29
29
|
command :create do |c|
|
30
|
+
def c.nodoc; true end
|
31
|
+
|
30
32
|
acting_as_option(c)
|
31
33
|
|
32
34
|
c.action do |global_options,options,args|
|
@@ -38,6 +40,7 @@ class Conjur::Command::Secrets < Conjur::Command
|
|
38
40
|
desc "Retrieve a secret"
|
39
41
|
arg_name "id"
|
40
42
|
command :value do |c|
|
43
|
+
def c.nodoc; true end
|
41
44
|
c.action do |global_options,options,args|
|
42
45
|
id = args.shift or raise "Missing parameter: id"
|
43
46
|
puts api.secret(id).value
|