Skip to content

Commit

Permalink
Log warning when unlicensed realms are skipped (#41832)
Browse files Browse the repository at this point in the history
Because realms are configured at node startup, but license levels can
change dynamically, it is possible to have a running node that has a
particular realm type configured, but that realm is not permitted under
the current license.
In this case the realm is silently ignored during authentication.

This commit adds a warning in the elasticsearch logs if authentication
fails, and there are realms that have been skipped due to licensing.
This message is not intended to imply that the realms could (or would)
have successfully authenticated the user, but they may help reduce
confusion about why authentication failed if the caller was expecting
the authentication to be handled by a particular realm that is in fact
unlicensed.

Backport of: #41778
  • Loading branch information
tvernum authored May 6, 2019
1 parent 13e9d4d commit ece62c6
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.cache.Cache;
import org.elasticsearch.common.cache.CacheBuilder;
import org.elasticsearch.common.collect.Tuple;
Expand All @@ -35,6 +36,7 @@
import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo;
import org.elasticsearch.xpack.core.security.support.Exceptions;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
Expand All @@ -43,7 +45,6 @@
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.audit.AuditUtil;
import org.elasticsearch.xpack.security.authc.support.RealmUserLookup;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;

import java.util.ArrayList;
Expand Down Expand Up @@ -484,6 +485,13 @@ private void consumeUser(User user, Map<Realm, Tuple<String, Exception>> message
final String cause = tuple.v2() == null ? "" : " (Caused by " + tuple.v2() + ")";
logger.warn("Authentication to realm {} failed - {}{}", realm.name(), message, cause);
});
List<Realm> unlicensedRealms = realms.getUnlicensedRealms();
if (unlicensedRealms.isEmpty() == false) {
logger.warn("Authentication failed using realms [{}]." +
" Realms [{}] were skipped because they are not permitted on the current license",
Strings.collectionToCommaDelimitedString(defaultOrderedRealmList),
Strings.collectionToCommaDelimitedString(unlicensedRealms));
}
listener.onFailure(request.authenticationFailed(authenticationToken));
} else {
threadContext.putTransient(AuthenticationResult.THREAD_CONTEXT_KEY, authenticationResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,8 @@
*/
package org.elasticsearch.xpack.security.authc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
Expand All @@ -39,6 +23,21 @@
import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
* Serves as a realms registry (also responsible for ordering the realms appropriately)
*/
Expand Down Expand Up @@ -119,6 +118,32 @@ public Iterator<Realm> iterator() {
}
}

/**
* Returns a list of realms that are configured, but are not permitted under the current license.
*/
public List<Realm> getUnlicensedRealms() {
// If auth is not allowed, then everything is unlicensed
if (licenseState.isAuthAllowed() == false) {
return Collections.unmodifiableList(realms);
}

AllowedRealmType allowedRealmType = licenseState.allowedRealmType();
// If all realms are allowed, then nothing is unlicensed
if (allowedRealmType == AllowedRealmType.ALL) {
return Collections.emptyList();
}

final List<Realm> allowedRealms = this.asList();
// Shortcut for the typical case, all the configured realms are allowed
if (allowedRealms.equals(this.realms.size())) {
return Collections.emptyList();
}

// Otherwise, we return anything in "all realms" that is not in the allowed realm list
List<Realm> unlicensed = realms.stream().filter(r -> allowedRealms.contains(r) == false).collect(Collectors.toList());
return Collections.unmodifiableList(unlicensed);
}

public Stream<Realm> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -104,6 +107,8 @@ public void testWithSettings() throws Exception {
assertThat(realm.name(), equalTo("realm_" + index));
i++;
}

assertThat(realms.getUnlicensedRealms(), empty());
}

public void testWithSettingsWhereDifferentRealmsHaveSameOrder() throws Exception {
Expand Down Expand Up @@ -142,6 +147,8 @@ public void testWithSettingsWhereDifferentRealmsHaveSameOrder() throws Exception
assertThat(realm.type(), equalTo("type_" + nameToRealmId.get(expectedRealmName)));
assertThat(realm.name(), equalTo(expectedRealmName));
}

assertThat(realms.getUnlicensedRealms(), empty());
}

public void testWithSettingsWithMultipleInternalRealmsOfSameType() throws Exception {
Expand Down Expand Up @@ -175,6 +182,8 @@ public void testWithEmptySettings() throws Exception {
assertThat(realm.type(), equalTo(NativeRealmSettings.TYPE));
assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE));
assertThat(iter.hasNext(), is(false));

assertThat(realms.getUnlicensedRealms(), empty());
}

public void testUnlicensedWithOnlyCustomRealms() throws Exception {
Expand Down Expand Up @@ -209,6 +218,8 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception {
i++;
}

assertThat(realms.getUnlicensedRealms(), empty());

when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.DEFAULT);

iter = realms.iterator();
Expand All @@ -225,6 +236,18 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception {
assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE));
assertThat(iter.hasNext(), is(false));

assertThat(realms.getUnlicensedRealms(), iterableWithSize(randomRealmTypesCount));
iter = realms.getUnlicensedRealms().iterator();
i = 0;
while (iter.hasNext()) {
realm = iter.next();
assertThat(realm.order(), equalTo(i));
int index = orderToIndex.get(i);
assertThat(realm.type(), equalTo("type_" + index));
assertThat(realm.name(), equalTo("realm_" + index));
i++;
}

when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE);

iter = realms.iterator();
Expand All @@ -240,6 +263,18 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception {
assertThat(realm.type(), equalTo(NativeRealmSettings.TYPE));
assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE));
assertThat(iter.hasNext(), is(false));

assertThat(realms.getUnlicensedRealms(), iterableWithSize(randomRealmTypesCount));
iter = realms.getUnlicensedRealms().iterator();
i = 0;
while (iter.hasNext()) {
realm = iter.next();
assertThat(realm.order(), equalTo(i));
int index = orderToIndex.get(i);
assertThat(realm.type(), equalTo("type_" + index));
assertThat(realm.name(), equalTo("realm_" + index));
i++;
}
}

public void testUnlicensedWithInternalRealms() throws Exception {
Expand All @@ -266,6 +301,7 @@ public void testUnlicensedWithInternalRealms() throws Exception {
types.add(realm.type());
}
assertThat(types, contains("ldap", "type_0"));
assertThat(realms.getUnlicensedRealms(), empty());

when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.DEFAULT);
iter = realms.iterator();
Expand All @@ -280,6 +316,11 @@ public void testUnlicensedWithInternalRealms() throws Exception {
}
assertThat(i, is(1));

assertThat(realms.getUnlicensedRealms(), iterableWithSize(1));
realm = realms.getUnlicensedRealms().get(0);
assertThat(realm.type(), equalTo("type_0"));
assertThat(realm.name(), equalTo("custom"));

when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE);
iter = realms.iterator();
assertThat(iter.hasNext(), is(true));
Expand All @@ -294,6 +335,14 @@ public void testUnlicensedWithInternalRealms() throws Exception {
assertThat(realm.type(), equalTo(NativeRealmSettings.TYPE));
assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE));
assertThat(iter.hasNext(), is(false));

assertThat(realms.getUnlicensedRealms(), iterableWithSize(2));
realm = realms.getUnlicensedRealms().get(0);
assertThat(realm.type(), equalTo("ldap"));
assertThat(realm.name(), equalTo("foo"));
realm = realms.getUnlicensedRealms().get(1);
assertThat(realm.type(), equalTo("type_0"));
assertThat(realm.name(), equalTo("custom"));
}

public void testUnlicensedWithNativeRealmSettings() throws Exception {
Expand All @@ -317,6 +366,7 @@ public void testUnlicensedWithNativeRealmSettings() throws Exception {
realm = iter.next();
assertThat(realm.type(), is(type));
assertThat(iter.hasNext(), is(false));
assertThat(realms.getUnlicensedRealms(), empty());

when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE);
iter = realms.iterator();
Expand All @@ -327,6 +377,11 @@ public void testUnlicensedWithNativeRealmSettings() throws Exception {
realm = iter.next();
assertThat(realm.type(), is(type));
assertThat(iter.hasNext(), is(false));

assertThat(realms.getUnlicensedRealms(), iterableWithSize(1));
realm = realms.getUnlicensedRealms().get(0);
assertThat(realm.type(), equalTo("ldap"));
assertThat(realm.name(), equalTo("foo"));
}

public void testUnlicensedWithNonStandardRealms() throws Exception {
Expand All @@ -346,6 +401,7 @@ public void testUnlicensedWithNonStandardRealms() throws Exception {
realm = iter.next();
assertThat(realm.type(), is(selectedRealmType));
assertThat(iter.hasNext(), is(false));
assertThat(realms.getUnlicensedRealms(), empty());

when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.DEFAULT);
iter = realms.iterator();
Expand All @@ -360,6 +416,11 @@ public void testUnlicensedWithNonStandardRealms() throws Exception {
assertThat(realm.type(), is(NativeRealmSettings.TYPE));
assertThat(iter.hasNext(), is(false));

assertThat(realms.getUnlicensedRealms(), iterableWithSize(1));
realm = realms.getUnlicensedRealms().get(0);
assertThat(realm.type(), equalTo(selectedRealmType));
assertThat(realm.name(), equalTo("foo"));

when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE);
iter = realms.iterator();
assertThat(iter.hasNext(), is(true));
Expand All @@ -372,6 +433,11 @@ public void testUnlicensedWithNonStandardRealms() throws Exception {
realm = iter.next();
assertThat(realm.type(), is(NativeRealmSettings.TYPE));
assertThat(iter.hasNext(), is(false));

assertThat(realms.getUnlicensedRealms(), iterableWithSize(1));
realm = realms.getUnlicensedRealms().get(0);
assertThat(realm.type(), equalTo(selectedRealmType));
assertThat(realm.name(), equalTo("foo"));
}

public void testDisabledRealmsAreNotAdded() throws Exception {
Expand Down Expand Up @@ -422,6 +488,11 @@ public void testDisabledRealmsAreNotAdded() throws Exception {
}

assertThat(count, equalTo(orderToIndex.size()));
assertThat(realms.getUnlicensedRealms(), empty());

// check that disabled realms are not included in unlicensed realms
when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE);
assertThat(realms.getUnlicensedRealms(), hasSize(orderToIndex.size()));
}

public void testAuthcAuthzDisabled() throws Exception {
Expand Down

0 comments on commit ece62c6

Please sign in to comment.