diff --git a/src/main/java/hudson/tasks/SMTPAuthentication.java b/src/main/java/hudson/tasks/SMTPAuthentication.java index da921c42..c8f6010a 100644 --- a/src/main/java/hudson/tasks/SMTPAuthentication.java +++ b/src/main/java/hudson/tasks/SMTPAuthentication.java @@ -3,9 +3,15 @@ import hudson.Extension; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; +import hudson.util.FormValidation; import hudson.util.Secret; +import jenkins.security.FIPS140; import org.kohsuke.stapler.DataBoundConstructor; import hudson.Util; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +import java.io.ObjectStreamException; /** * @author Nicolas De Loof @@ -20,6 +26,9 @@ public class SMTPAuthentication extends AbstractDescribableImpl { @@ -37,5 +53,13 @@ public static class DescriptorImpl extends Descriptor { public String getDisplayName() { return "Use SMTP Authentication"; } + + @RequirePOST + public FormValidation doCheckPassword(@QueryParameter Secret password) { + if (FIPS140.useCompliantAlgorithms() && Secret.toString(password).length() < 14) { + return FormValidation.error(jenkins.plugins.mailer.tasks.i18n.Messages.Mailer_SmtpPassNotFipsCompliant()); + } + return FormValidation.ok(); + } } } diff --git a/src/main/resources/jenkins/plugins/mailer/tasks/i18n/Messages.properties b/src/main/resources/jenkins/plugins/mailer/tasks/i18n/Messages.properties index ad728e9a..5a9fe43d 100644 --- a/src/main/resources/jenkins/plugins/mailer/tasks/i18n/Messages.properties +++ b/src/main/resources/jenkins/plugins/mailer/tasks/i18n/Messages.properties @@ -49,6 +49,6 @@ Mailer.TestMail.Subject=Test email #{0} Mailer.TestMail.Content=This is test email #{0} sent from {1} Mailer.InsecureAuthWarning=For security when using authentication it is recommended to enable either TLS or SSL Mailer.InsecureAuthError=Authentication requires either TLS or SSL to be enabled - +Mailer.SmtpPassNotFipsCompliant=When running in FIPS compliance mode, the password must be at least 14 characters long MailCommand.ShortDescription=\ Reads stdin and sends that out as an e-mail. diff --git a/src/test/java/jenkins/plugins/mailer/FipsModeTest.java b/src/test/java/jenkins/plugins/mailer/FipsModeTest.java new file mode 100644 index 00000000..c6f92504 --- /dev/null +++ b/src/test/java/jenkins/plugins/mailer/FipsModeTest.java @@ -0,0 +1,108 @@ +package jenkins.plugins.mailer; + +import hudson.ExtensionList; +import hudson.diagnosis.OldDataMonitor; +import hudson.tasks.Mailer; +import io.jenkins.plugins.casc.ConfigurationAsCode; +import io.jenkins.plugins.casc.ConfiguratorException; +import org.htmlunit.WebResponse; +import org.htmlunit.html.HtmlForm; +import org.htmlunit.html.HtmlPage; +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.RealJenkinsRule; +import org.jvnet.hudson.test.recipes.LocalData; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.io.Serializable; +import java.net.URL; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +public class FipsModeTest { + public static final String SHORT_PWD_ERROR_MESSAGE = "When running in FIPS compliance mode, the password must be at least 14 characters long"; + @Rule + public RealJenkinsRule r = new RealJenkinsRule().javaOptions("-Djenkins.security.FIPS140.COMPLIANCE=true").withDebugPort(5008); + + @Test @LocalData + public void testBlowsUpOnStart() throws Throwable { + r.then(FipsModeTest::verifyOldData); + } + + static void verifyOldData(JenkinsRule j) throws Throwable { + OldDataMonitor monitor = ExtensionList.lookupSingleton(OldDataMonitor.class); + Mailer.DescriptorImpl descriptor = Mailer.descriptor(); + assertNull(descriptor.getAuthentication()); + OldDataMonitor.VersionRange versionRange = monitor.getData().get(descriptor); + assertNotNull(versionRange); + assertThat(versionRange.extra, containsString("Mailer SMTP password: " + SHORT_PWD_ERROR_MESSAGE)); + } + + @Test + public void testConfig() throws Throwable { + r.then(FipsModeTest::_testConfig); + } + + public static void _testConfig(JenkinsRule j) throws Exception { + Assume.assumeThat("TODO the form elements for email-ext have the same names", j.getPluginManager().getPlugin("email-ext"), is(nullValue())); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + HtmlPage cp = wc.goTo("configure"); + wc.setThrowExceptionOnFailingStatusCode(false); + HtmlForm form = cp.getFormByName("config"); + + form.getInputByName("_.smtpHost").setValue("acme.com"); + form.getInputByName("_.defaultSuffix").setValue("@acme.com"); + form.getInputByName("_.authentication").setChecked(true); + form.getInputByName("_.username").setValue("user"); + form.getInputByName("_.password").setValue("pass"); + wc.waitForBackgroundJavaScript(1000); + assertThat(form.getTextContent(), containsString(SHORT_PWD_ERROR_MESSAGE)); + HtmlPage page = j.submit(form); + WebResponse webResponse = page.getWebResponse(); + assertNotEquals(200, webResponse.getStatusCode()); + assertThat(webResponse.getContentAsString(), containsString(SHORT_PWD_ERROR_MESSAGE)); + } + + } + + @Test @LocalData + public void casc() throws Throwable { + URL url = getClass().getResource("bad_fips_casc.yaml"); + r.then(new _casc(url.toString())); + } + + public static class _casc implements RealJenkinsRule.Step2 { + String resUrl; + + public _casc(String resUrl) { + this.resUrl = resUrl; + } + + @Override + public Serializable run(JenkinsRule r) throws Throwable { + try { + ConfigurationAsCode.get().configure(resUrl); + fail("The configuration should fail."); + } catch (ConfiguratorException e) { + Throwable cause = e.getCause(); + assertNotNull(cause); + cause = cause.getCause(); + assertThat(cause, instanceOf(IllegalArgumentException.class)); + assertThat(cause.getMessage(), containsString(SHORT_PWD_ERROR_MESSAGE)); + } + return null; + } + } + +} diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/hudson.util.Secret b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/hudson.util.Secret new file mode 100644 index 00000000..c9e4e308 --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/hudson.util.Secret @@ -0,0 +1,2 @@ +Ħ5uM3|Mn+^E;jy;qJKMJ- mKE`wT݋ȴyMG^,m +( gr,T̉028WN 2 rDiE} sa=<0 C]S~0TQl2|@ ּr&[Q _]殣Kv`]ҋ r0XnS= uOOdbזo(Z7㘎>0`|j&C \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/jenkins.model.Jenkins.crumbSalt b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/jenkins.model.Jenkins.crumbSalt new file mode 100644 index 00000000..f05abb66 --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/jenkins.model.Jenkins.crumbSalt @@ -0,0 +1,2 @@ +8eJVWO2 t.˿u= + \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/master.key b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/master.key new file mode 100644 index 00000000..2f2aab79 --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/master.key @@ -0,0 +1 @@ +98e782a367e04fbffad76f28cc32eb0b7757ff91bd31656cdbcc42a0acf3b92f925b0abf9fbbbd51e7d79bace99d182bb4b337a485b519a73e9089c3e6a78184aeb514731bbb578c205879187411f7e8f709f183c5fdf098155ef56362636c4a5762aa831b6d5d82deb1adfc7f306139e6d480093f639484ecb3f9143bc56097 \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/org.jenkinsci.main.modules.instance_identity.InstanceIdentity.KEY b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/org.jenkinsci.main.modules.instance_identity.InstanceIdentity.KEY new file mode 100644 index 00000000..d8614fd3 Binary files /dev/null and b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/casc/secrets/org.jenkinsci.main.modules.instance_identity.InstanceIdentity.KEY differ diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/hudson.tasks.Mailer.xml b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/hudson.tasks.Mailer.xml new file mode 100644 index 00000000..a2fb6fbf --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/hudson.tasks.Mailer.xml @@ -0,0 +1,11 @@ + + + + + smtpusr + {AQAAABAAAAAQcrlS6VKIiJEH44B7K+oxdcs0j4LcZbicOnwQK0Ivlfg=} + + true + false + UTF-8 + \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/jenkins.model.JenkinsLocationConfiguration.xml b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/jenkins.model.JenkinsLocationConfiguration.xml new file mode 100644 index 00000000..bae8aa3c --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/jenkins.model.JenkinsLocationConfiguration.xml @@ -0,0 +1,5 @@ + + + address not configured yet <nobody@nowhere> + http://localhost:8080/jenkins/ + \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/hudson.util.Secret b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/hudson.util.Secret new file mode 100644 index 00000000..c9e4e308 --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/hudson.util.Secret @@ -0,0 +1,2 @@ +Ħ5uM3|Mn+^E;jy;qJKMJ- mKE`wT݋ȴyMG^,m +( gr,T̉028WN 2 rDiE} sa=<0 C]S~0TQl2|@ ּr&[Q _]殣Kv`]ҋ r0XnS= uOOdbזo(Z7㘎>0`|j&C \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/jenkins.model.Jenkins.crumbSalt b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/jenkins.model.Jenkins.crumbSalt new file mode 100644 index 00000000..f05abb66 --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/jenkins.model.Jenkins.crumbSalt @@ -0,0 +1,2 @@ +8eJVWO2 t.˿u= + \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/master.key b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/master.key new file mode 100644 index 00000000..2f2aab79 --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/master.key @@ -0,0 +1 @@ +98e782a367e04fbffad76f28cc32eb0b7757ff91bd31656cdbcc42a0acf3b92f925b0abf9fbbbd51e7d79bace99d182bb4b337a485b519a73e9089c3e6a78184aeb514731bbb578c205879187411f7e8f709f183c5fdf098155ef56362636c4a5762aa831b6d5d82deb1adfc7f306139e6d480093f639484ecb3f9143bc56097 \ No newline at end of file diff --git a/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/org.jenkinsci.main.modules.instance_identity.InstanceIdentity.KEY b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/org.jenkinsci.main.modules.instance_identity.InstanceIdentity.KEY new file mode 100644 index 00000000..d8614fd3 Binary files /dev/null and b/src/test/resources/jenkins/plugins/mailer/FipsModeTest/testBlowsUpOnStart/secrets/org.jenkinsci.main.modules.instance_identity.InstanceIdentity.KEY differ diff --git a/src/test/resources/jenkins/plugins/mailer/bad_fips_casc.yaml b/src/test/resources/jenkins/plugins/mailer/bad_fips_casc.yaml new file mode 100644 index 00000000..e1b06d2e --- /dev/null +++ b/src/test/resources/jenkins/plugins/mailer/bad_fips_casc.yaml @@ -0,0 +1,8 @@ +unclassified: + mailer: + authentication: + password: "{AQAAABAAAAAQcrlS6VKIiJEH44B7K+oxdcs0j4LcZbicOnwQK0Ivlfg=}" + username: "smtpusr" + charset: "UTF-8" + useSsl: true + useTls: false \ No newline at end of file