-
Notifications
You must be signed in to change notification settings - Fork 246
[JENKINS-70101] Revive ability to snapshot() the CertificateCredentials so they can be used on remote agents #391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
14b4424
666186a
58c4e26
f4afd9f
f4896f9
3077afa
fda8836
f45cdbe
b5e0ec8
981355e
9d3cb56
2b9fa6f
f2b859f
b3ae79c
4a1d34e
f6731fb
9d32b42
3694a90
90e5a21
8e6831f
3213f69
9d98c2f
7053c11
e9bbe26
6fae01e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -155,6 +155,33 @@ | |
<artifactId>workflow-basic-steps</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>job-dsl</artifactId> | ||
<version>1.81</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>command-launcher</artifactId> | ||
<version>1.6</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. version should come from the bom, but additionally should not be needed as the agents should be created via |
||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>credentials-binding</artifactId> | ||
<version>523.vd859a_4b_122e6</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use the version from the bom. |
||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jenkins-ci.plugins</groupId> | ||
<artifactId>http_request</artifactId> | ||
<!-- Note: testCertHttpRequestOnNodeRemote() fails for 1.16 | ||
unless CertificateCredentialsSnapshotTaker is functional | ||
--> | ||
<version>1.16</version> | ||
<scope>test</scope> | ||
</dependency> | ||
Comment on lines
+176
to
+184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this needed - can the test provided by Devin be used without this? |
||
</dependencies> | ||
|
||
<build> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
*/ | ||
package com.cloudbees.plugins.credentials.impl; | ||
|
||
import com.cloudbees.plugins.credentials.CredentialsProvider; | ||
import com.cloudbees.plugins.credentials.CredentialsScope; | ||
import com.cloudbees.plugins.credentials.SecretBytes; | ||
import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; | ||
|
@@ -35,6 +36,7 @@ | |
import hudson.model.AbstractDescribableImpl; | ||
import hudson.model.Descriptor; | ||
import hudson.model.Items; | ||
import hudson.remoting.Channel; | ||
import hudson.util.FormValidation; | ||
import hudson.util.Secret; | ||
import java.io.ByteArrayInputStream; | ||
|
@@ -156,6 +158,21 @@ | |
return password.getPlainText().toCharArray(); | ||
} | ||
|
||
/** | ||
* When serializing over a {@link Channel} ensure that we send a self-contained version. | ||
* | ||
* @return the object instance to write to the stream. | ||
*/ | ||
private Object writeReplace() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is wrong. |
||
if (/* XStream */ Channel.current() == null | ||
|| /* already safe to serialize */ keyStoreSource | ||
.isSnapshotSource() | ||
) { | ||
return this; | ||
} | ||
return CredentialsProvider.snapshot(this); | ||
} | ||
|
||
/** | ||
* Returns the {@link KeyStore} containing the certificate. | ||
* | ||
|
@@ -369,17 +386,18 @@ | |
|
||
/** | ||
* The old uploaded keystore. | ||
* Still used for snapshot taking, with contents independent of Jenkins instance and JVM. | ||
*/ | ||
@CheckForNull | ||
@Deprecated | ||
private transient Secret uploadedKeystore; | ||
private Secret uploadedKeystore; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. create a different Implementation of the interface that is self contained for the snapshot. |
||
/** | ||
* The uploaded keystore. | ||
* | ||
* @since 2.1.5 | ||
*/ | ||
@CheckForNull | ||
private final SecretBytes uploadedKeystoreBytes; | ||
private SecretBytes uploadedKeystoreBytes; | ||
|
||
/** | ||
* Our constructor. | ||
|
@@ -409,6 +427,19 @@ | |
this.uploadedKeystoreBytes = uploadedKeystore; | ||
} | ||
|
||
/** | ||
* Our constructor for serialization (e.g. to remote agents, whose SecretBytes | ||
* in another JVM use a different static KEY); would re-encode. | ||
* | ||
* @param uploadedKeystore the keystore content. | ||
* @deprecated | ||
*/ | ||
@SuppressWarnings("unused") // by stapler | ||
@Deprecated | ||
public UploadedKeyStoreSource(@CheckForNull Secret uploadedKeystore) { | ||
this.uploadedKeystore = uploadedKeystore; | ||
} | ||
|
||
/** | ||
* Constructor able to receive file directly | ||
* | ||
|
@@ -428,6 +459,18 @@ | |
this.uploadedKeystoreBytes = uploadedKeystore; | ||
} | ||
|
||
/** | ||
* Request that if the less-efficient but more-portable Secret | ||
* is involved (e.g. to cross the remoting gap to another JVM), | ||
* we use the more secure and efficient SecretBytes. | ||
*/ | ||
public void useSecretBytes() { | ||
if (this.uploadedKeystore != null && this.uploadedKeystoreBytes == null) { | ||
this.uploadedKeystoreBytes = SecretBytes.fromBytes(DescriptorImpl.toByteArray(this.uploadedKeystore)); | ||
this.uploadedKeystore = null; | ||
} | ||
} | ||
|
||
/** | ||
* Migrate to the new field. | ||
* | ||
|
@@ -444,11 +487,14 @@ | |
} | ||
|
||
/** | ||
* Returns the private key file name. | ||
* Returns the private key + certificate file bytes. | ||
* | ||
* @return the private key file name. | ||
* @return the private key + certificate file bytes. | ||
*/ | ||
public SecretBytes getUploadedKeystore() { | ||
if (uploadedKeystore != null && uploadedKeystoreBytes == null) { | ||
return SecretBytes.fromBytes(DescriptorImpl.toByteArray(uploadedKeystore)); | ||
} | ||
return uploadedKeystoreBytes; | ||
} | ||
|
||
|
@@ -458,6 +504,9 @@ | |
@NonNull | ||
@Override | ||
public byte[] getKeyStoreBytes() { | ||
if (uploadedKeystore != null && uploadedKeystoreBytes == null) { | ||
return DescriptorImpl.toByteArray(uploadedKeystore); | ||
} | ||
return SecretBytes.getPlainData(uploadedKeystoreBytes); | ||
} | ||
|
||
|
@@ -474,7 +523,11 @@ | |
*/ | ||
@Override | ||
public boolean isSnapshotSource() { | ||
return true; | ||
//return this.snapshotSecretBytes; | ||
// If context is local, clone SecretBytes directly (only | ||
// usable in same JVM). Otherwise use Secret for transport | ||
// (see {@link CertificateCredentialsSnapshotTaker}. | ||
return (/* XStream */ Channel.current() == null); | ||
} | ||
|
||
@Override | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright (c) 2011-2016, CloudBees, Inc., Stephen Connolly. | ||
* | ||
* 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 com.cloudbees.plugins.credentials.impl; | ||
|
||
import com.cloudbees.plugins.credentials.CredentialsSnapshotTaker; | ||
import com.cloudbees.plugins.credentials.SecretBytes; | ||
import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; | ||
import hudson.Extension; | ||
import hudson.util.Secret; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.security.KeyStoreException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.cert.CertificateException; | ||
import java.util.Arrays; | ||
|
||
/** | ||
* The {@link CredentialsSnapshotTaker} for {@link StandardCertificateCredentials}. | ||
* Taking a snapshot of the credential ensures that all the details are captured | ||
* within the credential. | ||
* | ||
* @since 1.14 | ||
* | ||
* Historic note: This code was dropped from {@link CertificateCredentialsImpl} | ||
* codebase along with most of FileOnMasterKeyStoreSource (deprecated and headed | ||
* towards eventual deletion) due to SECURITY-1322, see more details at | ||
* https://www.jenkins.io/security/advisory/2019-05-21/ | ||
* In hind-sight, this snapshot taker was needed to let the | ||
* {@link CertificateCredentialsImpl.UploadedKeyStoreSource} be used | ||
* on remote agents. | ||
*/ | ||
@Extension | ||
public class CertificateCredentialsSnapshotTaker extends CredentialsSnapshotTaker<StandardCertificateCredentials> { | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public Class<StandardCertificateCredentials> type() { | ||
return StandardCertificateCredentials.class; | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public StandardCertificateCredentials snapshot(StandardCertificateCredentials credentials) { | ||
if (credentials instanceof CertificateCredentialsImpl) { | ||
final CertificateCredentialsImpl.KeyStoreSource keyStoreSource = ((CertificateCredentialsImpl) credentials).getKeyStoreSource(); | ||
if (keyStoreSource.isSnapshotSource()) { | ||
return credentials; | ||
} | ||
return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(), | ||
credentials.getDescription(), credentials.getPassword().getEncryptedValue(), | ||
new CertificateCredentialsImpl.UploadedKeyStoreSource(CertificateCredentialsImpl.UploadedKeyStoreSource.DescriptorImpl.toSecret(keyStoreSource.getKeyStoreBytes()))); | ||
Comment on lines
+77
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should creaste a new implementation of StandardCertificateCredentials and return that. Putting the extra info and logic into the current Implementation is not the correct path. |
||
} | ||
|
||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
final char[] password = credentials.getPassword().getPlainText().toCharArray(); | ||
try { | ||
credentials.getKeyStore().store(bos, password); | ||
bos.close(); | ||
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { | ||
return credentials; // as-is | ||
} finally { | ||
Arrays.fill(password, (char) 0); | ||
} | ||
|
||
return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(), | ||
credentials.getDescription(), credentials.getPassword().getEncryptedValue(), | ||
new CertificateCredentialsImpl.UploadedKeyStoreSource(CertificateCredentialsImpl.UploadedKeyStoreSource.DescriptorImpl.toSecret(bos.toByteArray()))); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is jobdsl needed here? (please remove it)