inspec 0.18.0 → 0.19.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -2
  3. data/README.md +27 -2
  4. data/docs/resources.rst +49 -3
  5. data/inspec.gemspec +1 -1
  6. data/lib/bundles/inspec-compliance/README.md +2 -1
  7. data/lib/bundles/inspec-compliance/api.rb +60 -102
  8. data/lib/bundles/inspec-compliance/cli.rb +133 -14
  9. data/lib/bundles/inspec-compliance/configuration.rb +43 -1
  10. data/lib/bundles/inspec-compliance/http.rb +80 -0
  11. data/lib/bundles/inspec-compliance.rb +1 -0
  12. data/lib/inspec/metadata.rb +40 -27
  13. data/lib/inspec/objects/test.rb +2 -1
  14. data/lib/inspec/resource.rb +1 -0
  15. data/lib/inspec/rspec_json_formatter.rb +1 -1
  16. data/lib/inspec/runner.rb +19 -13
  17. data/lib/inspec/runner_rspec.rb +1 -1
  18. data/lib/inspec/version.rb +1 -1
  19. data/lib/matchers/matchers.rb +32 -13
  20. data/lib/resources/grub_conf.rb +186 -0
  21. data/lib/resources/json.rb +1 -1
  22. data/lib/resources/service.rb +9 -3
  23. data/lib/utils/base_cli.rb +2 -1
  24. data/lib/utils/hash_map.rb +37 -0
  25. data/test/functional/inspec_compliance_test.rb +60 -0
  26. data/test/functional/inspec_exec_test.rb +49 -10
  27. data/test/helper.rb +3 -0
  28. data/test/integration/default/compare_matcher_spec.rb +21 -0
  29. data/test/unit/metadata_test.rb +49 -23
  30. data/test/unit/mock/cmd/systemctl-show-all-dbus +6 -0
  31. data/test/unit/mock/files/grub.conf +21 -0
  32. data/test/unit/mock/profiles/resource-tiny/inspec.yml +10 -0
  33. data/test/unit/mock/profiles/resource-tiny/libraries/resource.rb +3 -0
  34. data/test/unit/mock/profiles/supported_inspec/inspec.yml +2 -0
  35. data/test/unit/mock/profiles/unsupported_inspec/inspec.yml +2 -0
  36. data/test/unit/profile_test.rb +1 -1
  37. data/test/unit/resources/grub_conf_test.rb +29 -0
  38. data/test/unit/resources/service_test.rb +9 -0
  39. data/test/unit/utils/hash_map_test.rb +63 -0
  40. metadata +26 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fcd7959abdb3de12c11854323ad29f484f0dbda
4
- data.tar.gz: 79bbc6496cc71d18e02a9b05b4e6fbd3a5043315
3
+ metadata.gz: 3f8721f5a1366ca4d03be5b1cbfe3b8f61ba315f
4
+ data.tar.gz: 68a9c4ce7b72cde464e322d449ce8775f0afa6a8
5
5
  SHA512:
6
- metadata.gz: 4256867dac4a3a9978b4f5dfda36aba67f9d97461d9d074e694f048d51ae22cd2e835593524e5dcbcc9d3f87a34279cbcd295f6f66d0d372939c8daf5e73688f
7
- data.tar.gz: c6cc148cae574dd9dce10ad9781cb9e088fff6c52fdde9f3b40463d7166689e23923b539ba077a4d224e3611f33b0fd7c3b2799e9308590516a6231f536a78cc
6
+ metadata.gz: 595a34ad34f066fffe31f2ee6799d5692010521949d6dbe57c3d306f391922e7c4ea295f6504156d94b8b835553930623757a2a97e10ce706ae1340ccc40006f
7
+ data.tar.gz: d117f5aecbcdce653511e263b1b5c9ae394a354ae38bc5fdb93aa73850d9328d6e7934b3acdb5568e689d0163795134a15aef2e167212920baf6306d6d5e4e24
data/CHANGELOG.md CHANGED
@@ -1,7 +1,39 @@
1
1
  # Change Log
2
2
 
3
- ## [0.18.0](https://github.com/chef/inspec/tree/0.18.0) (2016-04-09)
4
- [Full Changelog](https://github.com/chef/inspec/compare/v0.17.1...0.18.0)
3
+ ## [0.19.0](https://github.com/chef/inspec/tree/0.19.0) (2016-04-17)
4
+ [Full Changelog](https://github.com/chef/inspec/compare/v0.18.0...0.19.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Add required inspec version to inspec.yml [\#644](https://github.com/chef/inspec/issues/644)
9
+ - Resource grub conf [\#652](https://github.com/chef/inspec/pull/652) ([arlimus](https://github.com/arlimus))
10
+ - fail on unsupported os/platform [\#651](https://github.com/chef/inspec/pull/651) ([arlimus](https://github.com/arlimus))
11
+ - specify required inspec version in inspec.yml [\#648](https://github.com/chef/inspec/pull/648) ([arlimus](https://github.com/arlimus))
12
+ - feature: `cmp \< / \> / \<= / \>= / == / != sth` matcher [\#643](https://github.com/chef/inspec/pull/643) ([arlimus](https://github.com/arlimus))
13
+ - Add 'static' value as enabled to systemd service enabled check [\#637](https://github.com/chef/inspec/pull/637) ([jmccann](https://github.com/jmccann))
14
+ - add dockerized inspec [\#635](https://github.com/chef/inspec/pull/635) ([arlimus](https://github.com/arlimus))
15
+ - inspec-compliance + Compliance 1.0 [\#576](https://github.com/chef/inspec/pull/576) ([srenatus](https://github.com/srenatus))
16
+
17
+ **Fixed bugs:**
18
+
19
+ - `add\_test': undefined method error on Ubuntu 15.10 with Ruby 2.1 [\#642](https://github.com/chef/inspec/issues/642)
20
+ - Install failed on Ubuntu with Ruby 2.1 [\#641](https://github.com/chef/inspec/issues/641)
21
+ - Inspec json resource . example not working [\#631](https://github.com/chef/inspec/issues/631)
22
+ - Checking on services on SLES 11 fails [\#627](https://github.com/chef/inspec/issues/627)
23
+ - Inspec check fails on `examples/profile` [\#485](https://github.com/chef/inspec/issues/485)
24
+ - bugfix: rspec world handling on rspec 3.5 [\#650](https://github.com/chef/inspec/pull/650) ([arlimus](https://github.com/arlimus))
25
+ - Prevent its\(:to\_i\) from generated tests [\#639](https://github.com/chef/inspec/pull/639) ([alexpop](https://github.com/alexpop))
26
+ - bugfix: non-profile execution with json formatter [\#632](https://github.com/chef/inspec/pull/632) ([arlimus](https://github.com/arlimus))
27
+
28
+ **Merged pull requests:**
29
+
30
+ - add usage instructions for inspec container [\#649](https://github.com/chef/inspec/pull/649) ([chris-rock](https://github.com/chris-rock))
31
+ - update documentation for json resource [\#647](https://github.com/chef/inspec/pull/647) ([chris-rock](https://github.com/chris-rock))
32
+ - Add support for suse 11 to service resource [\#638](https://github.com/chef/inspec/pull/638) ([spuranam](https://github.com/spuranam))
33
+ - Add -i to ssh example, link to cli options [\#636](https://github.com/chef/inspec/pull/636) ([vjeffrey](https://github.com/vjeffrey))
34
+
35
+ ## [v0.18.0](https://github.com/chef/inspec/tree/v0.18.0) (2016-04-09)
36
+ [Full Changelog](https://github.com/chef/inspec/compare/v0.17.1...v0.18.0)
5
37
 
6
38
  **Implemented enhancements:**
7
39
 
@@ -11,6 +43,7 @@
11
43
 
12
44
  **Merged pull requests:**
13
45
 
46
+ - 0.18.0 [\#629](https://github.com/chef/inspec/pull/629) ([arlimus](https://github.com/arlimus))
14
47
  - Encourage sharing of profiles [\#625](https://github.com/chef/inspec/pull/625) ([nathenharvey](https://github.com/nathenharvey))
15
48
  - add travis and appveyor badges [\#622](https://github.com/chef/inspec/pull/622) ([chris-rock](https://github.com/chris-rock))
16
49
  - remove unused profile.tar.gz [\#621](https://github.com/chef/inspec/pull/621) ([chris-rock](https://github.com/chris-rock))
data/README.md CHANGED
@@ -18,14 +18,14 @@ describe inetd_conf do
18
18
  end
19
19
  ```
20
20
 
21
- InSpec makes it easy to run your tests wherever you need.
21
+ InSpec makes it easy to run your tests wherever you need. More options listed here: https://github.com/chef/inspec/blob/master/docs/ctl_inspec.rst
22
22
 
23
23
  ```bash
24
24
  # run test locally
25
25
  inspec exec test.rb
26
26
 
27
27
  # run test on remote host on SSH
28
- inspec exec test.rb -t ssh://user@hostname
28
+ inspec exec test.rb -t ssh://user@hostname -i /path/to/key
29
29
 
30
30
  # run test on remote windows host on WinRM
31
31
  inspec exec test.rb -t winrm://Administrator@windowshost --password 'your-password'
@@ -51,6 +51,31 @@ InSpec requires Ruby ( >1.9 ).
51
51
  gem install inspec
52
52
  ```
53
53
 
54
+ ### Usage via Docker
55
+
56
+ Download the image and define an alias for convenience:
57
+
58
+ ```
59
+ docker pull chef/inspec
60
+ alias inspec='docker run -it --rm -v $(pwd):/share chef/inspec'
61
+ ```
62
+
63
+ If you call inspec from cli, it automatically mounts the current directory into the work directory. Therefore you can easily use local tests and key files. Note: Only files in the current directory are available to the container.
64
+
65
+ ```
66
+ $ ls -1
67
+ vagrant
68
+ test.rb
69
+
70
+
71
+ $ inspec exec test.rb -t ssh://root@192.168.64.2:11022 -i vagrant
72
+ ..
73
+
74
+ Finished in 0.04321 seconds (files took 0.54917 seconds to load)
75
+ 2 examples, 0 failures
76
+ ```
77
+
78
+
54
79
  ### Install it from source
55
80
 
56
81
  That requires [bundler](http://bundler.io/):
data/docs/resources.rst CHANGED
@@ -18,6 +18,7 @@ The following InSpec audit resources are available:
18
18
  * `file`_
19
19
  * `gem`_
20
20
  * `group <https://github.com/chef/inspec/blob/master/docs/resources.rst#group-1/>`_
21
+ * `grub_conf`_
21
22
  * `host`_
22
23
  * `inetd_conf`_
23
24
  * `interface`_
@@ -1567,6 +1568,38 @@ The following examples show how to use this InSpec audit resource.
1567
1568
 
1568
1569
 
1569
1570
 
1571
+
1572
+ grub_conf
1573
+ =====================================================
1574
+
1575
+ Test both Grub 1 and Grub 2 configurations.
1576
+
1577
+ **Stability: Experimental**
1578
+
1579
+ Syntax
1580
+ -----------------------------------------------------
1581
+ A ``grub_conf`` resource is used to specify a configuration file and boot configuration.
1582
+
1583
+ .. code-block:: ruby
1584
+
1585
+ describe grub_conf('/etc/grub.conf', 'default') do
1586
+ its('kernel') { should include '/vmlinuz-2.6.32-573.7.1.el6.x86_64' }
1587
+ its('initrd') { should include '/initramfs-2.6.32-573.el6.x86_64.img=1' }
1588
+ its('default') { should_not eq '1' }
1589
+ its('timeout') { should eq '5' }
1590
+ end
1591
+
1592
+ You can also check specific kernels:
1593
+
1594
+ .. code-block:: ruby
1595
+
1596
+ grub_conf('/etc/grub.conf', 'CentOS (2.6.32-573.12.1.el6.x86_64)') do
1597
+ its('kernel') { should include 'audit=1' }
1598
+ end
1599
+
1600
+
1601
+
1602
+
1570
1603
  host
1571
1604
  =====================================================
1572
1605
  Use the ``host`` |inspec resource| to test the name used to refer to a specific host and its availability, including the Internet protocols and ports over which that host name should be available.
@@ -1872,12 +1905,25 @@ Use the ``json`` |inspec resource| to test data in a |json| file.
1872
1905
 
1873
1906
  Syntax
1874
1907
  -----------------------------------------------------
1875
- A ``json`` |inspec resource| block declares the data to be tested:
1908
+ A ``json`` |inspec resource| block declares the data to be tested. Assume the following json file:
1909
+
1910
+ .. code-block:: json
1911
+
1912
+ {
1913
+ "name" : "hello",
1914
+ "meta" : {
1915
+ "creator" : "John Doe"
1916
+ }
1917
+ }
1918
+
1919
+
1920
+ This file can be queried via:
1876
1921
 
1877
1922
  .. code-block:: ruby
1878
1923
 
1879
- describe json do
1880
- its('name') { should eq 'foo' }
1924
+ describe json('/paht/to/name.json') do
1925
+ its('name') { should eq 'hello' }
1926
+ its(['meta','creator']) { should eq 'John Doe' }
1881
1927
  end
1882
1928
 
1883
1929
  where
data/inspec.gemspec CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency 'rainbow', '~> 2'
31
31
  spec.add_dependency 'method_source', '~> 0.8'
32
32
  spec.add_dependency 'rubyzip', '~> 1.1'
33
- spec.add_dependency 'rspec', '~> 3.3'
33
+ spec.add_dependency 'rspec', '~> 3'
34
34
  spec.add_dependency 'rspec-its', '~> 1.2'
35
35
  spec.add_dependency 'pry', '~> 0'
36
36
 
@@ -8,7 +8,8 @@ This extensions offers the following features:
8
8
 
9
9
  To use the CLI, this InSpec add-on adds the following commands:
10
10
 
11
- * `$ inspec compliance login user password` - authentication against Chef Compliance
11
+ * `$ inspec compliance api_token server --token TOKEN --user USER` - save the Chef Compliance API token for user
12
+ * `$ inspec compliance login` - authentication of the API token against Chef Compliance
12
13
  * `$ inspec compliance profiles` - list all available Chef Compliance profiles
13
14
  * `$ inspec compliance exec profile` - runs a Chef Compliance profile
14
15
  * `$ inspec compliance upload path/to/local/profile` - uploads a local profile to Chef Compliance
@@ -8,45 +8,44 @@ require 'uri'
8
8
  module Compliance
9
9
  # API Implementation does not hold any state by itself,
10
10
  # everything will be stored in local Configuration store
11
- class API # rubocop:disable Metrics/ClassLength
12
- # logs into the server, retrieves a token and stores it locally
13
- def self.login(server, username, password, insecure, apipath)
14
- config = Compliance::Configuration.new
15
- config['server'] = "#{server}#{apipath}"
16
- url = "#{config['server']}/oauth/token"
11
+ class API
12
+ # login method for pre-1.0 compliance server
13
+ def self.legacy_login_post(url, username, password, insecure)
14
+ # form request
15
+ # TODO: reuse post function
16
+ uri = URI.parse(url)
17
+ req = Net::HTTP::Post.new(uri.path)
18
+ req.basic_auth(username, password)
19
+ req.form_data={}
20
+
21
+ send_request(uri, req, insecure)
22
+ end
17
23
 
18
- success, data = Compliance::API.post(url, username, password, insecure)
24
+ # return all compliance profiles available for the user
25
+ def self.profiles(config)
26
+ url = "#{config['server']}/user/compliance"
27
+ # TODO, api should not be dependent on .supported?
28
+ response = Compliance::HTTP.get(url, config['token'], config['insecure'], !config.supported?(:oidc))
29
+ data = response.body
19
30
  if !data.nil?
20
- tokendata = JSON.parse(data)
21
- if tokendata['access_token']
22
- config['user'] = username
23
- config['token'] = tokendata['access_token']
24
- config['insecure'] = insecure
25
- config.store
26
- success = true
27
- msg = 'Successfully authenticated'
28
- else
29
- msg = 'Reponse does not include a token'
30
- end
31
+ profiles = JSON.parse(data)
32
+ # iterate over profiles
33
+ profiles.map do |owner, ps|
34
+ ps.keys.map do |name|
35
+ { org: owner, name: name }
36
+ end
37
+ end.flatten
31
38
  else
32
- msg = "Authentication failed for Server: #{url}"
39
+ []
33
40
  end
34
- [success, msg]
35
- end
36
-
37
- def self.logout
38
- config = Compliance::Configuration.new
39
- url = "#{config['server']}/logout"
40
- Compliance::API.post(url, config['token'], nil, config['insecure'])
41
- config.destroy
42
41
  end
43
42
 
44
43
  # return the server api version
45
- def self.version
46
- config = Compliance::Configuration.new
47
- url = "#{config['server']}/version"
48
-
49
- _success, data = Compliance::API.get(url, nil, nil, config['insecure'])
44
+ # NB this method does not use Compliance::Configuration to allow for using
45
+ # it before we know the version (e.g. oidc or not)
46
+ def self.version(url, insecure)
47
+ response = Compliance::HTTP.get(url+'/version', nil, insecure)
48
+ data = response.body
50
49
  if !data.nil?
51
50
  JSON.parse(data)
52
51
  else
@@ -54,30 +53,9 @@ module Compliance
54
53
  end
55
54
  end
56
55
 
57
- # return all compliance profiles available for the user
58
- def self.profiles
59
- config = Compliance::Configuration.new
60
- url = "#{config['server']}/user/compliance"
61
- _success, data = get(url, config['token'], '', config['insecure'])
62
-
63
- if !data.nil?
64
- profiles = JSON.parse(data)
65
- val = []
66
- # iterate over profiles
67
- profiles.each_key { |org|
68
- profiles[org].each_key { |name|
69
- val.push({ org: org, name: name })
70
- }
71
- }
72
- val
73
- else
74
- []
75
- end
76
- end
77
-
78
56
  # verifies that a profile
79
- def self.exist?(profile)
80
- profiles = Compliance::API.profiles
57
+ def self.exist?(config, profile)
58
+ profiles = Compliance::API.profiles(config)
81
59
  if !profiles.empty?
82
60
  index = profiles.index { |p| "#{p[:org]}/#{p[:name]}" == profile }
83
61
  !index.nil? && index >= 0
@@ -86,57 +64,37 @@ module Compliance
86
64
  end
87
65
  end
88
66
 
89
- def self.get(url, username, password, insecure)
90
- uri = URI.parse(url)
91
- req = Net::HTTP::Get.new(uri.path)
92
- req.basic_auth username, password
93
-
94
- send_request(uri, req, insecure)
95
- end
96
-
97
- def self.post(url, username, password, insecure)
98
- # form request
99
- uri = URI.parse(url)
100
- req = Net::HTTP::Post.new(uri.path)
101
- req.basic_auth username, password
102
- req.form_data={}
103
-
104
- send_request(uri, req, insecure)
105
- end
106
-
107
- # upload a file
108
- def self.post_file(url, username, password, file_path, insecure)
109
- uri = URI.parse(url)
110
- http = Net::HTTP.new(uri.host, uri.port)
111
-
112
- # set connection flags
113
- http.use_ssl = (uri.scheme == 'https')
114
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
115
-
116
- req = Net::HTTP::Post.new(uri.path)
117
- req.basic_auth username, password
118
-
119
- req.body_stream=File.open(file_path, 'rb')
120
- req.add_field('Content-Length', File.size(file_path))
121
- req.add_field('Content-Type', 'application/x-gtar')
122
-
123
- boundary = 'INSPEC-PROFILE-UPLOAD'
124
- req.add_field('session', boundary)
125
- res=http.request(req)
126
-
67
+ def self.upload(config, owner, profile_name, archive_path)
68
+ # upload the tar to Chef Compliance
69
+ url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
70
+ res = Compliance::HTTP.post_file(url, config['token'], archive_path, config['insecure'], !config.supported?(:oidc))
127
71
  [res.is_a?(Net::HTTPSuccess), res.body]
128
72
  end
129
73
 
130
- def self.send_request(uri, req, insecure)
131
- opts = {
132
- use_ssl: uri.scheme == 'https',
133
- }
134
- opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if insecure
74
+ def self.post_refresh_token(url, token, insecure)
75
+ uri = URI.parse("#{url}/login")
76
+ req = Net::HTTP::Post.new(uri.path)
77
+ # req['Authorization'] = "Bearer #{token}"
78
+ req.body = { token: token }.to_json
79
+ access_token = nil
80
+ response = Compliance::HTTP.send_request(uri, req, insecure)
81
+ data = response.body
82
+ if !data.nil?
83
+ begin
84
+ tokendata = JSON.parse(data)
85
+ access_token = tokendata['access_token']
86
+ msg = 'Successfully fetched access token'
87
+ success = true
88
+ rescue JSON::ParserError => e
89
+ success = false
90
+ msg = e.message
91
+ end
92
+ else
93
+ success = false
94
+ msg = 'Invalid refresh_token'
95
+ end
135
96
 
136
- res = Net::HTTP.start(uri.host, uri.port, opts) {|http|
137
- http.request(req)
138
- }
139
- [res.is_a?(Net::HTTPSuccess), res.body]
97
+ [success, msg, access_token]
140
98
  end
141
99
  end
142
100
  end
@@ -10,16 +10,44 @@ module Compliance
10
10
  namespace 'compliance'
11
11
 
12
12
  desc 'login SERVER', 'Log in to a Chef Compliance SERVER'
13
- option :user, type: :string, required: true,
14
- desc: 'Chef Compliance Username'
15
- option :password, type: :string, required: true,
16
- desc: 'Chef Compliance Password'
13
+ option :server, type: :string, desc: 'Chef Compliance Server URL'
17
14
  option :insecure, aliases: :k, type: :boolean,
18
15
  desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
16
+ option :user, type: :string, required: false,
17
+ desc: 'Chef Compliance Username (for legacy auth)'
18
+ option :password, type: :string, required: false,
19
+ desc: 'Chef Compliance Password (for legacy auth)'
19
20
  option :apipath, type: :string, default: '/api',
20
21
  desc: 'Set the path to the API, defaults to /api'
21
- def login(server)
22
- success, msg = Compliance::API.login(server, options['user'], options['password'], options['insecure'], options['apipath'])
22
+ option :token, type: :string, required: false,
23
+ desc: 'Chef Compliance access token'
24
+ option :refresh_token, type: :string, required: false,
25
+ desc: 'Chef Compliance refresh token'
26
+ def login(server) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, PerceivedComplexity
27
+ # show warning if the Compliance Server does not support
28
+ if !Compliance::Configuration.new.supported?(:oidc) && (!options['token'].nil? || !options['refresh_token'].nil?)
29
+ puts 'Your server supports --user and --password only'
30
+ end
31
+
32
+ options['server'] = server
33
+ url = options['server'] + options['apipath']
34
+ if !options['user'].nil? && !options['password'].nil?
35
+ # username / password
36
+ success, msg = login_legacy(url, options['user'], options['password'], options['insecure'])
37
+ elsif !options['user'].nil? && !options['token'].nil?
38
+ # access token
39
+ success, msg = store_access_token(url, options['user'], options['token'], options['insecure'])
40
+ elsif !options['refresh_token'].nil? && !options['user'].nil?
41
+ # refresh token
42
+ success, msg = store_refresh_token(url, options['refresh_token'], true, options['user'], options['insecure'])
43
+ # TODO: we should login with the refreshtoken here
44
+ elsif !options['refresh_token'].nil?
45
+ success, msg = login_refreshtoken(url, options)
46
+ else
47
+ puts 'Please run `inspec compliance login` with options --token or --refresh_token and --user'
48
+ exit 1
49
+ end
50
+
23
51
  if success
24
52
  puts 'Successfully authenticated'
25
53
  else
@@ -29,7 +57,8 @@ module Compliance
29
57
 
30
58
  desc 'profiles', 'list all available profiles in Chef Compliance'
31
59
  def profiles
32
- profiles = Compliance::API.profiles
60
+ config = Compliance::Configuration.new
61
+ profiles = Compliance::API.profiles(config)
33
62
  if !profiles.empty?
34
63
  # iterate over profiles
35
64
  headline('Available profiles:')
@@ -56,6 +85,11 @@ module Compliance
56
85
  option :overwrite, type: :boolean, default: false,
57
86
  desc: 'Overwrite existing profile on Chef Compliance.'
58
87
  def upload(path) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, PerceivedComplexity
88
+ unless File.exist?(path)
89
+ puts "Directory #{path} does not exist."
90
+ exit 1
91
+ end
92
+
59
93
  o = options.dup
60
94
  configure_logger(o)
61
95
  # check the profile, we only allow to upload valid profiles
@@ -88,7 +122,7 @@ module Compliance
88
122
 
89
123
  # check that the profile is not uploaded already,
90
124
  # confirm upload to the user (overwrite with --force)
91
- if Compliance::API.exist?("#{owner}/#{profile_name}") && !options['overwrite']
125
+ if Compliance::API.exist?(config, "#{owner}/#{profile_name}") && !options['overwrite']
92
126
  error.call('Profile exists on the server, use --overwrite')
93
127
  end
94
128
 
@@ -111,11 +145,9 @@ module Compliance
111
145
  puts "Start upload to #{owner}/#{profile_name}"
112
146
  pname = ERB::Util.url_encode(profile_name)
113
147
 
114
- # upload the tar to Chef Compliance
115
- url = "#{config['server']}/owners/#{owner}/compliance/#{pname}/tar"
148
+ puts 'Uploading to Chef Compliance'
149
+ success, msg = Compliance::API.upload(config, owner, pname, archive_path)
116
150
 
117
- puts "Uploading to #{url}"
118
- success, msg = Compliance::API.post_file(url, config['token'], '', archive_path, config['insecure'])
119
151
  if success
120
152
  puts 'Successfully uploaded profile'
121
153
  else
@@ -126,7 +158,8 @@ module Compliance
126
158
 
127
159
  desc 'version', 'displays the version of the Chef Compliance server'
128
160
  def version
129
- info = Compliance::API.version
161
+ config = Compliance::Configuration.new
162
+ info = Compliance::API.version(config['server'], config['insecure'])
130
163
  if !info.nil? && info['version']
131
164
  puts "Chef Compliance version: #{info['version']}"
132
165
  else
@@ -136,12 +169,98 @@ module Compliance
136
169
 
137
170
  desc 'logout', 'user logout from Chef Compliance'
138
171
  def logout
139
- if Compliance::API.logout
172
+ config = Compliance::Configuration.new
173
+ unless config.supported?(:oidc) || config['token'].nil?
174
+ config = Compliance::Configuration.new
175
+ url = "#{config['server']}/logout"
176
+ Compliance::API.post(url, config['token'], config['insecure'], !config.supported?(:oidc))
177
+ end
178
+
179
+ success = config.destroy
180
+
181
+ if success
140
182
  puts 'Successfully logged out'
141
183
  else
142
184
  puts 'Could not log out'
143
185
  end
144
186
  end
187
+
188
+ private
189
+
190
+ def login_refreshtoken(url, options)
191
+ success, msg, access_token = Compliance::API.post_refresh_token(url, options['refresh_token'], options['insecure'])
192
+ if success
193
+ config = Compliance::Configuration.new
194
+ config['server'] = url
195
+ config['token'] = access_token
196
+ config['insecure'] = options['insecure']
197
+ config['version'] = Compliance::API.version(url, options['insecure'])
198
+ config.store
199
+ end
200
+
201
+ [success, msg]
202
+ end
203
+
204
+ def login_legacy(url, username, password, insecure)
205
+ config = Compliance::Configuration.new
206
+ success, data = Compliance::API.legacy_login_post(url+'/oauth/token', username, password, insecure)
207
+ if !data.nil?
208
+ tokendata = JSON.parse(data)
209
+ if tokendata['access_token']
210
+ config['server'] = url
211
+ config['user'] = username
212
+ config['token'] = tokendata['access_token']
213
+ config['insecure'] = insecure
214
+ config['version'] = Compliance::API.version(url, insecure)
215
+ config.store
216
+ success = true
217
+ msg = 'Successfully authenticated'
218
+ else
219
+ msg = 'Reponse does not include a token'
220
+ end
221
+ else
222
+ msg = "Authentication failed for Server: #{url}"
223
+ end
224
+ [success, msg]
225
+ end
226
+
227
+ # saves a user access token (limited time)
228
+ def store_access_token(url, user, token, insecure)
229
+ config = Compliance::Configuration.new
230
+ config['server'] = url
231
+ config['insecure'] = insecure
232
+ config['user'] = user
233
+ config['token'] = token
234
+ config['version'] = Compliance::API.version(url, insecure)
235
+ config.store
236
+
237
+ [true, 'access token stored']
238
+ end
239
+
240
+ # saves the a user refresh token supplied by the user
241
+ def store_refresh_token(url, refresh_token, verify, user, insecure)
242
+ config = Compliance::Configuration.new
243
+ config['server'] = url
244
+ config['refresh_token'] = refresh_token
245
+ config['user'] = user
246
+ config['insecure'] = insecure
247
+ config['version'] = Compliance::API.version(url, insecure)
248
+
249
+ if !verify
250
+ config.store
251
+ success = true
252
+ msg = 'refresh token stored'
253
+ else
254
+ success, msg, access_token = Compliance::API.post_refresh_token(url, refresh_token, insecure)
255
+ if success
256
+ config['token'] = access_token
257
+ config.store
258
+ msg = 'token verified and stored'
259
+ end
260
+ end
261
+
262
+ [success, msg]
263
+ end
145
264
  end
146
265
 
147
266
  # register the subcommand to Inspec CLI registry
@@ -47,7 +47,49 @@ module Compliance
47
47
 
48
48
  # deletes data
49
49
  def destroy
50
- File.delete(@config_file)
50
+ if File.exist?(@config_file)
51
+ File.delete(@config_file)
52
+ else
53
+ true
54
+ end
55
+ end
56
+
57
+ # return if the (stored) api version does not support a certain feature
58
+ def supported?(feature)
59
+ sup = version_with_support(feature)
60
+
61
+ # we do not know the version, therefore we do not know if its possible to use the feature
62
+ return if self['version'].nil? || self['version']['version'].nil?
63
+
64
+ if sup.is_a?(Array)
65
+ Gem::Version.new(self['version']['version']) >= sup[0] &&
66
+ Gem::Version.new(self['version']['version']) < sup[1]
67
+ else
68
+ Gem::Version.new(self['version']['version']) >= sup
69
+ end
70
+ end
71
+
72
+ # exit 1 if the version of compliance that we're working with doesn't support odic
73
+ def legacy_check!(feature)
74
+ if !supported?(feature)
75
+ puts "This feature (#{feature}) is not available for legacy installations."
76
+ puts 'Please upgrade to a recent version of Chef Compliance.'
77
+ exit 1
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ # for a feature, returns either:
84
+ # - a version v0: v supports v0 iff v0 <= v
85
+ # - an array [v0, v1] of two versions: v supports [v0, v1] iff v0 <= v < v1
86
+ def version_with_support(feature)
87
+ case feature.to_sym
88
+ when :oidc
89
+ Gem::Version.new('0.16.19')
90
+ else
91
+ Gem::Version.new('0.0.0')
92
+ end
51
93
  end
52
94
  end
53
95
  end