conjur-cli 4.1.1 → 4.3.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.
@@ -0,0 +1,23 @@
1
+ require 'English'
2
+
3
+ module Overcommit::GitHook
4
+ # Try to avoid commiting code which breaks specs.
5
+ # Install the hook with `overcommit .` in the top directory.
6
+ class SpecsPass < HookSpecificCheck
7
+ include HookRegistry
8
+ file_types :rb
9
+
10
+ def run_check
11
+ unless in_path?('rspec')
12
+ return :warn, 'rspec not installed -- run `gem install rspec`'
13
+ end
14
+
15
+ output = `rspec 2>&1`
16
+ if $CHILD_STATUS.exitstatus == 0
17
+ return :good
18
+ else
19
+ return :bad, output
20
+ end
21
+ end
22
+ end
23
+ end
data/.gitignore CHANGED
@@ -24,4 +24,4 @@ tmp
24
24
  .kateproject.d
25
25
  .idea
26
26
  .rvmrc
27
-
27
+ update_ci.sh
data/Gemfile CHANGED
@@ -3,5 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in conjur.gemspec
4
4
  gemspec
5
5
 
6
- # when developing in parallel, you might want to uncomment the following:
7
6
  gem 'conjur-api', git: 'https://github.com/inscitiv/api-ruby.git', branch: 'master'
@@ -20,6 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.add_dependency 'highline'
21
21
  gem.add_dependency 'netrc'
22
22
  gem.add_dependency 'methadone'
23
+ gem.add_dependency 'deep_merge'
23
24
 
24
25
  gem.add_runtime_dependency 'cas_rest_client'
25
26
 
@@ -0,0 +1,36 @@
1
+ @dsl
2
+ Feature: Saving and restoring context
3
+
4
+ Background:
5
+
6
+ Scenario: Environment and api keys are saved in the context
7
+ When I run script:
8
+ """
9
+ namespace do
10
+ user "bob"
11
+ end
12
+ """
13
+ Then the context should contain "env"
14
+ And the context should contain "namespace"
15
+ And the context should contain "stack"
16
+ And the context should contain "account"
17
+ And the context should contain "api_keys"
18
+ And the context "api_keys" should contain "1" item
19
+
20
+ Scenario: API keys are restored from the context
21
+ When I use script context:
22
+ """
23
+ {
24
+ "namespace": "foobar",
25
+ "api_keys": [
26
+ "the-api-key"
27
+ ]
28
+ }
29
+ """
30
+ And I run script:
31
+ """
32
+ namespace
33
+ """
34
+ Then the context "namespace" should be "foobar"
35
+ And the context "api_keys" should contain "1" item
36
+
@@ -0,0 +1,11 @@
1
+ @dsl
2
+ Feature: Creating a Host
3
+
4
+ Background:
5
+
6
+ Scenario: Host id is propagated properly to API#create_host
7
+ When I run script:
8
+ """
9
+ host "the-host"
10
+ """
11
+ Then the model should contain "host" "the-host"
@@ -0,0 +1,30 @@
1
+ @dsl
2
+ Feature: Assigning ownership
3
+
4
+ Background:
5
+
6
+ Scenario: Create without ownership
7
+ When I run script:
8
+ """
9
+ role "user", "bob"
10
+ """
11
+ Then the "role" "cucumber:user:bob" should not have an owner
12
+
13
+ Scenario: Create with explicit ownership
14
+ When I run script:
15
+ """
16
+ role "user", "bob", ownerid: "foobar"
17
+ """
18
+ Then the "role" "cucumber:user:bob" should be owned by "foobar"
19
+
20
+ Scenario: Create with scoped ownership
21
+ When I run script:
22
+ """
23
+ role "user", "bob" do
24
+ owns do
25
+ resource "food", "bacon"
26
+ end
27
+ end
28
+ """
29
+ Then the "resource" "cucumber:food:bacon" should be owned by "cucumber:user:bob"
30
+
@@ -0,0 +1,45 @@
1
+ @dsl
2
+ Feature: Manpipulating permissions
3
+
4
+ Background:
5
+
6
+ Scenario: Permit using Role#can
7
+ When I run script:
8
+ """
9
+ bacon = resource "food", "bacon"
10
+ role "user", "bob" do
11
+ can "fry", bacon
12
+ end
13
+ """
14
+ Then "cucumber:user:bob" can "fry" "cucumber:food:bacon"
15
+
16
+ Scenario: Permit using Role#can with grant option
17
+ When I run script:
18
+ """
19
+ bacon = resource "food", "bacon"
20
+ role "user", "bob" do
21
+ can "fry", bacon, grant_option: true
22
+ end
23
+ """
24
+ Then "cucumber:user:bob" can "fry" "cucumber:food:bacon" with grant option
25
+
26
+ Scenario: Permit using Resource#permit
27
+ When I run script:
28
+ """
29
+ bob = role "user", "bob"
30
+ resource "food", "bacon" do
31
+ permit "fry", bob
32
+ end
33
+ """
34
+ Then "cucumber:user:bob" can "fry" "cucumber:food:bacon"
35
+
36
+ Scenario: Permit using Resource#permit with grant option
37
+ When I run script:
38
+ """
39
+ bob = role "user", "bob"
40
+ resource "food", "bacon" do
41
+ permit "fry", bob, grant_option: true
42
+ end
43
+ """
44
+ Then "cucumber:user:bob" can "fry" "cucumber:food:bacon" with grant option
45
+
@@ -0,0 +1,23 @@
1
+ @dsl
2
+ Feature: Creating a resource
3
+
4
+ Background:
5
+
6
+ Scenario: Create with simple kind and id
7
+ When I run script:
8
+ """
9
+ resource "food", "bacon"
10
+ """
11
+ Then the model should contain "resource" "cucumber:food:bacon"
12
+
13
+ Scenario: Create with scope
14
+ When I run script:
15
+ """
16
+ scope "test" do
17
+ resource "food", "bacon"
18
+ end
19
+ resource "food", "eggs"
20
+ """
21
+ Then the model should contain "resource" "cucumber:food:test/bacon"
22
+ And the model should contain "resource" "cucumber:food:eggs"
23
+
@@ -0,0 +1,11 @@
1
+ @dsl
2
+ Feature: Creating a role
3
+
4
+ Background:
5
+
6
+ Scenario: Create with simple kind and id
7
+ When I run script:
8
+ """
9
+ role "user", "bob"
10
+ """
11
+ Then the model should contain "role" "cucumber:user:bob"
@@ -0,0 +1,23 @@
1
+ @dsl
2
+ Feature: Creating a User
3
+
4
+ Background:
5
+
6
+ Scenario: Users don't incorporate the namespace as a path prefix
7
+ When I run script:
8
+ """
9
+ namespace do
10
+ user "bob"
11
+ end
12
+ """
13
+ Then the model should contain "user" "bob"
14
+
15
+ Scenario: Namespace can be used as a no-arg method
16
+ When I run script:
17
+ """
18
+ namespace "foobar" do
19
+ user "#{namespace}-bob"
20
+ end
21
+ """
22
+ Then the model should contain "user" "foobar-bob"
23
+
@@ -0,0 +1,46 @@
1
+ When(/^I use script context:$/) do |context|
2
+ @context = JSON.parse context
3
+ end
4
+
5
+ When(/^I run script:$/) do |script|
6
+ require 'conjur/dsl/runner'
7
+ @runner = Conjur::DSL::Runner.new(script)
8
+ @runner.context = @context if @context
9
+ @runner.execute
10
+ end
11
+
12
+ Then(/^the model should contain "(.*?)" "(.*?)"$/) do |kind, id|
13
+ @mock_api.thing(kind, id).should_not be_nil
14
+ end
15
+
16
+ Then(/^the "(.*?)" "(.*?)" should be owned by "(.*?)"$/) do |kind, id, owner|
17
+ step "the model should contain \"#{kind}\" \"#{id}\""
18
+ @mock_api.thing(kind, id).ownerid.should == owner
19
+ end
20
+
21
+ Then(/^the "(.*?)" "(.*?)" should not have an owner$/) do |kind, id|
22
+ step "the model should contain \"#{kind}\" \"#{id}\""
23
+ @mock_api.thing(kind, id).ownerid.should be_nil
24
+ end
25
+
26
+ Then(/^"(.*?)" can "(.*?)" "(.*?)"( with grant option)?$/) do |role, privilege, resource, grant_option|
27
+ resource = @mock_api.thing(:resource, resource)
28
+ permission = resource.permissions.find do |p|
29
+ p.privilege == privilege && p.role == role && p.grant_option == !grant_option.nil?
30
+ end
31
+ raise "#{role} cannot #{privilege} #{resource.id} with#{grant_option ? "" : "out"} grant_option" unless permission
32
+ end
33
+
34
+ Then(/^the context should contain "(.*?)"$/) do |key|
35
+ @runner.context.should have_key(key.to_s)
36
+ end
37
+
38
+ Then(/^the context "(.*?)" should be "(.*?)"$/) do |key, value|
39
+ @runner.context[key].should == value
40
+ end
41
+
42
+ Then(/^the context "(.*?)" should contain "(.*?)" item$/) do |key, key_count|
43
+ step "the context should contain \"#{key}\""
44
+ @runner.context[key].should have(key_count.to_i).items
45
+ end
46
+
@@ -1,4 +1,5 @@
1
1
  require 'simplecov'
2
2
  require 'aruba/cucumber'
3
+ require 'cucumber/rspec/doubles'
3
4
 
4
5
  SimpleCov.start
@@ -0,0 +1,117 @@
1
+ require 'ostruct'
2
+
3
+ class MockAPI
4
+ attr_reader :things
5
+
6
+ def initialize
7
+ @things = {}
8
+ end
9
+
10
+ def thing(kind, id)
11
+ (@things[kind.to_sym] || []).find{|r| r.id == id}
12
+ end
13
+
14
+ def create_host(options = {})
15
+ id = options.delete(:id)
16
+ if id
17
+ host = thing(:host, id)
18
+ else
19
+ id = SecureRandom.uuid
20
+ end
21
+ host ||= create_thing(:host, id, options, role: true)
22
+ end
23
+
24
+ def create_user(id, options = {})
25
+ thing(:user, id) || create_thing(:user, id, options, role: true)
26
+ end
27
+
28
+ def create_variable(mime_type, kind)
29
+ create_thing(:user, SecureRandom.uuid, mime_type: mime_type, kind: kind)
30
+ end
31
+
32
+ def create_resource(id, options = {})
33
+ resource(id).tap do |resource|
34
+ resource.send(:"exists?=", true)
35
+ populate_options resource, options
36
+ end
37
+ end
38
+
39
+ def create_role(id, options = {})
40
+ role(id).tap do |role|
41
+ role.send(:"exists?=", true)
42
+ populate_options role, options
43
+ end
44
+ end
45
+
46
+ [ :user, :host ].each do |kind|
47
+ define_method kind do |id|
48
+ thing(kind, id)
49
+ end
50
+ end
51
+
52
+ def role(id)
53
+ raise "Role id must be a string" unless id.is_a?(String)
54
+ thing(:role, id) || create_thing(:role, id, { exists?: false }, role: true)
55
+ end
56
+
57
+ def resource(id)
58
+ raise "Resource id must be a string" unless id.is_a?(String)
59
+ thing(:resource, id) || create_thing(:resource, id, exists?: false)
60
+ end
61
+
62
+ protected
63
+
64
+ def create_thing(kind, id, options, kind_options = {})
65
+ p kind, id, options, kind_options
66
+
67
+ thing = OpenStruct.new(kind: kind, id: id, exists?: true)
68
+
69
+ class << thing
70
+ def permit(privilege, role, options = {})
71
+ (self.permissions ||= []) << OpenStruct.new(privilege: privilege, role: role.id, grant_option: !!options[:grant_option])
72
+ end
73
+ end
74
+
75
+ if kind_options[:role]
76
+ thing.roleid = id
77
+ class << thing
78
+ def can(privilege, resource, options = {})
79
+ resource.permit privilege, self, options
80
+ end
81
+ end
82
+ end
83
+
84
+ populate_options(thing, options)
85
+
86
+ store_thing kind, thing
87
+
88
+ thing
89
+ end
90
+
91
+ def populate_options(thing, options)
92
+ options.each do |k,v|
93
+ thing.send("#{k}=", v)
94
+ end
95
+ end
96
+
97
+ def store_thing(kind, thing)
98
+ (things[kind] ||= []) << thing
99
+ end
100
+ end
101
+
102
+ Before("@dsl") do
103
+ puts "Using MockAPI"
104
+ puts "Using account 'cucumber'"
105
+
106
+ require 'conjur/api'
107
+ require 'conjur/config'
108
+ require 'conjur/dsl/runner'
109
+
110
+ Conjur.stub(:env).and_return "ci"
111
+ Conjur.stub(:stack).and_return "ci"
112
+ Conjur.stub(:account).and_return "cucumber"
113
+
114
+ Conjur::Core::API.stub(:conjur_account).and_return 'cucumber'
115
+ @mock_api ||= MockAPI.new
116
+ Conjur::DSL::Runner.any_instance.stub(:api).and_return @mock_api
117
+ end
@@ -21,6 +21,7 @@
21
21
  require 'gli'
22
22
  require 'conjur/config'
23
23
  require 'conjur/log'
24
+ require 'conjur/identifier_manipulation'
24
25
 
25
26
  module Conjur
26
27
  class CLI
@@ -28,34 +29,29 @@ module Conjur
28
29
 
29
30
  class << self
30
31
  def load_config
31
- [ File.join("/etc", "conjur.conf"), ( ENV['CONJURRC'] || File.join(ENV['HOME'], ".conjurrc") ) ].each do |f|
32
- if File.exists?(f)
33
- if Conjur.log
34
- Conjur.log << "Loading #{f}\n"
35
- end
36
- Conjur::Config.merge YAML.load(IO.read(f))
37
- end
38
- end
32
+ Conjur::Config.load
33
+ end
34
+
35
+ def apply_config
36
+ Conjur::Config.apply
39
37
  end
40
38
  end
41
39
 
42
40
  load_config
43
41
 
44
42
  Conjur::Config.plugins.each do |plugin|
45
- require "conjur-asset-#{plugin}"
43
+ begin
44
+ filename = "conjur-asset-#{plugin}"
45
+ require filename
46
+ rescue LoadError
47
+ warn "Could not load plugin '#{plugin}' specified in your config file.\nMake sure you have the #{filename}-api gem installed."
48
+ end
46
49
  end
47
50
 
48
51
  commands_from 'conjur/command'
49
52
 
50
53
  pre do |global,command,options,args|
51
- ENV['CONJUR_ENV'] = Config[:env] || "production"
52
- ENV['CONJUR_STACK'] = Config[:stack] if Config[:stack]
53
- ENV['CONJUR_STACK'] ||= 'v4' if ENV['CONJUR_ENV'] == 'production'
54
- ENV['CONJUR_ACCOUNT'] = Config[:account] or raise "Missing configuration setting: account. Please set it in ~/.conjurrc"
55
-
56
- if Conjur.log
57
- Conjur.log << "Using host #{Conjur::Authn::API.host}\n"
58
- end
54
+ apply_config
59
55
 
60
56
  require 'active_support/core_ext'
61
57
  options.delete_if{|k,v| v.blank?}
@@ -85,6 +81,10 @@ module Conjur
85
81
  $stderr.puts exception.response.body if exception.response
86
82
  end
87
83
  end
84
+
85
+ if Conjur.log
86
+ Conjur.log << "error: #{exception}\n#{exception.backtrace rescue 'NO BACKTRACE?'}"
87
+ end
88
88
  true
89
89
  end
90
90
  end
@@ -20,6 +20,8 @@
20
20
  #
21
21
  module Conjur
22
22
  class Command
23
+ extend IdentifierManipulation
24
+
23
25
  @@api = nil
24
26
 
25
27
  class << self
@@ -40,32 +42,6 @@ module Conjur
40
42
  def api
41
43
  @@api ||= Conjur::Authn.connect
42
44
  end
43
-
44
- # injects account into 2-tokens id
45
- def full_resource_id id
46
- parts = id.split(':') unless id.nil?
47
- if id.blank? or parts.size < 2
48
- raise "Expecting at least two tokens in #{id}"
49
- end
50
- if parts.size == 2
51
- id = [conjur_account, parts].flatten.join(":")
52
- end
53
- id
54
- end
55
-
56
- # removes accounts from 3+-tokens id, extracts kind
57
- def get_kind_and_id_from_args args, argname='id'
58
- flat_id = require_arg(args, argname)
59
- tokens=flat_id.split(':')
60
- raise "At least 2 tokens expected in #{flat_id}" if tokens.size<2
61
- tokens.shift if tokens.size>=3 # get rid of account
62
- kind=tokens.shift.gsub('-','_')
63
- [kind, tokens.join(':')]
64
- end
65
-
66
- def conjur_account
67
- Conjur::Core::API.conjur_account
68
- end
69
45
 
70
46
  def acting_as_option(command)
71
47
  command.arg_name 'Perform all actions as the specified Group'
@@ -93,11 +93,7 @@ class Conjur::Command::Assets < Conjur::Command
93
93
  member = require_arg(args, 'member')
94
94
  admin_option = !options.delete(:admin).nil?
95
95
 
96
- asset = api.send(kind, id)
97
- tokens = [ asset.resource_kind, asset.resource_id, role_name ]
98
- grant_role = [ asset.core_conjur_account, '@', tokens.join('/') ].join(':')
99
- api.role(grant_role).grant_to member, admin_option
100
-
96
+ api.send(kind, id).add_member role_name, member, admin_option: admin_option
101
97
  puts "Membership granted"
102
98
  end
103
99
  end
@@ -109,13 +105,8 @@ class Conjur::Command::Assets < Conjur::Command
109
105
  kind, id = get_kind_and_id_from_args(args, 'id')
110
106
  role_name = require_arg(args, 'role-name')
111
107
  member = require_arg(args, 'member')
112
-
113
- asset = api.send(kind, id)
114
- tokens = [ asset.resource_kind, asset.resource_id, role_name ]
115
- grant_role = [ asset.core_conjur_account, '@', tokens.join('/') ].join(':')
116
- api.role(grant_role).revoke_from member
117
-
108
+ api.send(kind, id).remove_member role_name, member
118
109
  puts "Membership revoked"
119
110
  end
120
111
  end
121
- end
112
+ end
@@ -0,0 +1,64 @@
1
+ require 'conjur/command'
2
+ require 'active_support/ordered_hash'
3
+
4
+ class Conjur::Command
5
+ class Audit < self
6
+ self.prefix = 'audit'
7
+
8
+ class << self
9
+ private
10
+ def extract_int_option(source, name, dest=nil)
11
+ if val = source[name]
12
+ raise "Expected an integer for #{name}, but got #{val}" unless /\d+/ =~ val
13
+ val.to_i.tap{ |i| dest[name] = i if dest }
14
+ end
15
+ end
16
+
17
+ def extract_audit_options options
18
+ {}.tap do |opts|
19
+ [:limit, :offset].each do |name|
20
+ extract_int_option(options, name, opts)
21
+ end
22
+ end
23
+ end
24
+
25
+ def show_audit_events events
26
+ puts JSON.pretty_generate(events)
27
+ end
28
+
29
+ def audit_feed_command kind, &block
30
+ command kind do |c|
31
+ c.desc "Maximum number of events to fetch"
32
+ c.flag [:l, :limit]
33
+
34
+ c.desc "Offset of the first event to return"
35
+ c.flag [:o, :offset]
36
+
37
+ c.action do |global_options, options, args|
38
+ opts = extract_audit_options options
39
+ show_audit_events instance_exec(args, opts, &block)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ desc "Show audit events related to a role"
47
+ arg_name 'role?'
48
+ audit_feed_command :role do |args, options|
49
+ if id = args.shift
50
+ method_name, method_args = :audit_role, [full_resource_id(id), options]
51
+ else
52
+ method_name, method_args = :audit_current_role, [options]
53
+ end
54
+ api.send method_name, *method_args
55
+ end
56
+
57
+ desc "Show audit events related to a resource"
58
+ arg_name 'resource'
59
+ audit_feed_command :resource do |args, options|
60
+ id = full_resource_id(require_arg args, "resource")
61
+ api.audit_resource id, options
62
+ end
63
+ end
64
+ end
@@ -63,7 +63,8 @@ class Conjur::Command::Groups < Conjur::Command
63
63
  opts = { admin_option: false }
64
64
  message = "Adminship revoked"
65
65
  end
66
- api.role(group.roleid).grant_to member, opts
66
+
67
+ group.add_member member, opts
67
68
  puts message
68
69
  end
69
70
  end
@@ -75,9 +76,7 @@ class Conjur::Command::Groups < Conjur::Command
75
76
  group = require_arg(args, 'group')
76
77
  member = require_arg(args, 'member')
77
78
 
78
- group = api.group(group)
79
- api.role(group.roleid).revoke_from member
80
-
79
+ api.group(group).remove_member member
81
80
  puts "Membership revoked"
82
81
  end
83
82
  end
@@ -0,0 +1,63 @@
1
+ #
2
+ # Copyright (C) 2013 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
+
24
+ class Conjur::Command::Authn < Conjur::Command
25
+ self.prefix = :script
26
+
27
+ desc "Run a Conjur DSL script"
28
+ arg_name "script"
29
+ command :execute do |c|
30
+ acting_as_option(c)
31
+
32
+ c.desc "Load context from this config file; save it when finished"
33
+ c.arg_name "context"
34
+ c.flag [:c, :context]
35
+
36
+ c.action do |global_options,options,args|
37
+ filename = nil
38
+ if script = args.pop
39
+ filename = script
40
+ script = File.read(script)
41
+ else
42
+ script = STDIN.read
43
+ end
44
+ require 'conjur/dsl/runner'
45
+ runner = Conjur::DSL::Runner.new(script, filename)
46
+ if options[:context]
47
+ runner.context = begin
48
+ JSON.parse(File.read(options[:context]))
49
+ rescue Errno::ENOENT
50
+ {}
51
+ end
52
+ end
53
+
54
+ result = runner.execute
55
+
56
+ if options[:context]
57
+ File.write(options[:context], JSON.pretty_generate(runner.context))
58
+ end
59
+
60
+ puts JSON.pretty_generate(result)
61
+ end
62
+ end
63
+ end
@@ -23,6 +23,29 @@ module Conjur
23
23
  @@attributes = {}
24
24
 
25
25
  class << self
26
+ def load
27
+ require 'yaml'
28
+ [ File.join("/etc", "conjur.conf"), ( ENV['CONJURRC'] || File.join(ENV['HOME'], ".conjurrc") ) ].each do |f|
29
+ if File.exists?(f)
30
+ if Conjur.log
31
+ Conjur.log << "Loading #{f}\n"
32
+ end
33
+ Conjur::Config.merge YAML.load(IO.read(f))
34
+ end
35
+ end
36
+ end
37
+
38
+ def apply
39
+ ENV['CONJUR_ENV'] = Config[:env] || "production"
40
+ ENV['CONJUR_STACK'] = Config[:stack] if Config[:stack]
41
+ ENV['CONJUR_STACK'] ||= 'v4' if ENV['CONJUR_ENV'] == 'production'
42
+ ENV['CONJUR_ACCOUNT'] = Config[:account] or raise "Missing configuration setting: account. Please set it in ~/.conjurrc"
43
+
44
+ if Conjur.log
45
+ Conjur.log << "Using host #{Conjur::Authn::API.host}\n"
46
+ end
47
+ end
48
+
26
49
  def inspect
27
50
  @@attributes.inspect
28
51
  end
@@ -0,0 +1,197 @@
1
+ require 'conjur/identifier_manipulation'
2
+
3
+ module Conjur
4
+ module DSL
5
+ # Entry point for the Conjur DSL.
6
+ #
7
+ # Methods are available in two categories: name scoping and asset building.
8
+ class Runner
9
+ include Conjur::IdentifierManipulation
10
+
11
+ attr_reader :script, :filename, :context
12
+
13
+ def initialize(script, filename = nil)
14
+ @context = {
15
+ "env" => Conjur.env,
16
+ "stack" => Conjur.stack,
17
+ "account" => Conjur.account,
18
+ "api_keys" => {}
19
+ }
20
+ @script = script
21
+ @filename = filename
22
+ @api = nil
23
+ @scopes = Array.new
24
+ @owners = Array.new
25
+ @objects = Array.new
26
+ end
27
+
28
+ def api
29
+ @api ||= connect
30
+ end
31
+
32
+ def context=(context)
33
+ @context.deep_merge! context
34
+ end
35
+
36
+ def api_keys
37
+ @context["api_keys"]
38
+ end
39
+
40
+ def current_object
41
+ !@objects.empty? ? @objects.last : nil
42
+ end
43
+
44
+ def current_scope
45
+ !@scopes.empty? ? @scopes.join('/') : nil
46
+ end
47
+
48
+ def scope name = nil, &block
49
+ if name != nil
50
+ do_scope name, &block
51
+ else
52
+ current_scope
53
+ end
54
+ end
55
+
56
+ def namespace ns = nil, &block
57
+ if block_given?
58
+ ns ||= context["namespace"]
59
+ if ns.nil?
60
+ require 'conjur/api/variables'
61
+ ns = context["namespace"] = api.create_variable("text/plain", "namespace").id
62
+ end
63
+ do_scope ns, &block
64
+ context
65
+ else
66
+ @scopes[0]
67
+ end
68
+ end
69
+
70
+ alias model namespace
71
+
72
+ def execute
73
+ args = [ script ]
74
+ args << filename if filename
75
+ instance_eval(*args)
76
+ end
77
+
78
+ def resource kind, id, options = {}, &block
79
+ id = full_resource_id([kind, qualify_id(id) ].join(':'))
80
+ find_or_create :resource, id, options, &block
81
+ end
82
+
83
+ def role kind, id, options = {}, &block
84
+ id = full_resource_id([ kind, qualify_id(id) ].join(':'))
85
+ find_or_create :role, id, options, &block
86
+ end
87
+
88
+ def create_variable id = nil, options = {}, &block
89
+ options[:id] = id if id
90
+ mime_type = options.delete(:mime_type)
91
+ kind = options.delete(:kind)
92
+ end
93
+
94
+ def owns
95
+ @owners.push current_object
96
+ begin
97
+ yield
98
+ ensure
99
+ @owners.pop
100
+ end
101
+ end
102
+
103
+ protected
104
+
105
+ def qualify_id id
106
+ if id[0] == "/"
107
+ id[1..-1]
108
+ else
109
+ [ current_scope, id ].compact.join('/')
110
+ end
111
+ end
112
+
113
+ def method_missing(sym, *args, &block)
114
+ if create_compatible_args?(args) && api.respond_to?(sym)
115
+ id = args[0]
116
+ id = qualify_id(id) unless sym == :user
117
+ find_or_create sym, id, args[1] || {}, &block
118
+ elsif current_object && current_object.respond_to?(sym)
119
+ current_object.send(sym, *args, &block)
120
+ else
121
+ super
122
+ end
123
+ end
124
+
125
+ def create_compatible_args?(args)
126
+ valid_prototypes = [
127
+ lambda { args.length == 1 },
128
+ lambda { args.length == 2 && args[1].is_a?(Hash) }
129
+ ]
130
+ !valid_prototypes.find{|p| p.call}.nil?
131
+ end
132
+
133
+ def find_or_create(type, id, options, &block)
134
+ find_method = type.to_sym
135
+ create_method = "create_#{type}".to_sym
136
+ unless (obj = api.send(find_method, id)) && obj.exists?
137
+ options = expand_options(options)
138
+ obj = if create_method == :create_variable
139
+ options[:id] = id
140
+ api.send(create_method, options.delete(:mime_type), options.delete(:kind), options)
141
+ elsif [ 2, -2 ].member?(api.method(create_method).arity)
142
+ api.send(create_method, id, options)
143
+ else
144
+ options[:id] = id
145
+ api.send(create_method, options)
146
+ end
147
+ end
148
+ do_object obj, &block
149
+ end
150
+
151
+ def do_object obj, &block
152
+ begin
153
+ api_keys[obj.resourceid] = obj.api_key
154
+ rescue
155
+ end
156
+
157
+ @objects.push obj
158
+ begin
159
+ yield obj if block_given?
160
+ obj
161
+ ensure
162
+ @objects.pop
163
+ end
164
+ end
165
+
166
+ def do_scope name, &block
167
+ return unless block_given?
168
+
169
+ @scopes.push(name)
170
+ begin
171
+ yield
172
+ ensure
173
+ @scopes.pop
174
+ end
175
+ end
176
+
177
+ def owner(options)
178
+ owner = options[:owner] || @owners.last
179
+ owner = owner.roleid if owner.respond_to?(:roleid)
180
+ owner
181
+ end
182
+
183
+ def expand_options(opts)
184
+ (opts || {}).tap do |options|
185
+ if owner = owner(options)
186
+ options[:ownerid] = owner
187
+ end
188
+ end
189
+ end
190
+
191
+ def connect
192
+ require 'conjur/authn'
193
+ Conjur::Authn.connect
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,29 @@
1
+ module Conjur
2
+ module IdentifierManipulation
3
+ # injects account into 2-tokens id
4
+ def full_resource_id id
5
+ parts = id.split(':') unless id.nil?
6
+ if id.blank? or parts.size < 2
7
+ raise "Expecting at least two tokens in #{id}"
8
+ end
9
+ if parts.size == 2
10
+ id = [conjur_account, parts].flatten.join(":")
11
+ end
12
+ id
13
+ end
14
+
15
+ # removes accounts from 3+-tokens id, extracts kind
16
+ def get_kind_and_id_from_args args, argname='id'
17
+ flat_id = require_arg(args, argname)
18
+ tokens=flat_id.split(':')
19
+ raise "At least 2 tokens expected in #{flat_id}" if tokens.size<2
20
+ tokens.shift if tokens.size>=3 # get rid of account
21
+ kind=tokens.shift.gsub('-','_')
22
+ [kind, tokens.join(':')]
23
+ end
24
+
25
+ def conjur_account
26
+ Conjur::Core::API.conjur_account
27
+ end
28
+ end
29
+ end
@@ -19,5 +19,5 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module Conjur
22
- VERSION = "4.1.1"
22
+ VERSION = "4.3.0"
23
23
  end
@@ -81,55 +81,36 @@ describe Conjur::Command::Assets, logged_in: true do
81
81
  end
82
82
  end
83
83
 
84
- shared_examples 'it obtains role via asset' do
84
+ shared_examples 'it obtains asset by kind and id' do
85
85
  it "obtains asset instance as api.#{KIND}(#{ID})" do
86
86
  api.should_receive(KIND.to_sym).with(ID)
87
87
  invoke_silently
88
88
  end
89
- it "account=asset.core_conjur_account" do
90
- asset.should_receive(:core_conjur_account)
91
- invoke_silently
92
- end
93
- it "kind=asset.resource_kind" do
94
- asset.should_receive(:resource_kind)
95
- invoke_silently
96
- end
97
- it "id=asset.resource_id" do
98
- asset.should_receive(:resource_id)
99
- invoke_silently
100
- end
101
-
102
- it "obtains role as #{ACCOUNT}:@:#{KIND}/#{ID}/#{ROLE}" do
103
- api.should_receive(:role).with("#{ACCOUNT}:@:#{KIND}/#{ID}/#{ROLE}")
104
- invoke_silently
105
- end
106
89
  end
107
-
108
- shared_context "asset with role" do
109
- before(:each) {
110
- asset.stub(:core_conjur_account).and_return(ACCOUNT)
111
- asset.stub(:resource_kind).and_return(KIND)
112
- asset.stub(:resource_id).and_return(ID)
113
- api.stub(:role).and_return(role_instance)
90
+
91
+ shared_context "asset instance" do
92
+ before(:each) {
93
+ api.stub(KIND.to_sym).and_return(asset)
94
+ asset.stub(:add_member)
95
+ asset.stub(:remove_member)
114
96
  }
115
- let(:role_instance) { double(grant_to: true, revoke_from: true) }
116
97
  end
117
98
 
118
99
  describe_command "asset:members:add #{KIND}:#{ID} #{ROLE} #{MEMBER}" do
119
- include_context "asset with role"
120
- it_behaves_like "it obtains role via asset"
100
+ include_context "asset instance"
101
+ it_behaves_like "it obtains asset by kind and id"
121
102
  it 'calls role.grant_to(member,...)' do
122
- role_instance.should_receive(:grant_to).with(MEMBER, anything)
103
+ asset.should_receive(:add_member).with(ROLE, MEMBER, anything)
123
104
  invoke_silently
124
105
  end
125
106
  it { expect { invoke }.to write "Membership granted" }
126
107
  end
127
108
 
128
109
  describe_command "asset:members:remove #{KIND}:#{ID} #{ROLE} #{MEMBER}" do
129
- include_context "asset with role"
130
- it_behaves_like "it obtains role via asset"
110
+ include_context "asset instance"
111
+ it_behaves_like "it obtains asset by kind and id"
131
112
  it 'calls role.revoke_from(member)' do
132
- role_instance.should_receive(:revoke_from).with(MEMBER)
113
+ asset.should_receive(:remove_member).with(ROLE, MEMBER)
133
114
  invoke_silently
134
115
  end
135
116
  it { expect { invoke }.to write "Membership revoked" }
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conjur::Command::Audit, logged_in: true do
4
+ let(:events) { [{'foo' => 'bar', 'zelda' => 'link', 'abc' => 'xyz'}, {'some' => 'other event'}] }
5
+
6
+ def expect_api_call method, *args
7
+ api.should_receive(method.to_sym).with(*args).and_return events
8
+ described_class.should_receive(:show_audit_events).with(events)
9
+ end
10
+
11
+ def invoke_expecting_api_call method, *args
12
+ expect_api_call method, *args
13
+ invoke
14
+ end
15
+
16
+ def invoke_silently
17
+ silence_stderr { invoke }
18
+ end
19
+
20
+ def self.describe_command_success cmd, method, *expected_args, &block
21
+ describe_command cmd do
22
+ it "calls api.#{method}(#{expected_args.map(&:inspect).join(',')})" do
23
+ instance_eval(&block) if block
24
+ invoke_expecting_api_call method, *expected_args
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.it_calls_the_api command, api_method, *api_args, &block
30
+ describe_command_success command, api_method, *api_args, &block
31
+ accepts_pagination_params command, api_method, *api_args, &block
32
+ end
33
+
34
+
35
+ def self.it_fails command, *raise_error_args
36
+ unless raise_error_args.empty? or ::Class === raise_error_args.first
37
+ raise_error_args.unshift Exception
38
+ end
39
+ describe_command command do
40
+ it "raises #{raise_error_args.map(&:inspect).join ' '}" do
41
+ expect { invoke_silently }.to raise_error(*raise_error_args)
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.accepts_pagination_params cmd, api_method, *api_method_args, &block
47
+ context "with valid pagination options" do
48
+ expected_opts = {limit: 12, offset: 2}
49
+ api_method_args = case api_method_args.last
50
+ when Hash
51
+ api_method_args[0..-2] << api_method_args.last.merge(expected_opts)
52
+ else
53
+ api_method_args.dup << expected_opts
54
+ end
55
+ describe_command_success cmd + " --limit 12 --offset 2", api_method, *api_method_args, &block
56
+ end
57
+ context "with garbage pagination options" do
58
+ it_fails cmd + " --limit hiythere", RuntimeError, /expected an integer for limit/i
59
+ it_fails cmd + " --offset hiythere", RuntimeError, /expected an integer for offset/i
60
+ end
61
+ end
62
+
63
+ describe "audit:role" do
64
+ context "without an argument" do
65
+ it_calls_the_api "audit:role", :audit_current_role, {}
66
+ end
67
+ context "with an argument" do
68
+ context "of a full id" do
69
+ it_calls_the_api "audit:role foo:bar:baz", :audit_role, 'foo:bar:baz', {}
70
+ end
71
+ context "without an account" do
72
+ it_calls_the_api "audit:role bar:baz", :audit_role, 'the-conjur-account:bar:baz', {} do
73
+ Conjur::Command.stub(conjur_account: "the-conjur-account")
74
+ end
75
+ end
76
+ context "without enough tokens" do
77
+ it_fails "audit:role not-enough-tokens", RuntimeError, /expecting at least two tokens/i
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "audit:resource" do
83
+ context "without an argument" do
84
+ it_fails "audit:resource", /missing parameter: resource/i
85
+ end
86
+ context "with an argument of" do
87
+ context "a full id" do
88
+ it_calls_the_api "audit:resource foo:bar:baz", :audit_resource, "foo:bar:baz", {}
89
+ end
90
+ context "an id with two tokens" do
91
+ it_calls_the_api "audit:resource foo:bar", :audit_resource, "the-conjur-account:foo:bar", {} do
92
+ Conjur::Command.stub(conjur_account: "the-conjur-account")
93
+ end
94
+ end
95
+ context "an id with one token" do
96
+ it_fails "audit:resource foo", /expecting at least two tokens/i
97
+ end
98
+ end
99
+ end
100
+ end
@@ -71,15 +71,16 @@ def post_response(id, attributes = {})
71
71
  end
72
72
 
73
73
  # stub parameters to be used in resource/asset tests
74
- KIND="asset_kind"
75
- ID="unique_id"
76
- ROLE='<role>'
77
- MEMBER='<member>'
78
- PRIVILEGE='<privilege>'
79
- OWNER='<owner/userid>'
80
- ACCOUNT='<core_account>'
81
-
74
+ KIND="asset_kind"
75
+ ID="unique_id"
76
+ ROLE='<role>'
77
+ MEMBER='<member>'
78
+ PRIVILEGE='<privilege>'
79
+ OWNER='<owner/userid>'
80
+ ACCOUNT='<core_account>'
82
81
 
83
82
  require 'write_expectation'
84
83
 
84
+ ENV['CONJURRC'] = '/dev/null'
85
+
85
86
  require 'conjur/cli'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conjur-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.1
4
+ version: 4.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-10-28 00:00:00.000000000 Z
13
+ date: 2013-11-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: conjur-api
@@ -92,6 +92,22 @@ dependencies:
92
92
  - - ! '>='
93
93
  - !ruby/object:Gem::Version
94
94
  version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: deep_merge
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
95
111
  - !ruby/object:Gem::Dependency
96
112
  name: cas_rest_client
97
113
  requirement: !ruby/object:Gem::Requirement
@@ -198,6 +214,7 @@ executables:
198
214
  extensions: []
199
215
  extra_rdoc_files: []
200
216
  files:
217
+ - .githooks/pre_commit/run_specs.rb
201
218
  - .gitignore
202
219
  - .kateproject
203
220
  - .project
@@ -208,8 +225,17 @@ files:
208
225
  - bin/conjur
209
226
  - bin/jsonfield
210
227
  - conjur.gemspec
228
+ - features/dsl_context.feature
229
+ - features/dsl_host_create.feature
230
+ - features/dsl_ownership.feature
231
+ - features/dsl_permission.feature
232
+ - features/dsl_resource_create.feature
233
+ - features/dsl_role_create.feature
234
+ - features/dsl_user_create.feature
211
235
  - features/jsonfield.feature
236
+ - features/step_definitions/dsl_steps.rb
212
237
  - features/support/env.rb
238
+ - features/support/hooks.rb
213
239
  - lib/conjur.rb
214
240
  - lib/conjur/authn.rb
215
241
  - lib/conjur/cli.rb
@@ -223,10 +249,13 @@ files:
223
249
  - lib/conjur/command/ids.rb
224
250
  - lib/conjur/command/resources.rb
225
251
  - lib/conjur/command/roles.rb
252
+ - lib/conjur/command/script.rb
226
253
  - lib/conjur/command/secrets.rb
227
254
  - lib/conjur/command/users.rb
228
255
  - lib/conjur/command/variables.rb
229
256
  - lib/conjur/config.rb
257
+ - lib/conjur/dsl/runner.rb
258
+ - lib/conjur/identifier_manipulation.rb
230
259
  - lib/conjur/version.rb
231
260
  - spec/command/assets_spec.rb
232
261
  - spec/command/audit_spec.rb
@@ -240,6 +269,7 @@ files:
240
269
  - spec/command_spec.rb
241
270
  - spec/spec_helper.rb
242
271
  - spec/write_expectation.rb
272
+ - update_ci.sh
243
273
  homepage: https://github.com/inscitiv/cli-ruby
244
274
  licenses:
245
275
  - MIT
@@ -255,7 +285,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
255
285
  version: '0'
256
286
  segments:
257
287
  - 0
258
- hash: -3349228530066988265
288
+ hash: -1644875280166095669
259
289
  required_rubygems_version: !ruby/object:Gem::Requirement
260
290
  none: false
261
291
  requirements:
@@ -264,7 +294,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
264
294
  version: '0'
265
295
  segments:
266
296
  - 0
267
- hash: -3349228530066988265
297
+ hash: -1644875280166095669
268
298
  requirements: []
269
299
  rubyforge_project:
270
300
  rubygems_version: 1.8.25
@@ -272,8 +302,17 @@ signing_key:
272
302
  specification_version: 3
273
303
  summary: Conjur command line interface
274
304
  test_files:
305
+ - features/dsl_context.feature
306
+ - features/dsl_host_create.feature
307
+ - features/dsl_ownership.feature
308
+ - features/dsl_permission.feature
309
+ - features/dsl_resource_create.feature
310
+ - features/dsl_role_create.feature
311
+ - features/dsl_user_create.feature
275
312
  - features/jsonfield.feature
313
+ - features/step_definitions/dsl_steps.rb
276
314
  - features/support/env.rb
315
+ - features/support/hooks.rb
277
316
  - spec/command/assets_spec.rb
278
317
  - spec/command/audit_spec.rb
279
318
  - spec/command/authn_spec.rb