Skip to content

Commit acc9120

Browse files
daniel-beckKevin-CB
authored andcommitted
[SECURITY-3513]
Co-authored-by: Kevin-CB <[email protected]>
1 parent 81983c6 commit acc9120

File tree

11 files changed

+99
-0
lines changed

11 files changed

+99
-0
lines changed

core/src/main/java/hudson/model/ComputerSet.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import jenkins.model.Jenkins;
6262
import jenkins.model.ModelObjectWithChildren;
6363
import jenkins.model.ModelObjectWithContextMenu.ContextMenu;
64+
import jenkins.security.ExtendedReadRedaction;
6465
import jenkins.util.Timer;
6566
import jenkins.widgets.HasWidgets;
6667
import net.sf.json.JSONObject;
@@ -75,6 +76,7 @@
7576
import org.kohsuke.stapler.export.ExportedBean;
7677
import org.kohsuke.stapler.interceptor.RequirePOST;
7778
import org.kohsuke.stapler.verb.POST;
79+
import org.springframework.security.access.AccessDeniedException;
7880

7981
/**
8082
* Serves as the top of {@link Computer}s in the URL hierarchy.
@@ -295,6 +297,19 @@ public synchronized void doCreateItem(StaplerRequest2 req, StaplerResponse2 rsp,
295297

296298
// copy through XStream
297299
String xml = Jenkins.XSTREAM.toXML(src);
300+
if (!src.hasPermission(Computer.CONFIGURE)) {
301+
final String redactedConfigDotXml = ExtendedReadRedaction.applyAll(xml);
302+
if (!xml.equals(redactedConfigDotXml)) {
303+
// AccessDeniedException2 does not permit a custom message, and anyway redirecting the user to the login screen is obviously pointless.
304+
throw new AccessDeniedException(
305+
Messages.ComputerSet_may_not_copy_as_it_contains_secrets_and_(
306+
src.getNodeName(),
307+
Jenkins.getAuthentication2().getName(),
308+
Computer.PERMISSIONS.title,
309+
Computer.EXTENDED_READ.name,
310+
Computer.CONFIGURE.name));
311+
}
312+
}
298313
Node result = (Node) Jenkins.XSTREAM.fromXML(xml);
299314
result.setNodeName(name);
300315
result.holdOffLaunchUntilSave = true;

core/src/main/resources/hudson/model/Messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ ComputerSet.NoSuchSlave=No such agent: {0}
112112
ComputerSet.SlaveAlreadyExists=Agent called ‘{0}’ already exists
113113
ComputerSet.SpecifySlaveToCopy=Specify which agent to copy
114114
ComputerSet.DisplayName=Nodes
115+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=May not copy {0} as it contains secrets and {1} has {2}/{3} but not /{4}
115116

116117
Descriptor.From=(from <a href="{1}" rel="noopener noreferrer" target="_blank">{0}</a>)
117118

core/src/main/resources/hudson/model/Messages_bg.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ CLI.wait-node-offline.shortDescription=\
155155
ComputerSet.DisplayName=\
156156
Компютри
157157

158+
# May not copy {0} as it contains secrets and {1} has {2}/{3} but not /{4}
159+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=\
160+
Не може да копирате „{0}“, защото на него има пароли, а „{1}“ има {2}/{3}, но\
161+
не и /{4}
162+
158163
Descriptor.From=\
159164
(от <a href="{1}">{0}</a>)
160165

core/src/main/resources/hudson/model/Messages_de.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ ComputerSet.DisplayName=Knoten
107107
ComputerSet.NoSuchSlave=Agent existiert nicht: „{0}“
108108
ComputerSet.SlaveAlreadyExists=Ein Agent mit dem Namen „{0}“ existiert bereits.
109109
ComputerSet.SpecifySlaveToCopy=Geben Sie an, welcher Agent kopiert werden soll
110+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=Kann „{0}“ nicht kopieren, da es Geheimnisse enthält und „{1}“ nur {2}/{3} aber nicht /{4} hat.
110111

111112
Descriptor.From=(aus <a href="{1}">{0}</a>)
112113

core/src/main/resources/hudson/model/Messages_fr.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ ComputerSet.NoSuchSlave=Aucun agent: {0}
107107
ComputerSet.SlaveAlreadyExists=L''agent ‘{0}’ existe déjà
108108
ComputerSet.SpecifySlaveToCopy=Quel agent à copier
109109
ComputerSet.DisplayName=Nœuds
110+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=Copie impossible de {0} car il contient des secrets et {1} possède {2}/{3} mais pas /{4}
110111

111112
Descriptor.From=(de <a href="{1}" rel="noopener noreferrer" target="_blank">{0}</a>)
112113

core/src/main/resources/hudson/model/Messages_it.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ ComputerSet.DisplayName=Nodi
156156
ComputerSet.NoSuchSlave=L''agente {0} non esiste
157157
ComputerSet.SlaveAlreadyExists=Un agente di nome "{0}" esiste già
158158
ComputerSet.SpecifySlaveToCopy=Specificare l''agente da copiare
159+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=Impossibile copiare \
160+
{0} perché contiene segreti e {1} ha {2}/{3} ma non /{4}
159161
Descriptor.From=(da <a href="{1}">{0}</a>)
160162
Executor.NotAvailable=N/D
161163
FileParameterDefinition.DisplayName=Parametro file

core/src/main/resources/hudson/model/Messages_pt_BR.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ UpdateCenter.Status.Success=Sucesso
105105
UpdateCenter.init=Iniciando o centro de atualização
106106
ParameterAction.DisplayName=Parâmetros
107107
ComputerSet.DisplayName=Nós
108+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=Pode não conseguir copiar {0} já que contem segredos e {1} \
109+
tem {2}/{3} mas não /{4}
108110
Run.DeletePermission.Description=\
109111
Esta permissão concede aos usuários o direito de manualmente apagarem construções do histórico de construções.
110112
Run.Summary.BrokenForALongTime=Quebrado há muito tempo

core/src/main/resources/hudson/model/Messages_sr.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Computer.BadChannel=Машина агента није повезана или
8585
ComputerSet.SlaveAlreadyExists=Већ постоји агент са именом {0}
8686
ComputerSet.SpecifySlaveToCopy=Одредите агент за копирање
8787
ComputerSet.DisplayName=Рачунари
88+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=Неможе се копирати {0} пошто садржи тајне и {1} има {2}/{3} а не /{4}
8889
Descriptor.From=(од <a href="{1}">{0}</a>)
8990
Executor.NotAvailable=Н/Д
9091
FreeStyleProject.DisplayName=Независни задатак

core/src/main/resources/hudson/model/Messages_sv_SE.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ ComputerSet.NoSuchSlave=Det finns ingen sådan agent: {0}
112112
ComputerSet.SlaveAlreadyExists=En agent med namnet "{0}" finns redan
113113
ComputerSet.SpecifySlaveToCopy=Ange vilken agent som ska kopieras
114114
ComputerSet.DisplayName=Noder
115+
ComputerSet.may_not_copy_as_it_contains_secrets_and_=Kan inte kopiera {0} eftersom den innehåller hemligheter och {1} har {2}/{3} men inte /{4}
115116

116117
Descriptor.From=(från <a href="{1}" rel="noopener noreferrer" target="_blank">{0}</a>)
117118

test/src/test/java/lib/form/PasswordTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import edu.umd.cs.findbugs.annotations.CheckForNull;
3939
import edu.umd.cs.findbugs.annotations.NonNull;
40+
import hudson.Extension;
4041
import hudson.FilePath;
4142
import hudson.Launcher;
4243
import hudson.cli.CopyJobCommand;
@@ -62,6 +63,7 @@
6263
import hudson.security.ACL;
6364
import hudson.slaves.DumbSlave;
6465
import hudson.slaves.NodeProperty;
66+
import hudson.slaves.NodePropertyDescriptor;
6567
import hudson.tasks.BuildStepDescriptor;
6668
import hudson.tasks.Builder;
6769
import hudson.util.FormValidation;
@@ -70,6 +72,7 @@
7072
import java.io.File;
7173
import java.io.IOException;
7274
import java.io.PrintStream;
75+
import java.net.URL;
7376
import java.util.Arrays;
7477
import java.util.Collection;
7578
import java.util.List;
@@ -82,7 +85,10 @@
8285
import jenkins.security.ExtendedReadRedaction;
8386
import jenkins.security.ExtendedReadSecretRedaction;
8487
import jenkins.tasks.SimpleBuildStep;
88+
import org.htmlunit.HttpMethod;
8589
import org.htmlunit.Page;
90+
import org.htmlunit.WebRequest;
91+
import org.htmlunit.WebResponse;
8692
import org.htmlunit.html.DomElement;
8793
import org.htmlunit.html.HtmlHiddenInput;
8894
import org.htmlunit.html.HtmlInput;
@@ -195,16 +201,74 @@ public void testNodeSecrets() throws Exception {
195201
}
196202
}
197203

204+
@For({ExtendedReadRedaction.class, ExtendedReadSecretRedaction.class})
205+
@Issue("SECURITY-3513")
206+
@Test
207+
public void testCopyNodeSecrets() throws Exception {
208+
Computer.EXTENDED_READ.setEnabled(true);
209+
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
210+
MockAuthorizationStrategy mockAuthorizationStrategy = new MockAuthorizationStrategy();
211+
mockAuthorizationStrategy.grant(Jenkins.READ, Computer.CREATE, Computer.CONFIGURE).everywhere().to("alice");
212+
mockAuthorizationStrategy.grant(Jenkins.READ, Computer.CREATE, Computer.EXTENDED_READ).everywhere().to("bob");
213+
j.jenkins.setAuthorizationStrategy(mockAuthorizationStrategy);
214+
215+
final DumbSlave onlineSlave = j.createOnlineSlave();
216+
final String secretText = "t0ps3cr3td4t4_node";
217+
final Secret encryptedSecret = Secret.fromString(secretText);
218+
final String encryptedSecretText = encryptedSecret.getEncryptedValue();
219+
220+
onlineSlave.getNodeProperties().add(new NodePropertyWithSecret(encryptedSecret));
221+
onlineSlave.save();
222+
223+
assertThat(readString(new File(onlineSlave.getRootDir(), "config.xml").toPath()), containsString(encryptedSecretText));
224+
assertEquals(2, j.getInstance().getComputers().length);
225+
226+
String agentCopyURL = j.getURL() + "/computer/createItem?mode=copy&from=" + onlineSlave.getNodeName() + "&name=";
227+
228+
{ // with configure, you can copy a node containing secrets
229+
try (JenkinsRule.WebClient wc = j.createWebClient().login("alice")) {
230+
WebResponse rsp = wc.getPage(wc.addCrumb(new WebRequest(new URL(agentCopyURL + "aliceAgent"),
231+
HttpMethod.POST))).getWebResponse();
232+
assertEquals(200, rsp.getStatusCode());
233+
assertEquals(3, j.getInstance().getComputers().length);
234+
235+
final Page page = wc.goTo("computer/aliceAgent/config.xml", "application/xml");
236+
final String content = page.getWebResponse().getContentAsString();
237+
238+
assertThat(content, not(containsString(secretText)));
239+
assertThat(content, containsString(encryptedSecretText));
240+
assertThat(content, containsString("<secret>" + encryptedSecretText + "</secret>"));
241+
}
242+
}
243+
244+
{ // without configure, you cannot copy a node containing secrets
245+
try (JenkinsRule.WebClient wc = j.createWebClient().withThrowExceptionOnFailingStatusCode(false).login("bob")) {
246+
WebResponse rsp = wc.getPage(wc.addCrumb(new WebRequest(new URL(agentCopyURL + "bobAgent"),
247+
HttpMethod.POST))).getWebResponse();
248+
249+
assertEquals(403, rsp.getStatusCode());
250+
assertThat(rsp.getContentAsString(), containsString("May not copy " + onlineSlave.getNodeName() + " as it contains secrets"));
251+
assertEquals(3, j.getInstance().getComputers().length);
252+
}
253+
}
254+
}
255+
198256
public static class NodePropertyWithSecret extends NodeProperty<Node> {
199257
private final Secret secret;
200258

259+
@DataBoundConstructor
201260
public NodePropertyWithSecret(Secret secret) {
202261
this.secret = secret;
203262
}
204263

205264
public Secret getSecret() {
206265
return secret;
207266
}
267+
268+
@Extension
269+
public static class DescriptorImpl extends NodePropertyDescriptor {
270+
271+
}
208272
}
209273

210274
@For({ExtendedReadRedaction.class, ExtendedReadSecretRedaction.class})

0 commit comments

Comments
 (0)