diff --git a/src/main/java/org/opensearch/security/tools/democonfig/Installer.java b/src/main/java/org/opensearch/security/tools/democonfig/Installer.java index 500c65f825..68bc4d7cbf 100644 --- a/src/main/java/org/opensearch/security/tools/democonfig/Installer.java +++ b/src/main/java/org/opensearch/security/tools/democonfig/Installer.java @@ -186,7 +186,7 @@ void readOptions(String[] args) { * Prints the help menu when -h option is passed */ void showHelp() { - formatter.printHelp("install_demo_configuration.sh", options, true); + formatter.printHelp("install_demo_configuration" + FILE_EXTENSION, options, true); System.exit(0); } @@ -438,4 +438,12 @@ void finishScriptExecution() { System.out.println(e.getMessage()); } } + + /** + * FOR TESTS ONLY + * resets the installer state to allow testing with fresh instance for the next test. + */ + static void resetInstance() { + instance = null; + } } diff --git a/src/test/java/org/opensearch/security/sanity/tests/InvalidAdminPasswordIT.java b/src/test/java/org/opensearch/security/sanity/tests/InvalidAdminPasswordIT.java new file mode 100644 index 0000000000..60d2eee138 --- /dev/null +++ b/src/test/java/org/opensearch/security/sanity/tests/InvalidAdminPasswordIT.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.sanity.tests; + +import org.hamcrest.MatcherAssert; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +public class InvalidAdminPasswordIT extends SecurityRestTestCase { + + static String currentPasswordVariable = System.getProperty("password"); + + @BeforeClass + public static void setUpAdminAsPasswordVariable() { + System.setProperty("password", "admin"); + } + + @AfterClass + public static void restorePasswordProperty() { + System.setProperty("password", currentPasswordVariable); + } + + @Test + public void testAdminCredentials_adminAsPassword_shouldFail() throws Exception { + try { + client().performRequest(new Request("GET", "")); + } catch (ResponseException e) { + Response res = e.getResponse(); + MatcherAssert.assertThat(res.getStatusLine().getStatusCode(), is(equalTo(401))); + MatcherAssert.assertThat(res.getStatusLine().getReasonPhrase(), is(equalTo("Unauthorized"))); + } + } +} diff --git a/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java index 8987744d58..97937a2c52 100644 --- a/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java +++ b/src/test/java/org/opensearch/security/sanity/tests/SingleClusterSanityIT.java @@ -19,7 +19,11 @@ import org.hamcrest.MatcherAssert; import org.junit.Test; +import org.opensearch.client.Request; +import org.opensearch.client.Response; + import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -34,6 +38,13 @@ public void testSecurityPluginInstallation() throws Exception { verifyPluginInstallationOnAllNodes(); } + @Test + public void testAdminCredentials_validAdminPassword_shouldSucceed() throws Exception { + Response response = client().performRequest(new Request("GET", "")); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), is(equalTo(200))); + MatcherAssert.assertThat(response.getStatusLine().getReasonPhrase(), is(equalTo("OK"))); + } + private void verifyPluginInstallationOnAllNodes() throws Exception { Map> nodesInCluster = (Map>) getAsMapByAdmin("_nodes").get("nodes"); diff --git a/src/test/java/org/opensearch/security/tools/democonfig/CertificateGeneratorTests.java b/src/test/java/org/opensearch/security/tools/democonfig/CertificateGeneratorTests.java new file mode 100644 index 0000000000..58cf6d1368 --- /dev/null +++ b/src/test/java/org/opensearch/security/tools/democonfig/CertificateGeneratorTests.java @@ -0,0 +1,178 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.tools.democonfig; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.time.Instant; +import java.time.LocalDate; +import java.time.Period; +import java.util.Base64; +import java.util.Date; +import java.util.TimeZone; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.opensearch.security.tools.democonfig.util.NoExitSecurityManager; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.createDirectory; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.deleteDirectoryRecursive; +import static org.junit.Assert.fail; + +public class CertificateGeneratorTests { + + private static Installer installer; + + @Before + public void setUp() { + installer = Installer.getInstance(); + installer.buildOptions(); + installer.OPENSEARCH_CONF_DIR = System.getProperty("user.dir") + File.separator + "test-conf"; + createDirectory(installer.OPENSEARCH_CONF_DIR); + } + + @After + public void tearDown() { + deleteDirectoryRecursive(installer.OPENSEARCH_CONF_DIR); + Installer.resetInstance(); + } + + @Test + public void testCreateDemoCertificates() throws Exception { + CertificateGenerator certificateGenerator = new CertificateGenerator(installer); + Certificates[] certificatesArray = Certificates.values(); + + certificateGenerator.createDemoCertificates(); + + // root-ca.pem, esnode.pem, esnode-key.pem, kirk.pem, kirk-key.pem + int expectedNumberOfCertificateFiles = 5; + + int certsFound = 0; + + for (Certificates cert : certificatesArray) { + String certFilePath = installer.OPENSEARCH_CONF_DIR + File.separator + cert.getFileName(); + File certFile = new File(certFilePath); + assertThat(certFile.exists(), is(equalTo(true))); + assertThat(certFile.canRead(), is(equalTo(true))); + + if (certFilePath.endsWith("-key.pem")) { + checkPrivateKeyValidity(certFilePath); + } else { + checkCertificateValidity(certFilePath); + } + + // increment a count since a valid certificate was found + certsFound++; + } + + assertThat(certsFound, equalTo(expectedNumberOfCertificateFiles)); + } + + @Test + public void testCreateDemoCertificates_invalidPath() { + installer.OPENSEARCH_CONF_DIR = "invalidPath"; + CertificateGenerator certificateGenerator = new CertificateGenerator(installer); + try { + System.setSecurityManager(new NoExitSecurityManager()); + certificateGenerator.createDemoCertificates(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + } + + private static void checkCertificateValidity(String certPath) throws Exception { + try (FileInputStream certInputStream = new FileInputStream(certPath)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate certificate = cf.generateCertificate(certInputStream); + + if (certificate instanceof X509Certificate) { + X509Certificate x509Certificate = (X509Certificate) certificate; + Date expiryDate = x509Certificate.getNotAfter(); + Instant expiry = expiryDate.toInstant(); + + Period duration = getPeriodBetween(x509Certificate.getNotBefore().toInstant(), expiry); + if (certPath.endsWith("-ca.pem")) { + // root-ca.pem is already expired as the validity is only 30 days from generation + // so we just check interval to be of 30 days + assertThat(duration.getDays(), equalTo(30)); + return; + } + + // we check that cert is valid for total of ~10 yrs + // we don't check days as leaps years may cause flaky-ness + assertThat(duration.getYears(), equalTo(9)); + assertThat(duration.getMonths(), equalTo(11)); + + x509Certificate.checkValidity(); + verifyExpiryAtLeastAYearFromNow(expiry); + + assertThat(x509Certificate.getSigAlgName(), is(equalTo("SHA256withRSA"))); + } + } + } + + private static void verifyExpiryAtLeastAYearFromNow(Instant expiry) { + Period gap = getPeriodBetween(Instant.now(), expiry); + assertThat(gap.getYears(), greaterThanOrEqualTo(1)); + } + + private static Period getPeriodBetween(Instant start, Instant end) { + LocalDate startDate = LocalDate.ofInstant(start, TimeZone.getTimeZone("EDT").toZoneId()); + LocalDate endDate = LocalDate.ofInstant(end, TimeZone.getTimeZone("EDT").toZoneId()); + + return Period.between(startDate, endDate); + } + + private void checkPrivateKeyValidity(String keyPath) { + try { + String pemContent = readPEMFile(keyPath); + + String base64Data = pemContent.replaceAll("-----BEGIN PRIVATE KEY-----|-----END PRIVATE KEY-----", "").replaceAll("\\s", ""); + + byte[] keyBytes = Base64.getDecoder().decode(base64Data); + KeyFactory kf = KeyFactory.getInstance("RSA"); + PrivateKey key = kf.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); + assertThat(key.getFormat(), is(equalTo("PKCS#8"))); + assertThat(key.getAlgorithm(), is(equalTo("RSA"))); + assertThat(key.isDestroyed(), is(equalTo(false))); + } catch (Exception e) { + fail("Error checking key validity: " + e.getMessage()); + } + } + + private static String readPEMFile(String pemFilePath) throws Exception { + StringBuilder pemContent = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) { + String line; + while ((line = reader.readLine()) != null) { + pemContent.append(line).append("\n"); + } + } + return pemContent.toString(); + } +} diff --git a/src/test/java/org/opensearch/security/tools/democonfig/InstallerTests.java b/src/test/java/org/opensearch/security/tools/democonfig/InstallerTests.java new file mode 100644 index 0000000000..4e64fdea72 --- /dev/null +++ b/src/test/java/org/opensearch/security/tools/democonfig/InstallerTests.java @@ -0,0 +1,519 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.tools.democonfig; + +// CS-SUPPRESS-SINGLE: RegexpSingleline extension key-word is used in file ext variable +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.opensearch.security.tools.democonfig.util.NoExitSecurityManager; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.opensearch.security.tools.democonfig.Installer.RPM_DEB_OPENSEARCH_HOME; +import static org.opensearch.security.tools.democonfig.Installer.printScriptHeaders; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.createDirectory; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.createFile; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.deleteDirectoryRecursive; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +public class InstallerTests { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final InputStream originalIn = System.in; + + private static Installer installer; + + @Before + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + installer = Installer.getInstance(); + installer.buildOptions(); + } + + @After + public void restoreStreams() { + System.setOut(originalOut); + System.setIn(originalIn); + Installer.resetInstance(); + } + + @Test + public void testPrintScriptHeaders() { + printScriptHeaders(); + + String expectedOutput = "### OpenSearch Security Demo Installer" + + System.lineSeparator() + + "### ** Warning: Do not use on production or public reachable systems **" + + System.lineSeparator(); + assertThat(outContent.toString(), equalTo(expectedOutput)); + } + + @Test + public void testReadOptions_withoutHelpOption() { + // All options except Help `-h` + String[] validOptions = { "/scriptDir", "-y", "-i", "-c", "-s", "-t" }; + installer.readOptions(validOptions); + + assertThat(installer.SCRIPT_DIR, equalTo("/scriptDir")); + assertThat(installer.assumeyes, is(true)); + assertThat(installer.initsecurity, is(true)); + assertThat(installer.cluster_mode, is(true)); + assertThat(installer.skip_updates, equalTo(0)); + assertThat(installer.environment, equalTo(ExecutionEnvironment.TEST)); + } + + @Test + public void testReadOptions_help() { + try { + System.setSecurityManager(new NoExitSecurityManager()); + String[] helpOption = { "/scriptDir", "-h" }; + installer.readOptions(helpOption); + } catch (SecurityException e) { + // if help text printed correctly then exit code 0 is expected + assertThat(e.getMessage(), equalTo("System.exit(0) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + + verifyStdOutContainsString("usage: install_demo_configuration" + installer.FILE_EXTENSION + " [-c] [-h] [-i] [-s] [-t] [-y]"); + } + + @Test + public void testGatherUserInputs_withoutAssumeYes() { + // -i & -c option is not passed + String[] validOptions = { "/scriptDir" }; + installer.readOptions(validOptions); + assertThat(installer.assumeyes, is(false)); + assertThat(installer.initsecurity, is(false)); + assertThat(installer.cluster_mode, is(false)); + + // set initsecurity and cluster_mode to no + readInputStream("y" + System.lineSeparator() + "n" + System.lineSeparator() + "n" + System.lineSeparator()); // pass all 3 inputs as + // y + installer.gatherUserInputs(); + + verifyStdOutContainsString("Install demo certificates?"); + verifyStdOutContainsString("Initialize Security Modules?"); + verifyStdOutContainsString("Cluster mode requires additional setup of:"); + verifyStdOutContainsString(" - Virtual memory (vm.max_map_count)" + System.lineSeparator()); + verifyStdOutContainsString("Enable cluster mode?"); + + assertThat(installer.initsecurity, is(false)); + assertThat(installer.cluster_mode, is(false)); + + outContent.reset(); + + // set initsecurity and cluster_mode to no + readInputStream("y" + System.lineSeparator() + "y" + System.lineSeparator() + "y" + System.lineSeparator()); // pass all 3 inputs as + // y + installer.gatherUserInputs(); + + verifyStdOutContainsString("Install demo certificates?"); + verifyStdOutContainsString("Initialize Security Modules?"); + verifyStdOutContainsString("Cluster mode requires additional setup of:"); + verifyStdOutContainsString(" - Virtual memory (vm.max_map_count)" + System.lineSeparator()); + verifyStdOutContainsString("Enable cluster mode?"); + + assertThat(installer.initsecurity, is(true)); + assertThat(installer.cluster_mode, is(true)); + + outContent.reset(); + + // no to demo certificates + try { + System.setSecurityManager(new NoExitSecurityManager()); + + readInputStream("n" + System.lineSeparator() + "n" + System.lineSeparator() + "n" + System.lineSeparator()); + installer.gatherUserInputs(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(0) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + verifyStdOutContainsString("Install demo certificates?"); + verifyStdOutDoesNotContainString("Initialize Security Modules?"); + verifyStdOutDoesNotContainString("Cluster mode requires additional setup of:"); + verifyStdOutDoesNotContainString(" - Virtual memory (vm.max_map_count)" + System.lineSeparator()); + verifyStdOutDoesNotContainString("Enable cluster mode?"); + + outContent.reset(); + + // pass initsecurity and cluster_mode options + String[] validOptionsIC = { "/scriptDir", "-i", "-c" }; + installer.readOptions(validOptionsIC); + assertThat(installer.assumeyes, is(false)); + assertThat(installer.initsecurity, is(true)); + assertThat(installer.cluster_mode, is(true)); + + readInputStream("y" + System.lineSeparator() + "y" + System.lineSeparator() + "y" + System.lineSeparator()); // pass all 3 inputs as + // y + installer.gatherUserInputs(); + + verifyStdOutContainsString("Install demo certificates?"); + verifyStdOutDoesNotContainString("Initialize Security Modules?"); + verifyStdOutDoesNotContainString("Enable cluster mode?"); + + assertThat(installer.initsecurity, is(true)); + assertThat(installer.cluster_mode, is(true)); + } + + @Test + public void testGatherInputs_withAssumeYes() { + String[] validOptionsYes = { "/scriptDir", "-y" }; + installer.readOptions(validOptionsYes); + assertThat(installer.assumeyes, is(true)); + + installer.gatherUserInputs(); + + assertThat(installer.initsecurity, is(true)); + assertThat(installer.cluster_mode, is(true)); + } + + @Test + public void testInitializeVariables_setBaseDir_invalidPath() { + String[] invalidScriptDirPath = { "/scriptDir", "-y" }; + installer.readOptions(invalidScriptDirPath); + + assertThrows("Expected NullPointerException to be thrown", NullPointerException.class, installer::initializeVariables); + + String[] invalidScriptDirPath2 = { "/opensearch/plugins/opensearch-security/tools", "-y" }; + installer.readOptions(invalidScriptDirPath2); + + try { + System.setSecurityManager(new NoExitSecurityManager()); + installer.initializeVariables(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + + verifyStdOutContainsString("DEBUG: basedir does not exist"); + } + + @Test + public void testSetBaseDir_valid() { + String currentDir = System.getProperty("user.dir"); + + String[] validBaseDir = { currentDir, "-y" }; + installer.readOptions(validBaseDir); + + installer.setBaseDir(); + + String expectedBaseDirValue = new File(currentDir).getParentFile().getParentFile().getParentFile().getAbsolutePath() + + File.separator; + assertThat(installer.BASE_DIR, equalTo(expectedBaseDirValue)); + } + + @Test + public void testSetOpenSearchVariables_invalidPath() { + String currentDir = System.getProperty("user.dir"); + + String[] validBaseDir = { currentDir, "-y" }; + installer.readOptions(validBaseDir); + + try { + System.setSecurityManager(new NoExitSecurityManager()); + installer.setBaseDir(); + installer.setOpenSearchVariables(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + + } + verifyStdOutContainsString("Unable to determine OpenSearch config file. Quit."); + verifyStdOutContainsString("Unable to determine OpenSearch bin directory. Quit."); + verifyStdOutContainsString("Unable to determine OpenSearch plugins directory. Quit."); + verifyStdOutContainsString("Unable to determine OpenSearch lib directory. Quit."); + + String expectedBaseDirValue = new File(currentDir).getParentFile().getParentFile().getParentFile().getAbsolutePath() + + File.separator; + String expectedOpensearchConfFilePath = expectedBaseDirValue + "config" + File.separator + "opensearch.yml"; + String expectedOpensearchBinDirPath = expectedBaseDirValue + "bin" + File.separator; + String expectedOpensearchPluginDirPath = expectedBaseDirValue + "plugins" + File.separator; + String expectedOpensearchLibDirPath = expectedBaseDirValue + "lib" + File.separator; + String expectedOpensearchInstallType = installer.determineInstallType(); + + assertThat(installer.OPENSEARCH_CONF_FILE, equalTo(expectedOpensearchConfFilePath)); + assertThat(installer.OPENSEARCH_BIN_DIR, equalTo(expectedOpensearchBinDirPath)); + assertThat(installer.OPENSEARCH_PLUGINS_DIR, equalTo(expectedOpensearchPluginDirPath)); + assertThat(installer.OPENSEARCH_LIB_PATH, equalTo(expectedOpensearchLibDirPath)); + assertThat(installer.OPENSEARCH_INSTALL_TYPE, equalTo(expectedOpensearchInstallType)); + + } + + @Test + public void testDetermineInstallType_windows() { + installer.OS = "Windows"; + + String installType = installer.determineInstallType(); + + assertThat(installType, equalTo(".zip")); + } + + @Test + public void testDetermineInstallType_rpm_deb() { + installer.OS = "Linux"; + String dir = System.getProperty("user.dir"); + installer.BASE_DIR = dir; + RPM_DEB_OPENSEARCH_HOME = new File(dir); + + String installType = installer.determineInstallType(); + + assertThat(installType, equalTo("rpm/deb")); + } + + @Test + public void testDetermineInstallType_default() { + installer.OS = "Anything else"; + installer.BASE_DIR = "/random-dir"; + String installType = installer.determineInstallType(); + + assertThat(installType, equalTo(".tar.gz")); + } + + @Test + public void testSetSecurityVariables() { + setUpSecurityDirectories(); + installer.setSecurityVariables(); + + assertThat(installer.OPENSEARCH_VERSION, is(equalTo("osVersion"))); + assertThat(installer.SECURITY_VERSION, is(equalTo("version"))); + tearDownSecurityDirectories(); + } + + @Test + public void testSetSecurityVariables_noSecurityPlugin() { + try { + System.setSecurityManager(new NoExitSecurityManager()); + + installer.setSecurityVariables(); + fail("Expected System.exit(-1) to be called"); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + } + + @Test + public void testPrintVariables() { + installer.OPENSEARCH_INSTALL_TYPE = "installType"; + installer.OS = "OS"; + installer.OPENSEARCH_CONF_DIR = "confDir"; + installer.OPENSEARCH_CONF_FILE = "confFile"; + installer.OPENSEARCH_BIN_DIR = "/bin"; + installer.OPENSEARCH_PLUGINS_DIR = "/plugins"; + installer.OPENSEARCH_LIB_PATH = "/lib"; + installer.OPENSEARCH_VERSION = "osVersion"; + installer.SECURITY_VERSION = "version"; + + installer.printVariables(); + + String expectedOutput = "OpenSearch install type: installType on OS" + + System.lineSeparator() + + "OpenSearch config dir: confDir" + + System.lineSeparator() + + "OpenSearch config file: confFile" + + System.lineSeparator() + + "OpenSearch bin dir: /bin" + + System.lineSeparator() + + "OpenSearch plugins dir: /plugins" + + System.lineSeparator() + + "OpenSearch lib dir: /lib" + + System.lineSeparator() + + "Detected OpenSearch Version: osVersion" + + System.lineSeparator() + + "Detected OpenSearch Security Version: version" + + System.lineSeparator(); + + assertThat(outContent.toString(), equalTo(expectedOutput)); + } + + @Test + public void testFinishScriptExecution() { + setUpSecurityDirectories(); + SecuritySettingsConfigurer.ADMIN_PASSWORD = "ble"; + + installer.finishScriptExecution(); + + String securityAdminScriptPath = installer.OPENSEARCH_PLUGINS_DIR + + "opensearch-security" + + File.separator + + "tools" + + File.separator + + "securityadmin" + + installer.FILE_EXTENSION; + String securityAdminDemoScriptPath = installer.OPENSEARCH_CONF_DIR + "securityadmin_demo" + installer.FILE_EXTENSION; + setWritePermissions(securityAdminDemoScriptPath); + + SecuritySettingsConfigurer securitySettingsConfigurer = new SecuritySettingsConfigurer(installer); + String lastLine = securitySettingsConfigurer.getSecurityAdminCommands(securityAdminScriptPath)[1]; + + String expectedOutput = "### Success" + + System.lineSeparator() + + "### Execute this script now on all your nodes and then start all nodes" + + System.lineSeparator() + + "### After the whole cluster is up execute: " + + System.lineSeparator() + + lastLine + + System.lineSeparator() + + "### or run ." + + File.separator + + "securityadmin_demo" + + installer.FILE_EXTENSION + + System.lineSeparator() + + "### After that you can also use the Security Plugin ConfigurationGUI" + + System.lineSeparator() + + "### To access your secured cluster open https://: and log in with admin/" + + SecuritySettingsConfigurer.ADMIN_PASSWORD + + "." + + System.lineSeparator() + + "### (Ignore the SSL certificate warning because we installed self-signed demo certificates)" + + System.lineSeparator(); + + assertThat(outContent.toString(), equalTo(expectedOutput)); + + tearDownSecurityDirectories(); + } + + @Test + public void testFinishScriptExecution_withInitSecurityEnabled() { + setUpSecurityDirectories(); + installer.initsecurity = true; + SecuritySettingsConfigurer.ADMIN_PASSWORD = "ble"; + + installer.finishScriptExecution(); + + String securityAdminScriptPath = installer.OPENSEARCH_PLUGINS_DIR + + "opensearch-security" + + File.separator + + "tools" + + File.separator + + "securityadmin" + + installer.FILE_EXTENSION; + String securityAdminDemoScriptPath = installer.OPENSEARCH_CONF_DIR + "securityadmin_demo" + installer.FILE_EXTENSION; + setWritePermissions(securityAdminDemoScriptPath); + + SecuritySettingsConfigurer securitySettingsConfigurer = new SecuritySettingsConfigurer(installer); + String lastLine = securitySettingsConfigurer.getSecurityAdminCommands(securityAdminScriptPath)[1]; + + String expectedOutput = "### Success" + + System.lineSeparator() + + "### Execute this script now on all your nodes and then start all nodes" + + System.lineSeparator() + + "### OpenSearch Security will be automatically initialized." + + System.lineSeparator() + + "### If you like to change the runtime configuration " + + System.lineSeparator() + + "### change the files in .." + + File.separator + + ".." + + File.separator + + ".." + + File.separator + + "config" + + File.separator + + "opensearch-security and execute: " + + System.lineSeparator() + + lastLine + + System.lineSeparator() + + "### or run ." + + File.separator + + "securityadmin_demo" + + installer.FILE_EXTENSION + + System.lineSeparator() + + "### To use the Security Plugin ConfigurationGUI" + + System.lineSeparator() + + "### To access your secured cluster open https://: and log in with admin/" + + SecuritySettingsConfigurer.ADMIN_PASSWORD + + "." + + System.lineSeparator() + + "### (Ignore the SSL certificate warning because we installed self-signed demo certificates)" + + System.lineSeparator(); + + assertThat(outContent.toString(), equalTo(expectedOutput)); + + tearDownSecurityDirectories(); + } + + private void readInputStream(String input) { + System.setIn(new ByteArrayInputStream(input.getBytes())); + } + + public void setUpSecurityDirectories() { + String currentDir = System.getProperty("user.dir"); + + String[] validBaseDir = { currentDir, "-y" }; + installer.readOptions(validBaseDir); + installer.setBaseDir(); + installer.OPENSEARCH_PLUGINS_DIR = installer.BASE_DIR + "plugins" + File.separator; + installer.OPENSEARCH_LIB_PATH = installer.BASE_DIR + "lib" + File.separator; + installer.OPENSEARCH_CONF_DIR = installer.BASE_DIR + "test-conf" + File.separator; + + createDirectory(installer.OPENSEARCH_PLUGINS_DIR); + createDirectory(installer.OPENSEARCH_LIB_PATH); + createDirectory(installer.OPENSEARCH_CONF_DIR); + createDirectory(installer.OPENSEARCH_PLUGINS_DIR + "opensearch-security"); + createFile(installer.OPENSEARCH_LIB_PATH + "opensearch-core-osVersion.jar"); + createFile(installer.OPENSEARCH_PLUGINS_DIR + "opensearch-security" + File.separator + "opensearch-security-version.jar"); + createFile(installer.OPENSEARCH_CONF_DIR + File.separator + "securityadmin_demo.sh"); + } + + public void tearDownSecurityDirectories() { + // Clean up testing directories or files + deleteDirectoryRecursive(installer.OPENSEARCH_PLUGINS_DIR); + deleteDirectoryRecursive(installer.OPENSEARCH_LIB_PATH); + deleteDirectoryRecursive(installer.OPENSEARCH_CONF_DIR); + } + + static void setWritePermissions(String filePath) { + if (!installer.OS.toLowerCase().contains("win")) { + Path file = Paths.get(filePath); + Set perms = new HashSet<>(); + perms.add(PosixFilePermission.OWNER_WRITE); + try { + Files.setPosixFilePermissions(file, perms); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private void verifyStdOutContainsString(String s) { + assertThat(outContent.toString(), containsString(s)); + } + + private void verifyStdOutDoesNotContainString(String s) { + assertThat(outContent.toString(), not(containsString(s))); + } +} diff --git a/src/test/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurerTests.java b/src/test/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurerTests.java new file mode 100644 index 0000000000..caba8b44d0 --- /dev/null +++ b/src/test/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurerTests.java @@ -0,0 +1,326 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.tools.democonfig; + +// CS-SUPPRESS-SINGLE: RegexpSingleline extension key-word is used in file ext variable +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.security.tools.democonfig.util.NoExitSecurityManager; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.opensearch.security.tools.democonfig.SecuritySettingsConfigurer.REST_ENABLED_ROLES; +import static org.opensearch.security.tools.democonfig.SecuritySettingsConfigurer.SYSTEM_INDICES; +import static org.opensearch.security.tools.democonfig.SecuritySettingsConfigurer.isKeyPresentInYMLFile; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.createDirectory; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.createFile; +import static org.opensearch.security.tools.democonfig.util.DemoConfigHelperUtil.deleteDirectoryRecursive; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +public class SecuritySettingsConfigurerTests { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final PrintStream originalErr = System.err; + private final InputStream originalIn = System.in; + + private final String adminPasswordKey = "initialAdminPassword"; + + private static SecuritySettingsConfigurer securitySettingsConfigurer; + + private static Installer installer; + + @Before + public void setUp() { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(outContent)); + installer = Installer.getInstance(); + installer.buildOptions(); + securitySettingsConfigurer = new SecuritySettingsConfigurer(installer); + setUpConf(); + } + + @After + public void tearDown() throws NoSuchFieldException, IllegalAccessException { + outContent.reset(); + System.setOut(originalOut); + System.setErr(originalErr); + System.setIn(originalIn); + deleteDirectoryRecursive(installer.OPENSEARCH_CONF_DIR); + unsetEnv(adminPasswordKey); + Installer.resetInstance(); + } + + @Test + public void testUpdateAdminPasswordWithCustomPassword() throws NoSuchFieldException, IllegalAccessException { + String customPassword = "myStrongPassword123"; // generateStrongPassword(); + setEnv(adminPasswordKey, customPassword); + + securitySettingsConfigurer.updateAdminPassword(); + + assertThat(customPassword, is(equalTo(SecuritySettingsConfigurer.ADMIN_PASSWORD))); + + verifyStdOutContainsString("ADMIN PASSWORD SET TO: " + customPassword); + } + + @Test + public void testUpdateAdminPasswordWithFilePassword() throws IOException { + String customPassword = "myStrongPassword123"; + String initialAdminPasswordTxt = installer.OPENSEARCH_CONF_DIR + adminPasswordKey + ".txt"; + createFile(initialAdminPasswordTxt); + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(initialAdminPasswordTxt, StandardCharsets.UTF_8))) { + writer.write(customPassword); + } catch (IOException e) { + throw new IOException("Unable to update the internal users file with the hashed password."); + } + + securitySettingsConfigurer.updateAdminPassword(); + + assertEquals(customPassword, SecuritySettingsConfigurer.ADMIN_PASSWORD); + verifyStdOutContainsString("ADMIN PASSWORD SET TO: " + customPassword); + } + + @Test + public void testUpdateAdminPassword_noPasswordSupplied() { + try { + System.setSecurityManager(new NoExitSecurityManager()); + securitySettingsConfigurer.updateAdminPassword(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + + verifyStdOutContainsString("No custom admin password found. Please provide a password."); + } + + @Test + public void testUpdateAdminPasswordWithWeakPassword() throws NoSuchFieldException, IllegalAccessException { + + setEnv(adminPasswordKey, "weakpassword"); + try { + System.setSecurityManager(new NoExitSecurityManager()); + securitySettingsConfigurer.updateAdminPassword(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + + verifyStdOutContainsString("Password weakpassword is weak. Please re-try with a stronger password."); + } + + @Test + public void testUpdateAdminPasswordWithWeakPassword_skipPasswordValidation() throws NoSuchFieldException, IllegalAccessException { + setEnv(adminPasswordKey, "weakpassword"); + installer.environment = ExecutionEnvironment.TEST; + securitySettingsConfigurer.updateAdminPassword(); + + assertThat("weakpassword", is(equalTo(SecuritySettingsConfigurer.ADMIN_PASSWORD))); + verifyStdOutContainsString("ADMIN PASSWORD SET TO: weakpassword"); + } + + @Test + public void testSecurityPluginAlreadyConfigured() { + securitySettingsConfigurer.writeSecurityConfigToOpenSearchYML(); + String expectedMessage = installer.OPENSEARCH_CONF_FILE + " seems to be already configured for Security. Quit."; + try { + System.setSecurityManager(new NoExitSecurityManager()); + securitySettingsConfigurer.checkIfSecurityPluginIsAlreadyConfigured(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + verifyStdOutContainsString(expectedMessage); + } + + @Test + public void testSecurityPluginNotConfigured() { + try { + securitySettingsConfigurer.checkIfSecurityPluginIsAlreadyConfigured(); + } catch (Exception e) { + fail("Expected checkIfSecurityPluginIsAlreadyConfigured to succeed without any errors."); + } + } + + @Test + public void testConfigFileDoesNotExist() { + installer.OPENSEARCH_CONF_FILE = "path/to/nonexistentfile"; + String expectedMessage = "OpenSearch configuration file does not exist. Quit."; + try { + System.setSecurityManager(new NoExitSecurityManager()); + securitySettingsConfigurer.checkIfSecurityPluginIsAlreadyConfigured(); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + + verifyStdOutContainsString(expectedMessage); + + // reset the file pointer + installer.OPENSEARCH_CONF_FILE = installer.OPENSEARCH_CONF_DIR + "opensearch.yml"; + } + + @Test + public void testBuildSecurityConfigMap() { + Map actual = securitySettingsConfigurer.buildSecurityConfigMap(); + + assertThat(actual.size(), is(17)); + assertThat(actual.get("plugins.security.ssl.transport.pemcert_filepath"), is(equalTo(Certificates.NODE_CERT.getFileName()))); + assertThat(actual.get("plugins.security.ssl.transport.pemkey_filepath"), is(equalTo(Certificates.NODE_KEY.getFileName()))); + assertThat(actual.get("plugins.security.ssl.transport.pemtrustedcas_filepath"), is(equalTo(Certificates.ROOT_CA.getFileName()))); + assertThat(actual.get("plugins.security.ssl.transport.enforce_hostname_verification"), is(equalTo(false))); + assertThat(actual.get("plugins.security.ssl.http.enabled"), is(equalTo(true))); + assertThat(actual.get("plugins.security.ssl.http.pemcert_filepath"), is(equalTo(Certificates.NODE_CERT.getFileName()))); + assertThat(actual.get("plugins.security.ssl.http.pemkey_filepath"), is(equalTo(Certificates.NODE_KEY.getFileName()))); + assertThat(actual.get("plugins.security.ssl.http.pemtrustedcas_filepath"), is(equalTo(Certificates.ROOT_CA.getFileName()))); + assertThat(actual.get("plugins.security.allow_unsafe_democertificates"), is(equalTo(true))); + assertThat(actual.get("plugins.security.authcz.admin_dn"), is(equalTo(List.of("CN=kirk,OU=client,O=client,L=test,C=de")))); + assertThat(actual.get("plugins.security.audit.type"), is(equalTo("internal_opensearch"))); + assertThat(actual.get("plugins.security.enable_snapshot_restore_privilege"), is(equalTo(true))); + assertThat(actual.get("plugins.security.check_snapshot_restore_write_privileges"), is(equalTo(true))); + assertThat(actual.get("plugins.security.restapi.roles_enabled"), is(equalTo(REST_ENABLED_ROLES))); + assertThat(actual.get("plugins.security.system_indices.enabled"), is(equalTo(true))); + assertThat(actual.get("plugins.security.system_indices.indices"), is(equalTo(SYSTEM_INDICES))); + assertThat(actual.get("node.max_local_storage_nodes"), is(equalTo(3))); + + installer.initsecurity = true; + actual = securitySettingsConfigurer.buildSecurityConfigMap(); + assertThat(actual.get("plugins.security.allow_default_init_securityindex"), is(equalTo(true))); + + installer.cluster_mode = true; + actual = securitySettingsConfigurer.buildSecurityConfigMap(); + assertThat(actual.get("network.host"), is(equalTo("0.0.0.0"))); + assertThat(actual.get("node.name"), is(equalTo("smoketestnode"))); + assertThat(actual.get("cluster.initial_cluster_manager_nodes"), is(equalTo("smoketestnode"))); + } + + @Test + public void testIsStringAlreadyPresentInFile_isNotPresent() throws IOException { + String str1 = "network.host"; + String str2 = "some.random.config"; + + installer.initsecurity = true; + securitySettingsConfigurer.writeSecurityConfigToOpenSearchYML(); + + assertThat(isKeyPresentInYMLFile(installer.OPENSEARCH_CONF_FILE, str1), is(equalTo(false))); + assertThat(isKeyPresentInYMLFile(installer.OPENSEARCH_CONF_FILE, str2), is(equalTo(false))); + } + + @Test + public void testIsStringAlreadyPresentInFile_isPresent() throws IOException { + String str1 = "network.host"; + String str2 = "some.random.config"; + + installer.initsecurity = true; + installer.cluster_mode = true; + securitySettingsConfigurer.writeSecurityConfigToOpenSearchYML(); + + assertThat(isKeyPresentInYMLFile(installer.OPENSEARCH_CONF_FILE, str1), is(equalTo(true))); + assertThat(isKeyPresentInYMLFile(installer.OPENSEARCH_CONF_FILE, str2), is(equalTo(false))); + } + + @Test + public void testCreateSecurityAdminDemoScriptAndGetSecurityAdminCommands() throws IOException { + String demoPath = installer.OPENSEARCH_CONF_DIR + "securityadmin_demo" + installer.FILE_EXTENSION; + securitySettingsConfigurer.createSecurityAdminDemoScript("scriptPath", demoPath); + + assertThat(new File(demoPath).exists(), is(equalTo(true))); + + String[] commands = securitySettingsConfigurer.getSecurityAdminCommands("scriptPath"); + + try (BufferedReader reader = new BufferedReader(new FileReader(demoPath, StandardCharsets.UTF_8))) { + assertThat(reader.readLine(), is(commands[0])); + assertThat(reader.readLine(), is(equalTo(commands[1]))); + } + } + + @Test + public void testCreateSecurityAdminDemoScript_invalidPath() { + String demoPath = null; + try { + securitySettingsConfigurer.createSecurityAdminDemoScript("scriptPath", demoPath); + fail("Expected to throw Exception"); + } catch (IOException | NullPointerException e) { + // expected + } + } + + @SuppressWarnings("unchecked") + public static void setEnv(String key, String value) throws NoSuchFieldException, IllegalAccessException { + Class[] classes = Collections.class.getDeclaredClasses(); + Map env = System.getenv(); + for (Class cl : classes) { + if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { + Field field = cl.getDeclaredField("m"); + field.setAccessible(true); + Object obj = field.get(env); + Map map = (Map) obj; + map.clear(); + map.put(key, value); + } + } + } + + @SuppressWarnings("unchecked") + public static void unsetEnv(String key) throws NoSuchFieldException, IllegalAccessException { + Class[] classes = Collections.class.getDeclaredClasses(); + Map env = System.getenv(); + for (Class cl : classes) { + if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { + Field field = cl.getDeclaredField("m"); + field.setAccessible(true); + Object obj = field.get(env); + Map map = (Map) obj; + map.remove(key); + } + } + } + + void setUpConf() { + installer.OPENSEARCH_CONF_DIR = System.getProperty("user.dir") + File.separator + "test-conf" + File.separator; + installer.OPENSEARCH_CONF_FILE = installer.OPENSEARCH_CONF_DIR + "opensearch.yml"; + String securityConfDir = installer.OPENSEARCH_CONF_DIR + "opensearch-security" + File.separator; + createDirectory(securityConfDir); + createFile(securityConfDir + "internal_users.yml"); + createFile(installer.OPENSEARCH_CONF_FILE); + } + + private void verifyStdOutContainsString(String s) { + assertThat(outContent.toString(), containsString(s)); + } +} diff --git a/src/test/java/org/opensearch/security/tools/democonfig/util/DemoConfigHelperUtil.java b/src/test/java/org/opensearch/security/tools/democonfig/util/DemoConfigHelperUtil.java new file mode 100644 index 0000000000..7fd4c3330d --- /dev/null +++ b/src/test/java/org/opensearch/security/tools/democonfig/util/DemoConfigHelperUtil.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.tools.democonfig.util; + +import java.io.File; + +public class DemoConfigHelperUtil { + public static void createDirectory(String path) { + File directory = new File(path); + if (!directory.exists() && !directory.mkdirs()) { + throw new RuntimeException("Failed to create directory: " + path); + } + } + + public static void createFile(String path) { + try { + File file = new File(path); + if (!file.exists() && !file.createNewFile()) { + throw new RuntimeException("Failed to create file: " + path); + } + } catch (Exception e) { + // without this the catch, we would need to throw exception, + // which would then require modifying caller method signature + throw new RuntimeException("Failed to create file: " + path, e); + } + } + + public static void deleteDirectoryRecursive(String path) { + File directory = new File(path); + if (directory.exists()) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteDirectoryRecursive(file.getAbsolutePath()); + } else { + file.delete(); + } + } + } + // Delete the empty directory after all its content is deleted + directory.delete(); + } + } +} diff --git a/src/test/java/org/opensearch/security/tools/democonfig/util/NoExitSecurityManager.java b/src/test/java/org/opensearch/security/tools/democonfig/util/NoExitSecurityManager.java new file mode 100644 index 0000000000..16bb61f169 --- /dev/null +++ b/src/test/java/org/opensearch/security/tools/democonfig/util/NoExitSecurityManager.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.tools.democonfig.util; + +public class NoExitSecurityManager extends SecurityManager { + @Override + public void checkPermission(java.security.Permission perm) { + // Allow everything except System.exit code 0 &b -1 + if (perm instanceof java.lang.RuntimePermission && ("exitVM.0".equals(perm.getName()) || "exitVM.-1".equals(perm.getName()))) { + StringBuilder sb = new StringBuilder(); + sb.append("System.exit("); + sb.append(perm.getName().contains("0") ? 0 : -1); + sb.append(") blocked to allow print statement testing."); + throw new SecurityException(sb.toString()); + } + } +}