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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +23 -0
  3. data/FEATURES +19 -0
  4. data/README.md +101 -0
  5. data/bin/pipeline +67 -0
  6. data/lib/pipeline.rb +301 -0
  7. data/lib/pipeline/event.rb +14 -0
  8. data/lib/pipeline/filters.rb +41 -0
  9. data/lib/pipeline/filters/base_filter.rb +19 -0
  10. data/lib/pipeline/filters/jira_one_time_filter.rb +57 -0
  11. data/lib/pipeline/filters/remove_all_filter.rb +16 -0
  12. data/lib/pipeline/finding.rb +52 -0
  13. data/lib/pipeline/mounters.rb +55 -0
  14. data/lib/pipeline/mounters/base_mounter.rb +31 -0
  15. data/lib/pipeline/mounters/docker_mounter.rb +44 -0
  16. data/lib/pipeline/mounters/filesystem_mounter.rb +25 -0
  17. data/lib/pipeline/mounters/git_mounter.rb +52 -0
  18. data/lib/pipeline/mounters/iso_mounter.rb +42 -0
  19. data/lib/pipeline/mounters/url_mounter.rb +28 -0
  20. data/lib/pipeline/options.rb +240 -0
  21. data/lib/pipeline/reporters.rb +50 -0
  22. data/lib/pipeline/reporters/base_reporter.rb +21 -0
  23. data/lib/pipeline/reporters/csv_reporter.rb +19 -0
  24. data/lib/pipeline/reporters/jira_reporter.rb +61 -0
  25. data/lib/pipeline/reporters/json_reporter.rb +20 -0
  26. data/lib/pipeline/reporters/text_reporter.rb +19 -0
  27. data/lib/pipeline/scanner.rb +28 -0
  28. data/lib/pipeline/tasks.rb +124 -0
  29. data/lib/pipeline/tasks/av.rb +43 -0
  30. data/lib/pipeline/tasks/base_task.rb +64 -0
  31. data/lib/pipeline/tasks/brakeman.rb +60 -0
  32. data/lib/pipeline/tasks/bundle-audit.rb +93 -0
  33. data/lib/pipeline/tasks/checkmarx.rb +62 -0
  34. data/lib/pipeline/tasks/eslint.rb +71 -0
  35. data/lib/pipeline/tasks/fim.rb +61 -0
  36. data/lib/pipeline/tasks/nsp.rb +59 -0
  37. data/lib/pipeline/tasks/owasp-dep-check.rb +120 -0
  38. data/lib/pipeline/tasks/patterns.json +394 -0
  39. data/lib/pipeline/tasks/retirejs.rb +106 -0
  40. data/lib/pipeline/tasks/scanjs-eslintrc +106 -0
  41. data/lib/pipeline/tasks/scanjs.rb +32 -0
  42. data/lib/pipeline/tasks/sfl.rb +67 -0
  43. data/lib/pipeline/tasks/test.rb +47 -0
  44. data/lib/pipeline/tasks/zap.rb +84 -0
  45. data/lib/pipeline/tracker.rb +47 -0
  46. data/lib/pipeline/util.rb +39 -0
  47. data/lib/pipeline/version.rb +3 -0
  48. data/lib/zapjson.json +0 -0
  49. 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,19 @@
1
+ class Pipeline::BaseFilter
2
+ attr_accessor :name
3
+ attr_accessor :description
4
+
5
+ def initialize()
6
+ end
7
+
8
+ def name
9
+ @name
10
+ end
11
+
12
+ def description
13
+ @description
14
+ end
15
+
16
+ def filter
17
+ end
18
+
19
+ 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