conjur-cli 4.8.0 → 4.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/conjur/config.rb CHANGED
@@ -31,7 +31,7 @@ module Conjur
31
31
  end
32
32
 
33
33
  def default_config_files
34
- [ File.join("/etc", "conjur.conf"), ( ENV['CONJURRC'] || File.join(ENV['HOME'], ".conjurrc") ), '.conjurrc' ]
34
+ [ File.join("/etc", "conjur.conf"), ( ENV['CONJURRC'] || File.expand_path("~/.conjurrc") ), '.conjurrc' ]
35
35
  end
36
36
 
37
37
  def load(config_files = default_config_files)
@@ -0,0 +1,121 @@
1
+ #
2
+ # Copyright (C) 2014 Conjur Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'conjur/api'
22
+ require 'yaml'
23
+
24
+ module Conjur
25
+ class Env
26
+
27
+ class CustomTag
28
+ def initialize id
29
+ raise "#{self.class.name.split('::').last} requires a parameter" if id.to_s.empty?
30
+ @id=id
31
+ end
32
+ def init_with(coder)
33
+ initialize(coder.scalar)
34
+ end
35
+ def conjur_id
36
+ @id
37
+ end
38
+ end
39
+
40
+ class ConjurVariable < CustomTag
41
+ def evaluate value
42
+ value.chomp
43
+ end
44
+ end
45
+
46
+ class ConjurTempfile < CustomTag
47
+ def evaluate value
48
+ @tempfile = if File.directory?("/dev/shm") and File.writable?("/dev/shm")
49
+ Tempfile.new("conjur","/dev/shm")
50
+ else
51
+ Tempfile.new("conjur")
52
+ end
53
+ @tempfile.write(value)
54
+ @tempfile.close
55
+ @tempfile.path
56
+ end
57
+ end
58
+
59
+ def initialize(options={})
60
+ raise ":file and :yaml options can not be provided together" if ( options.has_key?(:file) and options.has_key?(:yaml) )
61
+
62
+ yaml = if options.has_key?(:yaml)
63
+ raise ":yaml option should be non-empty string" unless options[:yaml].kind_of?(String)
64
+ raise ":yaml option should be non-empty string" if options[:yaml].empty?
65
+ options[:yaml]
66
+ elsif options.has_key?(:file)
67
+ raise ":file option should be non-empty string" unless options[:file].kind_of?(String)
68
+ raise ":file option should be non-empty string" if options[:file].empty?
69
+ File.read(options[:file])
70
+ else
71
+ raise "either :file or :yaml option is mandatory"
72
+ end
73
+
74
+ @definition = parse(yaml)
75
+ end
76
+
77
+ def parse(yaml)
78
+ YAML.add_tag("!var", ConjurVariable)
79
+ YAML.add_tag("!tmp", ConjurTempfile)
80
+ definition = YAML.load(yaml)
81
+ raise "Definition should be a Hash" unless definition.kind_of?(Hash)
82
+ bad_types = definition.values.select { |v| not (v.kind_of?(String) or v.kind_of?(CustomTag)) }.map {|v| v.class}.uniq
83
+ raise "Definition can not include values of types: #{bad_types}" unless bad_types.empty?
84
+ definition
85
+ end
86
+
87
+ def obtain(api)
88
+ runtime_environment={}
89
+ variable_ids= @definition.values.map { |v| v.conjur_id rescue nil }.compact
90
+ conjur_values=api.variable_values(variable_ids)
91
+ @definition.each { |environment_name, reference|
92
+ runtime_environment[environment_name]= if reference.respond_to?(:evaluate)
93
+ reference.evaluate( conjur_values[reference.conjur_id] )
94
+ else
95
+ reference # is a literal value
96
+ end
97
+ }
98
+ return runtime_environment
99
+ end
100
+
101
+ def check(api)
102
+ Hash[
103
+ @definition.map { |k,v|
104
+
105
+ status = if v.respond_to? :conjur_id
106
+ if api.resource("variable:"+v.conjur_id).permitted?(:execute)
107
+ :available
108
+ else
109
+ :unavailable
110
+ end
111
+ else
112
+ :literal
113
+ end
114
+
115
+ [ k, status ]
116
+ }
117
+ ]
118
+ end
119
+
120
+ end
121
+ end
@@ -25,6 +25,11 @@ module Conjur
25
25
  @objects = Array.new
26
26
  end
27
27
 
28
+ def owner=(owner)
29
+ raise "Owner should only be set once" unless @owners.empty?
30
+ @owners.push owner
31
+ end
32
+
28
33
  # Provides a hash to export various application specific
29
34
  # asset ids (or anything else you want)
30
35
  def assets
@@ -19,6 +19,6 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module Conjur
22
- VERSION = "4.8.0"
22
+ VERSION = "4.9.3"
23
23
  ::Version=VERSION
24
24
  end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+ require 'conjur/conjurenv'
3
+ require 'tempfile'
4
+
5
+
6
+ shared_examples_for "processes environment definition" do |cmd, options|
7
+ before { # suspend all interaction with the environment
8
+ Kernel.stub(:system).and_return(true)
9
+ }
10
+ let(:stub_object) { double(obtain:{}, check:{}) }
11
+
12
+ describe_command "env:#{cmd} #{options}" do
13
+ it "uses .conjurenv file by default" do
14
+ Conjur::Env.should_receive(:new).with(file:".conjurenv").and_return(stub_object)
15
+ invoke
16
+ end
17
+ end
18
+
19
+ describe_command "env:#{cmd} -c somefile #{options}" do
20
+ it "uses desired file" do
21
+ Conjur::Env.should_receive(:new).with(file:"somefile").and_return(stub_object)
22
+ invoke
23
+ end
24
+ end
25
+
26
+ describe_command "env:#{cmd} --yaml someyaml #{options}" do
27
+ it "uses inline yaml" do
28
+ Conjur::Env.should_receive(:new).with(yaml:"someyaml").and_return(stub_object)
29
+ invoke
30
+ end
31
+ end
32
+
33
+ describe_command "env:#{cmd} -c somefile --yaml someyaml #{options}" do
34
+ it "refuses to accept mutually exclusive options" do
35
+ Conjur::Env.should_not_receive(:new)
36
+ expect { invoke }.to raise_error /Options -c and --yaml can not be provided together/
37
+ end
38
+ end
39
+ end
40
+
41
+ describe Conjur::Command::Env, logged_in: true do
42
+
43
+ let(:stub_env) { double() }
44
+ describe ":check" do
45
+ it_behaves_like "processes environment definition", "check", ''
46
+
47
+ describe_command "env:check" do
48
+ before { Conjur::Env.should_receive(:new).and_return(stub_env) }
49
+ describe "without api errors" do
50
+ let(:stub_result) { { "a" => :available, "b"=> :available } }
51
+ before {
52
+ stub_env.should_receive(:check).with(an_instance_of(Conjur::API)).and_return(stub_result)
53
+ }
54
+
55
+ describe "if all variables are available" do
56
+ it "prints #check result to the output" do
57
+ expect { invoke }.to write "a: available\nb: available\n"
58
+ end
59
+
60
+ it "does not crash" do
61
+ expect { invoke }.to_not raise_error
62
+ end
63
+ end
64
+
65
+ describe "if some variables are unavailable" do
66
+ let(:stub_result) { { "a" => :unavailable, "b"=> :available } }
67
+ it "prints #check result to the output" do
68
+ expect { invoke rescue true }.to write "a: unavailable\nb: available\n"
69
+ end
70
+ it "crashes in the end" do
71
+ expect { invoke }.to raise_error "Some variables are not available"
72
+ end
73
+ end
74
+ end
75
+ it 'does not rescue unexpected errors' do
76
+ stub_env.should_receive(:check).with(an_instance_of(Conjur::API)).and_return { raise "Custom error" }
77
+ expect { invoke }.to raise_error "Custom error"
78
+ end
79
+ end
80
+ end
81
+
82
+ describe ":run" do
83
+ it_behaves_like "processes environment definition", "run","-- extcmd"
84
+ describe_command "env:run" do
85
+ it 'fails because of missing argument' do
86
+ Kernel.should_not_receive(:system)
87
+ expect { invoke }.to raise_error "External command with optional arguments should be provided"
88
+ end
89
+ end
90
+ describe_command "env:run -- extcmd --arg1 arg2" do
91
+ before {
92
+ Conjur::Env.should_receive(:new).and_return(stub_env)
93
+ }
94
+
95
+ describe "if no errors are raised" do
96
+ let(:stub_result) { { "a" => "value_a", "b" => "value_b" } }
97
+ before {
98
+ stub_env.should_receive(:obtain).with(an_instance_of(Conjur::API)).and_return(stub_result)
99
+ }
100
+ it "performs #exec with environment (names in uppercase)" do
101
+ Kernel.should_receive(:system).with({"A"=>"value_a", "B"=>"value_b"}, "extcmd", "--arg1","arg2").and_return(true)
102
+ invoke
103
+ end
104
+ end
105
+ it "does not rescue unexpected errors" do
106
+ stub_env.should_receive(:obtain).with(an_instance_of(Conjur::API)).and_return { raise "Custom error" }
107
+ expect { invoke }.to raise_error "Custom error"
108
+ end
109
+ end
110
+ end
111
+
112
+ describe ":template" do
113
+ context do
114
+ before { # prevent real operation
115
+ File.stub(:readable?).with("config.erb").and_return(true)
116
+ File.stub(:read).with("config.erb").and_return("template")
117
+ ERB.stub(:new).and_return(double(result:''))
118
+ Tempfile.stub(:new).and_return(double(write: true, close: true, path: 'somepath'))
119
+ FileUtils.stub(:copy).and_return(true)
120
+ }
121
+ it_behaves_like "processes environment definition", "template","config.erb"
122
+ end
123
+ describe_command "env:template" do
124
+ it 'fails because of missing argument' do
125
+ Tempfile.should_not_receive(:new)
126
+ expect { invoke }.to raise_error "Location of readable ERB template should be provided"
127
+ end
128
+ end
129
+ describe_command "env:template config.erb" do
130
+ let(:erb_template) { """
131
+ variable <%= conjurenv['a'] %>
132
+ other variable <%= conjurenv['b'] %>
133
+ """
134
+ }
135
+ before {
136
+ File.stub(:readable?).with("config.erb").and_return(true)
137
+ File.stub(:read).with("config.erb").and_return(erb_template)
138
+ Conjur::Env.should_receive(:new).and_return(stub_env)
139
+ stub_env.should_receive(:obtain).with(an_instance_of(Conjur::API)).and_return( {"a"=>"value_a","b"=>"value_b","c"=>"value_c"} )
140
+ }
141
+
142
+ it "creates persistent tempfile, saves rendered template into it, prints out name of the file" do
143
+ stubpath="/tmp/temp.file"
144
+ tempfile=double(close: true, path: stubpath)
145
+ Tempfile.should_receive(:new).and_return(tempfile)
146
+ tempfile.should_receive(:write).with("\nvariable value_a\nother variable value_b\n")
147
+ FileUtils.should_receive(:copy).with(stubpath,stubpath+'.saved') # avoid garbage collection
148
+ expect { invoke }.to write stubpath+".saved"
149
+ end
150
+ end
151
+ end
152
+ end
@@ -2,7 +2,57 @@ require 'spec_helper'
2
2
 
3
3
  tmpdir = Dir.mktmpdir
4
4
 
5
+ GITHUB_FP = "SHA1 Fingerprint=A0:C4:A7:46:00:ED:A7:2D:C0:BE:CB:9A:8C:B6:07:CA:58:EE:74:5E"
6
+ GITHUB_CERT = <<EOF
7
+ -----BEGIN CERTIFICATE-----
8
+ MIIF4DCCBMigAwIBAgIQDACTENIG2+M3VTWAEY3chzANBgkqhkiG9w0BAQsFADB1
9
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
10
+ d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
11
+ IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE0MDQwODAwMDAwMFoXDTE2MDQxMjEy
12
+ MDAwMFowgfAxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
13
+ BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF
14
+ Ewc1MTU3NTUwMRcwFQYDVQQJEw41NDggNHRoIFN0cmVldDEOMAwGA1UEERMFOTQx
15
+ MDcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
16
+ YW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxHaXRIdWIsIEluYy4xEzARBgNVBAMTCmdp
17
+ dGh1Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx1Nw8r/3z
18
+ Tu3BZ63myyLot+KrKPL33GJwCNEMr9YWaiGwNksXDTZjBK6/6iBRlWVm8r+5TaQM
19
+ Kev1FbHoNbNwEJTVG1m0Jg/Wg1dZneF8Cd3gE8pNb0Obzc+HOhWnhd1mg+2TDP4r
20
+ bTgceYiQz61YGC1R0cKj8keMbzgJubjvTJMLy4OUh+rgo7XZe5trD0P5yu6ADSin
21
+ dvEl9ME1PPZ0rd5qM4J73P1LdqfC7vJqv6kkpl/nLnwO28N0c/p+xtjPYOs2ViG2
22
+ wYq4JIJNeCS66R2hiqeHvmYlab++O3JuT+DkhSUIsZGJuNZ0ZXabLE9iH6H6Or6c
23
+ JL+fyrDFwGeNAgMBAAGjggHuMIIB6jAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl
24
+ 0yHU+PjWDzAdBgNVHQ4EFgQUakOQfTuYFHJSlTqqKApD+FF+06YwJQYDVR0RBB4w
25
+ HIIKZ2l0aHViLmNvbYIOd3d3LmdpdGh1Yi5jb20wDgYDVR0PAQH/BAQDAgWgMB0G
26
+ A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5o
27
+ dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzEuY3JsMDSg
28
+ MqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzEu
29
+ Y3JsMEIGA1UdIAQ7MDkwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBz
30
+ Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgYgGCCsGAQUFBwEBBHwwejAkBggrBgEF
31
+ BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAChkZodHRw
32
+ Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5kZWRWYWxp
33
+ ZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD
34
+ ggEBAG/nbcuC8++QhwnXDxUiLIz+06scipbbXRJd0XjAMbD/RciJ9wiYUhcfTEsg
35
+ ZGpt21DXEL5+q/4vgNipSlhBaYFyGQiDm5IQTmIte0ZwQ26jUxMf4pOmI1v3kj43
36
+ FHU7uUskQS6lPUgND5nqHkKXxv6V2qtHmssrA9YNQMEK93ga2rWDpK21mUkgLviT
37
+ PB5sPdE7IzprOCp+Ynpf3RcFddAkXb6NqJoQRPrStMrv19C1dqUmJRwIQdhkkqev
38
+ ff6IQDlhC8BIMKmCNK33cEYDfDWROtW7JNgBvBTwww8jO1gyug8SbGZ6bZ3k8OV8
39
+ XX4C2NesiZcLYbc2n7B9O+63M2k=
40
+ -----END CERTIFICATE-----
41
+ EOF
42
+
5
43
  describe Conjur::Command::Init do
44
+ it "properly defaults to a file path" do
45
+ expect(Conjur::CLI.commands[:init].flags[:f].default_value).to eq("#{ENV['HOME']}/.conjurrc")
46
+ end
47
+
48
+ describe ".get_certificate" do
49
+ it "returns the right certificate from github" do
50
+ fingerprint, certificate = Conjur::Command::Init.get_certificate('github.com:443')
51
+ expect(fingerprint).to eq(GITHUB_FP)
52
+ expect(certificate.strip).to eq(GITHUB_CERT.strip)
53
+ end
54
+ end
55
+
6
56
  context logged_in: false do
7
57
  before {
8
58
  File.stub(:exists?).and_return false
@@ -10,7 +60,7 @@ describe Conjur::Command::Init do
10
60
  context "auto-fetching fingerprint" do
11
61
  before {
12
62
  HighLine.any_instance.stub(:ask).with("Enter the hostname (and optional port) of your Conjur endpoint: ").and_return "the-host"
13
- Object.any_instance.should_receive(:`).with("echo | openssl s_client -connect the-host:443 2>/dev/null | openssl x509 -fingerprint").and_return "the-fingerprint"
63
+ Conjur::Command::Init.stub get_certificate: ["the-fingerprint", nil]
14
64
  HighLine.any_instance.stub(:ask).with(/^Trust this certificate/).and_return "yes"
15
65
  }
16
66
  describe_command 'init' do
@@ -40,6 +90,13 @@ describe Conjur::Command::Init do
40
90
  invoke
41
91
  end
42
92
  end
93
+ describe_command 'init -a the-account -h https://google.com' do
94
+ it "writes the config and cert" do
95
+ HighLine.any_instance.stub(:ask).and_return "yes"
96
+ File.should_receive(:open).twice
97
+ invoke
98
+ end
99
+ end
43
100
  describe_command 'init -a the-account -h localhost -c the-cert' do
44
101
  it "writes config and cert files" do
45
102
  File.should_receive(:open).twice
@@ -51,16 +108,13 @@ describe Conjur::Command::Init do
51
108
  it "writes config and cert files" do
52
109
  invoke
53
110
 
54
- File.read(File.join(tmpdir, ".conjurrc")).should == """---
55
- account: the-account
56
- plugins:
57
- - environment
58
- - layer
59
- - key-pair
60
- - pubkeys
61
- appliance_url: https://localhost/api
62
- cert_file: #{tmpdir}/conjur-the-account.pem
63
- """
111
+ expect(YAML.load(File.read(File.join(tmpdir, ".conjurrc")))).to eq({
112
+ account: 'the-account',
113
+ plugins: %w(environment layer key-pair pubkeys),
114
+ appliance_url: "https://localhost/api",
115
+ cert_file: "#{tmpdir}/conjur-the-account.pem"
116
+ }.stringify_keys)
117
+
64
118
  File.read(File.join(tmpdir, "conjur-the-account.pem")).should == "the-cert\n"
65
119
  end
66
120
  end
@@ -2,6 +2,18 @@ require 'spec_helper'
2
2
  require 'conjur/dsl/runner'
3
3
 
4
4
  describe Conjur::Command::Policy do
5
+ describe ".default_collection_user" do
6
+ it "returns the current username" do
7
+ expect(Conjur::Command::Policy.default_collection_user).to eq(`whoami`.strip)
8
+ end
9
+ end
10
+
11
+ describe ".default_collection_hostname" do
12
+ it "returns the current hostname" do
13
+ expect(Conjur::Command::Policy.default_collection_hostname).to eq(`hostname`.strip)
14
+ end
15
+ end
16
+
5
17
  context logged_in: true do
6
18
  let(:role) do
7
19
  double("role", exists?: true, api_key: "the-api-key", roleid: "the-role")
@@ -9,17 +21,15 @@ describe Conjur::Command::Policy do
9
21
  let(:resource) do
10
22
  double("resource", exists?: true).as_null_object
11
23
  end
12
- let(:name) { nil }
13
24
  before {
14
25
  File.stub(:read).with("policy-body").and_return "{}"
15
26
  Conjur::DSL::Runner.any_instance.stub(:api).and_return api
16
27
  }
17
28
  before {
29
+ api.stub(:role).and_call_original
30
+ api.stub(:resource).and_call_original
18
31
  api.stub(:role).with("the-account:policy:#{collection}/the-policy-1.0.0").and_return role
19
32
  api.stub(:resource).with("the-account:policy:#{collection}/the-policy-1.0.0").and_return resource
20
- if name
21
- resource.should_receive(:[]).with(:name, name)
22
- end
23
33
  }
24
34
 
25
35
  describe_command 'policy:load --collection the-collection policy-body' do
@@ -33,6 +43,15 @@ describe Conjur::Command::Policy do
33
43
  before {
34
44
  stub_const("ENV", "USER" => "alice", "HOSTNAME" => "localhost")
35
45
  }
46
+ describe_command 'policy:load --as-group the-group policy-body' do
47
+ let(:group) { double(:group, exists?: true) }
48
+ it "creates the policy" do
49
+ Conjur::Command.api.stub(:role).with("the-account:group:the-group").and_return group
50
+ Conjur::DSL::Runner.any_instance.should_receive(:owner=).with("the-account:group:the-group")
51
+
52
+ invoke.should == 0
53
+ end
54
+ end
36
55
  describe_command 'policy:load policy-body' do
37
56
  it "creates the policy with default collection" do
38
57
  invoke.should == 0
@@ -40,4 +59,4 @@ describe Conjur::Command::Policy do
40
59
  end
41
60
  end
42
61
  end
43
- end
62
+ end