Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log warning when cache hasher not compliant with FIPS #86740

Merged
merged 39 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a2f3d67
Warn on non-compliant cache hasher in FIPS
n1v0lg May 12, 2022
30d433a
WIP
n1v0lg May 12, 2022
425c1cb
WIP tests
n1v0lg May 12, 2022
c5087dc
WIP tests
n1v0lg May 12, 2022
a3c29bc
Test warning logged
n1v0lg May 12, 2022
ebca8d0
Negative tests
n1v0lg May 12, 2022
e4f32cb
Throws and logs test
n1v0lg May 12, 2022
1b954fb
Name clean up
n1v0lg May 12, 2022
56e2263
Nit
n1v0lg May 12, 2022
ad2a63f
Undo some changes
n1v0lg May 12, 2022
a068256
Undo
n1v0lg May 12, 2022
93037e7
Update docs/changelog/86740.yaml
n1v0lg May 12, 2022
daf1b17
Nit
n1v0lg May 12, 2022
d878548
Merge branch 'enhance/warn-on-md5-in-fips-mode' of github.com:n1v0lg/…
n1v0lg May 12, 2022
bba8667
Tweak changelog
n1v0lg May 12, 2022
2adacbb
Fix test
n1v0lg May 12, 2022
051b45a
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 17, 2022
6749748
Clean up
n1v0lg May 17, 2022
7533231
Expect logs
n1v0lg May 17, 2022
37d6515
Better message
n1v0lg May 17, 2022
2f8cd28
ssha256 first
n1v0lg May 17, 2022
e750523
Plug in realm ref
n1v0lg May 17, 2022
209a138
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 17, 2022
d304f45
Use interface
n1v0lg May 17, 2022
5b46f15
Address review
n1v0lg May 18, 2022
e6efbf6
Changelog
n1v0lg May 18, 2022
98a0238
Grammar
n1v0lg May 18, 2022
c63ef53
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
elasticmachine May 18, 2022
f55d94a
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 18, 2022
c4ba33c
Merge branch 'enhance/warn-on-md5-in-fips-mode' of github.com:n1v0lg/…
n1v0lg May 18, 2022
26808b4
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
elasticmachine May 18, 2022
7a53569
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 18, 2022
db324d3
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 18, 2022
2099787
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 19, 2022
2812bac
Feedback
n1v0lg May 19, 2022
83050ee
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 19, 2022
37d3624
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg May 31, 2022
49d1fa8
Only recommend ssha256
n1v0lg May 31, 2022
2fe1e42
Merge branch 'master' into enhance/warn-on-md5-in-fips-mode
n1v0lg Jun 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/86740.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 86740
summary: Log warning when hash function used by cache is not recommended in FIPS mode
area: FIPS
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,20 @@ static void validateForFips(Settings settings) {
+ " ] setting."
);
}
final var cacheHashAlgoSettings = settings.filter(k -> k.endsWith(".cache.hash_algo"));
cacheHashAlgoSettings.keySet().forEach((key) -> {
final var setting = cacheHashAlgoSettings.get(key);
assert setting != null;
final var hashAlgoName = setting.toLowerCase(Locale.ROOT);
if (hashAlgoName.equals("ssha256") == false && hashAlgoName.startsWith("pbkdf2") == false) {
logger.warn(
"[{}] is not recommended for in-memory credential hashing in a FIPS 140 JVM. "
+ "The recommended hasher for [{}] is SSHA256.",
setting,
key
);
}
});

if (validationErrors.isEmpty() == false) {
final StringBuilder sb = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,13 @@
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import org.elasticsearch.xpack.core.security.SecurityField;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
Expand All @@ -69,10 +73,12 @@
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.authc.service.CachingServiceAccountTokenStore;
import org.hamcrest.Matchers;
import org.junit.After;

Expand All @@ -94,6 +100,7 @@

import static java.util.Collections.emptyMap;
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_FORMAT_SETTING;
import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey;
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.INTERNAL_MAIN_INDEX_FORMAT;
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS;
import static org.hamcrest.Matchers.containsInAnyOrder;
Expand Down Expand Up @@ -600,7 +607,7 @@ public void testValidateForFipsMultipleValidationErrors() {
assertThat(iae.getMessage(), containsString("Only PBKDF2 is allowed for password hashing in a FIPS 140 JVM."));
}

public void testValidateForFipsNoErrors() {
public void testValidateForFipsNoErrorsOrLogs() throws IllegalAccessException {
final Settings settings = Settings.builder()
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
.put("xpack.security.transport.ssl.keystore.path", "path/to/keystore")
Expand All @@ -611,15 +618,67 @@ public void testValidateForFipsNoErrors() {
Hasher.getAvailableAlgoStoredHash().stream().filter(alg -> alg.startsWith("pbkdf2")).collect(Collectors.toList())
)
)
.put(
XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),
randomFrom(
Hasher.getAvailableAlgoStoredHash().stream().filter(alg -> alg.startsWith("pbkdf2")).collect(Collectors.toList())
)
)
.put(
ApiKeyService.CACHE_HASH_ALGO_SETTING.getKey(),
randomFrom(
Hasher.getAvailableAlgoCacheHash()
.stream()
.filter(alg -> alg.startsWith("pbkdf2") || alg.equals("ssha256"))
.collect(Collectors.toList())
)
)
.build();
Security.validateForFips(settings);
// no exception thrown
expectLogs(Security.class, Collections.emptyList(), () -> Security.validateForFips(settings));
}

public void testValidateForFipsNonFipsCompliantHashAlgoWarningLog() throws IllegalAccessException {
String key = randomCacheHashSetting();
final Settings settings = Settings.builder()
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
.put(key, randomNonFipsCompliantCacheHash())
.build();
expectLogs(Security.class, List.of(logEventForNonCompliantHash(key)), () -> Security.validateForFips(settings));
}

public void testValidateForMultipleNonFipsCompliantHashAlgoWarningLogs() throws IllegalAccessException {
String firstKey = randomCacheHashSetting();
String secondKey = randomValueOtherThan(firstKey, this::randomCacheHashSetting);
final Settings settings = Settings.builder()
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
.put(firstKey, randomNonFipsCompliantCacheHash())
.put(secondKey, randomNonFipsCompliantCacheHash())
.build();
expectLogs(
Security.class,
List.of(logEventForNonCompliantHash(firstKey), logEventForNonCompliantHash(secondKey)),
() -> Security.validateForFips(settings)
);
}

public void testValidateForFipsValidationErrorAndWarningLogs() throws IllegalAccessException {
String firstKey = randomCacheHashSetting();
String secondKey = randomValueOtherThan(firstKey, this::randomCacheHashSetting);
final Settings settings = Settings.builder()
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
.put(firstKey, randomNonFipsCompliantCacheHash())
.put(secondKey, randomNonFipsCompliantCacheHash())
.put("xpack.security.transport.ssl.keystore.path", "path/to/keystore")
.build();
expectLogs(Security.class, List.of(logEventForNonCompliantHash(firstKey), logEventForNonCompliantHash(secondKey)), () -> {
final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings));
assertThat(iae.getMessage(), containsString("JKS Keystores cannot be used in a FIPS 140 compliant JVM"));
});
}

public void testValidateForFipsNoErrorsForDefaultSettings() {
public void testValidateForFipsNoErrorsOrLogsForDefaultSettings() throws IllegalAccessException {
final Settings settings = Settings.builder().put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true).build();
Security.validateForFips(settings);
// no exception thrown
expectLogs(Security.class, Collections.emptyList(), () -> Security.validateForFips(settings));
}

public void testLicenseUpdateFailureHandlerUpdate() throws Exception {
Expand Down Expand Up @@ -748,15 +807,59 @@ public void testSecurityStatusMessageInLog() throws Exception {
}
}

private void logAndFail(Exception e) {
logger.error("unexpected exception", e);
fail("unexpected exception " + e.getMessage());
}

private void verifyHasAuthenticationHeaderValue(Exception e, String... expectedValues) {
assertThat(e, instanceOf(ElasticsearchSecurityException.class));
assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), notNullValue());
assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), hasSize(expectedValues.length));
assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), containsInAnyOrder(expectedValues));
}

private String randomCacheHashSetting() {
Authentication.RealmRef ref = AuthenticationTestHelper.randomRealmRef(randomBoolean());
return randomFrom(
getFullSettingKey(
new RealmConfig.RealmIdentifier(ref.getType(), ref.getName()),
CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING
),
CachingServiceAccountTokenStore.CACHE_HASH_ALGO_SETTING.getKey(),
ApiKeyService.CACHE_HASH_ALGO_SETTING.getKey()
);
}

private String randomNonFipsCompliantCacheHash() {
return randomFrom(
Hasher.getAvailableAlgoCacheHash()
.stream()
.filter(alg -> (alg.startsWith("pbkdf2") || alg.equals("ssha256")) == false)
.collect(Collectors.toList())
);
}

private MockLogAppender.SeenEventExpectation logEventForNonCompliantHash(String settingKey) {
return new MockLogAppender.SeenEventExpectation(
"hash not fips compliant",
Security.class.getName(),
Level.WARN,
"[*] is not recommended for in-memory credential hashing in a FIPS 140 JVM. "
+ "The recommended hasher for ["
+ settingKey
+ "] is SSHA256."
);
}

private void expectLogs(Class<?> clazz, List<MockLogAppender.LoggingExpectation> expected, Runnable runnable)
throws IllegalAccessException {
final MockLogAppender mockAppender = new MockLogAppender();
final Logger logger = LogManager.getLogger(clazz);
mockAppender.start();
try {
Loggers.addAppender(logger, mockAppender);
expected.forEach(mockAppender::addExpectation);
runnable.run();
mockAppender.assertAllExpectationsMatched();
} finally {
Loggers.removeAppender(logger, mockAppender);
mockAppender.stop();
}
}
}