inspec 1.26.0 → 1.27.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -32
- data/Rakefile +2 -2
- data/docs/resources/crontab.md.erb +17 -1
- data/docs/resources/http.md.erb +6 -3
- data/docs/resources/processes.md.erb +42 -2
- data/examples/inheritance/inspec.yml +1 -1
- data/examples/meta-profile/inspec.yml +1 -1
- data/examples/profile-attribute/inspec.yml +1 -1
- data/examples/profile/inspec.yml +1 -1
- data/lib/bundles/inspec-compliance/api.rb +8 -7
- data/lib/bundles/inspec-compliance/cli.rb +1 -1
- data/lib/bundles/inspec-init/templates/profile/inspec.yml +1 -1
- data/lib/fetchers/local.rb +4 -1
- data/lib/fetchers/url.rb +23 -6
- data/lib/inspec/dependencies/cache.rb +0 -1
- data/lib/inspec/dependencies/requirement.rb +0 -1
- data/lib/inspec/metadata.rb +8 -2
- data/lib/inspec/plugins/fetcher.rb +0 -1
- data/lib/inspec/profile.rb +3 -3
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +0 -1
- data/lib/resources/command.rb +1 -1
- data/lib/resources/crontab.rb +24 -9
- data/lib/resources/host.rb +1 -1
- data/lib/resources/interface.rb +2 -1
- data/lib/resources/postgres.rb +45 -39
- data/lib/resources/processes.rb +17 -4
- data/lib/utils/find_files.rb +1 -1
- data/lib/utils/nginx_parser.rb +74 -0
- data/lib/utils/spdx.rb +13 -0
- data/lib/utils/spdx.txt +344 -0
- metadata +6 -3
data/lib/inspec/metadata.rb
CHANGED
@@ -7,6 +7,7 @@ require 'logger'
|
|
7
7
|
require 'rubygems/version'
|
8
8
|
require 'rubygems/requirement'
|
9
9
|
require 'semverse'
|
10
|
+
require 'utils/spdx'
|
10
11
|
|
11
12
|
module Inspec
|
12
13
|
# Extract metadata.rb information
|
@@ -102,7 +103,7 @@ module Inspec
|
|
102
103
|
end
|
103
104
|
|
104
105
|
# return all warn and errors
|
105
|
-
def valid
|
106
|
+
def valid # rubocop:disable Metrics/AbcSize
|
106
107
|
errors = []
|
107
108
|
warnings = []
|
108
109
|
|
@@ -116,11 +117,16 @@ module Inspec
|
|
116
117
|
errors.push('Version needs to be in SemVer format')
|
117
118
|
end
|
118
119
|
|
119
|
-
%w{ title summary maintainer copyright }.each do |field|
|
120
|
+
%w{ title summary maintainer copyright license }.each do |field|
|
120
121
|
next unless params[field.to_sym].nil?
|
121
122
|
warnings.push("Missing profile #{field} in #{ref}")
|
122
123
|
end
|
123
124
|
|
125
|
+
# if version is set, ensure it is in SPDX format
|
126
|
+
if !params[:license].nil? && !Spdx.valid_license?(params[:license])
|
127
|
+
warnings.push("License '#{params[:license]}' needs to be in SPDX format. See https://spdx.org/licenses/.")
|
128
|
+
end
|
129
|
+
|
124
130
|
[errors, warnings]
|
125
131
|
end
|
126
132
|
|
data/lib/inspec/profile.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
# author: Christoph Hartmann
|
5
5
|
|
6
6
|
require 'forwardable'
|
7
|
-
require '
|
7
|
+
require 'openssl'
|
8
8
|
require 'inspec/polyfill'
|
9
9
|
require 'inspec/cached_fetcher'
|
10
10
|
require 'inspec/file_provider'
|
@@ -406,7 +406,7 @@ module Inspec
|
|
406
406
|
# get all dependency checksums
|
407
407
|
deps = Hash[locked_dependencies.list.map { |k, v| [k, v.profile.sha256] }]
|
408
408
|
|
409
|
-
res = Digest::SHA256.new
|
409
|
+
res = OpenSSL::Digest::SHA256.new
|
410
410
|
files = source_reader.tests.to_a + source_reader.libraries.to_a +
|
411
411
|
source_reader.data_files.to_a +
|
412
412
|
[['inspec.yml', source_reader.metadata.content]] +
|
@@ -415,7 +415,7 @@ module Inspec
|
|
415
415
|
files.sort { |a, b| a[0] <=> b[0] }
|
416
416
|
.map { |f| res << f[0] << "\0" << f[1] << "\0" }
|
417
417
|
|
418
|
-
res.
|
418
|
+
res.digest.unpack('H*')[0]
|
419
419
|
end
|
420
420
|
|
421
421
|
private
|
data/lib/inspec/version.rb
CHANGED
data/lib/matchers/matchers.rb
CHANGED
@@ -88,7 +88,6 @@ end
|
|
88
88
|
|
89
89
|
RSpec::Matchers.define :contain_duplicates do
|
90
90
|
match do |arr|
|
91
|
-
warn '[DEPRECATION] `contain_duplicates` is deprecated and will be removed in the next major version. See https://github.com/chef/inspec/issues/738 for more details'
|
92
91
|
dup = arr.select { |element| arr.count(element) > 1 }
|
93
92
|
!dup.uniq.empty?
|
94
93
|
end
|
data/lib/resources/command.rb
CHANGED
@@ -50,7 +50,7 @@ module Inspec::Resources
|
|
50
50
|
if inspec.os.linux?
|
51
51
|
res = inspec.backend.run_command("bash -c 'type \"#{@command}\"'")
|
52
52
|
elsif inspec.os.windows?
|
53
|
-
res = inspec.backend.run_command("
|
53
|
+
res = inspec.backend.run_command("Get-Command \"#{@command}\"")
|
54
54
|
elsif inspec.os.unix?
|
55
55
|
res = inspec.backend.run_command("type \"#{@command}\"")
|
56
56
|
else
|
data/lib/resources/crontab.rb
CHANGED
@@ -46,15 +46,30 @@ module Inspec::Resources
|
|
46
46
|
data, = parse_comment_line(l, comment_char: '#', standalone_comments: false)
|
47
47
|
return nil if data.nil? || data.empty?
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
'minute'
|
52
|
-
|
53
|
-
'day'
|
54
|
-
|
55
|
-
'weekday' =>
|
56
|
-
|
57
|
-
|
49
|
+
case data
|
50
|
+
when /@hourly .*/
|
51
|
+
{ 'minute' => '0', 'hour' => '*', 'day' => '*', 'month' => '*', 'weekday' => '*', 'command' => data.split(/\s+/, 2).at(1) }
|
52
|
+
when /@(midnight|daily) .*/
|
53
|
+
{ 'minute' => '0', 'hour' => '0', 'day' => '*', 'month' => '*', 'weekday' => '*', 'command' => data.split(/\s+/, 2).at(1) }
|
54
|
+
when /@weekly .*/
|
55
|
+
{ 'minute' => '0', 'hour' => '0', 'day' => '*', 'month' => '*', 'weekday' => '0', 'command' => data.split(/\s+/, 2).at(1) }
|
56
|
+
when /@monthly ./
|
57
|
+
{ 'minute' => '0', 'hour' => '0', 'day' => '1', 'month' => '*', 'weekday' => '*', 'command' => data.split(/\s+/, 2).at(1) }
|
58
|
+
when /@(annually|yearly) .*/
|
59
|
+
{ 'minute' => '0', 'hour' => '0', 'day' => '1', 'month' => '1', 'weekday' => '*', 'command' => data.split(/\s+/, 2).at(1) }
|
60
|
+
when /@reboot .*/
|
61
|
+
{ 'minute' => '-1', 'hour' => '-1', 'day' => '-1', 'month' => '-1', 'weekday' => '-1', 'command' => data.split(/\s+/, 2).at(1) }
|
62
|
+
else
|
63
|
+
elements = data.split(/\s+/, 6)
|
64
|
+
{
|
65
|
+
'minute' => elements.at(0),
|
66
|
+
'hour' => elements.at(1),
|
67
|
+
'day' => elements.at(2),
|
68
|
+
'month' => elements.at(3),
|
69
|
+
'weekday' => elements.at(4),
|
70
|
+
'command' => elements.at(5),
|
71
|
+
}
|
72
|
+
end
|
58
73
|
end
|
59
74
|
|
60
75
|
def crontab_cmd
|
data/lib/resources/host.rb
CHANGED
@@ -148,7 +148,7 @@ module Inspec::Resources
|
|
148
148
|
def ping(hostname, port = nil, _proto = nil)
|
149
149
|
# ICMP: Test-NetConnection www.microsoft.com
|
150
150
|
# TCP and port: Test-NetConnection -ComputerName www.microsoft.com -RemotePort 80
|
151
|
-
request = "Test-NetConnection -ComputerName #{hostname}"
|
151
|
+
request = "Test-NetConnection -ComputerName #{hostname} -WarningAction SilentlyContinue"
|
152
152
|
request += " -RemotePort #{port}" unless port.nil?
|
153
153
|
request += '| Select-Object -Property ComputerName, TcpTestSucceeded, PingSucceeded | ConvertTo-Json'
|
154
154
|
cmd = inspec.command(request)
|
data/lib/resources/interface.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# author: Christoph Hartmann
|
3
3
|
# author: Dominik Richter
|
4
|
+
# author: Aaron Lippold
|
4
5
|
|
5
6
|
require 'utils/convert'
|
6
7
|
|
@@ -64,7 +65,7 @@ module Inspec::Resources
|
|
64
65
|
class LinuxInterface < InterfaceInfo
|
65
66
|
def interface_info(iface)
|
66
67
|
# will return "[mtu]\n1500\n[type]\n1"
|
67
|
-
cmd = inspec.command("find /sys/class/net/#{iface}/ -
|
68
|
+
cmd = inspec.command("find /sys/class/net/#{iface}/ -maxdepth 1 -type f -exec sh -c 'echo \"[$(basename {})]\"; cat {} || echo -n' \\;")
|
68
69
|
return nil if cmd.exit_status.to_i != 0
|
69
70
|
|
70
71
|
# parse values, we only recieve values, therefore we threat them as keys
|
data/lib/resources/postgres.rb
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
# copyright: 2015, Vulcano Security GmbH
|
3
3
|
# author: Dominik Richter
|
4
4
|
# author: Christoph Hartmann
|
5
|
+
# author: Aaron Lippold
|
5
6
|
# license: All rights reserved
|
6
7
|
|
7
8
|
module Inspec::Resources
|
8
9
|
class Postgres < Inspec.resource(1)
|
9
10
|
name 'postgres'
|
10
11
|
|
11
|
-
attr_reader :service, :data_dir, :conf_dir, :conf_path
|
12
|
+
attr_reader :service, :data_dir, :conf_dir, :conf_path, :version, :cluster
|
12
13
|
def initialize
|
13
14
|
os = inspec.os
|
14
15
|
if os.debian?
|
@@ -18,48 +19,26 @@ module Inspec::Resources
|
|
18
19
|
# Debian allows multiple versions of postgresql to be
|
19
20
|
# installed as well as multiple "clusters" to be configured.
|
20
21
|
#
|
21
|
-
version = version_from_dir('/etc/postgresql')
|
22
|
-
cluster = cluster_from_dir("/etc/postgresql/#{version}")
|
23
|
-
@conf_dir = "/etc/postgresql/#{version}/#{cluster}"
|
24
|
-
@data_dir = "/var/lib/postgresql/#{version}/#{cluster}"
|
25
|
-
elsif os.redhat?
|
26
|
-
#
|
27
|
-
# /var/lib/pgsql/data is the default data directory on RHEL6
|
28
|
-
# and RHEL7. However, PR #824 explicitly added version-based
|
29
|
-
# directories. Thus, we call #version_from_dir unless it looks
|
30
|
-
# like we are using unversioned directories.
|
31
|
-
#
|
32
|
-
# TODO(ssd): This has the potential to be noisy because of the
|
33
|
-
# warning in version_from_dir. We should determine which case
|
34
|
-
# is more common and only warn in the less common case.
|
35
|
-
#
|
36
|
-
version = if inspec.directory('/var/lib/pgsql/data').exist?
|
37
|
-
warn 'Found /var/lib/pgsql/data. Assuming postgresql install uses un-versioned directories.'
|
38
|
-
nil
|
39
|
-
else
|
40
|
-
version_from_dir('/var/lib/pgsql/')
|
41
|
-
end
|
42
|
-
|
43
|
-
@data_dir = File.join('/var/lib/pgsql/', version.to_s, 'data')
|
44
|
-
elsif os[:name] == 'arch'
|
45
|
-
#
|
46
|
-
# https://wiki.archlinux.org/index.php/PostgreSQL
|
47
|
-
#
|
48
|
-
# The archlinux wiki points to /var/lib/postgresql/data as the
|
49
|
-
# main data directory.
|
50
|
-
#
|
51
|
-
@data_dir = '/var/lib/postgres/data'
|
22
|
+
@version = version_from_psql || version_from_dir('/etc/postgresql')
|
23
|
+
@cluster = cluster_from_dir("/etc/postgresql/#{@version}")
|
24
|
+
@conf_dir = "/etc/postgresql/#{@version}/#{@cluster}"
|
25
|
+
@data_dir = "/var/lib/postgresql/#{@version}/#{@cluster}"
|
52
26
|
else
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
27
|
+
@version = version_from_psql
|
28
|
+
if @version.nil?
|
29
|
+
if inspec.directory('/var/lib/pgsql/data').exist?
|
30
|
+
warn 'Unable to determine PostgreSQL version: psql did not return
|
31
|
+
a version number and unversioned data directories were found.'
|
32
|
+
nil
|
33
|
+
else
|
34
|
+
@version = version_from_dir('/var/lib/pgsql/')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@data_dir = locate_data_dir_location_by_version(@version)
|
60
38
|
end
|
61
39
|
|
62
40
|
@service = 'postgresql'
|
41
|
+
@service += "-#{@version}" if @version.to_f >= 9.4
|
63
42
|
@conf_dir ||= @data_dir
|
64
43
|
verify_dirs
|
65
44
|
@conf_path = File.join @conf_dir, 'postgresql.conf'
|
@@ -81,6 +60,33 @@ module Inspec::Resources
|
|
81
60
|
end
|
82
61
|
end
|
83
62
|
|
63
|
+
def version_from_psql
|
64
|
+
return unless inspec.command('psql').exist?
|
65
|
+
inspec.command("psql --version | awk '{ print $NF }' | awk -F. '{ print $1\".\"$2 }'").stdout.strip
|
66
|
+
end
|
67
|
+
|
68
|
+
def locate_data_dir_location_by_version(ver = @version)
|
69
|
+
data_dir_loc = nil
|
70
|
+
dir_list = [
|
71
|
+
"/var/lib/pgsql/#{ver}/data",
|
72
|
+
'/var/lib/pgsql/data',
|
73
|
+
'/var/lib/postgres/data',
|
74
|
+
'/var/lib/postgresql/data',
|
75
|
+
]
|
76
|
+
|
77
|
+
dir_list.each do |dir|
|
78
|
+
data_dir_loc if inspec.directory(dir).exists?
|
79
|
+
break
|
80
|
+
end
|
81
|
+
|
82
|
+
if data_dir_loc.nil?
|
83
|
+
warn 'Unable to find the PostgreSQL data_dir in expected location(s), please
|
84
|
+
execute "psql -t -A -p <port> -h <host> -c "show hba_file";" as the PostgreSQL
|
85
|
+
DBA to find the non-starndard data_dir location.'
|
86
|
+
end
|
87
|
+
data_dir_loc
|
88
|
+
end
|
89
|
+
|
84
90
|
def version_from_dir(dir)
|
85
91
|
dirs = inspec.command("ls -d #{dir}/*/").stdout
|
86
92
|
entries = dirs.lines.count
|
data/lib/resources/processes.rb
CHANGED
@@ -25,15 +25,24 @@ module Inspec::Resources
|
|
25
25
|
@grep = grep
|
26
26
|
# turn into a regexp if it isn't one yet
|
27
27
|
if grep.class == String
|
28
|
-
|
29
|
-
|
28
|
+
# if windows ignore case as we can't make up our minds
|
29
|
+
if inspec.os.windows?
|
30
|
+
grep = '(?i)' + grep
|
31
|
+
else
|
32
|
+
grep = '(/[^/]*)*' + grep unless grep[0] == '/'
|
33
|
+
grep = '^' + grep + '(\s|$)'
|
34
|
+
end
|
35
|
+
grep = Regexp.new(grep)
|
30
36
|
end
|
37
|
+
|
31
38
|
all_cmds = ps_axo
|
32
39
|
@list = all_cmds.find_all do |hm|
|
33
40
|
hm[:command] =~ grep
|
34
41
|
end
|
42
|
+
end
|
35
43
|
|
36
|
-
|
44
|
+
def exists?
|
45
|
+
!@list.empty?
|
37
46
|
end
|
38
47
|
|
39
48
|
def to_s
|
@@ -74,6 +83,10 @@ module Inspec::Resources
|
|
74
83
|
if os.linux?
|
75
84
|
command = 'ps axo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command'
|
76
85
|
regex = /^([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
|
86
|
+
elsif os.windows?
|
87
|
+
command = '$Proc = Get-Process -IncludeUserName | Where-Object {$_.Path -ne $null } | Select-Object PriorityClass,Id,CPU,PM,VirtualMemorySize,NPM,SessionId,Responding,StartTime,TotalProcessorTime,UserName,Path | ConvertTo-Csv -NoTypeInformation;$Proc.Replace("""","").Replace("`r`n","`n")'
|
88
|
+
# Wanted to use /(?:^|,)([^,]*)/; works on rubular.com not sure why here?
|
89
|
+
regex = /^(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+)$/
|
77
90
|
else
|
78
91
|
command = 'ps axo pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user,command'
|
79
92
|
regex = /^\s*([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
|
@@ -95,7 +108,7 @@ module Inspec::Resources
|
|
95
108
|
end.compact
|
96
109
|
lines.map do |m|
|
97
110
|
a = m.to_a[1..-1] # grab all matching groups
|
98
|
-
a.unshift(nil) unless os.linux?
|
111
|
+
a.unshift(nil) unless os.linux? || os.windows?
|
99
112
|
a[1] = a[1].to_i
|
100
113
|
a[4] = a[4].to_i
|
101
114
|
a[5] = a[5].to_i
|
data/lib/utils/find_files.rb
CHANGED
@@ -26,8 +26,8 @@ module FindFiles
|
|
26
26
|
type = TYPES[opts[:type].to_sym] if opts[:type]
|
27
27
|
|
28
28
|
cmd = "find #{path}"
|
29
|
-
cmd += " -maxdepth #{depth.to_i}" if depth.to_i > 0
|
30
29
|
cmd += " -type #{type}" unless type.nil?
|
30
|
+
cmd += " -maxdepth #{depth.to_i}" if depth.to_i > 0
|
31
31
|
|
32
32
|
result = inspec.command(cmd)
|
33
33
|
exit_status = result.exit_status
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
require 'parslet'
|
6
|
+
|
7
|
+
class NginxParser < Parslet::Parser
|
8
|
+
root :outermost
|
9
|
+
# only designed for rabbitmq config files for now:
|
10
|
+
rule(:outermost) { filler? >> exp.repeat }
|
11
|
+
|
12
|
+
rule(:filler?) { one_filler.repeat }
|
13
|
+
rule(:one_filler) { match('\s+') | match["\n"] | comment }
|
14
|
+
rule(:space) { match('\s+') }
|
15
|
+
rule(:comment) { str('#') >> (match["\n\r"].absent? >> any).repeat }
|
16
|
+
|
17
|
+
rule(:exp) {
|
18
|
+
section | assignment
|
19
|
+
}
|
20
|
+
rule(:assignment) {
|
21
|
+
(identifier >> values.maybe.as(:args)).as(:assignment) >> str(';') >> filler?
|
22
|
+
}
|
23
|
+
|
24
|
+
rule(:identifier) {
|
25
|
+
(match('[a-zA-Z]') >> match('[a-zA-Z0-9_]').repeat).as(:identifier) >> space >> space.repeat
|
26
|
+
}
|
27
|
+
|
28
|
+
rule(:value) {
|
29
|
+
((match('[#;{]').absent? >> any) >> (
|
30
|
+
str('\\') >> any | match('[#;{]|\s').absent? >> any
|
31
|
+
).repeat).as(:value) >> space.repeat
|
32
|
+
}
|
33
|
+
rule(:values) {
|
34
|
+
value.repeat >> space.maybe
|
35
|
+
}
|
36
|
+
|
37
|
+
rule(:section) {
|
38
|
+
identifier.as(:section) >> values.maybe.as(:args) >> str('{') >> filler? >> exp.repeat.as(:expressions) >> str('}') >> filler?
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
class NginxTransform < Parslet::Transform
|
43
|
+
Group = Struct.new(:id, :args, :body)
|
44
|
+
Exp = Struct.new(:key, :vals)
|
45
|
+
|
46
|
+
def self.assemble_binary(seq)
|
47
|
+
b = ErlangBitstream.new
|
48
|
+
seq.each { |i| b.add(i) }
|
49
|
+
b.value
|
50
|
+
end
|
51
|
+
|
52
|
+
rule(section: { identifier: simple(:x) }, args: subtree(:y), expressions: subtree(:z)) { Group.new(x.to_s, y, z) }
|
53
|
+
rule(assignment: { identifier: simple(:x), args: subtree(:y) }) { Exp.new(x.to_s, y) }
|
54
|
+
rule(value: simple(:x)) { x.to_s }
|
55
|
+
end
|
56
|
+
|
57
|
+
class NginxConfig
|
58
|
+
def self.parse(content)
|
59
|
+
lex = NginxParser.new.parse(content)
|
60
|
+
tree = NginxTransform.new.apply(lex)
|
61
|
+
gtree = NginxTransform::Group.new(nil, '', tree)
|
62
|
+
read_nginx_group(gtree)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.read_nginx_group(t)
|
66
|
+
agg_conf = Hash.new([])
|
67
|
+
agg_conf['_'] = t.args unless t.args == ''
|
68
|
+
|
69
|
+
groups, conf = t.body.partition { |i| i.is_a? NginxTransform::Group }
|
70
|
+
conf.each { |x| agg_conf[x.key] += [x.vals.join(' ')] }
|
71
|
+
groups.each { |x| agg_conf[x.id] += [read_nginx_group(x)] }
|
72
|
+
agg_conf
|
73
|
+
end
|
74
|
+
end
|
data/lib/utils/spdx.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Christoph Hartmann
|
3
|
+
# author: Dominik Richter
|
4
|
+
class Spdx
|
5
|
+
def self.licenses
|
6
|
+
spdx_file = File.join(File.dirname(__FILE__), 'spdx.txt').freeze
|
7
|
+
File.read(spdx_file).split("\n")
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.valid_license?(license)
|
11
|
+
licenses.include?(license)
|
12
|
+
end
|
13
|
+
end
|