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

Auto-convert V6 configuration instances into V7 configuration instances #4546

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions src/main/java/org/opensearch/security/DefaultObjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,23 @@ public static <T> T readValue(String string, JavaType jt) throws IOException {
}
}

@SuppressWarnings("removal")
public static <T> T convertValue(JsonNode jsonNode, JavaType jt) throws IOException {

final SecurityManager sm = System.getSecurityManager();

if (sm != null) {
sm.checkPermission(new SpecialPermission());
}

try {
return AccessController.doPrivileged((PrivilegedExceptionAction<T>) () -> objectMapper.convertValue(jsonNode, jt));
} catch (final PrivilegedActionException e) {
throw (IOException) e.getCause();
}
}


public static TypeFactory getTypeFactory() {
return objectMapper.getTypeFactory();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@

package org.opensearch.security.configuration;

import java.util.Map;

import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;

/**
* Callback function on change particular configuration
*/
Expand All @@ -39,5 +34,5 @@ public interface ConfigurationChangeListener {
/**
* @param configuration not null updated configuration on that was subscribe current listener
*/
void onChange(Map<CType, SecurityDynamicConfiguration<?>> typeToConfig);
void onChange(ConfigurationMap typeToConfig);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
Expand Down Expand Up @@ -94,10 +92,10 @@ boolean isAuditConfigDocPresentInIndex() {
return isAuditConfigDocPresentInIndex.get();
}

Map<CType, SecurityDynamicConfiguration<?>> load(final CType[] events, long timeout, TimeUnit timeUnit, boolean acceptInvalid)
throws InterruptedException, TimeoutException {
ConfigurationMap load(final CType<?>[] events, long timeout, TimeUnit timeUnit, boolean acceptInvalid) throws InterruptedException,
TimeoutException {
final CountDownLatch latch = new CountDownLatch(events.length);
final Map<CType, SecurityDynamicConfiguration<?>> rs = new HashMap<>(events.length);
ConfigurationMap.Builder result = new ConfigurationMap.Builder();
final boolean isDebugEnabled = log.isDebugEnabled();
loadAsync(events, new ConfigCallback() {

Expand All @@ -118,7 +116,7 @@ public void success(SecurityDynamicConfiguration<?> dConf) {
isAuditConfigDocPresentInIndex.set(true);
}

rs.put(dConf.getCType(), dConf);
result.with(dConf);
latch.countDown();
if (isDebugEnabled) {
log.debug(
Expand All @@ -142,7 +140,7 @@ public void singleFailure(Failure failure) {

@Override
public void noData(String id) {
CType cType = CType.fromString(id);
CType<?> cType = CType.fromString(id);

// Since NODESDN is newly introduced data-type applying for existing clusters as well, we make it backward compatible by
// returning valid empty
Expand All @@ -154,7 +152,7 @@ public void noData(String id) {
cType,
ConfigurationRepository.getDefaultConfigVersion()
);
rs.put(cType, empty);
result.with(empty);
latch.countDown();
return;
} catch (Exception e) {
Expand All @@ -172,7 +170,7 @@ public void noData(String id) {
ConfigurationRepository.getDefaultConfigVersion()
);
empty.putCObject("config", AuditConfig.from(settings));
rs.put(cType, empty);
result.with(empty);
latch.countDown();
return;
} catch (Exception e) {
Expand Down Expand Up @@ -204,10 +202,10 @@ public void failure(Throwable t) {
);
}

return rs;
return result.build();
}

void loadAsync(final CType[] events, final ConfigCallback callback, boolean acceptInvalid) {
void loadAsync(final CType<?>[] events, final ConfigCallback callback, boolean acceptInvalid) {
if (events == null || events.length == 0) {
log.warn("No config events requested to load");
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.configuration;

import java.util.Set;

import com.google.common.collect.ImmutableMap;

import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;

/**
* Allows type safe access of configuration instances via the configuration type
*/
public class ConfigurationMap {
public static final ConfigurationMap EMPTY = new ConfigurationMap(ImmutableMap.of());

private final ImmutableMap<CType<?>, SecurityDynamicConfiguration<?>> map;

private ConfigurationMap(ImmutableMap<CType<?>, SecurityDynamicConfiguration<?>> map) {
this.map = map;
}

public <T> SecurityDynamicConfiguration<T> get(CType<T> ctype) {
@SuppressWarnings("unchecked")
SecurityDynamicConfiguration<T> config = (SecurityDynamicConfiguration<T>) map.get(ctype);

if (config == null) {
return null;
}

if (!config.getCType().equals(ctype)) {
throw new RuntimeException("Stored configuration does not match type: " + ctype + "; " + config);
}

return config;
}

public boolean containsKey(CType<?> ctype) {
return map.containsKey(ctype);
}

public Set<CType<?>> keySet() {
return map.keySet();
}

public int size() {
return this.map.size();
}

public ImmutableMap<CType<?>, SecurityDynamicConfiguration<?>> rawMap() {
return this.map;
}

public static ConfigurationMap of(SecurityDynamicConfiguration<?>... configs) {
Builder builder = new Builder();

for (SecurityDynamicConfiguration<?> config : configs) {
builder.with(config);
}

return builder.build();
}

public static class Builder {
private ImmutableMap.Builder<CType<?>, SecurityDynamicConfiguration<?>> map = new ImmutableMap.Builder<>();

public Builder() {}

public <T> Builder with(SecurityDynamicConfiguration<T> config) {
map.put(config.getCType(), config);
return this;
}

public Builder with(ConfigurationMap configurationMap) {
map.putAll(configurationMap.map);
return this;
}

public ConfigurationMap build() {
return new ConfigurationMap(this.map.build());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -100,7 +99,7 @@ public class ConfigurationRepository implements ClusterStateListener {

private final String securityIndex;
private final Client client;
private final Cache<CType, SecurityDynamicConfiguration<?>> configCache;
private final Cache<CType<?>, SecurityDynamicConfiguration<?>> configCache;
private final List<ConfigurationChangeListener> configurationChangedListener;
private final ConfigurationLoaderSecurity7 cl;
private final Settings settings;
Expand Down Expand Up @@ -283,7 +282,7 @@ private void initalizeClusterConfiguration(final boolean installDefaultConfig) {
while (!dynamicConfigFactory.isInitialized()) {
try {
LOGGER.debug("Try to load config ...");
reloadConfiguration(Arrays.asList(CType.values()), true);
reloadConfiguration(CType.values(), true);
break;
} catch (Exception e) {
LOGGER.debug("Unable to load configuration due to {}", String.valueOf(ExceptionUtils.getRootCause(e)));
Expand Down Expand Up @@ -510,21 +509,23 @@ public void setDynamicConfigFactory(DynamicConfigFactory dynamicConfigFactory) {
* @param configurationType
* @return can also return empty in case it was never loaded
*/
public SecurityDynamicConfiguration<?> getConfiguration(CType configurationType) {
public <T> SecurityDynamicConfiguration<T> getConfiguration(CType<T> configurationType) {
SecurityDynamicConfiguration<?> conf = configCache.getIfPresent(configurationType);
if (conf != null) {
return conf.deepClone();
@SuppressWarnings("unchecked")
SecurityDynamicConfiguration<T> result = (SecurityDynamicConfiguration<T>) conf.deepClone();
return result;
}
return SecurityDynamicConfiguration.empty();
return SecurityDynamicConfiguration.empty(configurationType);
}

private final Lock LOCK = new ReentrantLock();

public boolean reloadConfiguration(final Collection<CType> configTypes) throws ConfigUpdateAlreadyInProgressException {
public boolean reloadConfiguration(final Collection<CType<?>> configTypes) throws ConfigUpdateAlreadyInProgressException {
return reloadConfiguration(configTypes, false);
}

private boolean reloadConfiguration(final Collection<CType> configTypes, final boolean fromBackgroundThread)
private boolean reloadConfiguration(final Collection<CType<?>> configTypes, final boolean fromBackgroundThread)
throws ConfigUpdateAlreadyInProgressException {
if (!fromBackgroundThread && !initalizeConfigTask.isDone()) {
LOGGER.warn("Unable to reload configuration, initalization thread has not yet completed.");
Expand All @@ -533,7 +534,7 @@ private boolean reloadConfiguration(final Collection<CType> configTypes, final b
return loadConfigurationWithLock(configTypes);
}

private boolean loadConfigurationWithLock(Collection<CType> configTypes) {
private boolean loadConfigurationWithLock(Collection<CType<?>> configTypes) {
try {
if (LOCK.tryLock(60, TimeUnit.SECONDS)) {
try {
Expand All @@ -551,21 +552,21 @@ private boolean loadConfigurationWithLock(Collection<CType> configTypes) {
}
}

private void reloadConfiguration0(Collection<CType> configTypes, boolean acceptInvalid) {
final Map<CType, SecurityDynamicConfiguration<?>> loaded = getConfigurationsFromIndex(configTypes, false, acceptInvalid);
private void reloadConfiguration0(Collection<CType<?>> configTypes, boolean acceptInvalid) {
ConfigurationMap loaded = getConfigurationsFromIndex(configTypes, false, acceptInvalid);
notifyConfigurationListeners(loaded);
}

private void notifyConfigurationListeners(final Map<CType, SecurityDynamicConfiguration<?>> configuration) {
configCache.putAll(configuration);
private void notifyConfigurationListeners(ConfigurationMap configuration) {
configCache.putAll(configuration.rawMap());
notifyAboutChanges(configuration);
}

public synchronized void subscribeOnChange(ConfigurationChangeListener listener) {
configurationChangedListener.add(listener);
}

private synchronized void notifyAboutChanges(Map<CType, SecurityDynamicConfiguration<?>> typeToConfig) {
private synchronized void notifyAboutChanges(ConfigurationMap typeToConfig) {
for (ConfigurationChangeListener listener : configurationChangedListener) {
try {
LOGGER.debug("Notify {} listener about change configuration with type {}", listener);
Expand All @@ -583,21 +584,18 @@ private synchronized void notifyAboutChanges(Map<CType, SecurityDynamicConfigura
* @param logComplianceEvent
* @return
*/
public Map<CType, SecurityDynamicConfiguration<?>> getConfigurationsFromIndex(
Collection<CType> configTypes,
boolean logComplianceEvent
) {
public ConfigurationMap getConfigurationsFromIndex(Collection<CType<?>> configTypes, boolean logComplianceEvent) {
return getConfigurationsFromIndex(configTypes, logComplianceEvent, this.acceptInvalid);
}

public Map<CType, SecurityDynamicConfiguration<?>> getConfigurationsFromIndex(
Collection<CType> configTypes,
public ConfigurationMap getConfigurationsFromIndex(
Collection<CType<?>> configTypes,
boolean logComplianceEvent,
boolean acceptInvalid
) {

final ThreadContext threadContext = threadPool.getThreadContext();
final Map<CType, SecurityDynamicConfiguration<?>> retVal = new HashMap<>();
final ConfigurationMap.Builder resultBuilder = new ConfigurationMap.Builder();

try (StoredContext ctx = threadContext.stashContext()) {
threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true");
Expand All @@ -611,34 +609,35 @@ public Map<CType, SecurityDynamicConfiguration<?>> getConfigurationsFromIndex(
} else {
LOGGER.debug("security index exists and was created with ES 7 (new layout)");
}
retVal.putAll(
validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())
resultBuilder.with(
validate(cl.load(configTypes.toArray(new CType<?>[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())
);

} else {
// wait (and use new layout)
LOGGER.debug("security index not exists (yet)");
retVal.putAll(
validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())
resultBuilder.with(
validate(cl.load(configTypes.toArray(new CType<?>[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())
);
}

} catch (Exception e) {
throw new OpenSearchException(e);
}

ConfigurationMap result = resultBuilder.build();

if (logComplianceEvent && auditLog.getComplianceConfig() != null && auditLog.getComplianceConfig().isEnabled()) {
CType configurationType = configTypes.iterator().next();
CType<?> configurationType = configTypes.iterator().next();
Map<String, String> fields = new HashMap<String, String>();
fields.put(configurationType.toLCString(), Strings.toString(MediaTypeRegistry.JSON, retVal.get(configurationType)));
fields.put(configurationType.toLCString(), Strings.toString(MediaTypeRegistry.JSON, result.get(configurationType)));
auditLog.logDocumentRead(this.securityIndex, configurationType.toLCString(), null, fields);
}

return retVal;
return result;
}

private Map<CType, SecurityDynamicConfiguration<?>> validate(Map<CType, SecurityDynamicConfiguration<?>> conf, int expectedSize)
throws InvalidConfigException {
private ConfigurationMap validate(ConfigurationMap conf, int expectedSize) throws InvalidConfigException {

if (conf == null || conf.size() != expectedSize) {
throw new InvalidConfigException("Retrieved only partial configuration");
Expand Down
Loading
Loading