/*
* JBoss, Home of Professional Open Source
* Copyright 2014, JBoss Inc., and individual contributors as indicated
* by the @authors tag.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.as.test.manualmode.vault;
import static org.hamcrest.CoreMatchers.containsString;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE;
import static org.jboss.as.test.integration.management.util.CustomCLIExecutor.MANAGEMENT_NATIVE_PORT;
import static org.jboss.as.test.integration.management.util.ModelUtil.createOpNode;
import static org.junit.Assert.assertThat;
import static org.wildfly.core.test.standalone.mgmt.HTTPSConnectionWithCLITestCase.reloadServer;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.test.integration.management.util.CustomCLIExecutor;
import org.jboss.as.test.integration.security.PicketBoxModuleUtil;
import org.jboss.as.test.integration.security.common.AbstractBaseSecurityRealmsServerSetupTask;
import org.jboss.as.test.integration.security.common.CoreUtils;
import org.jboss.as.test.integration.security.common.SecurityTestConstants;
import org.jboss.as.test.integration.security.common.config.realm.Authentication;
import org.jboss.as.test.integration.security.common.config.realm.RealmKeystore;
import org.jboss.as.test.integration.security.common.config.realm.SecurityRealm;
import org.jboss.as.test.integration.security.common.config.realm.ServerIdentity;
import org.jboss.as.test.module.util.TestModule;
import org.jboss.as.test.shared.TestSuiteEnvironment;
import org.jboss.dmr.ModelNode;
import org.jboss.logging.Logger;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.core.testrunner.ManagementClient;
import org.wildfly.core.testrunner.ServerControl;
import org.wildfly.core.testrunner.ServerController;
import org.wildfly.core.testrunner.ServerSetupTask;
import org.wildfly.core.testrunner.WildflyTestRunner;
import org.wildfly.test.api.Authentication.CallbackHandler;
/**
* Testing support for vault encrypted passwords in jboss-cli configuration
* file. It tries to connect to native management interface with cli console
* with configured SSL.
*
* @author Filip Bogyai
*/
@RunWith(WildflyTestRunner.class)
@ServerControl(manual = true)
@Ignore("[WFCORE-1958] Clean up testsuite Elytron registration.")
public class VaultPasswordsInCLITestCase {
private static Logger LOGGER = Logger.getLogger(VaultPasswordsInCLITestCase.class);
private static final File WORK_DIR = new File("cli-vault-workdir");
public static final File SERVER_KEYSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.SERVER_KEYSTORE);
public static final File SERVER_TRUSTSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.SERVER_TRUSTSTORE);
public static final File CLIENT_KEYSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.CLIENT_KEYSTORE);
public static final File CLIENT_TRUSTSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.CLIENT_TRUSTSTORE);
// see genseckey-cli-vault-keystore in pom.xml for the keystore creation
public static final File VAULT_KEYSTORE_FILE = new File("target/cli-vault.keystore");
public static final File VAULT_CONFIG_FILE = new File(WORK_DIR, "vault-config-xml");
public static final String VAULT_KEYSTORE_PASS = "secret_1";
public static final String ALIAS_NAME = "vault";
private static final String MANAGEMENT_CLI_REALM = "ManagementCLIRealm";
private static final String JBOSS_CLI_FILE = "jboss-cli.xml";
private static final File RIGHT_VAULT_PASSWORD_FILE = new File(WORK_DIR, "right-vault-pass.xml");
private static final File WRONG_VAULT_PASSWORD_FILE = new File(WORK_DIR, "wrong-vault-pass.xml");
private static final String TESTING_OPERATION = "/core-service=management/management-interface=http-interface:read-resource";
private static final ManagementInterfacesSetup managementInterfacesSetup = new ManagementInterfacesSetup();
private static final ManagemenCLIRealmSetup managementCLICliRealmSetup = new ManagemenCLIRealmSetup();
@Inject
private static ServerController containerController;
private static TestModule picketLink;
@BeforeClass
public static void prepareServer() throws Exception {
LOGGER.info("*** starting server");
containerController.startInAdminMode();
ManagementClient mgmtClient = containerController.getClient();
managementInterfacesSetup.setup(mgmtClient);
managementCLICliRealmSetup.setup(mgmtClient);
picketLink = PicketBoxModuleUtil.createTestModule();
createAndInitializeVault();
// To apply new security realm settings for native interface reload of
// server is required
LOGGER.trace("*** reload server");
reloadServer();
}
/**
* Testing access to native interface with wrong password to keystore in
* jboss-cli configuration. This password is masked and is loaded from
* vault. Exception with message that password is incorrect should be
* thrown.
*/
@Test
public void testWrongVaultPassword() throws InterruptedException, IOException {
String cliOutput = CustomCLIExecutor.execute(WRONG_VAULT_PASSWORD_FILE, TESTING_OPERATION);
assertThat("Password should be wrong", cliOutput, containsString("Keystore was tampered with, or password was incorrect"));
}
/**
* Testing access to native interface with right password to keystore in
* jboss-cli configuration. This password is masked and is loaded from
* vault.
*/
@Test
public void testRightVaultPassword() throws InterruptedException, IOException {
String cliOutput = CustomCLIExecutor.execute(RIGHT_VAULT_PASSWORD_FILE, TESTING_OPERATION);
assertThat("Password should be right and authentication successful", cliOutput, containsString("\"outcome\" => \"success\""));
}
@AfterClass
public static void resetConfigurationForNativeInterface() throws Exception {
LOGGER.info("*** reseting test configuration");
resetHttpInterfaceConfiguration(getNativeModelControllerClient());
// reload to apply changes
reloadServer();
ManagementClient client = containerController.getClient();
managementInterfacesSetup.tearDown(client);
managementCLICliRealmSetup.tearDown(client);
LOGGER.info("*** stopping container");
containerController.stop();
picketLink.remove();
FileUtils.deleteDirectory(WORK_DIR);
}
static class ManagemenCLIRealmSetup extends AbstractBaseSecurityRealmsServerSetupTask {
@Override
protected SecurityRealm[] getSecurityRealms() throws Exception {
final ServerIdentity serverIdentity = new ServerIdentity.Builder().ssl(
new RealmKeystore.Builder().keystorePassword(SecurityTestConstants.KEYSTORE_PASSWORD)
.keystorePath(SERVER_KEYSTORE_FILE.getAbsolutePath()).build()).build();
final Authentication authentication = new Authentication.Builder().truststore(
new RealmKeystore.Builder().keystorePassword(SecurityTestConstants.KEYSTORE_PASSWORD)
.keystorePath(SERVER_TRUSTSTORE_FILE.getAbsolutePath()).build()).build();
final SecurityRealm realm = new SecurityRealm.Builder().name(MANAGEMENT_CLI_REALM).serverIdentity(serverIdentity)
.authentication(authentication).build();
return new SecurityRealm[]{realm};
}
}
static class ManagementInterfacesSetup implements ServerSetupTask {
public void setup(ManagementClient managementClient) throws Exception {
FileUtils.deleteDirectory(WORK_DIR);
WORK_DIR.mkdirs();
CoreUtils.createKeyMaterial(WORK_DIR);
final ModelControllerClient client = managementClient.getControllerClient();
// secure http interface
ModelNode operation = createOpNode("core-service=management/management-interface=http-interface",
ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION);
operation.get(NAME).set("security-realm");
operation.get(VALUE).set(MANAGEMENT_CLI_REALM);
CoreUtils.applyUpdate(operation, client);
operation = createOpNode("core-service=management/management-interface=http-interface",
ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION);
operation.get(NAME).set("secure-socket-binding");
operation.get(VALUE).set("management-https");
CoreUtils.applyUpdate(operation, client);
// add native socket binding
operation = createOpNode("socket-binding-group=standard-sockets/socket-binding=management-native", ModelDescriptionConstants.ADD);
operation.get("port").set(MANAGEMENT_NATIVE_PORT);
operation.get("interface").set("management");
CoreUtils.applyUpdate(operation, client);
// create native interface to control server while http interface will be secured
operation = createOpNode("core-service=management/management-interface=native-interface", ModelDescriptionConstants.ADD);
operation.get("security-realm").set("ManagementRealm");
operation.get("socket-binding").set("management-native");
CoreUtils.applyUpdate(operation, client);
}
@Override
public void tearDown(ManagementClient managementClient) throws Exception {
final ModelControllerClient client = managementClient.getControllerClient();
ModelNode operation = createOpNode("core-service=management/management-interface=native-interface", ModelDescriptionConstants.REMOVE);
CoreUtils.applyUpdate(operation, client);
operation = createOpNode("socket-binding-group=standard-sockets/socket-binding=management-native", ModelDescriptionConstants.REMOVE);
CoreUtils.applyUpdate(operation, client);
FileUtils.deleteDirectory(WORK_DIR);
}
}
private static void createAndInitializeVault() throws Exception {
// see genseckey-cli-vault-keystore in pom.xml for the keystore creation
String keystoreURL = VAULT_KEYSTORE_FILE.getAbsolutePath();
String encryptionDirURL = WORK_DIR.getAbsolutePath();
String salt = "87654321";
int iterationCount = 20;
TestVaultSession nonInteractiveSession = new TestVaultSession(keystoreURL, VAULT_KEYSTORE_PASS, encryptionDirURL, salt, iterationCount);
nonInteractiveSession.startVaultSession(ALIAS_NAME);
String rightPassword = SecurityTestConstants.KEYSTORE_PASSWORD;
String wrongPassword = "blablabla";
String rightBlock = "right";
String wrongBlock = "wrong";
String attributeName = "password";
// write vault configuration to file
FileUtils.write(VAULT_CONFIG_FILE, nonInteractiveSession.vaultConfiguration());
// add right and wrong password for clients keystore into vault
String vaultPasswordString = nonInteractiveSession.addSecuredAttribute(rightBlock, attributeName, rightPassword.toCharArray());
String wrongVaultPasswordString = nonInteractiveSession.addSecuredAttribute(wrongBlock, attributeName, wrongPassword.toCharArray());
String vaultConfiguration = "<vault file=\"" + VAULT_CONFIG_FILE.getAbsolutePath() + "\"/>";
// create jboss-cli configuration file with ssl and vaulted passwords
FileUtils.write(RIGHT_VAULT_PASSWORD_FILE, CoreUtils.propertiesReplacer(JBOSS_CLI_FILE, CLIENT_KEYSTORE_FILE, CLIENT_TRUSTSTORE_FILE,
vaultPasswordString, vaultConfiguration));
FileUtils.write(WRONG_VAULT_PASSWORD_FILE, CoreUtils.propertiesReplacer(JBOSS_CLI_FILE, CLIENT_KEYSTORE_FILE, CLIENT_TRUSTSTORE_FILE,
wrongVaultPasswordString, vaultConfiguration));
}
private static void resetHttpInterfaceConfiguration(ModelControllerClient client) throws Exception {
// change back security realm for http management interface
ModelNode operation = createOpNode("core-service=management/management-interface=http-interface",
ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION);
operation.get(NAME).set("security-realm");
operation.get(VALUE).set("ManagementRealm");
CoreUtils.applyUpdate(operation, client);
// undefine secure socket binding from http interface
operation = createOpNode("core-service=management/management-interface=http-interface",
ModelDescriptionConstants.UNDEFINE_ATTRIBUTE_OPERATION);
operation.get(NAME).set("secure-socket-binding");
CoreUtils.applyUpdate(operation, client);
}
private static ModelControllerClient getNativeModelControllerClient() {
ModelControllerClient client = null;
try {
client = ModelControllerClient.Factory.create("remote", InetAddress.getByName(TestSuiteEnvironment.getServerAddress()),
MANAGEMENT_NATIVE_PORT, new CallbackHandler());
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
return client;
}
}