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

Support security config updates on the REST API using permission #3264

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions config/roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ security_rest_api_full_access:
cluster_permissions:
- 'restapi:admin/actiongroups'
- 'restapi:admin/allowlist'
- 'restapi:admin/config/update'
- 'restapi:admin/internalusers'
- 'restapi:admin/nodesdn'
- 'restapi:admin/roles'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,18 @@
package org.opensearch.security;

// CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.PrivilegedAction;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.search.QueryCachingPolicy;
import org.apache.lucene.search.Weight;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.opensearch.OpenSearchException;
import org.opensearch.OpenSearchSecurityException;
import org.opensearch.SpecialPermission;
import org.opensearch.Version;
import org.opensearch.action.ActionRequest;
import org.opensearch.core.action.ActionResponse;
import org.opensearch.action.search.PitService;
import org.opensearch.action.search.SearchScrollAction;
import org.opensearch.action.support.ActionFilter;
Expand All @@ -80,7 +51,6 @@
import org.opensearch.common.lifecycle.Lifecycle;
import org.opensearch.common.lifecycle.LifecycleComponent;
import org.opensearch.common.lifecycle.LifecycleListener;
import org.opensearch.core.common.io.stream.NamedWriteableRegistry;
import org.opensearch.common.logging.DeprecationLogger;
import org.opensearch.common.network.NetworkModule;
import org.opensearch.common.network.NetworkService;
Expand All @@ -93,14 +63,18 @@
import org.opensearch.common.util.BigArrays;
import org.opensearch.common.util.PageCacheRecycler;
import org.opensearch.common.util.concurrent.ThreadContext;
import org.opensearch.core.action.ActionResponse;
import org.opensearch.core.common.io.stream.NamedWriteableRegistry;
import org.opensearch.core.index.Index;
import org.opensearch.core.indices.breaker.CircuitBreakerService;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.transport.TransportResponse;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.env.Environment;
import org.opensearch.env.NodeEnvironment;
import org.opensearch.extensions.ExtensionsManager;
import org.opensearch.http.HttpServerTransport;
import org.opensearch.http.HttpServerTransport.Dispatcher;
import org.opensearch.core.index.Index;
import org.opensearch.index.IndexModule;
import org.opensearch.index.cache.query.QueryCache;
import org.opensearch.indices.IndicesService;
Expand All @@ -111,7 +85,6 @@
import org.opensearch.repositories.RepositoriesService;
import org.opensearch.rest.RestController;
import org.opensearch.rest.RestHandler;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.script.ScriptService;
import org.opensearch.search.internal.InternalScrollSearchRequest;
import org.opensearch.search.internal.ReaderContext;
Expand Down Expand Up @@ -140,6 +113,7 @@
import org.opensearch.security.configuration.PrivilegesInterceptorImpl;
import org.opensearch.security.configuration.Salt;
import org.opensearch.security.configuration.SecurityFlsDlsIndexSearcherWrapper;
import org.opensearch.security.dlic.rest.api.Endpoint;
import org.opensearch.security.dlic.rest.api.SecurityRestApiActions;
import org.opensearch.security.dlic.rest.validation.PasswordValidator;
import org.opensearch.security.filter.SecurityFilter;
Expand Down Expand Up @@ -190,10 +164,42 @@
import org.opensearch.transport.TransportRequest;
import org.opensearch.transport.TransportRequestHandler;
import org.opensearch.transport.TransportRequestOptions;
import org.opensearch.core.transport.TransportResponse;
import org.opensearch.transport.TransportResponseHandler;
import org.opensearch.transport.TransportService;
import org.opensearch.watcher.ResourceWatcherService;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.PrivilegedAction;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS;
import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.SECURITY_CONFIG_UPDATE;
import static org.opensearch.security.setting.DeprecatedSettings.checkForDeprecatedSetting;
import static org.opensearch.security.support.ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION;
// CS-ENFORCE-SINGLE

public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
Expand Down Expand Up @@ -332,21 +338,24 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath)
sm.checkPermission(new SpecialPermission());
}

AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
return null;
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
return null;
});

final String advancedModulesEnabledKey = ConfigConstants.SECURITY_ADVANCED_MODULES_ENABLED;
if (settings.hasValue(advancedModulesEnabledKey)) {
deprecationLogger.deprecate("Setting {} is ignored.", advancedModulesEnabledKey);
}

checkForDeprecatedSetting(
settings,
SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION,
ENDPOINTS_WITH_PERMISSIONS.get(Endpoint.CONFIG).build(SECURITY_CONFIG_UPDATE) + " permission"
);

log.info("Clustername: {}", settings.get("cluster.name", "opensearch"));

if (!transportSSLEnabled && !SSLConfig.isSslOnlyMode()) {
Expand Down Expand Up @@ -1788,7 +1797,7 @@ public List<Setting<?>> getSettings() {
);
settings.add(
Setting.boolSetting(
ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION,
SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION,
false,
Property.NodeScope,
Property.Filtered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ public class RestApiAdminPrivilegesEvaluator {

protected final Logger logger = LogManager.getLogger(RestApiAdminPrivilegesEvaluator.class);

public final static String CERTS_INFO_ACTION = "certs";
public final static String CERTS_INFO_ACTION = "certs/info";

public final static String RELOAD_CERTS_ACTION = "reloadcerts";
public final static String RELOAD_CERTS_ACTION = "certs/reload";

public final static String SECURITY_CONFIG_UPDATE = "update";

private final static String REST_API_PERMISSION_PREFIX = "restapi:admin";

Expand All @@ -61,21 +63,13 @@ default String build() {
public final static Map<Endpoint, PermissionBuilder> ENDPOINTS_WITH_PERMISSIONS = ImmutableMap.<Endpoint, PermissionBuilder>builder()
.put(Endpoint.ACTIONGROUPS, action -> buildEndpointPermission(Endpoint.ACTIONGROUPS))
.put(Endpoint.ALLOWLIST, action -> buildEndpointPermission(Endpoint.ALLOWLIST))
.put(Endpoint.CONFIG, action -> buildEndpointActionPermission(Endpoint.CONFIG, action))
.put(Endpoint.INTERNALUSERS, action -> buildEndpointPermission(Endpoint.INTERNALUSERS))
.put(Endpoint.NODESDN, action -> buildEndpointPermission(Endpoint.NODESDN))
.put(Endpoint.ROLES, action -> buildEndpointPermission(Endpoint.ROLES))
.put(Endpoint.ROLESMAPPING, action -> buildEndpointPermission(Endpoint.ROLESMAPPING))
.put(Endpoint.TENANTS, action -> buildEndpointPermission(Endpoint.TENANTS))
.put(Endpoint.SSL, action -> {
switch (action) {
case CERTS_INFO_ACTION:
return buildEndpointActionPermission(Endpoint.SSL, "certs/info");
case RELOAD_CERTS_ACTION:
return buildEndpointActionPermission(Endpoint.SSL, "certs/reload");
default:
return null;
}
})
.put(Endpoint.SSL, action -> buildEndpointActionPermission(Endpoint.SSL, action))
.build();

private final ThreadContext threadContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,41 @@

package org.opensearch.security.dlic.rest.api;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.inject.Inject;
import org.opensearch.common.settings.Settings;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestRequest.Method;
import org.opensearch.security.dlic.rest.validation.EndpointValidator;
import org.opensearch.security.dlic.rest.validation.RequestContentValidator;
import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType;
import org.opensearch.security.dlic.rest.validation.ValidationResult;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.threadpool.ThreadPool;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.opensearch.security.dlic.rest.api.RequestHandler.methodNotImplementedHandler;
import static org.opensearch.security.dlic.rest.api.Responses.methodNotImplementedMessage;
import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.SECURITY_CONFIG_UPDATE;
import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix;
import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED;

public class SecurityConfigApiAction extends AbstractApiAction {

private static final List<Route> getRoutes = addRoutesPrefix(Collections.singletonList(new Route(Method.GET, "/securityconfig/")));

private static final List<Route> allRoutes = new ImmutableList.Builder<Route>().addAll(getRoutes)
.addAll(
addRoutesPrefix(ImmutableList.of(new Route(Method.PUT, "/securityconfig/config"), new Route(Method.PATCH, "/securityconfig/")))
private static final List<Route> routes = addRoutesPrefix(
List.of(
new Route(Method.GET, "/securityconfig"),
new Route(Method.PATCH, "/securityconfig"),
new Route(Method.PUT, "/securityconfig/config")
)
.build();
);

private final boolean allowPutOrPatch;

private final boolean restApiAdminEnabled;

@Inject
public SecurityConfigApiAction(
final ClusterService clusterService,
Expand All @@ -57,12 +56,13 @@
super(Endpoint.CONFIG, clusterService, threadPool, securityApiDependencies);
allowPutOrPatch = securityApiDependencies.settings()
.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, false);
this.restApiAdminEnabled = securityApiDependencies.settings().getAsBoolean(SECURITY_RESTAPI_ADMIN_ENABLED, false);
this.requestHandlersBuilder.configureRequestHandlers(this::securityConfigApiActionRequestHandlers);
}

@Override
public List<Route> routes() {
return allowPutOrPatch ? allRoutes : getRoutes;
return routes;
}

@Override
Expand All @@ -74,20 +74,29 @@
protected void consumeParameters(RestRequest request) {}

private void securityConfigApiActionRequestHandlers(RequestHandler.RequestHandlersBuilder requestHandlersBuilder) {
requestHandlersBuilder.onChangeRequest(
Method.PUT,
request -> withAllowedEndpoint(request).map(ignore -> processPutRequest("config", request))
)
.onChangeRequest(Method.PATCH, request -> withAllowedEndpoint(request).map(this::processPatchRequest))
requestHandlersBuilder.withAccessHandler(this::accessHandler)
.verifyAccessForAllMethods()
.onChangeRequest(Method.PUT, request -> processPutRequest("config", request))
.onChangeRequest(Method.PATCH, this::processPatchRequest)
.override(Method.DELETE, methodNotImplementedHandler)
.override(Method.POST, methodNotImplementedHandler);
}

ValidationResult<RestRequest> withAllowedEndpoint(final RestRequest request) {
if (!allowPutOrPatch) {
return ValidationResult.error(RestStatus.NOT_IMPLEMENTED, methodNotImplementedMessage(request.method()));
boolean accessHandler(final RestRequest request) {
switch (request.method()) {
case PATCH:
case PUT:
if (!allowPutOrPatch && !restApiAdminEnabled) {
return false;

Check warning on line 90 in src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java#L90

Added line #L90 was not covered by tests
} else if (allowPutOrPatch && !restApiAdminEnabled) {
return true;

Check warning on line 92 in src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java#L92

Added line #L92 was not covered by tests
} else {
return securityApiDependencies.restApiAdminPrivilegesEvaluator()
.isCurrentUserAdminFor(endpoint, SECURITY_CONFIG_UPDATE);

Check warning on line 95 in src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java#L94-L95

Added lines #L94 - L95 were not covered by tests
}
DarshitChanpura marked this conversation as resolved.
Show resolved Hide resolved
default:
return true;

Check warning on line 98 in src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigApiAction.java#L98

Added line #L98 was not covered by tests
}
return ValidationResult.success(request);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public static Collection<RestHandler> getHandler(
new AllowlistApiAction(Endpoint.ALLOWLIST, clusterService, threadPool, securityApiDependencies),
new AuditApiAction(clusterService, threadPool, securityApiDependencies),
new MultiTenancyConfigApiAction(clusterService, threadPool, securityApiDependencies),
new SecuritySSLCertsAction(clusterService, threadPool, securityKeyStore, certificatesReloadEnabled, securityApiDependencies)
new SecuritySSLCertsApiAction(clusterService, threadPool, securityKeyStore, certificatesReloadEnabled, securityApiDependencies)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import static org.opensearch.security.dlic.rest.api.Responses.badRequestMessage;
import static org.opensearch.security.dlic.rest.api.Responses.ok;
import static org.opensearch.security.dlic.rest.api.Responses.response;
import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.CERTS_INFO_ACTION;
import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.RELOAD_CERTS_ACTION;
import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix;

/**
Expand All @@ -45,7 +47,7 @@
* This action serves GET request for _plugins/_security/api/ssl/certs endpoint and
* PUT _plugins/_security/api/ssl/{certType}/reloadcerts
*/
public class SecuritySSLCertsAction extends AbstractApiAction {
public class SecuritySSLCertsApiAction extends AbstractApiAction {
private static final List<Route> ROUTES = addRoutesPrefix(
ImmutableList.of(new Route(Method.GET, "/ssl/certs"), new Route(Method.PUT, "/ssl/{certType}/reloadcerts/"))
);
Expand All @@ -56,7 +58,7 @@

private final boolean httpsEnabled;

public SecuritySSLCertsAction(
public SecuritySSLCertsApiAction(
final ClusterService clusterService,
final ThreadPool threadPool,
final SecurityKeyStore securityKeyStore,
Expand Down Expand Up @@ -116,18 +118,17 @@
}).error((status, toXContent) -> response(channel, status, toXContent)));
}

private boolean accessHandler(final RestRequest request) {
switch (request.method()) {
case GET:
return securityApiDependencies.restApiAdminPrivilegesEvaluator().isCurrentUserAdminFor(endpoint, "certs");
case PUT:
return securityApiDependencies.restApiAdminPrivilegesEvaluator().isCurrentUserAdminFor(endpoint, "reloadcerts");
default:
return false;
boolean accessHandler(final RestRequest request) {
if (request.method() == Method.GET) {
return securityApiDependencies.restApiAdminPrivilegesEvaluator().isCurrentUserAdminFor(endpoint, CERTS_INFO_ACTION);

Check warning on line 123 in src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsApiAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsApiAction.java#L123

Added line #L123 was not covered by tests
} else if (request.method() == Method.PUT) {
return securityApiDependencies.restApiAdminPrivilegesEvaluator().isCurrentUserAdminFor(endpoint, RELOAD_CERTS_ACTION);

Check warning on line 125 in src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsApiAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsApiAction.java#L125

Added line #L125 was not covered by tests
} else {
return false;

Check warning on line 127 in src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsApiAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsApiAction.java#L127

Added line #L127 was not covered by tests
}
}

private ValidationResult<SecurityKeyStore> withSecurityKeyStore() {
ValidationResult<SecurityKeyStore> withSecurityKeyStore() {
if (securityKeyStore == null) {
return ValidationResult.error(RestStatus.OK, badRequestMessage("keystore is not initialized"));
}
Expand Down
Loading
Loading