Skip to content

Commit fa28d1c

Browse files
authored
Security: use x-pack config files when present (#33688)
In versions prior to 6.3, x-pack configuration files were kept within a x-pack subdirectory. In 6.3 these files were moved to the config directory with a backwards compatibilty layer to look for the files in the old location if file does not exist in the new location. This works for files that are not pre-created for the user, but in the case of security there are several files created by default. This change updates the security code to check if the file in the new location is a default file that has not been modified. If it is a default file, the code will also see if a file exists in the pre 6.3 location and use it instead. Closes #33464
1 parent 72d741e commit fa28d1c

File tree

7 files changed

+141
-16
lines changed

7 files changed

+141
-16
lines changed

x-pack/plugin/security/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ artifacts {
137137
archives jar
138138
testArtifacts testJar
139139
}
140+
141+
processResources {
142+
from('src/main/config') {
143+
into 'config'
144+
}
145+
}
146+
140147
sourceSets.test.resources {
141148
srcDir '../core/src/test/resources'
142149
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package org.elasticsearch.xpack.security;
77

8+
import org.apache.logging.log4j.LogManager;
89
import org.apache.logging.log4j.Logger;
910
import org.apache.lucene.util.SetOnce;
1011
import org.elasticsearch.Version;
@@ -27,8 +28,8 @@
2728
import org.elasticsearch.common.inject.Module;
2829
import org.elasticsearch.common.inject.util.Providers;
2930
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
31+
import org.elasticsearch.common.logging.DeprecationLogger;
3032
import org.elasticsearch.common.logging.LoggerMessageFormat;
31-
import org.elasticsearch.common.logging.Loggers;
3233
import org.elasticsearch.common.network.NetworkModule;
3334
import org.elasticsearch.common.network.NetworkService;
3435
import org.elasticsearch.common.regex.Regex;
@@ -221,7 +222,10 @@
221222
import org.joda.time.DateTimeZone;
222223

223224
import java.io.IOException;
225+
import java.io.InputStream;
226+
import java.io.UncheckedIOException;
224227
import java.nio.charset.StandardCharsets;
228+
import java.nio.file.Files;
225229
import java.nio.file.Path;
226230
import java.time.Clock;
227231
import java.util.ArrayList;
@@ -253,7 +257,8 @@
253257
public class Security extends Plugin implements ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin, DiscoveryPlugin, MapperPlugin,
254258
ExtensiblePlugin {
255259

256-
private static final Logger logger = Loggers.getLogger(Security.class);
260+
private static final Logger LOGGER = LogManager.getLogger(Security.class);
261+
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(LOGGER);
257262

258263
public static final String NAME4 = XPackField.SECURITY + "4";
259264
public static final Setting<Optional<String>> USER_SETTING =
@@ -535,7 +540,7 @@ private AuthenticationFailureHandler createAuthenticationFailureHandler(final Re
535540
extensionName = extension.toString();
536541
}
537542
if (failureHandler == null) {
538-
logger.debug("Using default authentication failure handler");
543+
LOGGER.debug("Using default authentication failure handler");
539544
final Map<String, List<String>> defaultFailureResponseHeaders = new HashMap<>();
540545
realms.asList().stream().forEach((realm) -> {
541546
Map<String, List<String>> realmFailureHeaders = realm.getAuthenticationFailureHeaders();
@@ -556,7 +561,7 @@ private AuthenticationFailureHandler createAuthenticationFailureHandler(final Re
556561
}
557562
failureHandler = new DefaultAuthenticationFailureHandler(defaultFailureResponseHeaders);
558563
} else {
559-
logger.debug("Using authentication failure handler from extension [" + extensionName + "]");
564+
LOGGER.debug("Using authentication failure handler from extension [" + extensionName + "]");
560565
}
561566
return failureHandler;
562567
}
@@ -949,7 +954,7 @@ static void validateAutoCreateIndex(Settings settings) {
949954
}
950955
}
951956

952-
logger.warn("the [action.auto_create_index] setting is configured to be restrictive [{}]. " +
957+
LOGGER.warn("the [action.auto_create_index] setting is configured to be restrictive [{}]. " +
953958
" for the next 6 months audit indices are allowed to be created, but please make sure" +
954959
" that any future history indices after 6 months with the pattern " +
955960
"[.security_audit_log*] are allowed to be created", value);
@@ -1039,7 +1044,7 @@ public UnaryOperator<Map<String, IndexTemplateMetaData>> getIndexTemplateMetaDat
10391044
templates.put(SECURITY_TEMPLATE_NAME, IndexTemplateMetaData.Builder.fromXContent(parser, SECURITY_TEMPLATE_NAME));
10401045
} catch (IOException e) {
10411046
// TODO: should we handle this with a thrown exception?
1042-
logger.error("Error loading template [{}] as part of metadata upgrading", SECURITY_TEMPLATE_NAME);
1047+
LOGGER.error("Error loading template [{}] as part of metadata upgrading", SECURITY_TEMPLATE_NAME);
10431048
}
10441049

10451050
final byte[] auditTemplate = TemplateUtils.loadTemplate("/" + IndexAuditTrail.INDEX_TEMPLATE_NAME + ".json",
@@ -1049,12 +1054,12 @@ public UnaryOperator<Map<String, IndexTemplateMetaData>> getIndexTemplateMetaDat
10491054
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, auditTemplate)) {
10501055
IndexTemplateMetaData auditMetadata = new IndexTemplateMetaData.Builder(
10511056
IndexTemplateMetaData.Builder.fromXContent(parser, IndexAuditTrail.INDEX_TEMPLATE_NAME))
1052-
.settings(IndexAuditTrail.customAuditIndexSettings(settings, logger))
1057+
.settings(IndexAuditTrail.customAuditIndexSettings(settings, LOGGER))
10531058
.build();
10541059
templates.put(IndexAuditTrail.INDEX_TEMPLATE_NAME, auditMetadata);
10551060
} catch (IOException e) {
10561061
// TODO: should we handle this with a thrown exception?
1057-
logger.error("Error loading template [{}] as part of metadata upgrading", IndexAuditTrail.INDEX_TEMPLATE_NAME);
1062+
LOGGER.error("Error loading template [{}] as part of metadata upgrading", IndexAuditTrail.INDEX_TEMPLATE_NAME);
10581063
}
10591064

10601065
return templates;
@@ -1167,4 +1172,55 @@ public void accept(DiscoveryNode node, ClusterState state) {
11671172
public void reloadSPI(ClassLoader loader) {
11681173
securityExtensions.addAll(SecurityExtension.loadExtensions(loader));
11691174
}
1175+
1176+
public static Path resolveConfigFile(Environment env, String name) {
1177+
final Path config = env.configFile().resolve(name);
1178+
final Path legacyConfig = env.configFile().resolve("x-pack").resolve(name);
1179+
// config and legacy config can be the same path if name is an absolute path
1180+
if (config.equals(legacyConfig) == false) {
1181+
final boolean configFileExists = Files.exists(config);
1182+
final boolean legacyConfigExists = Files.exists(legacyConfig);
1183+
if (configFileExists == false) {
1184+
if (legacyConfigExists) {
1185+
DEPRECATION_LOGGER.deprecated("Config file [" + name + "] is in a deprecated location. Move from " +
1186+
legacyConfig.toString() + " to " + config.toString());
1187+
return legacyConfig;
1188+
}
1189+
} else if (legacyConfigExists) {
1190+
// there is a file in both locations
1191+
if (isDefaultFile(name, config)) {
1192+
// use the legacy file as the new file is the default but warn user
1193+
DEPRECATION_LOGGER.deprecated("Config file [" + name + "] exists in a deprecated location and non-deprecated " +
1194+
"location. The file in the non-deprecated location is the default file. Using file found in the deprecated " +
1195+
"location. Move " + legacyConfig.toString() + " to " + config.toString());
1196+
return legacyConfig;
1197+
} else {
1198+
// the regular file has been modified, but the old still exists, warn the user
1199+
DEPRECATION_LOGGER.deprecated("Config file [" + name + "] exists in a deprecated location and non-deprecated " +
1200+
"location. Using file found in the non-deprecated location [" + config.toString() + "]. Determine which file " +
1201+
"should be kept and move it to " + config.toString() + ", then remove " + legacyConfig.toString());
1202+
}
1203+
}
1204+
}
1205+
return config;
1206+
}
1207+
1208+
static boolean isDefaultFile(String name, Path file) {
1209+
try (InputStream in = XPackPlugin.class.getResourceAsStream("/config/" + name)) {
1210+
if (in != null) {
1211+
try (InputStream fin = Files.newInputStream(file)) {
1212+
int inValue = in.read();
1213+
int finValue = fin.read();
1214+
while (inValue != -1 && finValue != -1 && inValue == finValue) {
1215+
inValue = in.read();
1216+
finValue = fin.read();
1217+
}
1218+
return inValue == finValue;
1219+
}
1220+
}
1221+
} catch (IOException e) {
1222+
throw new UncheckedIOException(e);
1223+
}
1224+
return false;
1225+
}
11701226
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.elasticsearch.watcher.FileChangesListener;
1818
import org.elasticsearch.watcher.FileWatcher;
1919
import org.elasticsearch.watcher.ResourceWatcherService;
20-
import org.elasticsearch.xpack.core.XPackPlugin;
2120
import org.elasticsearch.xpack.core.XPackSettings;
2221
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
2322
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
@@ -26,6 +25,7 @@
2625
import org.elasticsearch.xpack.core.security.support.Validation;
2726
import org.elasticsearch.xpack.core.security.support.Validation.Users;
2827
import org.elasticsearch.xpack.core.security.user.User;
28+
import org.elasticsearch.xpack.security.Security;
2929
import org.elasticsearch.xpack.security.support.SecurityFiles;
3030

3131
import java.io.IOException;
@@ -92,7 +92,7 @@ public boolean userExists(String username) {
9292
}
9393

9494
public static Path resolveFile(Environment env) {
95-
return XPackPlugin.resolveConfigFile(env, "users");
95+
return Security.resolveConfigFile(env, "users");
9696
}
9797

9898
/**

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserRolesStore.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
import org.elasticsearch.watcher.FileChangesListener;
1717
import org.elasticsearch.watcher.FileWatcher;
1818
import org.elasticsearch.watcher.ResourceWatcherService;
19-
import org.elasticsearch.xpack.core.XPackPlugin;
2019
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
2120
import org.elasticsearch.xpack.core.security.support.NoOpLogger;
2221
import org.elasticsearch.xpack.core.security.support.Validation;
22+
import org.elasticsearch.xpack.security.Security;
2323
import org.elasticsearch.xpack.security.support.SecurityFiles;
2424

2525
import java.io.IOException;
@@ -79,7 +79,7 @@ public String[] roles(String username) {
7979
}
8080

8181
public static Path resolveFile(Environment env) {
82-
return XPackPlugin.resolveConfigFile(env, "users_roles");
82+
return Security.resolveConfigFile(env, "users_roles");
8383
}
8484

8585
/**

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
import org.elasticsearch.watcher.FileChangesListener;
3232
import org.elasticsearch.watcher.FileWatcher;
3333
import org.elasticsearch.watcher.ResourceWatcherService;
34-
import org.elasticsearch.xpack.core.XPackPlugin;
3534
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
3635
import org.elasticsearch.xpack.core.security.authc.support.CachingRealm;
3736
import org.elasticsearch.xpack.core.security.authc.support.DnRoleMapperSettings;
37+
import org.elasticsearch.xpack.security.Security;
3838

3939
import static java.util.Collections.emptyMap;
4040
import static java.util.Collections.unmodifiableMap;
@@ -80,7 +80,7 @@ synchronized void addListener(Runnable listener) {
8080

8181
public static Path resolveFile(Settings settings, Environment env) {
8282
String location = DnRoleMapperSettings.ROLE_MAPPING_FILE_SETTING.get(settings);
83-
return XPackPlugin.resolveConfigFile(env, location);
83+
return Security.resolveConfigFile(env, location);
8484
}
8585

8686
/**

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
import org.elasticsearch.watcher.FileChangesListener;
2323
import org.elasticsearch.watcher.FileWatcher;
2424
import org.elasticsearch.watcher.ResourceWatcherService;
25-
import org.elasticsearch.xpack.core.XPackPlugin;
2625
import org.elasticsearch.xpack.core.XPackSettings;
2726
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
2827
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivileges;
2928
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
3029
import org.elasticsearch.xpack.core.security.support.NoOpLogger;
3130
import org.elasticsearch.xpack.core.security.support.Validation;
31+
import org.elasticsearch.xpack.security.Security;
3232

3333
import java.io.IOException;
3434
import java.nio.charset.StandardCharsets;
@@ -130,7 +130,7 @@ Set<String> getAllRoleNames() {
130130
}
131131

132132
public static Path resolveFile(Environment env) {
133-
return XPackPlugin.resolveConfigFile(env, "roles.yml");
133+
return Security.resolveConfigFile(env, "roles.yml");
134134
}
135135

136136
public static Set<String> parseFileForRoleNames(Path path, Logger logger) {

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
import org.junit.Before;
5050

5151
import java.io.IOException;
52+
import java.nio.file.Files;
53+
import java.nio.file.Path;
54+
import java.nio.file.StandardOpenOption;
5255
import java.util.Arrays;
5356
import java.util.Collection;
5457
import java.util.Collections;
@@ -456,4 +459,63 @@ public void testGetFieldFilterSecurityEnabledDeprecatedSetting() throws Exceptio
456459
"future release! See the breaking changes documentation for the next major version.");
457460
}
458461
}
462+
463+
/**
464+
* Tests the default file comparison check. Note: this will fail when run in an IDE as the
465+
* processing of resources isn't handled properly.
466+
*
467+
* TODO: can we add an assume for whether the test should run if it is an IDE context?
468+
*/
469+
public void testIsDefaultFileCheck() throws Exception {
470+
Path homeDir = createTempDir();
471+
Path configDir = homeDir.resolve("config");
472+
Path xPackConfigDir = configDir.resolve("x-pack");
473+
Environment environment = new Environment(Settings.builder().put("path.home", homeDir).build(), configDir);
474+
475+
List<String> defaultFiles = Arrays.asList("roles.yml", "role_mapping.yml", "users", "users_roles");
476+
Files.createDirectories(xPackConfigDir);
477+
478+
for (String defaultFileName : defaultFiles) {
479+
logger.info("testing default file: {}", defaultFileName);
480+
Path defaultFile = getDataPath("/config/" + defaultFileName);
481+
final byte[] defaultBytes = Files.readAllBytes(defaultFile);
482+
final Path defaultFileConfigPath = configDir.resolve(defaultFileName);
483+
Path resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
484+
assertEquals(defaultFileConfigPath, resolvedPath);
485+
486+
Files.write(defaultFileConfigPath, defaultBytes);
487+
assertTrue(Security.isDefaultFile(defaultFileName, defaultFileConfigPath));
488+
489+
resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
490+
assertEquals(defaultFileConfigPath, resolvedPath);
491+
492+
// put a file in x-pack dir
493+
final Path xPackFilePath = xPackConfigDir.resolve(defaultFileName);
494+
Files.write(xPackFilePath, Collections.singletonList(randomAlphaOfLength(8)));
495+
resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
496+
assertEquals(xPackFilePath, resolvedPath);
497+
assertWarnings("Config file [" + defaultFileName + "] exists in a deprecated location and non-deprecated location. The" +
498+
" file in the non-deprecated location is the default file. Using file found in the deprecated location. Move " +
499+
xPackFilePath + " to " + defaultFileConfigPath);
500+
501+
// modify file in new location
502+
Files.write(defaultFileConfigPath, Collections.singletonList(randomAlphaOfLength(8)),
503+
randomBoolean() ? StandardOpenOption.TRUNCATE_EXISTING : StandardOpenOption.APPEND);
504+
505+
assertFalse(Security.isDefaultFile(defaultFileName, defaultFileConfigPath));
506+
resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
507+
assertEquals(defaultFileConfigPath, resolvedPath);
508+
assertWarnings("Config file [" + defaultFileName + "] exists in a deprecated location and non-deprecated location. " +
509+
"Using file found in the non-deprecated location [" + defaultFileConfigPath + "]. Determine which file should be kept and" +
510+
" move it to " + defaultFileConfigPath + ", then remove " + xPackFilePath);
511+
512+
// remove default file
513+
Files.delete(defaultFileConfigPath);
514+
resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
515+
assertEquals(xPackFilePath, resolvedPath);
516+
assertWarnings("Config file [" + defaultFileName + "] is in a deprecated location. Move from " +
517+
xPackFilePath + " to " + defaultFileConfigPath);
518+
}
519+
520+
}
459521
}

0 commit comments

Comments
 (0)