{
98 | private static final long serialVersionUID = 1L;
99 |
100 | @StepContextParameter
101 | private transient Run, ?> build;
102 |
103 | @StepContextParameter
104 | private transient TaskListener taskListener;
105 |
106 | @StepContextParameter
107 | private transient FilePath filePath;
108 |
109 | @Inject
110 | private transient ContrastAgentStep step;
111 |
112 | @Override
113 | public Void run() throws AbortException {
114 | String agentFileName = VulnerabilityTrendHelper.getDefaultAgentFileNameFromString(step.getAgentType());
115 |
116 | TeamServerProfile teamServerProfile = VulnerabilityTrendHelper.getProfile(step.getProfile());
117 | File agentFile = new File(step.getOutputDirectory() + "/" + agentFileName);
118 |
119 | if (teamServerProfile == null) {
120 | VulnerabilityTrendHelper.logMessage(taskListener, "Unable to find TeamServer profile.");
121 | throw new AbortException("Unable to find TeamServer profile.");
122 | }
123 |
124 | VulnerabilityTrendHelper.logMessage(taskListener, "Building connected to TeamServer with profile " + step.getProfile());
125 | ContrastSDK contrastSDK = VulnerabilityTrendHelper.createSDK(teamServerProfile.getUsername(), teamServerProfile.getServiceKey(),
126 | teamServerProfile.getApiKey(), teamServerProfile.getTeamServerUrl());
127 |
128 | VulnerabilityTrendHelper.logMessage(taskListener, "Downloading the agent to " + agentFile.getAbsolutePath());
129 |
130 | byte[] agent;
131 |
132 | try {
133 | agent = contrastSDK.getAgent(VulnerabilityTrendHelper.getAgentTypeFromString(step.getAgentType()),
134 | teamServerProfile.getOrgUuid());
135 | } catch (Exception e) {
136 | VulnerabilityTrendHelper.logMessage(taskListener, e.getMessage());
137 | throw new AbortException("Unable to download agent from TeamServer.");
138 | }
139 |
140 | VulnerabilityTrendHelper.logMessage(taskListener, "Saving agent to file.");
141 | /* Regular Java io will not work on remote Jenkins slaves.
142 | * The agent file will not persist on the slave with java.io.File, probably due to how the Jenkins agent technology works.
143 | * It is better to use the Hudson libraries. */
144 | try {
145 | filePath.child(step.getOutputDirectory()).mkdirs();
146 | OutputStream outputStream = null;
147 | InputStream inputStream = null;
148 | try {
149 | outputStream = filePath.child(step.getOutputDirectory() + "/" + agentFileName).write();
150 | inputStream = new ByteArrayInputStream(agent);
151 | IOUtils.copy(inputStream,outputStream);
152 | } finally {
153 | IOUtils.closeQuietly(outputStream);
154 | }
155 | } catch (Exception e) {
156 | VulnerabilityTrendHelper.logMessage(taskListener, e.getMessage());
157 | throw new AbortException("Unable to save file to " + step.getOutputDirectory() + " The exception message is " + e.getMessage() + " The stack trace is " + Arrays.toString(e.getStackTrace()));
158 | }
159 |
160 | return null;
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/ContrastPluginConfig.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import com.contrastsecurity.exceptions.UnauthorizedException;
4 | import com.contrastsecurity.models.Organizations;
5 | import com.contrastsecurity.sdk.ContrastSDK;
6 | import hudson.Extension;
7 | import hudson.model.AbstractProject;
8 | import hudson.model.JobProperty;
9 | import hudson.model.JobPropertyDescriptor;
10 | import hudson.model.Result;
11 | import hudson.util.CopyOnWriteList;
12 | import hudson.util.FormValidation;
13 | import hudson.util.ListBoxModel;
14 | import hudson.util.Secret;
15 | import jenkins.model.Jenkins;
16 | import net.sf.json.JSONArray;
17 | import net.sf.json.JSONObject;
18 | import org.apache.commons.lang.StringUtils;
19 | import org.kohsuke.stapler.DataBoundConstructor;
20 | import org.kohsuke.stapler.QueryParameter;
21 | import org.kohsuke.stapler.StaplerRequest;
22 |
23 | import javax.servlet.ServletException;
24 | import java.io.IOException;
25 | import java.util.ArrayList;
26 | import java.util.Collection;
27 |
28 | /**
29 | * Contrast Plugin Configuration
30 | *
31 | * Adds the necessary configuration options to a job's properties. Used in VulnerabilityTrendRecorder
32 | */
33 | public class ContrastPluginConfig extends JobProperty> {
34 |
35 | @DataBoundConstructor
36 | public ContrastPluginConfig() {
37 |
38 | }
39 |
40 | @Override
41 | public ContrastPluginConfigDescriptor getDescriptor() {
42 | Jenkins instance = Jenkins.getInstance();
43 |
44 | if (instance != null) {
45 | return (ContrastPluginConfigDescriptor) instance.getDescriptor(getClass());
46 | } else {
47 | return null;
48 | }
49 | }
50 |
51 | @Extension
52 | public static class ContrastPluginConfigDescriptor extends JobPropertyDescriptor {
53 |
54 | private CopyOnWriteList teamServerProfiles = new CopyOnWriteList<>();
55 |
56 | private CopyOnWriteList globalThresholdConditions = new CopyOnWriteList<>();
57 |
58 | public ContrastPluginConfigDescriptor() {
59 | super(ContrastPluginConfig.class);
60 | load();
61 | }
62 |
63 | @Override
64 | public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
65 | final JSONArray array = json.optJSONArray("profile");
66 |
67 | if (array != null) {
68 | teamServerProfiles.replaceBy(req.bindJSONToList(TeamServerProfile.class, array));
69 | } else {
70 | if (json.keySet().isEmpty()) {
71 | teamServerProfiles = new CopyOnWriteList<>();
72 | } else {
73 | teamServerProfiles.replaceBy(req.bindJSON(TeamServerProfile.class, json.getJSONObject("profile")));
74 | }
75 | }
76 |
77 | // refresh all org rules and applications
78 | for (TeamServerProfile teamServerProfile : teamServerProfiles) {
79 | ContrastSDK contrastSDK = VulnerabilityTrendHelper.createSDK(teamServerProfile.getUsername(), teamServerProfile.getServiceKey(),
80 | teamServerProfile.getApiKey(), teamServerProfile.getTeamServerUrl());
81 |
82 | teamServerProfile.setVulnerabilityTypes(VulnerabilityTrendHelper.saveRules(contrastSDK, teamServerProfile.getOrgUuid()));
83 |
84 | teamServerProfile.setApps(VulnerabilityTrendHelper.saveApplicationIds(contrastSDK, teamServerProfile.getOrgUuid()));
85 | }
86 |
87 |
88 | final JSONArray globalThresholdConditionJsonArray = json.optJSONArray("globalThresholdCondition");
89 |
90 | if (globalThresholdConditionJsonArray != null) {
91 | globalThresholdConditions.replaceBy(req.bindJSONToList(GlobalThresholdCondition.class, globalThresholdConditionJsonArray));
92 | } else {
93 | if (json.keySet().isEmpty()) {
94 | globalThresholdConditions = new CopyOnWriteList<>();
95 | } else {
96 | globalThresholdConditions.replaceBy(req.bindJSON(GlobalThresholdCondition.class, json.getJSONObject("globalThresholdCondition")));
97 | }
98 | }
99 |
100 | save();
101 |
102 | return true;
103 | }
104 |
105 | @SuppressWarnings("unused")
106 | public ListBoxModel doFillTeamServerProfileNameItems() {
107 | return VulnerabilityTrendHelper.getProfileNames();
108 | }
109 |
110 | /**
111 | * Fills the Threshold Category select drop down with vulnerability types for the configured profile.
112 | *
113 | * @return ListBoxModel filled with vulnerability types.
114 | */
115 | public ListBoxModel doFillThresholdVulnTypeItems(@QueryParameter("teamServerProfileName") final String teamServerProfileName) throws IOException {
116 | return VulnerabilityTrendHelper.getVulnerabilityTypes(teamServerProfileName);
117 | }
118 |
119 | /**
120 | * Validates the configured TeamServer profile by attempting to get the default profile for the username.
121 | *
122 | * @param username String username of the TeamServer user
123 | * @param apiKey String apiKey of the TeamServer user
124 | * @param serviceKey String serviceKey of the TeamServer user
125 | * @param teamServerUrl String TeamServer Url
126 | * @return FormValidation
127 | * @throws IOException
128 | * @throws ServletException
129 | */
130 | public FormValidation doTestTeamServerConnection(@QueryParameter("username") final String username,
131 | @QueryParameter("apiKey") final Secret apiKey,
132 | @QueryParameter("serviceKey") final Secret serviceKey,
133 | @QueryParameter("teamServerUrl") final String teamServerUrl) throws IOException, ServletException {
134 |
135 | if (StringUtils.isEmpty(username)) {
136 | return FormValidation.error("Connection error: Username cannot be empty.");
137 | }
138 |
139 | if (StringUtils.isEmpty(apiKey.getPlainText())) {
140 | return FormValidation.error("Connection error: Api Key cannot be empty.");
141 | }
142 |
143 | if (StringUtils.isEmpty(serviceKey.getPlainText())) {
144 | return FormValidation.error("Connection error: Service Key cannot be empty");
145 | }
146 |
147 | if (StringUtils.isEmpty(teamServerUrl)) {
148 | return FormValidation.error("Connection error: Contrast URL cannot be empty.");
149 | }
150 |
151 | if (!teamServerUrl.endsWith("/Contrast/api")) {
152 | return FormValidation.error("Connection error: Contrast Url does not end with /Contrast/api.");
153 | }
154 |
155 | try {
156 | ContrastSDK contrastSDK = VulnerabilityTrendHelper.createSDK(username, serviceKey.getPlainText(), apiKey.getPlainText(), teamServerUrl);
157 |
158 | Organizations organizations = contrastSDK.getProfileDefaultOrganizations();
159 |
160 | if (organizations == null || organizations.getOrganization() == null) {
161 | return FormValidation.error("Connection error: No organization found, Check your credentials and URL.");
162 | }
163 | Collection validationCollection = new ArrayList<>();
164 |
165 | validationCollection.add(FormValidation.ok("Successfully connected to Contrast."));
166 |
167 | if(VulnerabilityTrendHelper.isEnabledJobOutcomePolicyExist(contrastSDK,organizations.getOrganization().getOrgUuid())) {
168 | validationCollection.add(FormValidation.warning("Your Contrast administrator has set a policy for vulnerability thresholds. " +
169 | "The Contrast policy overrides Jenkins security controls for applications included in both."));
170 | }
171 | return FormValidation.aggregate(validationCollection);
172 | } catch (IOException | UnauthorizedException e) {
173 | return FormValidation.error(String.format("Unable to connect to Contrast. %s", e.getMessage()));
174 | }
175 | }
176 |
177 | public TeamServerProfile[] getTeamServerProfiles() {
178 | final TeamServerProfile[] profileArray = new TeamServerProfile[teamServerProfiles.size()];
179 |
180 | return teamServerProfiles.toArray(profileArray);
181 | }
182 |
183 | public GlobalThresholdCondition[] getGlobalThresholdConditions() {
184 | final GlobalThresholdCondition[] globalThresholdConditionArray = new GlobalThresholdCondition[globalThresholdConditions.size()];
185 |
186 | return globalThresholdConditions.toArray(globalThresholdConditionArray);
187 | }
188 |
189 | /**
190 | * Fills the Threshold Severity select drop down with severities for the configured application.
191 | *
192 | * @return ListBoxModel filled with severities.
193 | */
194 | public ListBoxModel doFillThresholdSeverityItems() {
195 | return VulnerabilityTrendHelper.getSeverityListBoxModel();
196 | }
197 |
198 | /**
199 | * Validation of the 'thresholdCount' form Field.
200 | *
201 | * @param value This parameter receives the value that the user has typed.
202 | * @return Indicates the outcome of the validation. This is sent to the browser.
203 | */
204 | public FormValidation doCheckThresholdCount(@QueryParameter String value) {
205 |
206 | if (!value.isEmpty()) {
207 | try {
208 | int temp = Integer.parseInt(value);
209 |
210 | if (temp < 0) {
211 | return FormValidation.error("Please enter a positive integer.");
212 | }
213 |
214 | } catch (NumberFormatException e) {
215 | return FormValidation.error("Please enter a valid integer.");
216 | }
217 | } else {
218 | return FormValidation.error("Please enter a positive integer.");
219 | }
220 |
221 | return FormValidation.ok();
222 | }
223 |
224 | /**
225 | * Validation of the 'name' form Field.
226 | *
227 | * @param value This parameter receives the value that the user has typed.
228 | * @return Indicates the outcome of the validation. This is sent to the browser.
229 | */
230 | public FormValidation doCheckProfileName(@QueryParameter String value) {
231 | if (value.length() == 0)
232 | return FormValidation.error("Please set a profile name.");
233 |
234 | return FormValidation.ok();
235 | }
236 |
237 | /**
238 | * Validation of the 'username' form Field.
239 | *
240 | * @param value This parameter receives the value that the user has typed.
241 | * @return Indicates the outcome of the validation. This is sent to the browser.
242 | */
243 | public FormValidation doCheckUsername(@QueryParameter String value) {
244 | if (value.length() == 0)
245 | return FormValidation.error("Please set a username.");
246 | return FormValidation.ok();
247 | }
248 |
249 | /**
250 | * Validation of the 'apiKey' form Field.
251 | *
252 | * @param value This parameter receives the value that the user has typed.
253 | * @return Indicates the outcome of the validation. This is sent to the browser.
254 | */
255 | public FormValidation doCheckApiKey(@QueryParameter String value) {
256 | if (value.length() == 0)
257 | return FormValidation.error("Please set an API Key.");
258 | return FormValidation.ok();
259 | }
260 |
261 | /**
262 | * Validation of the 'serviceKey' form Field.
263 | *
264 | * @param value This parameter receives the value that the user has typed.
265 | * @return Indicates the outcome of the validation. This is sent to the browser.
266 | */
267 | public FormValidation doCheckServiceKey(@QueryParameter String value) {
268 | if (value.length() == 0)
269 | return FormValidation.error("Please set a Service Key.");
270 | return FormValidation.ok();
271 | }
272 |
273 | /**
274 | * Validation of the 'thresholdSeverity' form Field.
275 | *
276 | * @param value This parameter receives the value that the user has typed.
277 | * @return Indicates the outcome of the validation. This is sent to the browser.
278 | */
279 | public FormValidation doCheckThresholdSeverity(@QueryParameter String value) {
280 | return FormValidation.ok();
281 | }
282 |
283 | /**
284 | * Validation of the 'teamServerUrl' form Field.
285 | *
286 | * @param value This parameter receives the value that the user has typed.
287 | * @return Indicates the outcome of the validation. This is sent to the browser.
288 | */
289 | public FormValidation doCheckTeamServerUrl(@QueryParameter String value) {
290 | if (value.length() == 0)
291 | return FormValidation.error("Please set a TeamServer Url.");
292 | return FormValidation.ok();
293 | }
294 |
295 | /**
296 | * Validation of the 'orgUuid' form Field.
297 | *
298 | * @param value This parameter receives the value that the user has typed.
299 | * @return Indicates the outcome of the validation. This is sent to the browser.
300 | */
301 | public FormValidation doCheckOrgUuid(@QueryParameter String value) {
302 | if (value.length() == 0)
303 | return FormValidation.error("Please set an Organization Uuid.");
304 | return FormValidation.ok();
305 | }
306 |
307 | @Override
308 | public String getDisplayName() {
309 | return "Contrast Plugin Configuration";
310 | }
311 |
312 |
313 | public ListBoxModel doFillVulnerableBuildResultItems() {
314 | ListBoxModel items = new ListBoxModel();
315 |
316 | items.add(Result.FAILURE.toString());
317 | items.add(Result.SUCCESS.toString());
318 | items.add(Result.UNSTABLE.toString());
319 | items.add(Result.NOT_BUILT.toString());
320 | items.add(Result.ABORTED.toString());
321 |
322 | return items;
323 | }
324 | }
325 | }
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/GlobalThresholdCondition.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import org.kohsuke.stapler.DataBoundConstructor;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | @Getter
11 | @Setter
12 | public class GlobalThresholdCondition {
13 |
14 | private String teamServerProfileName;
15 |
16 | private Integer thresholdCount;
17 |
18 | private String thresholdSeverity;
19 |
20 | private String thresholdVulnType;
21 |
22 | private boolean autoRemediated;
23 | private boolean confirmed;
24 | private boolean suspicious;
25 | private boolean notAProblem;
26 | private boolean remediated;
27 | private boolean reported;
28 | private boolean fixed;
29 | private boolean beingTracked;
30 | private boolean untracked;
31 |
32 | @DataBoundConstructor
33 | public GlobalThresholdCondition(String teamServerProfileName, Integer thresholdCount, String thresholdSeverity, String thresholdVulnType, boolean autoRemediated,
34 | boolean confirmed, boolean suspicious, boolean notAProblem, boolean remediated,
35 | boolean reported, boolean fixed, boolean beingTracked, boolean untracked) {
36 |
37 | this.teamServerProfileName = teamServerProfileName;
38 | this.thresholdCount = thresholdCount;
39 | this.thresholdSeverity = thresholdSeverity;
40 | this.thresholdVulnType = thresholdVulnType;
41 | this.autoRemediated = autoRemediated;
42 | this.confirmed = confirmed;
43 | this.suspicious = suspicious;
44 | this.notAProblem = notAProblem;
45 | this.remediated = remediated;
46 | this.reported = reported;
47 | this.fixed = fixed;
48 | this.beingTracked = beingTracked;
49 | this.untracked = untracked;
50 | }
51 |
52 | public List getVulnerabilityStatuses() {
53 | List status = new ArrayList();
54 | if (autoRemediated) {
55 | status.add(Constants.VULNERABILITY_STATUS_AUTO_REMEDIATED);
56 | }
57 | if (confirmed) {
58 | status.add(Constants.VULNERABILITY_STATUS_CONFIRMED);
59 | }
60 | if (suspicious) {
61 | status.add(Constants.VULNERABILITY_STATUS_SUSPICIOUS);
62 | }
63 | if (notAProblem) {
64 | status.add(Constants.VULNERABILITY_STATUS_NOT_A_PROBLEM);
65 | }
66 | if (remediated) {
67 | status.add(Constants.VULNERABILITY_STATUS_REMEDIATED);
68 | }
69 | if (reported) {
70 | status.add(Constants.VULNERABILITY_STATUS_REPORTED);
71 | }
72 | if (fixed) {
73 | status.add(Constants.VULNERABILITY_STATUS_FIXED);
74 | }
75 | if (beingTracked) {
76 | status.add(Constants.VULNERABILITY_STATUS_BEING_TRACKED);
77 | }
78 | if (untracked) {
79 | status.add(Constants.VULNERABILITY_STATUS_UNTRACKED);
80 | }
81 | return status;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/MatchBy.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | public enum MatchBy {
4 | APPLICATION_ORIGIN_NAME, //The name that the application was instrumented with.
5 | APPLICATION_ID,
6 | APPLICATION_SHORT_NAME
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/TeamServerProfile.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import hudson.util.Secret;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | import java.util.List;
9 |
10 | @Setter
11 | public class TeamServerProfile {
12 |
13 | @Getter
14 | private String name;
15 |
16 | @Getter
17 | private String username;
18 |
19 | private Secret apiKey;
20 |
21 | private Secret serviceKey;
22 |
23 | @Getter
24 | private String orgUuid;
25 |
26 | @Getter
27 | private String teamServerUrl;
28 |
29 | @Getter
30 | private String applicationName;
31 |
32 | @Getter
33 | private List vulnerabilityTypes;
34 |
35 | private boolean applyVulnerableBuildResultOnContrastError;
36 |
37 | @Getter
38 | private String vulnerableBuildResult;
39 |
40 | @Getter
41 | private boolean allowGlobalThresholdConditionsOverride;
42 |
43 | @Getter
44 | private List apps;
45 |
46 | //Compatibility fix for plugin versions <= 3.6
47 | /**
48 | * @Deprecated
49 | * Use {@link TeamServerProfile#applyVulnerableBuildResultOnContrastError}
50 | */
51 | @Getter
52 | @Deprecated
53 | private boolean failOnWrongApplicationId;
54 |
55 | /////// Compatibility fix for plugin versions <=2.6
56 | /**
57 | * @Deprecated
58 | * Use {@link TeamServerProfile#applyVulnerableBuildResultOnContrastError}
59 | */
60 | @Getter
61 | @Deprecated
62 | private boolean failOnWrongApplicationName;
63 |
64 | @DataBoundConstructor
65 | public TeamServerProfile(String name, String username, String apiKey, String serviceKey, String orgUuid,
66 | String teamServerUrl, boolean applyVulnerableBuildResultOnContrastError,
67 | String vulnerableBuildResult, boolean allowGlobalThresholdConditionsOverride) {
68 | this.name = name;
69 | this.username = username;
70 | this.apiKey = Secret.fromString(apiKey);
71 | this.serviceKey = Secret.fromString(serviceKey);
72 | this.orgUuid = orgUuid;
73 | this.teamServerUrl = teamServerUrl;
74 | this.applyVulnerableBuildResultOnContrastError = applyVulnerableBuildResultOnContrastError;
75 | this.vulnerableBuildResult = vulnerableBuildResult;
76 | this.allowGlobalThresholdConditionsOverride = allowGlobalThresholdConditionsOverride;
77 | }
78 |
79 | public Secret getSecretApiKey() {
80 | return apiKey;
81 | }
82 |
83 | public Secret getSecretServiceKey() {
84 | return serviceKey;
85 | }
86 |
87 | String getApiKey() {
88 | return apiKey.getPlainText();
89 | }
90 |
91 | String getServiceKey() {
92 | return serviceKey.getPlainText();
93 | }
94 |
95 | public boolean isApplyVulnerableBuildResultOnContrastError() {
96 | //backwards compatability fix for profiles with old configurations
97 | if(!this.applyVulnerableBuildResultOnContrastError && (this.isFailOnWrongApplicationId() || this.isFailOnWrongApplicationName())) {
98 | return true;
99 | }
100 | return this.applyVulnerableBuildResultOnContrastError;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/ThresholdCondition.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 |
4 | import com.contrastsecurity.exceptions.UnauthorizedException;
5 | import com.contrastsecurity.sdk.ContrastSDK;
6 | import hudson.Extension;
7 | import hudson.RelativePath;
8 | import hudson.model.AbstractDescribableImpl;
9 | import hudson.model.Descriptor;
10 | import hudson.util.ComboBoxModel;
11 | import hudson.util.FormValidation;
12 | import hudson.util.ListBoxModel;
13 | import jenkins.model.Jenkins;
14 | import lombok.Getter;
15 | import lombok.Setter;
16 | import org.kohsuke.stapler.DataBoundConstructor;
17 | import org.kohsuke.stapler.DataBoundSetter;
18 | import org.kohsuke.stapler.QueryParameter;
19 |
20 | import java.io.IOException;
21 | import java.util.ArrayList;
22 | import java.util.Calendar;
23 | import java.util.List;
24 | import java.util.StringJoiner;
25 |
26 | /**
27 | * ThresholdCondition class contains the variables and logic to populate the conditions when verifying for vulnerabilities.
28 | */
29 | public class ThresholdCondition extends AbstractDescribableImpl {
30 |
31 | @Setter
32 | @Getter
33 | private Integer thresholdCount;
34 |
35 | @Setter
36 | @Getter
37 | private String thresholdSeverity;
38 |
39 | @Setter
40 | @Getter
41 | private String thresholdVulnType;
42 |
43 | @Setter
44 | @Getter
45 | private ApplicationDefinition applicationDefinition;
46 |
47 | @Getter
48 | private String applicationId;
49 |
50 | @DataBoundSetter
51 | public void setApplicationId(String applicationId) {
52 | this.applicationId = applicationId;
53 | }
54 |
55 | //// Compatibility fix for plugin versions <=2.6
56 | @Setter
57 | @Getter
58 | private String applicationName;
59 |
60 | /**
61 | * Name that was used to instrument the agent
62 | */
63 | @Setter
64 | @Getter
65 | private String applicationOriginName;
66 |
67 | /**
68 | * Type of agent used to instrument the application
69 | */
70 | @Setter
71 | @Getter
72 | private String agentType;
73 |
74 | /**
75 | * Only gets set when application is not initiated
76 | */
77 | @Setter
78 | @Getter
79 | private boolean failOnAppNotFound;
80 |
81 | /**
82 | * 0 = instrumented
83 | * 1 = not instrumented
84 | */
85 | @Setter
86 | @Getter
87 | private int applicationState;
88 |
89 | @Setter
90 | @Getter
91 | private MatchBy matchBy;
92 |
93 | @Setter
94 | @Getter
95 | private boolean autoRemediated;
96 | @Setter
97 | @Getter
98 | private boolean confirmed;
99 | @Setter
100 | @Getter
101 | private boolean suspicious;
102 | @Setter
103 | @Getter
104 | private boolean notAProblem;
105 | @Setter
106 | @Getter
107 | private boolean remediated;
108 | @Setter
109 | @Getter
110 | private boolean reported;
111 |
112 | @Setter
113 | @Getter
114 | private boolean fixed;
115 | @Setter
116 | @Getter
117 | private boolean beingTracked;
118 | @Setter
119 | @Getter
120 | private boolean untracked;
121 |
122 | @DataBoundConstructor
123 | public ThresholdCondition(Integer thresholdCount, String thresholdSeverity, String thresholdVulnType, int applicationState, ApplicationDefinition applicationDefinition,
124 | String applicationId, boolean autoRemediated, boolean confirmed, boolean suspicious,
125 | boolean notAProblem, boolean remediated, boolean reported, boolean fixed,
126 | boolean beingTracked, boolean untracked) {
127 |
128 | this.thresholdCount = thresholdCount;
129 | this.thresholdSeverity = thresholdSeverity;
130 | this.thresholdVulnType = thresholdVulnType;
131 | this.applicationDefinition = applicationDefinition;
132 | this.applicationId = applicationId;
133 | this.applicationState = applicationState;
134 | if(applicationState == 0) {
135 | this.matchBy = MatchBy.APPLICATION_ID;
136 | }else if(applicationDefinition != null) {
137 | this.matchBy = applicationDefinition.getMatchBy();
138 | this.applicationOriginName = applicationDefinition.getApplicationOriginName();
139 | this.agentType = applicationDefinition.getAgentType();
140 | this.failOnAppNotFound = applicationDefinition.isFailOnAppNotFound();
141 | }
142 |
143 | this.autoRemediated = autoRemediated;
144 | this.confirmed = confirmed;
145 | this.suspicious = suspicious;
146 | this.notAProblem = notAProblem;
147 | this.remediated = remediated;
148 | this.reported = reported;
149 | this.fixed = fixed;
150 | this.beingTracked = beingTracked;
151 | this.untracked = untracked;
152 | }
153 |
154 | @Override
155 | public String toString() {
156 | StringBuilder sb = new StringBuilder();
157 |
158 | sb.append("count is ").append(thresholdCount);
159 |
160 | if (thresholdSeverity != null) {
161 | sb.append(", severity is ").append(thresholdSeverity);
162 | }
163 |
164 | if (thresholdVulnType != null) {
165 | sb.append(", rule type is ").append(thresholdVulnType);
166 | }
167 |
168 | if (applicationId != null) {
169 | sb.append(", application ID is ").append(applicationId);
170 | }
171 |
172 | sb.append(".");
173 |
174 | return sb.toString();
175 | }
176 |
177 | /**
178 | * Returns the description of a condition that has been overridden with a job outcome policy.
179 | * @return
180 | */
181 | public String getStringForOverriden() {
182 | StringJoiner sj = new StringJoiner(", ");
183 | String preString = "[";
184 | String postString = "]";
185 |
186 | if(applicationOriginName != null) {
187 | sj.add("name='"+applicationOriginName+"'");
188 | }
189 |
190 | if(agentType != null) {
191 | sj.add("language='"+agentType+"'");
192 | }
193 |
194 | if(applicationId != null) {
195 | sj.add("applicationId='"+getPreparedApplicationId()+"'");
196 | }
197 |
198 | if(applicationId != null && applicationOriginName == null) {
199 | sj.add(applicationId);
200 | }
201 |
202 | return preString + sj.toString() + postString;
203 | }
204 |
205 | /**
206 | * Background: Some configurations can make the Application ID to be stored as 'AppName (AppId)'
207 | * This is a getter that only returns the actual application id.
208 | * Returns a proper application Id.
209 | * @return Returns the app id without the name attached
210 | */
211 | public String getPreparedApplicationId() {
212 | if (VulnerabilityTrendHelper.getAppIdFromAppTitle(applicationId) != null) {
213 | return VulnerabilityTrendHelper.getAppIdFromAppTitle(applicationId);
214 | }
215 | return applicationId;
216 | }
217 |
218 | /**
219 | * Descriptor for {@link ThresholdCondition}.
220 | */
221 | @Extension
222 | public static class DescriptorImpl extends Descriptor {
223 |
224 | Calendar lastAppsRefresh;
225 | int appsRefreshIntervalMinutes = 1;
226 |
227 | /**
228 | * Validation of the 'thresholdCount' form Field.
229 | *
230 | * @param value This parameter receives the value that the user has typed.
231 | * @return Indicates the outcome of the validation. This is sent to the browser.
232 | */
233 | public FormValidation doCheckThresholdCount(@QueryParameter String value) {
234 |
235 | if (!value.isEmpty()) {
236 | try {
237 | int temp = Integer.parseInt(value);
238 |
239 | if (temp < 0) {
240 | return FormValidation.error("Please enter a positive integer.");
241 | }
242 |
243 | } catch (NumberFormatException e) {
244 | return FormValidation.error("Please enter a valid integer.");
245 | }
246 | } else {
247 | return FormValidation.error("Please enter a positive integer.");
248 | }
249 |
250 | return FormValidation.ok();
251 | }
252 |
253 | /**
254 | * Validation of the 'thresholdSeverity' form Field.
255 | *
256 | * @param value This parameter receives the value that the user has typed.
257 | * @return Indicates the outcome of the validation. This is sent to the browser.
258 | */
259 | public FormValidation doCheckThresholdSeverity(@QueryParameter String value) {
260 | return FormValidation.ok();
261 | }
262 |
263 | /**
264 | * Validation of the 'thresholdCategory' form Field.
265 | *
266 | * @param value This parameter receives the value that the user has typed.
267 | * @return Indicates the outcome of the validation. This is sent to the browser.
268 | */
269 | public FormValidation doCheckThresholdVulnType(@QueryParameter String value) {
270 | return FormValidation.ok();
271 | }
272 |
273 | /**
274 | * Validation of the 'applicationId' form Field.
275 | *
276 | * @param value This parameter receives the value that the user has typed.
277 | * @return Indicates the outcome of the validation. This is sent to the browser.
278 | */
279 | public FormValidation doCheckApplicationId(@QueryParameter("teamServerProfileName") @RelativePath("..") final String teamServerProfileName, @QueryParameter String value) {
280 | if (VulnerabilityTrendHelper.appExistsInProfile(teamServerProfileName, value)) {
281 | TeamServerProfile profile = VulnerabilityTrendHelper.getProfile(teamServerProfileName);
282 | ContrastSDK contrastSDK = VulnerabilityTrendHelper.createSDK(profile.getUsername(), profile.getServiceKey(), profile.getApiKey(), profile.getTeamServerUrl());
283 | String appId = value;
284 | if(value.contains("(") && value.contains(")")) {
285 | appId = VulnerabilityTrendHelper.getAppIdFromAppTitle(value);
286 | }
287 | try {
288 | if (VulnerabilityTrendHelper.isApplicableEnabledJobOutcomePolicyExist(contrastSDK, profile.getOrgUuid(), appId)) {
289 | return FormValidation.warning("Your Contrast administrator has set a policy for vulnerability thresholds for this application. The Contrast policy overrides Jenkins vulnerability security controls and 'query vulnerabilities by' selection.");
290 | }
291 | } catch (IOException | UnauthorizedException e) {
292 | return FormValidation.warning("Unable to make connection with Contrast: " + e.getMessage());
293 | }
294 | } else if(!value.isEmpty()) {
295 | return FormValidation.warning("Application not found.");
296 | }
297 | return FormValidation.ok();
298 |
299 | }
300 |
301 | /**
302 | * Fills the Threshold Category combo box with application ids.
303 | *
304 | * @return ComboBoxModel filled with application ids.
305 | */
306 | public ComboBoxModel doFillApplicationIdItems(@QueryParameter("teamServerProfileName") @RelativePath("..") final String teamServerProfileName) {
307 |
308 | // Refresh apps every ${appsRefreshIntervalMinutes} minutes before filling in the combobox
309 | if (lastAppsRefresh == null || (Calendar.getInstance().getTimeInMillis() - lastAppsRefresh.getTimeInMillis()) / 60000 >= appsRefreshIntervalMinutes) {
310 | refreshApps(teamServerProfileName);
311 | lastAppsRefresh = Calendar.getInstance();
312 | }
313 |
314 | return VulnerabilityTrendHelper.getApplicationIdsComboBoxModel(teamServerProfileName);
315 | }
316 |
317 | public void refreshApps(String teamServerProfileName) {
318 | Jenkins jenkins = Jenkins.getInstance();
319 | if (jenkins != null) {
320 | ContrastPluginConfig.ContrastPluginConfigDescriptor contrastPluginConfigDescriptor = jenkins.getDescriptorByType(ContrastPluginConfig.ContrastPluginConfigDescriptor.class);
321 | TeamServerProfile teamServerProfile = VulnerabilityTrendHelper.getProfile(teamServerProfileName, contrastPluginConfigDescriptor);
322 |
323 | ContrastSDK contrastSDK = VulnerabilityTrendHelper.createSDK(teamServerProfile.getUsername(), teamServerProfile.getServiceKey(),
324 | teamServerProfile.getApiKey(), teamServerProfile.getTeamServerUrl());
325 |
326 | teamServerProfile.setApps(VulnerabilityTrendHelper.saveApplicationIds(contrastSDK, teamServerProfile.getOrgUuid()));
327 | contrastPluginConfigDescriptor.save();
328 | }
329 | }
330 |
331 | public ListBoxModel doFillAgentTypeItems() {
332 | return VulnerabilityTrendHelper.getAgentTypeListBoxModel();
333 | }
334 |
335 | /**
336 | * Fills the Threshold Category select drop down with vulnerability types for the configured profile.
337 | *
338 | * @return ListBoxModel filled with vulnerability types.
339 | */
340 | public ListBoxModel doFillThresholdVulnTypeItems(@QueryParameter("teamServerProfileName") @RelativePath("..") final String teamServerProfileName) throws IOException {
341 | return VulnerabilityTrendHelper.getVulnerabilityTypes(teamServerProfileName);
342 | }
343 |
344 | /**
345 | * Fills the Threshold Severity select drop down with severities for the configured application.
346 | *
347 | * @return ListBoxModel filled with severities.
348 | */
349 | public ListBoxModel doFillThresholdSeverityItems() {
350 | return VulnerabilityTrendHelper.getSeverityListBoxModel();
351 | }
352 |
353 | /**
354 | * Display name in the Build Action dropdown.
355 | *
356 | * @return String
357 | */
358 | public String getDisplayName() {
359 | return "Threshold Condition";
360 | }
361 | }
362 |
363 | public List getVulnerabilityStatuses() {
364 | List status = new ArrayList();
365 | if (autoRemediated) {
366 | status.add(Constants.VULNERABILITY_STATUS_AUTO_REMEDIATED);
367 | }
368 | if (confirmed) {
369 | status.add(Constants.VULNERABILITY_STATUS_CONFIRMED);
370 | }
371 | if (suspicious) {
372 | status.add(Constants.VULNERABILITY_STATUS_SUSPICIOUS);
373 | }
374 | if (notAProblem) {
375 | status.add(Constants.VULNERABILITY_STATUS_NOT_A_PROBLEM);
376 | }
377 | if (remediated) {
378 | status.add(Constants.VULNERABILITY_STATUS_REMEDIATED);
379 | }
380 | if (reported) {
381 | status.add(Constants.VULNERABILITY_STATUS_REPORTED);
382 | }
383 | if (fixed) {
384 | status.add(Constants.VULNERABILITY_STATUS_FIXED);
385 | }
386 | if (beingTracked) {
387 | status.add(Constants.VULNERABILITY_STATUS_BEING_TRACKED);
388 | }
389 | if (untracked) {
390 | status.add(Constants.VULNERABILITY_STATUS_UNTRACKED);
391 | }
392 | return status;
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityFrequencyAction.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import hudson.model.AbstractBuild;
4 | import hudson.model.Action;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | @Getter
12 | @Setter
13 | public class VulnerabilityFrequencyAction implements Action {
14 |
15 | private AbstractBuild, ?> build;
16 | private VulnerabilityTrendResult result;
17 |
18 | public VulnerabilityFrequencyAction(VulnerabilityTrendResult result, AbstractBuild, ?> build) {
19 | this.result = result;
20 | this.build = build;
21 | }
22 |
23 | public String getIconFileName() {
24 | return "/plugin/contrast-continuous-application-security/img/trend_graph.png";
25 | }
26 |
27 | public String getDisplayName() {
28 | return "Vulnerability Report";
29 | }
30 |
31 | public String getUrlName() {
32 | return "vulnReport";
33 | }
34 |
35 | public int getBuildNumber() {
36 | return this.build.getNumber();
37 | }
38 |
39 | public String getReport() {
40 | return "Vulnerability Report";
41 | }
42 |
43 | public Map getTraceResult() {
44 | return result.getTraceResult();
45 | }
46 |
47 | public Map getSeverityResult() {
48 | return result.getSeverityResult();
49 | }
50 |
51 | public List getSeverities() {
52 | return VulnerabilityTrendHelper.SEVERITIES;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return "Build Number # " + build.getNumber() + "
" + result.toString();
58 | }
59 | }
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendHelper.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 |
4 | import com.contrastsecurity.exceptions.UnauthorizedException;
5 | import com.contrastsecurity.http.RuleSeverity;
6 | import com.contrastsecurity.http.SecurityCheckFilter;
7 | import com.contrastsecurity.http.SecurityCheckForm;
8 | import com.contrastsecurity.http.TraceFilterForm;
9 | import com.contrastsecurity.models.AgentType;
10 | import com.contrastsecurity.models.Application;
11 | import com.contrastsecurity.models.Applications;
12 | import com.contrastsecurity.models.JobOutcomePolicy;
13 | import com.contrastsecurity.models.Rules;
14 | import com.contrastsecurity.models.SecurityCheck;
15 | import com.contrastsecurity.models.Trace;
16 | import com.contrastsecurity.models.Traces;
17 | import com.contrastsecurity.sdk.ContrastSDK;
18 | import hudson.ProxyConfiguration;
19 | import hudson.model.Result;
20 | import hudson.model.Run;
21 | import hudson.model.TaskListener;
22 | import hudson.util.ComboBoxModel;
23 | import hudson.util.ListBoxModel;
24 | import jenkins.model.Jenkins;
25 | import org.apache.commons.lang3.StringUtils;
26 |
27 | import javax.annotation.Nullable;
28 | import java.io.IOException;
29 | import java.net.MalformedURLException;
30 | import java.net.Proxy;
31 | import java.net.URL;
32 | import java.util.ArrayList;
33 | import java.util.Arrays;
34 | import java.util.Collections;
35 | import java.util.EnumSet;
36 | import java.util.List;
37 |
38 |
39 | public class VulnerabilityTrendHelper {
40 |
41 | public static ContrastSDK createSDK(String username, String serviceKey, String apiKey, String teamServerUrl) {
42 | ContrastSDK contrastSDK;
43 | Jenkins jenkinsInstance = Jenkins.getInstance();
44 | ProxyConfiguration proxyConfig = null;
45 | if (jenkinsInstance != null) {
46 | proxyConfig = jenkinsInstance.proxy;
47 | }
48 |
49 | URL url = null;
50 | Proxy proxyToUse = Proxy.NO_PROXY;
51 | try {
52 | url = new URL(teamServerUrl);
53 | } catch (MalformedURLException e) {
54 | e.printStackTrace();
55 | }
56 |
57 | if (proxyConfig != null && url != null) {
58 | Proxy proxy = proxyConfig.createProxy(url.getHost());
59 | if (proxy != null && proxy.type() == Proxy.Type.HTTP) {
60 | proxyToUse = proxy;
61 | }
62 | }
63 | contrastSDK = new ContrastSDK.Builder(username, serviceKey, apiKey).withApiUrl(teamServerUrl).withProxy(proxyToUse).build();
64 |
65 | return contrastSDK;
66 | }
67 |
68 | public static TeamServerProfile getProfile(String profileName) {
69 | if (profileName == null)
70 | return null;
71 |
72 | ContrastPluginConfig.ContrastPluginConfigDescriptor contrastPluginConfigDescriptor = new ContrastPluginConfig.ContrastPluginConfigDescriptor();
73 | final TeamServerProfile[] profiles = contrastPluginConfigDescriptor.getTeamServerProfiles();
74 |
75 | // Return the first profile; it is assumed that if it is empty,
76 | // it's the first element in a drop down that hasn't fully loaded yet
77 | if (StringUtils.isEmpty(profileName)) {
78 | return profiles[0];
79 | }
80 |
81 | for (TeamServerProfile profile : profiles) {
82 | if (profileName.trim().equalsIgnoreCase(profile.getName().trim())) {
83 | return profile;
84 | }
85 | }
86 | return null;
87 | }
88 |
89 | public static TeamServerProfile getProfile(String profileName, ContrastPluginConfig.ContrastPluginConfigDescriptor contrastPluginConfigDescriptor) {
90 | if (profileName == null)
91 | return null;
92 |
93 | final TeamServerProfile[] profiles = contrastPluginConfigDescriptor.getTeamServerProfiles();
94 |
95 | // Return the first profile; it is assumed that if it is empty,
96 | // it's the first element in a drop down that hasn't fully loaded yet
97 | if (StringUtils.isEmpty(profileName)) {
98 | return profiles[0];
99 | }
100 |
101 | for (TeamServerProfile profile : profiles) {
102 | if (profileName.equals(profile.getName())) {
103 | return profile;
104 | }
105 | }
106 | return null;
107 | }
108 |
109 | public static List getGlobalThresholdConditions(String profileName) {
110 | final GlobalThresholdCondition[] globalThresholdConditions = new ContrastPluginConfig.ContrastPluginConfigDescriptor().getGlobalThresholdConditions();
111 | List globalThresholdConditionList = new ArrayList<>();
112 |
113 | if (globalThresholdConditions[0] == null) {
114 | return null;
115 | }
116 |
117 | for (GlobalThresholdCondition globalThresholdCondition : globalThresholdConditions) {
118 | if (profileName.equals(globalThresholdCondition.getTeamServerProfileName())) {
119 | globalThresholdConditionList.add(globalThresholdCondition);
120 | }
121 | }
122 | return globalThresholdConditionList;
123 | }
124 |
125 | /**
126 | * Helper method for combining global threshold conditions and threshold conditions configured in jobs.
127 | *
128 | * @param thresholdConditions
129 | * @param globalThresholdConditions
130 | */
131 | public static List getThresholdConditions(final List thresholdConditions,
132 | final List globalThresholdConditions) {
133 | List newThresholdConditions = new ArrayList<>();
134 |
135 | if (thresholdConditions == null || globalThresholdConditions == null) {
136 | return newThresholdConditions;
137 | }
138 | for (ThresholdCondition thresholdCondition : thresholdConditions) {
139 | for (GlobalThresholdCondition globalThresholdCondition : globalThresholdConditions) {
140 | ThresholdCondition newThresholdCondition = new ThresholdCondition(
141 | globalThresholdCondition.getThresholdCount(),
142 | globalThresholdCondition.getThresholdSeverity(), globalThresholdCondition.getThresholdVulnType(), thresholdCondition.getApplicationState(), thresholdCondition.getApplicationDefinition(),
143 | thresholdCondition.getApplicationId(),globalThresholdCondition.isAutoRemediated(),
144 | globalThresholdCondition.isConfirmed(), globalThresholdCondition.isSuspicious(),
145 | globalThresholdCondition.isNotAProblem(), globalThresholdCondition.isRemediated(),
146 | globalThresholdCondition.isReported(), globalThresholdCondition.isFixed(), globalThresholdCondition.isBeingTracked(),
147 | globalThresholdCondition.isUntracked());
148 | if (thresholdCondition.getApplicationName() != null) {
149 | newThresholdCondition.setApplicationName(thresholdCondition.getApplicationName());
150 | }
151 | newThresholdConditions.add(newThresholdCondition);
152 | }
153 | }
154 | return newThresholdConditions;
155 | }
156 |
157 | /**
158 | * Helper method for logging messages.
159 | *
160 | * @param listener Listener
161 | * @param msg String to log
162 | */
163 | public static void logMessage(TaskListener listener, String msg) {
164 | listener.getLogger().println("[Contrast] - " + msg);
165 | }
166 |
167 | /**
168 | * Returns the sublist of severities greater than or equal to the configured severity level
169 | *
170 | * @param severity include severity to filter with severity list with
171 | * @return list of severity strings
172 | */
173 | public static EnumSet getSeverityList(String severity) {
174 |
175 | List severityList = SEVERITIES.subList(SEVERITIES.indexOf(severity), SEVERITIES.size());
176 |
177 | List ruleSeverities = new ArrayList<>();
178 |
179 | for (String severityToAdd : severityList) {
180 | ruleSeverities.add(RuleSeverity.valueOf(severityToAdd.toUpperCase()));
181 | }
182 |
183 | return EnumSet.copyOf(ruleSeverities);
184 | }
185 |
186 | /**
187 | * Retrieves the enabled rules for an organization
188 | *
189 | * @param sdk Contrast SDK object
190 | * @param organizationUuid uuid of the organization
191 | */
192 | public static List saveRules(ContrastSDK sdk, String organizationUuid) {
193 | Rules rules;
194 | List vulnerabilityTypes = new ArrayList<>();
195 |
196 | try {
197 | rules = sdk.getRules(organizationUuid);
198 | } catch (IOException | UnauthorizedException e) {
199 | return vulnerabilityTypes;
200 | }
201 |
202 | for (Rules.Rule rule : rules.getRules()) {
203 | vulnerabilityTypes.add(new VulnerabilityType(rule.getName(), rule.getTitle()));
204 | }
205 |
206 | return vulnerabilityTypes;
207 | }
208 |
209 |
210 | /**
211 | * The available severities for the configuration dropdowns
212 | *
213 | * @return ListBoxModel of severities
214 | */
215 | public static ListBoxModel getSeverityListBoxModel() {
216 | ListBoxModel items = new ListBoxModel();
217 | items.add(VulnerabilityTrendHelper.EMPTY_SELECT, null);
218 |
219 | for (String severity : VulnerabilityTrendHelper.SEVERITIES) {
220 | items.add(severity, severity);
221 | }
222 |
223 | return items;
224 | }
225 |
226 | /**
227 | * The configured profile names for the dropdowns
228 | *
229 | * @return ListBoxModel of TeamServer profile names
230 | */
231 | public static ListBoxModel getProfileNames() {
232 | final ListBoxModel model = new ListBoxModel();
233 |
234 | for (TeamServerProfile profile : new ContrastPluginConfig.ContrastPluginConfigDescriptor().getTeamServerProfiles()) {
235 | model.add(profile.getName(), profile.getName());
236 | }
237 |
238 | return model;
239 | }
240 |
241 | /**
242 | * The vulnerability types for a profile
243 | *
244 | * @param teamServerProfileName Name of the profile
245 | * @return ListBoxModel of vulnerability types
246 | */
247 | public static ListBoxModel getVulnerabilityTypes(String teamServerProfileName) {
248 | ListBoxModel items = new ListBoxModel();
249 |
250 | TeamServerProfile teamServerProfile = VulnerabilityTrendHelper.getProfile(teamServerProfileName);
251 |
252 | items.add(VulnerabilityTrendHelper.EMPTY_SELECT, null);
253 |
254 | if (teamServerProfile != null) {
255 | for (VulnerabilityType vulnerabilityType : teamServerProfile.getVulnerabilityTypes()) {
256 | items.add(vulnerabilityType.getTitle(), vulnerabilityType.getName());
257 | }
258 | }
259 |
260 | return items;
261 | }
262 |
263 | /**
264 | * The available agent types for the configuration dropdown
265 | *
266 | * @return ListBoxModel of agent types
267 | */
268 | public static ListBoxModel getAgentTypeListBoxModel() {
269 | ListBoxModel items = new ListBoxModel();
270 |
271 | items.add("Java", AgentType.JAVA.toString());
272 | items.add(".NET", AgentType.DOTNET.toString());
273 | items.add("Node", AgentType.NODE.toString());
274 | items.add("Ruby", AgentType.RUBY.toString());
275 | items.add("Python", AgentType.PYTHON.toString());
276 | items.add(".NET_Core", AgentType.DOTNET_CORE.toString());
277 | items.add("Proxy", AgentType.PROXY.toString());
278 |
279 | return items;
280 | }
281 |
282 | public static AgentType getAgentTypeFromString(String type) {
283 | switch (type.toUpperCase()) {
284 | case ".NET":
285 | return AgentType.DOTNET;
286 | case "NODE":
287 | return AgentType.NODE;
288 | case "RUBY":
289 | return AgentType.RUBY;
290 | case "PYTHON":
291 | return AgentType.PYTHON;
292 | case ".NET_CORE":
293 | return AgentType.DOTNET_CORE;
294 | case "PROXY":
295 | return AgentType.PROXY;
296 | case "JAVA":
297 | default:
298 | return AgentType.JAVA;
299 | }
300 | }
301 |
302 | public static String getDefaultAgentFileNameFromString(String type) {
303 | switch (type.toUpperCase()) {
304 | case ".NET":
305 | return "dotnet-contrast.zip";
306 | case "NODE":
307 | return "node-contrast.tgz";
308 | case "RUBY":
309 | return "ruby-contrast.gem";
310 | case "PYTHON":
311 | return "python-contrast.tar.gz";
312 | case ".NET_CORE":
313 | return "dotnet_core-contrast.zip";
314 | case "JAVA":
315 | default:
316 | return "contrast.jar";
317 | }
318 | }
319 |
320 | public static String buildAppVersionTag(Run, ?> build, String applicationId) {
321 | return applicationId + "-" + build.getNumber();
322 | }
323 |
324 | public static String buildAppVersionTagHierarchical(Run, ?> build, String applicationId) {
325 | return applicationId + "-" + build.getParent().getFullName() + "-" + build.getNumber();
326 | }
327 |
328 | /**
329 | * Number of traces by severity
330 | *
331 | * @param traces
332 | * @return String with the number of traces for each severity.
333 | */
334 | public static String getVulnerabilityInfoString(Traces traces) {
335 | StringBuilder info = new StringBuilder();
336 | int note = 0;
337 | int low = 0;
338 | int medium = 0;
339 | int high = 0;
340 | int critical = 0;
341 |
342 | if (traces == null || traces.getTraces() == null || traces.getTraces().isEmpty()) {
343 | return info.toString();
344 | } else {
345 | info.append("Found vulnerabilities: ");
346 | for (Trace trace : traces.getTraces()) {
347 | switch (trace.getSeverity()) {
348 | case "Note":
349 | note++;
350 | break;
351 | case "Low":
352 | low++;
353 | break;
354 | case "Medium":
355 | medium++;
356 | break;
357 | case "High":
358 | high++;
359 | break;
360 | case "Critical":
361 | critical++;
362 | break;
363 | default:
364 | break;
365 | }
366 |
367 | }
368 | if (note > 0) {
369 | info.append("Note - " + String.valueOf(note) + " ");
370 | }
371 | if (low > 0) {
372 | info.append("Low - " + String.valueOf(low) + " ");
373 | }
374 | if (medium > 0) {
375 | info.append("Medium - " + String.valueOf(medium) + " ");
376 | }
377 | if (high > 0) {
378 | info.append("High - " + String.valueOf(high) + " ");
379 | }
380 | if (critical > 0) {
381 | info.append("Critical - " + String.valueOf(critical) + " ");
382 | }
383 | info.append(".");
384 | return info.toString();
385 | }
386 |
387 | }
388 | /**
389 | * Collection of all traces
390 | *
391 | * @param sdk ContrastSDK instance
392 | * @param organizationId Organization ID of the application
393 | * @param applicationId Application ID (optional)
394 | * @param filter TraceFormFilter to limit results
395 | * @return Traces object
396 | */
397 | public static Traces getAllTraces(ContrastSDK sdk, String organizationId, String applicationId, TraceFilterForm filter) throws IOException, UnauthorizedException {
398 | Traces traces = new Traces();
399 |
400 | // Contrast API returns traces paginated at 20 per page by default. Increase page size for faster transfer.
401 | int page = 0;
402 | int pageSize = 50;
403 | filter.setLimit(pageSize);
404 |
405 | do {
406 | filter.setOffset(page * pageSize);
407 |
408 | Traces intermediateTraces;
409 |
410 | if (applicationId == null) {
411 | intermediateTraces = sdk.getTracesInOrg(organizationId, filter);
412 |
413 | } else {
414 | intermediateTraces = sdk.getTraces(organizationId, applicationId, filter);
415 | }
416 |
417 | if (page == 0) {
418 | traces = intermediateTraces;
419 | } else {
420 | traces.getTraces().addAll(intermediateTraces.getTraces());
421 | }
422 |
423 | page++;
424 | } while (traces.getTraces().size() < traces.getCount());
425 |
426 | return traces;
427 | }
428 |
429 | static boolean applicationIdExists(ContrastSDK sdk, String organizationUuid, String applicationId) {
430 |
431 | if (applicationId == null || applicationId.isEmpty()) {
432 | return false;
433 | }
434 |
435 | Applications applications;
436 |
437 | try {
438 | applications = sdk.getApplications(organizationUuid);
439 | } catch (IOException | UnauthorizedException e) {
440 | return false;
441 | }
442 |
443 | for (Application application : applications.getApplications()) {
444 | if (applicationId.equals(application.getId())) {
445 | return true;
446 | }
447 | }
448 |
449 | return false;
450 | }
451 |
452 | /**
453 | * The apps for a profile
454 | *
455 | * @param teamServerProfileName Name of the profile
456 | * @return ListBoxModel of apps
457 | */
458 | public static ListBoxModel getApplicationIds(String teamServerProfileName) {
459 | ListBoxModel items = new ListBoxModel();
460 |
461 | TeamServerProfile teamServerProfile = VulnerabilityTrendHelper.getProfile(teamServerProfileName);
462 |
463 | if (teamServerProfile != null) {
464 | for (App app : teamServerProfile.getApps()) {
465 | items.add(app.getTitle(), app.getName());
466 | }
467 | }
468 |
469 | return items;
470 | }
471 |
472 | /**
473 | * The apps for a profile
474 | *
475 | * @param teamServerProfileName Name of the profile
476 | * @return ComboBoxModel of apps
477 | */
478 | public static ComboBoxModel getApplicationIdsComboBoxModel(String teamServerProfileName) {
479 | ComboBoxModel items = new ComboBoxModel();
480 |
481 | TeamServerProfile teamServerProfile = VulnerabilityTrendHelper.getProfile(teamServerProfileName);
482 |
483 | if (teamServerProfile != null) {
484 | for (App app : teamServerProfile.getApps()) {
485 | items.add(app.getTitle());
486 | }
487 | }
488 |
489 | return items;
490 | }
491 |
492 | public static boolean appExistsInProfile(String teamServerProfileName, String appTitle) {
493 | TeamServerProfile teamServerProfile = VulnerabilityTrendHelper.getProfile(teamServerProfileName);
494 |
495 | if (teamServerProfile != null) {
496 | for (App app : teamServerProfile.getApps()) {
497 | if (appTitle.equals(app.getTitle()) || appTitle.equals(app.getName()))
498 | return true;
499 | }
500 | }
501 | return false;
502 | }
503 |
504 | /**
505 | * get app id from app title in the post build action combo box
506 | * @param appTitle for example: WebGoat (a123745f-5857-45e4-a278-ddb5012e1996)
507 | * @return appId
508 | */
509 | @Nullable
510 | public static String getAppIdFromAppTitle(String appTitle) {
511 | int beginIndex = org.apache.commons.lang.StringUtils.lastIndexOf(appTitle, "(");
512 | int endIndex = org.apache.commons.lang.StringUtils.lastIndexOf(appTitle, ")");
513 |
514 | if (beginIndex >= 0 && endIndex >= 0 && endIndex > beginIndex) {
515 | return appTitle.substring(beginIndex + 1, endIndex);
516 | }
517 | return null;
518 | }
519 |
520 |
521 | /**
522 | * get the application name from the app title.
523 | * @param appTitle
524 | * @return
525 | */
526 | public static String getAppNameFromAppTitle(String appTitle) {
527 | return appTitle.substring(0, appTitle.lastIndexOf(" ("));
528 | }
529 |
530 | /**
531 | * Retrieves the applications
532 | *
533 | * @param sdk Contrast SDK object
534 | * @param organizationUuid uuid of the organization
535 | */
536 | public static List saveApplicationIds(ContrastSDK sdk, String organizationUuid) {
537 | Applications applications;
538 | List apps = new ArrayList<>();
539 |
540 | try {
541 | applications = sdk.getApplications(organizationUuid);
542 | } catch (IOException | UnauthorizedException e) {
543 | return apps;
544 | }
545 |
546 | for (Application application : applications.getApplications()) {
547 | apps.add(new App(application.getId(), application.getName() + " (" + application.getId() + ")"));
548 | }
549 |
550 | return apps;
551 | }
552 |
553 | public static SecurityCheck makeSecurityCheck(ContrastSDK sdk, String organizationUuid, String applicationId, Long jobStartTime, int queryBy, TraceFilterForm filterForm) throws IOException, UnauthorizedException {
554 | SecurityCheckForm form = new SecurityCheckForm(applicationId);
555 | form.setJobStartTime(jobStartTime);
556 |
557 | SecurityCheckFilter securityCheckFilter = new SecurityCheckFilter();
558 | if(Constants.QUERY_BY_START_DATE == queryBy) {
559 | securityCheckFilter.setQueryBy(SecurityCheckFilter.QueryBy.START_DATE);
560 | securityCheckFilter.setStartDate(filterForm.getStartDate().toInstant().toEpochMilli());
561 | } else {
562 | securityCheckFilter.setQueryBy(SecurityCheckFilter.QueryBy.APP_VERSION_TAG);
563 | securityCheckFilter.setAppVersionTags(filterForm.getAppVersionTags());
564 | }
565 |
566 | form.setSecurityCheckFilter(securityCheckFilter);
567 |
568 | return sdk.makeSecurityCheck(organizationUuid, form);
569 | }
570 |
571 | public static Result getJenkinsResultFromJobOutcome(JobOutcomePolicy.Outcome outcome) throws VulnerabilityTrendHelperException {
572 | switch(outcome) {
573 | case FAIL:
574 | return Result.FAILURE;
575 | case SUCCESS:
576 | return Result.SUCCESS;
577 | case UNSTABLE:
578 | return Result.UNSTABLE;
579 | default:
580 | throw new VulnerabilityTrendHelperException("Unrecognized Job Outcome: " + outcome.toString());
581 | }
582 | }
583 |
584 | /**
585 | * Checks to see if the organization has a job outcome policy that is enabled
586 | * @param sdk Contrast SDK
587 | * @param organizationUuid uuid of the organization
588 | * @return true = an enabled job outcome policy exists, false = no enabled job outcome policy exist
589 | */
590 | public static boolean isEnabledJobOutcomePolicyExist(ContrastSDK sdk, String organizationUuid) throws IOException, UnauthorizedException {
591 | return sdk.getEnabledJobOutcomePolicies(organizationUuid).size() > 0;
592 | }
593 |
594 | public static boolean isApplicableEnabledJobOutcomePolicyExist(ContrastSDK sdk, String organizationUuid, String applicationId) throws IOException, UnauthorizedException {
595 | return applicationIdExists(sdk, organizationUuid, applicationId) && sdk.getEnabledJoboutcomePoliciesByApplication(organizationUuid, applicationId).size() > 0;
596 | }
597 |
598 |
599 |
600 | public static final String EMPTY_SELECT = "All";
601 | public static final List SEVERITIES = Collections.unmodifiableList(Arrays.asList("Note", "Low", "Medium", "High", "Critical"));
602 | }
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendHelperException.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | public class VulnerabilityTrendHelperException extends Exception {
4 | public VulnerabilityTrendHelperException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendProjectAction.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import com.aspectsecurity.contrast.contrastjenkins.plots.SeverityFrequencyPlot;
4 | import com.aspectsecurity.contrast.contrastjenkins.plots.VulnerabilityFrequencyPlot;
5 | import hudson.model.AbstractBuild;
6 | import hudson.model.AbstractProject;
7 | import hudson.model.Action;
8 | import hudson.util.Graph;
9 |
10 | import java.util.List;
11 |
12 | public class VulnerabilityTrendProjectAction implements Action {
13 |
14 | private AbstractProject, ?> project;
15 |
16 | public VulnerabilityTrendProjectAction(final AbstractProject, ?> project) {
17 | this.project = project;
18 | }
19 |
20 | @Override
21 | public String getIconFileName() {
22 | return null;
23 | }
24 |
25 | @Override
26 | public String getDisplayName() {
27 | return null;
28 | }
29 |
30 | @Override
31 | public String getUrlName() {
32 | return "vulnTrendCharts";
33 | }
34 |
35 | public AbstractProject, ?> getProject() {
36 | return this.project;
37 | }
38 |
39 | // used in floatingBox.jelly
40 | public Graph getVulnerabilityGraph() {
41 | return new VulnerabilityFrequencyPlot(project);
42 | }
43 |
44 | // used in floatingBox.jelly
45 | public Graph getSeverityGraph() {
46 | return new SeverityFrequencyPlot(project);
47 | }
48 |
49 | public String getProjectName() {
50 | return this.project.getName();
51 | }
52 |
53 | // used to hide charts
54 | public boolean getHasBuilds() {
55 | return !project.getBuilds().isEmpty();
56 | }
57 |
58 | public String getBuildResult() {
59 | List extends AbstractBuild, ?>> builds = project.getBuilds();
60 | StringBuilder buildResult = new StringBuilder();
61 |
62 | final Class buildClass = VulnerabilityFrequencyAction.class;
63 |
64 | for (AbstractBuild, ?> currentBuild : builds) {
65 | buildResult.append(currentBuild.getAction(buildClass).toString());
66 | }
67 |
68 | return buildResult.toString();
69 | }
70 | }
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendRecorder.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import com.contrastsecurity.exceptions.UnauthorizedException;
4 | import com.contrastsecurity.http.TraceFilterForm;
5 | import com.contrastsecurity.models.Application;
6 | import com.contrastsecurity.models.Organizations;
7 | import com.contrastsecurity.models.SecurityCheck;
8 | import com.contrastsecurity.models.Trace;
9 | import com.contrastsecurity.models.Traces;
10 | import com.contrastsecurity.sdk.ContrastSDK;
11 | import hudson.AbortException;
12 | import hudson.EnvVars;
13 | import hudson.Extension;
14 | import hudson.Launcher;
15 | import hudson.model.AbstractBuild;
16 | import hudson.model.AbstractProject;
17 | import hudson.model.Action;
18 | import hudson.model.BuildListener;
19 | import hudson.model.Result;
20 | import hudson.tasks.BuildStepDescriptor;
21 | import hudson.tasks.BuildStepMonitor;
22 | import hudson.tasks.Publisher;
23 | import hudson.tasks.Recorder;
24 | import hudson.util.ListBoxModel;
25 | import jenkins.model.Jenkins;
26 | import lombok.Getter;
27 | import lombok.Setter;
28 | import net.sf.json.JSONArray;
29 | import net.sf.json.JSONObject;
30 | import org.kohsuke.stapler.DataBoundConstructor;
31 | import org.kohsuke.stapler.StaplerRequest;
32 | import org.kohsuke.stapler.bind.JavaScriptMethod;
33 |
34 | import java.io.IOException;
35 | import java.util.ArrayList;
36 | import java.util.Collections;
37 | import java.util.Date;
38 | import java.util.HashMap;
39 | import java.util.HashSet;
40 | import java.util.List;
41 | import java.util.Map;
42 | import java.util.Set;
43 |
44 |
45 | /**
46 | * Vulnerability Trend Builder
47 | *
48 | * Checks the number of vulnerabilities in the application against the configured threshold.
49 | */
50 | @Getter
51 | @Setter
52 | public class VulnerabilityTrendRecorder extends Recorder {
53 |
54 | private List conditions;
55 | private String teamServerProfileName;
56 | private boolean overrideGlobalThresholdConditions;
57 | private int queryBy;
58 |
59 |
60 | @DataBoundConstructor
61 | public VulnerabilityTrendRecorder(List conditions, String teamServerProfileName, boolean overrideGlobalThresholdConditions, int queryBy) {
62 | this.conditions = conditions;
63 | this.teamServerProfileName = teamServerProfileName;
64 | this.overrideGlobalThresholdConditions = overrideGlobalThresholdConditions;
65 | this.queryBy = queryBy;
66 | }
67 |
68 | protected Object readResolve() {
69 |
70 | Jenkins jenkins = Jenkins.getInstance();
71 | if (jenkins != null) {
72 | ContrastPluginConfig.ContrastPluginConfigDescriptor contrastPluginConfigDescriptor = jenkins.getDescriptorByType(ContrastPluginConfig.ContrastPluginConfigDescriptor.class);
73 |
74 | final TeamServerProfile[] profiles = contrastPluginConfigDescriptor.getTeamServerProfiles();
75 | for (TeamServerProfile profile : profiles) {
76 | if (profile.getApps() == null) {
77 | ContrastSDK contrastSDK = VulnerabilityTrendHelper.createSDK(profile.getUsername(), profile.getServiceKey(),
78 | profile.getApiKey(), profile.getTeamServerUrl());
79 | List apps = VulnerabilityTrendHelper.saveApplicationIds(contrastSDK, profile.getOrgUuid());
80 | profile.setApps(apps);
81 | profile.setFailOnWrongApplicationId(profile.isFailOnWrongApplicationName());
82 |
83 | contrastPluginConfigDescriptor.save();
84 | }
85 | }
86 |
87 | for (ThresholdCondition thresholdCondition : conditions) {
88 | if (thresholdCondition.getApplicationId() == null && thresholdCondition.getApplicationName() != null) {
89 | TeamServerProfile profile = VulnerabilityTrendHelper.getProfile(teamServerProfileName);
90 | for (App app : profile.getApps()) {
91 | String subStr = app.getTitle().substring(0, app.getTitle().lastIndexOf(" ("));
92 | if (subStr.equals(thresholdCondition.getApplicationName())) {
93 | thresholdCondition.setApplicationId(app.getName());
94 | break;
95 | }
96 | }
97 | }
98 | }
99 | }
100 | return this;
101 | }
102 |
103 | private TraceFilterForm buildFilterFormForCondition(final ThresholdCondition condition, final AbstractBuild, ?> build, final String appId, final BuildListener listener) throws IOException, InterruptedException {
104 | TraceFilterForm filterForm = new TraceFilterForm();
105 |
106 | if (queryBy == Constants.QUERY_BY_APP_VERSION_TAG_HIERARCHICAL_FORMAT) {
107 | String appVersionTag = VulnerabilityTrendHelper.buildAppVersionTagHierarchical(build, appId);
108 |
109 | List appVersionTagsList = new ArrayList<>();
110 | appVersionTagsList.add(appVersionTag);
111 |
112 | if (condition.getApplicationName() != null) {
113 | String appVersionTagAppName = VulnerabilityTrendHelper.buildAppVersionTagHierarchical(build, condition.getApplicationName());
114 | appVersionTagsList.add(appVersionTagAppName);
115 | }
116 |
117 | filterForm.setAppVersionTags(appVersionTagsList);
118 | } else if (queryBy == Constants.QUERY_BY_START_DATE) {
119 | filterForm.setStartDate(new Date(build.getStartTimeInMillis()));
120 | } else if (queryBy == Constants.QUERY_BY_PARAMETER) {
121 | final EnvVars env = build.getEnvironment(listener);
122 | String appVersionTag;
123 |
124 | if (env.get("APPVERSIONTAG") != null) {
125 | appVersionTag = env.get("APPVERSIONTAG");
126 | } else {
127 | appVersionTag = "";
128 | }
129 |
130 | if (appVersionTag.isEmpty()) {
131 | VulnerabilityTrendHelper.logMessage(listener, "Warning: queryBy Parameter is configured, but APPVERSIONTAG is not set. All vulnerabilities will be returned for this application");
132 | }
133 |
134 | List appVersionTagsList = new ArrayList<>();
135 | appVersionTagsList.add(appVersionTag);
136 |
137 | filterForm.setAppVersionTags(appVersionTagsList);
138 | } else {
139 | String appVersionTag = VulnerabilityTrendHelper.buildAppVersionTag(build, appId);
140 |
141 | List appVersionTagsList = new ArrayList<>();
142 | appVersionTagsList.add(appVersionTag);
143 |
144 | if (condition.getApplicationName() != null) {
145 | String appVersionTagAppName = VulnerabilityTrendHelper.buildAppVersionTag(build, condition.getApplicationName());
146 | appVersionTagsList.add(appVersionTagAppName);
147 | }
148 |
149 | filterForm.setAppVersionTags(appVersionTagsList);
150 | }
151 |
152 | if (condition.getThresholdSeverity() != null) {
153 | filterForm.setSeverities(VulnerabilityTrendHelper.getSeverityList(condition.getThresholdSeverity()));
154 | }
155 |
156 | if (condition.getThresholdVulnType() != null) {
157 | filterForm.setVulnTypes(Collections.singletonList(condition.getThresholdVulnType()));
158 | }
159 |
160 | if (!condition.getVulnerabilityStatuses().isEmpty()) {
161 | filterForm.setStatus(condition.getVulnerabilityStatuses());
162 | }
163 | return filterForm;
164 | }
165 |
166 | /**
167 | * Set the build result based on contrast configuration
168 | * @param build build object
169 | * @param profile teamserver profile
170 | * @param message Message to log when status is failure
171 | * @return true = the result was set, false = the result was not set
172 | */
173 | private boolean updateBuildResultOnError(AbstractBuild, ?> build, TeamServerProfile profile, String message, BuildListener listener) throws AbortException {
174 | if(profile.isApplyVulnerableBuildResultOnContrastError()) {
175 | Result profileVulnerableBuildResult = Result.fromString(profile.getVulnerableBuildResult());
176 | if(Result.FAILURE.equals(profileVulnerableBuildResult)) {
177 | throw new AbortException(message);
178 | } else {
179 | build.setResult(profileVulnerableBuildResult);
180 | return true;
181 | }
182 | } else {
183 | VulnerabilityTrendHelper.logMessage(listener, "Warning: Build result was not updated because the connection "+profile.getName()+" is configured to not update the build status when an error occurs.");
184 | }
185 | return false;
186 | }
187 |
188 | private boolean globalThresholdRequired(List conditions, ContrastSDK contrastSDK, TeamServerProfile profile)
189 | throws IOException, UnauthorizedException {
190 | for(ThresholdCondition condition : conditions) {
191 | if((!profile.isAllowGlobalThresholdConditionsOverride() || !overrideGlobalThresholdConditions)
192 | && !VulnerabilityTrendHelper.isApplicableEnabledJobOutcomePolicyExist(contrastSDK, profile.getOrgUuid(), condition.getPreparedApplicationId())) {
193 | return true;
194 | }
195 | }
196 | return false;
197 | }
198 |
199 | @Override
200 | public boolean perform(AbstractBuild, ?> build, Launcher launcher, final BuildListener listener) throws IOException, InterruptedException {
201 | boolean errorEncountered = false;
202 | if (!build.isBuilding()) {
203 | return false;
204 | }
205 |
206 | VulnerabilityTrendHelper.logMessage(listener, "Checking the number of vulnerabilities for this application.");
207 | ContrastSDK contrastSDK;
208 | Traces traces;
209 | Set resultTraces = new HashSet<>();
210 |
211 | TeamServerProfile profile = VulnerabilityTrendHelper.getProfile(teamServerProfileName);
212 | if(profile == null) {
213 | throw new AbortException("Unable to find TeamServer profile.");
214 | }
215 |
216 | final String CONTRAST_ERROR_PREFIX = profile.isApplyVulnerableBuildResultOnContrastError() ? "Error: " : "Warning: ";
217 |
218 | contrastSDK = VulnerabilityTrendHelper.createSDK(profile.getUsername(), profile.getServiceKey(), profile.getApiKey(), profile.getTeamServerUrl());
219 |
220 |
221 |
222 | try {
223 | final Organizations organizations = contrastSDK.getProfileDefaultOrganizations();
224 | if (organizations == null || organizations.getOrganization() == null) {
225 | String errorMessage = CONTRAST_ERROR_PREFIX + "No organization found, Check your credentials and URL.";
226 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
227 | updateBuildResultOnError(build, profile, errorMessage, listener);
228 | return true;
229 | }
230 |
231 | } catch (UnauthorizedException | IOException e) {
232 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to connect to Contrast.";
233 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
234 | VulnerabilityTrendHelper.logMessage(listener, e.getMessage());
235 | updateBuildResultOnError(build, profile, errorMessage, listener);
236 | return true;
237 | }
238 |
239 | boolean ignoreContrastFindings = Boolean.parseBoolean(build.getBuildVariableResolver().resolve("ignoreContrastFindings"));
240 | List globalThresholdConditions = VulnerabilityTrendHelper.getGlobalThresholdConditions(profile.getName());
241 | List thresholdConditions = conditions;
242 |
243 | // initialize app id in conditions
244 | for (ThresholdCondition condition : thresholdConditions) {
245 | MatchBy matchBy = condition.getMatchBy() == null ? MatchBy.APPLICATION_ID : condition.getMatchBy();
246 | switch(matchBy){
247 | case APPLICATION_ORIGIN_NAME:
248 | try {
249 | Application app = contrastSDK.getApplicationByNameAndLanguage(profile.getOrgUuid(), condition.getApplicationOriginName(), VulnerabilityTrendHelper.getAgentTypeFromString(condition.getAgentType()));
250 |
251 | if (app == null) {
252 | String errorMessage = String.format(CONTRAST_ERROR_PREFIX + "Application with [name = %s, agentType = %s] not found.", condition.getApplicationOriginName(), condition.getAgentType());
253 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
254 |
255 | if (condition.isFailOnAppNotFound()) {
256 | throw new AbortException(errorMessage);
257 | }
258 |
259 | if(updateBuildResultOnError(build, profile, errorMessage, listener)) {
260 | return true;
261 | } else {
262 | errorEncountered = true;
263 | continue;
264 | }
265 | } else {
266 | condition.setApplicationId(app.getId());
267 | VulnerabilityTrendHelper.logMessage(listener, "Fetched Application : [name = '" + condition.getApplicationOriginName() + "', displayName = '" + app.getName() + "', agentType='" + app.getLanguage() + "'] with ID: [" + condition.getPreparedApplicationId() + "]");
268 | }
269 | } catch (UnauthorizedException e) {
270 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to retrieve application information from Contrast.";
271 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
272 | VulnerabilityTrendHelper.logMessage(listener, e.getMessage());
273 | if(updateBuildResultOnError(build, profile, errorMessage, listener)) {
274 | return true;
275 | } else {
276 | errorEncountered = true;
277 | continue;
278 | }
279 | }
280 | break;
281 | case APPLICATION_ID:
282 | default:
283 | break;
284 | }
285 | }
286 |
287 | try {
288 | if (globalThresholdRequired(thresholdConditions, contrastSDK, profile)) {
289 | thresholdConditions =
290 | VulnerabilityTrendHelper.getThresholdConditions(conditions, globalThresholdConditions);
291 | if (thresholdConditions.isEmpty()) {
292 | String errorMessage = CONTRAST_ERROR_PREFIX + "Vulnerability Security Controls for connection '" + profile.getName() + "' are not defined.";
293 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
294 | if(updateBuildResultOnError(build, profile, errorMessage, listener)) {
295 | return true;
296 | } else {
297 | errorEncountered = true;
298 | }
299 | }
300 | }
301 | } catch (UnauthorizedException e) {
302 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to retrieve job outcome policy information from Contrast";
303 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
304 | VulnerabilityTrendHelper.logMessage(listener, e.getMessage());
305 | updateBuildResultOnError(build, profile, errorMessage, listener);
306 | return true;
307 | }
308 |
309 | // iterate over conditions; fail on first
310 | for (ThresholdCondition condition : thresholdConditions) {
311 | String appId = condition.getPreparedApplicationId();
312 |
313 | boolean applicationIdExists = VulnerabilityTrendHelper.applicationIdExists(contrastSDK, profile.getOrgUuid(), appId);
314 | if (!applicationIdExists) {
315 | String errorMessage = CONTRAST_ERROR_PREFIX + "Application with ID '" + appId + "' not found.";
316 | VulnerabilityTrendHelper.logMessage(listener, CONTRAST_ERROR_PREFIX + "Application with ID '" + appId + "' not found.");
317 | if(updateBuildResultOnError(build, profile, errorMessage, listener)) {
318 | return true;
319 | } else {
320 | errorEncountered = true;
321 | continue;
322 | }
323 | } else {
324 | try {
325 |
326 | TraceFilterForm filterForm = buildFilterFormForCondition(condition, build, appId, listener);
327 | SecurityCheck securityCheck = VulnerabilityTrendHelper.makeSecurityCheck(contrastSDK, profile.getOrgUuid(), appId, build.getTimeInMillis(), queryBy, filterForm);
328 |
329 | if(securityCheck.getResult() != null) {
330 | String applicationDisplayForConsoleOutput = condition.getStringForOverriden();
331 | VulnerabilityTrendHelper.logMessage(listener, "Checking application " + applicationDisplayForConsoleOutput);
332 | VulnerabilityTrendHelper.logMessage(listener,"Your Contrast admin has overridden policies you may have set in Vulnerability Security Controls or the 'query by' parameter");
333 |
334 | if(securityCheck.getResult()) { //failed a policy
335 | VulnerabilityTrendHelper.logMessage(listener, "This application did not violate any Contrast policies");
336 | return true;
337 | } else {
338 | try {
339 | Result jobResult = VulnerabilityTrendHelper.getJenkinsResultFromJobOutcome(securityCheck.getJobOutcomePolicy().getOutcome());
340 | String message = "This application "+applicationDisplayForConsoleOutput+" violated the Contrast policy '"+securityCheck.getJobOutcomePolicy().getName()+"'";
341 | VulnerabilityTrendHelper.logMessage(listener,message);
342 | if(Result.FAILURE.equals(jobResult)) {
343 | throw new AbortException(message);
344 | } else {
345 | build.setResult(jobResult);
346 | return true;
347 | }
348 | } catch (VulnerabilityTrendHelperException e) {
349 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to retrieve outcome from job outcome policy";
350 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
351 | VulnerabilityTrendHelper.logMessage(listener, e.getMessage());
352 | if(updateBuildResultOnError(build, profile, errorMessage, listener)) {
353 | return true;
354 | } else {
355 | errorEncountered = true;
356 | continue;
357 | }
358 | }
359 | }
360 | } else {
361 | VulnerabilityTrendHelper.logMessage(listener, "filterForm: " + filterForm);
362 | VulnerabilityTrendHelper.logMessage(listener, "Checking the threshold condition where " + condition.toString());
363 | if (queryBy == Constants.QUERY_BY_START_DATE || queryBy == Constants.QUERY_BY_PARAMETER) {
364 | traces = VulnerabilityTrendHelper.getAllTraces(contrastSDK, profile.getOrgUuid(), appId, filterForm);
365 | } else {
366 | traces = VulnerabilityTrendHelper.getAllTraces(contrastSDK, profile.getOrgUuid(), null, filterForm);
367 | }
368 | resultTraces.addAll(traces.getTraces());
369 | int thresholdCount = condition.getThresholdCount(); // Integer.parseInt(condition.getThresholdCount());
370 |
371 | if (traces.getCount() > thresholdCount && !ignoreContrastFindings) {
372 | // save results before failing build
373 | buildResult(resultTraces, build);
374 |
375 | Result buildResult = Result.fromString(profile.getVulnerableBuildResult());
376 | VulnerabilityTrendHelper.logMessage(listener, "Failed on the threshold condition where " + condition.toString());
377 | VulnerabilityTrendHelper.logMessage(listener, VulnerabilityTrendHelper.getVulnerabilityInfoString(traces));
378 | if (buildResult.toString().equals(Result.FAILURE.toString())) {
379 | throw new AbortException("Failed on the threshold condition where " + condition.toString());
380 | } else {
381 | build.setResult(buildResult);
382 | return true;
383 | }
384 | }
385 | }
386 | } catch (UnauthorizedException e) {
387 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to retrieve vulnerability information from TeamServer.";
388 | VulnerabilityTrendHelper.logMessage(listener, errorMessage);
389 | VulnerabilityTrendHelper.logMessage(listener, e.getMessage());
390 | if(updateBuildResultOnError(build, profile, errorMessage, listener)) {
391 | return true;
392 | } else {
393 | errorEncountered = true;
394 | continue;
395 | }
396 |
397 | }
398 | }
399 | }
400 |
401 | buildResult(resultTraces, build);
402 | if(!errorEncountered) {
403 | VulnerabilityTrendHelper.logMessage(listener, "This build passes all vulnerability threshold conditions!");
404 | }
405 |
406 | return true;
407 |
408 | }
409 |
410 | @Override
411 | public DescriptorImpl getDescriptor() {
412 | return (DescriptorImpl) super.getDescriptor();
413 | }
414 |
415 | @Override
416 | public BuildStepMonitor getRequiredMonitorService() {
417 | return BuildStepMonitor.NONE;
418 | }
419 |
420 | @Override
421 | public Action getProjectAction(AbstractProject, ?> project) {
422 | return new VulnerabilityTrendProjectAction(project);
423 | }
424 |
425 |
426 | /**
427 | * Descriptor for {@link VulnerabilityTrendRecorder}.
428 | */
429 | @Extension
430 | public static class DescriptorImpl extends BuildStepDescriptor {
431 |
432 | private List conditions;
433 | private String teamServerProfileName;
434 | private Boolean overrideGlobalThresholdConditions;
435 | private Integer queryBy;
436 |
437 | public DescriptorImpl() {
438 | super(VulnerabilityTrendRecorder.class);
439 | load();
440 | }
441 |
442 | @JavaScriptMethod
443 | public boolean isAllowGlobalThresholdConditionsOverride(String teamServerProfileName) {
444 | return VulnerabilityTrendHelper.getProfile(teamServerProfileName).isAllowGlobalThresholdConditionsOverride();
445 | }
446 |
447 | @SuppressWarnings("unused")
448 | public ListBoxModel doFillTeamServerProfileNameItems() {
449 | return VulnerabilityTrendHelper.getProfileNames();
450 | }
451 |
452 | /**
453 | * Allows this builder to be available for all classes.
454 | *
455 | * @param aClass Passed in class.
456 | * @return true
457 | */
458 | public boolean isApplicable(Class extends AbstractProject> aClass) {
459 | return true;
460 | }
461 |
462 | /**
463 | * Display name in the Build Action dropdown.
464 | *
465 | * @return String
466 | */
467 | public String getDisplayName() {
468 | return "Contrast Assess";
469 | }
470 |
471 | /**
472 | * Save's the publisher's config.jelly data.
473 | *
474 | * @param req StaplerRequest
475 | * @param json Json of the form for this Publisher
476 | * @return if the save was successful
477 | */
478 | @Override
479 | public Publisher newInstance(StaplerRequest req, JSONObject json) {
480 | final JSONArray array = json.optJSONArray("conditions");
481 |
482 | if (array != null) {
483 | conditions = req.bindJSONToList(ThresholdCondition.class, array);
484 | } else {
485 | conditions = new ArrayList<>();
486 |
487 | if (!json.keySet().isEmpty()) {
488 | conditions.add(req.bindJSON(ThresholdCondition.class, json.getJSONObject("conditions")));
489 | }
490 | }
491 | teamServerProfileName = (String) json.get("teamServerProfileName");
492 | overrideGlobalThresholdConditions = (Boolean) json.get("overrideGlobalThresholdConditions");
493 | queryBy = Integer.parseInt((String) json.get("queryBy"));
494 |
495 | save();
496 |
497 | return new VulnerabilityTrendRecorder(conditions, teamServerProfileName, overrideGlobalThresholdConditions, queryBy);
498 | }
499 |
500 | public List getConditions() {
501 | return conditions;
502 | }
503 |
504 | public void setConditions(List conditions) {
505 | this.conditions = conditions;
506 | }
507 | }
508 |
509 |
510 | /**
511 | * Builds a String representation of the Traces found when checking for vulnerabilities.
512 | *
513 | * @param traces - traces founding during build
514 | * @param build - current build
515 | */
516 | private void buildResult(Set traces, AbstractBuild, ?> build) {
517 | Map traceResult = new HashMap<>();
518 | Map severityResult = new HashMap<>();
519 |
520 | for (Trace trace : traces) {
521 |
522 | if (severityResult.containsKey(trace.getSeverity())) {
523 | Integer previousCount = severityResult.get(trace.getSeverity());
524 | severityResult.put(trace.getSeverity(), previousCount + 1);
525 | } else {
526 | severityResult.put(trace.getSeverity(), 1);
527 | }
528 |
529 | if (traceResult.containsKey(trace.getRule())) {
530 | Integer previousCount = traceResult.get(trace.getRule());
531 | traceResult.put(trace.getRule(), previousCount + 1);
532 | } else {
533 | traceResult.put(trace.getRule(), 1);
534 | }
535 | }
536 |
537 | // Add remaining severities for chart
538 | for (String severity : VulnerabilityTrendHelper.SEVERITIES) {
539 | if (!severityResult.containsKey(severity)) {
540 | severityResult.put(severity, 0);
541 | }
542 | }
543 |
544 | VulnerabilityTrendResult result = new VulnerabilityTrendResult(traceResult, severityResult);
545 |
546 | build.addAction(new VulnerabilityFrequencyAction(result, build));
547 | }
548 | }
549 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendResult.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import java.util.Map;
7 |
8 | @Getter
9 | @Setter
10 | public class VulnerabilityTrendResult {
11 |
12 | private Map traceResult;
13 | private Map severityResult;
14 |
15 | public VulnerabilityTrendResult(Map traceResult, Map severityResult) {
16 | this.traceResult = traceResult;
17 | this.severityResult = severityResult;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendStep.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import com.contrastsecurity.exceptions.UnauthorizedException;
4 | import com.contrastsecurity.http.TraceFilterForm;
5 | import com.contrastsecurity.models.Application;
6 | import com.contrastsecurity.models.Organizations;
7 | import com.contrastsecurity.models.SecurityCheck;
8 | import com.contrastsecurity.models.Traces;
9 | import com.contrastsecurity.sdk.ContrastSDK;
10 | import com.google.inject.Inject;
11 | import hudson.AbortException;
12 | import hudson.EnvVars;
13 | import hudson.Extension;
14 | import hudson.model.Result;
15 | import hudson.model.Run;
16 | import hudson.model.TaskListener;
17 | import hudson.util.ListBoxModel;
18 | import jenkins.model.Jenkins;
19 | import lombok.Getter;
20 | import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
21 | import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
22 | import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution;
23 | import org.jenkinsci.plugins.workflow.steps.Step;
24 | import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
25 | import org.kohsuke.stapler.DataBoundConstructor;
26 | import org.kohsuke.stapler.DataBoundSetter;
27 | import org.kohsuke.stapler.QueryParameter;
28 |
29 | import java.io.IOException;
30 | import java.util.ArrayList;
31 | import java.util.Collections;
32 | import java.util.Date;
33 | import java.util.List;
34 | import java.util.Map;
35 |
36 | @Getter
37 | public class VulnerabilityTrendStep extends AbstractStepImpl {
38 |
39 | private String profile;
40 |
41 | @DataBoundSetter
42 | public void setProfile(String profile) {
43 | this.profile = profile;
44 | }
45 |
46 | private int count;
47 |
48 | @DataBoundSetter
49 | public void setCount(int count) {
50 | this.count = count;
51 | }
52 |
53 | private String rule;
54 |
55 | @DataBoundSetter
56 | public void setRule(String rule) {
57 | this.rule = rule;
58 | }
59 |
60 | private String severity;
61 |
62 | @DataBoundSetter
63 | public void setSeverity(String severity) {
64 | this.severity = severity;
65 | }
66 |
67 | private String applicationId;
68 |
69 | @DataBoundSetter
70 | public void setApplicationId(String applicationId) {
71 | this.applicationId = applicationId;
72 | }
73 |
74 | private String applicationName;
75 |
76 | @DataBoundSetter
77 | public void setApplicationName(String applicationName) {
78 | this.applicationName = applicationName;
79 | }
80 |
81 | private String appVersionTag;
82 |
83 | @DataBoundSetter
84 | public void setAppVersionTag(String appVersionTag) {
85 | this.appVersionTag = appVersionTag;
86 | }
87 |
88 | private int queryBy;
89 |
90 | @DataBoundSetter
91 | public void setQueryBy(int queryBy) {
92 | this.queryBy = queryBy;
93 | }
94 |
95 | /**
96 | * Type of agent used to instrument the application
97 | */
98 | private String agentType;
99 |
100 | @DataBoundSetter
101 | public void setAgentType(String agentType) { this.agentType = agentType; }
102 |
103 | @DataBoundConstructor
104 | public VulnerabilityTrendStep(String profile, int count, String rule, String severity, String applicationId, int queryBy) {
105 | this.profile = profile;
106 | this.count = count;
107 | this.rule = rule;
108 | this.severity = severity;
109 | this.applicationId = applicationId;
110 | this.queryBy = queryBy;
111 | }
112 |
113 | // Used to build the new instance
114 | public VulnerabilityTrendStep() {
115 |
116 | }
117 |
118 | @Override
119 | public VulnerabilityTrendStepDescriptorImpl getDescriptor() {
120 | Jenkins instance = Jenkins.getInstance();
121 |
122 | if (instance != null) {
123 | return (VulnerabilityTrendStepDescriptorImpl) instance.getDescriptor(getClass());
124 | } else {
125 | return null;
126 | }
127 | }
128 |
129 | @Extension
130 | public static class VulnerabilityTrendStepDescriptorImpl extends AbstractStepDescriptorImpl {
131 |
132 | public VulnerabilityTrendStepDescriptorImpl() {
133 | super(Execution.class);
134 | }
135 |
136 | @Override
137 | public String getFunctionName() {
138 | return "contrastVerification";
139 | }
140 |
141 | @Override
142 | public String getDisplayName() {
143 | return "Verify vulnerabilities in a build";
144 | }
145 |
146 | @Override
147 | public Step newInstance(Map arguments) {
148 | VulnerabilityTrendStep step = new VulnerabilityTrendStep();
149 |
150 | if (arguments.containsKey("profile")) {
151 | Object profile = arguments.get("profile");
152 |
153 | if (profile != null) {
154 | step.setProfile((String) profile);
155 | } else {
156 | throw new IllegalArgumentException("Profile must be set.");
157 | }
158 | }
159 |
160 | if (arguments.containsKey("count")) {
161 | Object count = arguments.get("count");
162 | step.setCount((int) count);
163 | }
164 |
165 | if (arguments.containsKey("rule")) {
166 | Object rule = arguments.get("rule");
167 | step.setRule((String) rule);
168 | }
169 |
170 | if (arguments.containsKey("severity")) {
171 | Object severity = arguments.get("severity");
172 | step.setSeverity((String) severity);
173 | }
174 |
175 | if (arguments.containsKey("applicationId")) {
176 | String applicationId = (String) arguments.get("applicationId");
177 | step.setApplicationId(applicationId);
178 | }
179 |
180 | if (step.getApplicationId() == null) {
181 | Object applicationName = arguments.get("applicationName");
182 |
183 | if (applicationName != null) {
184 | step.setApplicationName((String) applicationName);
185 | } else {
186 | throw new IllegalArgumentException("If Application ID is not set, Application Name must be set.");
187 | }
188 | }
189 |
190 | if (step.getApplicationId() == null
191 | //// Compatibility fix for plugin versions <=2.6
192 | && arguments.containsKey("agentType")) {
193 | Object agentType = arguments.get("agentType");
194 |
195 | if (agentType != null) {
196 | step.setAgentType((String) agentType);
197 | } else {
198 | throw new IllegalArgumentException("If Application ID is not set, Agent Type must be set.");
199 | }
200 | }
201 |
202 |
203 | if (arguments.containsKey("queryBy")) {
204 | Object queryBy = arguments.get("queryBy");
205 |
206 | step.setQueryBy((int) queryBy);
207 |
208 | if (step.getQueryBy() == Constants.QUERY_BY_PARAMETER) {
209 | step.setAppVersionTag((String) arguments.get("appVersionTag"));
210 | }
211 | } else if (arguments.containsKey("appVersionTagFormat")) {
212 | Object queryBy = arguments.get("appVersionTagFormat");
213 | step.setQueryBy((int) queryBy);
214 | }
215 |
216 | return step;
217 | }
218 |
219 | @SuppressWarnings("unused")
220 | public ListBoxModel doFillProfileItems() {
221 | return VulnerabilityTrendHelper.getProfileNames();
222 | }
223 |
224 | /**
225 | * Fills the Threshold Category select drop down with application ids.
226 | *
227 | * @return ListBoxModel filled with application ids.
228 | */
229 | public ListBoxModel doFillApplicationIdItems(@QueryParameter("profile") final String teamServerProfileName) throws IOException {
230 | return VulnerabilityTrendHelper.getApplicationIds(teamServerProfileName);
231 | }
232 |
233 | @SuppressWarnings("unused")
234 | public ListBoxModel doFillRuleItems(@QueryParameter("profile") final String teamServerProfileName) {
235 | return VulnerabilityTrendHelper.getVulnerabilityTypes(teamServerProfileName);
236 | }
237 |
238 | @SuppressWarnings("unused")
239 | public ListBoxModel doFillSeverityItems() {
240 | return VulnerabilityTrendHelper.getSeverityListBoxModel();
241 | }
242 | }
243 |
244 | public static class Execution extends AbstractSynchronousStepExecution {
245 | private static final long serialVersionUID = 1L;
246 |
247 | @StepContextParameter
248 | transient Run, ?> build;
249 |
250 | @StepContextParameter
251 | transient TaskListener taskListener;
252 |
253 | @Inject
254 | transient VulnerabilityTrendStep step;
255 |
256 | private TraceFilterForm makeFilterFormWithQueryBy() throws IOException, InterruptedException {
257 | TraceFilterForm filterForm = new TraceFilterForm();
258 |
259 | if (step.getQueryBy() == Constants.QUERY_BY_APP_VERSION_TAG_HIERARCHICAL_FORMAT) {
260 |
261 | String appVersionTag = VulnerabilityTrendHelper.buildAppVersionTagHierarchical(build, step.getApplicationId());
262 |
263 | List appVersionTagsList = new ArrayList<>();
264 | appVersionTagsList.add(appVersionTag);
265 |
266 | if (step.getApplicationName() != null) {
267 | String appVersionTagAppName = VulnerabilityTrendHelper.buildAppVersionTagHierarchical(build, step.getApplicationName());
268 | appVersionTagsList.add(appVersionTagAppName);
269 | }
270 |
271 | filterForm.setAppVersionTags(appVersionTagsList);
272 | } else if (step.getQueryBy() == Constants.QUERY_BY_START_DATE) {
273 | filterForm.setStartDate(new Date(build.getStartTimeInMillis()));
274 | } else if (step.getQueryBy() == Constants.QUERY_BY_PARAMETER) {
275 | final EnvVars env = build.getEnvironment(taskListener);
276 | String appVersionTag;
277 |
278 | if (step.getAppVersionTag() != null) {
279 | appVersionTag = step.getAppVersionTag();
280 | } else if (env.get("APPVERSIONTAG") != null) {
281 | appVersionTag = env.get("APPVERSIONTAG");
282 | } else {
283 | appVersionTag = "";
284 | }
285 |
286 | if (appVersionTag.isEmpty()) {
287 | VulnerabilityTrendHelper.logMessage(taskListener, "Warning: queryBy Parameter is configured, but appVersionTag is not set. All vulnerabilities will be returned for this application");
288 | }
289 |
290 | List appVersionTagsList = new ArrayList<>();
291 | appVersionTagsList.add(appVersionTag);
292 |
293 | filterForm.setAppVersionTags(appVersionTagsList);
294 | } else {
295 | String appVersionTag = VulnerabilityTrendHelper.buildAppVersionTag(build, step.getApplicationId());
296 |
297 | List appVersionTagsList = new ArrayList<>();
298 | appVersionTagsList.add(appVersionTag);
299 |
300 | if (step.getApplicationName() != null) {
301 | String appVersionTagAppName = VulnerabilityTrendHelper.buildAppVersionTag(build, step.getApplicationName());
302 | appVersionTagsList.add(appVersionTagAppName);
303 | }
304 |
305 | filterForm.setAppVersionTags(appVersionTagsList);
306 | }
307 |
308 | return filterForm;
309 | }
310 |
311 | /**
312 | * Set the build result based on contrast configuration
313 | * @param profile teamserver profile
314 | * @parma message Message to log when result is failure
315 | * @return true = the result was set, false = the result was not set
316 | */
317 | private boolean updateBuildResult(TeamServerProfile profile, String message) throws AbortException {
318 | if(profile.isApplyVulnerableBuildResultOnContrastError()) {
319 | Result profileVulnerableBuildResult = Result.fromString(profile.getVulnerableBuildResult());
320 | VulnerabilityTrendHelper.logMessage(taskListener, "Setting build result to : "+profileVulnerableBuildResult.toString());
321 | if(Result.FAILURE.equals(profileVulnerableBuildResult)) {
322 | throw new AbortException(message);
323 | } else {
324 | build.setResult(profileVulnerableBuildResult);
325 | }
326 | return true;
327 | }
328 | return false;
329 | }
330 |
331 | @Override
332 | public Void run() throws AbortException, InterruptedException {
333 | TeamServerProfile teamServerProfile = VulnerabilityTrendHelper.getProfile(step.getProfile());
334 |
335 | if (teamServerProfile == null) {
336 | VulnerabilityTrendHelper.logMessage(taskListener, "Unable to find TeamServer profile.");
337 | throw new AbortException("Unable to find TeamServer profile.");
338 | }
339 |
340 | final String CONTRAST_ERROR_PREFIX = teamServerProfile.isApplyVulnerableBuildResultOnContrastError() ? "Error: " : "Warning: ";
341 | ContrastSDK contrastSDK = VulnerabilityTrendHelper.createSDK(teamServerProfile.getUsername(), teamServerProfile.getServiceKey(),
342 | teamServerProfile.getApiKey(), teamServerProfile.getTeamServerUrl());
343 |
344 | try {
345 | final Organizations organizations = contrastSDK.getProfileDefaultOrganizations();
346 | if (organizations == null || organizations.getOrganization() == null) {
347 | String errorMessage = CONTRAST_ERROR_PREFIX + "No organization found, Check your credentials and URL.";
348 | VulnerabilityTrendHelper.logMessage(taskListener, errorMessage);
349 | updateBuildResult(teamServerProfile, errorMessage);
350 | return null;
351 | }
352 |
353 | } catch (UnauthorizedException | IOException e) {
354 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to connect to Contrast.";
355 | VulnerabilityTrendHelper.logMessage(taskListener, errorMessage);
356 | VulnerabilityTrendHelper.logMessage(taskListener, e.getMessage());
357 | updateBuildResult(teamServerProfile, errorMessage);
358 | return null;
359 | }
360 |
361 | //Convert app name and agent type to app id
362 | if(step.getApplicationId() == null && step.getApplicationName() != null && step.getAgentType() != null) {
363 | try {
364 | Application app = contrastSDK.getApplicationByNameAndLanguage(teamServerProfile.getOrgUuid(),
365 | step.getApplicationName(),
366 | VulnerabilityTrendHelper.getAgentTypeFromString(step.getAgentType()));
367 |
368 | if(app == null) {
369 | String errorMessage = String.format(CONTRAST_ERROR_PREFIX + "Application with [name = %s, agentType = %s] not found.", step.getApplicationName(), step.getAgentType());
370 | VulnerabilityTrendHelper.logMessage(taskListener, errorMessage);
371 | updateBuildResult(teamServerProfile, errorMessage);
372 | return null;
373 | } else {
374 | step.setApplicationId(app.getId());
375 | VulnerabilityTrendHelper.logMessage(taskListener, "Fetched Application : [name = '"+step.getApplicationName()+"', displayName = '"+app.getName()+"', agentType='"+app.getLanguage()+"'] with ID: ["+step.getApplicationId()+"]");
376 | }
377 | } catch (UnauthorizedException | IOException e) {
378 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to retrieve information from TeamServer.";
379 | VulnerabilityTrendHelper.logMessage(taskListener, errorMessage);
380 | VulnerabilityTrendHelper.logMessage(taskListener, e.getMessage());
381 | updateBuildResult(teamServerProfile, errorMessage);
382 | return null;
383 | }
384 | }
385 |
386 | // Convert app display name to app id
387 | //// Compatibility fix for plugin versions <=2.6
388 | if (step.getApplicationId() == null && step.getApplicationName() != null && step.getAgentType() == null) {
389 | for (App app : teamServerProfile.getApps()) {
390 | String subStr = app.getTitle().substring(0, app.getTitle().lastIndexOf(" ("));
391 | if (subStr.equals(step.getApplicationName())) {
392 | step.setApplicationId(app.getName());
393 | break;
394 | }
395 | }
396 | }
397 | //validation
398 | boolean applicationIdExists = VulnerabilityTrendHelper.applicationIdExists(contrastSDK, teamServerProfile.getOrgUuid(), step.getApplicationId());
399 | if (!applicationIdExists) {
400 | String errorMessage = CONTRAST_ERROR_PREFIX + "Application with ID '" + step.getApplicationId() + "' not found.";
401 | VulnerabilityTrendHelper.logMessage(taskListener, errorMessage);
402 | updateBuildResult(teamServerProfile, errorMessage);
403 | return null;
404 | } else {
405 | VulnerabilityTrendHelper.logMessage(taskListener, "Checking the number of vulnerabilities for " + step.getApplicationId());
406 |
407 | String stepString = step.buildStepString();
408 |
409 | try {
410 | //makeFilterForm
411 | TraceFilterForm filterForm = makeFilterFormWithQueryBy();
412 |
413 | SecurityCheck securityCheck = VulnerabilityTrendHelper.makeSecurityCheck(contrastSDK, teamServerProfile.getOrgUuid(), step.getApplicationId(), build.getStartTimeInMillis(), step.queryBy, filterForm);
414 | StringBuilder applicationDisplayForConsoleOutputBuilder = new StringBuilder("[");
415 |
416 | if(step.getApplicationName() != null && !step.getApplicationName().isEmpty()) {
417 | applicationDisplayForConsoleOutputBuilder.append("name = " + step.getApplicationName());
418 | }
419 | if(step.getAgentType() != null && !step.getAgentType().isEmpty()) {
420 | applicationDisplayForConsoleOutputBuilder.append(", agentType = "+step.getAgentType());
421 | }
422 | if(step.getApplicationId() != null && !step.getApplicationId().isEmpty()) {
423 | applicationDisplayForConsoleOutputBuilder.append(", appId = "+step.getApplicationId());
424 | }
425 | applicationDisplayForConsoleOutputBuilder.append("]");
426 |
427 | String appicationDisplayForConsoleOutput = applicationDisplayForConsoleOutputBuilder.toString();
428 |
429 | if(securityCheck.getResult() != null) { // jop is defined for app
430 | VulnerabilityTrendHelper.logMessage(taskListener,"Your Contrast admin has overridden policies you may have set in Vulnerability Security Controls or the 'query by' parameter");
431 | if(securityCheck.getResult()) { //failed a policy
432 | VulnerabilityTrendHelper.logMessage(taskListener, "This application did not violate any Contrast policies");
433 | } else {
434 | try {
435 | Result jobResult = VulnerabilityTrendHelper.getJenkinsResultFromJobOutcome(securityCheck.getJobOutcomePolicy().getOutcome());
436 | String message = "This application "+appicationDisplayForConsoleOutput+" has failed the Contrast policy '"+securityCheck.getJobOutcomePolicy().getName()+"'";
437 | VulnerabilityTrendHelper.logMessage(taskListener,message);
438 | VulnerabilityTrendHelper.logMessage(taskListener, "Setting build result to : " + jobResult);
439 | if(Result.FAILURE.equals(jobResult)) {
440 | throw new AbortException(message);
441 | } else {
442 | build.setResult(jobResult);
443 | return null;
444 | }
445 | } catch (VulnerabilityTrendHelperException e) {
446 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to retrieve outcome from job outcome policy";
447 | VulnerabilityTrendHelper.logMessage(taskListener, errorMessage);
448 | VulnerabilityTrendHelper.logMessage(taskListener, e.getMessage());
449 | updateBuildResult(teamServerProfile, errorMessage);
450 | return null;
451 | }
452 | }
453 | } else { //regular verify
454 |
455 | VulnerabilityTrendHelper.logMessage(taskListener, "Checking the step condition where " + stepString);
456 |
457 | Traces traces;
458 |
459 | if (step.getSeverity() != null) {
460 | filterForm.setSeverities(VulnerabilityTrendHelper.getSeverityList(step.getSeverity()));
461 | }
462 |
463 | if (step.getRule() != null) {
464 | filterForm.setVulnTypes(Collections.singletonList(step.getRule()));
465 | }
466 | VulnerabilityTrendHelper.logMessage(taskListener, "filterForm: " + filterForm);
467 | if (step.getQueryBy() == Constants.QUERY_BY_START_DATE || step.getQueryBy() == Constants.QUERY_BY_PARAMETER) {
468 | traces = VulnerabilityTrendHelper.getAllTraces(contrastSDK, teamServerProfile.getOrgUuid(), step.getApplicationId(), filterForm);
469 | } else {
470 | traces = VulnerabilityTrendHelper.getAllTraces(contrastSDK, teamServerProfile.getOrgUuid(), null, filterForm);
471 | }
472 |
473 | if (traces.getCount() > step.getCount()) {
474 | Result buildResult = Result.fromString(teamServerProfile.getVulnerableBuildResult());
475 | VulnerabilityTrendHelper.logMessage(taskListener, "Failed on the condition where " + stepString);
476 | VulnerabilityTrendHelper.logMessage(taskListener, VulnerabilityTrendHelper.getVulnerabilityInfoString(traces));
477 | VulnerabilityTrendHelper.logMessage(taskListener, "Setting build result to : " + buildResult);
478 | if (buildResult.toString().equals(Result.FAILURE.toString())) {
479 | throw new AbortException("Failed on the condition where " + stepString);
480 | } else {
481 | build.setResult(buildResult);
482 | return null;
483 | }
484 |
485 | }
486 | VulnerabilityTrendHelper.logMessage(taskListener, "This step has passed successfully");
487 | }
488 | } catch (AbortException e) { //Fail Fast when Failure is selected for a vulnerable build
489 | throw e;
490 | }catch (UnauthorizedException | IOException e) {
491 | String errorMessage = CONTRAST_ERROR_PREFIX + "Unable to retrieve vulnerability information from TeamServer.";
492 | VulnerabilityTrendHelper.logMessage(taskListener, errorMessage);
493 | VulnerabilityTrendHelper.logMessage(taskListener, e.getMessage());
494 | updateBuildResult(teamServerProfile, errorMessage);
495 | return null;
496 | }
497 | }
498 | return null;
499 | }
500 |
501 | String getBuildName() {
502 | return build.getParent().getFullName();
503 | }
504 | }
505 |
506 | private String buildStepString() {
507 | StringBuilder sb = new StringBuilder();
508 |
509 | sb.append("count is ").append(count);
510 |
511 | if (severity != null) {
512 | sb.append(", severity is ").append(severity);
513 | }
514 |
515 | if (rule != null) {
516 | sb.append(", rule type is ").append(rule);
517 | }
518 |
519 | if (applicationId != null) {
520 | sb.append(", applicationId is ").append(applicationId);
521 | }
522 |
523 | if (queryBy != 0) {
524 | sb.append(", queryBy is ").append(queryBy);
525 | }
526 |
527 | sb.append(".");
528 |
529 | return sb.toString();
530 | }
531 | }
532 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityType.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 |
6 | @Data
7 | @AllArgsConstructor
8 | public class VulnerabilityType {
9 |
10 | public String name;
11 | public String title;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/plots/SeverityFrequencyPlot.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins.plots;
2 |
3 | import com.aspectsecurity.contrast.contrastjenkins.VulnerabilityFrequencyAction;
4 | import com.aspectsecurity.contrast.contrastjenkins.VulnerabilityTrendHelper;
5 | import hudson.model.AbstractProject;
6 | import hudson.model.Run;
7 | import hudson.util.Graph;
8 | import hudson.util.RunList;
9 | import hudson.util.ShiftedCategoryAxis;
10 | import org.jfree.chart.ChartFactory;
11 | import org.jfree.chart.JFreeChart;
12 | import org.jfree.chart.axis.CategoryAxis;
13 | import org.jfree.chart.axis.NumberAxis;
14 | import org.jfree.chart.plot.CategoryPlot;
15 | import org.jfree.chart.plot.PlotOrientation;
16 | import org.jfree.chart.renderer.category.BarRenderer;
17 | import org.jfree.data.category.DefaultCategoryDataset;
18 |
19 | import java.awt.*;
20 | import java.util.ArrayList;
21 | import java.util.Calendar;
22 | import java.util.Collections;
23 | import java.util.Map;
24 |
25 |
26 | public class SeverityFrequencyPlot extends Graph {
27 |
28 | private AbstractProject, ?> project;
29 |
30 | public SeverityFrequencyPlot(AbstractProject, ?> project) {
31 | super(Calendar.getInstance(), 500, 200);
32 | this.project = project;
33 | }
34 |
35 | @Override
36 | protected JFreeChart createGraph() {
37 | DefaultCategoryDataset dataset = createSeverityFrequencyDataset();
38 |
39 | JFreeChart chart = ChartFactory.createStackedBarChart(
40 | null, // title
41 | "Build Number", // x axis
42 | "Severity Count", // y axis
43 | dataset, // data
44 | PlotOrientation.VERTICAL, // orientation
45 | true, // legend
46 | true, // tooltips
47 | false); //urls
48 |
49 | chart.setBackgroundPaint(Color.white);
50 |
51 | CategoryPlot plot = (CategoryPlot) chart.getPlot();
52 | plot.setBackgroundPaint(Color.WHITE);
53 | plot.setOutlinePaint(null);
54 | plot.setRangeGridlinesVisible(true);
55 | plot.setRangeGridlinePaint(Color.black);
56 |
57 | BarRenderer renderer = (BarRenderer) plot.getRenderer();
58 | setColors(renderer);
59 |
60 | // Set colors here
61 | plot.setRenderer(renderer);
62 |
63 | CategoryAxis domainAxis = new ShiftedCategoryAxis("Build Number");
64 | plot.setDomainAxis(domainAxis);
65 |
66 | NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
67 | rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
68 | rangeAxis.setAutoRange(true);
69 |
70 | return chart;
71 | }
72 |
73 | private DefaultCategoryDataset createSeverityFrequencyDataset() {
74 | java.util.List actions = new ArrayList<>();
75 | RunList> builds = project.getBuilds().limit(10);
76 |
77 | // Get all build actions
78 | for (Run, ?> run : builds) {
79 | VulnerabilityFrequencyAction action = run.getAction(VulnerabilityFrequencyAction.class);
80 |
81 | if (action == null) {
82 | continue;
83 | }
84 | actions.add(action);
85 | }
86 |
87 | // put them in chronological order
88 | Collections.reverse(actions);
89 |
90 | // build data setup
91 | DefaultCategoryDataset ds = new DefaultCategoryDataset();
92 | for (VulnerabilityFrequencyAction action : actions) {
93 | Map result = action.getResult().getSeverityResult();
94 |
95 | String buildNumber = Integer.toString(action.getBuildNumber());
96 |
97 | for (String severity : VulnerabilityTrendHelper.SEVERITIES) {
98 | ds.addValue(result.get(severity), severity, buildNumber);
99 | }
100 | }
101 |
102 | return ds;
103 | }
104 |
105 |
106 | private java.util.List setColors(BarRenderer renderer) {
107 | java.util.List colors = new ArrayList<>();
108 |
109 | renderer.setSeriesPaint(0, new Color(232, 232, 232));
110 | renderer.setSeriesPaint(1, new Color(186, 186, 186));
111 | renderer.setSeriesPaint(2, new Color(247, 182, 0));
112 | renderer.setSeriesPaint(3, new Color(247, 138, 49));
113 | renderer.setSeriesPaint(4, new Color(230, 48, 37));
114 |
115 | return colors;
116 | }
117 | }
--------------------------------------------------------------------------------
/src/main/java/com/aspectsecurity/contrast/contrastjenkins/plots/VulnerabilityFrequencyPlot.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins.plots;
2 |
3 | import com.aspectsecurity.contrast.contrastjenkins.VulnerabilityFrequencyAction;
4 | import hudson.model.AbstractProject;
5 | import hudson.model.Run;
6 | import hudson.util.Graph;
7 | import hudson.util.RunList;
8 | import hudson.util.ShiftedCategoryAxis;
9 | import org.jfree.chart.ChartFactory;
10 | import org.jfree.chart.JFreeChart;
11 | import org.jfree.chart.axis.CategoryAxis;
12 | import org.jfree.chart.axis.NumberAxis;
13 | import org.jfree.chart.plot.CategoryPlot;
14 | import org.jfree.chart.plot.PlotOrientation;
15 | import org.jfree.chart.renderer.category.BarRenderer;
16 | import org.jfree.data.category.DefaultCategoryDataset;
17 |
18 | import java.awt.*;
19 | import java.util.ArrayList;
20 | import java.util.Calendar;
21 | import java.util.Collections;
22 | import java.util.List;
23 | import java.util.Map;
24 |
25 |
26 | public class VulnerabilityFrequencyPlot extends Graph {
27 |
28 | private AbstractProject, ?> project;
29 |
30 | public VulnerabilityFrequencyPlot(AbstractProject, ?> project) {
31 | super(Calendar.getInstance(), 500, 200);
32 | this.project = project;
33 | }
34 |
35 | @Override
36 | protected JFreeChart createGraph() {
37 | DefaultCategoryDataset dataset = createVulnerabilityFrequencyDataset();
38 |
39 | JFreeChart chart = ChartFactory.createStackedBarChart(
40 | null, // title
41 | "Build Number", // x axis title
42 | "Vulnerability Count", // y axis title
43 | dataset, // data
44 | PlotOrientation.VERTICAL, // orientation
45 | true, // legend
46 | true, // tooltips
47 | false); // urls
48 |
49 | chart.setBackgroundPaint(Color.white);
50 |
51 | CategoryPlot plot = (CategoryPlot) chart.getPlot();
52 | plot.setBackgroundPaint(Color.WHITE);
53 | plot.setOutlinePaint(null);
54 | plot.setRangeGridlinesVisible(true);
55 | plot.setRangeGridlinePaint(Color.black);
56 |
57 | BarRenderer renderer = (BarRenderer) plot.getRenderer();
58 | setColors(renderer);
59 |
60 | // Set colors here
61 | plot.setRenderer(renderer);
62 |
63 | CategoryAxis domainAxis = new ShiftedCategoryAxis("Build Number");
64 | plot.setDomainAxis(domainAxis);
65 |
66 | NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
67 | rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
68 | rangeAxis.setAutoRange(true);
69 |
70 | return chart;
71 | }
72 |
73 | private DefaultCategoryDataset createVulnerabilityFrequencyDataset() {
74 | List actions = new ArrayList<>();
75 | RunList> builds = project.getBuilds().limit(10);
76 |
77 | // Get all build actions
78 | for (Run, ?> run : builds) {
79 | VulnerabilityFrequencyAction action = run.getAction(VulnerabilityFrequencyAction.class);
80 |
81 | if (action == null) {
82 | continue;
83 | }
84 | actions.add(action);
85 | }
86 |
87 | // put them in chronological order
88 | Collections.reverse(actions);
89 |
90 | // build data setup
91 | DefaultCategoryDataset ds = new DefaultCategoryDataset();
92 | for (VulnerabilityFrequencyAction action : actions) {
93 | Map result = action.getResult().getTraceResult();
94 |
95 | for (Map.Entry entry : result.entrySet()) {
96 | ds.addValue(entry.getValue(), entry.getKey(), Integer.toString(action.getBuildNumber()));
97 | }
98 | }
99 | return ds;
100 | }
101 |
102 | private List setColors(BarRenderer renderer) {
103 | List colors = new ArrayList<>();
104 |
105 | renderer.setSeriesPaint(0, new Color(60, 195, 178));
106 | renderer.setSeriesPaint(1, new Color(174, 205, 67));
107 | renderer.setSeriesPaint(2, new Color(247, 182, 0));
108 | renderer.setSeriesPaint(3, new Color(94, 68, 130));
109 | renderer.setSeriesPaint(4, new Color(247, 138, 49));
110 |
111 | renderer.setSeriesPaint(5, new Color(60, 195, 178, 128));
112 | renderer.setSeriesPaint(6, new Color(174, 205, 67, 128));
113 | renderer.setSeriesPaint(7, new Color(247, 182, 0, 128));
114 | renderer.setSeriesPaint(8, new Color(94, 68, 130, 128));
115 | renderer.setSeriesPaint(9, new Color(247, 138, 49, 128));
116 |
117 | renderer.setSeriesPaint(10, new Color(49, 67, 78));
118 | renderer.setSeriesPaint(11, new Color(37, 140, 191));
119 | renderer.setSeriesPaint(12, new Color(230, 48, 37));
120 |
121 | renderer.setSeriesPaint(13, new Color(49, 67, 78, 128));
122 | renderer.setSeriesPaint(14, new Color(37, 140, 191, 128));
123 | renderer.setSeriesPaint(15, new Color(230, 48, 37, 128));
124 |
125 | return colors;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/ContrastAgentStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/ContrastAgentStep/help.html:
--------------------------------------------------------------------------------
1 |
2 | Pipeline step for adding a Contrast agent to your build.
3 |
4 | Usage Example:
5 |
6 | contrastAgent profile: 'Localhost', outputDirectory: "${project.build.directory} + '/tmp'"
7 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/ContrastPluginConfig/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/ContrastPluginConfig/global.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Configure the security controls for your Contrast Connection by defining these vulnerability filters.
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityFrequencyAction/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vulnerability Report
6 |
7 |
Vulnerability Rule Count
8 |
9 |
10 | - ${trace.key} : ${trace.value}
11 |
12 |
13 |
14 |
Vulnerability Severity Count
15 |
16 |
17 | - ${severity} : ${it.severityResult.get(severity)}
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendProjectAction/floatingBox.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Vulnerability Trends Across Builds
9 |

10 |
11 |
12 |
13 |
Severity Trends Across Builds
14 |

15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendProjectAction/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendRecorder/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
41 |
42 |
43 |
45 |
46 |
47 |
48 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
117 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendRecorder/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main/resources/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendStep/help.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | This plugin is for continuous application security with Contrast integration.
5 |
6 |
--------------------------------------------------------------------------------
/src/main/webapp/help-allowGlobalThresholdConditionsOverride.html:
--------------------------------------------------------------------------------
1 |
2 | This option allows you to choose if the global Contrast Vulnerability Threshold Conditions can be overridden in a job configuration.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help-apiKey.html:
--------------------------------------------------------------------------------
1 |
2 | You may find your Contrast API Key in the Your Account->Profile section of the Contrast UI.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help-applicationId.html:
--------------------------------------------------------------------------------
1 |
2 | ID of the application on TeamServer.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help-applicationName.html:
--------------------------------------------------------------------------------
1 |
2 | The application name that you will provide to the Contrast agent during application startup to begin instrumentation.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help-applicationNotInstrumented.html:
--------------------------------------------------------------------------------
1 |
2 | Contrast will not know of your application's existence if it has not yet been run with the Contrast agent.
3 | In this case, enter your application name and language. If the application display name is different from the application name, use the application name.
4 | The Contrast-Jenkins plugin will query Contrast for vulnerability information using the exact name provided.
5 |
--------------------------------------------------------------------------------
/src/main/webapp/help-applyVulnerableBuildResultOnContrastError.html:
--------------------------------------------------------------------------------
1 |
2 | This option allows you to fail builds if the specified application ID is not found on TeamServer.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help-orgUuid.html:
--------------------------------------------------------------------------------
1 |
2 | You may find your Organization ID in the Your Account->Profile section of the Contrast UI.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help-queryBy-appVersionTag.html:
--------------------------------------------------------------------------------
1 |
2 | Filter vulnerabilities using the appVersionTag parameter in the following format "${applicationId}-${BUILD_NUMBER}".
3 | It may be necessary to override the value reported by the agent at runtime. For example, with the Java agent: -Dcontrast.override.appversion=${applicationId}-${BUILD_NUMBER}.
4 | applicationId is the ID of the application selected.
5 |
--------------------------------------------------------------------------------
/src/main/webapp/help-queryBy-appVersionTagBuildName.html:
--------------------------------------------------------------------------------
1 |
2 | Filter vulnerabilities using the appVersionTag parameter in the following format "${applicationId}-${JOB_NAME}-${BUILD_NUMBER}".
3 | It may be necessary to override the value reported by the agent at runtime. For example, with the Java agent: -Dcontrast.override.appversion=${applicationId}-${JOB_NAME}-${BUILD_NUMBER}.
4 | applicationId is the ID of the application selected.
5 |
--------------------------------------------------------------------------------
/src/main/webapp/help-queryBy-parameter.html:
--------------------------------------------------------------------------------
1 |
2 | Filter vulnerabilities in this build based on the value of the APPVERSIONTAG environment variable. This requires the application version reported by the Contrast to agent match the value of APPVERSIONTAG.
3 | It may be necessary to override the value reported by the agent at runtime. For example, with the Java agent: -Dcontrast.override.appversion=v1.2.3 .
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help-queryBy-startDate.html:
--------------------------------------------------------------------------------
1 |
2 | Filter vulnerabilities based on the startDate parameter (build timestamp), when the build was scheduled. Choosing this option excludes vulnerabilities found before the build was scheduled.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help-serviceKey.html:
--------------------------------------------------------------------------------
1 |
2 | You may find your Contrast Service Key (Not the Agent Service Key) in the Your Account->Profile section of the Contrast UI.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help-teamServerUrl.html:
--------------------------------------------------------------------------------
1 |
2 |
This is your TeamServer URL. If you are a SaaS customer then this is http://app.contrastsecurity.com/Contrast/api.
3 |
4 |
If you are EOP then use your hostname.
5 |
6 |
--------------------------------------------------------------------------------
/src/main/webapp/help-thresholdCount.html:
--------------------------------------------------------------------------------
1 |
2 | Use 0 to fail on any number of vulnerabilities.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help-thresholdSeverity.html:
--------------------------------------------------------------------------------
1 |
2 | This is the threshold severity to filter on.
3 |
4 |
5 |
6 | This variable is inclusive so selection 'Low' will filter on severities 'Low' and above.
7 |
8 |
--------------------------------------------------------------------------------
/src/main/webapp/help-thresholdVulnType.html:
--------------------------------------------------------------------------------
1 |
2 | This is the vulnerability type (rule) to filter on.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help-username.html:
--------------------------------------------------------------------------------
1 |
2 | This is the username (usually your email) from your Contrast account login credentials.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help-vulnerabilityStatus.html:
--------------------------------------------------------------------------------
1 |
2 | Only vulnerabilities with the selected statuses will be verified against the Threshold Conditions.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help-vulnerableBuildResult.html:
--------------------------------------------------------------------------------
1 |
2 | This option allows you to select the result of the build that violates the Vulnerability Threshold Conditions.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/img/trend_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/contrast-continuous-application-security-plugin/725c21f4a7452656bb4ef2ad05b301b54da66817/src/main/webapp/img/trend_graph.png
--------------------------------------------------------------------------------
/src/test/java/com/aspectsecurity/contrast/contrastjenkins/ContrastPluginConfigStub.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 |
4 | public class ContrastPluginConfigStub extends ContrastPluginConfig {
5 |
6 | public ContrastPluginConfigStub() {
7 | super();
8 | }
9 |
10 | public static class ContrastPluginConfigDescriptorStub extends ContrastPluginConfig.ContrastPluginConfigDescriptor {
11 |
12 | @Override
13 | public synchronized void load() {
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/test/java/com/aspectsecurity/contrast/contrastjenkins/ContrastPluginConfigTest.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import hudson.util.FormValidation;
4 | import junit.framework.TestCase;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | public class ContrastPluginConfigTest extends TestCase {
9 |
10 | private ContrastPluginConfig.ContrastPluginConfigDescriptor descriptor;
11 |
12 | @Before
13 | @Override
14 | public void setUp() {
15 | descriptor = new ContrastPluginConfigStub.ContrastPluginConfigDescriptorStub();
16 | }
17 |
18 | @Test
19 | public void testDoCheckUsernameValid() {
20 | FormValidation result = descriptor.doCheckUsername("contrast_admin");
21 | assertEquals(result.kind, FormValidation.Kind.OK);
22 | }
23 |
24 | @Test
25 | public void testDoCheckUsernameInvalid() {
26 | FormValidation result = descriptor.doCheckUsername("");
27 | assertEquals(result.kind, FormValidation.Kind.ERROR);
28 | }
29 |
30 | @Test
31 | public void testDoCheckApiKeyValid() {
32 | FormValidation result = descriptor.doCheckApiKey("ABCDEFG");
33 | assertEquals(result.kind, FormValidation.Kind.OK);
34 | }
35 |
36 | @Test
37 | public void testDoCheckApiKeyInvalid() {
38 | FormValidation result = descriptor.doCheckApiKey("");
39 | assertEquals(result.kind, FormValidation.Kind.ERROR);
40 | }
41 | }
--------------------------------------------------------------------------------
/src/test/java/com/aspectsecurity/contrast/contrastjenkins/ThresholdConditionStub.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 |
4 | public class ThresholdConditionStub extends ThresholdCondition {
5 |
6 | public ThresholdConditionStub() {
7 | super(0, "test", "test", 0,null,"test",
8 | false, false,false, false, false, false,
9 | false, false, false);
10 | }
11 |
12 | public static class ThresholdConditionDescriptorStub extends ThresholdCondition.DescriptorImpl {
13 |
14 | @Override
15 | public synchronized void load() {
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/test/java/com/aspectsecurity/contrast/contrastjenkins/ThresholdConditionTest.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import hudson.util.FormValidation;
4 | import hudson.util.ListBoxModel;
5 | import junit.framework.TestCase;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 |
9 | public class ThresholdConditionTest extends TestCase {
10 |
11 | private ThresholdConditionStub.ThresholdConditionDescriptorStub descriptor;
12 |
13 | @Before
14 | @Override
15 | public void setUp() {
16 | descriptor = new ThresholdConditionStub.ThresholdConditionDescriptorStub();
17 | }
18 |
19 | @Test
20 | public void testDoFillThresholdSeverityItems() {
21 | ListBoxModel result = descriptor.doFillThresholdSeverityItems();
22 | assertTrue(result.size() > 0);
23 | }
24 |
25 | @Test
26 | public void testDoCheckThresholdCountValid() {
27 | FormValidation result = descriptor.doCheckThresholdCount("10");
28 | assertEquals(result.kind, FormValidation.Kind.OK);
29 | }
30 |
31 | @Test
32 | public void testDoCheckThresholdCountInvalid() {
33 | FormValidation result = descriptor.doCheckThresholdCount("blah");
34 | assertEquals(result.kind, FormValidation.Kind.ERROR);
35 | }
36 | }
--------------------------------------------------------------------------------
/src/test/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendHelperTest.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import com.aspectsecurity.contrast.contrastjenkins.ContrastPluginConfig.ContrastPluginConfigDescriptor;
4 | import com.contrastsecurity.http.TraceFilterForm;
5 | import com.contrastsecurity.models.Application;
6 | import com.contrastsecurity.models.Applications;
7 | import com.contrastsecurity.models.JobOutcomePolicy;
8 | import com.contrastsecurity.models.Trace;
9 | import com.contrastsecurity.models.Traces;
10 | import com.contrastsecurity.sdk.ContrastSDK;
11 | import com.google.common.collect.Lists;
12 | import hudson.model.AbstractProject;
13 | import hudson.model.ItemGroup;
14 | import hudson.model.Job;
15 | import hudson.model.Run;
16 | import junit.framework.TestCase;
17 | import org.junit.Test;
18 | import org.junit.runner.RunWith;
19 | import org.mockito.Mockito;
20 | import org.powermock.api.mockito.PowerMockito;
21 | import org.powermock.core.classloader.annotations.PrepareForTest;
22 | import org.powermock.modules.junit4.PowerMockRunner;
23 |
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | import static org.mockito.BDDMockito.times;
28 | import static org.mockito.BDDMockito.verify;
29 | import static org.mockito.Matchers.*;
30 | import static org.mockito.Mockito.when;
31 | import static org.powermock.api.mockito.PowerMockito.mock;
32 |
33 | @RunWith(PowerMockRunner.class)
34 | @PrepareForTest({VulnerabilityTrendHelper.class, ContrastPluginConfigDescriptor.class})
35 | public class VulnerabilityTrendHelperTest extends TestCase {
36 |
37 | @Test
38 | public void testGetVulnerabilityInfoString() {
39 | Trace traceMock = mock(Trace.class);
40 | when(traceMock.getSeverity()).thenReturn("Medium");
41 |
42 | Trace traceMock2 = mock(Trace.class);
43 | when(traceMock2.getSeverity()).thenReturn("High");
44 |
45 | List traces = new ArrayList<>();
46 | traces.add(traceMock);
47 | traces.add(traceMock2);
48 |
49 | Traces tracesMock = mock(Traces.class);
50 | when(tracesMock.getCount()).thenReturn(2);
51 | when(tracesMock.getTraces()).thenReturn(traces);
52 |
53 | String info = VulnerabilityTrendHelper.getVulnerabilityInfoString(tracesMock);
54 | assertEquals("Found vulnerabilities: Medium - 1 High - 1 .", info);
55 | }
56 |
57 | @Test
58 | public void testGetVulnerabilityInfoStringEmptyTraces() {
59 |
60 | List traces = new ArrayList<>();
61 |
62 | Traces tracesMock = mock(Traces.class);
63 | when(tracesMock.getCount()).thenReturn(0);
64 | when(tracesMock.getTraces()).thenReturn(traces);
65 |
66 | String info = VulnerabilityTrendHelper.getVulnerabilityInfoString(tracesMock);
67 | assertEquals("", info);
68 | }
69 |
70 | @Test
71 | public void testGetVulnerabilityInfoStringNullTraces() {
72 |
73 | String info = VulnerabilityTrendHelper.getVulnerabilityInfoString(null);
74 | assertEquals("", info);
75 | }
76 |
77 | @Test
78 | public void testBuildAppVersionTagHierarchical() {
79 | String parentFullName = "project";
80 | int buildNumber = 1;
81 | String applicationId = "NodeTestBench";
82 | String appVersionTagActual = applicationId + "-" + parentFullName + "-" + buildNumber;
83 |
84 | Run, ?> build = mock(Run.class);
85 |
86 | Job parent = mock(Job.class);
87 | ItemGroup itemGroup = mock(ItemGroup.class);
88 | when(itemGroup.getFullName()).thenReturn("");
89 | when(parent.getParent()).thenReturn(itemGroup);
90 |
91 | when(build.getNumber()).thenReturn(buildNumber);
92 | when(build.getParent()).thenReturn(parent);
93 | when(parent.getFullName()).thenReturn(parentFullName);
94 |
95 | String appVersionTag = VulnerabilityTrendHelper.buildAppVersionTagHierarchical(build, applicationId);
96 | assertEquals(appVersionTag, appVersionTagActual);
97 | }
98 |
99 | @Test
100 | public void testGetAllTracesForApplication() throws Exception {
101 | Application application = mock(Application.class);
102 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
103 | TraceFilterForm mockTraceFilterForm = mock(TraceFilterForm.class);
104 | Traces page1 = mock(Traces.class);
105 | Traces page2 = mock(Traces.class);
106 |
107 | List traceList1 = new ArrayList<>();
108 | List traceList2 = new ArrayList<>();
109 |
110 | for (int i = 0; i < 50; i++) {
111 | traceList1.add(mock(Trace.class));
112 | }
113 |
114 | traceList2.add(mock(Trace.class));
115 |
116 | PowerMockito.stub(PowerMockito.method(VulnerabilityTrendHelper.class, "createSDK")).toReturn(contrastSDKMock);
117 |
118 | when(application.getId()).thenReturn("test");
119 | when(page1.getCount()).thenReturn(51);
120 | when(page2.getCount()).thenReturn(51);
121 | when(page1.getTraces()).thenReturn(traceList1);
122 | when(page2.getTraces()).thenReturn(traceList2);
123 | when(contrastSDKMock.getTraces(anyString(), anyString(), any(TraceFilterForm.class)))
124 | .thenReturn(page1)
125 | .thenReturn(page2);
126 |
127 | Traces tracesReturned = VulnerabilityTrendHelper.getAllTraces(contrastSDKMock, "1", application.getId(), mockTraceFilterForm);
128 |
129 | assertEquals(51, tracesReturned.getTraces().size());
130 | verify(contrastSDKMock, times(2)).getTraces(anyString(), anyString(), any(TraceFilterForm.class));
131 | }
132 |
133 | @Test
134 | public void testGetAllTracesNullApplication() throws Exception {
135 | Application application = mock(Application.class);
136 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
137 | TraceFilterForm mockTraceFilterForm = mock(TraceFilterForm.class);
138 | Traces page1 = mock(Traces.class);
139 | Traces page2 = mock(Traces.class);
140 |
141 | List traceList1 = new ArrayList<>();
142 | List traceList2 = new ArrayList<>();
143 |
144 | for (int i = 0; i < 50; i++) {
145 | traceList1.add(mock(Trace.class));
146 | }
147 |
148 | traceList2.add(mock(Trace.class));
149 |
150 | PowerMockito.stub(PowerMockito.method(VulnerabilityTrendHelper.class, "createSDK")).toReturn(contrastSDKMock);
151 |
152 | when(application.getId()).thenReturn("test");
153 | when(page1.getCount()).thenReturn(51);
154 | when(page2.getCount()).thenReturn(51);
155 | when(page1.getTraces()).thenReturn(traceList1);
156 | when(page2.getTraces()).thenReturn(traceList2);
157 | when(contrastSDKMock.getTracesInOrg(anyString(), any(TraceFilterForm.class)))
158 | .thenReturn(page1)
159 | .thenReturn(page2);
160 |
161 | Traces tracesReturned = VulnerabilityTrendHelper.getAllTraces(contrastSDKMock, "1", null, mockTraceFilterForm);
162 |
163 | assertEquals(51, tracesReturned.getTraces().size());
164 | verify(contrastSDKMock, times(2)).getTracesInOrg(anyString(), any(TraceFilterForm.class));
165 | }
166 |
167 | @Test
168 | public void testGetAllTracesWhenEmptyResponse() throws Exception {
169 | Application application = mock(Application.class);
170 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
171 | TraceFilterForm mockTraceFilterForm = mock(TraceFilterForm.class);
172 | Traces page1 = mock(Traces.class);
173 | List traceList = new ArrayList<>();
174 |
175 | PowerMockito.stub(PowerMockito.method(VulnerabilityTrendHelper.class, "createSDK")).toReturn(contrastSDKMock);
176 |
177 | when(application.getId()).thenReturn("test");
178 | when(page1.getCount()).thenReturn(0);
179 | when(page1.getTraces()).thenReturn(traceList);
180 | when(contrastSDKMock.getTraces(anyString(), anyString(), any(TraceFilterForm.class)))
181 | .thenReturn(page1);
182 |
183 | Traces tracesReturned = VulnerabilityTrendHelper.getAllTraces(contrastSDKMock, "1", application.getId(), mockTraceFilterForm);
184 |
185 | assertEquals(0, tracesReturned.getTraces().size());
186 | verify(contrastSDKMock, times(1)).getTraces(anyString(), anyString(), any(TraceFilterForm.class));
187 | }
188 |
189 | @Test
190 | public void testGetDefaultAgentFileNameFromStringJava() throws Exception {
191 | assertEquals("contrast.jar", VulnerabilityTrendHelper.getDefaultAgentFileNameFromString("Java"));
192 | }
193 | @Test
194 | public void testGetDefaultAgtestGetDefaultAgentFileNameFromStringNode() throws Exception {
195 | assertEquals("node-contrast.tgz", VulnerabilityTrendHelper.getDefaultAgentFileNameFromString("Node"));
196 | }
197 | @Test
198 | public void testGetDefaultAgtestGetDefaultAgentFileNameFromStringDotNet() throws Exception {
199 | assertEquals("dotnet-contrast.zip", VulnerabilityTrendHelper.getDefaultAgentFileNameFromString(".NET"));
200 | }
201 | @Test
202 | public void testGetDefaultAgtestGetDefaultAgentFileNameFromStringEmpty() throws Exception {
203 | assertEquals("contrast.jar", VulnerabilityTrendHelper.getDefaultAgentFileNameFromString(""));
204 | }
205 |
206 | @Test
207 | public void testIsEnabledJobOutcomePolicyExistFalse() throws Exception{
208 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
209 | String orgUuId = "fakeOrgUuid";
210 |
211 | when(contrastSDKMock.getEnabledJobOutcomePolicies(orgUuId)).thenReturn(Lists.newArrayList());
212 |
213 | assertEquals(false, VulnerabilityTrendHelper.isEnabledJobOutcomePolicyExist(contrastSDKMock, orgUuId));
214 | }
215 |
216 | @Test
217 | public void testIsEnabledJobOutcomePolicyExistTrue() throws Exception{
218 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
219 | String orgUuId = "fakeOrgUuid";
220 | ArrayList jobOutcomePolicies = new ArrayList<>();
221 | jobOutcomePolicies.add(new JobOutcomePolicy());
222 |
223 | when(contrastSDKMock.getEnabledJobOutcomePolicies(orgUuId)).thenReturn(jobOutcomePolicies);
224 |
225 | assertEquals(true, VulnerabilityTrendHelper.isEnabledJobOutcomePolicyExist(contrastSDKMock, orgUuId));
226 | }
227 |
228 | @Test
229 | public void testIsApplicableEnabledJobOutcomePolicyExistFalse() throws Exception{
230 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
231 | String orgUuId = "fakeOrgUuid";
232 | String appId = "fakeAppId";
233 |
234 | List applicationList = new ArrayList<>();
235 | Application application = mock(Application.class);
236 | when(application.getId()).thenReturn(appId);
237 | applicationList.add(application);
238 |
239 | Applications applications = mock(Applications.class);
240 | when(applications.getApplications()).thenReturn(applicationList);
241 | when(contrastSDKMock.getApplications(anyString())).thenReturn(applications);
242 | when(contrastSDKMock.getEnabledJoboutcomePoliciesByApplication(orgUuId, appId)).thenReturn(Lists.newArrayList());
243 |
244 | assertFalse(VulnerabilityTrendHelper
245 | .isApplicableEnabledJobOutcomePolicyExist(contrastSDKMock, orgUuId, appId));
246 | }
247 |
248 | @Test
249 | public void testIsApplicableEnabledJobOutcomePolicyExistTrue() throws Exception{
250 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
251 | String orgUuId = "fakeOrgUuid";
252 | String appId = "fakeAppId";
253 | ArrayList jobOutcomePolicies = new ArrayList<>();
254 | jobOutcomePolicies.add(new JobOutcomePolicy());
255 |
256 | List applicationList = new ArrayList<>();
257 | Application application = mock(Application.class);
258 | when(application.getId()).thenReturn(appId);
259 | applicationList.add(application);
260 |
261 | Applications applications = mock(Applications.class);
262 | when(applications.getApplications()).thenReturn(applicationList);
263 | when(contrastSDKMock.getApplications(anyString())).thenReturn(applications);
264 | when(contrastSDKMock.getEnabledJoboutcomePoliciesByApplication(orgUuId, appId)).thenReturn(jobOutcomePolicies);
265 |
266 | assertTrue(VulnerabilityTrendHelper
267 | .isApplicableEnabledJobOutcomePolicyExist(contrastSDKMock, orgUuId, appId));
268 | }
269 |
270 | @Test
271 | public void testGetProfileSpaceExists() throws Exception {
272 | ContrastPluginConfigDescriptor descriptorMock = mock(ContrastPluginConfigDescriptor.class);
273 | TeamServerProfile profile = mock(TeamServerProfile.class);
274 | when(profile.getName()).thenReturn("MyProfile");
275 | TeamServerProfile[] profiles = {profile};
276 | when(descriptorMock.getTeamServerProfiles()).thenReturn(profiles);
277 |
278 | PowerMockito.whenNew(ContrastPluginConfigDescriptor.class).withNoArguments().thenReturn(descriptorMock);
279 | TeamServerProfile actualProfile = VulnerabilityTrendHelper.getProfile("myprofile");
280 |
281 | assertNotNull(actualProfile);
282 | assertEquals("MyProfile", actualProfile.getName());
283 | }
284 |
285 | @Test
286 | public void testGetProfileMisMatchedCase() throws Exception {
287 | ContrastPluginConfigDescriptor descriptorMock = mock(ContrastPluginConfigDescriptor.class);
288 | TeamServerProfile profile = mock(TeamServerProfile.class);
289 | when(profile.getName()).thenReturn("MyProfile");
290 | TeamServerProfile[] profiles = {profile};
291 | when(descriptorMock.getTeamServerProfiles()).thenReturn(profiles);
292 |
293 | PowerMockito.whenNew(ContrastPluginConfigDescriptor.class).withNoArguments().thenReturn(descriptorMock);
294 | TeamServerProfile actualProfile = VulnerabilityTrendHelper.getProfile(" MyProfile ");
295 |
296 | assertNotNull(actualProfile);
297 | assertEquals("MyProfile", actualProfile.getName());
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/src/test/java/com/aspectsecurity/contrast/contrastjenkins/VulnerabilityTrendStepTest.java:
--------------------------------------------------------------------------------
1 | package com.aspectsecurity.contrast.contrastjenkins;
2 |
3 | import com.contrastsecurity.exceptions.UnauthorizedException;
4 | import com.contrastsecurity.http.TraceFilterForm;
5 | import com.contrastsecurity.models.JobOutcomePolicy;
6 | import com.contrastsecurity.models.Organization;
7 | import com.contrastsecurity.models.Organizations;
8 | import com.contrastsecurity.models.SecurityCheck;
9 | import com.contrastsecurity.models.Traces;
10 | import com.contrastsecurity.sdk.ContrastSDK;
11 | import hudson.AbortException;
12 | import hudson.model.Result;
13 | import hudson.model.Run;
14 | import hudson.model.TaskListener;
15 | import jenkins.model.Jenkins;
16 | import org.junit.Before;
17 | import org.junit.Test;
18 | import org.junit.runner.RunWith;
19 | import org.mockito.Mock;
20 | import org.powermock.api.mockito.PowerMockito;
21 | import org.powermock.core.classloader.annotations.PrepareForTest;
22 | import org.powermock.modules.junit4.PowerMockRunner;
23 |
24 | import java.io.IOException;
25 | import java.io.PrintStream;
26 |
27 | import static org.junit.Assert.assertNull;
28 | import static org.mockito.BDDMockito.given;
29 | import static org.mockito.Matchers.any;
30 | import static org.mockito.Matchers.anyInt;
31 | import static org.mockito.Matchers.anyLong;
32 | import static org.mockito.Matchers.anyString;
33 | import static org.mockito.Mockito.doNothing;
34 | import static org.mockito.Mockito.verify;
35 | import static org.mockito.Mockito.when;
36 | import static org.powermock.api.mockito.PowerMockito.doReturn;
37 | import static org.powermock.api.mockito.PowerMockito.mock;
38 | import static org.powermock.api.mockito.PowerMockito.spy;
39 |
40 |
41 | @RunWith(PowerMockRunner.class)
42 | @PrepareForTest({Jenkins.class, VulnerabilityTrendStep.class, VulnerabilityTrendHelper.class})
43 | public class VulnerabilityTrendStepTest {
44 |
45 | @Mock
46 | TaskListener taskListenerMock;
47 | @Mock
48 | PrintStream printStreamMock;
49 | @Mock
50 | Jenkins jenkins;
51 | @Mock
52 | VulnerabilityTrendHelper vulnerabilityTrendHelper;
53 | @Mock
54 | VulnerabilityTrendStep.VulnerabilityTrendStepDescriptorImpl vulnerabilityTrendStepDescriptor;
55 |
56 | @Before
57 | public void setUp() {
58 | PowerMockito.mockStatic(Jenkins.class);
59 | PowerMockito.mockStatic(VulnerabilityTrendStep.class);
60 | PowerMockito.mockStatic(VulnerabilityTrendHelper.class);
61 |
62 | when(jenkins.getDescriptorByType(VulnerabilityTrendStep.VulnerabilityTrendStepDescriptorImpl.class)).thenReturn(vulnerabilityTrendStepDescriptor);
63 | }
64 |
65 | @Test
66 | public void testSuccessfulBuild() throws Exception {
67 | VulnerabilityTrendStep.Execution stepExecution = spy(new VulnerabilityTrendStep.Execution());
68 | stepExecution.step = new VulnerabilityTrendStep("local", 10, null, null, "WebGoat", Constants.QUERY_BY_APP_VERSION_TAG_DEFAULT_FORMAT);
69 |
70 | when(Jenkins.getInstance()).thenReturn(jenkins);
71 |
72 | stepExecution.taskListener = taskListenerMock;
73 |
74 | when(taskListenerMock.getLogger()).thenReturn(printStreamMock);
75 | doNothing().when(printStreamMock).println();
76 |
77 | Traces tracesMock = mock(Traces.class);
78 | when(tracesMock.getCount()).thenReturn(0);
79 |
80 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
81 |
82 | doReturn("test").when(stepExecution).getBuildName();
83 |
84 | given(VulnerabilityTrendHelper.createSDK(anyString(), anyString(), anyString(), anyString())).willReturn(contrastSDKMock);
85 |
86 | TeamServerProfile profile = mock(TeamServerProfile.class);
87 |
88 | given(VulnerabilityTrendHelper.getProfile(anyString())).willReturn(profile);
89 |
90 | Organizations mockOrgs = mock(Organizations.class);
91 | Organization mockOrg = mock(Organization.class);
92 | when(mockOrgs.getOrganization()).thenReturn(mockOrg);
93 | given(contrastSDKMock.getProfileDefaultOrganizations()).willReturn(mockOrgs);
94 |
95 | when(contrastSDKMock.getTracesInOrg(anyString(), any(TraceFilterForm.class))).thenReturn(tracesMock);
96 |
97 | assertNull(stepExecution.run());
98 | }
99 |
100 | @Test
101 | public void testSuccessfulBuildWithParameter() throws Exception {
102 | VulnerabilityTrendStep.Execution stepExecution = spy(new VulnerabilityTrendStep.Execution());
103 | stepExecution.step = new VulnerabilityTrendStep("local", 10, null, null, "WebGoat", Constants.QUERY_BY_PARAMETER);
104 |
105 | when(Jenkins.getInstance()).thenReturn(jenkins);
106 |
107 | stepExecution.taskListener = taskListenerMock;
108 |
109 | when(taskListenerMock.getLogger()).thenReturn(printStreamMock);
110 | doNothing().when(printStreamMock).println();
111 |
112 | Traces tracesMock = mock(Traces.class);
113 | when(tracesMock.getCount()).thenReturn(0);
114 |
115 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
116 |
117 | doReturn("test").when(stepExecution).getBuildName();
118 |
119 | given(VulnerabilityTrendHelper.createSDK(anyString(), anyString(), anyString(), anyString())).willReturn(contrastSDKMock);
120 |
121 | TeamServerProfile profile = mock(TeamServerProfile.class);
122 |
123 | given(VulnerabilityTrendHelper.getProfile(anyString())).willReturn(profile);
124 |
125 | Organizations mockOrgs = mock(Organizations.class);
126 | Organization mockOrg = mock(Organization.class);
127 | when(mockOrgs.getOrganization()).thenReturn(mockOrg);
128 | given(contrastSDKMock.getProfileDefaultOrganizations()).willReturn(mockOrgs);
129 |
130 | when(contrastSDKMock.getTracesInOrg(anyString(), any(TraceFilterForm.class))).thenReturn(tracesMock);
131 |
132 | assertNull(stepExecution.run());
133 | }
134 |
135 | @Test
136 | public void testUnsuccessfulBuild() throws Exception {
137 | VulnerabilityTrendStep.Execution stepExecution = spy(new VulnerabilityTrendStep.Execution());
138 | stepExecution.step = new VulnerabilityTrendStep("local", 10, "xss", "High", "WebGoat", Constants.QUERY_BY_APP_VERSION_TAG_DEFAULT_FORMAT);
139 | stepExecution.build = mock(Run.class);
140 |
141 | when(Jenkins.getInstance()).thenReturn(jenkins);
142 |
143 | stepExecution.taskListener = taskListenerMock;
144 |
145 | when(taskListenerMock.getLogger()).thenReturn(printStreamMock);
146 | doNothing().when(printStreamMock).println();
147 |
148 | Traces tracesMock = mock(Traces.class);
149 | when(tracesMock.getCount()).thenReturn(11);
150 |
151 |
152 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
153 | SecurityCheck undifinedPolicySecurityCheck = mock(SecurityCheck.class);
154 | given(undifinedPolicySecurityCheck.getResult()).willReturn(null);
155 |
156 | Organizations mockOrgs = mock(Organizations.class);
157 | Organization mockOrg = mock(Organization.class);
158 | when(mockOrgs.getOrganization()).thenReturn(mockOrg);
159 | given(contrastSDKMock.getProfileDefaultOrganizations()).willReturn(mockOrgs);
160 |
161 | doReturn("test").when(stepExecution).getBuildName();
162 |
163 | given(VulnerabilityTrendHelper.createSDK(anyString(), anyString(), anyString(), anyString())).willReturn(contrastSDKMock);
164 | given(VulnerabilityTrendHelper.applicationIdExists(any(ContrastSDK.class), anyString(), anyString())).willReturn(true);
165 | given(VulnerabilityTrendHelper.makeSecurityCheck(any(ContrastSDK.class), anyString(), anyString(), anyLong(), anyInt(), any(TraceFilterForm.class))).willReturn(undifinedPolicySecurityCheck);
166 |
167 | TeamServerProfile profile = mock(TeamServerProfile.class);
168 | given(profile.getVulnerableBuildResult()).willReturn(Result.UNSTABLE.toString());
169 | given(profile.isApplyVulnerableBuildResultOnContrastError()).willReturn(true);
170 |
171 | given(VulnerabilityTrendHelper.getProfile(anyString())).willReturn(profile);
172 | given(VulnerabilityTrendHelper.getAllTraces(any(ContrastSDK.class), anyString(), anyString(), any(TraceFilterForm.class))).willReturn(tracesMock);
173 |
174 | when(contrastSDKMock.getTracesInOrg(anyString(), any(TraceFilterForm.class))).thenReturn(tracesMock);
175 |
176 | assertNull(stepExecution.run());
177 | verify(stepExecution.build).setResult(Result.fromString(profile.getVulnerableBuildResult()));
178 | }
179 |
180 | @Test
181 | public void testUnsuccessfulBuildJop() throws Exception {
182 | VulnerabilityTrendStep.Execution stepExecution = spy(new VulnerabilityTrendStep.Execution());
183 | stepExecution.step = new VulnerabilityTrendStep("local", 10, "xss", "High", "WebGoat", Constants.QUERY_BY_APP_VERSION_TAG_DEFAULT_FORMAT);
184 | stepExecution.build = mock(Run.class);
185 |
186 | when(Jenkins.getInstance()).thenReturn(jenkins);
187 |
188 | stepExecution.taskListener = taskListenerMock;
189 |
190 | when(taskListenerMock.getLogger()).thenReturn(printStreamMock);
191 | doNothing().when(printStreamMock).println();
192 |
193 | Traces tracesMock = mock(Traces.class);
194 | when(tracesMock.getCount()).thenReturn(11);
195 |
196 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
197 |
198 | Organizations mockOrgs = mock(Organizations.class);
199 | Organization mockOrg = mock(Organization.class);
200 | when(mockOrgs.getOrganization()).thenReturn(mockOrg);
201 | given(contrastSDKMock.getProfileDefaultOrganizations()).willReturn(mockOrgs);
202 |
203 | JobOutcomePolicy jobOutcomePolicy = mock(JobOutcomePolicy.class);
204 | when(jobOutcomePolicy.getOutcome()).thenReturn(JobOutcomePolicy.Outcome.UNSTABLE);
205 |
206 | SecurityCheck undifinedPolicySecurityCheck = mock(SecurityCheck.class);
207 | when(undifinedPolicySecurityCheck.getResult()).thenReturn(false);
208 | when(undifinedPolicySecurityCheck.getJobOutcomePolicy()).thenReturn(jobOutcomePolicy);
209 |
210 |
211 |
212 | doReturn("test").when(stepExecution).getBuildName();
213 |
214 | given(VulnerabilityTrendHelper.createSDK(anyString(), anyString(), anyString(), anyString())).willReturn(contrastSDKMock);
215 | given(VulnerabilityTrendHelper.applicationIdExists(any(ContrastSDK.class), anyString(), anyString())).willReturn(true);
216 | given(VulnerabilityTrendHelper.makeSecurityCheck(any(ContrastSDK.class), anyString(), anyString(), anyLong(), anyInt(), any(TraceFilterForm.class))).willReturn(undifinedPolicySecurityCheck);
217 | given(VulnerabilityTrendHelper.getJenkinsResultFromJobOutcome(any(JobOutcomePolicy.Outcome.class))).willReturn(Result.UNSTABLE);
218 |
219 | TeamServerProfile profile = mock(TeamServerProfile.class);
220 | given(profile.getVulnerableBuildResult()).willReturn(Result.UNSTABLE.toString());
221 | given(profile.isApplyVulnerableBuildResultOnContrastError()).willReturn(false);
222 |
223 | given(VulnerabilityTrendHelper.getProfile(anyString())).willReturn(profile);
224 | given(VulnerabilityTrendHelper.getAllTraces(any(ContrastSDK.class), anyString(), anyString(), any(TraceFilterForm.class))).willReturn(tracesMock);
225 |
226 | when(contrastSDKMock.getTracesInOrg(anyString(), any(TraceFilterForm.class))).thenReturn(tracesMock);
227 |
228 | assertNull(stepExecution.run());
229 | verify(stepExecution.build).setResult(Result.fromString(profile.getVulnerableBuildResult()));
230 |
231 | }
232 |
233 |
234 | @Test(expected = AbortException.class)
235 | public void testVulnerableBuildFailureSelected() throws Exception {
236 | VulnerabilityTrendStep.Execution stepExecution = spy(new VulnerabilityTrendStep.Execution());
237 | stepExecution.step = new VulnerabilityTrendStep("local", 10, "xss", "High", "WebGoat", Constants.QUERY_BY_APP_VERSION_TAG_DEFAULT_FORMAT);
238 | stepExecution.build = mock(Run.class);
239 |
240 | when(Jenkins.getInstance()).thenReturn(jenkins);
241 |
242 | stepExecution.taskListener = taskListenerMock;
243 |
244 | when(taskListenerMock.getLogger()).thenReturn(printStreamMock);
245 | doNothing().when(printStreamMock).println();
246 |
247 | Traces tracesMock = mock(Traces.class);
248 | when(tracesMock.getCount()).thenReturn(11);
249 |
250 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
251 |
252 | Organizations mockOrgs = mock(Organizations.class);
253 | Organization mockOrg = mock(Organization.class);
254 | when(mockOrgs.getOrganization()).thenReturn(mockOrg);
255 | given(contrastSDKMock.getProfileDefaultOrganizations()).willReturn(mockOrgs);
256 |
257 | SecurityCheck undifinedPolicySecurityCheck = mock(SecurityCheck.class);
258 | given(undifinedPolicySecurityCheck.getResult()).willReturn(null);
259 |
260 |
261 | doReturn("test").when(stepExecution).getBuildName();
262 |
263 | given(VulnerabilityTrendHelper.createSDK(anyString(), anyString(), anyString(), anyString())).willReturn(contrastSDKMock);
264 | given(VulnerabilityTrendHelper.applicationIdExists(any(ContrastSDK.class), anyString(), anyString())).willReturn(true);
265 | given(VulnerabilityTrendHelper.makeSecurityCheck(any(ContrastSDK.class), anyString(), anyString(), anyLong(), anyInt(), any(TraceFilterForm.class))).willReturn(undifinedPolicySecurityCheck);
266 |
267 | TeamServerProfile profile = mock(TeamServerProfile.class);
268 | given(profile.getVulnerableBuildResult()).willReturn(Result.FAILURE.toString());
269 | given(profile.isApplyVulnerableBuildResultOnContrastError()).willReturn(false);
270 |
271 | given(VulnerabilityTrendHelper.getProfile(anyString())).willReturn(profile);
272 | given(VulnerabilityTrendHelper.getAllTraces(any(ContrastSDK.class), anyString(), anyString(), any(TraceFilterForm.class))).willReturn(tracesMock);
273 |
274 | when(contrastSDKMock.getTracesInOrg(anyString(), any(TraceFilterForm.class))).thenReturn(tracesMock);
275 |
276 | stepExecution.run();
277 | }
278 |
279 | @Test(expected = AbortException.class)
280 | public void testVulnerableBuildFailureSelectedJOP() throws Exception {
281 | VulnerabilityTrendStep.Execution stepExecution = spy(new VulnerabilityTrendStep.Execution());
282 | stepExecution.step = new VulnerabilityTrendStep("local", 10, "xss", "High", "WebGoat", Constants.QUERY_BY_APP_VERSION_TAG_DEFAULT_FORMAT);
283 | stepExecution.build = mock(Run.class);
284 |
285 | when(Jenkins.getInstance()).thenReturn(jenkins);
286 |
287 | stepExecution.taskListener = taskListenerMock;
288 |
289 | when(taskListenerMock.getLogger()).thenReturn(printStreamMock);
290 | doNothing().when(printStreamMock).println();
291 |
292 | Traces tracesMock = mock(Traces.class);
293 | when(tracesMock.getCount()).thenReturn(11);
294 |
295 | ContrastSDK contrastSDKMock = mock(ContrastSDK.class);
296 |
297 | Organizations mockOrgs = mock(Organizations.class);
298 | Organization mockOrg = mock(Organization.class);
299 | when(mockOrgs.getOrganization()).thenReturn(mockOrg);
300 | given(contrastSDKMock.getProfileDefaultOrganizations()).willReturn(mockOrgs);
301 |
302 | JobOutcomePolicy jobOutcomePolicy = mock(JobOutcomePolicy.class);
303 | when(jobOutcomePolicy.getOutcome()).thenReturn(JobOutcomePolicy.Outcome.FAIL);
304 |
305 | SecurityCheck undifinedPolicySecurityCheck = mock(SecurityCheck.class);
306 | when(undifinedPolicySecurityCheck.getResult()).thenReturn(false);
307 | when(undifinedPolicySecurityCheck.getJobOutcomePolicy()).thenReturn(jobOutcomePolicy);
308 |
309 |
310 |
311 | doReturn("test").when(stepExecution).getBuildName();
312 |
313 | given(VulnerabilityTrendHelper.createSDK(anyString(), anyString(), anyString(), anyString())).willReturn(contrastSDKMock);
314 | given(VulnerabilityTrendHelper.applicationIdExists(any(ContrastSDK.class), anyString(), anyString())).willReturn(true);
315 | given(VulnerabilityTrendHelper.makeSecurityCheck(any(ContrastSDK.class), anyString(), anyString(), anyLong(), anyInt(), any(TraceFilterForm.class))).willReturn(undifinedPolicySecurityCheck);
316 | given(VulnerabilityTrendHelper.getJenkinsResultFromJobOutcome(any(JobOutcomePolicy.Outcome.class))).willReturn(Result.FAILURE);
317 |
318 | TeamServerProfile profile = mock(TeamServerProfile.class);
319 | given(profile.getVulnerableBuildResult()).willReturn(Result.FAILURE.toString());
320 | given(profile.isApplyVulnerableBuildResultOnContrastError()).willReturn(false);
321 |
322 | given(VulnerabilityTrendHelper.getProfile(anyString())).willReturn(profile);
323 | given(VulnerabilityTrendHelper.getAllTraces(any(ContrastSDK.class), anyString(), anyString(), any(TraceFilterForm.class))).willReturn(tracesMock);
324 |
325 | when(contrastSDKMock.getTracesInOrg(anyString(), any(TraceFilterForm.class))).thenReturn(tracesMock);
326 |
327 | stepExecution.run();
328 | }
329 |
330 |
331 | }
--------------------------------------------------------------------------------
/vulnerabilityTrendRecorderConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 10
6 |
7 |
8 | 0
9 | Critical
10 |
11 |
12 | 2
13 | High
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------