├── .gitignore
├── src
└── main
│ ├── resources
│ ├── com
│ │ └── acunetix
│ │ │ └── BuildScanner
│ │ │ ├── help-profile.html
│ │ │ ├── help-threat.html
│ │ │ ├── help.html
│ │ │ ├── help-stopScan.html
│ │ │ ├── help-target.html
│ │ │ ├── help-gApiKey.html
│ │ │ ├── help-stopTargetScans.html
│ │ │ ├── help-repTemp.html
│ │ │ ├── help-incScan.html
│ │ │ ├── help-gApiKeyID.html
│ │ │ ├── help-svRep.html
│ │ │ ├── global.jelly
│ │ │ └── config.jelly
│ ├── index.jelly
│ └── Messages.properties
│ └── java
│ └── com
│ └── acunetix
│ ├── SR.java
│ ├── Engine.java
│ └── BuildScanner.java
├── LICENSE.txt
├── pom.xml
├── CHANGELOG.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | # IntelliJ files
3 | *.iml
4 | *.iws
5 | *.ipr
6 | .idea
7 | out
8 | pom.xsd
9 | target
10 | work
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-profile.html:
--------------------------------------------------------------------------------
1 |
2 | The Scan Type used to scan the target
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-threat.html:
--------------------------------------------------------------------------------
1 |
2 | The Threat Level (High, Medium, Low) to fail the build on
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help.html:
--------------------------------------------------------------------------------
1 |
2 | Trigger automated Acunetix scans as part of your web application's build process
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-stopScan.html:
--------------------------------------------------------------------------------
1 |
2 | If selected, the scan will be stopped if the build fails (Threat Level is reached)
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-target.html:
--------------------------------------------------------------------------------
1 |
2 | The Scan Target on which to run a scan (excludes Targets requiring Manual Intervention)
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-gApiKey.html:
--------------------------------------------------------------------------------
1 |
2 | Acunetix API Key. The API Key may be obtained from the Acunetix User Interface under the Administrator's Profile section
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-stopTargetScans.html:
--------------------------------------------------------------------------------
1 |
2 | This is useful when older versions of the web application are discarded by the CI/CD when an updated version is created.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | Plugin for starting Acunetix Premium scans
7 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-repTemp.html:
--------------------------------------------------------------------------------
1 |
2 | Generate a Report after the scan completes.
3 | The report is available for download from Acunetix backend within a time frame of one hour
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-incScan.html:
--------------------------------------------------------------------------------
1 |
2 | This will create an Incremental scan. The first scan will scan all the site. Subsequent scans will only scan the changes in the site, resulting in smaller scans.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-gApiKeyID.html:
--------------------------------------------------------------------------------
1 |
2 | The URL of the Acunetix API. Example: https://localhost:3443/api/v1.
3 | Note: When change the API key make sure it is saved, using "Apply" button, before trying "Test Connection"
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/help-svRep.html:
--------------------------------------------------------------------------------
1 |
2 | Option to download the report in the project workspace from where it can be used for other operations like attaching
3 | to an email or archiving.
4 | Note: Archiving can be done with "Archive the artifacts" post-build action
5 |
6 |
--------------------------------------------------------------------------------
/src/main/java/com/acunetix/SR.java:
--------------------------------------------------------------------------------
1 | package com.acunetix;
2 |
3 | import java.text.MessageFormat;
4 |
5 | import static java.util.ResourceBundle.getBundle;
6 |
7 | /**
8 | * Expose message resources
9 | */
10 | class SR {
11 | private static java.util.ResourceBundle acunetixBundle = getBundle("Messages");
12 |
13 | private SR() {
14 | }
15 |
16 | static String getString(String key, Object... args) {
17 | String message = acunetixBundle.getString(key);
18 |
19 | if (args != null && args.length > 0) {
20 | return MessageFormat.format(message, args);
21 | }
22 | return message;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Acunetix Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8 | persons to whom the Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11 | Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/global.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/resources/com/acunetix/BuildScanner/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ${%Incremental scan}
23 |
24 |
25 |
26 |
27 | ${%Stop scans on the Target before starting a new scan}
28 |
29 |
30 |
31 |
32 |
33 |
34 | ${%Stop the scan when build fails}
35 |
36 |
37 |
38 |
39 |
40 |
41 | ${%Save report in workspace}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/resources/Messages.properties:
--------------------------------------------------------------------------------
1 | cannot.connect.to.application=Cannot connect to Acunetix API engine
2 | connected.successfully=Connected successfully
3 | please.set.the.api.url=Please set the API URL
4 | please.set.the.api.key=Please set the API Key
5 | invalid.api.url=Invalid API URL
6 | api.key.not.set=API Key is not set
7 | starting.scan.on.target.0=Starting scan on target: {0}
8 | bad.response.0=Bad response received: {0}
9 | scan.threat.0=At least one \"{0}\" vulnerability was found
10 | scan.aborted.outside=The scan was aborted outside of this instance
11 | scan.aborted=The scan was aborted
12 | scan.completed=The scan was completed
13 | scan.scheduled=The scan was scheduled. Please ensure the configured scan can run immediately
14 | abort.scan.scheduled=The build was aborted because the scan is scheduled
15 | aborting.the.build=Aborting the build
16 | build.aborted=The build was aborted
17 | scan.threat=The scan threat level is greater or equal than the configured level
18 | the.scan.was.stopped=The scan was stopped
19 | certificate.to.the.java.ca.store=Please add the Acunetix scanner certificate to Java CA store
20 | scan.started=Scan started
21 | the.scan.is.in.scheduled.state=The scan is in scheduled state! Waiting to start ...
22 | the.scan.was.deleted=The scan was deleted
23 | could.not.connect.to.application.connection.refused=Could not connect to application. Connection refused
24 | could.not.find.scan.with.scanid.0=Could not find the scan with scanId: {0}
25 | could.not.connect=The connection could not be established. Check if the server is responsive and API key is valid
26 | invalid.target=The target is invalid
27 | invalid.scan_type=The scan type is invalid
28 | could.not.find.scan.with.scanid.0.create.new=Could not find the scan with scanId: {0}\n Please create a new one by saving the project configuration
29 | view.scan.status=View scan on Acunetix:
30 | check.vulnerabilities.found=Check vulnerabilities found by this scan:
31 | generating.0.report=Generating \"{0}\" report
32 | report.saved.in.workspace.0=Report saved in workspace: \"{0}\"
33 | scan.report.download.link.0=Scan report download link: \"{0}\"
34 | invalid.report.file.path.0=Invalid report file path: \"{0}\"
35 |
36 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.jenkins-ci.plugins
7 | credentials
8 | 2.1.19
9 |
10 |
11 | org.jenkins-ci.plugins
12 | plain-credentials
13 | 1.4
14 |
15 |
16 |
17 | org.jenkins-ci.plugins
18 | plugin
19 | 2.11
20 |
21 |
22 | com.acunetix
23 | acunetix
24 | 1.2.15-SNAPSHOT
25 | hpi
26 |
27 |
28 | 2.73.3
29 | 8
30 | 2.33
31 |
32 |
33 | Acunetix
34 | Plugin for starting Acunetix Premium scans
35 | https://github.com/jenkinsci/acunetix-plugin/blob/master/README.md
36 |
37 |
38 |
39 | MIT License
40 | http://opensource.org/licenses/MIT
41 |
42 |
43 |
44 |
45 |
46 | Acunetix
47 | Acunetix
48 | product@acunetix.com
49 |
50 |
51 |
52 | scm:git:git://github.com/jenkinsci/acunetix-plugin.git
53 | scm:git:git@github.com:jenkinsci/acunetix-plugin.git
54 | http://github.com/jenkinsci/acunetix-plugin
55 | HEAD
56 |
57 |
58 |
59 | repo.jenkins-ci.org
60 | https://repo.jenkins-ci.org/public/
61 |
62 |
63 |
64 |
65 | repo.jenkins-ci.org
66 | https://repo.jenkins-ci.org/public/
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Version 1.0.0 (March 15, 2017)
4 |
5 | 1. Initial release
6 | 2. New features
7 | 1. Trigger Acunetix scans from within Jenkins upon each build
8 | 2. Trigger Acunetix scans with built-in or custom Scan Types to
9 | only scan for specific vulnerabilities
10 | 3. Configure Jenkins to fail a build (and optionally abort the
11 | scan) as soon as a specific threat-level (high, medium or low
12 | severity) is reached
13 | 4. Automatically generate reports and save them within Jenkins
14 | 3. Improvements
15 | 1. N/A
16 | 4. Bugfixes
17 | 1. N/A
18 |
19 | ## Version 1.1.0 (October 24, 2018)
20 |
21 | Improvements: Use Jenkins credentials for storing the API Key
22 |
23 | ## Version 1.2.0 (October 25, 2018)
24 |
25 | Improvements: Better exception handling like situations when
26 | configured target or profile have been deleted in main application
27 |
28 | Bug fixes:
29 |
30 | - Plugin retrieve only first 100 targets
31 | - Scans can now be executed on the online version of the scanner
32 | - Reports cannot be downloaded. Now links to the reports will be
33 | provided on the output.
34 |
35 | ## Version 1.2.1 (January 10, 2019)
36 |
37 | Bug fixes:
38 |
39 | - Fixed 429 error when pairing with online build
40 |
41 | ## Version 1.2.2 (January 18, 2019)
42 |
43 | Bug fixes:
44 |
45 | - Fixed 429 error for reports
46 |
47 | ## Version 1.2.3 (February 06, 2019)
48 |
49 | Bug fixes:
50 |
51 | - Saved API URL is not loaded and shown in Jenkins system page
52 |
53 | ## Version 1.2.5 (February 14, 2020)
54 |
55 | - Compatibility with Acunetix version 13
56 | - New feature: incremental scans
57 |
58 | ## Version 1.2.6 (February 26, 2020)
59 |
60 | - Save reports in workspace
61 | - Improved logging
62 |
63 | ## Version 1.2.8 (April 21, 2020)
64 |
65 | - Fixed compatibility with online version
66 | - Fix: report was generated even when the scan could not be performed
67 | - Specific error when the target was deleted and plugin configuration was not updated
68 | - Re-added report download link in console
69 | - Report save to workspace configurable through a checkbox
70 |
71 | ## Version 1.2.9 (May 19, 2020)
72 |
73 | - Provide all report templates when choosing for a report type. Before were available only standard reports
74 |
75 | ## Version 1.2.11 (February 15, 2021)
76 |
77 | - Fix: use correct workspace on slaves
78 | - Fix: Comprehensive report not downloaded properly
79 |
80 | ## Version 1.2.12 (March 22, 2021)
81 |
82 | - Added option to stop scans on the Target before starting a new scan
83 |
84 | ## Version 1.2.13 (March 29, 2021)
85 |
86 | - Changed description
87 |
88 | ## Version 1.2.14 (June 28, 2021)
89 |
90 | - Fix: error trying to make pipeline script with Snippet Generator
91 | - Fix: incremental scan added multiple times
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | | Plugin Information |
2 | |-----------------------------------------------------------------------------------------------|
3 | | View Acunetix [on the plugin site](https://plugins.jenkins.io/acunetix) for more information. |
4 |
5 | Older versions of this plugin may not be safe to use. Please review the
6 | following warnings before using an older version:
7 |
8 | - [SSRF
9 | vulnerability](https://jenkins.io/security/advisory/2019-02-19/#SECURITY-980)
10 | - [Credentials stored in plain
11 | text](https://jenkins.io/security/advisory/2019-02-19/#SECURITY-951)
12 |
13 |
14 |
15 | #
16 |
17 | # Plugin Information
18 |
19 | This plugin allows you to trigger automated Acunetix scans as part of
20 | your web application's build process inside of Jenkins.
21 |
22 | # Description
23 |
24 | Acunetix is an automated web application security testing and
25 | vulnerability management platform. Acunetix automatically crawls and
26 | scans off-the-shelf and custom-built websites and web applications for
27 | over 3000 web vulnerabilities to help organizations shore up their web
28 | security.
29 | The [Acunetix Jenkins
30 | Plugin](http://www.acunetix.com/blog/web-security-zone/acunetix-jenkins-plugin/)
31 | enables you to:
32 |
33 | 1. Trigger Acunetix scans from within Jenkins upon each build
34 | 2. Trigger Acunetix scans with built-in or custom Scan Types to only
35 | scan for specific vulnerabilities
36 | 3. Configure Jenkins to fail a build (and optionally abort the scan) as
37 | soon as a specific threat-level (high, medium or low severity) is
38 | reached
39 | 4. Automatically generate reports
40 |
41 |
42 | After setting up the Acunetix Jenkins Plugin, you can configure any
43 | Jenkins job with a build step action to trigger an Acunetix scan. When
44 | an Acunetix scan is triggered, Jenkins will launch a scan against a
45 | Target you specify and is scanned with settings configured in Acunetix.
46 | Jenkins will pass or fail the build based on criteria you provided.
47 |
48 | | |
49 | |------------------------------------------------------------------------------------------------------------------------|
50 | | **Note** – The Acunetix Jenkins Plugin requires an Acunetix API key, which is only available in *Acunetix Enterprise*. |
51 |
52 | # Installation
53 |
54 | To install the Acunetix Jenkins Plugin:
55 |
56 | 1. In Jenkins, navigate to *Manage Jenkins \> Manage Plugins* and
57 | select the *Available* tab
58 | 2. Search the Jenkins Plugin Index for *Acunetix*
59 | 3. Select *Install without restart* or *Download and install after
60 | restart*. Jenkins will install the plugin based on your preference
61 |
62 | # Configuration
63 |
64 | To configure the Acunetix Jenkins Plugin:
65 |
66 | 1. [Make Acunetix reachable from hosts other than
67 | localhost](https://www.acunetix.com/blog/docs/use-acunetix-host-localhost/)
68 | 2. [Add the Acunetix Root CA Certificate to
69 | Jenkins](http://www.acunetix.com/blog/docs/installing-and-configuring-the-acunetix-jenkins-plugin)
70 | 3. Obtain an Acunetix API key
71 | 4. Modify the Jenkins Content Security Policy (optional)
72 |
73 | # Usage
74 |
75 | 1. In Jenkins, navigate to the job you wish to run an Acunetix scan in,
76 | and select *Configure* in the sidebar
77 | 2. In the *Build* section, select *Acunetix* from the *Add build step*
78 | drop-down menu
79 | 3. You will then be presented with the options outlined below.
80 | 1. **Scan Type** – Choose a *Scan Type* with which you want the
81 | scan to run. *Scan Types* are used to reduce the scope of the
82 | tests the scanner runs during the scan.
83 | 2. **Scan Target** – Choose a *Scan Target* you wish to scan. *Scan
84 | Targets* are obtained from Acunetix, with the exception of
85 | Targets requiring Manual Intervention. Targets contain part of
86 | the Target description for distinguishability between Targets
87 | that have the same URL.
88 | 3. **Fail build if threat level is** – Choose at which threat level
89 | to fail the Jenkins build based upon the scan's threat level
90 | (High severity, Medium severity or Low Severity).
91 | 4. **Stop the scan when build fails** – Check this checkbox if you
92 | would like to abort the scan when the fail condition in *Fail
93 | build if threat level is* is met. This is setting is enabled by
94 | default.
95 | 5. **Generate Report** – Choose to a report to generate upon
96 | completion of the scan. The report will be saved in project workspace
97 | 4. Click *Save*
98 |
99 | # FAQs
100 |
101 | 1. **Which edition of Acunetix do I need to use the Acunetix Jenkins
102 | Plugin?**
103 |
104 | The Acunetix Jenkins Plugin requires access to the Acunetix API and API
105 | key, which is only available in *Acunetix Enterprise*.
106 |
107 | 1. **The Target I have set-up in Acunetix is not showing in drop-down
108 | list inside Jenkins.**
109 |
110 | The Acunetix Jenkins Plugin will display all Targets in an Acunetix
111 | installation, with the exception of Targets requiring Manual
112 | Intervention as part of their Login Sequence. Please make sure that the
113 | Target you wish to select does not make use of Manual Intervention.
114 |
115 | 1. **How can I differentiate between multiple Targets with the same
116 | URL?**
117 |
118 | If you have multiple Targets with the same URL, it is advised that you
119 | enter a description in the Target's settings to be able to differentiate
120 | between them. The Target's description will show up in Jenkins if one is
121 | available.
122 |
123 | 1. **Why does a scan take long for to start?**
124 |
125 | When Jenkins attempts to start a scan, the scan is placed in a scan
126 | queue. If the scan queue is empty, then the scan will start immediately.
127 | However, if the maximum number of scans (including scheduled scans) in
128 | the scan queue is reached, the scan will wait in the queue until other
129 | scans finish processing. This also means that the Jenkins build will not
130 | finish processing until the scan is complete.
131 |
132 | 1. **What happens to the scan if I abort the Jenkins build?**
133 |
134 | Aborting the Jenkins build will also abort the scan. You may still view
135 | partial results inside of Acunetix. Reports will not be automatically
136 | generated if the Jenkins build is aborted (you can manually generate
137 | reports from within the Acunetix UI).
138 |
139 | 1. **What happens if I stop an Acunetix scan from outside Jenkins?**
140 |
141 | If a scan that was started by Jenkins is stopped from the Acunetix UI or
142 | via the Acunetix API, the Jenkins build will also be aborted. Reports
143 | will not be automatically generated if the scan is stopped (you can
144 | manually generate reports from within the Acunetix UI)
145 |
146 | 1. **What kind of reports can be generated from Jenkins?**
147 |
148 | All *Standard* reports can be generated from Jenkins (Affected Items,
149 | Developer, Executive Summary and Quick reports). Compliance reports (PCI
150 | DSS, OWASP Top 10, ISO 27001…) for the scans run by Jenkins may be
151 | generated from within the Acunetix UI.
152 |
153 | 1. **What happens to reports generated from Jenkins?**
154 |
155 | Reports generated from Jenkins are generated on the main application and then downloaded in project workspace.
156 | The report can be archived within job folder with post-build action "Archive the artifacts" and deleted from workspace
157 |
158 | 1. **How do I disable or remove the Acunetix Jenkins Plugin**
159 |
160 | Please refer to [this Jenkins
161 | article](https://wiki.jenkins-ci.org/display/JENKINS/Removing+and+disabling+plugins)
162 | on disabling and removing Jenkins plugins and associated plugin data
163 |
164 | # Changelog
165 |
166 | Please refer to [this](https://github.com/jenkinsci/acunetix-plugin/blob/master/CHANGELOG.md)
--------------------------------------------------------------------------------
/src/main/java/com/acunetix/Engine.java:
--------------------------------------------------------------------------------
1 | package com.acunetix;
2 |
3 | import hudson.FilePath;
4 | import net.sf.json.JSONArray;
5 | import net.sf.json.JSONNull;
6 | import net.sf.json.JSONObject;
7 |
8 | import javax.net.ssl.HttpsURLConnection;
9 | import java.io.BufferedReader;
10 | import java.io.DataOutputStream;
11 | import java.io.IOException;
12 | import java.io.InputStreamReader;
13 | import java.net.MalformedURLException;
14 | import java.net.URL;
15 | import java.net.URLConnection;
16 | import java.util.*;
17 |
18 |
19 | public class Engine {
20 | private String apiUrl;
21 | private String apiKey;
22 | private static final Map threatCategory = new HashMap<>();
23 | static {
24 | threatCategory.put("High", new String[]{"3"});
25 | threatCategory.put("Medium", new String[]{"3", "2"});
26 | threatCategory.put("Low", new String[]{"3", "2", "1"});
27 | }
28 | private static final Map scan_status_categories = new HashMap<>();
29 | static {
30 | scan_status_categories.put("All", new String[]{"scheduled", "queued", "starting", "processing", "aborting",
31 | "aborted", "pausing", "paused", "completed", "failed"});
32 | scan_status_categories.put("Active", new String[]{"scheduled", "queued", "starting", "processing"});
33 | scan_status_categories.put("Finished", new String[]{"aborting","aborted", "pausing", "paused", "completed",
34 | "failed"});
35 | }
36 |
37 | public Engine(String apiUrl, String apiKey) {
38 | this.apiUrl = apiUrl;
39 | this.apiKey = apiKey;
40 | }
41 |
42 | public static String getThreatName(String threat) {
43 | switch (threat) {
44 | case "3":
45 | return "High";
46 | case "2":
47 | return "Medium";
48 | case "1":
49 | return "Low";
50 | }
51 | return null;
52 | }
53 |
54 | private static class Resp {
55 | int respCode;
56 | String respStr = null;
57 | JSONObject jso = null;
58 | }
59 |
60 | private HttpsURLConnection openConnection(String endpoint, String method) throws IOException {
61 | return openConnection(endpoint, method, "application/json; charset=UTF-8");
62 | }
63 |
64 | private HttpsURLConnection openConnection(String endpoint) throws IOException {
65 | return openConnection(endpoint, "GET", "application/json; charset=UTF-8");
66 | }
67 |
68 | private HttpsURLConnection openConnection(String endpoint, String method, String contentType) throws IOException {
69 | URL url = new URL(endpoint);
70 | HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
71 | connection.setRequestMethod(method);
72 | connection.setRequestProperty("Content-Type", contentType);
73 | connection.setRequestProperty("User-Agent", "Mozilla/5.0");
74 | connection.addRequestProperty("X-AUTH", apiKey);
75 |
76 | return connection;
77 | }
78 |
79 | private Resp doGet(String urlStr) throws IOException {
80 | HttpsURLConnection connection = openConnection(urlStr);
81 | try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"))) {
82 | String inputLine;
83 | StringBuilder resbuf = new StringBuilder();
84 | while ((inputLine = in.readLine()) != null) {
85 | resbuf.append(inputLine);
86 | }
87 | Resp resp = new Resp();
88 | resp.respCode = connection.getResponseCode();
89 | resp.jso = JSONObject.fromObject(resbuf.toString());
90 | return resp;
91 | }
92 | }
93 |
94 | public String getUrl(String apiUrl, String downloadLink) throws MalformedURLException {
95 | URL url = new URL(apiUrl);
96 | if (downloadLink.matches("^(http|https)://.*$")) {
97 | return downloadLink;
98 | } else {
99 | return url.getProtocol() + "://" + url.getAuthority() + downloadLink;
100 | }
101 | }
102 |
103 | public int doTestConnection(String urlStr) throws IOException {
104 | HttpsURLConnection connection = openConnection(urlStr);
105 | return connection.getResponseCode();
106 | }
107 |
108 | private Resp doPost(String urlStr) throws IOException {
109 | HttpsURLConnection connection = openConnection(urlStr,"POST");
110 | connection.setUseCaches(false);
111 | connection.setDoInput(true);
112 | connection.setDoOutput(true);
113 | Resp resp = new Resp();
114 | resp.respCode = connection.getResponseCode();
115 | return resp;
116 | }
117 |
118 | private Resp doDelete(String urlStr) throws IOException {
119 | HttpsURLConnection connection = openConnection(urlStr,"DELETE");
120 | connection.setUseCaches(false);
121 | connection.setDoInput(true);
122 | connection.setDoOutput(true);
123 | Resp resp = new Resp();
124 | resp.respCode = connection.getResponseCode();
125 | return resp;
126 | }
127 |
128 | private Resp doPostLoc(String urlStr, String urlParams) throws IOException, NullPointerException {
129 | HttpsURLConnection connection = openConnection(urlStr, "POST");
130 | connection.setUseCaches(false);
131 | connection.setDoInput(true);
132 | connection.setDoOutput(true);
133 | try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
134 | outputStream.writeBytes(urlParams);
135 | }
136 | String location = connection.getHeaderField("Location");
137 | Resp resp = new Resp();
138 | resp.respCode = connection.getResponseCode();
139 | if (400 <= resp.respCode && resp.respCode <= 599) {
140 | throw new RuntimeException("HTTP request failed with status code " + resp.respCode);
141 | }
142 | try {
143 | resp.respStr = location.substring(location.lastIndexOf("/") + 1);
144 | } catch (NullPointerException e){
145 | e.printStackTrace();
146 | throw new ConnectionException();
147 | }
148 | return resp;
149 | }
150 |
151 | private JSONArray getObjects(String objectName) throws IOException, NullPointerException {
152 | JSONArray objects = null;
153 | JSONArray cursors;
154 |
155 | Resp resp = doGet(apiUrl + "/" + objectName);
156 | if (resp.respCode != 200) {
157 | throw new IOException(SR.getString("bad.response.0", resp.respCode));
158 | }
159 | objects = resp.jso.getJSONArray(objectName);
160 | JSONObject pagination = resp.jso.getJSONObject("pagination");
161 | if (pagination.containsKey("next_cursor")) {
162 | Integer cursor = 0;
163 | while ((cursor >= 100) || (cursor%100>0)) {
164 | resp = doGet(apiUrl + "/" + objectName + "?c=" + cursor);
165 | objects.addAll(resp.jso.getJSONArray(objectName));
166 | pagination = resp.jso.getJSONObject("pagination");
167 | if (pagination.getString("next_cursor").equals("null")) {
168 | break;
169 | }
170 | cursor = pagination.getInt("next_cursor");
171 | }
172 | }
173 | else{
174 | if (pagination.size() > 0) {
175 | cursors = pagination.getJSONArray("cursors");
176 | String cursor;
177 | while (cursors.size() > 1) {
178 | cursor = cursors.getString(1);
179 | resp = doGet(apiUrl + "/" + objectName + "?c=" + cursor);
180 | objects.addAll(resp.jso.getJSONArray(objectName));
181 | pagination = resp.jso.getJSONObject("pagination");
182 | cursors = pagination.getJSONArray("cursors");
183 | }
184 | }
185 | }
186 |
187 | // }
188 | return objects;
189 | }
190 |
191 | public JSONArray getTargets() throws IOException {
192 | return getObjects("targets");
193 | }
194 |
195 |
196 | public String getTargetName(String targetId) throws IOException {
197 | JSONArray targets = getTargets();
198 | for (int i = 0; i < targets.size(); i++) {
199 | JSONObject item = targets.getJSONObject(i);
200 | String target_id = item.getString("target_id");
201 | if (target_id.equals(targetId)) {
202 | String address = item.getString("address");
203 | String description = item.getString("description");
204 | String target_name = address;
205 | if (description.length() > 0) {
206 | if (description.length() > 100) {
207 | description = description.substring(0, 100);
208 | }
209 | target_name += " (" + description + ")";
210 | }
211 | return target_name;
212 | }
213 | }
214 | return null;
215 | }
216 |
217 | public JSONArray getScanningProfiles() throws IOException {
218 | return getObjects("scanning_profiles");
219 | }
220 |
221 | public Boolean checkScanProfileExists(String profileId) throws IOException {
222 | JSONArray profiles = getScanningProfiles();
223 | for (int i = 0; i < profiles.size(); i++) {
224 | JSONObject item = profiles.getJSONObject(i);
225 | String profile_id = item.getString("profile_id");
226 | if (profile_id.equals(profileId)) {
227 | return true;
228 | }
229 | }
230 | return false;
231 | }
232 |
233 | public Boolean checkIncScanExist(String target_id, String profile_id) {
234 | try {
235 | JSONArray scans = getScans();
236 | for (int i = 0; i < scans.size(); i++) {
237 | JSONObject item = scans.getJSONObject(i);
238 | if (item.getBoolean("incremental")) {
239 | if ((item.getString("target_id").equals(target_id)) && (item.getString("profile_id").equals(profile_id))) {
240 | return true;
241 | }
242 | }
243 | }
244 | }
245 | catch (IOException e){
246 | e.printStackTrace();
247 | }
248 | return false;
249 | }
250 |
251 | public Boolean checkScanExist(String scanId) {
252 | try {
253 | JSONArray scans = getScans();
254 | for (int i = 0; i < scans.size(); i++) {
255 | JSONObject item = scans.getJSONObject(i);
256 | String id = item.getString("scan_id");
257 | if (id.equals(scanId)) {
258 | return true;
259 | }
260 | }
261 | }
262 | catch (IOException e){
263 | e.printStackTrace();
264 | }
265 | return false;
266 | }
267 |
268 | public String getIncScanId(String target_id, String profile_id) {
269 | try {
270 | JSONArray scans = getScans();
271 | for (int i = 0; i < scans.size(); i++) {
272 | JSONObject item = scans.getJSONObject(i);
273 | if (item.getBoolean("incremental")) {
274 | if ((item.getString("target_id").equals(target_id)) && (item.getString("profile_id").equals(profile_id))) {
275 | return item.getString("scan_id");
276 | }
277 | }
278 | }
279 | }
280 | catch (IOException e){
281 | e.printStackTrace();
282 | }
283 | return null;
284 | }
285 |
286 |
287 | public String startScan(String scanningProfileId, String targetId, Boolean waitFinish) throws IOException {
288 | JSONObject jso = new JSONObject();
289 | jso.put("target_id", targetId);
290 | jso.put("profile_id", scanningProfileId);
291 | jso.put("user_authorized_to_scan", "yes");
292 | JSONObject jsoChild = new JSONObject();
293 | jsoChild.put("disable", false);
294 | jsoChild.put("start_date", JSONNull.getInstance());
295 | jsoChild.put("time_sensitive", false);
296 | jso.put("schedule", jsoChild);
297 | String scanId = doPostLoc(apiUrl + "/scans", jso.toString()).respStr;
298 | if (waitFinish) {
299 | while (!getScanStatus(scanId).equals("completed")) {
300 | try {
301 | Thread.sleep(10000);
302 | } catch (InterruptedException e) {
303 | e.printStackTrace();
304 | }
305 | }
306 | }
307 | return scanId;
308 | }
309 |
310 | public String createIncScan(String scanningProfileId, String targetId) throws IOException {
311 | JSONObject jso = new JSONObject();
312 | jso.put("target_id", targetId);
313 | jso.put("profile_id", scanningProfileId);
314 | jso.put("user_authorized_to_scan", "yes");
315 | jso.put("incremental", true);
316 | JSONObject jsoChild = new JSONObject();
317 | jsoChild.put("disable", false);
318 | jsoChild.put("start_date", JSONNull.getInstance());
319 | jsoChild.put("time_sensitive", false);
320 | jsoChild.put("triggerable", true);
321 | jso.put("schedule", jsoChild);
322 | String scanId = doPostLoc(apiUrl + "/scans", jso.toString()).respStr;
323 | return scanId;
324 | }
325 |
326 | public String triggerIncScan(String scanId, Boolean waitFinish) throws IOException {
327 | String resScanId = doPost(apiUrl + "/scans/" + scanId + "/trigger").respStr;
328 | if (waitFinish) {
329 | while (!getScanStatus(scanId).equals("completed")) {
330 | try {
331 | Thread.sleep(10000);
332 | } catch (InterruptedException e) {
333 | e.printStackTrace();
334 | }
335 | }
336 | }
337 | return resScanId;
338 | }
339 |
340 | public JSONArray getScans() throws IOException {
341 | return getObjects("scans");
342 | }
343 |
344 | public String getScanThreat(String scanId) throws IOException {
345 | JSONObject jso = doGet(apiUrl + "/scans/" + scanId).jso;
346 | return jso.getJSONObject("current_session").getString("threat");
347 | }
348 |
349 | public String getScanStatus(String scanId) throws IOException {
350 | JSONObject jso = doGet(apiUrl + "/scans/" + scanId).jso;
351 | return jso.getJSONObject("current_session").getString("status");
352 | }
353 |
354 | public String getScanProfile(String scanId) throws IOException {
355 | JSONObject jso = doGet(apiUrl + "/scans/" + scanId).jso;
356 | return jso.getString("profile_id");
357 | }
358 |
359 | public String getScanTarget(String scanId) throws IOException {
360 | JSONObject jso = doGet(apiUrl + "/scans/" + scanId).jso;
361 | return jso.getString("target_id");
362 | }
363 |
364 | public void stopScan(String scanId) {
365 | try {
366 | Resp resp = doPost(apiUrl + "/scans/" + scanId + "/abort");
367 | if (resp.respCode != 204) {
368 | throw new IOException(SR.getString("bad.response.0", resp.respCode));
369 | }
370 | } catch (IOException e) {
371 | e.printStackTrace();
372 | }
373 | }
374 |
375 | public void deleteScan(String scanId) {
376 | try {
377 | doDelete(apiUrl + "/scans/" + scanId);
378 | } catch (IOException e) {
379 | e.printStackTrace();
380 | }
381 | }
382 |
383 | public void stopTargetScans(String targetId) throws IOException {
384 | JSONArray scans = getScans();
385 | for (int i=0; i < scans.size(); i++) {
386 | JSONObject item = scans.getJSONObject(i);
387 | if (item.getString("target_id").equals(targetId)) {
388 | String status = item.getJSONObject("current_session").getString("status");
389 | if(Arrays.asList(scan_status_categories.get("Active")).contains(status)) {
390 | stopScan(item.getString("scan_id"));
391 | }
392 | }
393 | }
394 | }
395 |
396 |
397 | public JSONArray getReportTemplates() throws IOException {
398 | Resp resp = doGet(apiUrl + "/report_templates");
399 | if (resp.respCode == 200) {
400 | return resp.jso.getJSONArray("templates");
401 | }
402 | throw new IOException(SR.getString("bad.response.0", resp.respCode));
403 | }
404 |
405 | public String getReportTemplateName(String reportTemplateId) throws IOException {
406 | JSONArray jsa = getReportTemplates();
407 | for (int i = 0; i < jsa.size(); i++) {
408 | JSONObject item = jsa.getJSONObject(i);
409 | if (item.getString("template_id").equals(reportTemplateId)) {
410 | return item.getString("name");
411 | }
412 | }
413 | return null;
414 | }
415 |
416 | private String getReportStatus(String reportId) throws IOException {
417 | JSONObject jso = doGet(apiUrl + "/reports/" + reportId).jso;
418 | return jso.getString("status");
419 | }
420 |
421 | public void waitReportStatus(String reportId) throws IOException, InterruptedException {
422 | while (!getReportStatus(reportId).equals("completed")) {
423 | Thread.sleep(10000);
424 | }
425 | }
426 |
427 | public String generateReport(String sourceId, String reportTemplateId, String listType) throws IOException, InterruptedException {
428 | //returns download link of html report
429 | JSONObject jso = new JSONObject();
430 | jso.put("template_id", reportTemplateId);
431 | JSONObject jsoChild = new JSONObject();
432 | jsoChild.put("list_type", listType);
433 | List id_list = new ArrayList<>();
434 | id_list.add(sourceId);
435 | jsoChild.put("id_list", id_list);
436 | jso.put("source", jsoChild);
437 | String reportId = doPostLoc(apiUrl + "/reports", jso.toString()).respStr;
438 | waitReportStatus(reportId);
439 | String[] downloadLinkList = doGet(apiUrl + "/reports/" + reportId).jso.getString("download").split(",");
440 | String downloadLink = null;
441 | for (String item : downloadLinkList) {
442 | if (item.contains(".html")) {
443 | downloadLink = item.replaceAll("\"", "").replaceAll("\\[", "".replaceAll("]", ""));
444 | break;
445 | }
446 | }
447 | // download report
448 | return downloadLink;
449 | }
450 |
451 | public Boolean checkThreat(String checkThreat, String scanThreat) {
452 | //return true if the threat detected is equal or greater than threat set
453 | //checkthreat is the level set in plugin config and scanThreat from the scan result
454 | if (checkThreat.equals("DoNotFail")) {
455 | return false;
456 | }
457 | return Arrays.asList(threatCategory.get(checkThreat)).contains(scanThreat);
458 | }
459 |
460 | public Integer getVersion() throws IOException {
461 | if (apiUrl.matches(":\\d+")) {
462 | JSONObject jso = doGet(apiUrl + "/info").jso;
463 | return jso.getInt("major_version");
464 | }
465 | else {
466 | return 13;
467 | }
468 | }
469 |
470 | public String getReportFileName(String urlSource) throws IOException {
471 | URLConnection connection = new URL(urlSource).openConnection();
472 | connection.addRequestProperty("User-Agent", "Mozilla");
473 | String cd = connection.getHeaderField("Content-Disposition");
474 | String fileName = null;
475 | if (cd != null && cd.contains("=")) {
476 | fileName = "Acunetix_" + cd.split("=")[1].trim().replaceAll("\"", "");
477 | }
478 | return fileName;
479 | }
480 |
481 | public void doDownload(String urlSource, FilePath savePath) throws IOException, InterruptedException {
482 | URL url = new URL(urlSource);
483 | savePath.copyFrom(url);
484 | }
485 |
486 |
487 | }
488 |
489 | class ConnectionException extends RuntimeException {
490 | public ConnectionException() {
491 | super(SR.getString("cannot.connect.to.application"));
492 | }
493 | public ConnectionException(String message) {
494 | super(message);
495 | }
496 | }
497 |
--------------------------------------------------------------------------------
/src/main/java/com/acunetix/BuildScanner.java:
--------------------------------------------------------------------------------
1 | package com.acunetix;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsMatchers;
4 | import com.cloudbees.plugins.credentials.CredentialsProvider;
5 | import com.cloudbees.plugins.credentials.common.StandardCredentials;
6 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
7 | import com.cloudbees.plugins.credentials.domains.DomainRequirement;
8 | import hudson.Extension;
9 | import hudson.FilePath;
10 | import hudson.Launcher;
11 | import hudson.model.AbstractProject;
12 | import hudson.model.Item;
13 | import hudson.model.Run;
14 | import hudson.model.TaskListener;
15 | import hudson.remoting.VirtualChannel;
16 | import hudson.security.ACL;
17 | import hudson.tasks.BuildStepDescriptor;
18 | import hudson.util.FormValidation;
19 | import hudson.util.ListBoxModel;
20 | import jenkins.model.Jenkins;
21 | import jenkins.tasks.SimpleBuildStep;
22 | import net.sf.json.JSONArray;
23 | import net.sf.json.JSONObject;
24 | import org.jenkinsci.plugins.plaincredentials.StringCredentials;
25 | import org.kohsuke.stapler.AncestorInPath;
26 | import org.kohsuke.stapler.DataBoundConstructor;
27 | import org.kohsuke.stapler.QueryParameter;
28 | import org.kohsuke.stapler.StaplerRequest;
29 | import org.kohsuke.stapler.verb.POST;
30 |
31 | import javax.annotation.Nonnull;
32 | import javax.net.ssl.SSLHandshakeException;
33 | import java.io.FileNotFoundException;
34 | import java.io.IOException;
35 | import java.io.PrintStream;
36 | import java.util.ArrayList;
37 | import java.util.Collections;
38 |
39 | import static com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials;
40 |
41 |
42 | public class BuildScanner extends hudson.tasks.Builder implements SimpleBuildStep {
43 | private final String profile;
44 | private final String target;
45 | private String targetName;
46 | private final String repTemp;
47 | private String reportTemplateName;
48 | private final String threat;
49 | private final Boolean stopScan;
50 | private Boolean incScan;
51 | private String incScanId;
52 | private final Boolean svRep;
53 | private final Boolean stopTargetScans;
54 |
55 |
56 | @DataBoundConstructor
57 | public BuildScanner(String profile, String target, String repTemp, String threat, Boolean stopScan, Boolean svRep, Boolean incScan, String incScanId, Boolean stopTargetScans) {
58 | this.profile = profile;
59 | this.target = target;
60 | this.repTemp = repTemp;
61 | this.threat = threat;
62 | this.stopScan = stopScan;
63 | this.svRep = svRep;
64 | this.stopTargetScans = stopTargetScans;
65 | this.incScan = incScan;
66 | try {
67 | Engine aac = new Engine(getDescriptor().getgApiUrl(), getDescriptor().getgApiKey());
68 | if (aac.getVersion() > 12) {
69 | if (incScan) {
70 | if (aac.checkIncScanExist(target, profile)) {
71 | this.incScanId = aac.getIncScanId(target,profile);
72 | }
73 | else {
74 | this.incScanId = aac.createIncScan(profile, target);
75 | }
76 | }
77 | else{
78 | this.incScanId = incScanId;
79 | }
80 | }
81 | else {
82 | this.incScan = false;
83 | }
84 | this.targetName = aac.getTargetName(this.target);
85 | this.reportTemplateName = aac.getReportTemplateName(this.repTemp);
86 | } catch (IOException e) {
87 | e.printStackTrace();
88 | }
89 | }
90 |
91 |
92 | public String getProfile() {
93 | return profile;
94 | }
95 |
96 |
97 | public String getTarget() {
98 | return target;
99 | }
100 |
101 | public String getRepTemp() {
102 | return repTemp;
103 | }
104 |
105 | public String getThreat() {
106 | return threat;
107 | }
108 |
109 | public Boolean getStopScan() {
110 | return stopScan;
111 | }
112 |
113 | private String getTargetName() {
114 | return targetName;
115 | }
116 |
117 | private String getReportTemplateName() {
118 | return reportTemplateName;
119 | }
120 |
121 | public Boolean getIncScan() {
122 | return incScan;
123 | }
124 |
125 | public String getIncScanId() {
126 | return incScanId;
127 | }
128 |
129 | public Boolean getStopTargetScans() {
130 | return stopTargetScans;
131 | }
132 |
133 | public Boolean getSvRep() { return svRep; }
134 |
135 | @Override
136 | public void perform(@Nonnull Run, ?> build, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws hudson.AbortException, InterruptedException {
137 | final String PROCESSING = "processing";
138 | final String COMPLETED = "completed";
139 | final String ABORTED = "aborted";
140 | final String SCHEDULED = "scheduled";
141 | final String QUEUED = "queued";
142 | final String NOREPORT = "no_report";
143 | final PrintStream listenerLogger = listener.getLogger();
144 |
145 | Engine engine = new Engine(getDescriptor().getgApiUrl(), getDescriptor().getgApiKey());
146 | String scanId = null;
147 | Boolean scanAbortedExternally = false;
148 | Boolean scanAbortedByUser = false;
149 | String scanStatus = "";
150 | String scanThreat;
151 | Boolean started = false;
152 | Boolean bThreat = false;
153 | Boolean bScheduled = false;
154 |
155 | try {
156 | Engine testengine = new Engine(getDescriptor().getgApiUrl(), getDescriptor().getgApiKey());
157 | int rc = testengine.doTestConnection(getDescriptor().getgApiUrl() + "/me");
158 | if (rc != 200) {
159 | listenerLogger.println(SR.getString("aborting.the.build"));
160 | throw new hudson.AbortException(SR.getString("cannot.connect.to.application"));
161 | }
162 | if ((engine.getTargetName(target) == null)) {
163 | listenerLogger.println(SR.getString("aborting.the.build"));
164 | throw new hudson.AbortException(SR.getString("invalid.target"));
165 | }
166 | else if (!engine.checkScanProfileExists(profile)) {
167 | listenerLogger.println(SR.getString("aborting.the.build"));
168 | throw new hudson.AbortException(SR.getString("invalid.scan_type"));
169 | }
170 | listenerLogger.println(SR.getString("starting.scan.on.target.0", getTargetName()));
171 | if (this.stopTargetScans)
172 | {
173 | engine.stopTargetScans(target);
174 | }
175 | if (this.incScan) {
176 | if (!engine.checkScanExist(incScanId)) {
177 | throw new hudson.AbortException(SR.getString("could.not.find.scan.with.scanid.0.create.new", this.incScanId));
178 | }
179 | engine.triggerIncScan(this.incScanId, false);
180 | scanId = this.incScanId;
181 | }
182 | else {
183 | scanId = engine.startScan(profile, target, false);
184 | if (scanId == null) {
185 | listenerLogger.println(SR.getString("aborting.the.build"));
186 | throw new hudson.AbortException(SR.getString("cannot.connect.to.application"));
187 | }
188 | }
189 | while (!scanStatus.equals(COMPLETED)) {
190 | if (scanStatus.equals(PROCESSING) && !started) {
191 | started = true;
192 | listenerLogger.println(SR.getString("scan.started"));
193 | listenerLogger.println(SR.getString("view.scan.status") + getDescriptor().getgApiUrl().replace("api/v1", "#") + "/scans/" + scanId + "/info");
194 | }
195 | if (scanStatus.equals(SCHEDULED) && !bScheduled) {
196 | bScheduled = true;
197 | listenerLogger.println(SR.getString("the.scan.is.in.scheduled.state"));
198 | }
199 | if (scanStatus.equals(ABORTED)) {
200 | scanAbortedExternally = true;
201 | listenerLogger.println(SR.getString("aborting.the.build"));
202 | throw new hudson.AbortException(SR.getString("scan.aborted.outside"));
203 | }
204 |
205 | scanThreat = engine.getScanThreat(scanId);
206 | if (engine.checkThreat(threat, scanThreat)) {
207 | bThreat = true;
208 | //listenerLogger.println(SR.getString("scan.threat.0.1", Engine.getThreatName(scanThreat), this.getThreat()));
209 | listenerLogger.println(SR.getString("scan.threat.0", Engine.getThreatName(scanThreat)));
210 | listenerLogger.println(SR.getString("check.vulnerabilities.found") + getDescriptor().getgApiUrl().replace("api/v1", "#") + "/scans/" + scanId + "/vulnerabilities");
211 | listenerLogger.println(SR.getString("aborting.the.build"));
212 | throw new hudson.AbortException(SR.getString("scan.threat"));
213 | }
214 | Thread.sleep(10000);
215 | scanStatus = engine.getScanStatus(scanId);
216 | }
217 | listenerLogger.println(SR.getString("scan.completed"));
218 | } catch (InterruptedException e) {
219 | scanAbortedByUser = true;
220 | listenerLogger.println(SR.getString("aborting.the.build"));
221 | throw new hudson.AbortException(SR.getString("build.aborted"));
222 | } catch (hudson.AbortException e) {
223 | throw e;
224 | } catch (SSLHandshakeException e) {
225 | e.printStackTrace();
226 | throw new hudson.AbortException(SR.getString("certificate.to.the.java.ca.store"));
227 | } catch (FileNotFoundException e) {
228 | e.printStackTrace();
229 | if (!engine.checkScanExist(scanId)) {
230 | listenerLogger.println(SR.getString("aborting.the.build"));
231 | scanAbortedExternally = true;
232 | throw new hudson.AbortException(SR.getString("could.not.find.scan.with.scanid.0", scanId));
233 | }
234 | } catch (java.net.ConnectException e) {
235 | e.printStackTrace();
236 | listenerLogger.println(SR.getString("aborting.the.build"));
237 | scanAbortedExternally = true;
238 | throw new hudson.AbortException(SR.getString("could.not.connect.to.application.connection.refused"));
239 | } catch (ConnectionException e) {
240 | listenerLogger.println(SR.getString("aborting.the.build"));
241 | throw new hudson.AbortException(SR.getString("cannot.connect.to.application"));
242 | } catch (Exception e) {
243 | e.printStackTrace();
244 | listenerLogger.println(e.getMessage());
245 | } finally {
246 | try {
247 | if (stopScan && scanId != null && !scanAbortedExternally && (bThreat || scanAbortedByUser) && !bScheduled) {
248 | engine.stopScan(scanId);
249 | try {
250 | String status = "";
251 | while (!status.equals(ABORTED) && !status.equals(COMPLETED)) {
252 | Thread.sleep(10000);
253 | status = engine.getScanStatus(scanId);
254 | }
255 | listenerLogger.println(SR.getString("the.scan.was.stopped"));
256 | } catch (InterruptedException | IOException e) {
257 | e.printStackTrace();
258 | listenerLogger.println(e.getMessage());
259 | }
260 | }
261 | if (!repTemp.equals(NOREPORT) && scanId != null && !scanAbortedByUser && !scanAbortedExternally) {
262 | listenerLogger.println(SR.getString("generating.0.report", getReportTemplateName()));
263 | Thread.sleep(10000);
264 | String downloadLink = engine.generateReport(scanId, repTemp, "scans");
265 | listenerLogger.println(SR.getString("scan.report.download.link.0", engine.getUrl(getDescriptor().getgApiUrl(), downloadLink)));
266 | if (svRep) {
267 | VirtualChannel channel = workspace.getChannel();
268 | String reportName = engine.getReportFileName(engine.getUrl(getDescriptor().getgApiUrl(), downloadLink));
269 | FilePath reportFile = new hudson.FilePath(channel, workspace.getRemote() + "/" + reportName);
270 | engine.doDownload(engine.getUrl(getDescriptor().getgApiUrl(), downloadLink), reportFile);
271 | if (reportFile.exists()) {
272 | String savedReportUrl = Jenkins.getInstance().getRootUrl() + build.getParent().getUrl() + "ws/" + reportName;
273 | listenerLogger.println(SR.getString("report.saved.in.workspace.0", reportName));
274 | } else {
275 | listenerLogger.println(SR.getString("invalid.report.file.path.0", reportName));
276 | }
277 | }
278 | }
279 | } catch (InterruptedException | IOException e) {
280 | e.printStackTrace();
281 | listenerLogger.println(e.getMessage());
282 | }
283 | }
284 | }
285 |
286 | @Override
287 | public DescriptorImpl getDescriptor() {
288 | return (DescriptorImpl) super.getDescriptor();
289 | }
290 |
291 | @Extension
292 | public static final class DescriptorImpl extends BuildStepDescriptor {
293 | private String gApiUrl;
294 | private String gApiKeyID;
295 |
296 | public DescriptorImpl() {
297 | load();
298 | }
299 | @POST
300 | public FormValidation doTestConnection(@QueryParameter("gApiUrl") final String ApiUrl,
301 | @QueryParameter("gApiKey") final String apiKey) {
302 | Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
303 | try {
304 | if (ApiUrl.length() == 0)
305 | return FormValidation.error(SR.getString("please.set.the.api.url"));
306 | if (!ApiUrl.contains("/api/v1"))
307 | return FormValidation.error(SR.getString("invalid.api.url"));
308 | Engine apio = new Engine(ApiUrl, getgApiKey());
309 | int respCode = apio.doTestConnection(ApiUrl + "/me");
310 | if (respCode == 200) {
311 | return FormValidation.ok(SR.getString("connected.successfully"));
312 | }
313 | } catch (SSLHandshakeException e) {
314 | e.printStackTrace();
315 | return FormValidation.error(SR.getString("certificate.to.the.java.ca.store"));
316 | } catch (IOException e) {
317 | e.printStackTrace();
318 | return FormValidation.error(e.getMessage());
319 | }
320 | return FormValidation.error(SR.getString("cannot.connect.to.application"));
321 | }
322 |
323 | public boolean isApplicable(Class extends AbstractProject> aClass) {
324 | return true;
325 | }
326 |
327 | /**
328 | * This human readable name is used in the configuration screen.
329 | */
330 | public String getDisplayName() {
331 | return "Acunetix";
332 | }
333 |
334 | @Override
335 | public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
336 | gApiUrl = formData.getString("gApiUrl");
337 | gApiKeyID = formData.getString("gApiKeyID");
338 | save();
339 | return super.configure(req, formData);
340 | }
341 |
342 | public String getgApiUrl() {
343 | return gApiUrl;
344 | }
345 |
346 | private String getgApiKeyID() {return gApiKeyID;}
347 |
348 | private String getgApiKey() {
349 | StandardCredentials credentials = null;
350 | try {
351 | credentials = CredentialsMatchers.firstOrNull(
352 | lookupCredentials(StandardCredentials.class, (Item) null, ACL.SYSTEM, new ArrayList()),
353 | CredentialsMatchers.withId(gApiKeyID));
354 | }
355 | catch (NullPointerException e) {
356 | throw new ConnectionException(SR.getString("api.key.not.set"));
357 | }
358 | if (credentials != null) {
359 | if (credentials instanceof StringCredentials) {
360 | return ((StringCredentials) credentials).getSecret().getPlainText();
361 | }
362 | }
363 | throw new IllegalStateException("Could not find Acunetix API Key ID: " + gApiKeyID);
364 | }
365 |
366 |
367 |
368 | public ListBoxModel doFillProfileItems() throws IOException {
369 | ListBoxModel items = new ListBoxModel();
370 | Engine apio = new Engine(gApiUrl, getgApiKey());
371 | JSONArray jsa = apio.getScanningProfiles();
372 | for (int i = 0; i < jsa.size(); i++) {
373 | JSONObject item = jsa.getJSONObject(i);
374 | String profile_name = item.getString("name");
375 | String profile_id = item.getString("profile_id");
376 | items.add(profile_name, profile_id);
377 | }
378 | return items;
379 | }
380 |
381 | public ListBoxModel doFillTargetItems() throws IOException {
382 | ListBoxModel items = new ListBoxModel();
383 | Engine apio = new Engine(gApiUrl, getgApiKey());
384 | JSONArray jsa = apio.getTargets();
385 | for (int i = 0; i < jsa.size(); i++) {
386 | JSONObject item = jsa.getJSONObject(i);
387 | String mi = item.getString("manual_intervention");
388 | if (mi.equals("null") || mi.equals("false")) {
389 | String address = item.getString("address");
390 | String target_id = item.getString("target_id");
391 | String description = item.getString("description");
392 | String target_name = address;
393 | if (description.length() > 0) {
394 | if (description.length() > 100) {
395 | description = description.substring(0, 100);
396 | }
397 | target_name += " (" + description + ")";
398 | }
399 | items.add(target_name, target_id);
400 | }
401 | }
402 | return items;
403 | }
404 |
405 | public ListBoxModel doFillRepTempItems() throws IOException {
406 | ListBoxModel items = new ListBoxModel();
407 | Engine apio = new Engine(gApiUrl, getgApiKey());
408 | JSONArray jsa = apio.getReportTemplates();
409 | items.add("Do not generate a report", "no_report");
410 | for (int i = 0; i < jsa.size(); i++) {
411 | JSONObject item = jsa.getJSONObject(i);
412 | String group = item.getString("group");
413 | String reportTemplate_name = item.getString("name");
414 | String template_id = item.getString("template_id");
415 | if (!reportTemplate_name.equals("Scan Comparison")) {
416 | items.add(reportTemplate_name, template_id);
417 | }
418 | }
419 | return items;
420 | }
421 |
422 | public ListBoxModel doFillThreatItems() throws IOException {
423 | ListBoxModel items = new ListBoxModel();
424 | items.add("Do not fail the build", "DoNotFail");
425 | items.add("High", "High");
426 | items.add("Medium or High", "Medium");
427 | items.add("Low, Medium or High", "Low");
428 | return items;
429 | }
430 |
431 | public ListBoxModel doFillGApiKeyIDItems(
432 | @AncestorInPath Item item) {
433 | StandardListBoxModel result = new StandardListBoxModel();
434 | if (item == null) {
435 | if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
436 | return result.includeCurrentValue(gApiKeyID);
437 | }
438 | } else {
439 | if (!item.hasPermission(Item.EXTENDED_READ)
440 | && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
441 | return result.includeCurrentValue(gApiKeyID);
442 | }
443 | }
444 | if (gApiKeyID != null) {
445 | result.includeMatchingAs(ACL.SYSTEM, Jenkins.getInstance(), StringCredentials.class,
446 | Collections. emptyList(), CredentialsMatchers.allOf(CredentialsMatchers.withId(gApiKeyID)));
447 | }
448 | return result
449 | .includeMatchingAs(ACL.SYSTEM, Jenkins.getInstance(), StringCredentials.class,
450 | Collections. emptyList(), CredentialsMatchers.allOf(CredentialsMatchers.instanceOf(StringCredentials.class)));
451 | }
452 |
453 | public Boolean testVersion() {
454 | Boolean res = false;
455 | try {
456 | Engine aac = new Engine(getgApiUrl(), getgApiKey());
457 | res = aac.getVersion() > 12;
458 | } catch (IOException e) {
459 | e.printStackTrace();
460 | }
461 | return res;
462 | }
463 | }
464 | }
465 |
466 |
--------------------------------------------------------------------------------