conjur-cli 5.6.6 → 6.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.dockerignore +1 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -1
- data/APPLIANCE_VERSION +1 -1
- data/CHANGELOG.md +3 -42
- data/Gemfile +4 -7
- data/Humanfile.md +31 -0
- data/Jenkinsfile +34 -63
- data/README.md +41 -55
- data/Rakefile +5 -1
- data/bin/conjur +0 -2
- data/build-deb.sh +1 -3
- data/ci/cli-test.sh +6 -0
- data/ci/package.sh +3 -1
- data/ci/publish.sh +2 -2
- data/ci/secrets/publish.yml +2 -2
- data/ci/wait_for_server.sh +10 -0
- data/conjur-cli.gemspec +7 -7
- data/dev/docker-compose.yml +24 -0
- data/dev/start.sh +15 -0
- data/dev/stop.sh +5 -0
- data/docker-compose.yml +30 -0
- data/features/authentication/authenticate.feature +34 -0
- data/features/authentication/login.feature +13 -0
- data/features/authentication/logout.feature +15 -0
- data/{acceptance-features → features}/authentication/whoami.feature +0 -0
- data/features/authorization/resource/annotate.feature +22 -0
- data/features/authorization/resource/check.feature +47 -0
- data/{acceptance-features → features}/authorization/resource/exists.feature +18 -6
- data/features/authorization/resource/permitted_roles.feature +35 -0
- data/features/authorization/resource/show.feature +34 -0
- data/features/authorization/role/exists.feature +28 -0
- data/features/authorization/role/members.feature +45 -0
- data/features/authorization/role/memberships.feature +43 -0
- data/features/conjurenv/check.feature +34 -0
- data/features/conjurenv/run.feature +15 -0
- data/{acceptance-features → features}/conjurenv/template.feature +8 -3
- data/{acceptance-features → features}/directory/user/update_password.feature +8 -2
- data/{acceptance-features → features}/directory/variable/value.feature +9 -5
- data/{acceptance-features → features}/directory/variable/values-add.feature +8 -3
- data/features/hostfactory/tokens.feature +22 -0
- data/features/pubkeys/show.feature +18 -0
- data/features/step_definitions/authn_steps.rb +22 -0
- data/features/step_definitions/cli_steps.rb +28 -0
- data/features/step_definitions/file_steps.rb +12 -0
- data/features/step_definitions/flow_control_steps.rb +7 -0
- data/features/step_definitions/graph_steps.rb +4 -3
- data/{acceptance-features → features}/step_definitions/http_steps.rb +0 -0
- data/features/step_definitions/overrides.rb +9 -0
- data/features/step_definitions/policy_steps.rb +11 -0
- data/{acceptance-features → features}/step_definitions/trusted_proxy_steps.rb +0 -0
- data/features/support/blank.yml +1 -0
- data/features/support/env.rb +21 -7
- data/features/support/hooks.rb +31 -116
- data/features/support/world.rb +16 -76
- data/jenkins.sh +33 -0
- data/lib/conjur/authenticator.rb +83 -0
- data/lib/conjur/authn.rb +5 -20
- data/lib/conjur/cli.rb +13 -6
- data/lib/conjur/command.rb +30 -350
- data/lib/conjur/command/authn.rb +23 -15
- data/lib/conjur/command/host_factories.rb +2 -74
- data/lib/conjur/command/hosts.rb +6 -113
- data/lib/conjur/command/init.rb +20 -35
- data/lib/conjur/command/{secrets.rb → policies.rb} +33 -22
- data/lib/conjur/command/pubkeys.rb +3 -63
- data/lib/conjur/command/resources.rb +45 -162
- data/lib/conjur/command/roles.rb +11 -181
- data/lib/conjur/command/rspec/helpers.rb +0 -1
- data/lib/conjur/command/rspec/mock_services.rb +4 -4
- data/lib/conjur/command/users.rb +2 -159
- data/lib/conjur/command/variables.rb +5 -218
- data/lib/conjur/complete.rb +2 -2
- data/lib/conjur/config.rb +1 -11
- data/lib/conjur/conjurenv.rb +12 -9
- data/lib/conjur/identifier_manipulation.rb +3 -5
- data/lib/conjur/version.rb +2 -2
- data/{publish-rubygem.sh → publish.sh} +0 -4
- data/spec/authn_spec.rb +4 -0
- data/spec/command/hosts_spec.rb +2 -69
- data/spec/command/init_spec.rb +16 -11
- data/spec/command/pubkeys_spec.rb +1 -46
- data/spec/command/resources_spec.rb +21 -170
- data/spec/command/roles_spec.rb +5 -181
- data/spec/command/users_spec.rb +3 -79
- data/spec/command_spec.rb +1 -20
- data/spec/complete_spec.rb +1 -23
- data/spec/config_spec.rb +1 -1
- data/spec/spec_helper.rb +4 -5
- data/test.sh +29 -25
- metadata +92 -212
- data/.githooks/pre_commit/run_specs.rb +0 -23
- data/Dockerfile +0 -15
- data/Dockerfile.fpm +0 -18
- data/Dockerfile.publish +0 -12
- data/Dockerfile.standalone +0 -33
- data/Dockerfile.validate-packaging +0 -9
- data/VERSION +0 -1
- data/acceptance-features/audit/audit_event_send.feature +0 -107
- data/acceptance-features/audit/fetch.feature +0 -16
- data/acceptance-features/audit/send.feature +0 -51
- data/acceptance-features/authentication/authenticate.feature +0 -10
- data/acceptance-features/authentication/login.feature +0 -12
- data/acceptance-features/authentication/logout.feature +0 -13
- data/acceptance-features/authorization/resource/annotate.feature +0 -35
- data/acceptance-features/authorization/resource/check.feature +0 -24
- data/acceptance-features/authorization/resource/create.feature +0 -21
- data/acceptance-features/authorization/resource/deny.feature +0 -12
- data/acceptance-features/authorization/resource/give.feature +0 -24
- data/acceptance-features/authorization/resource/permit.feature +0 -20
- data/acceptance-features/authorization/resource/permitted_roles.feature +0 -16
- data/acceptance-features/authorization/resource/show.feature +0 -28
- data/acceptance-features/authorization/role/create.feature +0 -13
- data/acceptance-features/authorization/role/exists.feature +0 -19
- data/acceptance-features/authorization/role/grant_to.feature +0 -21
- data/acceptance-features/authorization/role/graph.feature +0 -57
- data/acceptance-features/authorization/role/members.feature +0 -23
- data/acceptance-features/authorization/role/memberships.feature +0 -27
- data/acceptance-features/bootstrap.feature +0 -13
- data/acceptance-features/conjurenv/check.feature +0 -21
- data/acceptance-features/conjurenv/run.feature +0 -10
- data/acceptance-features/directory/group/create.feature +0 -20
- data/acceptance-features/directory/group/retire.feature +0 -54
- data/acceptance-features/directory/host/create.feature +0 -23
- data/acceptance-features/directory/host/retire.feature +0 -6
- data/acceptance-features/directory/hostfactory/create.feature +0 -28
- data/acceptance-features/directory/hostfactory/tokens.feature +0 -16
- data/acceptance-features/directory/layer/create.feature +0 -10
- data/acceptance-features/directory/layer/hosts-add.feature +0 -9
- data/acceptance-features/directory/layer/hosts-remove.feature +0 -10
- data/acceptance-features/directory/layer/retire.feature +0 -43
- data/acceptance-features/directory/user/create.feature +0 -23
- data/acceptance-features/directory/user/retire.feature +0 -6
- data/acceptance-features/directory/variable/create.feature +0 -14
- data/acceptance-features/directory/variable/retire.feature +0 -17
- data/acceptance-features/dsl/policy_owner.feature +0 -45
- data/acceptance-features/dsl/resource_owner.feature +0 -17
- data/acceptance-features/dsl/retire.feature +0 -15
- data/acceptance-features/global-privilege/elevate.feature +0 -20
- data/acceptance-features/global-privilege/reveal.privilege +0 -20
- data/acceptance-features/pubkeys/add.feature +0 -22
- data/acceptance-features/pubkeys/delete.feature +0 -9
- data/acceptance-features/pubkeys/names.feature +0 -26
- data/acceptance-features/pubkeys/show.feature +0 -27
- data/acceptance-features/step_definitions/cli_steps.rb +0 -57
- data/acceptance-features/step_definitions/graph_steps.rb +0 -22
- data/acceptance-features/step_definitions/user_steps.rb +0 -51
- data/acceptance-features/support/env.rb +0 -23
- data/acceptance-features/support/hooks.rb +0 -178
- data/acceptance-features/support/world.rb +0 -176
- data/acceptance-features/trusted_proxies.feature +0 -82
- data/bin/conjurize +0 -26
- data/bin/jsonfield +0 -70
- data/build-standalone +0 -6
- data/deprecations.sh +0 -38
- data/features/conjurize.feature +0 -134
- data/features/dsl_context.feature +0 -36
- data/features/dsl_host_create.feature +0 -11
- data/features/dsl_ownership.feature +0 -30
- data/features/dsl_permission.feature +0 -45
- data/features/dsl_resource_create.feature +0 -23
- data/features/dsl_role_create.feature +0 -11
- data/features/dsl_user_create.feature +0 -23
- data/features/jsonfield.feature +0 -49
- data/features/role_graph.feature +0 -58
- data/features/step_definitions/conjurize_steps.rb +0 -5
- data/features/step_definitions/dsl_steps.rb +0 -52
- data/features/support/conjur.conf +0 -6
- data/lib/conjur/command/assets.rb +0 -121
- data/lib/conjur/command/audit.rb +0 -155
- data/lib/conjur/command/bootstrap.rb +0 -129
- data/lib/conjur/command/dsl_command.rb +0 -75
- data/lib/conjur/command/elevate.rb +0 -76
- data/lib/conjur/command/field.rb +0 -45
- data/lib/conjur/command/groups.rb +0 -208
- data/lib/conjur/command/ids.rb +0 -34
- data/lib/conjur/command/layers.rb +0 -211
- data/lib/conjur/command/ldapsync.rb +0 -118
- data/lib/conjur/command/rspec/audit_helpers.rb +0 -68
- data/lib/conjur/command/rubydsl.rb +0 -93
- data/lib/conjur/command/script.rb +0 -48
- data/lib/conjur/command/server.rb +0 -67
- data/lib/conjur/conjurize.rb +0 -71
- data/lib/conjur/conjurize/script.rb +0 -150
- data/lib/conjur/dsl/runner.rb +0 -273
- data/publish-deb.sh +0 -6
- data/push-image +0 -29
- data/spec/command/assets_spec.rb +0 -115
- data/spec/command/audit_spec.rb +0 -376
- data/spec/command/elevate_spec.rb +0 -28
- data/spec/command/env_spec.rb +0 -168
- data/spec/command/groups_spec.rb +0 -77
- data/spec/command/host_factories_spec.rb +0 -38
- data/spec/command/layers_spec.rb +0 -35
- data/spec/command/ldapsync_spec.rb +0 -28
- data/spec/command/rubydsl_spec.rb +0 -63
- data/spec/command/variable_expiration_spec.rb +0 -164
- data/spec/command/variables_spec.rb +0 -192
- data/spec/conjurize/script_spec.rb +0 -62
- data/spec/conjurize_spec.rb +0 -70
- data/spec/dsl/runner_spec.rb +0 -93
- data/spec/env_spec.rb +0 -214
data/features/support/world.rb
CHANGED
@@ -1,88 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
last_stdout
|
5
|
-
end
|
6
|
-
|
7
|
-
def last_stdout
|
8
|
-
raise "No commands have been run" unless last_cmd
|
9
|
-
stdout_from last_cmd
|
10
|
-
end
|
11
|
-
|
12
|
-
attr_accessor :last_cmd
|
13
|
-
|
14
|
-
def account
|
15
|
-
Conjur::Core::API.conjur_account
|
16
|
-
end
|
17
|
-
|
18
|
-
def role_kind
|
19
|
-
@role_kind ||= "cli-cukes"
|
20
|
-
end
|
21
|
-
|
22
|
-
def role_id_map
|
23
|
-
@role_id_map ||= {}
|
1
|
+
module CLIWorld
|
2
|
+
def tempfiles
|
3
|
+
@tempfiles ||= []
|
24
4
|
end
|
25
5
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def filter_hash_graph graph
|
36
|
-
allowed = role_id_map.values
|
37
|
-
edges = graph['graph']
|
38
|
-
filtered = edges.select do |edge|
|
39
|
-
allowed.member?(edge['parent']) and allowed.member?(edge['child'])
|
6
|
+
def api_key_of username
|
7
|
+
role = @policy_response.created_roles["cucumber:user:#{username}"]
|
8
|
+
if role
|
9
|
+
role['api_key']
|
10
|
+
else
|
11
|
+
$conjur.resource("cucumber:user:#{username}").rotate_api_key
|
40
12
|
end
|
41
|
-
{'graph' => filtered}
|
42
13
|
end
|
43
|
-
|
44
|
-
def filter_array_graph graph
|
45
|
-
allowed = role_id_map.values
|
46
|
-
graph.select do |edge|
|
47
|
-
edge.all?{|v| allowed.member?(v)}
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def graph edges
|
52
|
-
# generate roles
|
53
|
-
edges.flatten.uniq.each do |role_id|
|
54
|
-
role_id_map[role_id] = expanded = expand_role_id(role_id)
|
55
|
-
run_command "conjur role create '#{expanded}'"
|
56
|
-
end
|
57
14
|
|
58
|
-
|
59
|
-
|
60
|
-
run_command "conjur role grant_to #{expand_role_id(parent)} #{expand_role_id(child)}"
|
61
|
-
end
|
15
|
+
def clear_last_json
|
16
|
+
@last_json = nil
|
62
17
|
end
|
63
18
|
|
64
|
-
def
|
65
|
-
|
19
|
+
def last_json
|
20
|
+
@last_json || last_command_started.stdout
|
66
21
|
end
|
67
22
|
|
68
|
-
def
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
def prepend_namespace id
|
73
|
-
"#{namespace}-#{id}"
|
74
|
-
end
|
75
|
-
|
76
|
-
def namespace
|
77
|
-
@namespace ||= "ns-#{Time.now.to_i}-#{rand(1 << 32)}"
|
78
|
-
end
|
79
|
-
|
80
|
-
def expand_roles string
|
81
|
-
role_id_map.each do |role, expanded|
|
82
|
-
string.gsub! role, expanded
|
83
|
-
end
|
84
|
-
string
|
23
|
+
def load_policy id, policy, method
|
24
|
+
@policy_response = $conjur.load_policy id, policy, method: method
|
85
25
|
end
|
86
26
|
end
|
87
27
|
|
88
|
-
World(
|
28
|
+
World(CLIWorld)
|
data/jenkins.sh
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/bin/bash -ex
|
2
|
+
|
3
|
+
# Constants
|
4
|
+
RUBY_VERSION_DEFAULT="2.2.4"
|
5
|
+
|
6
|
+
# Arguments
|
7
|
+
RUBY_VERSION=${1-${RUBY_VERSION_DEFAULT}}
|
8
|
+
|
9
|
+
# Script
|
10
|
+
|
11
|
+
# Clones 'Dockerfile' and updates the Ruby version in FROM, returning the cloned file's path
|
12
|
+
function dockerfile_path {
|
13
|
+
echo "Setting Ruby version as ${RUBY_VERSION}" >&2
|
14
|
+
cp "Dockerfile" "Dockerfile.${RUBY_VERSION}"
|
15
|
+
sed -i -e "s/${RUBY_VERSION_DEFAULT}/${RUBY_VERSION}/g" Dockerfile.${RUBY_VERSION}
|
16
|
+
|
17
|
+
echo "Dockerfile.${RUBY_VERSION}"
|
18
|
+
}
|
19
|
+
|
20
|
+
rm -f Gemfile.lock # Needed for bundle to work right
|
21
|
+
|
22
|
+
IMAGE_NAME="cli-ruby:${RUBY_VERSION}" # The tag is the version of Ruby tested against
|
23
|
+
|
24
|
+
docker build -t ${IMAGE_NAME} -f $(dockerfile_path) .
|
25
|
+
|
26
|
+
docker run --rm \
|
27
|
+
-v $PWD:/src \
|
28
|
+
${IMAGE_NAME} \
|
29
|
+
bash -c '''
|
30
|
+
bundle update
|
31
|
+
bundle exec rake jenkins
|
32
|
+
bundle exec rake build
|
33
|
+
'''
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Conjur
|
2
|
+
# Keeps a fresh Conjur access token in a named file by re-authenticating as needed.
|
3
|
+
class Authenticator
|
4
|
+
require 'tempfile'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
TOKEN_LIFESPAN = ( ENV['CONJUR_TOKEN_LIFESPAN'] || 5 * 60 ).to_i.seconds
|
8
|
+
DELAY = ( ENV['CONJUR_TOKEN_REFRESH_DELAY'] || 10 ).to_i.seconds
|
9
|
+
|
10
|
+
attr_reader :authenticate, :filename
|
11
|
+
|
12
|
+
# +authenticate+ should be a proc that authenticates with Conjur and returns an
|
13
|
+
# access token as a Hash.
|
14
|
+
def initialize authenticate, filename
|
15
|
+
@authenticate = authenticate
|
16
|
+
@filename = filename
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def default_filename
|
21
|
+
"/run/conjur-access-token"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check the token every +DELAY+ seconds and refresh it if it's out of date.
|
25
|
+
def run authenticate:, filename: default_filename
|
26
|
+
while true
|
27
|
+
authenticator = Authenticator.new(authenticate, filename)
|
28
|
+
authenticator.refresh unless authenticator.fresh?
|
29
|
+
sleep DELAY
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def fresh?
|
35
|
+
token && (token_age <= TOKEN_LIFESPAN)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Perform atomic replacement of the token
|
39
|
+
def refresh
|
40
|
+
token = authenticate.call
|
41
|
+
file = Tempfile.new('conjur-access-token.')
|
42
|
+
begin
|
43
|
+
file.write JSON.pretty_generate(token)
|
44
|
+
file.close
|
45
|
+
FileUtils.mv file.path, filename
|
46
|
+
Conjur.log << "Refreshed Conjur auth token to #{filename.inspect}\n" if Conjur.log
|
47
|
+
ensure
|
48
|
+
file.unlink
|
49
|
+
end
|
50
|
+
rescue
|
51
|
+
$stderr.puts $!
|
52
|
+
end
|
53
|
+
|
54
|
+
def token
|
55
|
+
return false if @token == false
|
56
|
+
@token ||= load_token
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def random nbytes = 12
|
62
|
+
@random ||= Random.new
|
63
|
+
@random.bytes(nbytes).unpack('h*').first
|
64
|
+
end
|
65
|
+
|
66
|
+
def directory
|
67
|
+
File.dirname(filename)
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_token
|
71
|
+
return false unless File.file?(filename)
|
72
|
+
JSON.parse(File.read(filename)) rescue false
|
73
|
+
end
|
74
|
+
|
75
|
+
def token_born
|
76
|
+
File.mtime(filename)
|
77
|
+
end
|
78
|
+
|
79
|
+
def token_age
|
80
|
+
Time.now - token_born
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/conjur/authn.rb
CHANGED
@@ -34,7 +34,6 @@ module Conjur::Authn
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
autoload :API, 'conjur/authn-api'
|
38
37
|
class << self
|
39
38
|
def login(options = {})
|
40
39
|
delete_credentials
|
@@ -43,18 +42,14 @@ module Conjur::Authn
|
|
43
42
|
|
44
43
|
def authenticate(options = {})
|
45
44
|
require 'conjur/api'
|
46
|
-
Conjur::API.authenticate
|
45
|
+
Conjur::API.authenticate *get_credentials(options)
|
47
46
|
end
|
48
47
|
|
49
48
|
def delete_credentials
|
50
|
-
netrc.delete
|
49
|
+
netrc.delete Conjur.configuration.authn_url
|
51
50
|
netrc.save
|
52
51
|
end
|
53
52
|
|
54
|
-
def host
|
55
|
-
Conjur::Authn::API.host
|
56
|
-
end
|
57
|
-
|
58
53
|
def netrc
|
59
54
|
@netrc ||= read_netrc
|
60
55
|
end
|
@@ -83,7 +78,7 @@ module Conjur::Authn
|
|
83
78
|
end
|
84
79
|
|
85
80
|
def read_credentials
|
86
|
-
netrc[
|
81
|
+
netrc[Conjur.configuration.authn_url]
|
87
82
|
end
|
88
83
|
|
89
84
|
def fetch_credentials(options = {})
|
@@ -94,7 +89,7 @@ module Conjur::Authn
|
|
94
89
|
alias save_credentials fetch_credentials
|
95
90
|
|
96
91
|
def write_credentials
|
97
|
-
netrc[
|
92
|
+
netrc[Conjur.configuration.authn_url] = @credentials
|
98
93
|
netrc.save
|
99
94
|
@credentials
|
100
95
|
end
|
@@ -128,10 +123,8 @@ module Conjur::Authn
|
|
128
123
|
end
|
129
124
|
if token = token_from_environment
|
130
125
|
cls.new_from_token token
|
131
|
-
elsif token_file = token_from_file
|
132
|
-
cls.new_from_token_file token_file
|
133
126
|
else
|
134
|
-
cls.new_from_key
|
127
|
+
cls.new_from_key *get_credentials(options)
|
135
128
|
end
|
136
129
|
end
|
137
130
|
|
@@ -155,13 +148,5 @@ module Conjur::Authn
|
|
155
148
|
require 'base64'
|
156
149
|
JSON.parse(Base64.decode64(token))
|
157
150
|
end
|
158
|
-
|
159
|
-
def token_from_file
|
160
|
-
token_file = ENV['CONJUR_AUTHN_TOKEN_FILE']
|
161
|
-
if token_file && !File.exists?(token_file)
|
162
|
-
$stderr.puts "Warning: CONJUR_AUTHN_TOKEN_FILE #{token_file.inspect} does not exist"
|
163
|
-
end
|
164
|
-
token_file
|
165
|
-
end
|
166
151
|
end
|
167
152
|
end
|
data/lib/conjur/cli.rb
CHANGED
@@ -37,6 +37,7 @@ module Conjur
|
|
37
37
|
autoload :Log, 'conjur/log'
|
38
38
|
autoload :IdentifierManipulation, 'conjur/identifier_manipulation'
|
39
39
|
autoload :Authn, 'conjur/authn'
|
40
|
+
autoload :Authenticator, 'conjur/authenticator'
|
40
41
|
autoload :Command, 'conjur/command'
|
41
42
|
autoload :DSL, 'conjur/dsl/runner'
|
42
43
|
autoload :DSLCommand, 'conjur/command/dsl_command'
|
@@ -117,7 +118,7 @@ module Conjur
|
|
117
118
|
require 'conjur/api'
|
118
119
|
|
119
120
|
if command.name_for_help.first == "init" and options.has_key?("account")
|
120
|
-
ENV["CONJUR_ACCOUNT"]=options["account"]
|
121
|
+
ENV["CONJUR_ACCOUNT"] = options["account"]
|
121
122
|
end
|
122
123
|
apply_config
|
123
124
|
require 'active_support/core_ext'
|
@@ -154,12 +155,18 @@ module Conjur
|
|
154
155
|
$stderr.puts "error: this command is not supported by the current Conjur server version"
|
155
156
|
run_default_handler = false
|
156
157
|
elsif exception.is_a?(RestClient::Exception) && exception.response
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
run_default_handler = false # suppress default error message
|
158
|
+
if exception.http_code == 401
|
159
|
+
$stderr.puts "Unable to authenticate with Conjur. Please check your credentials."
|
160
|
+
run_default_handler = false
|
161
161
|
else
|
162
|
-
|
162
|
+
err = Conjur::Error.create exception.response.body
|
163
|
+
|
164
|
+
if err
|
165
|
+
$stderr.puts "error: " + err.message
|
166
|
+
run_default_handler = false # suppress default error message
|
167
|
+
else
|
168
|
+
$stderr.puts exception.response.body
|
169
|
+
end
|
163
170
|
end
|
164
171
|
end
|
165
172
|
|
data/lib/conjur/command.rb
CHANGED
@@ -19,7 +19,6 @@
|
|
19
19
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
20
|
#
|
21
21
|
require 'base64'
|
22
|
-
require 'semantic'
|
23
22
|
|
24
23
|
module Conjur
|
25
24
|
class Command
|
@@ -29,7 +28,7 @@ module Conjur
|
|
29
28
|
|
30
29
|
class << self
|
31
30
|
attr_accessor :prefix
|
32
|
-
|
31
|
+
|
33
32
|
def method_missing *a, &b
|
34
33
|
Conjur::CLI.send *a, &b
|
35
34
|
end
|
@@ -70,61 +69,12 @@ module Conjur
|
|
70
69
|
def command.nodoc; true end
|
71
70
|
end
|
72
71
|
|
73
|
-
def acting_as_option command
|
74
|
-
return if command.flags.member?(:"as-group") # avoid duplicate flags
|
75
|
-
command.desc 'Perform all actions as the specified Group'
|
76
|
-
command.arg_name 'GROUP'
|
77
|
-
command.flag [:'as-group']
|
78
|
-
|
79
|
-
command.desc 'Perform all actions as the specified Role'
|
80
|
-
command.arg_name 'ROLE'
|
81
|
-
command.flag [:'as-role']
|
82
|
-
end
|
83
|
-
|
84
|
-
def collection_option command
|
85
|
-
command.desc 'An optional prefix for created roles and resources'
|
86
|
-
command.arg_name 'collection'
|
87
|
-
command.flag [:collection]
|
88
|
-
end
|
89
|
-
|
90
72
|
def context_option command
|
91
73
|
command.desc "Load context from this config file, and save it when finished. The file permissions will be 0600 by default."
|
92
74
|
command.arg_name "FILE"
|
93
75
|
command.flag [:c, :context]
|
94
76
|
end
|
95
77
|
|
96
|
-
def interactive_option command
|
97
|
-
command.arg_name 'interactive'
|
98
|
-
command.desc 'Create variable interactively'
|
99
|
-
command.switch [:i, :'interactive']
|
100
|
-
end
|
101
|
-
|
102
|
-
def annotate_option command
|
103
|
-
command.arg_name 'annotate'
|
104
|
-
command.desc 'Add variable annotations interactively'
|
105
|
-
command.switch [:a, :annotate]
|
106
|
-
end
|
107
|
-
|
108
|
-
def min_version command, version
|
109
|
-
version = Semantic::Version.new version
|
110
|
-
if version.pre == nil
|
111
|
-
# Version check doesn't work correctly if one version has
|
112
|
-
# the "-###" suffix and the other does not. Versions
|
113
|
-
# returned by the server have the suffix.
|
114
|
-
version.pre = "0"
|
115
|
-
end
|
116
|
-
command.instance_variable_set(:@conjur_min_version, version)
|
117
|
-
end
|
118
|
-
|
119
|
-
def prompt_for_annotations
|
120
|
-
highline.say('Add annotations (a name and value for each one):')
|
121
|
-
{}.tap do |annotations|
|
122
|
-
until (name = highline.ask(' annotation name (press enter to quit annotations): ')).empty?
|
123
|
-
annotations[name] = read_till_eof(' annotation value (^D on its own line to finish):')
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
78
|
def highline
|
129
79
|
require 'highline'
|
130
80
|
@highline ||= HighLine.new($stdin,$stderr)
|
@@ -142,62 +92,35 @@ module Conjur
|
|
142
92
|
end
|
143
93
|
end.join("\n")
|
144
94
|
end
|
145
|
-
|
146
|
-
def
|
147
|
-
c.desc "Filter by kind"
|
148
|
-
c.flag [:k, :kind]
|
149
|
-
end
|
150
|
-
|
151
|
-
def command_option_as_role c
|
95
|
+
|
96
|
+
def command_options_for_list(c)
|
152
97
|
return if c.flags.member?(:role) # avoid duplicate flags
|
153
98
|
c.desc "Role to act as. By default, the current logged-in role is used."
|
154
99
|
c.arg_name 'ROLE'
|
155
100
|
c.flag [:role]
|
156
|
-
|
157
|
-
|
158
|
-
def command_options_for_search c
|
101
|
+
|
159
102
|
c.desc "Full-text search on resource id and annotation values"
|
160
103
|
c.flag [:s, :search]
|
161
104
|
|
105
|
+
c.desc "Maximum number of records to return"
|
106
|
+
c.flag [:l, :limit]
|
107
|
+
|
162
108
|
c.desc "Offset to start from"
|
163
109
|
c.flag [:o, :offset]
|
164
110
|
|
165
|
-
c.desc "
|
166
|
-
c.
|
167
|
-
|
168
|
-
c.desc "Show the number of results, rather than printing them"
|
169
|
-
c.switch [:count]
|
170
|
-
end
|
171
|
-
|
172
|
-
def command_options_for_list(c)
|
173
|
-
command_option_as_role c
|
174
|
-
command_options_for_search c
|
175
|
-
|
176
|
-
c.desc "Show only ids"
|
177
|
-
c.switch [:i, :ids]
|
178
|
-
|
111
|
+
c.desc "Show full object information"
|
112
|
+
c.switch [:inspect]
|
113
|
+
|
179
114
|
c.desc "Show annotations in 'raw' format"
|
180
115
|
c.switch [:r, :"raw-annotations"]
|
181
116
|
end
|
182
|
-
|
183
|
-
def process_command_options_for_search options
|
184
|
-
opts = options.slice(:search, :count, :limit, :offset, :kind)
|
185
|
-
opts[:acting_as] = options[:role] if options[:role]
|
186
|
-
opts[:search] = opts[:search].gsub('-',' ') if opts[:search]
|
187
|
-
if opts[:kind] && opts[:kind].index(',')
|
188
|
-
opts[:kind] = opts[:kind].split(',')
|
189
|
-
end
|
190
|
-
opts
|
191
|
-
end
|
192
117
|
|
193
118
|
def command_impl_for_list(global_options, options, args)
|
194
|
-
opts =
|
119
|
+
opts = options.slice(:search, :limit, :options, :kind)
|
120
|
+
opts[:acting_as] = options[:role] if options[:role]
|
121
|
+
opts[:search]=opts[:search].gsub('-',' ') if opts[:search]
|
195
122
|
resources = api.resources(opts)
|
196
|
-
if
|
197
|
-
puts resources
|
198
|
-
elsif options[:ids]
|
199
|
-
puts JSON.pretty_generate(resources.map(&:resourceid))
|
200
|
-
else
|
123
|
+
if options[:inspect]
|
201
124
|
resources = resources.map &:attributes
|
202
125
|
unless options[:'raw-annotations']
|
203
126
|
resources = resources.map do |r|
|
@@ -209,6 +132,8 @@ module Conjur
|
|
209
132
|
end
|
210
133
|
end
|
211
134
|
puts JSON.pretty_generate resources
|
135
|
+
else
|
136
|
+
puts JSON.pretty_generate(resources.map(&:id))
|
212
137
|
end
|
213
138
|
end
|
214
139
|
|
@@ -220,160 +145,24 @@ module Conjur
|
|
220
145
|
end
|
221
146
|
exit_now! message unless valid
|
222
147
|
end
|
223
|
-
|
224
|
-
def retire_options command
|
225
|
-
command.arg_name 'role'
|
226
|
-
command.desc "Specify a role to give the retired record to (default: the 'attic' user)"
|
227
|
-
command.long_desc %Q(When retired, all a record's roles and permissions are revoked.
|
228
|
-
|
229
|
-
As a final step, the record is 'given' (e.g. 'conjur resource give') to a destination role.
|
230
|
-
The default role to receive the record is the user 'attic'. This option can be used to specify
|
231
|
-
an alternative destination role.)
|
232
|
-
command.flag [:d, :"destination-role"]
|
233
|
-
end
|
234
|
-
|
235
|
-
def destination_role options
|
236
|
-
destination = options[:"destination-role"]
|
237
|
-
if destination
|
238
|
-
api.role(destination)
|
239
|
-
else
|
240
|
-
api.user('attic')
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
def elevated?
|
245
|
-
api.privilege == 'elevate' && api.global_privilege_permitted?('elevate')
|
246
|
-
end
|
247
|
-
|
248
|
-
def validate_retire_privileges record, options
|
249
|
-
return true if elevated?
|
250
|
-
|
251
|
-
if record.respond_to?(:role)
|
252
|
-
memberships = current_user.role.memberships.map(&:roleid)
|
253
|
-
validate_privileges "You can't administer this record" do
|
254
|
-
# The current user has a role which is admin of the record's role
|
255
|
-
record.role.members.find{|m| memberships.member?(m.member.roleid) && m.admin_option}
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
validate_privileges "You don't own the record" do
|
260
|
-
# The current user has the role which owns the record's resource
|
261
|
-
current_user.role.member_of?(record.resource.ownerid)
|
262
|
-
end
|
263
|
-
|
264
|
-
role = destination_role(options)
|
265
|
-
exit_now! "Destination role '#{role.roleid}' doesn't exist" unless role.exists?
|
266
|
-
end
|
267
|
-
|
268
|
-
def retire_resource obj
|
269
|
-
obj.resource.attributes['permissions'].each do |p|
|
270
|
-
role = api.role(p['role'])
|
271
|
-
privilege = p['privilege']
|
272
|
-
next if obj.respond_to?(:roleid) && role.roleid == obj.roleid && privilege == 'read'
|
273
|
-
puts "Denying #{privilege} privilege to #{role.roleid}"
|
274
|
-
obj.resource.deny(privilege, role)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
def retire_role obj
|
279
|
-
members = obj.role.members
|
280
|
-
# Move the invoking role to the end of the roles list, so that it doesn't
|
281
|
-
# lose its permissions in the middle of this operation.
|
282
|
-
# I'm sure there's a cleaner way to do this.
|
283
|
-
self_member = members.select{|m| m.member.roleid == current_user.role.roleid}
|
284
|
-
self_member.each do |m|
|
285
|
-
members.delete m
|
286
|
-
end
|
287
|
-
members.concat self_member if self_member
|
288
|
-
members.each do |r|
|
289
|
-
member = api.role(r.member)
|
290
|
-
puts "Revoking from role #{member.roleid}"
|
291
|
-
obj.role.revoke_from member
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
def retire_internal_role roleObj
|
296
|
-
members = roleObj.members
|
297
|
-
# Move the invoking role to the end of the roles list, so that it doesn't
|
298
|
-
# lose its permissions in the middle of this operation.
|
299
|
-
self_member = members.select{|m| m.member.roleid == current_user.role.roleid}
|
300
|
-
self_member.each do |m|
|
301
|
-
members.delete m
|
302
|
-
end
|
303
|
-
members.concat self_member if self_member
|
304
|
-
members.each do |r|
|
305
|
-
member = api.role(r.member)
|
306
|
-
puts "Revoking from role #{member.roleid}"
|
307
|
-
roleObj.revoke_from member
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
def give_away_resource obj, options
|
312
|
-
destination = options[:"destination-role"]
|
313
|
-
destination_role = if destination
|
314
|
-
api.role(destination)
|
315
|
-
else
|
316
|
-
api.user('attic')
|
317
|
-
end
|
318
148
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
# * A list of role ids
|
329
|
-
# * A list of RoleGrant
|
330
|
-
#
|
331
|
-
# +options+ may include:
|
332
|
-
#
|
333
|
-
# * **V** Show full RoleGrant info
|
334
|
-
# * **system** show system (internal) roles
|
335
|
-
#
|
336
|
-
# @return [Numeric, Hash] Convert the input to a number or Hash.
|
337
|
-
def display_members(members, member_field, options)
|
338
|
-
# Get this case out of the way
|
339
|
-
return display(members) if members.is_a?(Numeric)
|
340
|
-
|
341
|
-
result, roleid_function = if members.blank?
|
342
|
-
[ [], nil ]
|
343
|
-
elsif members.first.is_a?(Conjur::RoleGrant)
|
344
|
-
if options[:V]
|
345
|
-
items = members.collect do |member|
|
346
|
-
{
|
347
|
-
member: member.member.roleid,
|
348
|
-
grantor: member.grantor.roleid,
|
349
|
-
admin_option: member.admin_option
|
350
|
-
}.tap do |obj|
|
351
|
-
obj[:role] = member.role.roleid if member.role
|
352
|
-
end
|
353
|
-
end
|
354
|
-
[
|
355
|
-
items, lambda {|member| member[member_field] }
|
356
|
-
]
|
357
|
-
else
|
358
|
-
[ members.map{|m| m.send(member_field) }.map(&:roleid), lambda{|r| r} ]
|
359
|
-
end
|
149
|
+
def display_members(members, options)
|
150
|
+
result = if options[:V]
|
151
|
+
members.collect {|member|
|
152
|
+
{
|
153
|
+
role: member.role.id,
|
154
|
+
member: member.member.id,
|
155
|
+
admin_option: member.admin_option
|
156
|
+
}
|
157
|
+
}
|
360
158
|
else
|
361
|
-
|
159
|
+
members.map(&:member).map(&:id)
|
362
160
|
end
|
363
|
-
|
364
|
-
unless options[:system]
|
365
|
-
result.reject! do |member|
|
366
|
-
roleid_function.call(member) =~ /^.+?:@/
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
161
|
display result
|
371
162
|
end
|
372
163
|
|
373
164
|
def display(obj, options = {})
|
374
|
-
str = if obj.
|
375
|
-
obj.to_s
|
376
|
-
elsif obj.respond_to?(:attributes)
|
165
|
+
str = if obj.respond_to?(:attributes)
|
377
166
|
JSON.pretty_generate obj.attributes
|
378
167
|
elsif obj.respond_to?(:id)
|
379
168
|
obj.id
|
@@ -387,105 +176,9 @@ an alternative destination role.)
|
|
387
176
|
puts str
|
388
177
|
end
|
389
178
|
|
390
|
-
def prompt_to_confirm kind, properties
|
391
|
-
puts
|
392
|
-
puts "A new #{kind} will be created with the following properties:"
|
393
|
-
puts
|
394
|
-
properties.select{|k,v| !v.blank?}.each do |k,v|
|
395
|
-
printf "%-10s: %s\n", k, v
|
396
|
-
end
|
397
|
-
puts
|
398
|
-
|
399
|
-
exit(0) unless %w(yes y).member?(highline.ask("Proceed? (yes/no): ").strip.downcase)
|
400
|
-
end
|
401
|
-
|
402
179
|
def integer? v
|
403
180
|
Integer(v, 10) rescue false
|
404
181
|
end
|
405
|
-
|
406
|
-
def prompt_for_id kind, label = 'id'
|
407
|
-
highline.ask("Enter the #{label}: ") do |q|
|
408
|
-
q.readline = true
|
409
|
-
q.validate = lambda{|id|
|
410
|
-
!id.blank? && !api.send(kind, id).exists?
|
411
|
-
}
|
412
|
-
q.responses[:not_valid] = "<% if @answer.blank? %>"\
|
413
|
-
"#{label} cannot be blank<% else %>"\
|
414
|
-
"A #{kind} called '<%= @answer %>' already exists<% end %>"
|
415
|
-
end
|
416
|
-
end
|
417
|
-
|
418
|
-
def prompt_for_public_key
|
419
|
-
public_key = highline.ask("Enter the public key (press enter to skip): ") do |q|
|
420
|
-
q.validate = lambda{|key|
|
421
|
-
if key.blank?
|
422
|
-
true
|
423
|
-
else
|
424
|
-
validate_public_key key
|
425
|
-
end
|
426
|
-
}
|
427
|
-
q.responses[:not_valid] = "Public key format is invalid; please try again"
|
428
|
-
end
|
429
|
-
public_key.blank? ? nil : public_key.strip
|
430
|
-
end
|
431
|
-
|
432
|
-
# http://serverfault.com/questions/453296/how-do-i-validate-a-rsa-ssh-public-key-file-id-rsa-pub
|
433
|
-
def validate_public_key key
|
434
|
-
if system('which ssh-keygen 2>&1 > /dev/null')
|
435
|
-
Conjur.log.debug "Using ssh-keygen to verify the public key\n" if Conjur.log
|
436
|
-
require 'tempfile'
|
437
|
-
tempfile = Tempfile.new 'public_key'
|
438
|
-
tempfile.write(key)
|
439
|
-
tempfile.close
|
440
|
-
`ssh-keygen -l -f #{tempfile.path}`
|
441
|
-
$? == 0
|
442
|
-
else
|
443
|
-
Conjur.log.debug "ssh-keygen is not available; falling back to simple string testing\n" if Conjur.log
|
444
|
-
# Should be a line with at least 2 components,
|
445
|
-
# first one being the algo id and second a base64 string.
|
446
|
-
# In principle this means:
|
447
|
-
# Base64.strict_decode64 key.strip[/\Assh-\w+ (\S+).*/, 1]
|
448
|
-
|
449
|
-
# Since the pubkeys service is more strict: needs a name and
|
450
|
-
# rejects ones with a space, instead reproduce its algorithm here.
|
451
|
-
begin
|
452
|
-
components = key.strip.split ' '
|
453
|
-
Base64.strict_decode64 components[1]
|
454
|
-
components.length == 3
|
455
|
-
rescue NoMethodError, ArgumentError
|
456
|
-
false
|
457
|
-
end
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
def prompt_for_group options = {}
|
462
|
-
options[:hint] ||= "press enter to own the record yourself"
|
463
|
-
group_ids = api.groups.map(&:id)
|
464
|
-
|
465
|
-
highline.ask("Enter the group which will own the record (#{options[:hint]}): ", [ "" ] + group_ids) do |q|
|
466
|
-
require 'readline'
|
467
|
-
Readline.completion_append_character = ""
|
468
|
-
Readline.completer_word_break_characters = ""
|
469
|
-
|
470
|
-
q.readline = true
|
471
|
-
q.validate = lambda{|id|
|
472
|
-
@group = nil
|
473
|
-
id.empty? || (@group = api.group(id)).exists?
|
474
|
-
}
|
475
|
-
q.responses[:not_valid] = "Group '<%= @answer %>' doesn't exist, or you don't have permission to use it"
|
476
|
-
end
|
477
|
-
@group ? @group.roleid : nil
|
478
|
-
end
|
479
|
-
|
480
|
-
def prompt_for_idnumber label
|
481
|
-
result = highline.ask("Enter a #{label}: ") do |q|
|
482
|
-
q.validate = lambda{|id|
|
483
|
-
id.blank? || integer?(id)
|
484
|
-
}
|
485
|
-
q.responses[:not_valid] = "The #{label} must be an integer"
|
486
|
-
end
|
487
|
-
result.blank? ? nil : result.to_i
|
488
|
-
end
|
489
182
|
|
490
183
|
def prompt_for_password
|
491
184
|
require 'highline'
|
@@ -509,27 +202,14 @@ an alternative destination role.)
|
|
509
202
|
password
|
510
203
|
end
|
511
204
|
|
512
|
-
def current_role
|
513
|
-
kind, id = api.username.split('/', 2)
|
514
|
-
if id.nil?
|
515
|
-
id = kind
|
516
|
-
kind = 'user'
|
517
|
-
end
|
518
|
-
api.role([ kind, id ].join(":"))
|
519
|
-
end
|
520
|
-
|
521
205
|
def has_admin?(role, other_role)
|
522
|
-
return true if role.
|
523
|
-
memberships = role.memberships.map(&:
|
524
|
-
other_role.members.any? { |m| memberships.member?(m.member.
|
206
|
+
return true if role.id == other_role.id
|
207
|
+
memberships = role.memberships.map(&:id)
|
208
|
+
other_role.members.any? { |m| memberships.member?(m.member.id) && m.admin_option }
|
525
209
|
rescue RestClient::Forbidden
|
526
210
|
false
|
527
211
|
end
|
528
212
|
|
529
|
-
def notify_deprecated
|
530
|
-
STDERR.puts 'WARNING! This command is deprecated and will be removed. Use policy instead.'
|
531
|
-
end
|
532
|
-
|
533
213
|
end
|
534
214
|
end
|
535
215
|
end
|