提交 7831263d 编写于 作者: D Daniel Beck 提交者: Oliver Gondža

[JENKINS-40494] Process warnings from update sites (#2680)

* [FIX JENKINS-40494] Process warnings from update sites

* [JENKINS-40494] Address review comments

* [JENKINS-40494] Add warnings to available/update plugin manager tabs

* [JENKINS-40494] Add tests

* [JENKINS-40494] Address review feedback
上级 6996049d
......@@ -26,6 +26,7 @@
package hudson.model;
import hudson.ClassicPluginStrategy;
import hudson.ExtensionList;
import hudson.PluginManager;
import hudson.PluginWrapper;
import hudson.Util;
......@@ -46,7 +47,9 @@ import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
......@@ -55,17 +58,24 @@ import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jenkins.model.Jenkins;
import jenkins.model.DownloadSettings;
import jenkins.security.UpdateSiteWarningsConfiguration;
import jenkins.util.JSONSignatureValidator;
import jenkins.util.SystemProperties;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.HttpResponse;
......@@ -513,6 +523,12 @@ public class UpdateSite {
* Plugins in the repository, keyed by their artifact IDs.
*/
public final Map<String,Plugin> plugins = new TreeMap<String,Plugin>(String.CASE_INSENSITIVE_ORDER);
/**
* List of warnings (mostly security) published with the update site.
*
* @since TODO
*/
private final Set<Warning> warnings = new HashSet<Warning>();
/**
* If this is non-null, Jenkins is going to check the connectivity to this URL to make sure
......@@ -528,6 +544,18 @@ public class UpdateSite {
} else {
core = null;
}
JSONArray w = o.optJSONArray("warnings");
if (w != null) {
for (int i = 0; i < w.size(); i++) {
try {
warnings.add(new Warning(w.getJSONObject(i)));
} catch (JSONException ex) {
LOGGER.log(Level.WARNING, "Failed to parse JSON for warning", ex);
}
}
}
for(Map.Entry<String,JSONObject> e : (Set<Map.Entry<String,JSONObject>>)o.getJSONObject("plugins").entrySet()) {
Plugin p = new Plugin(sourceId, e.getValue());
// JENKINS-33308 - include implied dependencies for older plugins that may need them
......@@ -545,6 +573,16 @@ public class UpdateSite {
connectionCheckUrl = (String)o.get("connectionCheckUrl");
}
/**
* Returns the set of warnings
* @return the set of warnings
* @since TODO
*/
@Restricted(NoExternalUse.class)
public Set<Warning> getWarnings() {
return this.warnings;
}
/**
* Is there a new version of the core?
*/
......@@ -646,6 +684,232 @@ public class UpdateSite {
}
/**
* A version range for {@code Warning}s indicates which versions of a given plugin are affected
* by it.
*
* {@link #name}, {@link #firstVersion} and {@link #lastVersion} fields are only used for administrator notices.
*
* The {@link #pattern} is used to determine whether a given warning applies to the current installation.
*
* @since TODO
*/
@Restricted(NoExternalUse.class)
public static final class WarningVersionRange {
/**
* Human-readable English name for this version range, e.g. 'regular', 'LTS', '2.6 line'.
*/
@Nullable
public final String name;
/**
* First version in this version range to be subject to the warning.
*/
@Nullable
public final String firstVersion;
/**
* Last version in this version range to be subject to the warning.
*/
@Nullable
public final String lastVersion;
/**
* Regular expression pattern for this version range that matches all included version numbers.
*/
@Nonnull
private final Pattern pattern;
public WarningVersionRange(JSONObject o) {
this.name = Util.fixEmpty(o.optString("name"));
this.firstVersion = Util.fixEmpty(o.optString("firstVersion"));
this.lastVersion = Util.fixEmpty(o.optString("lastVersion"));
Pattern p;
try {
p = Pattern.compile(o.getString("pattern"));
} catch (PatternSyntaxException ex) {
LOGGER.log(Level.WARNING, "Failed to compile pattern '" + o.getString("pattern") + "', using '.*' instead", ex);
p = Pattern.compile(".*");
}
this.pattern = p;
}
public boolean includes(VersionNumber number) {
return pattern.matcher(number.toString()).matches();
}
}
/**
* Represents a warning about a certain component, mostly related to known security issues.
*
* @see UpdateSiteWarningsConfiguration
* @see jenkins.security.UpdateSiteWarningsMonitor
*
* @since TODO
*/
@Restricted(NoExternalUse.class)
public static final class Warning {
public enum Type {
CORE,
PLUGIN,
UNKNOWN
}
/**
* The type classifier for this warning.
*/
@Nonnull
public /* final */ Type type;
/**
* The globally unique ID of this warning.
*
* <p>This is typically the CVE identifier or SECURITY issue (Jenkins project);
* possibly with a unique suffix (e.g. artifactId) if either applies to multiple components.</p>
*/
@Exported
@Nonnull
public final String id;
/**
* The name of the affected component.
* <ul>
* <li>If type is 'core', this is 'core' by convention.
* <li>If type is 'plugin', this is the artifactId of the affected plugin
* </ul>
*/
@Exported
@Nonnull
public final String component;
/**
* A short, English language explanation for this warning.
*/
@Exported
@Nonnull
public final String message;
/**
* A URL with more information about this, typically a security advisory. For use in administrator notices
* only, so
*/
@Exported
@Nonnull
public final String url;
/**
* A list of named version ranges specifying which versions of the named component this warning applies to.
*
* If this list is empty, all versions of the component are considered to be affected by this warning.
*/
@Exported
@Nonnull
public final List<WarningVersionRange> versionRanges;
/**
*
* @param o the {@link JSONObject} representing the warning
* @throws JSONException if the argument does not match the expected format
*/
@Restricted(NoExternalUse.class)
public Warning(JSONObject o) {
try {
this.type = Type.valueOf(o.getString("type").toUpperCase(Locale.US));
} catch (IllegalArgumentException ex) {
this.type = Type.UNKNOWN;
}
this.id = o.getString("id");
this.component = o.getString("name");
this.message = o.getString("message");
this.url = o.getString("url");
if (o.has("versions")) {
List<WarningVersionRange> ranges = new ArrayList<>();
JSONArray versions = o.getJSONArray("versions");
for (int i = 0; i < versions.size(); i++) {
WarningVersionRange range = new WarningVersionRange(versions.getJSONObject(i));
ranges.add(range);
}
this.versionRanges = Collections.unmodifiableList(ranges);
} else {
this.versionRanges = Collections.emptyList();
}
}
/**
* Two objects are considered equal if they are the same type and have the same ID.
*
* @param o the other object
* @return true iff this object and the argument are considered equal
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Warning)) return false;
Warning warning = (Warning) o;
return id.equals(warning.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
public boolean isPluginWarning(@Nonnull String pluginName) {
return type == Type.PLUGIN && pluginName.equals(this.component);
}
/**
* Returns true if this warning is relevant to the current configuration
* @return true if this warning is relevant to the current configuration
*/
public boolean isRelevant() {
switch (this.type) {
case CORE:
VersionNumber current = Jenkins.getVersion();
if (!isRelevantToVersion(current)) {
return false;
}
return true;
case PLUGIN:
// check whether plugin is installed
PluginWrapper plugin = Jenkins.getInstance().getPluginManager().getPlugin(this.component);
if (plugin == null) {
return false;
}
// check whether warning is relevant to installed version
VersionNumber currentCore = plugin.getVersionNumber();
if (!isRelevantToVersion(currentCore)) {
return false;
}
return true;
case UNKNOWN:
default:
return false;
}
}
public boolean isRelevantToVersion(@Nonnull VersionNumber version) {
if (this.versionRanges.isEmpty()) {
// no version ranges specified, so all versions are affected
return true;
}
for (UpdateSite.WarningVersionRange range : this.versionRanges) {
if (range.includes(version)) {
return true;
}
}
return false;
}
}
public final class Plugin extends Entry {
/**
* Optional URL to the Wiki page that discusses this plugin.
......@@ -863,6 +1127,49 @@ public class UpdateSite {
return true;
}
/**
* @since TODO
*/
@CheckForNull
@Restricted(NoExternalUse.class)
public Set<Warning> getWarnings() {
ExtensionList<UpdateSiteWarningsConfiguration> list = ExtensionList.lookup(UpdateSiteWarningsConfiguration.class);
if (list.size() == 0) {
return Collections.emptySet();
}
Set<Warning> warnings = new HashSet<>();
UpdateSiteWarningsConfiguration configuration = list.get(0);
for (Warning warning: configuration.getAllWarnings()) {
if (configuration.isIgnored(warning)) {
// warning is currently being ignored
continue;
}
if (!warning.isPluginWarning(this.name)) {
// warning is not about this plugin
continue;
}
if (!warning.isRelevantToVersion(new VersionNumber(this.version))) {
// warning is not relevant to this version
continue;
}
warnings.add(warning);
}
return warnings;
}
/**
* @since TODO
*/
@Restricted(DoNotUse.class)
public boolean hasWarnings() {
return getWarnings().size() > 0;
}
/**
* @deprecated as of 1.326
* Use {@link #deploy()}.
......
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security;
import hudson.Extension;
import hudson.PluginWrapper;
import hudson.model.UpdateSite;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Configuration for update site-provided warnings.
*
* @see UpdateSiteWarningsMonitor
*
* @since TODO
*/
@Extension
@Restricted(NoExternalUse.class)
public class UpdateSiteWarningsConfiguration extends GlobalConfiguration {
private HashSet<String> ignoredWarnings = new HashSet<>();
@Override
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
}
public UpdateSiteWarningsConfiguration() {
load();
}
@Nonnull
public Set<String> getIgnoredWarnings() {
return Collections.unmodifiableSet(ignoredWarnings);
}
public boolean isIgnored(@Nonnull UpdateSite.Warning warning) {
return ignoredWarnings.contains(warning.id);
}
@CheckForNull
public PluginWrapper getPlugin(@Nonnull UpdateSite.Warning warning) {
if (warning.type != UpdateSite.Warning.Type.PLUGIN) {
return null;
}
return Jenkins.getInstance().getPluginManager().getPlugin(warning.component);
}
@Nonnull
public Set<UpdateSite.Warning> getAllWarnings() {
HashSet<UpdateSite.Warning> allWarnings = new HashSet<>();
for (UpdateSite site : Jenkins.getInstance().getUpdateCenter().getSites()) {
UpdateSite.Data data = site.getData();
if (data != null) {
allWarnings.addAll(data.getWarnings());
}
}
return allWarnings;
}
@Nonnull
public Set<UpdateSite.Warning> getApplicableWarnings() {
Set<UpdateSite.Warning> allWarnings = getAllWarnings();
HashSet<UpdateSite.Warning> applicableWarnings = new HashSet<>();
for (UpdateSite.Warning warning: allWarnings) {
if (warning.isRelevant()) {
applicableWarnings.add(warning);
}
}
return Collections.unmodifiableSet(applicableWarnings);
}
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
HashSet<String> newIgnoredWarnings = new HashSet<>();
for (Object key : json.keySet()) {
String warningKey = key.toString();
if (!json.getBoolean(warningKey)) {
newIgnoredWarnings.add(warningKey);
}
}
this.ignoredWarnings = newIgnoredWarnings;
this.save();
return true;
}
}
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.PluginWrapper;
import hudson.model.AdministrativeMonitor;
import hudson.model.UpdateSite;
import hudson.util.HttpResponses;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Administrative monitor showing plugin/core warnings published by the configured update site to the user.
*
* <p>Terminology overview:</p>
*
* <ul>
* <li>Applicable warnings are those relevant to currently installed components
* <li>Active warnings are those actually shown to users.
* <li>Hidden warnings are those _not_ shown to users due to them being configured to be hidden.
* <li>Inapplicable warnings are those that are not applicable.
* </ul>
*
* <p></p>The following sets may be non-empty:</p>
*
* <ul>
* <li>Intersection of applicable and active
* <li>Intersection of applicable and hidden
* <li>Intersection of hidden and inapplicable (although not really relevant)
* <li>Intersection of inapplicable and neither hidden nor active
* </ul>
*
* <p>The following sets must necessarily be empty:</p>
*
* <ul>
* <li>Intersection of applicable and inapplicable
* <li>Intersection of active and hidden
* <li>Intersection of active and inapplicable
* </ul>
*
* @since TODO
*/
@Extension
@Restricted(NoExternalUse.class)
public class UpdateSiteWarningsMonitor extends AdministrativeMonitor {
@Override
public boolean isActivated() {
return !getActiveCoreWarnings().isEmpty() || !getActivePluginWarningsByPlugin().isEmpty();
}
public List<UpdateSite.Warning> getActiveCoreWarnings() {
List<UpdateSite.Warning> CoreWarnings = new ArrayList<>();
for (UpdateSite.Warning warning : getActiveWarnings()) {
if (warning.type != UpdateSite.Warning.Type.CORE) {
// this is not a core warning
continue;
}
CoreWarnings.add(warning);
}
return CoreWarnings;
}
public Map<PluginWrapper, List<UpdateSite.Warning>> getActivePluginWarningsByPlugin() {
Map<PluginWrapper, List<UpdateSite.Warning>> activePluginWarningsByPlugin = new HashMap<>();
for (UpdateSite.Warning warning : getActiveWarnings()) {
if (warning.type != UpdateSite.Warning.Type.PLUGIN) {
// this is not a plugin warning
continue;
}
String pluginName = warning.component;
PluginWrapper plugin = Jenkins.getInstance().getPluginManager().getPlugin(pluginName);
if (!activePluginWarningsByPlugin.containsKey(plugin)) {
activePluginWarningsByPlugin.put(plugin, new ArrayList<UpdateSite.Warning>());
}
activePluginWarningsByPlugin.get(plugin).add(warning);
}
return activePluginWarningsByPlugin;
}
private Set<UpdateSite.Warning> getActiveWarnings() {
ExtensionList<UpdateSiteWarningsConfiguration> configurations = ExtensionList.lookup(UpdateSiteWarningsConfiguration.class);
if (configurations.isEmpty()) {
return Collections.emptySet();
}
UpdateSiteWarningsConfiguration configuration = configurations.get(0);
HashSet<UpdateSite.Warning> activeWarnings = new HashSet<>();
for (UpdateSite.Warning warning : configuration.getApplicableWarnings()) {
if (!configuration.getIgnoredWarnings().contains(warning.id)) {
activeWarnings.add(warning);
}
}
return Collections.unmodifiableSet(activeWarnings);
}
/**
* Redirects the user to the plugin manager or security configuration
*/
@RequirePOST
public HttpResponse doForward(@QueryParameter String fix, @QueryParameter String configure) {
if (fix != null) {
return HttpResponses.redirectViaContextPath("pluginManager");
}
if (configure != null) {
return HttpResponses.redirectViaContextPath("configureSecurity");
}
// shouldn't happen
return HttpResponses.redirectViaContextPath("/");
}
/**
* Returns true iff there are applicable but ignored (i.e. hidden) warnings.
*
* @return true iff there are applicable but ignored (i.e. hidden) warnings.
*/
public boolean hasApplicableHiddenWarnings() {
ExtensionList<UpdateSiteWarningsConfiguration> configurations = ExtensionList.lookup(UpdateSiteWarningsConfiguration.class);
if (configurations.isEmpty()) {
return false;
}
UpdateSiteWarningsConfiguration configuration = configurations.get(0);
return getActiveWarnings().size() < configuration.getApplicableWarnings().size();
}
@Override
public String getDisplayName() {
return Messages.UpdateSiteWarningsMonitor_DisplayName();
}
}
......@@ -110,6 +110,15 @@ THE SOFTWARE.
<j:if test="${p.isNeededDependenciesForNewerJenkins()}">
<div class="compatWarning">${%depCoreWarning(p.getNeededDependenciesRequiredCore().toString())}</div>
</j:if>
<j:if test="${p.hasWarnings()}">
<div class="securityWarning">${%securityWarning}
<ul>
<j:forEach var="warning" items="${p.getWarnings()}">
<li><a href="${warning.url}" target="_blank">${warning.message}</a></li>
</j:forEach>
</ul>
</div>
</j:if>
</td>
<td class="pane"><st:out value="${p.version}" /></td>
<j:if test="${isUpdates}">
......
......@@ -35,3 +35,5 @@ depCoreWarning=\
built for Jenkins {0} or newer. The dependent plugins may \
or may not work in your Jenkins and consequently this \
plugin may or may not work in your Jenkins.
securityWarning=\
Warning: This plugin version may not be safe to use. Please review the following security notices:
......@@ -24,4 +24,5 @@ ApiTokenProperty.DisplayName=API Token
ApiTokenProperty.ChangeToken.TokenIsHidden=Token is hidden
ApiTokenProperty.ChangeToken.Success=<div>Updated. See the new token in the field above</div>
ApiTokenProperty.ChangeToken.SuccessHidden=<div>Updated. You need to login as the user to see the token</div>
RekeySecretAdminMonitor.DisplayName=Re-keying
\ No newline at end of file
RekeySecretAdminMonitor.DisplayName=Re-keying
UpdateSiteWarningsMonitor.DisplayName=Update Site Warnings
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.UpdateSiteWarningsConfiguration
f = namespace(lib.FormTagLib)
st = namespace("jelly:stapler")
st.adjunct(includes: "jenkins.security.UpdateSiteWarningsConfiguration.style")
def printEntry(warning, title, checked) {
f.block {
f.checkbox(name: warning.id,
title: title,
checked: checked,
class: 'hideWarnings');
div(class: "setting-description") {
a(warning.url, href: warning.url)
}
}
}
f.section(title:_("Hidden security warnings")) {
f.advanced(title: _("Security warnings"), align:"left") {
f.block {
text(_("blurb"))
}
f.entry(title: _("Security warnings"),
help: '/descriptorByName/UpdateSiteWarningsConfiguration/help') {
table(width:"100%") {
descriptor.applicableWarnings.each { warning ->
if (warning.type == hudson.model.UpdateSite.Warning.Type.CORE) {
printEntry(warning,
_("warning.core", warning.message),
!descriptor.isIgnored(warning))
}
else if (warning.type == hudson.model.UpdateSite.Warning.Type.PLUGIN) {
def plugin = descriptor.getPlugin(warning)
printEntry(warning,
_("warning.plugin", plugin.displayName, warning.message),
!descriptor.isIgnored(warning))
}
}
}
}
}
}
# The MIT License
#
# Copyright (c) 2016, CloudBees, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
warning.core = Jenkins core: {0}
warning.plugin = {0}: {1}
blurb = This section allows you to disable warnings published on the update site. \
Checked warnings are visible (the default), unchecked warnings are hidden.
<p>
This list contains all warnings relevant to currently installed components published by the configured update sites.
These are typically security-related.
Warnings that have been published but are not relevant to currently installed components (either because the affected component isn't installed, or an unaffected version is installed) are not shown here.
</p>
<p>
Checked entries (the default) are <em>active</em>, i.e. they're shown to administrators in an administrative monitor.
Entries can be unchecked to hide them.
This can be useful if you've evaluated a specific warning and are confident it does not apply to your environment or configuration, and continued use of the specified component does not constitute a security problem.
</p>
<p>
Please note that only specific warnings can be disabled; it is not possible to disable all warnings about a certain component.
If you wish to disable the display of warnings entirely, then you can disable the administrative monitor in <em>Configure System</em>.
</p>
.hideWarnings:not(:checked) + label {
color: grey;
text-decoration: line-through;
}
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.UpdateSiteWarningsMonitor
def f = namespace(lib.FormTagLib)
def listWarnings(warnings) {
warnings.each { warning ->
li {
a(warning.message, href: warning.url)
}
}
}
def coreWarnings = my.activeCoreWarnings
def pluginWarnings = my.activePluginWarningsByPlugin
div(class: "error") {
text(_("blurb"))
ul {
if (!coreWarnings.isEmpty()) {
li {
text(_("coreTitle", jenkins.model.Jenkins.version))
ul {
listWarnings(coreWarnings)
}
}
}
if (!pluginWarnings.isEmpty()) {
li {
pluginWarnings.each { plugin, warnings ->
a(_("pluginTitle", plugin.displayName, plugin.version), href: plugin.url)
ul {
listWarnings(warnings)
}
}
}
}
}
if (my.hasApplicableHiddenWarnings()) {
text(_("more"))
}
}
form(method: "post", action: "${rootURL}/${it.url}/forward") {
div {
if (!pluginWarnings.isEmpty()) {
f.submit(name: 'fix', value: _("pluginManager.link"))
}
f.submit(name: 'configure', value: _("configureSecurity.link"))
}
}
# The MIT License
#
# Copyright (c) 2016, CloudBees, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
pluginTitle = {0} {1}:
coreTitle = Jenkins {0} core and libraries:
blurb = Warnings have been published for the following currently installed components:
more = Additional warnings are hidden due to the current security configuration.
pluginManager.link = Go to plugin manager
configureSecurity.link = Configure which of these warnings are shown
......@@ -33,6 +33,7 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import javax.servlet.ServletException;
......@@ -41,6 +42,8 @@ import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.*;
import jenkins.security.UpdateSiteWarningsConfiguration;
import jenkins.security.UpdateSiteWarningsMonitor;
import org.apache.commons.io.FileUtils;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
......@@ -52,6 +55,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;
public class UpdateSiteTest {
......@@ -127,5 +131,24 @@ public class UpdateSiteTest {
assertEquals(FormValidation.ok(), us.updateDirectly(/* TODO the certificate is now expired, and downloading a fresh copy did not seem to help */false).get());
assertNotNull(us.getPlugin("AdaptivePlugin"));
}
@Test public void lackOfDataDoesNotFailWarningsCode() throws Exception {
assertNull("plugin data is not present", j.jenkins.getUpdateCenter().getSite("default").getData());
// nothing breaking?
j.jenkins.getExtensionList(UpdateSiteWarningsMonitor.class).get(0).getActivePluginWarningsByPlugin();
j.jenkins.getExtensionList(UpdateSiteWarningsMonitor.class).get(0).getActiveCoreWarnings();
j.jenkins.getExtensionList(UpdateSiteWarningsConfiguration.class).get(0).getAllWarnings();
}
@Test public void incompleteWarningsJson() throws Exception {
PersistedList<UpdateSite> sites = j.jenkins.getUpdateCenter().getSites();
sites.clear();
URL url = new URL(baseUrl, "/plugins/warnings-update-center-malformed.json");
UpdateSite site = new UpdateSite(UpdateCenter.ID_DEFAULT, url.toString());
sites.add(site);
assertEquals(FormValidation.ok(), site.updateDirectly(false).get());
assertEquals("number of warnings", 7, site.getData().getWarnings().size());
assertNotEquals("plugin data is present", Collections.emptyMap(), site.getData().plugins);
}
}
{
"connectionCheckUrl": "http://www.google.com/",
"core": {
"buildDate": "Dec 18, 2016",
"name": "core",
"sha1": "x4rDgtNYm9l7zJ16RVuxMRgGoQE=",
"url": "http://updates.jenkins-ci.org/download/war/2.37/jenkins.war",
"version": "2.37"
},
"id": "jenkins40494",
"plugins": {
"display-url-api": {
"buildDate": "Sep 22, 2016",
"dependencies": [
{
"name": "junit",
"optional": false,
"version": "1.3"
}
],
"developers": [
{
"developerId": "jdumay",
"email": "jdumay@cloudbees.com",
"name": "James Dumay"
}
],
"excerpt": "\\\\ Provides the DisplayURLProvider extension point to provide alternate URLs for use in notifications. URLs can be requested/extended for these UI locations: * Root page. * Job. * Run. * Run changes. * Test result. ",
"gav": "org.jenkins-ci.plugins:display-url-api:0.5",
"labels": [],
"name": "display-url-api",
"previousTimestamp": "2016-09-22T09:35:08.00Z",
"previousVersion": "0.4",
"releaseTimestamp": "2016-09-22T10:42:00.00Z",
"requiredCore": "1.625.3",
"scm": "github.com",
"sha1": "QEykyLSFuZTnFyHrzZEOgzSsYcU=",
"title": "Display URL API",
"url": "http://updates.jenkins-ci.org/download/plugins/display-url-api/0.5/display-url-api.hpi",
"version": "0.5",
"wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Display+URL+API+Plugin"
},
"junit": {
"buildDate": "Oct 17, 2016",
"dependencies": [
{
"name": "structs",
"optional": false,
"version": "1.2"
}
],
"developers": [
{
"developerId": "ogondza"
}
],
"excerpt": "Allows JUnit-format test results to be published.",
"gav": "org.jenkins-ci.plugins:junit:1.19",
"labels": [
"report"
],
"name": "junit",
"previousTimestamp": "2016-08-08T15:10:34.00Z",
"previousVersion": "1.18",
"releaseTimestamp": "2016-10-17T12:15:20.00Z",
"requiredCore": "1.580.1",
"scm": "github.com",
"sha1": "f3jcYlxz6/8PK43W3KL5LFtz7ro=",
"title": "JUnit Plugin",
"url": "http://updates.jenkins-ci.org/download/plugins/junit/1.19/junit.hpi",
"version": "1.19",
"wiki": "https://wiki.jenkins-ci.org/display/JENKINS/JUnit+Plugin"
},
"mailer": {
"buildDate": "Sep 04, 2016",
"dependencies": [
{
"name": "display-url-api",
"optional": false,
"version": "0.2"
}
],
"developers": [
{
"developerId": "andresrc"
}
],
"excerpt": "This plugin allows you to configure email notifications for build results. This is a break-out of the original core based email component. ",
"gav": "org.jenkins-ci.plugins:mailer:1.18",
"labels": [],
"name": "mailer",
"previousTimestamp": "2016-04-20T08:46:22.00Z",
"previousVersion": "1.17",
"releaseTimestamp": "2016-09-04T09:14:16.00Z",
"requiredCore": "1.625.3",
"scm": "github.com",
"sha1": "poG1EauZFM5lZE5hCBx5mqr/mMA=",
"title": "Jenkins Mailer Plugin",
"url": "http://updates.jenkins-ci.org/download/plugins/mailer/1.18/mailer.hpi",
"version": "1.18",
"wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Mailer"
},
"extra-columns": {
"buildDate": "Apr 11, 2016",
"dependencies": [
{
"name": "junit",
"optional": false,
"version": "1.11"
}
],
"developers": [
{
"developerId": "fredg",
"name": "Fred G."
}
],
"excerpt": "This is a general listview-column plugin that currently contains the following columns: Test Result, Configure Project button, Disable/Enable Project button, Project Description, Build Description, SCM Type, Last/Current Build Console output, Job Type, Build Duration, Build Parameters.",
"gav": "org.jenkins-ci.plugins:extra-columns:1.17",
"labels": [
"listview-column"
],
"name": "extra-columns",
"previousTimestamp": "2015-12-11T01:18:48.00Z",
"previousVersion": "1.16",
"releaseTimestamp": "2016-04-11T22:36:22.00Z",
"requiredCore": "1.475",
"scm": "github.com",
"sha1": "8y9Y91n7/Aw47G3pCxzJzTd94J0=",
"title": "Extra Columns Plugin",
"url": "http://updates.jenkins-ci.org/download/plugins/extra-columns/1.17/extra-columns.hpi",
"version": "1.17",
"wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Extra+Columns+Plugin"
},
"structs": {
"buildDate": "Aug 30, 2016",
"dependencies": [],
"developers": [
{
"developerId": "jglick"
}
],
"excerpt": "Library plugin for DSL plugins that need concise names for Jenkins extensions",
"gav": "org.jenkins-ci.plugins:structs:1.5",
"labels": [],
"name": "structs",
"previousTimestamp": "2016-08-26T14:11:44.00Z",
"previousVersion": "1.4",
"releaseTimestamp": "2016-08-30T14:10:10.00Z",
"requiredCore": "1.580.1",
"scm": "github.com",
"sha1": "fK+F0PEfSS//DOqmNJvX2rQYabo=",
"title": "Structs Plugin",
"url": "http://updates.jenkins-ci.org/download/plugins/structs/1.5/structs.hpi",
"version": "1.5",
"wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Structs+plugin"
}
},
"warnings": [
{
"id": "SECURITY-208",
"type": "plugin",
"name": "google-login",
"message": "Authentication bypass vulnerability",
"url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2015-10-12",
"versions": [
{
"lastVersion": "1.1",
"pattern": "1[.][01](|[.-].*)"
}
]
},
{
"id": "SECURITY-136",
"type": "plugin",
"name": "extra-columns",
"message": "Stored XSS vulnerability",
"url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-04-11",
"versions": [
{
"lastVersion": "1.16",
"pattern": "1[.](\\d|1[0123456])(|[.-].*)"
}
]
},
{
"id": "SECURITY-258",
"type": "plugin",
"name": "extra-columns",
"message": "Groovy sandbox protection incomplete",
"url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-04-11",
"versions": [
{
"lastVersion": "1.18",
"pattern": "1[.](\\d|1[012345678])(|[.-].*)"
}
]
},
{
"id": "SECURITY-85",
"type": "plugin",
"name": "tap",
"message": "Path traversal vulnerability",
"url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-06-20",
"versions": [
{
"lastVersion": "1.24",
"pattern": "1[.](\\d|1\\d|2[01234])(|[.-].*)"
}
]
},
{
"id": "SECURITY-278",
"type": "plugin",
"name": "image-gallery",
"message": "Path traversal vulnerability",
"url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-06-20",
"versions": [
{
"lastVersion": "1.3",
"pattern": "(0[.].*|1[.][0123])(|[.-].*)"
}
]
},
{
"id": "SECURITY-290",
"type": "plugin",
"name": "build-failure-analyzer",
"message": "Cross-site scripting vulnerability",
"url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-06-20",
"versions": [
{
"lastVersion": "1.15.0",
"pattern": "1[.](\\d|1[012345])[.]\\d+(|[.-].*)"
}
]
},
{
"id": "SECURITY-305",
"type": "plugin",
"versions": [
{
"lastVersion": "1.7.24",
"pattern": "1[.]7[.](\\d(|[.-].*)|24)"
}
]
},
{
"id": "SECURITY-309",
"type": "plugin",
"name": "cucumber-reports",
"message": "Plugin disables Content-Security-Policy for files served by Jenkins",
"url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-07-27",
"versions": [
{
"firstVersion": "1.3.0",
"lastVersion": "2.5.1",
"pattern": "(1[.][34]|2[.][012345])(|[.-].*)"
}
]
}
],
"updateCenterVersion": "1"
}
\ No newline at end of file
......@@ -1580,6 +1580,13 @@ TEXTAREA.rich-editor {
color: #FF0000;
}
#plugins .securityWarning {
white-space: normal;
margin-top: 0.5em;
padding-left: 2em;
color: #FF0000;
}
/* ========================= progress bar ========================= */
table.progress-bar {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册