inspec 1.29.0 → 1.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/bin/inspec +1 -1
  4. data/docs/profiles.md +14 -5
  5. data/docs/resources/iptables.md.erb +12 -5
  6. data/docs/resources/mssql_session.md.erb +11 -28
  7. data/docs/resources/mysql_session.md.erb +12 -0
  8. data/docs/resources/oracledb_session.md.erb +10 -28
  9. data/docs/resources/package.md.erb +6 -0
  10. data/docs/resources/postgres_conf.md.erb +2 -0
  11. data/examples/inheritance/controls/example.rb +0 -1
  12. data/examples/meta-profile/controls/example.rb +0 -1
  13. data/examples/profile/controls/example.rb +0 -1
  14. data/examples/profile/controls/gordon.rb +0 -1
  15. data/inspec.gemspec +1 -0
  16. data/lib/bundles/inspec-compliance/api.rb +12 -10
  17. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +0 -1
  18. data/lib/inspec.rb +0 -1
  19. data/lib/inspec/backend.rb +0 -1
  20. data/lib/inspec/cli.rb +1 -1
  21. data/lib/inspec/metadata.rb +1 -1
  22. data/lib/inspec/polyfill.rb +0 -1
  23. data/lib/inspec/profile.rb +1 -1
  24. data/lib/inspec/resource.rb +1 -1
  25. data/lib/inspec/rule.rb +0 -1
  26. data/lib/inspec/runner.rb +0 -1
  27. data/lib/inspec/version.rb +1 -1
  28. data/lib/matchers/matchers.rb +0 -1
  29. data/lib/resources/apache.rb +0 -1
  30. data/lib/resources/apache_conf.rb +0 -1
  31. data/lib/resources/audit_policy.rb +0 -1
  32. data/lib/resources/auditd_conf.rb +0 -1
  33. data/lib/resources/auditd_rules.rb +0 -1
  34. data/lib/resources/command.rb +0 -1
  35. data/lib/resources/directory.rb +7 -3
  36. data/lib/resources/docker.rb +30 -3
  37. data/lib/resources/etc_group.rb +0 -1
  38. data/lib/resources/file.rb +0 -1
  39. data/lib/resources/grub_conf.rb +0 -1
  40. data/lib/resources/inetd_conf.rb +0 -1
  41. data/lib/resources/kernel_module.rb +0 -1
  42. data/lib/resources/kernel_parameter.rb +0 -1
  43. data/lib/resources/limits_conf.rb +0 -1
  44. data/lib/resources/login_def.rb +0 -1
  45. data/lib/resources/mssql_session.rb +62 -14
  46. data/lib/resources/mysql.rb +0 -1
  47. data/lib/resources/mysql_conf.rb +0 -1
  48. data/lib/resources/mysql_session.rb +15 -6
  49. data/lib/resources/nginx_conf.rb +95 -0
  50. data/lib/resources/ntp_conf.rb +0 -1
  51. data/lib/resources/oracledb_session.rb +109 -12
  52. data/lib/resources/os_env.rb +0 -1
  53. data/lib/resources/package.rb +47 -3
  54. data/lib/resources/packages.rb +0 -1
  55. data/lib/resources/parse_config.rb +0 -1
  56. data/lib/resources/passwd.rb +0 -1
  57. data/lib/resources/postgres.rb +9 -5
  58. data/lib/resources/postgres_conf.rb +12 -3
  59. data/lib/resources/postgres_session.rb +0 -1
  60. data/lib/resources/powershell.rb +0 -1
  61. data/lib/resources/processes.rb +0 -1
  62. data/lib/resources/registry_key.rb +0 -1
  63. data/lib/resources/service.rb +1 -1
  64. data/lib/resources/ssh_conf.rb +0 -1
  65. data/lib/resources/ssl.rb +0 -1
  66. data/lib/utils/database_helpers.rb +77 -0
  67. data/lib/utils/filter_array.rb +0 -1
  68. data/lib/utils/find_files.rb +0 -1
  69. data/lib/utils/nginx_parser.rb +4 -2
  70. data/lib/utils/simpleconfig.rb +0 -1
  71. metadata +18 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a517bb883e12e171576ca6d64da4005fc89eb117
4
- data.tar.gz: '04879fa4af63f6e2274b2965d1167bc5971c9b59'
3
+ metadata.gz: 745f9c48fb9d28298944aef579f3d8d24255acf5
4
+ data.tar.gz: 0e69074ae2ba59b6fd09c6f09ddb784a97fd4fee
5
5
  SHA512:
6
- metadata.gz: f97bd24162ff420b55122fdfe53abecdb06085d2fce6dddbea87eb47f140e30785e1d607ddd081dcf45a0ed3ff11112a9a48eca02ed2c22d2b6023492cc9294a
7
- data.tar.gz: 20b0a7c4dc401b382d99c9bbb6c691bf4ffbbe147dda7c1d2e24076a851ed746b3bbf89539b12a1329cec87f8fe04c2e8f699fb736d31478b2bf53d92f736b4f
6
+ metadata.gz: '043509ac03b2be5d7025476cbe33ffff43cf8cc6b5176b4f2519317ef8c3190302b3edc2d870a218c10192699b927b4e6027806fdd3ff677bdcbbf51d605d051'
7
+ data.tar.gz: '009d85fe8d9e0f0e778286b229e1ef4ad70ee18e023bfd638143cf2a68db068272fc862bc17161151842e3e0fd7b16cb516c0b9079a73f13ad327baf1e171aa9'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Change Log
2
2
 
3
+ ## [v1.30.0](https://github.com/chef/inspec/tree/v1.30.0) (2017-06-29)
4
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.29.0...v1.30.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Ensure docker resource works with docker 1.13+ [\#1966](https://github.com/chef/inspec/pull/1966) ([chris-rock](https://github.com/chris-rock))
9
+ - Add `rpm\_dbpath` support to the package resource [\#1960](https://github.com/chef/inspec/pull/1960) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
10
+ - Allow mysql resource to accept socket path [\#1933](https://github.com/chef/inspec/pull/1933) ([rshade](https://github.com/rshade))
11
+ - add nginx\_conf resource [\#1889](https://github.com/chef/inspec/pull/1889) ([arlimus](https://github.com/arlimus))
12
+ - oracle\_session and mssql\_session improvement [\#1857](https://github.com/chef/inspec/pull/1857) ([chris-rock](https://github.com/chris-rock))
13
+
14
+ **Fixed bugs:**
15
+
16
+ - Fix socket handling in mysql resource [\#1971](https://github.com/chef/inspec/pull/1971) ([chris-rock](https://github.com/chris-rock))
17
+ - Fix typo in the version\_from\_dir method in postgres\_session resource [\#1962](https://github.com/chef/inspec/pull/1962) ([aaronlippold](https://github.com/aaronlippold))
18
+ - make postgres resource working in mock runner \(for inspec check\) [\#1961](https://github.com/chef/inspec/pull/1961) ([chris-rock](https://github.com/chris-rock))
19
+ - Fix directory resource output and exists check [\#1950](https://github.com/chef/inspec/pull/1950) ([adamleff](https://github.com/adamleff))
20
+ - Fix postgres\_conf ability to test parameters that have a dot in them [\#1938](https://github.com/chef/inspec/pull/1938) ([aaronlippold](https://github.com/aaronlippold))
21
+
3
22
  ## [v1.29.0](https://github.com/chef/inspec/tree/v1.29.0) (2017-06-22)
4
23
  [Full Changelog](https://github.com/chef/inspec/compare/v1.28.1...v1.29.0)
5
24
 
data/bin/inspec CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # encoding: utf-8
3
- # Copyright 2015 Dominik Richter. All rights reserved.
3
+ # Copyright 2015 Dominik Richter
4
4
  # author: Dominik Richter
5
5
  # author: Christoph Hartmann
6
6
 
data/docs/profiles.md CHANGED
@@ -282,15 +282,24 @@ Attributes may be used in profiles to define secrets, such as user names and pas
282
282
 
283
283
  For example, a control:
284
284
 
285
+ # define these attributes on the top-level of your file and re-use them across all tests!
285
286
  val_user = attribute('user', default: 'alice', description: 'An identification for the user')
286
287
  val_password = attribute('password', description: 'A value for the password')
287
288
 
288
- describe val_user do
289
- it { should eq 'bob' }
290
- end
289
+ control 'system-users' do
290
+ impact 0.8
291
+ desc '
292
+ This test assures that the user "Bob" has a user installed on the system, along with a
293
+ specified password.
294
+ '
295
+
296
+ describe val_user do
297
+ it { should eq 'bob' }
298
+ end
291
299
 
292
- describe val_password do
293
- it { should eq 'secret' }
300
+ describe val_password do
301
+ it { should eq 'secret' }
302
+ end
294
303
  end
295
304
 
296
305
  And a Yaml file named `profile-attribute.yml`:
@@ -20,8 +20,7 @@ where
20
20
  * `rule:'name'` is the name of a rule that matches a set of packets
21
21
  * `table:'name'` is the packet matching table against which the test is run
22
22
  * `chain: 'name'` is the name of a user-defined chain or one of `ACCEPT`, `DROP`, `QUEUE`, or `RETURN`
23
- * `have_rule('RULE')` tests that rule in the iptables file
24
-
23
+ * `have_rule('RULE')` tests that rule in the iptables list. This must match the entire line taken from `iptables -S CHAIN`.
25
24
 
26
25
  ## Matchers
27
26
 
@@ -57,14 +56,22 @@ The `have_rule` matcher tests the named rule against the information in the `ipt
57
56
 
58
57
  The following examples show how to use this InSpec audit resource.
59
58
 
60
- ### Test if the IP table allows a packet through
59
+ ### Test if the INPUT chain is in default ACCEPT mode
61
60
 
62
61
  describe iptables do
63
62
  it { should have_rule('-P INPUT ACCEPT') }
64
63
  end
65
64
 
66
- ### Test if the IP table allows a packet through, for a specific table and chain
65
+ ### Test if the INPUT chain from the mangle table is in ACCEPT mode
67
66
 
68
- describe iptables(table:'mangle', chain: 'input') do
67
+ describe iptables(table:'mangle', chain: 'INPUT') do
69
68
  it { should have_rule('-P INPUT ACCEPT') }
70
69
  end
70
+
71
+ ### Test if there is a rule allowing Postgres (5432/TCP) traffic
72
+
73
+ describe iptables do
74
+ it { should have_rule('-A INPUT -p tcp -m tcp -m multiport --dports 5432 -m comment --comment "postgres" -j ACCEPT') }
75
+ end
76
+
77
+ Note that the rule specification must exactly match what's in the output of `iptables -S INPUT`, which will depend on how you've built your rules.
@@ -10,24 +10,20 @@ Use the `mssql_session` InSpec audit resource to test SQL commands run against a
10
10
 
11
11
  A `mssql_session` resource block declares the username and password to use for the session, and then the command to be run:
12
12
 
13
- describe mssql_session(user: 'username', pass: 'password').query('QUERY') do
14
- its('output') { should eq('') }
13
+ describe mssql_session(user: 'username', password: 'password').query('QUERY').row(0).column('result') do
14
+ its('value') { should eq('') }
15
15
  end
16
16
 
17
17
  where
18
18
 
19
19
  * `mssql_session` declares a username and password with permission to run the query. Omitting the username or password parameters results in the use of Windows authentication as the user InSpec is executing as. You may also optionally pass a host and instance name. If omitted, they will default to host: localhost and the default instance.
20
20
  * `query('QUERY')` contains the query to be run
21
- * `its('output') { should eq('') }` compares the results of the query against the expected result in the test
21
+ * `its('value') { should eq('') }` compares the results of the query against the expected result in the test
22
22
 
23
23
  ## Matchers
24
24
 
25
25
  This InSpec audit resource has the following matchers:
26
26
 
27
- ### be
28
-
29
- <%= partial "/shared/matcher_be" %>
30
-
31
27
  ### cmp
32
28
 
33
29
  <%= partial "/shared/matcher_cmp" %>
@@ -36,19 +32,6 @@ This InSpec audit resource has the following matchers:
36
32
 
37
33
  <%= partial "/shared/matcher_eq" %>
38
34
 
39
- ### include
40
-
41
- <%= partial "/shared/matcher_include" %>
42
-
43
- ### match
44
-
45
- <%= partial "/shared/matcher_match" %>
46
-
47
- ### output
48
-
49
- The `output` matcher tests the results of the query:
50
-
51
- its('output') { should eq(/^0/) }
52
35
 
53
36
  ## Examples
54
37
 
@@ -56,24 +39,24 @@ The following examples show how to use this InSpec audit resource.
56
39
 
57
40
  ### Test for matching databases
58
41
 
59
- sql = mssql_session(user: 'my_user', pass: 'password')
42
+ sql = mssql_session(user: 'my_user', password: 'password')
60
43
 
61
- describe sql.query('show databases like \'test\';') do
62
- its('stdout') { should_not match(/test/) }
44
+ describe sql.query("SELECT SERVERPROPERTY('ProductVersion') as result").row(0).column('result') do
45
+ its("value") { should cmp > '12.00.4457' }
63
46
  end
64
47
 
65
48
  ### Test using Windows authentication
66
49
 
67
50
  sql = mssql_session
68
51
 
69
- describe sql.query('show databases like \'test\';') do
70
- its('stdout') { should_not match(/test/) }
52
+ describe sql.query("SELECT SERVERPROPERTY('ProductVersion') as result").row(0).column('result') do
53
+ its("value") { should cmp > '12.00.4457' }
71
54
  end
72
55
 
73
56
  ### Test a specific host and instance
74
57
 
75
- sql = mssql_session(user: 'my_user', pass: 'password', host: 'mssqlserver', instance: 'foo')
58
+ sql = mssql_session(user: 'my_user', password: 'password', host: 'mssqlserver', instance: 'foo')
76
59
 
77
- describe sql.query('show databases like \'test\';') do
78
- its('stdout') { should_not match(/test/) }
60
+ describe sql.query("SELECT SERVERPROPERTY('ProductVersion') as result").row(0).column('result') do
61
+ its("value") { should cmp > '12.00.4457' }
79
62
  end
@@ -61,3 +61,15 @@ The following examples show how to use this InSpec audit resource.
61
61
  describe sql.query('show databases like \'test\';') do
62
62
  its('stdout') { should_not match(/test/) }
63
63
  end
64
+
65
+ ### Alternate Connection: Different Host
66
+
67
+ sql = mysql_session('my_user','password','db.example.com')
68
+
69
+ ### Alternate Connection: Different Port
70
+
71
+ sql = mysql_seesion('my_user','password','localhost',3307)
72
+
73
+ ### Alternate Connection: Using a socket
74
+
75
+ sql = mysql_session('my_user','password', nil, nil, '/var/lib/mysql-default/mysqld.sock')
@@ -10,24 +10,20 @@ Use the `oracledb_session` InSpec audit resource to test SQL commands run agains
10
10
 
11
11
  A `oracledb_session` resource block declares the username and password to use for the session with an optional service to connect to, and then the command to be run:
12
12
 
13
- describe oracledb_session(user: 'username', pass: 'password').query('QUERY') do
14
- its('output') { should eq('') }
13
+ describe oracledb_session(user: 'username', password: 'password', service: 'ORCL.localdomain').query('QUERY').row(0).column('result') do
14
+ its('value') { should eq('') }
15
15
  end
16
16
 
17
17
  where
18
18
 
19
- * `oracledb_session` declares a username and password with permission to run the query (required), and an optional parameters for host (default: `localhost`), SID (default: `nil`, which uses the default SID, and path to the sqlplus binary (default: `sqlplus`).
19
+ * `oracledb_session` declares a username and password with permission to run the query (required), and an optional parameters for host (default: `localhost`), SID (default: `nil`, which uses the default SID, and path to the sqlplus binary (default: `sqlplus`).
20
20
  * `query('QUERY')` contains the query to be run
21
- * `its('output') { should eq('') }` compares the results of the query against the expected result in the test
21
+ * `its('value') { should eq('') }` compares the results of the query against the expected result in the test
22
22
 
23
23
  ## Matchers
24
24
 
25
25
  This InSpec audit resource has the following matchers:
26
26
 
27
- ### be
28
-
29
- <%= partial "/shared/matcher_be" %>
30
-
31
27
  ### cmp
32
28
 
33
29
  <%= partial "/shared/matcher_cmp" %>
@@ -36,20 +32,6 @@ This InSpec audit resource has the following matchers:
36
32
 
37
33
  <%= partial "/shared/matcher_eq" %>
38
34
 
39
- ### include
40
-
41
- <%= partial "/shared/matcher_include" %>
42
-
43
- ### match
44
-
45
- <%= partial "/shared/matcher_match" %>
46
-
47
- ### output
48
-
49
- The `output` matcher tests the results of the query:
50
-
51
- its('output') { should eq(/^0/) }
52
-
53
35
  ## Examples
54
36
 
55
37
  The following examples show how to use this InSpec audit resource.
@@ -57,15 +39,15 @@ The following examples show how to use this InSpec audit resource.
57
39
  ### Test for matching databases
58
40
 
59
41
  sql = oracledb_session(user: 'my_user', pass: 'password')
60
-
61
- describe sql.query('SELECT NAME FROM v$database;') do
62
- its('stdout') { should_not match(/test/) }
42
+
43
+ describe sql.query('SELECT NAME AS VALUE FROM v$database;').row(0).column('value') do
44
+ its('value') { should cmp 'ORCL' }
63
45
  end
64
46
 
65
47
  ### Test for matching databases with custom host, SID and sqlplus binary location
66
48
 
67
49
  sql = oracledb_session(user: 'my_user', pass: 'password', host: 'oraclehost', sid: 'mysid', sqlplus_bin: '/u01/app/oracle/product/12.1.0/dbhome_1/bin/sqlplus')
68
-
69
- describe sql.query('SELECT NAME FROM v$database;') do
70
- its('stdout') { should_not match(/test/) }
50
+
51
+ describe sql.query('SELECT NAME FROM v$database;').row(0).column('name') do
52
+ its('value') { should cmp 'ORCL' }
71
53
  end
@@ -96,6 +96,12 @@ The following examples show how to use this InSpec audit resource.
96
96
  it { should_not be_running }
97
97
  end
98
98
 
99
+ ### Verify if some_package is installed according to my_rpmdb
100
+
101
+ describe package('some_package', rpm_dbpath: '/var/lib/my_rpmdb') do
102
+ it { should be_installed }
103
+ end
104
+
99
105
  ### Verify if Memcached is installed, enabled, and running
100
106
 
101
107
  Memcached is an in-memory key-value store that helps improve the performance of database-driven websites and can be installed, maintained, and tested using the `memcached` cookbook (maintained by Chef). The following example is from the `memcached` cookbook and shows how to use a combination of the `package`, `service`, and `port` InSpec audit resources to test if Memcached is installed, enabled, and running:
@@ -14,6 +14,7 @@ A `postgres_conf` resource block declares one (or more) settings in the `postgre
14
14
  its('setting') { should eq 'value' }
15
15
  end
16
16
 
17
+
17
18
  where
18
19
 
19
20
  * `'setting'` specifies a setting in the `postgresql.conf` file
@@ -71,6 +72,7 @@ The following examples show how to use this InSpec audit resource.
71
72
  its('log_duration') { should eq 'on' }
72
73
  its('log_hostname') { should eq 'on' }
73
74
  its('log_line_prefix') { should eq '%t %u %d %h' }
75
+ its(['pgaudit.log_parameter']) { should cmp 'on' }
74
76
  end
75
77
 
76
78
  ### Test the port on which PostgreSQL listens
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2016, Chef Software, Inc.
3
- # license: All rights reserved
4
3
 
5
4
  # manipulate controls of `profile`
6
5
  include_controls 'profile' do
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2015, The Authors
3
- # license: All rights reserved
4
3
 
5
4
  # import full profile
6
5
  include_controls 'dev-sec/ssh-baseline'
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2015, Chef Software, Inc.
3
- # license: All rights reserved
4
3
 
5
4
  title '/tmp profile'
6
5
 
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2016, Chef Software, Inc.
3
- # license: All rights reserved
4
3
 
5
4
  title 'Gordon Config Checks'
6
5
 
data/inspec.gemspec CHANGED
@@ -44,4 +44,5 @@ Gem::Specification.new do |spec|
44
44
  spec.add_dependency 'addressable', '~> 2.4'
45
45
  spec.add_dependency 'parslet', '~> 1.5'
46
46
  spec.add_dependency 'semverse'
47
+ spec.add_dependency 'htmlentities'
47
48
  end
@@ -204,26 +204,28 @@ module Compliance
204
204
  end
205
205
 
206
206
  def self.is_automate_server_pre_080?(config)
207
- # Automate versions before 0.8.x may have a "version" key in the config.
208
- # Unless it's a hash that also contains a "version" key, it came from
209
- # an Automate server that is pre-0.8.x.
207
+ # Automate versions before 0.8.x do not have a valid version in the config
210
208
  return false unless config['server_type'] == 'automate'
211
- return true unless config.key?('version')
212
- return true unless config['version'].is_a?(Hash)
213
- config['version']['version'].nil?
209
+ server_version_from_config(config).nil?
214
210
  end
215
211
 
216
212
  def self.is_automate_server_080_and_later?(config)
217
213
  # Automate versions 0.8.x and later will have a "version" key in the config
218
- # that looks like: "version":{"api":"compliance","version":"0.8.24"}
214
+ # that is properly parsed out via server_version_from_config below
219
215
  return false unless config['server_type'] == 'automate'
220
- return false unless config.key?('version')
221
- return false unless config['version'].is_a?(Hash)
222
- !config['version']['version'].nil?
216
+ !server_version_from_config(config).nil?
223
217
  end
224
218
 
225
219
  def self.is_automate_server?(config)
226
220
  config['server_type'] == 'automate'
227
221
  end
222
+
223
+ def self.server_version_from_config(config)
224
+ # Automate versions 0.8.x and later will have a "version" key in the config
225
+ # that looks like: "version":{"api":"compliance","version":"0.8.24"}
226
+ return nil unless config.key?('version')
227
+ return nil unless config['version'].is_a?(Hash)
228
+ config['version']['version']
229
+ end
228
230
  end
229
231
  end
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2015, The Authors
3
- # license: All rights reserved
4
3
 
5
4
  title 'sample section'
6
5
 
data/lib/inspec.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2015, Dominik Richter
3
- # license: All rights reserved
4
3
  # author: Dominik Richter
5
4
  # author: Christoph Hartmann
6
5
 
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2015, Dominik Richter
3
- # license: All rights reserved
4
3
  # author: Dominik Richter
5
4
  # author: Christoph Hartmann
6
5
 
data/lib/inspec/cli.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # encoding: utf-8
3
- # Copyright 2015 Dominik Richter. All rights reserved.
3
+ # Copyright 2015 Dominik Richter
4
4
  # author: Dominik Richter
5
5
  # author: Christoph Hartmann
6
6
 
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- # Copyright 2015 Dominik Richter. All rights reserved.
2
+ # Copyright 2015 Dominik Richter
3
3
  # author: Dominik Richter
4
4
  # author: Christoph Hartmann
5
5
 
@@ -2,7 +2,6 @@
2
2
  # copyright: 2016, Chef Software Inc.
3
3
  # author: Dominik Richter
4
4
  # author: Christoph Hartmann
5
- # license: All rights reserved
6
5
 
7
6
  class Struct
8
7
  unless instance_methods.include? :to_h