From 722ff407c4083d47e97f43a86b00a7dff0930c04 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Sat, 22 Sep 2018 13:41:57 -0700 Subject: [PATCH] [JEP-214] Telemetry with initial escape hatch implementation (#3604) --- .../java/jenkins/telemetry/Correlator.java | 63 ++++++ .../java/jenkins/telemetry/Telemetry.java | 196 ++++++++++++++++ .../impl/SecuritySystemProperties.java | 117 ++++++++++ .../help-usageStatisticsCollected.html | 21 -- .../help-usageStatisticsCollected.jelly | 48 ++++ .../help-usageStatisticsCollected_bg.html | 22 -- .../help-usageStatisticsCollected_fr.html | 25 --- .../help-usageStatisticsCollected_it.html | 25 --- .../help-usageStatisticsCollected_ja.html | 22 -- .../help-usageStatisticsCollected_pt_BR.html | 20 -- .../description.jelly | 6 + .../java/jenkins/telemetry/TelemetryTest.java | 209 ++++++++++++++++++ 12 files changed, 639 insertions(+), 135 deletions(-) create mode 100644 core/src/main/java/jenkins/telemetry/Correlator.java create mode 100644 core/src/main/java/jenkins/telemetry/Telemetry.java create mode 100644 core/src/main/java/jenkins/telemetry/impl/SecuritySystemProperties.java delete mode 100644 core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.html create mode 100644 core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.jelly delete mode 100644 core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_bg.html delete mode 100644 core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_fr.html delete mode 100644 core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_it.html delete mode 100644 core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_ja.html delete mode 100644 core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_pt_BR.html create mode 100644 core/src/main/resources/jenkins/telemetry/impl/SecuritySystemProperties/description.jelly create mode 100644 test/src/test/java/jenkins/telemetry/TelemetryTest.java diff --git a/core/src/main/java/jenkins/telemetry/Correlator.java b/core/src/main/java/jenkins/telemetry/Correlator.java new file mode 100644 index 0000000000..ea583bdb1f --- /dev/null +++ b/core/src/main/java/jenkins/telemetry/Correlator.java @@ -0,0 +1,63 @@ +/* + * The MIT License + * + * Copyright (c) 2018, 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.telemetry; + +import hudson.Extension; +import hudson.model.Describable; +import hudson.model.Descriptor; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.util.UUID; + +/** + * This class stores a UUID identifying this instance for telemetry reporting to allow deduplication or merging of submitted records. + * + * We're not using anything derived from instance identity so we cannot connect an instance's public appearance with its submissions. + * + * This really only uses Descriptor/Describable to get a Saveable implementation for free. + */ +@Extension +@Restricted(NoExternalUse.class) +public class Correlator extends Descriptor implements Describable { + private String correlationId; + + public Correlator() { + super(Correlator.class); + load(); + if (correlationId == null) { + correlationId = UUID.randomUUID().toString(); + save(); + } + } + + public String getCorrelationId() { + return correlationId; + } + + @Override + public Descriptor getDescriptor() { + return this; + } +} \ No newline at end of file diff --git a/core/src/main/java/jenkins/telemetry/Telemetry.java b/core/src/main/java/jenkins/telemetry/Telemetry.java new file mode 100644 index 0000000000..ec766206ea --- /dev/null +++ b/core/src/main/java/jenkins/telemetry/Telemetry.java @@ -0,0 +1,196 @@ +/* + * The MIT License + * + * Copyright (c) 2018, 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.telemetry; + +import com.google.common.annotations.VisibleForTesting; +import hudson.Extension; +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import hudson.ProxyConfiguration; +import hudson.model.AsyncPeriodicWork; +import hudson.model.TaskListener; +import hudson.model.UsageStatistics; +import jenkins.model.Jenkins; +import jenkins.util.SystemProperties; +import net.sf.json.JSONObject; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Extension point for collecting JEP-214 telemetry. + * + * @see JEP-214 + * + * @since TODO + */ +public abstract class Telemetry implements ExtensionPoint { + + // https://webhook.site is a nice stand-in for this during development; just needs to end in ? to submit the ID as query parameter + @Restricted(NoExternalUse.class) + @VisibleForTesting + static String ENDPOINT = SystemProperties.getString(Telemetry.class.getName() + ".endpoint", "https://uplink.jenkins.io/events"); + + private static final Logger LOGGER = Logger.getLogger(Telemetry.class.getName()); + + /** + * ID of this collector, typically a basic alphanumeric string (and _- characters). + * + * Good IDs are globally unique and human readable (i.e. no UUIDs). + * + * For a periodically updated list of all public implementations, see https://jenkins.io/doc/developer/extensions/jenkins-core/#telemetry + * + * @return ID of the collector, never null or empty + */ + @Nonnull + public abstract String getId(); + + /** + * User friendly display name for this telemetry collector, ideally localized. + * + * @return display name, never null or empty + */ + @Nonnull + public abstract String getDisplayName(); + + /** + * Start date for the collection. + * Will be checked in Jenkins to not collect outside the defined time span. + * This does not have to be precise enough for time zones to be a consideration. + * + * @return collection start date + */ + @Nonnull + public abstract LocalDate getStart(); + + /** + * End date for the collection. + * Will be checked in Jenkins to not collect outside the defined time span. + * This does not have to be precise enough for time zones to be a consideration. + * + * @return collection end date + */ + @Nonnull + public abstract LocalDate getEnd(); + + /** + * Returns the content to be sent to the telemetry service. + * + * This method is called periodically, once per content submission. + * + * @return + */ + @Nonnull + public abstract JSONObject createContent(); + + public static ExtensionList all() { + return ExtensionList.lookup(Telemetry.class); + } + + @Extension + public static class TelemetryReporter extends AsyncPeriodicWork { + + public TelemetryReporter() { + super("telemetry collection"); + } + + @Override + public long getRecurrencePeriod() { + return TimeUnit.HOURS.toMillis(24); + } + + @Override + protected void execute(TaskListener listener) throws IOException, InterruptedException { + if (UsageStatistics.DISABLED || !Jenkins.getInstance().isUsageStatisticsCollected()) { + LOGGER.info("Collection of anonymous usage statistics is disabled, skipping telemetry collection and submission"); + return; + } + Telemetry.all().forEach(telemetry -> { + if (telemetry.getStart().isAfter(LocalDate.now())) { + LOGGER.config("Skipping telemetry for '" + telemetry.getId() + "' as it is configured to start later"); + return; + } + if (telemetry.getEnd().isBefore(LocalDate.now())) { + LOGGER.config("Skipping telemetry for '" + telemetry.getId() + "' as it is configured to end in the past"); + return; + } + + JSONObject data = new JSONObject(); + try { + data = telemetry.createContent(); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Failed to build telemetry content for: '" + telemetry.getId() + "'", e); + } + + JSONObject wrappedData = new JSONObject(); + wrappedData.put("type", telemetry.getId()); + wrappedData.put("payload", data); + wrappedData.put("correlator", ExtensionList.lookupSingleton(Correlator.class).getCorrelationId()); + + try { + URL url = new URL(ENDPOINT); + URLConnection conn = ProxyConfiguration.open(url); + if (!(conn instanceof HttpURLConnection)) { + LOGGER.config("URL did not result in an HttpURLConnection: " + ENDPOINT); + return; + } + HttpURLConnection http = (HttpURLConnection) conn; + http.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + http.setDoOutput(true); + + String body = wrappedData.toString(); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("Submitting JSON: " + body); + } + + try (OutputStream out = http.getOutputStream(); + OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { + writer.append(body); + } + + LOGGER.config("Telemetry submission received response '" + http.getResponseCode() + " " + http.getResponseMessage() + "' for: " + telemetry.getId()); + } catch (MalformedURLException e) { + LOGGER.config("Malformed endpoint URL: " + ENDPOINT + " for telemetry: " + telemetry.getId()); + } catch (IOException e) { + // deliberately low visibility, as temporary infra problems aren't a big deal and we'd + // rather have some unsuccessful submissions than admins opting out to clean up logs + LOGGER.log(Level.CONFIG, "Failed to submit telemetry: " + telemetry.getId() + " to: " + ENDPOINT, e); + } + }); + } + } +} diff --git a/core/src/main/java/jenkins/telemetry/impl/SecuritySystemProperties.java b/core/src/main/java/jenkins/telemetry/impl/SecuritySystemProperties.java new file mode 100644 index 0000000000..ca2d9e1bf2 --- /dev/null +++ b/core/src/main/java/jenkins/telemetry/impl/SecuritySystemProperties.java @@ -0,0 +1,117 @@ +/* + * The MIT License + * + * Copyright (c) 2018, 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.telemetry.impl; + +import hudson.Extension; +import jenkins.model.Jenkins; +import jenkins.telemetry.Telemetry; +import jenkins.util.SystemProperties; +import net.sf.json.JSONObject; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import javax.annotation.Nonnull; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; + +/** + * Telemetry implementation gathering information about system properties. + */ +@Extension +@Restricted(NoExternalUse.class) +public class SecuritySystemProperties extends Telemetry { + @Nonnull + @Override + public String getId() { + return "security-system-properties"; + } + + @Nonnull + @Override + public LocalDate getStart() { + return LocalDate.of(2018, 9, 1); + } + + @Nonnull + @Override + public LocalDate getEnd() { + return LocalDate.of(2018, 12, 1); + } + + @Nonnull + @Override + public String getDisplayName() { + return "Use of Security-related Java system properties"; + } + + @Nonnull + @Override + public JSONObject createContent() { + Map security = new TreeMap<>(); + putBoolean(security, "hudson.ConsoleNote.INSECURE", false); + putBoolean(security, "hudson.model.ParametersAction.keepUndefinedParameters", false); + putBoolean(security, "hudson.model.User.allowNonExistentUserToLogin", false); + putBoolean(security, "hudson.model.User.allowUserCreationViaUrl", false); + putBoolean(security, "hudson.model.User.SECURITY_243_FULL_DEFENSE", true); + putBoolean(security, "hudson.remoting.URLDeserializationHelper.avoidUrlWrapping", false); + putBoolean(security, "jenkins.security.ClassFilterImpl.SUPPRESS_WHITELIST", false); + putBoolean(security, "jenkins.security.ClassFilterImpl.SUPPRESS_ALL", false); + + putStringInfo(security, "hudson.model.ParametersAction.safeParameters"); + putStringInfo(security, "hudson.model.DirectoryBrowserSupport.CSP"); + putStringInfo(security, "hudson.security.HudsonPrivateSecurityRealm.ID_REGEX"); + + Map info = new TreeMap<>(); + info.put("core", Jenkins.getVersion().toString()); + info.put("clientDate", clientDateString()); + info.put("properties", security); + + return JSONObject.fromObject(info); + } + + private static String clientDateString() { + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); + df.setTimeZone(tz); // strip timezone + return df.format(new Date()); + } + + private static void putBoolean(Map propertiesMap, String systemProperty, boolean defaultValue) { + propertiesMap.put(systemProperty, Boolean.toString(SystemProperties.getBoolean(systemProperty, defaultValue))); + } + + private static void putStringInfo(Map propertiesMap, String systemProperty) { + String reportedValue = "null"; + String value = SystemProperties.getString(systemProperty); + if (value != null) { + reportedValue = Integer.toString(value.length()); + } + propertiesMap.put(systemProperty, reportedValue); + } +} diff --git a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.html b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.html deleted file mode 100644 index 37b1e5cc65..0000000000 --- a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.html +++ /dev/null @@ -1,21 +0,0 @@ -
- Knowing how Jenkins is used is a tremendous help in guiding the direction of the development, especially - for open-source projects where users are inherently hard to track down. When this option is enabled, - Jenkins periodically send information about your usage of Jenkins. - -

- Specifically, it contains the following information: - -

    -
  • Your Jenkins version -
  • For your master and each agent, OS type, and # of executors -
  • Plugins that are installed and their versions -
  • Number of jobs per each job type in your Jenkins -
- -

- The information doesn't contain anything that identifies you or allows us to contact you - (except information inherently revealed by HTTP, such as the IP address). Tabulated - data of these usage statistics submissions will be shared with the community. - -

diff --git a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.jelly b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.jelly new file mode 100644 index 0000000000..96df143a83 --- /dev/null +++ b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected.jelly @@ -0,0 +1,48 @@ + + +
+ For any project, it's critical to know how the software is used, but tracking usage data is inherently difficult in open-source projects. + Anonymous usage statistics address this need. + When enabled, Jenkins periodically sends information to the Jenkins project. + The Jenkins project uses this information to set development priorities. +
+

General usage statistics

+
+

Jenkins reports the following general usage statistics:

+
    +
  • Your Jenkins version
  • +
  • For your master and each agent, the OS type and number of executors
  • +
  • Installed plugins and their versions
  • +
  • Number of items (like jobs) of each item type
  • +
+

+ This does not report any personally identifiable information. The only information reported by Jenkins is information inherently revealed by the HTTP protocol, such as the IP address. + + These usage statistics are aggregated, updated monthly, and published to stats.jenkins.io +

+
+

Telemetry collection

+
+

+ In addition to the general usage statistics listed above, the Jenkins project collects telemetry data from specific trials to inform future development. + Each trial has a specific purpose and a defined end date, after which collection stops, independent of the installed versions of Jenkins or plugins. + Once a trial is complete, the trial results may be aggregated and shared with the developer community. +

+

+ The following trials are currently set up on this instance: +

+ +
+ +
${collector.displayName}
+
+ +

+ Start date: ${collector.start} + End date: ${collector.end} +

+
+
+
+
+
diff --git a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_bg.html b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_bg.html deleted file mode 100644 index 20aacefce3..0000000000 --- a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_bg.html +++ /dev/null @@ -1,22 +0,0 @@ -
- От голяма помощ е да знаем как Jenkins се ползва. Това може да определи - посоката на разработка, което иначе е трудно, защото няма как да се проследяват - потребителите на проект с отворен код. Като изберете тази опция Jenkins - периодично ще изпраща анонимни данни за използването. - -

- Това е пълното описание на включената информация: - -

    -
  • версията на jenkins; -
  • операционната система и броя изпълнявани изграждания от основния и подчинените компютри; -
  • списък с инсталираните приставки и версиите им; -
  • броят задачи за всеки вид задача в инсталацията на Jenkins -
- -

- Информацията не съдържа нищо, които да ви идентифицира или да позволява да се свържем - с вас (с изключение на информацията указана поради естеството на HTTP, като IP адреси). - Тези данни ще бъдат споделени с общността в табличен вид. - -

diff --git a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_fr.html b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_fr.html deleted file mode 100644 index 8d4df6df9d..0000000000 --- a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_fr.html +++ /dev/null @@ -1,25 +0,0 @@ -
- Comprendre comment Jenkins est utilisé est d'une grande aide pour guider les développements, - particulièrement pour les projets open-source où les utilisateurs sont typiquement difficiles - à connaitre. Quand cette option est activée, Jenkins envoie périodiquement des informations sur votre - utilisation de Jenkins. - -

- Plus précisément, il envoie les informations suivantes: - -

    -
  • Votre version de Jenkins -
  • Pour le Jenkins maître et pour chaque esclave, le type d'OS et le nombre d'exécuteurs -
  • Les plugins qui sont installés et leurs versions -
  • Le nombre de jobs pour chaque type de job dans votre installation de Jenkins -
- -

- Cette information ne contient rien qui vous identifie ou qui nous permette de vous contacter - (hors certaines informations intrinsèques à HTTP, comme votre adresse IP). - Les données compilées de ces statistiques d'utilisation seront partagées avec la communauté. - -

- N'hésitez pas à lire - la discussion sur ce mécanisme et à contribuer aux débats. -

diff --git a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_it.html b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_it.html deleted file mode 100644 index 2fc1e8a8df..0000000000 --- a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_it.html +++ /dev/null @@ -1,25 +0,0 @@ -
- Sapere come viene utilizzato Jenkins aiuta notevolmente a guidare la - direzione dello sviluppo, specialmente nel caso dei progetti open source - in cui è inerentemente difficile rintracciare gli utenti. Quando - quest'opzione è abilitata, Jenkins invierà periodicamente informazioni - sull'utilizzo di Jenkins. - -

- Specificamente, tali informazioni conterranno quanto segue: - -

    -
  • La versione di Jenkins -
  • Per il master e ogni agente, il tipo di sistema operativo e il numero di esecutori -
  • I plugin installati e le rispettive versioni -
  • Il numero di processi per ogni tipo di processo in Jenkins -
- -

- Le informazioni non conterranno nulla che possa identificare l'utente o - consentirci di contattare l'utente (ad eccezione delle informazioni rivelate - naturalmente dal protocollo HTTP, come l'indirizzo IP). I dati tabulati - relativi agli invii di queste statistiche di utilizzo saranno condivisi con - la comunità. - -

diff --git a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_ja.html b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_ja.html deleted file mode 100644 index 7db3dac478..0000000000 --- a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_ja.html +++ /dev/null @@ -1,22 +0,0 @@ -
- Jenkinsがどのように使用されているか把握することは、特にユーザーを特定できないオープンソースプロジェクトにとって、 - 開発の方向性を決定する上で非常に役に立ちます。このオプションを有効にすると、Jenkinsの利用状況を定期的に送信します。 - -

- 送信する情報は次の情報を含んでいます。 - -

    -
  • Jenkinsのバージョン -
  • マスターと各スレーブの、OSとエグゼキューターの数 -
  • インストールされているプラグインとそのバージョン -
  • ジョブのタイプ毎のジョブ数 -
- -

- 情報には、あなたを特定できる、もしくはあなたに連絡できるようなものは含みません(ただし、IPアドレスのようなHTTP関連の情報は除きます)。 - 送信された利用状況の一覧データは、コミュニティで共有されます。 - -

- - コミュニティでのこの仕組みに関するディスカッションを参考にしてください。ご意見はこちらにお願いします。 -

diff --git a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_pt_BR.html b/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_pt_BR.html deleted file mode 100644 index bde77e44f1..0000000000 --- a/core/src/main/resources/hudson/model/UsageStatistics/help-usageStatisticsCollected_pt_BR.html +++ /dev/null @@ -1,20 +0,0 @@ -
- Saber como Jenkins é utilizado é uma tremenda ajuda guiando-nos no desenvolvimento, especificamente para - projetos de código livre onde os usuários são inerentemente difíceis de rastrear. Ao ativar esta opção, - Jenkins periodicamente enviará informações sobre o seu uso. - -

- Especificamente, as informações enviadas serão: - -

    -
  • A versão do seu Jenkins -
  • Para cada master e cada slave, o tipo de Sistema Operacional, e número de executores -
  • Os plugins instalados e suas versões -
  • Número de jobs por tipo de job no seu Jenkins -
- -

- Essas informaçãos não contem nada o identifique ou nos permita contatá-lo - (exceto pelas informações reveladas via HTTP, tal como Endereço IP). - Dados das estatísticas de uso submetidas são compartilhados com a comunidade. -

diff --git a/core/src/main/resources/jenkins/telemetry/impl/SecuritySystemProperties/description.jelly b/core/src/main/resources/jenkins/telemetry/impl/SecuritySystemProperties/description.jelly new file mode 100644 index 0000000000..dc78d7fda8 --- /dev/null +++ b/core/src/main/resources/jenkins/telemetry/impl/SecuritySystemProperties/description.jelly @@ -0,0 +1,6 @@ + + + For many security fixes, the Jenkins security team adds hidden options, sometimes called escape hatches, to disable the security fix. + This hedges against severe regressions not caught in testing, or unexpected legitimate use cases. + It is unknown to the team which of these options are actually in use, so this trial collects anonymous information about the values of (typically boolean) security-related Java system properties. + \ No newline at end of file diff --git a/test/src/test/java/jenkins/telemetry/TelemetryTest.java b/test/src/test/java/jenkins/telemetry/TelemetryTest.java new file mode 100644 index 0000000000..952d4e8968 --- /dev/null +++ b/test/src/test/java/jenkins/telemetry/TelemetryTest.java @@ -0,0 +1,209 @@ +package jenkins.telemetry; + +import hudson.ExtensionList; +import hudson.model.UnprotectedRootAction; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import hudson.security.csrf.CrumbExclusion; +import net.sf.json.JSONObject; +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; + +public class TelemetryTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Rule + public LoggerRule logger = new LoggerRule().record(Telemetry.class, Level.ALL).capture(100); + + private static int counter = 0; + + @Test + public void testSubmission() throws Exception { + j.jenkins.setNoUsageStatistics(false); // tests usually don't submit this, but we need this + assertEquals("no requests received", 0, counter); + Telemetry.ENDPOINT = j.getURL().toString() + "uplink/events"; + ExtensionList.lookupSingleton(Telemetry.TelemetryReporter.class).doRun(); + do { + Thread.sleep(250); + } while (counter == 0); // this might end up being flaky due to 1 to many active telemetry trials + assertThat(logger.getMessages(), hasItem("Telemetry submission received response '200 OK' for: test-data")); + assertThat(logger.getMessages(), hasItem("Skipping telemetry for 'future' as it is configured to start later")); + assertThat(logger.getMessages(), hasItem("Skipping telemetry for 'past' as it is configured to end in the past")); + assertThat(types, hasItem("test-data")); + assertThat(types, not(hasItem("future"))); + assertThat(types, not(hasItem("past"))); + assertThat(correlators.size(), is(1)); + assertTrue("at least one request received", counter > 0); // TestTelemetry plus whatever real impls exist + } + + @TestExtension + public static class DisabledFutureTelemetry extends Telemetry { + + @Nonnull + @Override + public String getId() { + return "future"; + } + + @Nonnull + @Override + public String getDisplayName() { + return "future"; + } + + @Nonnull + @Override + public LocalDate getStart() { + return LocalDate.now().plus(1, ChronoUnit.DAYS); + } + + @Nonnull + @Override + public LocalDate getEnd() { + return LocalDate.MAX; + } + + @Nonnull + @Override + public JSONObject createContent() { + return new JSONObject(); + } + } + + @TestExtension + public static class DisabledPastTelemetry extends Telemetry { + + @Nonnull + @Override + public String getId() { + return "past"; + } + + @Nonnull + @Override + public String getDisplayName() { + return "past"; + } + + @Nonnull + @Override + public LocalDate getStart() { + return LocalDate.MIN; + } + + @Nonnull + @Override + public LocalDate getEnd() { + return LocalDate.now().minus(1, ChronoUnit.DAYS); + } + + @Nonnull + @Override + public JSONObject createContent() { + return new JSONObject(); + } + } + + @TestExtension + public static class TestTelemetry extends Telemetry { + + @Nonnull + @Override + public String getId() { + return "test-data"; + } + + @Nonnull + @Override + public String getDisplayName() { + return "test-data"; + } + + @Nonnull + @Override + public LocalDate getStart() { + return LocalDate.MIN; + } + + @Nonnull + @Override + public LocalDate getEnd() { + return LocalDate.MAX; + } + + @Nonnull + @Override + public JSONObject createContent() { + return new JSONObject(); + } + } + + @TestExtension + public static class NoCrumb extends CrumbExclusion { + @Override + public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + String pathInfo = request.getPathInfo(); + if (pathInfo != null && pathInfo.startsWith("/uplink")) { + chain.doFilter(request, response); + return true; + } + return false; + } + } + + private static Set correlators = new HashSet<>(); + private static Set types = new HashSet<>(); + + @TestExtension + public static class TelemetryReceiver implements UnprotectedRootAction { + public void doEvents(StaplerRequest request, StaplerResponse response) throws IOException { + StringWriter sw = new StringWriter(); + IOUtils.copy(request.getInputStream(), sw, StandardCharsets.UTF_8); + JSONObject json = JSONObject.fromObject(sw.toString()); + correlators.add(json.getString("correlator")); + types.add(json.getString("type")); + counter++; + } + + @CheckForNull + @Override + public String getIconFileName() { + return null; + } + + @CheckForNull + @Override + public String getDisplayName() { + return null; + } + + @CheckForNull + @Override + public String getUrlName() { + return "uplink"; + } + } +}