Skip to content

Commit bccf988

Browse files
authored
Run active directory tests against a samba4 fixture (elastic/x-pack-elasticsearch#4067)
This commit adds a Samba4 test fixture that acts as a domain controller and has the same contents as the cloud active directory instance that we previously used for tests. The tests also support reading information from environment variables so that they can be run against a real active directory instance in our CI builds. In addition, this commit also fixes a few issues that surfaced when making this change. The first is a change in the base DN that is searched when performing down-level authentication. The base DN is now the configuration object instead of the domain DN. This change was required due to the original producing unnecessary referrals, which we cannot easily follow when running against this test figure. Referrals cannot easily be followed as they are returned by the ldap server with an unresolvable DNS name unless the host points to the samba4 instance for DNS. The port returned in the referral url is the one samba is bound to, which differs from the port that is forwarded to the host by the test fixture. The other issue that is resolved by this change is the addition of settings that allow specifying non-standard ports for active directory. This is needed for down-level authentication as we may need to query the regular port of active directory instead of the global catalog port as the configuration object is not replicated to the global catalog. relates elastic/x-pack-elasticsearch#185 Relates elastic/x-pack-elasticsearch#3800 Original commit: elastic/x-pack-elasticsearch@883c742
1 parent 4c78ede commit bccf988

File tree

28 files changed

+478
-191
lines changed

28 files changed

+478
-191
lines changed

plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/ActiveDirectorySessionFactorySettings.java

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ public final class ActiveDirectorySessionFactorySettings {
2020
public static final String AD_UPN_USER_SEARCH_FILTER_SETTING = "user_search.upn_filter";
2121
public static final String AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING = "user_search.down_level_filter";
2222
public static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope";
23+
public static final Setting<Integer> AD_LDAP_PORT_SETTING = Setting.intSetting("port.ldap", 389, Setting.Property.NodeScope);
24+
public static final Setting<Integer> AD_LDAPS_PORT_SETTING = Setting.intSetting("port.ldaps", 636, Setting.Property.NodeScope);
25+
public static final Setting<Integer> AD_GC_LDAP_PORT_SETTING = Setting.intSetting("port.gc_ldap", 3268, Setting.Property.NodeScope);
26+
public static final Setting<Integer> AD_GC_LDAPS_PORT_SETTING = Setting.intSetting("port.gc_ldaps", 3269, Setting.Property.NodeScope);
2327
public static final Setting<Boolean> POOL_ENABLED = Setting.boolSetting("user_search.pool.enabled",
2428
settings -> Boolean.toString(PoolingSessionFactorySettings.BIND_DN.exists(settings)), Setting.Property.NodeScope);
2529

@@ -36,6 +40,10 @@ public static Set<Setting<?>> getSettings() {
3640
settings.add(Setting.simpleString(AD_UPN_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope));
3741
settings.add(Setting.simpleString(AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope));
3842
settings.add(Setting.simpleString(AD_USER_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope));
43+
settings.add(AD_LDAP_PORT_SETTING);
44+
settings.add(AD_LDAPS_PORT_SETTING);
45+
settings.add(AD_GC_LDAP_PORT_SETTING);
46+
settings.add(AD_GC_LDAPS_PORT_SETTING);
3947
settings.add(POOL_ENABLED);
4048
settings.addAll(PoolingSessionFactorySettings.getSettings());
4149
return settings;

plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public void testReloadingKeyStore() throws Exception {
128128
assertThat(aliases[0], is("key"));
129129
};
130130
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks = (updatedTrustManager, config) -> {
131-
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(4));
131+
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5));
132132
};
133133
validateSSLConfigurationIsReloaded(settings, env, keyManagerPreChecks, trustManagerPreChecks, modifier, keyManagerPostChecks,
134134
trustManagerPostChecks);
@@ -244,7 +244,7 @@ public void testReloadingTrustStore() throws Exception {
244244
};
245245

246246
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks = (updatedTrustManager, config) -> {
247-
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5));
247+
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(6));
248248
};
249249

250250
validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks);

plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ public void testReadCertificateInformation() throws Exception {
485485

486486
final SSLService sslService = new SSLService(settings, env);
487487
final List<CertificateInfo> certificates = new ArrayList<>(sslService.getLoadedCertificates());
488-
assertThat(certificates, iterableWithSize(7));
488+
assertThat(certificates, iterableWithSize(8));
489489
Collections.sort(certificates,
490490
Comparator.comparing((CertificateInfo c) -> c.alias() == null ? "" : c.alias()).thenComparing(CertificateInfo::path));
491491

@@ -508,6 +508,15 @@ public void testReadCertificateInformation() throws Exception {
508508
assertThat(cert.expiry(), equalTo(DateTime.parse("2029-08-27T16:32:42Z")));
509509
assertThat(cert.hasPrivateKey(), equalTo(false));
510510

511+
cert = iterator.next();
512+
assertThat(cert.alias(), equalTo("mykey"));
513+
assertThat(cert.path(), equalTo(jksPath.toString()));
514+
assertThat(cert.format(), equalTo("jks"));
515+
assertThat(cert.serialNumber(), equalTo("3151a81eec8d4e34c56a8466a8510bcfbe63cc31"));
516+
assertThat(cert.subjectDn(), equalTo("CN=samba4"));
517+
assertThat(cert.expiry(), equalTo(DateTime.parse("2021-02-14T17:49:11.000Z")));
518+
assertThat(cert.hasPrivateKey(), equalTo(false));
519+
511520
cert = iterator.next();
512521
assertThat(cert.alias(), equalTo("openldap"));
513522
assertThat(cert.path(), equalTo(jksPath.toString()));

plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java

+28-10
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
6262
final DownLevelADAuthenticator downLevelADAuthenticator;
6363
final UpnADAuthenticator upnADAuthenticator;
6464

65+
private final int ldapPort;
66+
6567
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException {
6668
super(config, sslService, new ActiveDirectoryGroupsResolver(config.settings()),
6769
ActiveDirectorySessionFactorySettings.POOL_ENABLED, () -> {
@@ -88,18 +90,24 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
8890
+ "] setting for active directory");
8991
}
9092
String domainDN = buildDnFromDomain(domainName);
93+
ldapPort = ActiveDirectorySessionFactorySettings.AD_LDAP_PORT_SETTING.get(settings);
94+
final int ldapsPort = ActiveDirectorySessionFactorySettings.AD_LDAPS_PORT_SETTING.get(settings);
95+
final int gcLdapPort = ActiveDirectorySessionFactorySettings.AD_GC_LDAP_PORT_SETTING.get(settings);
96+
final int gcLdapsPort = ActiveDirectorySessionFactorySettings.AD_GC_LDAPS_PORT_SETTING.get(settings);
97+
9198
defaultADAuthenticator = new DefaultADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
9299
metaDataResolver, domainDN, threadPool);
93100
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
94-
metaDataResolver, domainDN, sslService, threadPool);
101+
metaDataResolver, domainDN, sslService, threadPool, ldapPort, ldapsPort, gcLdapPort, gcLdapsPort);
95102
upnADAuthenticator = new UpnADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
96103
metaDataResolver, domainDN, threadPool);
97104

98105
}
99106

100107
@Override
101108
protected List<String> getDefaultLdapUrls(Settings settings) {
102-
return Collections.singletonList("ldap://" + settings.get(ActiveDirectorySessionFactorySettings.AD_DOMAIN_NAME_SETTING) + ":389");
109+
return Collections.singletonList("ldap://" + settings.get(ActiveDirectorySessionFactorySettings.AD_DOMAIN_NAME_SETTING) +
110+
":" + ldapPort);
103111
}
104112

105113
@Override
@@ -361,16 +369,24 @@ static class DownLevelADAuthenticator extends ADAuthenticator {
361369
final Settings settings;
362370
final SSLService sslService;
363371
final RealmConfig config;
372+
private final int ldapPort;
373+
private final int ldapsPort;
374+
private final int gcLdapPort;
375+
private final int gcLdapsPort;
364376

365377
DownLevelADAuthenticator(RealmConfig config, TimeValue timeout, boolean ignoreReferralErrors, Logger logger,
366378
GroupsResolver groupsResolver, LdapMetaDataResolver metaDataResolver, String domainDN, SSLService sslService,
367-
ThreadPool threadPool) {
379+
ThreadPool threadPool, int ldapPort, int ldapsPort, int gcLdapPort, int gcLdapsPort) {
368380
super(config, timeout, ignoreReferralErrors, logger, groupsResolver, metaDataResolver, domainDN,
369381
ActiveDirectorySessionFactorySettings.AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, DOWN_LEVEL_FILTER, threadPool);
370382
this.domainDN = domainDN;
371383
this.settings = config.settings();
372384
this.sslService = sslService;
373385
this.config = config;
386+
this.ldapPort = ldapPort;
387+
this.ldapsPort = ldapsPort;
388+
this.gcLdapPort = gcLdapPort;
389+
this.gcLdapsPort = gcLdapsPort;
374390
}
375391

376392
@Override
@@ -400,7 +416,8 @@ void netBiosDomainNameToDn(LDAPInterface ldapInterface, String netBiosDomainName
400416
if (cachedName != null) {
401417
listener.onResponse(cachedName);
402418
} else if (usingGlobalCatalog(ldapInterface) == false) {
403-
search(ldapInterface, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds, ignoreReferralErrors,
419+
search(ldapInterface, "CN=Configuration," + domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds,
420+
ignoreReferralErrors,
404421
ActionListener.wrap((results) -> handleSearchResults(results, netBiosDomainName, domainNameCache, listener),
405422
listener::onFailure),
406423
"ncname");
@@ -417,16 +434,17 @@ void netBiosDomainNameToDn(LDAPInterface ldapInterface, String netBiosDomainName
417434
final LDAPConnection finalLdapConnection = ldapConnection;
418435
final LDAPConnection searchConnection = LdapUtils.privilegedConnect(
419436
() -> new LDAPConnection(finalLdapConnection.getSocketFactory(), connectionOptions(config, sslService, logger),
420-
finalLdapConnection.getConnectedAddress(), finalLdapConnection.getSSLSession() != null ? 636 : 389));
437+
finalLdapConnection.getConnectedAddress(),
438+
finalLdapConnection.getSSLSession() != null ? ldapsPort : ldapPort));
421439
final byte[] passwordBytes = CharArrays.toUtf8Bytes(password.getChars());
422440
final SimpleBindRequest bind = bindDN.isEmpty()
423441
? new SimpleBindRequest(username, passwordBytes)
424442
: new SimpleBindRequest(bindDN, bindPassword);
425443
LdapUtils.maybeForkThenBind(searchConnection, bind, threadPool, new ActionRunnable<String>(listener) {
426444
@Override
427445
protected void doRun() throws Exception {
428-
search(searchConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds,
429-
ignoreReferralErrors,
446+
search(searchConnection, "CN=Configuration," + domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
447+
timeLimitSeconds, ignoreReferralErrors,
430448
ActionListener.wrap(
431449
results -> {
432450
IOUtils.close(searchConnection);
@@ -473,7 +491,7 @@ static void handleSearchResults(List<SearchResultEntry> results, String netBiosD
473491
}
474492
}
475493

476-
static boolean usingGlobalCatalog(LDAPInterface ldap) throws LDAPException {
494+
boolean usingGlobalCatalog(LDAPInterface ldap) throws LDAPException {
477495
if (ldap instanceof LDAPConnection) {
478496
return usingGlobalCatalog((LDAPConnection) ldap);
479497
} else {
@@ -490,8 +508,8 @@ static boolean usingGlobalCatalog(LDAPInterface ldap) throws LDAPException {
490508
}
491509
}
492510

493-
private static boolean usingGlobalCatalog(LDAPConnection ldapConnection) {
494-
return ldapConnection.getConnectedPort() == 3268 || ldapConnection.getConnectedPort() == 3269;
511+
private boolean usingGlobalCatalog(LDAPConnection ldapConnection) {
512+
return ldapConnection.getConnectedPort() == gcLdapPort || ldapConnection.getConnectedPort() == gcLdapsPort;
495513
}
496514
}
497515

plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java

-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.elasticsearch.common.util.concurrent.ThreadContext;
1414
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
1515
import org.elasticsearch.env.TestEnvironment;
16-
import org.elasticsearch.test.junit.annotations.Network;
1716
import org.elasticsearch.threadpool.TestThreadPool;
1817
import org.elasticsearch.threadpool.ThreadPool;
1918
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
@@ -28,12 +27,10 @@
2827
import java.util.List;
2928
import java.util.concurrent.ExecutionException;
3029

31-
import static org.hamcrest.Matchers.anyOf;
3230
import static org.hamcrest.Matchers.contains;
3331
import static org.hamcrest.Matchers.containsString;
3432
import static org.hamcrest.Matchers.instanceOf;
3533
import static org.hamcrest.Matchers.is;
36-
import static org.hamcrest.Matchers.lessThan;
3734

3835
public class LdapSessionFactoryTests extends LdapTestCase {
3936
private Settings globalSettings;

plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
import static org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings.HOSTNAME_VERIFICATION_SETTING;
4646
import static org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings.URLS_SETTING;
47-
//
47+
4848
public abstract class LdapTestCase extends ESTestCase {
4949

5050
private static final String USER_DN_TEMPLATES_SETTING_KEY = LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING.getKey();
@@ -120,8 +120,7 @@ public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate
120120
.put(SessionFactorySettings.TIMEOUT_TCP_CONNECTION_SETTING, TimeValue.timeValueSeconds(1L))
121121
.put(SessionFactorySettings.IGNORE_REFERRAL_ERRORS_SETTING.getKey(), ignoreReferralErrors)
122122
.put("group_search.base_dn", groupSearchBase)
123-
.put("group_search.scope", scope)
124-
.put("ssl.verification_mode", VerificationMode.CERTIFICATE);
123+
.put("group_search.scope", scope);
125124
if (serverSetType != null) {
126125
builder.put(LdapLoadBalancingSettings.LOAD_BALANCE_SETTINGS + "." +
127126
LdapLoadBalancingSettings.LOAD_BALANCE_TYPE_SETTING, serverSetType.toString());

qa/openldap-tests/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import static org.hamcrest.Matchers.notNullValue;
2222

2323
@SuppressWarnings("unchecked")
24-
public class SearchGroupsResolverTests extends GroupsResolverTestCase {
24+
public class SearchGroupsResolverTests extends GroupsResolverTestCase {
2525

2626
private static final String BRUCE_BANNER_DN = "uid=hulk,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
2727

qa/third-party/active-directory/build.gradle

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1+
Project smbFixtureProject = xpackProject("test:smb-fixture")
2+
evaluationDependsOn(smbFixtureProject.path)
3+
4+
apply plugin: 'elasticsearch.vagrantsupport'
15
apply plugin: 'elasticsearch.standalone-test'
26

37
dependencies {
48
testCompile project(xpackModule('security'))
59
testCompile project(path: xpackModule('security'), configuration: 'testArtifacts')
610
}
711

8-
// add test resources from security, so certificate tool tests can use example certs
12+
// add test resources from security, so tests can use example certs
913
sourceSets.test.resources.srcDirs(project(xpackModule('security')).sourceSets.test.resources.srcDirs)
14+
compileTestJava.options.compilerArgs << "-Xlint:-deprecation,-rawtypes,-serial,-try,-unchecked"
1015

11-
// we have to repeate these patterns because the security test resources are effectively in the src of this project
16+
// we have to repeat these patterns because the security test resources are effectively in the src of this project
1217
forbiddenPatterns {
1318
exclude '**/*.key'
1419
exclude '**/*.p12'
@@ -21,7 +26,22 @@ test {
2126
* other if we allow them to set the number of available processors as it's set-once in Netty.
2227
*/
2328
systemProperty 'es.set.netty.runtime.available.processors', 'false'
29+
include '**/*IT.class'
30+
include '**/*Tests.class'
2431
}
2532

2633
// these are just tests, no need to audit
2734
thirdPartyAudit.enabled = false
35+
36+
task smbFixture {
37+
dependsOn "vagrantCheckVersion", "virtualboxCheckVersion", smbFixtureProject.up
38+
}
39+
40+
if (project.rootProject.vagrantSupported) {
41+
if (project.hasProperty('useExternalAD') == false) {
42+
test.dependsOn smbFixture
43+
test.finalizedBy smbFixtureProject.halt
44+
}
45+
} else {
46+
test.enabled = false
47+
}

0 commit comments

Comments
 (0)