inspec 1.17.0 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,7 @@ module Secrets
9
9
  attr_reader :attributes
10
10
 
11
11
  def self.resolve(target)
12
- unless target.is_a?(String) && File.file?(target) && target.end_with?('.yml')
12
+ unless target.is_a?(String) && File.file?(target) && ['.yml', '.yaml'].include?(File.extname(target).downcase)
13
13
  return nil
14
14
  end
15
15
  new(target)
@@ -18,6 +18,8 @@ module Secrets
18
18
  # array of yaml file paths
19
19
  def initialize(target)
20
20
  @attributes = ::YAML.load_file(target)
21
+ rescue => e
22
+ raise "Error reading Inspec attributes: #{e}"
21
23
  end
22
24
  end
23
25
  end
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '1.17.0'.freeze
7
+ VERSION = '1.18.0'.freeze
8
8
  end
data/lib/resources/gem.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  # author: Christoph Hartmann
3
3
  # author: Dominik Richter
4
+ # author: Joe Nuspl
4
5
 
5
6
  module Inspec::Resources
6
7
  class GemPackage < Inspec.resource(1)
@@ -13,16 +14,32 @@ module Inspec::Resources
13
14
  end
14
15
  "
15
16
 
16
- def initialize(package_name)
17
+ attr_reader :gem_binary
18
+
19
+ def initialize(package_name, gem_binary = nil)
17
20
  @package_name = package_name
21
+ @gem_binary = case gem_binary
22
+ when nil
23
+ 'gem'
24
+ when :chef
25
+ if inspec.os.windows?
26
+ 'c:\opscode\chef\embedded\bin\gem'
27
+ else
28
+ '/opt/chef/embedded/bin/gem'
29
+ end
30
+ when :chef_server
31
+ '/opt/opscode/embedded/bin/gem'
32
+ else
33
+ gem_binary
34
+ end
18
35
  end
19
36
 
20
37
  def info
21
38
  return @info if defined?(@info)
22
39
 
23
- cmd = inspec.command("gem list --local -a -q \^#{@package_name}\$")
40
+ cmd = inspec.command("#{@gem_binary} list --local -a -q \^#{@package_name}\$")
24
41
  @info = {
25
- installed: cmd.exit_status == 0,
42
+ installed: cmd.exit_status.zero?,
26
43
  type: 'gem',
27
44
  }
28
45
  return @info unless @info[:installed]
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+ # author: Richard Nixon
3
+ # author: Christoph Hartmann
4
+
5
+ require 'openssl'
6
+ require 'hashie/mash'
7
+
8
+ module Inspec::Resources
9
+ class RsaKey < Inspec.resource(1)
10
+ name 'key_rsa'
11
+ desc 'public/private RSA key pair test'
12
+ example "
13
+ describe rsa_key('/etc/pki/www.mywebsite.com.key') do
14
+ its('public_key') { should match /BEGIN RSA PUBLIC KEY/ }
15
+ end
16
+
17
+ describe rsa_key('/etc/pki/www.mywebsite.com.key', 'passphrase') do
18
+ it { should be_private }
19
+ it { should be_public }
20
+ end
21
+ "
22
+
23
+ def initialize(keypath, passphrase = nil)
24
+ @key_path = keypath
25
+ @key_file = inspec.file(@key_path)
26
+ @key = nil
27
+ @passphrase = passphrase
28
+
29
+ return skip_resource "Unable to find key file #{@key_path}" unless @key_file.exist?
30
+
31
+ begin
32
+ @key = OpenSSL::PKey.read(@key_file.content, @passphrase)
33
+ rescue OpenSSL::PKey::RSAError => _
34
+ return skip_resource "Unable to load key file #{@key_path}"
35
+ end
36
+ end
37
+
38
+ def public?
39
+ return if @key.nil?
40
+ @key.public?
41
+ end
42
+
43
+ def public_key
44
+ return if @key.nil?
45
+ @key.public_key.to_s
46
+ end
47
+
48
+ def private?
49
+ return if @key.nil?
50
+ @key.private?
51
+ end
52
+
53
+ def private_key
54
+ return if @key.nil?
55
+ @key.to_s
56
+ end
57
+
58
+ def key_length
59
+ return if @key.nil?
60
+ @key.public_key.n.num_bytes * 8
61
+ end
62
+
63
+ def to_s
64
+ "rsa_key #{@key_path}"
65
+ end
66
+ end
67
+ end
@@ -294,11 +294,24 @@ module Inspec::Resources
294
294
  # uses a double-colon for short-hand notation.
295
295
  ip6addr += ':' if ip6addr =~ /\w:$/
296
296
 
297
+ begin
298
+ ip_parser = IPAddr.new(ip6addr)
299
+ rescue IPAddr::InvalidAddressError
300
+ # This IP is not parsable. There appears to be a bug in netstat
301
+ # output that truncates link-local IP addresses:
302
+ # example: udp6 0 0 fe80::42:acff:fe11::123 :::* 0 54550 3335/ntpd
303
+ # actual link address: inet6 fe80::42:acff:fe11:5/64 scope link
304
+ #
305
+ # in this example, the "5" is truncated making the netstat output
306
+ # an invalid IP address.
307
+ return [nil, nil]
308
+ end
309
+
297
310
  # Check to see if this is a IPv4 address in a tcp6/udp6 line.
298
311
  # If so, don't put brackets around the IP or URI won't know how
299
312
  # to properly handle it.
300
313
  # example: tcp6 0 0 127.0.0.1:8005 :::* LISTEN
301
- if IPAddr.new(ip6addr).ipv4?
314
+ if ip_parser.ipv4?
302
315
  ip_addr = URI("addr://#{ip6addr}:#{ip6[2]}")
303
316
  host = ip_addr.host
304
317
  else
@@ -333,6 +346,7 @@ module Inspec::Resources
333
346
 
334
347
  # extract host and port information
335
348
  host, port = parse_net_address(parsed[4], protocol)
349
+ return {} if host.nil?
336
350
 
337
351
  # extract PID
338
352
  process = parsed[9].split('/')
@@ -450,13 +464,17 @@ module Inspec::Resources
450
464
  # the last . to :
451
465
  local_addr[local_addr.rindex('.')] = ':'
452
466
  host, port = parse_net_address(local_addr, protocol)
453
- {
454
- 'port' => port,
455
- 'address' => host,
456
- 'protocol' => protocol,
457
- }
467
+ if host.nil?
468
+ nil
469
+ else
470
+ {
471
+ 'port' => port,
472
+ 'address' => host,
473
+ 'protocol' => protocol,
474
+ }
475
+ end
458
476
  }
459
- ports
477
+ ports.compact
460
478
  end
461
479
  end
462
480
 
@@ -492,6 +510,7 @@ module Inspec::Resources
492
510
  local_addr[local_addr.rindex('.')] = ':'
493
511
  # extract host and port information
494
512
  host, port = parse_net_address(local_addr, protocol)
513
+ return {} if host.nil?
495
514
  # map data
496
515
  {
497
516
  'port' => port,
@@ -0,0 +1,143 @@
1
+ # encoding: utf-8
2
+ # author: Richard Nixon
3
+ # author: Christoph Hartmann
4
+
5
+ require 'openssl'
6
+ require 'hashie/mash'
7
+
8
+ module Inspec::Resources
9
+ class X509CertificateResource < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
10
+ name 'x509_certificate'
11
+ desc 'Used to test x.509 certificates'
12
+ example "
13
+ describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
14
+ its('subject') { should match /CN=My Website/ }
15
+ its('validity_in_days') { should be > 30 }
16
+ end
17
+
18
+ describe x509_certificate('trials/x509/cert.pem') do
19
+ it { should be_certificate }
20
+ it { should be_valid }
21
+ its('fingerprint') { should eq '62b137bdf427e7273dc2e487877b3033e4c8ce17' }
22
+ its('signature_algorithm') { should eq 'sha1WithRSAEncryption' }
23
+ its('validity_in_days') { should_not be < 100 }
24
+ its('validity_in_days') { should be >= 100 }
25
+ its('subject_dn') { should eq '/C=DE/ST=Berlin/L=Berlin/O=InSpec/OU=Chef Software, Inc/CN=inspec.io/emailAddress=support@chef.io' }
26
+ its('subject.C') { should eq 'DE' }
27
+ its('subject.emailAddress') { should_not be_empty }
28
+ its('subject.emailAddress') { should eq 'support@chef.io' }
29
+ its('issuer_dn') { should eq '/C=DE/ST=Berlin/L=Berlin/O=InSpec/OU=Chef Software, Inc/CN=inspec.io/emailAddress=support@chef.io' }
30
+ its('key_length') { should be >= 2048 }
31
+ its('extensions.subjectKeyIdentifier') { should cmp 'A5:16:0B:12:F4:48:0F:06:6C:32:29:67:98:12:DF:3D:0D:75:9D:5C' }
32
+ end
33
+ "
34
+
35
+ # @see https://tools.ietf.org/html/rfc5280#page-23
36
+ def initialize(filename)
37
+ @certpath = filename
38
+ @issuer = nil
39
+ @parsed_subject = nil
40
+ @parsed_issuer = nil
41
+ @extensions = nil
42
+
43
+ file = inspec.file(@certpath)
44
+ return skip_resource "Unable to find certificate file #{@certpath}" unless file.exist?
45
+
46
+ begin
47
+ @cert = OpenSSL::X509::Certificate.new file.content
48
+ rescue OpenSSL::X509::CertificateError
49
+ @cert = nil
50
+ return skip_resource "Unable to load certificate #{@certpath}"
51
+ end
52
+ end
53
+
54
+ # Forward these methods directly to OpenSSL::X509::Certificate instance
55
+ %w{version not_before not_after signature_algorithm public_key }.each do |m|
56
+ define_method m.to_sym do |*args|
57
+ @cert.method(m.to_sym).call(*args)
58
+ end
59
+ end
60
+
61
+ def certificate?
62
+ !@cert.nil?
63
+ end
64
+
65
+ def fingerprint
66
+ return if @cert.nil?
67
+ OpenSSL::Digest::SHA1.new(@cert.to_der).to_s
68
+ end
69
+
70
+ def serial
71
+ return if @cert.nil?
72
+ @cert.serial.to_i
73
+ end
74
+
75
+ def subject_dn
76
+ return if @cert.nil?
77
+ @cert.subject.to_s
78
+ end
79
+
80
+ def subject
81
+ return if @cert.nil?
82
+ # Return cached subject if we have already parsed it
83
+ return @parsed_subject if @parsed_subject
84
+ # Use a Mash to make it easier to access hash elements in "its('subject') {should ...}"
85
+ @parsed_subject = Hashie::Mash.new(Hash[@cert.subject.to_a.map { |k, v, _| [k, v] }])
86
+ end
87
+
88
+ def issuer_dn
89
+ return if @cert.nil?
90
+ @cert.issuer.to_s
91
+ end
92
+
93
+ def issuer
94
+ return if @cert.nil?
95
+ # Return cached subject if we have already parsed it
96
+ return @parsed_issuer if @parsed_issuer
97
+ # Use a Mash to make it easier to access hash elements in "its('issuer') {should ...}"
98
+ @parsed_issuer = Hashie::Mash.new(Hash[@cert.issuer.to_a.map { |k, v, _| [k, v] }])
99
+ end
100
+
101
+ def key_length
102
+ return if @cert.nil?
103
+ @cert.public_key.n.num_bytes * 8
104
+ end
105
+
106
+ def validity_in_days
107
+ (not_after - Time.now.utc) / 86400
108
+ end
109
+
110
+ def valid?
111
+ now = Time.now
112
+ certificate? && (now >= not_before && now <= not_after)
113
+ end
114
+
115
+ def extensions
116
+ # Return cached Mash if we already parsed the certificate extensions
117
+ return @extensions if @extensions
118
+ # Return the exception class if we failed to instantiate a Cert from file
119
+ return @cert unless @cert.respond_to? :extensions
120
+ # Use a Mash to make it easier to access hash elements in "its('entensions') {should ...}"
121
+ @extensions = Hashie::Mash.new({})
122
+ # Make sure standard extensions exist so we don't get nil for nil:NilClass
123
+ # when the user tests for extensions which aren't present
124
+ %w{
125
+ keyUsage extendedKeyUsage basicConstraints subjectKeyIdentifier
126
+ authorityKeyIdentifier subjectAltName issuerAltName authorityInfoAccess
127
+ crlDistributionPoints issuingDistributionPoint certificatePolicies
128
+ policyConstraints nameConstraints noCheck tlsfeature nsComment
129
+ }.each { |extension| @extensions[extension] ||= [] }
130
+ # Now parse the extensions into the Mash
131
+ extension_array = @cert.extensions.map(&:to_s)
132
+ extension_array.each do |extension|
133
+ kv = extension.split(/ *= */, 2)
134
+ @extensions[kv.first] = kv.last.split(/ *, */)
135
+ end
136
+ @extensions
137
+ end
138
+
139
+ def to_s
140
+ "x509_certificate #{@certpath}"
141
+ end
142
+ end
143
+ end
data/lib/resources/yum.rb CHANGED
@@ -2,8 +2,6 @@
2
2
  # author: Christoph Hartmann
3
3
  # author: Dominik Richter
4
4
 
5
- require 'resources/file'
6
-
7
5
  # Usage:
8
6
  # describe yum do
9
7
  # its('repos') { should exist }
@@ -22,6 +20,7 @@ require 'resources/file'
22
20
  # describe yum.repo('epel') do
23
21
  # it { should exist }
24
22
  # it { should be_enabled }
23
+ # its('baseurl') { should include 'mycompany.biz' }
25
24
  # end
26
25
  #
27
26
  # deprecated:
@@ -33,7 +32,7 @@ require 'resources/file'
33
32
  module Inspec::Resources
34
33
  class Yum < Inspec.resource(1)
35
34
  name 'yum'
36
- desc 'Use the yum InSpec audit resource to test packages in the Yum repository.'
35
+ desc 'Use the yum InSpec audit resource to test the configuration of Yum repositories.'
37
36
  example "
38
37
  describe yum.repo('name') do
39
38
  it { should exist }
@@ -121,22 +120,37 @@ module Inspec::Resources
121
120
  def info
122
121
  return @cache if defined?(@cache)
123
122
  selection = @yum.repositories.select { |e| e['id'] == @reponame || shortname(e['id']) == @reponame }
124
- @cache = selection[0] if !selection.nil? && selection.length == 1
123
+ @cache = selection.empty? ? {} : selection.first
125
124
  @cache
126
125
  end
127
126
 
128
127
  def exist?
129
- !info.nil?
128
+ !info.empty?
130
129
  end
131
130
 
132
131
  def enabled?
133
- repo = info
134
- return false if repo.nil?
132
+ return false unless exist?
135
133
  info['status'] == 'enabled'
136
134
  end
137
135
 
136
+ # provide a method for each of the repo metadata items we know about
137
+ [
138
+ :baseurl,
139
+ :expire,
140
+ :filename,
141
+ :mirrors,
142
+ :pkgs,
143
+ :size,
144
+ :status,
145
+ :updated,
146
+ ].each do |key|
147
+ define_method key do
148
+ info[key.to_s]
149
+ end
150
+ end
151
+
138
152
  def to_s
139
- "YumRepo #{shortname(info['id'])}"
153
+ "YumRepo #{@reponame}"
140
154
  end
141
155
  end
142
156
 
@@ -73,7 +73,13 @@ class SimpleConfig
73
73
  m = opts[:group_re].match(line)
74
74
  return nil if m.nil?
75
75
  @groups.push(m[1])
76
- @vals = @params[m[1]] = {}
76
+
77
+ # We use a Hashie::Mash to provide method syntax for retrieving
78
+ # values. For configs that have keys whose values may be hashes
79
+ # (such as a mysql config that has [sections]), providing
80
+ # a hash whose values are accessible via methods provide
81
+ # a better user experience with `its` blocks.
82
+ @vals = @params[m[1]] = Hashie::Mash.new
77
83
  end
78
84
 
79
85
  def parse_implicit_assignment_line(line, opts)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.0
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-21 00:00:00.000000000 Z
11
+ date: 2017-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train
@@ -246,6 +246,20 @@ dependencies:
246
246
  - - "~>"
247
247
  - !ruby/object:Gem::Version
248
248
  version: '0.1'
249
+ - !ruby/object:Gem::Dependency
250
+ name: addressable
251
+ requirement: !ruby/object:Gem::Requirement
252
+ requirements:
253
+ - - "~>"
254
+ - !ruby/object:Gem::Version
255
+ version: '2.5'
256
+ type: :runtime
257
+ prerelease: false
258
+ version_requirements: !ruby/object:Gem::Requirement
259
+ requirements:
260
+ - - "~>"
261
+ - !ruby/object:Gem::Version
262
+ version: '2.5'
249
263
  description: InSpec provides a framework for creating end-to-end infrastructure tests.
250
264
  You can use it for integration or even compliance testing. Create fully portable
251
265
  test profiles and use them in your workflow to ensure stability and security. Integrate
@@ -271,11 +285,13 @@ files:
271
285
  - docs/cli.md
272
286
  - docs/dsl_inspec.md
273
287
  - docs/dsl_resource.md
288
+ - docs/habitat.md
274
289
  - docs/inspec_and_friends.md
275
290
  - docs/matchers.md
276
291
  - docs/migration.md
277
292
  - docs/plugin_kitchen_inspec.html.md
278
293
  - docs/profiles.md
294
+ - docs/resources.md
279
295
  - docs/resources/apache_conf.md.erb
280
296
  - docs/resources/apt.md.erb
281
297
  - docs/resources/audit_policy.md.erb
@@ -306,6 +322,7 @@ files:
306
322
  - docs/resources/json.md.erb
307
323
  - docs/resources/kernel_module.md.erb
308
324
  - docs/resources/kernel_parameter.md.erb
325
+ - docs/resources/key_rsa.md
309
326
  - docs/resources/launchd_service.md.erb
310
327
  - docs/resources/limits_conf.md.erb
311
328
  - docs/resources/login_def.md.erb
@@ -343,6 +360,7 @@ files:
343
360
  - docs/resources/windows_feature.md.erb
344
361
  - docs/resources/windows_task.md.erb
345
362
  - docs/resources/wmi.md.erb
363
+ - docs/resources/x509_certificate.md
346
364
  - docs/resources/xinetd_conf.md.erb
347
365
  - docs/resources/yaml.md.erb
348
366
  - docs/resources/yum.md.erb
@@ -469,6 +487,7 @@ files:
469
487
  - lib/inspec/objects/list.rb
470
488
  - lib/inspec/objects/or_test.rb
471
489
  - lib/inspec/objects/ruby_helper.rb
490
+ - lib/inspec/objects/tag.rb
472
491
  - lib/inspec/objects/test.rb
473
492
  - lib/inspec/objects/value.rb
474
493
  - lib/inspec/plugins.rb
@@ -480,6 +499,7 @@ files:
480
499
  - lib/inspec/polyfill.rb
481
500
  - lib/inspec/profile.rb
482
501
  - lib/inspec/profile_context.rb
502
+ - lib/inspec/profile_vendor.rb
483
503
  - lib/inspec/require_loader.rb
484
504
  - lib/inspec/resource.rb
485
505
  - lib/inspec/rspec_json_formatter.rb
@@ -487,6 +507,7 @@ files:
487
507
  - lib/inspec/runner.rb
488
508
  - lib/inspec/runner_mock.rb
489
509
  - lib/inspec/runner_rspec.rb
510
+ - lib/inspec/schema.rb
490
511
  - lib/inspec/secrets.rb
491
512
  - lib/inspec/secrets/yaml.rb
492
513
  - lib/inspec/shell.rb
@@ -522,6 +543,7 @@ files:
522
543
  - lib/resources/json.rb
523
544
  - lib/resources/kernel_module.rb
524
545
  - lib/resources/kernel_parameter.rb
546
+ - lib/resources/key_rsa.rb
525
547
  - lib/resources/limits_conf.rb
526
548
  - lib/resources/login_def.rb
527
549
  - lib/resources/mount.rb
@@ -557,6 +579,7 @@ files:
557
579
  - lib/resources/windows_feature.rb
558
580
  - lib/resources/windows_task.rb
559
581
  - lib/resources/wmi.rb
582
+ - lib/resources/x509_certificate.rb
560
583
  - lib/resources/xinetd.rb
561
584
  - lib/resources/yaml.rb
562
585
  - lib/resources/yum.rb
@@ -589,7 +612,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
589
612
  requirements:
590
613
  - - ">="
591
614
  - !ruby/object:Gem::Version
592
- version: '0'
615
+ version: '2.1'
593
616
  required_rubygems_version: !ruby/object:Gem::Requirement
594
617
  requirements:
595
618
  - - ">="