From ebc273b4f69326b1c5c37bd638d62613618ac223 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Fri, 10 Apr 2026 12:50:41 +0530 Subject: [PATCH 1/4] Add test coverage for certificate credentials in SystemCredentialsTest --- .../plugins/casc/SystemCredentialsTest.java | 58 ++++++++++++++++-- .../plugins/casc/SystemCredentialsTest.yml | 14 ++--- .../io/jenkins/plugins/casc/test.p12 | Bin 2685 -> 2708 bytes 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java b/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java index de521a1161..dffddb86c9 100644 --- a/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java +++ b/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java @@ -3,20 +3,30 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.DirectEntryPrivateKeySource; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.CertificateCredentials; import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; +import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; +import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl.UploadedKeyStoreSource; import hudson.security.ACL; import io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator; import io.jenkins.plugins.casc.misc.ConfiguredWithCode; import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; import io.jenkins.plugins.casc.model.CNode; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; import java.util.Collections; +import java.util.Enumeration; import java.util.List; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -41,7 +51,8 @@ public class SystemCredentialsTest { public RuleChain chain = RuleChain.outerRule(new EnvironmentVariables() .set("SUDO_PASSWORD", "1234") .set("SSH_PRIVATE_KEY", "s3cr3t") - .set("SSH_KEY_PASSWORD", "ABCD")) + .set("SSH_KEY_PASSWORD", "123456") + .set("CERTIFICATE_BASE64", getBase64Keystore())) .around(log) .around(new JenkinsConfiguredWithCodeRule()); @@ -63,16 +74,35 @@ public void configure_system_credentials() throws Exception { List certs = CredentialsProvider.lookupCredentials( CertificateCredentials.class, jenkins, ACL.SYSTEM, Collections.emptyList()); - assertThat(certs, hasSize(0)); - // TODO: add test for uploaded certificate - // assertThat(certs.get(0).getPassword().getPlainText(), equalTo("ABCD")); + assertThat(certs, hasSize(1)); + + CertificateCredentialsImpl certImpl = (CertificateCredentialsImpl) certs.get(0); + assertThat(certImpl.getId(), equalTo("uploaded_certificate")); + assertThat(certImpl.getPassword().getPlainText(), equalTo("123456")); + assertThat(certImpl.getKeyStoreSource(), notNullValue()); + assertThat(certImpl.getKeyStoreSource(), instanceOf(UploadedKeyStoreSource.class)); + assertThat(certImpl.getKeyStore(), notNullValue()); + assertThat(certImpl.getKeyStore().size(), greaterThan(0)); + + UploadedKeyStoreSource keyStoreSource = (UploadedKeyStoreSource) certImpl.getKeyStoreSource(); + assertThat(keyStoreSource.getUploadedKeystore(), notNullValue()); + + byte[] expectedBytes = getRawKeystoreBytes(); + byte[] actualBytes = keyStoreSource.getUploadedKeystore().getPlainData(); + assertThat("The bytes in Jenkins should be identical to the source file", actualBytes, equalTo(expectedBytes)); + + Enumeration aliases = certImpl.getKeyStore().aliases(); + assertThat("Keystore should not be empty", aliases.hasMoreElements(), equalTo(true)); + + String firstAlias = aliases.nextElement(); + assertThat("Alias should contain a valid key", certImpl.getKeyStore().isKeyEntry(firstAlias), equalTo(true)); List sshPrivateKeys = CredentialsProvider.lookupCredentials( BasicSSHUserPrivateKey.class, jenkins, ACL.SYSTEM, Collections.emptyList()); assertThat(sshPrivateKeys, hasSize(1)); final BasicSSHUserPrivateKey ssh_with_passphrase = sshPrivateKeys.get(0); - assertThat(ssh_with_passphrase.getPassphrase().getPlainText(), equalTo("ABCD")); + assertThat(ssh_with_passphrase.getPassphrase().getPlainText(), equalTo("123456")); final DirectEntryPrivateKeySource source = (DirectEntryPrivateKeySource) ssh_with_passphrase.getPrivateKeySource(); @@ -81,7 +111,23 @@ public void configure_system_credentials() throws Exception { // credentials should not appear in plain text in log for (LogRecord logRecord : log.getRecords()) { assertThat(logRecord.getMessage(), not(containsString("1234"))); - assertThat(logRecord.getMessage(), not(containsString("ABCD"))); + assertThat(logRecord.getMessage(), not(containsString("123456"))); + } + } + + private static String getBase64Keystore() { + return Base64.getEncoder().encodeToString(getRawKeystoreBytes()); + } + + private static byte[] getRawKeystoreBytes() { + try { + URL res = SystemCredentialsTest.class.getResource("test.p12"); + if (res == null) { + throw new IllegalStateException("Cannot find test.p12 on classpath"); + } + return Files.readAllBytes(Paths.get(res.toURI())); + } catch (Exception e) { + throw new RuntimeException("Failed to read test.p12 from classpath", e); } } } diff --git a/integrations/src/test/resources/io/jenkins/plugins/casc/SystemCredentialsTest.yml b/integrations/src/test/resources/io/jenkins/plugins/casc/SystemCredentialsTest.yml index 8ac47a4522..805c2c8d77 100644 --- a/integrations/src/test/resources/io/jenkins/plugins/casc/SystemCredentialsTest.yml +++ b/integrations/src/test/resources/io/jenkins/plugins/casc/SystemCredentialsTest.yml @@ -16,13 +16,13 @@ credentials: # global credentials - credentials: -# - certificate: -# scope: SYSTEM -# id: ssh_private_key -# password: ${SSH_KEY_PASSWORD} -# keyStoreSource: -# secretBytes: -# keyStoreFile: /docker/secret/id_rsa + - certificate: + scope: SYSTEM + id: uploaded_certificate + password: ${SSH_KEY_PASSWORD} + keyStoreSource: + uploaded: + uploadedKeystore: ${CERTIFICATE_BASE64} - basicSSHUserPrivateKey: scope: SYSTEM id: ssh_with_passphrase_provided diff --git a/integrations/src/test/resources/io/jenkins/plugins/casc/test.p12 b/integrations/src/test/resources/io/jenkins/plugins/casc/test.p12 index 253d4081a3aeffab7d17e8c0a308ee1e85d6456f..6c09c5a29241249bca7c4792de00596e3e137592 100644 GIT binary patch literal 2708 zcma);X*d*&7RP7C%oxi^*6eEyCS!(-u`h}2WYxjYk<|_-Q}l(hy*Ml`!9mUqoEm0u{QHR*))S zD7X~%waDX)<*Z$x>z#NrjnwoxCT72&bXHdsWe4M`=_-6PSn#v;;N*pl=tVxs2}UTU zPeS;cY#gB-=9p&ZMjeCOQ^|oVFy*(X0&|{|C4Q1Lk%Tz{CDW#)_aU~|16H@y@0ZNt z$jQC>X+NJ6zuSEm!PdB6#cYq>uUJBO3q_w_tyh8*@Fnt!%O?29XqFLYO6h#%7)7oV zQ6x7~w~+%zW@?%V1|3|Yh!TQd%yKh1!?=gRc@3kfKCfD^)#$404NQ0b=4!(2t`O>%QJ~++B z7L=eGV;I{Yc;(!i0(3TM^AyyR*j>`yJ-^E5K%UpJJn~*m3jL%5^Jmh z%L1@Xg@CFD=HiQb?FM!Z4Jtu3?9QGe>@9d(JMZ}H0=UOPn~3la=8s#(7T;%k!h0Ow zHy7)4U2o$2Gfv5Bz9G1bO`+IV{5}p&ZY6x;N0ilW9}k&Adf)st>p%YI#-*uR9O;;R z!hidb)Rplp6|+z^dvlq+Z#nOgW4W{^*oV)OcCs!Tbh|lRqpG^!47Z|>{CKi6EkU!? z2=L00!jDZA5T^s1bIz`Nm&@oAOf{&~%0^H9+ftkSJ_)@k942M)$ZKu8(of#k^_3a- zZm#@Hnz_K7Z1+wL`sK@0Hhh`dCDw z3E(q-^||JqonX}jwbNyimj!q1SlvjN!R~spy-5)&Dvx(NGxvLe$V6=ahh%IOeYEA8 z@a8KmZt%gp_Vw3gLWld-N5kGe!n2p6-aYZN>WJio4&!Q}I|DTB7y-aVD zX>K;IFi%ViS~I^DM`D@1X_Sg3vpNRCEPTt;h9y!^5u);ugpW z_Ue->-vlDNj_DDS^bTH0zjX2YMJDo#Jr330nQ-d^HgBP* ztzc@6tX+hZay+J}AFiuWfSO%prxj*wR#Rbd=n&VwHqjR?@hA<^g!g#*W>WL@%A?Uk zKyVbt_b73rjux7w))7;Bz#==39^lF;ny9jQi}CUOmFRAob*p-B3i&;29Ri`{8f7NX zmD&BBp-g^m<9?QD$TJ!?l63ad58++brr0ZVGVkbss;(=Nk!G~cy+&Z2`2%(Jl?$bY z^^^Jn<1(ha3ZA|sMgZ){SWw|TH> zS=S_cp-wYvD6R1*Ry@%q|Bm+O*yqhJ39dY{8nxu3P5+HP1$Xvwt?X_x)t;Z_<7eNm z81aV=!Gq6B7aE;xM?RKIA_7>B+dujfuCc2I%XXDsB(55cxT0aysk2;B_2GvJ1Hzd* zIxA5lqFDVqiXDylx4FhGg01A7&F6IaFL?9Dr=ztWw;4HGqLxLs#iP5h`(zkIzJa8D ze#^EL?(uO%i%m~{Eqxd+wMg^2C+T%xx%98Y`6tSl0E^_7_6f7&4LI?3xL(_J4mE|{ zKy6ZMbg`3J1yZ*}*Doxe>oKI5BkVbnhMI%Yx;1r z6!MiXZfczr6EUJWS^}xcxB9vszc>~0g6-QQ2&ps-7o_+UndPRlrIh(nNQ&CP$CUMh zvVqPcI}@1q`-6@q(!Bb*&^qMeJrx4YrQp4+*%TH0c4U#^rX z9Kar$K4leX=O&uZ7{uXx<%JfRcqLlAfb1Y+2CK|Eq4wDmf$kcEmO;<;Z%_5$dtc7^ zNGB{$IEJ#uoN_J{>*_t6&qiCIF=+VjPYncw0Kk%%EASn8>&<;-3I{#XhI=?`ynI}y p`h#39D23~*ul!dqpY)nj<&I5h9;B>8Oh)`!)!bCy{eMNwKLINQ;wk_D literal 2685 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 Date: Fri, 10 Apr 2026 13:47:20 +0530 Subject: [PATCH 2/4] Change credentials --- .../jenkins/plugins/casc/CredentialsReadmeTest.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java b/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java index 21692b3fcd..05e2cb4721 100644 --- a/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java +++ b/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java @@ -56,7 +56,7 @@ public class CredentialsReadmeTest { + "PWLXZZQYqsfWIQwvXTEdAkEA2bziyReYAb9fi17alcvwZXGzyyMY8WOGns8NZKcq\n" + "INF8D3PDpcCyOvQI/TS3qHYmGyWdHiKCWsgBqE6kyjqpNQ==\n" + "-----END RSA PRIVATE KEY-----\n"; - public static final String PASSWORD = "password"; + public static final String PASSWORD = "123456"; public static final String TEXT = "text"; public static final String ACCESS_KEY = "access-key"; public static final String SECRET_ACCESS_KEY = "secret-access-key"; @@ -143,12 +143,9 @@ public void testGlobalScopedCredentials() throws Exception { SecretBytes.fromString(Base64.getEncoder().encodeToString(fileContent)); UploadedKeyStoreSource keyStoreSource = (UploadedKeyStoreSource) cert.getKeyStoreSource(); assertThat(keyStoreSource.getUploadedKeystore().getPlainData(), is(secretBytes.getPlainData())); - assertThat(cert.getKeyStore().containsAlias("1"), is(true)); - assertThat(cert.getKeyStore().getCertificate("1").getType(), is("X.509")); - assertThat( - CredentialsNameProvider.name(cert), - is( - "EMAILADDRESS=me@myhost.mydomain, CN=pkcs12, O=Fort-Funston, L=SanFrancisco, ST=CA, C=US (my secret cert)")); + assertThat(cert.getKeyStore().containsAlias("test"), is(true)); + assertThat(cert.getKeyStore().getCertificate("test").getType(), is("X.509")); + assertThat(CredentialsNameProvider.name(cert), is("CN=Test, OU=Jenkins, O=JCasC, L=Delhi, ST=Delhi, C=IN (my secret cert)")); assertThat(cert.getScope(), is(CredentialsScope.GLOBAL)); } } From 5ec484f7dff54480c6fac747f4d7d1c4b0de6197 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Fri, 10 Apr 2026 13:48:01 +0530 Subject: [PATCH 3/4] Fix formatting --- .../java/io/jenkins/plugins/casc/CredentialsReadmeTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java b/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java index 05e2cb4721..f320d8641d 100644 --- a/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java +++ b/integrations/src/test/java/io/jenkins/plugins/casc/CredentialsReadmeTest.java @@ -145,7 +145,9 @@ public void testGlobalScopedCredentials() throws Exception { assertThat(keyStoreSource.getUploadedKeystore().getPlainData(), is(secretBytes.getPlainData())); assertThat(cert.getKeyStore().containsAlias("test"), is(true)); assertThat(cert.getKeyStore().getCertificate("test").getType(), is("X.509")); - assertThat(CredentialsNameProvider.name(cert), is("CN=Test, OU=Jenkins, O=JCasC, L=Delhi, ST=Delhi, C=IN (my secret cert)")); + assertThat( + CredentialsNameProvider.name(cert), + is("CN=Test, OU=Jenkins, O=JCasC, L=Delhi, ST=Delhi, C=IN (my secret cert)")); assertThat(cert.getScope(), is(CredentialsScope.GLOBAL)); } } From b41f48b62c5be56e3fca7e026d08846959f87624 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Fri, 10 Apr 2026 15:09:46 +0530 Subject: [PATCH 4/4] validate certificate subject CN --- .../plugins/casc/SystemCredentialsTest.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java b/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java index dffddb86c9..be9a712c43 100644 --- a/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java +++ b/integrations/src/test/java/io/jenkins/plugins/casc/SystemCredentialsTest.java @@ -24,6 +24,8 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.cert.X509Certificate; import java.util.Base64; import java.util.Collections; import java.util.Enumeration; @@ -91,11 +93,18 @@ public void configure_system_credentials() throws Exception { byte[] actualBytes = keyStoreSource.getUploadedKeystore().getPlainData(); assertThat("The bytes in Jenkins should be identical to the source file", actualBytes, equalTo(expectedBytes)); - Enumeration aliases = certImpl.getKeyStore().aliases(); + KeyStore keyStore = certImpl.getKeyStore(); + Enumeration aliases = keyStore.aliases(); assertThat("Keystore should not be empty", aliases.hasMoreElements(), equalTo(true)); - String firstAlias = aliases.nextElement(); - assertThat("Alias should contain a valid key", certImpl.getKeyStore().isKeyEntry(firstAlias), equalTo(true)); + String alias = aliases.nextElement(); + X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias); + assertThat(certificate, notNullValue()); + + String subject = certificate.getSubjectX500Principal().getName(); + assertThat(subject, containsString("CN=Test")); + + assertThat(certificate.getType(), equalTo("X.509")); List sshPrivateKeys = CredentialsProvider.lookupCredentials( BasicSSHUserPrivateKey.class, jenkins, ACL.SYSTEM, Collections.emptyList());