diff --git a/src/test/java/hudson/plugins/git/FIPSModeUrlCheckTest.java b/src/test/java/hudson/plugins/git/FIPSModeUrlCheckTest.java index 2d3ff5348b..2f59c84c59 100644 --- a/src/test/java/hudson/plugins/git/FIPSModeUrlCheckTest.java +++ b/src/test/java/hudson/plugins/git/FIPSModeUrlCheckTest.java @@ -100,84 +100,91 @@ public void testGitSCMSourceCheck() throws Throwable { @Test public void testUserRemoteConfigCheck() throws Throwable { - rule.then( r -> { - SystemCredentialsProvider.getInstance() - .getCredentials() - .add(new UsernamePasswordCredentialsImpl( - CredentialsScope.GLOBAL, "mycreds", null, "jenkins", "s3cr3t")); - SystemCredentialsProvider.getInstance().save(); - FreeStyleProject p = r.createProject(FreeStyleProject.class, "mbp"); - UserRemoteConfig.DescriptorImpl descriptor = - ExtensionList.lookupSingleton(UserRemoteConfig.DescriptorImpl.class); + Assume.assumeTrue(DockerClientFactory.instance().isDockerAvailable()); + // ssh with credentials all good + try (GitServerContainer containerUnderTest = + new GitServerContainer(GitServerVersions.V2_45.getDockerImageName()).withGitRepo("someRepo")) { + containerUnderTest.withClasspathResourceMapping( + "ssh-keys/id_rsa.pub", "/home/git/.ssh/authorized_keys", BindMode.READ_ONLY); + containerUnderTest.withClasspathResourceMapping( + "sshd_config", "/etc/ssh/sshd_config", BindMode.READ_ONLY); - { - // http with credentials rejected - FormValidation validation = descriptor.doCheckUrl(p, "mycreds", "http://github.com/olamy/beer"); - assertThat(validation.kind, is(FormValidation.Kind.ERROR)); - assertThat(validation.getMessage(), containsString(Messages.git_fips_url_notsecured())); - } + containerUnderTest.start(); - { - // https without credentials all good - FormValidation validation = descriptor.doCheckUrl(p, null, "https://github.com/jenkinsci/git-plugin"); - assertThat(validation.kind, is(FormValidation.Kind.OK)); - } + SshIdentity sshClientIdentity = new SshIdentity( + this.getClass() + .getClassLoader() + .getResourceAsStream("ssh-keys/id_rsa") + .readAllBytes(), + this.getClass() + .getClassLoader() + .getResourceAsStream("ssh-keys/id_rsa.pub") + .readAllBytes(), + new byte[0]); + byte[] privateKey = sshClientIdentity.getPrivateKey(); + byte[] passphrase = sshClientIdentity.getPassphrase(); + // ssh://git@localhost:33011/srv/git/someRepo.git + // we don't want the user part of the uri or jgit will use this user + // and we want to be sure to test our implementation with dynamic user + final String repoUrl = StringUtils.remove(containerUnderTest.getGitRepoURIAsSSH().toString(), "git@"); + rule.then( r -> { + BasicSSHUserPrivateKey sshUserPrivateKey = getBasicSSHUserPrivateKey(privateKey, passphrase); + SystemCredentialsProvider.getInstance().getCredentials().add(sshUserPrivateKey); + SystemCredentialsProvider.getInstance() + .getCredentials() + .add(new UsernamePasswordCredentialsImpl( + CredentialsScope.GLOBAL, "mycreds", null, "jenkins", "s3cr3t")); + SystemCredentialsProvider.getInstance().save(); + FreeStyleProject p = r.createProject(FreeStyleProject.class, "mbp"); + UserRemoteConfig.DescriptorImpl descriptor = + ExtensionList.lookupSingleton(UserRemoteConfig.DescriptorImpl.class); - Assume.assumeTrue(DockerClientFactory.instance().isDockerAvailable()); + { + // http with credentials rejected + FormValidation validation = descriptor.doCheckUrl(p, "mycreds", "http://github.com/olamy/beer"); + assertThat(validation.kind, is(FormValidation.Kind.ERROR)); + assertThat(validation.getMessage(), containsString(Messages.git_fips_url_notsecured())); + } - { - // ssh with credentials all good - try (GitServerContainer containerUnderTest = - new GitServerContainer(GitServerVersions.V2_45.getDockerImageName()).withGitRepo("someRepo")) { - containerUnderTest.withClasspathResourceMapping( - "ssh-keys/id_rsa.pub", "/home/git/.ssh/authorized_keys", BindMode.READ_ONLY); - containerUnderTest.withClasspathResourceMapping( - "sshd_config", "/etc/ssh/sshd_config", BindMode.READ_ONLY); + { + // https without credentials all good + FormValidation validation = descriptor.doCheckUrl(p, null, "https://github.com/jenkinsci/git-plugin"); + assertThat(validation.kind, is(FormValidation.Kind.OK)); + } - containerUnderTest.start(); + Assume.assumeTrue(DockerClientFactory.instance().isDockerAvailable()); - SshIdentity sshClientIdentity = new SshIdentity( - this.getClass() - .getClassLoader() - .getResourceAsStream("ssh-keys/id_rsa") - .readAllBytes(), - this.getClass() - .getClassLoader() - .getResourceAsStream("ssh-keys/id_rsa.pub") - .readAllBytes(), - new byte[0]); - BasicSSHUserPrivateKey sshUserPrivateKey = getBasicSSHUserPrivateKey(sshClientIdentity); - SystemCredentialsProvider.getInstance().getCredentials().add(sshUserPrivateKey); - String repoUrl = containerUnderTest.getGitRepoURIAsSSH().toString(); - // ssh://git@localhost:33011/srv/git/someRepo.git - // we don't want the user part of the uri or jgit will use this user - // and we want to be sure to test our implementation with dynamic user - repoUrl = StringUtils.remove(repoUrl, "git@"); + { FormValidation validation = descriptor.doCheckUrl(p, sshUserPrivateKey.getId(), repoUrl); assertThat(validation.kind, is(FormValidation.Kind.OK)); } - } + }); + } + + // http without credentials all good + try (GitHttpServerContainer containerUnderTest = + new GitHttpServerContainer(GitServerVersions.V2_45.getDockerImageName())) { + containerUnderTest.start(); + String repoUri = containerUnderTest.getGitRepoURIAsHttp().toString(); + rule.then( r -> { + FreeStyleProject p = r.createProject(FreeStyleProject.class, "mbp2"); + UserRemoteConfig.DescriptorImpl descriptor = + ExtensionList.lookupSingleton(UserRemoteConfig.DescriptorImpl.class); + // no TLS is fine without credentials + FormValidation validation = descriptor.doCheckUrl( + p, null, repoUri); + assertThat(validation.kind, is(FormValidation.Kind.OK)); + }); + } - { - // http without credentials all good - try (GitHttpServerContainer containerUnderTest = - new GitHttpServerContainer(GitServerVersions.V2_45.getDockerImageName())) { - containerUnderTest.start(); - // no TLS is fine without credentials - FormValidation validation = descriptor.doCheckUrl( - p, null, containerUnderTest.getGitRepoURIAsHttp().toString()); - assertThat(validation.kind, is(FormValidation.Kind.OK)); - } - } - }); } - private static BasicSSHUserPrivateKey getBasicSSHUserPrivateKey(SshIdentity sshIdentity) { + private static BasicSSHUserPrivateKey getBasicSSHUserPrivateKey(byte[] privateKey, byte[] passphrase) { BasicSSHUserPrivateKey.PrivateKeySource privateKeySource = new BasicSSHUserPrivateKey.PrivateKeySource() { @NotNull @Override public List getPrivateKeys() { - return List.of(new String(sshIdentity.getPrivateKey())); + return List.of(new String(privateKey)); } }; return new BasicSSHUserPrivateKey( @@ -185,112 +192,111 @@ public List getPrivateKeys() { "some-id", "git", privateKeySource, - new String(sshIdentity.getPassphrase()), + new String(passphrase), "description"); } @Test public void gitStepTLSCheck() throws Throwable { - rule.then( r -> { - WorkflowJob p = r.createProject(WorkflowJob.class, "some project"); - { - // http with creds rejected - p.setDefinition(new CpsFlowDefinition( - "node {\n" + - " dir('foo') {\n" + - " git url: 'http://foo.com/beer.git', credentialsId: 'yup'\n" + - " }\n" + - "}", true)); - WorkflowRun b = r.buildAndAssertStatus(Result.FAILURE, p); - r.assertLogContains(Messages.git_fips_url_notsecured(), b); - } - - Assume.assumeTrue(DockerClientFactory.instance().isDockerAvailable()); - - { - // http without creds not rejected - try (GitHttpServerContainer containerUnderTest = - new GitHttpServerContainer(GitServerVersions.V2_45.getDockerImageName())) { - containerUnderTest.start(); - // need to have at least on revision to avoid build failure - File tmp = directory.newFolder(); - Git git = Git.cloneRepository() - .setURI(containerUnderTest.getGitRepoURIAsHttp().toString()) - .setDirectory(tmp) - .call(); - StoredConfig storedConfig = git.getRepository().getConfig(); - storedConfig.setBoolean("commit", null, "gpgsign", false); - storedConfig.setBoolean("tag", null, "gpgSign", false); - storedConfig.save(); - Files.writeString(new File(tmp, "foo.txt").toPath(), "nothing too see here"); - git.add().addFilepattern("foo.txt").call(); - git.commit().setMessage("add foo").call(); - git.push().call(); + Assume.assumeTrue(DockerClientFactory.instance().isDockerAvailable()); + try (GitHttpServerContainer containerUnderTest = + new GitHttpServerContainer(GitServerVersions.V2_45.getDockerImageName())) { + containerUnderTest.start(); + // need to have at least on revision to avoid build failure + File tmp = directory.newFolder(); + Git git = Git.cloneRepository() + .setURI(containerUnderTest.getGitRepoURIAsHttp().toString()) + .setDirectory(tmp) + .call(); + StoredConfig storedConfig = git.getRepository().getConfig(); + storedConfig.setBoolean("commit", null, "gpgsign", false); + storedConfig.setBoolean("tag", null, "gpgSign", false); + storedConfig.save(); + Files.writeString(new File(tmp, "foo.txt").toPath(), "nothing too see here"); + git.add().addFilepattern("foo.txt").call(); + git.commit().setMessage("add foo").call(); + git.push().call(); + String repoUri = containerUnderTest.getGitRepoURIAsHttp().toString(); + rule.then(r -> { + WorkflowJob p = r.createProject(WorkflowJob.class, "some project"); + { + // http with creds rejected + p.setDefinition(new CpsFlowDefinition( + "node {\n" + + " dir('foo') {\n" + + " git url: 'http://foo.com/beer.git', credentialsId: 'yup'\n" + + " }\n" + + "}", true)); + WorkflowRun b = r.buildAndAssertStatus(Result.FAILURE, p); + r.assertLogContains(Messages.git_fips_url_notsecured(), b); + } + { + // http without creds not rejected p.setDefinition(new CpsFlowDefinition( "node {\n" + " dir('foo') {\n" + - " git url: '" + containerUnderTest.getGitRepoURIAsHttp() + "', changelog: false\n" + + " git url: '" + repoUri + "', changelog: false\n" + " }\n" + "}", true)); - WorkflowRun b = r.buildAndAssertStatus(Result.SUCCESS, p); + r.buildAndAssertStatus(Result.SUCCESS, p); } - } - }); + }); + } } @Test public void checkoutStepTLSCheck() throws Throwable { - rule.then( r -> { - WorkflowJob p = r.createProject(WorkflowJob.class, "some project"); - { - // http with creds rejected - // Intentionally using modern syntax to check compatibility - p.setDefinition(new CpsFlowDefinition( - "node {\n" + - " dir('foo') {\n" + - " checkout scmGit(branches: [[name: 'master']],\n" + - " userRemoteConfigs: [[credentialsId: 'foocreds', url: 'http://github.com/foo/beer.git']])\n" + - " }\n" + - "}", true)); - WorkflowRun b = r.buildAndAssertStatus(Result.FAILURE, p); - r.assertLogContains(Messages.git_fips_url_notsecured(), b); - } - - Assume.assumeTrue(DockerClientFactory.instance().isDockerAvailable()); - - { - // http without creds not rejected - try (GitHttpServerContainer containerUnderTest = - new GitHttpServerContainer(GitServerVersions.V2_45.getDockerImageName())) { - containerUnderTest.start(); - // need to have at least on revision to avoid build failure - File tmp = directory.newFolder(); - Git git = Git.cloneRepository() - .setURI(containerUnderTest.getGitRepoURIAsHttp().toString()) - .setDirectory(tmp) - .call(); - StoredConfig storedConfig = git.getRepository().getConfig(); - storedConfig.setBoolean("commit", null, "gpgsign", false); - storedConfig.setBoolean("tag", null, "gpgSign", false); - storedConfig.save(); - Files.writeString(new File(tmp, "foo.txt").toPath(), "nothing too see here"); - git.add().addFilepattern("foo.txt").call(); - git.commit().setMessage("add foo").call(); - git.push().call(); + Assume.assumeTrue(DockerClientFactory.instance().isDockerAvailable()); + try (GitHttpServerContainer containerUnderTest = + new GitHttpServerContainer(GitServerVersions.V2_45.getDockerImageName())) { + containerUnderTest.start(); + // need to have at least on revision to avoid build failure + File tmp = directory.newFolder(); + Git git = Git.cloneRepository() + .setURI(containerUnderTest.getGitRepoURIAsHttp().toString()) + .setDirectory(tmp) + .call(); + StoredConfig storedConfig = git.getRepository().getConfig(); + storedConfig.setBoolean("commit", null, "gpgsign", false); + storedConfig.setBoolean("tag", null, "gpgSign", false); + storedConfig.save(); + Files.writeString(new File(tmp, "foo.txt").toPath(), "nothing too see here"); + git.add().addFilepattern("foo.txt").call(); + git.commit().setMessage("add foo").call(); + git.push().call(); + String repoUri = containerUnderTest.getGitRepoURIAsHttp().toString(); + rule.then(r -> { + WorkflowJob p = r.createProject(WorkflowJob.class, "some project"); + { + // http with creds rejected + // Intentionally using modern syntax to check compatibility + p.setDefinition(new CpsFlowDefinition( + "node {\n" + + " dir('foo') {\n" + + " checkout scmGit(branches: [[name: 'master']],\n" + + " userRemoteConfigs: [[credentialsId: 'foocreds', url: 'http://github.com/foo/beer.git']])\n" + + " }\n" + + "}", true)); + WorkflowRun b = r.buildAndAssertStatus(Result.FAILURE, p); + r.assertLogContains(Messages.git_fips_url_notsecured(), b); + } + { + // http without creds not rejected // Intentionally using old syntax to check compatibility p.setDefinition(new CpsFlowDefinition( "node {\n" + " dir('foo') {\n" + " checkout([$class: 'GitSCM',\n" + " branches: [[name: '*/master']],\n" + - " userRemoteConfigs: [[url: '" + containerUnderTest.getGitRepoURIAsHttp() + "']]])\n" + + " userRemoteConfigs: [[url: '" + repoUri + "']]])\n" + " }\n" + "}", true)); - WorkflowRun b = r.buildAndAssertStatus(Result.SUCCESS, p); + r.buildAndAssertStatus(Result.SUCCESS, p); } - } - }); + + }); + } } } diff --git a/src/test/java/jenkins/plugins/git/FIPSModeSCMSourceTest.java b/src/test/java/jenkins/plugins/git/FIPSModeSCMSourceTest.java index 6d2c8b8744..2d8b38f348 100644 --- a/src/test/java/jenkins/plugins/git/FIPSModeSCMSourceTest.java +++ b/src/test/java/jenkins/plugins/git/FIPSModeSCMSourceTest.java @@ -1,20 +1,14 @@ package jenkins.plugins.git; +import hudson.logging.LogRecorder; import hudson.model.TaskListener; import hudson.plugins.git.GitException; import hudson.util.StreamTaskListener; -import hudson.util.VersionNumber; -import jenkins.security.FIPS140; -import org.junit.Assume; -import org.junit.BeforeClass; -import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; -import org.jvnet.hudson.test.FlagRule; -import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.LoggerRule; import org.jvnet.hudson.test.RealJenkinsRule; +import java.util.List; import java.util.logging.Level; import static org.hamcrest.CoreMatchers.containsString; @@ -26,39 +20,22 @@ public class FIPSModeSCMSourceTest { @Rule public RealJenkinsRule rule = new RealJenkinsRule().omitPlugins("eddsa-api", "trilead-api", "git-tag-message") - .javaOptions("-Djenkins.security.FIPS140.COMPLIANCE=true"); - - @Rule - public LoggerRule logger = new LoggerRule(); - - @BeforeClass - public static void checkJenkinsVersion() { - /* TODO: Remove when failing tests are fixed */ - /* JenkinsRule throws an exception before any test method is executed */ - /* Guess the version number from the Maven command line property */ - /* Default version number copied from pom.xml jenkins.version */ - VersionNumber jenkinsFailsTests = new VersionNumber("2.461"); - VersionNumber jenkinsVersion = new VersionNumber(System.getProperty("jenkins.version", "2.440.3")); - /** Skip tests to avoid delaying plugin BOM and Spring Security 6.x Upgrade */ - boolean skipTests = false; - if (jenkinsVersion.isNewerThanOrEqualTo(jenkinsFailsTests)) { - skipTests = true; - } - Assume.assumeFalse(skipTests); - } + .javaOptions("-Djenkins.security.FIPS140.COMPLIANCE=true") + .withLogger(AbstractGitSCMSource.class, Level.SEVERE); @Test @SuppressWarnings("deprecation") public void remotesAreNotFetchedTest() throws Throwable { - rule.then( r -> { GitSCMSource source = new GitSCMSource("http://insecure-repo"); - // Credentials are null, so we should have no FIPS error - logger.record(AbstractGitSCMSource.class, Level.SEVERE); - logger.capture(10); TaskListener listener = StreamTaskListener.fromStderr(); assertThrows("expected exception as repo doesn't exist", GitException.class, () -> source.fetch(listener)); - assertThat("We should no see the error in the logs", logger.getMessages().size(), is(0)); + + LogRecorder logRecorder = new LogRecorder(AbstractGitSCMSource.class.getName()); + LogRecorder.Target target = new LogRecorder.Target(AbstractGitSCMSource.class.getName(), Level.SEVERE); + logRecorder.setLoggers(List.of(target)); + r.jenkins.getLog().getRecorders().add(logRecorder); + assertThat("We should no see the error in the logs", logRecorder.getLogRecords().size(), is(0)); // Using creds we should be getting an exception Throwable exception = assertThrows("We're not saving creds", IllegalArgumentException.class, () -> source.setCredentialsId("cred-id")); @@ -69,7 +46,7 @@ public void remotesAreNotFetchedTest() throws Throwable { // This would be equivalent to a user manually adding credentials to config.xml GitSCMSource anotherSource = new GitSCMSource("fake", "http://insecure", "credentialsId", "", "", true); exception = assertThrows("fetch was interrupted so no credential was leaked", IllegalArgumentException.class, () -> anotherSource.fetch(listener)); - assertThat("We should have a severe log indicating the error", logger.getMessages().size(), is(1)); + assertThat("We should have a severe log indicating the error", logRecorder.getLogRecords().size(), is(1)); assertThat("Exception indicates problem", exception.getMessage(), containsString("FIPS requires a secure channel")); }); }