diff --git a/commands/src/main/java/org/wildfly/extras/creaper/commands/elytron/tls/AddLdapKeyStore.java b/commands/src/main/java/org/wildfly/extras/creaper/commands/elytron/tls/AddLdapKeyStore.java new file mode 100644 index 00000000..100c65a2 --- /dev/null +++ b/commands/src/main/java/org/wildfly/extras/creaper/commands/elytron/tls/AddLdapKeyStore.java @@ -0,0 +1,377 @@ +package org.wildfly.extras.creaper.commands.elytron.tls; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jboss.dmr.ModelNode; +import org.wildfly.extras.creaper.core.online.OnlineCommand; +import org.wildfly.extras.creaper.core.online.OnlineCommandContext; +import org.wildfly.extras.creaper.core.online.operations.Address; +import org.wildfly.extras.creaper.core.online.operations.Operations; +import org.wildfly.extras.creaper.core.online.operations.Values; +import org.wildfly.extras.creaper.core.online.operations.admin.Administration; + +public final class AddLdapKeyStore implements OnlineCommand { + + private final String name; + private final String dirContext; + private final String searchPath; + private final Boolean searchRecursive; + private final Integer searchTimeLimit; + private final String filterAlias; + private final String filterCertificate; + private final String filterIterate; + private final LdapMapping ldapMapping; + private final NewItemTemplate newItemTemplate; + private final boolean replaceExisting; + + private AddLdapKeyStore(Builder builder) { + this.name = builder.name; + this.dirContext = builder.dirContext; + this.searchPath = builder.searchPath; + this.searchRecursive = builder.searchRecursive; + this.searchTimeLimit = builder.searchTimeLimit; + this.filterAlias = builder.filterAlias; + this.filterCertificate = builder.filterCertificate; + this.filterIterate = builder.filterIterate; + this.ldapMapping = builder.ldapMapping; + this.newItemTemplate = builder.newItemTemplate; + // Replace existing + this.replaceExisting = builder.replaceExisting; + } + + @Override + public void apply(OnlineCommandContext ctx) throws Exception { + Operations ops = new Operations(ctx.client); + Address keyStoreAddress = Address.subsystem("elytron").and("ldap-key-store", name); + if (replaceExisting) { + ops.removeIfExists(keyStoreAddress); + new Administration(ctx.client).reloadIfRequired(); + } + + Values keyStoreValues = Values.empty() + .and("name", name) + .and("dir-context", dirContext) + .and("search-path", searchPath) + .andOptional("search-recursive", searchRecursive) + .andOptional("search-time-limit", searchTimeLimit) + .andOptional("filterAlias", filterAlias) + .andOptional("filter-certificate", filterCertificate) + .andOptional("filter-iterate", filterIterate); + // LdapMapping + if (ldapMapping != null) { + keyStoreValues = keyStoreValues + .andOptional("alias-attribute", ldapMapping.getAliasAttribute()) + .andOptional("certificate-attribute", ldapMapping.getCertificateAttribute()) + .andOptional("certificate-type", ldapMapping.getCertificateType()) + .andOptional("certificate-chain-attribute", ldapMapping.getCertificateChainAttribute()) + .andOptional("certificate-chain-encoding", ldapMapping.getCertificateChainEncoding()); + } + + if (newItemTemplate != null) { + keyStoreValues = keyStoreValues + .andOptional("new-item-path", newItemTemplate.getNewItemPath()) + .andOptional("new-item-rdn", newItemTemplate.getNewItemRdn()); + if (newItemTemplate.getNewItemAttributes() != null && !newItemTemplate.getNewItemAttributes().isEmpty()) { + List newItemAttributesNodeList = new ArrayList(); + for (NewItemAttribute newItemAttribute : newItemTemplate.getNewItemAttributes()) { + ModelNode attributeNode = new ModelNode(); + + if (newItemAttribute.getName() != null && !newItemAttribute.getName().isEmpty()) { + attributeNode.add("name", newItemAttribute.getName()); + } + + ModelNode valuesList = new ModelNode().setEmptyList(); + for (String value : newItemAttribute.getValues()) { + valuesList.add(value); + } + attributeNode.add("value", valuesList); + + attributeNode = attributeNode.asObject(); + newItemAttributesNodeList.add(attributeNode); + } + ModelNode newIdentityAttributesNode = new ModelNode(); + newIdentityAttributesNode.set(newItemAttributesNodeList); + keyStoreValues = keyStoreValues + .and("new-item-attributes", newIdentityAttributesNode); + } + } + + if (ldapMapping != null) { + keyStoreValues = keyStoreValues + .andOptional("alias-attribute", ldapMapping.getAliasAttribute()) + .andOptional("certificate-attribute", ldapMapping.getCertificateAttribute()) + .andOptional("certificate-type", ldapMapping.getCertificateType()) + .andOptional("certificate-chain-attribute", ldapMapping.getCertificateChainAttribute()) + .andOptional("certificate-chain-encoding", ldapMapping.getCertificateChainEncoding()); + } + + ops.add(keyStoreAddress, keyStoreValues); + + } + + public static final class Builder { + + private final String name; + private String dirContext; + private String searchPath; + private Boolean searchRecursive; + private Integer searchTimeLimit; + private String filterAlias; + private String filterCertificate; + private String filterIterate; + private LdapMapping ldapMapping; + private NewItemTemplate newItemTemplate; + private boolean replaceExisting; + + public Builder(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Name of the ldap-key-store must be specified as non empty value"); + } + this.name = name; + } + + public Builder dirContext(String dirContext) { + this.dirContext = dirContext; + return this; + } + + public Builder searchPath(String searchPath) { + this.searchPath = searchPath; + return this; + } + + public Builder searchRecursive(Boolean searchRecursive) { + this.searchRecursive = searchRecursive; + return this; + } + + public Builder searchTimeLimit(Integer searchTimeLimit) { + this.searchTimeLimit = searchTimeLimit; + return this; + } + + public Builder filterAlias(String filterAlias) { + this.filterAlias = filterAlias; + return this; + } + + public Builder filterCertificate(String filterCertificate) { + this.filterCertificate = filterCertificate; + return this; + } + + public Builder filterIterate(String filterIterate) { + this.filterIterate = filterIterate; + return this; + } + + public Builder ldapMapping(LdapMapping ldapMapping) { + this.ldapMapping = ldapMapping; + return this; + } + + public Builder newItemTemplate(NewItemTemplate newItemTemplate) { + this.newItemTemplate = newItemTemplate; + return this; + } + + public Builder replaceExisting() { + this.replaceExisting = true; + return this; + } + + + public AddLdapKeyStore build() { + + if (dirContext == null || dirContext.isEmpty()) { + throw new IllegalArgumentException("Dir context of the ldap-key-store must be specified as non empty value"); + } + if (searchPath == null || searchPath.isEmpty()) { + throw new IllegalArgumentException("Search path of the ldap-key-store must be specified as non empty value"); + } + + return new AddLdapKeyStore(this); + } + } + + public static final class LdapMapping { + + private String aliasAttribute; + private String certificateAttribute; + private String certificateType; + private String certificateChainAttribute; + private String certificateChainEncoding; + + private LdapMapping(LdapMappingBuilder builder) { + this.aliasAttribute = builder.aliasAttribute; + this.certificateAttribute = builder.certificateAttribute; + this.certificateType = builder.certificateType; + this.certificateChainAttribute = builder.certificateChainAttribute; + this.certificateChainEncoding = builder.certificateChainEncoding; + } + + public String getAliasAttribute() { + return aliasAttribute; + } + + public String getCertificateAttribute() { + return certificateAttribute; + } + + public String getCertificateType() { + return certificateType; + } + + public String getCertificateChainAttribute() { + return certificateChainAttribute; + } + + public String getCertificateChainEncoding() { + return certificateChainEncoding; + } + + } + + public static final class LdapMappingBuilder { + + private String aliasAttribute; + private String certificateAttribute; + private String certificateType; + private String certificateChainAttribute; + private String certificateChainEncoding; + + public LdapMappingBuilder aliasAttribute(String aliasAttribute) { + this.aliasAttribute = aliasAttribute; + return this; + } + + public LdapMappingBuilder certificateAttribute(String certificateAttribute) { + this.certificateAttribute = certificateAttribute; + return this; + } + + public LdapMappingBuilder certificateType(String certificateType) { + this.certificateType = certificateType; + return this; + } + + public LdapMappingBuilder certificateChainAttribute(String certificateChainAttribute) { + this.certificateChainAttribute = certificateChainAttribute; + return this; + } + + public LdapMappingBuilder certificateChainEncoding(String certificateChainEncoding) { + this.certificateChainEncoding = certificateChainEncoding; + return this; + } + + public LdapMapping build() { + return new LdapMapping(this); + } + } + + public static final class NewItemTemplate { + + private final List newItemAttributes; + private final String newItemPath; + private final String newItemRdn; + + + private NewItemTemplate(NewItemTemplateBuilder builder) { + this.newItemPath = builder.newItemPath; + this.newItemRdn = builder.newItemRdn; + this.newItemAttributes = builder.newItemAttributes; + } + + public String getNewItemPath() { + return newItemPath; + } + + public String getNewItemRdn() { + return newItemRdn; + } + + public List getNewItemAttributes() { + return newItemAttributes; + } + + } + + public static final class NewItemTemplateBuilder { + + private String newItemPath; + private String newItemRdn; + private List newItemAttributes = new ArrayList(); + + public NewItemTemplateBuilder newItemPath(String newItemPath) { + this.newItemPath = newItemPath; + return this; + } + + public NewItemTemplateBuilder newItemRdn(String newItemRdn) { + this.newItemRdn = newItemRdn; + return this; + } + + public NewItemTemplateBuilder addNewItemAttributes(NewItemAttribute... newItemAttributes) { + if (newItemAttributes == null) { + throw new IllegalArgumentException("NewItemAttributes added to ldap-key-store must not be null"); + } + Collections.addAll(this.newItemAttributes, newItemAttributes); + return this; + } + + public NewItemTemplate build() { + return new NewItemTemplate(this); + } + } + + public static final class NewItemAttribute { + + private final String name; + private List values; + + private NewItemAttribute(NewItemAttributeBuilder builder) { + this.name = builder.name; + this.values = builder.values; + } + + public String getName() { + return name; + } + + public List getValues() { + return values; + } + + } + + public static final class NewItemAttributeBuilder { + + private String name; + private List values = new ArrayList(); + + public NewItemAttributeBuilder name(String name) { + this.name = name; + return this; + } + + public NewItemAttributeBuilder addValues(String... values) { + if (values == null) { + throw new IllegalArgumentException("Values added to NewIdentityAttributesBuilder for ldap-key-store must not be null"); + } + Collections.addAll(this.values, values); + return this; + } + + public NewItemAttribute build() { + if (values == null || values.isEmpty()) { + throw new IllegalArgumentException("values must not be null and must include at least one entry"); + } + return new NewItemAttribute(this); + } + } + +} diff --git a/testsuite/standalone/src/test/java/org/wildfly/extras/creaper/commands/elytron/tls/AddLdapKeyStoreOnlineTest.java b/testsuite/standalone/src/test/java/org/wildfly/extras/creaper/commands/elytron/tls/AddLdapKeyStoreOnlineTest.java new file mode 100644 index 00000000..ee201d69 --- /dev/null +++ b/testsuite/standalone/src/test/java/org/wildfly/extras/creaper/commands/elytron/tls/AddLdapKeyStoreOnlineTest.java @@ -0,0 +1,262 @@ +package org.wildfly.extras.creaper.commands.elytron.tls; + +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.jboss.arquillian.junit.Arquillian; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wildfly.extras.creaper.commands.elytron.AbstractElytronOnlineTest; +import org.wildfly.extras.creaper.core.online.operations.Address; + +/** + * Not possible to test creation as adding resource create connection to ldap server. + * Only tests which don't create resource can be tested. E.g. validation checks. + */ +@RunWith(Arquillian.class) +public class AddLdapKeyStoreOnlineTest extends AbstractElytronOnlineTest { + + private static final String TEST_DIR_CONTEXT_NAME = "CreaperTestDirContext"; + private static final Address TEST_DIR_CONTEXT_ADDRESS = SUBSYSTEM_ADDRESS.and("dir-context", + TEST_DIR_CONTEXT_NAME); + private static final String TEST_LDAP_KEY_STORE_NAME = "CreaperTestLdapKeyStore"; + private static final String TEST_LDAP_KEY_STORE_NAME2 = "CreaperTestLdapKeyStore2"; + private static final Address TEST_LDAP_KEY_STORE_ADDRESS = SUBSYSTEM_ADDRESS .and("ldap-key-store", + TEST_LDAP_KEY_STORE_NAME); + private static final Address TEST_LDAP_KEY_STORE_ADDRESS2 = SUBSYSTEM_ADDRESS .and("ldap-key-store", + TEST_LDAP_KEY_STORE_NAME2); + + private static final String TEST_SEARCH_PATH = "CN=Users"; + +// https://issues.jboss.org/browse/JBEAP-6387 +// private final AddDirContext addDirContext = new AddDirContext.Builder(TEST_DIR_CONTEXT_NAME) +// .url("ldap://localhost") +// .principal("CN=user") +// .credential("password") +// .authenticationLevel(AddDirContext.AuthenticationLevel.SIMPLE) +// .build(); + +// https://issues.jboss.org/browse/JBEAP-6389 +// @After +// public void cleanup() throws Exception { +// ops.removeIfExists(TEST_LDAP_KEY_STORE_ADDRESS); +// ops.removeIfExists(TEST_DIR_CONTEXT_ADDRESS); +// administration.reloadIfRequired(); +// } + @AfterClass + public static void removeSubsystem() throws Exception { + } + +// https://issues.jboss.org/browse/JBEAP-6389 +// @Test +// public void addSimpleLdapKeyStore() throws Exception { +// +// client.apply(addDirContext); +// +// AddLdapKeyStore addLdapKeyStore = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) +// .dirContext(TEST_DIR_CONTEXT_NAME) +// .searchPath(TEST_SEARCH_PATH) +// .build(); +// +// client.apply(addLdapKeyStore); +// assertTrue("Ldap key store should be created", ops.exists(TEST_LDAP_KEY_STORE_ADDRESS)); +// } + +// @Test +// public void addTwoSimpleLdapKeyStores() throws Exception { +// client.apply(addDirContext); +// +// AddLdapKeyStore addLdapKeyStore = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) +// .dirContext(TEST_DIR_CONTEXT_NAME) +// .searchPath(TEST_SEARCH_PATH) +// .build(); +// +// AddLdapKeyStore addLdapKeyStore2 = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME2) +// .dirContext(TEST_DIR_CONTEXT_NAME) +// .searchPath(TEST_SEARCH_PATH) +// .build(); +// +// client.apply(addLdapKeyStore); +// client.apply(addLdapKeyStore2); +// +// assertTrue("Ldap key store should be created", ops.exists(TEST_LDAP_KEY_STORE_ADDRESS)); +// assertTrue("Second ldap key store should be created", ops.exists(TEST_LDAP_KEY_STORE_ADDRESS2)); +// } + + /* https://issues.jboss.org/browse/JBEAP-6389 + @Test + public void addFullLdapKeyStore() throws Exception { + + client.apply(addDirContext); + + AddLdapKeyStore addLdapKeyStore = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) + .dirContext(TEST_DIR_CONTEXT_NAME) + .searchPath(TEST_SEARCH_PATH) +// https://issues.jboss.org/browse/JBEAP-6387 +// .filterAlias("filter-alias1") +// .filterCertificate("filter-certificate1") +// .filterIterate("filter-iterate1") + .searchRecursive(true) + .searchTimeLimit(1) + // LdapMapping + .ldapMapping(new LdapMappingBuilder() + .aliasAttribute("aliasAttribute1") + .certificateAttribute("certificateAttribute1") + .certificateChainAttribute("certificateChainAttribute1") + .certificateChainEncoding("certificateChainEncoding1") + .certificateType("certificateType1") + .build()) + .newItemTemplate(new NewItemTemplateBuilder() +// .newItemPath("CN=user") +// .newItemRdn("DN=Users") + .addNewItemAttributes( + new NewItemAttributeBuilder() + .name("name1") + .addValues("value 1", "value2") + .build(), + new NewItemAttributeBuilder() + .name("name2") + .addValues("value3", "value4") + .build()) + .build()) + .build(); + + client.apply(addLdapKeyStore); + + assertTrue("Ldap key store should be created", ops.exists(TEST_LDAP_KEY_STORE_ADDRESS)); + + checkAttribute("dir-context", TEST_DIR_CONTEXT_NAME); + checkAttribute("search-path", TEST_SEARCH_PATH); +// checkAttribute("filter-alias", "ffilter-alias1"); +// checkAttribute("filter-certificate", "filter-certificate1"); +// checkAttribute("filter-iterate", "filter-iterate1"); + checkAttribute("search-recursive", "true"); + checkAttribute("search-time-limit", "1"); + // Ldap Mapping + checkAttribute("alias-attribute", "aliasAttribute1"); + checkAttribute("certificate-attribute", "certificateAttribute1"); + checkAttribute("certificate-chain-attribute", "certificateChainAttribute1"); + checkAttribute("certificate-chain-encoding", "certificateChainEncoding1"); + checkAttribute("certificate-type", "certificateType1"); + // New Item attribute +// checkAttribute("new-item-path", "CN=user"); +// checkAttribute("new-item-rdn", "DN=Users"); + checkAttribute("new-item-attributes[0].name", "name1"); + checkAttribute("new-item-attributes[0].value[0]", "value 1"); + checkAttribute("new-item-attributes[0].value[1]", "value2"); + checkAttribute("new-item-attributes[1].name", "name2"); + checkAttribute("new-item-attributes[1].value[0]", "value3"); + checkAttribute("new-item-attributes[1].value[1]", "value4"); + } +*/ + +// https://issues.jboss.org/browse/JBEAP-6389 +// @Test(expected = CommandFailedException.class) +// public void addDuplicateLdapKeyStoreNotAllowed() throws Exception { +// client.apply(addDirContext); +// +// AddLdapKeyStore addLdapKeyStore = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) +// .dirContext(TEST_DIR_CONTEXT_NAME) +// .searchPath(TEST_SEARCH_PATH) +// .build(); +// +// AddLdapKeyStore addLdapKeyStore2 = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) +// .dirContext(TEST_DIR_CONTEXT_NAME) +// .searchPath(TEST_SEARCH_PATH) +// .build(); +// +// client.apply(addLdapKeyStore); +// assertTrue("Ldap key store should be created", ops.exists(TEST_LDAP_KEY_STORE_ADDRESS)); +// client.apply(addLdapKeyStore2); +// fail("Ldap key store " + TEST_LDAP_KEY_STORE_NAME + "already exists in configuration, exception should be thrown"); +// } + + // https://issues.jboss.org/browse/JBEAP-6389 +// @Test(expected = CommandFailedException.class) +// public void addDuplicateLdapKeyStoreAllowed() throws Exception { +// client.apply(addDirContext); +// +// AddLdapKeyStore addLdapKeyStore = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) +// .dirContext(TEST_DIR_CONTEXT_NAME) +// .searchPath(TEST_SEARCH_PATH) +// .searchTimeLimit(1000) +// .build(); +// +// AddLdapKeyStore addLdapKeyStore2 = new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) +// .dirContext(TEST_DIR_CONTEXT_NAME) +// .searchPath(TEST_SEARCH_PATH) +// .searchTimeLimit(2000) +// .replaceExisting() +// .build(); +// +// client.apply(addLdapKeyStore); +// assertTrue("Ldap realm should be created", ops.exists(TEST_LDAP_KEY_STORE_ADDRESS)); +// client.apply(addLdapKeyStore2); +// assertTrue("Ldap realm should be created", ops.exists(TEST_LDAP_KEY_STORE_ADDRESS)); +// // check whether it was really rewritten +// checkAttribute("search-time-limit", "2000"); +// +// } + + + @Test(expected = IllegalArgumentException.class) + public void addKeyStore_nullName() throws Exception { + new AddLdapKeyStore.Builder(null) + .dirContext(TEST_DIR_CONTEXT_NAME) + .searchPath(TEST_SEARCH_PATH) + .build(); + fail("Creating command with null ldap keystore name should throw exception"); + } + + @Test(expected = IllegalArgumentException.class) + public void addKeyStore_emptyName() throws Exception { + new AddLdapKeyStore.Builder("") + .dirContext(TEST_DIR_CONTEXT_NAME) + .searchPath(TEST_SEARCH_PATH) + .build(); + fail("Creating command with empty ldap keystore name should throw exception"); + } + + @Test(expected = IllegalArgumentException.class) + public void addKeyStore_nullDirContext() throws Exception { + new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) + .dirContext(null) + .searchPath(TEST_SEARCH_PATH) + .build(); + fail("Creating command with null ldap keystore name should throw exception"); + } + + @Test(expected = IllegalArgumentException.class) + public void addKeyStore_emptyDirContext() throws Exception { + new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) + .dirContext("") + .searchPath(TEST_SEARCH_PATH) + .build(); + fail("Creating command with empty ldap keystore name should throw exception"); + } + + @Test(expected = IllegalArgumentException.class) + public void addKeyStore_nullSearchPath() throws Exception { + new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) + .dirContext(TEST_DIR_CONTEXT_NAME) + .searchPath(null) + .build(); + fail("Creating command with null ldap keystore search path should throw exception"); + } + + @Test(expected = IllegalArgumentException.class) + public void addKeyStore_emptySearchPath() throws Exception { + new AddLdapKeyStore.Builder(TEST_LDAP_KEY_STORE_NAME) + .dirContext(TEST_DIR_CONTEXT_NAME) + .searchPath("") + .build(); + fail("Creating command with empty ldap keystore search path should throw exception"); + } + + private void checkAttribute(String attribute, String expectedValue) throws IOException { + checkAttribute(TEST_LDAP_KEY_STORE_ADDRESS, attribute, expectedValue); + } + +}