conjur-cli 4.1.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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