Skip to content
7 changes: 7 additions & 0 deletions x-pack/plugin/security/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ artifacts {
archives jar
testArtifacts testJar
}

processResources {
from('src/main/config') {
into 'config'
}
}

sourceSets.test.resources {
srcDir '../core/src/test/resources'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.security;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.Version;
Expand All @@ -27,8 +28,8 @@
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.regex.Regex;
Expand All @@ -49,6 +50,7 @@
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.discovery.DiscoveryModule;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
Expand Down Expand Up @@ -221,7 +223,10 @@
import org.joda.time.DateTimeZone;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Clock;
import java.util.ArrayList;
Expand Down Expand Up @@ -253,7 +258,8 @@
public class Security extends Plugin implements ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin, DiscoveryPlugin, MapperPlugin,
ExtensiblePlugin {

private static final Logger logger = Loggers.getLogger(Security.class);
private static final Logger logger = LogManager.getLogger(Security.class);
private static DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: these are statics, so they should be named with all caps?


public static final String NAME4 = XPackField.SECURITY + "4";
public static final Setting<Optional<String>> USER_SETTING =
Expand Down Expand Up @@ -1167,4 +1173,61 @@ public void accept(DiscoveryNode node, ClusterState state) {
public void reloadSPI(ClassLoader loader) {
securityExtensions.addAll(SecurityExtension.loadExtensions(loader));
}

public static Path resolveConfigFile(Environment env, String name) {
final Path config = env.configFile().resolve(name);
final Path legacyConfig = env.configFile().resolve("x-pack").resolve(name);
// config and legacy config can be the same path if name is an absolute path
if (config.equals(legacyConfig) == false) {
final boolean configFileExists = Files.exists(config);
final boolean legacyConfigExists = Files.exists(legacyConfig);
if (configFileExists == false) {
if (legacyConfigExists) {
deprecationLogger.deprecated("Config file [" + name + "] is in a deprecated location. Move from " +
legacyConfig.toString() + " to " + config.toString());
return legacyConfig;
}
} else if (legacyConfigExists) {
// there is a file in both locations
if (isDefaultFile(name, config)) {
// use the legacy file as the new file is the default but warn user
deprecationLogger.deprecated("Config file [" + name + "] exists in a deprecated location and non-deprecated location." +
" The file in the non-deprecated location is the default file. Using file found in the deprecated location. Move " +
legacyConfig.toString() + " to " + config.toString());
return legacyConfig;
} else {
// the regular file has been modified, but the old still exists, warn the user
deprecationLogger.deprecated("Config file [" + name + "] exists in a deprecated location and non-deprecated location." +
" Using file found in the non-deprecated location [" + config.toString() + "]. Determine which file should be" +
" kept and move it to " + config.toString() + ", then remove " + legacyConfig.toString());
}
}
}
return config;
}

static boolean isDefaultFile(String name, Path file) {
InputStream in = null;
try {
in = XPackPlugin.class.getResourceAsStream("/config/" + name);
if (in != null) {
try (InputStream fin = Files.newInputStream(file)) {
int inValue = in.read();
int finValue = fin.read();
while (inValue != -1 && finValue != -1 && inValue == finValue) {
inValue = in.read();
finValue = fin.read();
}
return inValue == finValue;
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
if (in != null) {
IOUtils.closeWhileHandlingException(in);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason try-with-resources can't be used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot that try-with-resources can handle nulls. Switched.

}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
Expand All @@ -25,6 +24,7 @@
import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.core.security.support.Validation.Users;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.support.SecurityFiles;

import java.io.IOException;
Expand Down Expand Up @@ -93,7 +93,7 @@ public boolean userExists(String username) {
}

public static Path resolveFile(Environment env) {
return XPackPlugin.resolveConfigFile(env, "users");
return Security.resolveConfigFile(env, "users");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.support.NoOpLogger;
import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.support.SecurityFiles;

import java.io.IOException;
Expand Down Expand Up @@ -80,7 +80,7 @@ public String[] roles(String username) {
}

public static Path resolveFile(Environment env) {
return XPackPlugin.resolveConfigFile(env, "users_roles");
return Security.resolveConfigFile(env, "users_roles");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.support.CachingRealm;
import org.elasticsearch.xpack.core.security.authc.support.DnRoleMapperSettings;
import org.elasticsearch.xpack.security.Security;

import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap;
Expand Down Expand Up @@ -80,7 +80,7 @@ synchronized void addListener(Runnable listener) {

public static Path resolveFile(Settings settings, Environment env) {
String location = DnRoleMapperSettings.ROLE_MAPPING_FILE_SETTING.get(settings);
return XPackPlugin.resolveConfigFile(env, location);
return Security.resolveConfigFile(env, location);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivileges;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.support.NoOpLogger;
import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.security.Security;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -130,7 +130,7 @@ Set<String> getAllRoleNames() {
}

public static Path resolveFile(Environment env) {
return XPackPlugin.resolveConfigFile(env, "roles.yml");
return Security.resolveConfigFile(env, "roles.yml");
}

public static Set<String> parseFileForRoleNames(Path path, Logger logger) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
import org.junit.Before;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -456,4 +459,63 @@ public void testGetFieldFilterSecurityEnabledDeprecatedSetting() throws Exceptio
"future release! See the breaking changes documentation for the next major version.");
}
}

/**
* Tests the default file comparison check. Note: this will fail when run in an IDE as the
* processing of resources isn't handled properly.
*
* TODO: can we add an assume for whether the test should run if it is an IDE context?
*/
public void testIsDefaultFileCheck() throws Exception {
Path homeDir = createTempDir();
Path configDir = homeDir.resolve("config");
Path xPackConfigDir = configDir.resolve("x-pack");
Environment environment = new Environment(Settings.builder().put("path.home", homeDir).build(), configDir);

List<String> defaultFiles = Arrays.asList("roles.yml", "role_mapping.yml", "users", "users_roles");
Files.createDirectories(xPackConfigDir);

for (String defaultFileName : defaultFiles) {
logger.info("testing default file: {}", defaultFileName);
Path defaultFile = getDataPath("/config/" + defaultFileName);
final byte[] defaultBytes = Files.readAllBytes(defaultFile);
final Path defaultFileConfigPath = configDir.resolve(defaultFileName);
Path resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
assertEquals(defaultFileConfigPath, resolvedPath);

Files.write(defaultFileConfigPath, defaultBytes);
assertTrue(Security.isDefaultFile(defaultFileName, defaultFileConfigPath));

resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
assertEquals(defaultFileConfigPath, resolvedPath);

// put a file in x-pack dir
final Path xPackFilePath = xPackConfigDir.resolve(defaultFileName);
Files.write(xPackFilePath, Collections.singletonList(randomAlphaOfLength(8)));
resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
assertEquals(xPackFilePath, resolvedPath);
assertWarnings("Config file [" + defaultFileName + "] exists in a deprecated location and non-deprecated location. The" +
" file in the non-deprecated location is the default file. Using file found in the deprecated location. Move " +
xPackFilePath + " to " + defaultFileConfigPath);

// modify file in new location
Files.write(defaultFileConfigPath, Collections.singletonList(randomAlphaOfLength(8)),
randomBoolean() ? StandardOpenOption.TRUNCATE_EXISTING : StandardOpenOption.APPEND);

assertFalse(Security.isDefaultFile(defaultFileName, defaultFileConfigPath));
resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
assertEquals(defaultFileConfigPath, resolvedPath);
assertWarnings("Config file [" + defaultFileName + "] exists in a deprecated location and non-deprecated location. " +
"Using file found in the non-deprecated location [" + defaultFileConfigPath + "]. Determine which file should be kept and" +
" move it to " + defaultFileConfigPath + ", then remove " + xPackFilePath);

// remove default file
Files.delete(defaultFileConfigPath);
resolvedPath = Security.resolveConfigFile(environment, defaultFileName);
assertEquals(xPackFilePath, resolvedPath);
assertWarnings("Config file [" + defaultFileName + "] is in a deprecated location. Move from " +
xPackFilePath + " to " + defaultFileConfigPath);
}

}
}