conjur-cli 4.8.0 → 4.9.3

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