owasp-pipeline 0.8.3
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 +7 -0
- data/CHANGES +23 -0
- data/FEATURES +19 -0
- data/README.md +101 -0
- data/bin/pipeline +67 -0
- data/lib/pipeline.rb +301 -0
- data/lib/pipeline/event.rb +14 -0
- data/lib/pipeline/filters.rb +41 -0
- data/lib/pipeline/filters/base_filter.rb +19 -0
- data/lib/pipeline/filters/jira_one_time_filter.rb +57 -0
- data/lib/pipeline/filters/remove_all_filter.rb +16 -0
- data/lib/pipeline/finding.rb +52 -0
- data/lib/pipeline/mounters.rb +55 -0
- data/lib/pipeline/mounters/base_mounter.rb +31 -0
- data/lib/pipeline/mounters/docker_mounter.rb +44 -0
- data/lib/pipeline/mounters/filesystem_mounter.rb +25 -0
- data/lib/pipeline/mounters/git_mounter.rb +52 -0
- data/lib/pipeline/mounters/iso_mounter.rb +42 -0
- data/lib/pipeline/mounters/url_mounter.rb +28 -0
- data/lib/pipeline/options.rb +240 -0
- data/lib/pipeline/reporters.rb +50 -0
- data/lib/pipeline/reporters/base_reporter.rb +21 -0
- data/lib/pipeline/reporters/csv_reporter.rb +19 -0
- data/lib/pipeline/reporters/jira_reporter.rb +61 -0
- data/lib/pipeline/reporters/json_reporter.rb +20 -0
- data/lib/pipeline/reporters/text_reporter.rb +19 -0
- data/lib/pipeline/scanner.rb +28 -0
- data/lib/pipeline/tasks.rb +124 -0
- data/lib/pipeline/tasks/av.rb +43 -0
- data/lib/pipeline/tasks/base_task.rb +64 -0
- data/lib/pipeline/tasks/brakeman.rb +60 -0
- data/lib/pipeline/tasks/bundle-audit.rb +93 -0
- data/lib/pipeline/tasks/checkmarx.rb +62 -0
- data/lib/pipeline/tasks/eslint.rb +71 -0
- data/lib/pipeline/tasks/fim.rb +61 -0
- data/lib/pipeline/tasks/nsp.rb +59 -0
- data/lib/pipeline/tasks/owasp-dep-check.rb +120 -0
- data/lib/pipeline/tasks/patterns.json +394 -0
- data/lib/pipeline/tasks/retirejs.rb +106 -0
- data/lib/pipeline/tasks/scanjs-eslintrc +106 -0
- data/lib/pipeline/tasks/scanjs.rb +32 -0
- data/lib/pipeline/tasks/sfl.rb +67 -0
- data/lib/pipeline/tasks/test.rb +47 -0
- data/lib/pipeline/tasks/zap.rb +84 -0
- data/lib/pipeline/tracker.rb +47 -0
- data/lib/pipeline/util.rb +39 -0
- data/lib/pipeline/version.rb +3 -0
- data/lib/zapjson.json +0 -0
- metadata +205 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
# Tracks internal pipeline events.
|
2
|
+
# Can be used for control, but also tracking what happens.
|
3
|
+
class Pipeline::Event
|
4
|
+
attr_reader :parent
|
5
|
+
attr_accessor :path
|
6
|
+
attr_accessor :appname
|
7
|
+
|
8
|
+
def initialize appname, parent = nil
|
9
|
+
@appname = appname
|
10
|
+
@parent = parent
|
11
|
+
@timestamp = Time.now
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Pipeline::Filters
|
2
|
+
@filters = []
|
3
|
+
|
4
|
+
#Add a task. This will call +_klass_.new+ when running tests
|
5
|
+
def self.add klass
|
6
|
+
@filters << klass unless @filters.include? klass
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.filters
|
10
|
+
@filters
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.initialize_filters filters_directory = ""
|
14
|
+
Dir.glob(File.join(filters_directory, "*.rb")).sort.each do |f|
|
15
|
+
require f
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#No need to use this directly.
|
20
|
+
def initialize options = { }
|
21
|
+
end
|
22
|
+
|
23
|
+
#Run all the tasks on the given Tracker.
|
24
|
+
#Returns a new instance of tasks with the results.
|
25
|
+
def self.filter(tracker)
|
26
|
+
@filters.each do |c|
|
27
|
+
filter = c.new()
|
28
|
+
begin
|
29
|
+
filter.filter tracker
|
30
|
+
rescue => e
|
31
|
+
Pipeline.error e.message
|
32
|
+
tracker.error e
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#Load all files in filters/ directory
|
39
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/filters/*.rb").sort.each do |f|
|
40
|
+
require f.match(/(pipeline\/filters\/.*)\.rb$/)[0]
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'pipeline/filters/base_filter'
|
2
|
+
require 'json'
|
3
|
+
require 'curb'
|
4
|
+
|
5
|
+
class Pipeline::JiraOneTimeFilter < Pipeline::BaseFilter
|
6
|
+
|
7
|
+
# Pipeline::Filters.add self
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@name = "Jira One Time Filter"
|
11
|
+
@description = "Checks that each issue that will be reported doesn't already exist in JIRA."
|
12
|
+
end
|
13
|
+
|
14
|
+
def filter tracker
|
15
|
+
@project = tracker.options[:jira_project.to_s]
|
16
|
+
@api = tracker.options[:jira_api_url.to_s]
|
17
|
+
@cookie = tracker.options[:jira_cookie.to_s]
|
18
|
+
@component = tracker.options[:jira_component.to_s]
|
19
|
+
@appname = tracker.options[:appname]
|
20
|
+
|
21
|
+
potential_findings = Array.new(tracker.findings)
|
22
|
+
tracker.findings.clear
|
23
|
+
potential_findings.each do |finding|
|
24
|
+
if confirm_new finding
|
25
|
+
tracker.report finding
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def confirm_new finding
|
32
|
+
json = get_jira_query_json finding
|
33
|
+
http = Curl.post("#{@api}/search", json.to_s) do |http|
|
34
|
+
http.headers['Content-Type'] = "application/json"
|
35
|
+
http.headers['Cookie'] = @cookie
|
36
|
+
end
|
37
|
+
if http.response_code != 200 # OK ...
|
38
|
+
Pipeline.error "Problem with HTTP #{http.response_code} - #{http.body_str}"
|
39
|
+
end
|
40
|
+
|
41
|
+
result = JSON.parse(http.body_str)
|
42
|
+
# Pipeline.error "Got back #{result} with #{result['total']}"
|
43
|
+
|
44
|
+
if result['total'] < 1
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_jira_query_json finding
|
51
|
+
json =
|
52
|
+
{"jql"=>"project=#{@project} AND component='#{@component}' AND labels='#{@appname}' AND description ~ 'FINGERPRINT: #{finding.fingerprint}'"}.to_json
|
53
|
+
json
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# project = APPS and component = "Automated Findings" and Labels = "service-portal" and description ~ "FINGERPRINT: bundlerauditgemsource"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'pipeline/filters/base_filter'
|
2
|
+
|
3
|
+
class Pipeline::RemoveAllFilter < Pipeline::BaseFilter
|
4
|
+
|
5
|
+
#Pipeline::Filters.add self
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@name = "Remove All Filter"
|
9
|
+
@description = "Remove all the things..."
|
10
|
+
end
|
11
|
+
|
12
|
+
def filter tracker
|
13
|
+
tracker.findings.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Pipeline::Finding
|
4
|
+
attr_reader :appname
|
5
|
+
attr_reader :timestamp
|
6
|
+
attr_reader :severity
|
7
|
+
attr_reader :source
|
8
|
+
attr_reader :description
|
9
|
+
attr_reader :detail
|
10
|
+
attr_reader :fingerprint
|
11
|
+
|
12
|
+
def initialize appname, description, detail, source, severity, fingerprint
|
13
|
+
@appname = appname
|
14
|
+
@timestamp = Time.now
|
15
|
+
@description = description
|
16
|
+
@detail = detail
|
17
|
+
@source = source
|
18
|
+
@stringsrc = source.to_s
|
19
|
+
@severity = severity
|
20
|
+
@fingerprint = fingerprint
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_string
|
24
|
+
s = "Finding: #{@appname}"
|
25
|
+
s << "\n\tDescription: #{@description}"
|
26
|
+
s << "\n\tTimestamp: #{@timestamp}"
|
27
|
+
s << "\n\tSource: #{@stringsrc}"
|
28
|
+
s << "\n\tSeverity: #{@severity}"
|
29
|
+
s << "\n\tFingerprint: #{@fingerprint}"
|
30
|
+
s << "\n\tDetail: #{@detail}"
|
31
|
+
s
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_csv
|
35
|
+
s = "#{@appname},#{@description},#{@timestamp},#{@source.to_s},#{@severity},#{@fingerprint},#{@detail}\n"
|
36
|
+
s
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_json
|
40
|
+
json = {
|
41
|
+
'appname' => @appname,
|
42
|
+
'description' => @description,
|
43
|
+
'fingerprint' => @fingerprint,
|
44
|
+
'detail' => @detail,
|
45
|
+
'source' => @source,
|
46
|
+
'severity' => @severity,
|
47
|
+
'timestamp' => @timestamp
|
48
|
+
}.to_json
|
49
|
+
json
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Knows how to test file types and then defer to the helper.
|
2
|
+
|
3
|
+
require 'pipeline/event'
|
4
|
+
|
5
|
+
class Pipeline::Mounters
|
6
|
+
@mounters = []
|
7
|
+
|
8
|
+
attr_reader :target
|
9
|
+
attr_reader :warnings
|
10
|
+
|
11
|
+
def self.add klass
|
12
|
+
@mounters << klass unless @mounters.include? klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.mounters
|
16
|
+
@mounters
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@warnings = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_warning warning
|
24
|
+
@warnings << warning
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.mount tracker
|
28
|
+
target = tracker.options[:target]
|
29
|
+
Pipeline.debug "Mounting target: #{target}"
|
30
|
+
trigger = Pipeline::Event.new(tracker.options[:appname])
|
31
|
+
@mounters.each do | c |
|
32
|
+
mounter = c.new trigger, tracker.options
|
33
|
+
begin
|
34
|
+
Pipeline.debug "Checking about mounting #{target} with #{mounter}"
|
35
|
+
if mounter.supports? target
|
36
|
+
Pipeline.notify "Mounting #{target} with #{mounter}"
|
37
|
+
path = mounter.mount target
|
38
|
+
Pipeline.notify "Mounted #{target} with #{mounter}"
|
39
|
+
return path
|
40
|
+
end
|
41
|
+
rescue => e
|
42
|
+
Pipeline.notify e.message
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.get_mounter_name mounter_class
|
48
|
+
mounter_class.to_s.split("::").last
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#Load all files in mounters/ directory
|
53
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/mounters/*.rb").sort.each do |f|
|
54
|
+
require f.match(/(pipeline\/mounters\/.*)\.rb$/)[0]
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
class Pipeline::BaseMounter
|
3
|
+
attr_reader :errors
|
4
|
+
attr_reader :trigger
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :description
|
7
|
+
|
8
|
+
def initialize(trigger)
|
9
|
+
@errors = []
|
10
|
+
@trigger = trigger
|
11
|
+
end
|
12
|
+
|
13
|
+
def error error
|
14
|
+
@errors << error
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name
|
19
|
+
end
|
20
|
+
|
21
|
+
def description
|
22
|
+
@description
|
23
|
+
end
|
24
|
+
|
25
|
+
def mount
|
26
|
+
end
|
27
|
+
|
28
|
+
def supports? target
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'pipeline/mounters/base_mounter'
|
2
|
+
|
3
|
+
class Pipeline::DockerMounter < Pipeline::BaseMounter
|
4
|
+
|
5
|
+
Pipeline::Mounters.add self
|
6
|
+
|
7
|
+
#Pass in path to the root of the Rails application
|
8
|
+
def initialize trigger, options
|
9
|
+
super(trigger)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def mount target
|
14
|
+
base = @options[:working_dir]
|
15
|
+
target = target.slice(0, target.length - 7)
|
16
|
+
working_target = base + "/docker/" + target + "/"
|
17
|
+
Pipeline.notify "Cleaning directory: #{working_target}"
|
18
|
+
if ! working_target.match(/\A.*\/line\/tmp\/.*/)
|
19
|
+
Pipeline.notify "Bailing in case #{working_target} is malicious."
|
20
|
+
else
|
21
|
+
result = `rm -rf #{working_target}`
|
22
|
+
Pipeline.debug result
|
23
|
+
result = `mkdir -p #{working_target}`
|
24
|
+
Pipeline.debug result
|
25
|
+
result = `docker export #{target} > #{working_target}#{target}.tar`
|
26
|
+
Pipeline.debug result
|
27
|
+
result = `tar -C #{working_target} -xf #{working_target}#{target}.tar`
|
28
|
+
Pipeline.debug result
|
29
|
+
result = `rm #{working_target}#{target}.tar`
|
30
|
+
Pipeline.debug result
|
31
|
+
end
|
32
|
+
return working_target
|
33
|
+
end
|
34
|
+
|
35
|
+
def supports? target
|
36
|
+
last = target.slice(-7,target.length)
|
37
|
+
Pipeline.debug "In Docker mounter, target: #{target} became: #{last} ... wondering if it matched .docker"
|
38
|
+
if last === ".docker"
|
39
|
+
return true
|
40
|
+
else
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'pipeline/mounters/base_mounter'
|
2
|
+
|
3
|
+
class Pipeline::FileSystemMounter < Pipeline::BaseMounter
|
4
|
+
Pipeline::Mounters.add self
|
5
|
+
|
6
|
+
def initialize trigger, options
|
7
|
+
super(trigger)
|
8
|
+
@options = options
|
9
|
+
@name = "FileSystem"
|
10
|
+
@description = "Mount a file via normal file system commands."
|
11
|
+
end
|
12
|
+
|
13
|
+
def mount target
|
14
|
+
return target
|
15
|
+
end
|
16
|
+
|
17
|
+
def supports? target
|
18
|
+
last = target.slice(-1)
|
19
|
+
if last === "/" or last === "."
|
20
|
+
return true
|
21
|
+
else
|
22
|
+
return false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'pipeline/mounters/base_mounter'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Pipeline::GitMounter < Pipeline::BaseMounter
|
5
|
+
|
6
|
+
Pipeline::Mounters.add self
|
7
|
+
|
8
|
+
def initialize trigger, options
|
9
|
+
super(trigger)
|
10
|
+
@options = options
|
11
|
+
@name = "Git"
|
12
|
+
@description = "Pull a repo."
|
13
|
+
end
|
14
|
+
|
15
|
+
def mount target
|
16
|
+
base = @options[:working_dir]
|
17
|
+
|
18
|
+
Pipeline.debug "Making base."
|
19
|
+
FileUtils.mkdir_p base
|
20
|
+
|
21
|
+
# Grap the path part of the git url.
|
22
|
+
protocol, path, suffix = target.match(/\A(.*\/\/)(.*)(.git)\z/i).captures
|
23
|
+
working_target = File.expand_path(base + "" + path + "/")
|
24
|
+
|
25
|
+
Pipeline.notify "Cleaning directory: #{working_target}"
|
26
|
+
if ! Dir.exists? working_target
|
27
|
+
Pipeline.notify "#{working_target} is not a directory."
|
28
|
+
FileUtils.mkdir_p working_target
|
29
|
+
else
|
30
|
+
Pipeline.debug "Removing : #{working_target}"
|
31
|
+
FileUtils.rm_rf working_target
|
32
|
+
FileUtils.mkdir_p working_target
|
33
|
+
end
|
34
|
+
# result = `rm -rf #{working_target}`
|
35
|
+
# puts result
|
36
|
+
Pipeline.debug "Cloning into: #{working_target}"
|
37
|
+
result = `git clone -q #{target} #{working_target}`
|
38
|
+
# puts result
|
39
|
+
#end
|
40
|
+
return working_target
|
41
|
+
end
|
42
|
+
|
43
|
+
def supports? target
|
44
|
+
last = target.slice(-4,target.length)
|
45
|
+
if last === ".git"
|
46
|
+
return true
|
47
|
+
else
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'pipeline/mounters/base_mounter'
|
2
|
+
|
3
|
+
class Pipeline::ISOMounter < Pipeline::BaseMounter
|
4
|
+
|
5
|
+
# THIS DOESN'T WORK SO DON'T REGISTER FOR NOW
|
6
|
+
# Pipeline::Mounters.add self
|
7
|
+
|
8
|
+
def initialize trigger, options
|
9
|
+
super(trigger)
|
10
|
+
@options = options
|
11
|
+
@name = "ISO"
|
12
|
+
@description = "Mount an iso image."
|
13
|
+
end
|
14
|
+
|
15
|
+
def mount target
|
16
|
+
base = @options[:working_dir]
|
17
|
+
working_target = base + "/" + target + "/"
|
18
|
+
Pipeline.notify "Cleaning directory: #{working_target}"
|
19
|
+
|
20
|
+
if ! working_target.match(/\A.*\/line\/tmp\/.*/)
|
21
|
+
Pipeline.notify "Bailing in case #{working_target} is malicious."
|
22
|
+
else
|
23
|
+
result = `rm -rf #{working_target}`
|
24
|
+
# puts result
|
25
|
+
result = `mkdir -p #{working_target}`
|
26
|
+
# puts result
|
27
|
+
Pipeline.notify "Mounting #{target} to #{working_target}"
|
28
|
+
result = `mount -t iso9660 #{target} #{working_target}`
|
29
|
+
# puts result
|
30
|
+
end
|
31
|
+
return working_target
|
32
|
+
end
|
33
|
+
|
34
|
+
def supports? target
|
35
|
+
last = target.slice(-4,target.length)
|
36
|
+
if last === ".iso"
|
37
|
+
return true
|
38
|
+
else
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|