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

[Backport 2.x] Rest admin permissions (#2411) #2466

Merged
merged 3 commits into from
Feb 27, 2023
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
15 changes: 14 additions & 1 deletion config/roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,20 @@ kibana_read_only:
# The security REST API access role is used to assign specific users access to change the security settings through the REST API.
security_rest_api_access:
reserved: true


security_rest_api_full_access:
reserved: true
cluster_permissions:
- 'restapi:admin/actiongroups'
- 'restapi:admin/allowlist'
- 'restapi:admin/internalusers'
- 'restapi:admin/nodesdn'
- 'restapi:admin/roles'
- 'restapi:admin/rolesmapping'
- 'restapi:admin/ssl/certs/info'
- 'restapi:admin/ssl/certs/reload'
- 'restapi:admin/tenants'

# Allows users to view monitors, destinations and alerts
alerting_read_access:
reserved: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@
import org.opensearch.security.ssl.OpenSearchSecuritySSLPlugin;
import org.opensearch.security.ssl.SslExceptionHandler;
import org.opensearch.security.ssl.http.netty.ValidatingDispatcher;
import org.opensearch.security.ssl.rest.SecuritySSLCertsInfoAction;
import org.opensearch.security.ssl.rest.SecuritySSLReloadCertsAction;
import org.opensearch.security.ssl.transport.DefaultPrincipalExtractor;
import org.opensearch.security.ssl.transport.SecuritySSLNettyTransport;
import org.opensearch.security.ssl.util.SSLConfigConstants;
Expand Down Expand Up @@ -466,18 +464,25 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
if(!SSLConfig.isSslOnlyMode()) {
handlers.add(new SecurityInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool)));
handlers.add(new SecurityHealthAction(settings, restController, Objects.requireNonNull(backendRegistry)));
handlers.add(new SecuritySSLCertsInfoAction(settings, restController, sks, Objects.requireNonNull(threadPool), Objects.requireNonNull(adminDns)));
handlers.add(new DashboardsInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool)));
handlers.add(new TenantInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool),
Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr)));
handlers.add(new SecurityConfigUpdateAction(settings, restController,Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor));
handlers.add(new SecurityWhoAmIAction(settings ,restController,Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor));
if (sslCertReloadEnabled) {
handlers.add(new SecuritySSLReloadCertsAction(settings, restController, sks, Objects.requireNonNull(threadPool), Objects.requireNonNull(adminDns)));
}
final Collection<RestHandler> apiHandlers = SecurityRestApiActions.getHandler(settings, configPath, restController, localClient, adminDns, cr, cs, principalExtractor, evaluator, threadPool, Objects.requireNonNull(auditLog));
handlers.addAll(apiHandlers);
log.debug("Added {} management rest handler(s)", apiHandlers.size());
Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr)));
handlers.add(new SecurityConfigUpdateAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor));
handlers.add(new SecurityWhoAmIAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor));
handlers.addAll(
SecurityRestApiActions.getHandler(
settings,
configPath,
restController,
localClient,
adminDns,
cr, cs, principalExtractor,
evaluator,
threadPool,
Objects.requireNonNull(auditLog), sks,
sslCertReloadEnabled)
);
log.debug("Added {} rest handler(s)", handlers.size());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ public abstract class AbstractApiAction extends BaseRestHandler {
final ThreadPool threadPool;
protected String opendistroIndex;
private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator;
protected final RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator;
protected final AuditLog auditLog;
protected final Settings settings;
private AdminDNs adminDNs;

protected AbstractApiAction(final Settings settings, final Path configPath, final RestController controller,
final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl,
Expand All @@ -88,12 +88,13 @@ protected AbstractApiAction(final Settings settings, final Path configPath, fina
this.opendistroIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME,
ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX);

this.adminDNs = adminDNs;
this.cl = cl;
this.cs = cs;
this.threadPool = threadPool;
this.restApiPrivilegesEvaluator = new RestApiPrivilegesEvaluator(settings, adminDNs, evaluator,
principalExtractor, configPath, threadPool);
this.restApiAdminPrivilegesEvaluator =
new RestApiAdminPrivilegesEvaluator(threadPool.getThreadContext(), evaluator, adminDNs);
this.auditLog = auditLog;
}

Expand Down Expand Up @@ -195,7 +196,12 @@ protected void handlePut(final RestChannel channel, final RestRequest request, f
}

boolean existed = existingConfiguration.exists(name);
existingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass()));
final Object newContent = DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass());
if (!hasPermissionsToCreate(existingConfiguration, newContent, getResourceName())) {
forbidden(channel, "No permissions");
return;
}
existingConfiguration.putCObject(name, newContent);

saveAnUpdateConfigs(client, request, getConfigName(), existingConfiguration, new OnSucessActionListener<IndexResponse>(channel) {

Expand All @@ -216,6 +222,12 @@ protected void handlePost(final RestChannel channel, final RestRequest request,
notImplemented(channel, Method.POST);
}

protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) throws IOException {
return false;
}

protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content)
throws IOException{

Expand Down Expand Up @@ -448,7 +460,6 @@ protected static XContentBuilder convertToJson(RestChannel channel, ToXContent t
}

protected void response(RestChannel channel, RestStatus status, String message) {

try {
final XContentBuilder builder = channel.newBuilder();
builder.startObject();
Expand Down Expand Up @@ -558,8 +569,7 @@ public String getName() {
protected abstract Endpoint getEndpoint();

protected boolean isSuperAdmin() {
User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
return adminDNs.isAdmin(user);
return restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ public AccountApiAction(Settings settings,
this.threadContext = threadPool.getThreadContext();
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@Override
public List<Route> routes() {
return routes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,35 @@ protected void handlePut(RestChannel channel, RestRequest request, Client client

// Prevent the case where action group references to itself in the allowed_actions.
final SecurityDynamicConfiguration<?> existingActionGroupsConfig = load(getConfigName(), false);
existingActionGroupsConfig.putCObject(name, DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass()));
final Object actionGroup = DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass());
existingActionGroupsConfig.putCObject(name, actionGroup);
if (hasActionGroupSelfReference(existingActionGroupsConfig, name)) {
badRequestResponse(channel, name + " cannot be an allowed_action of itself");
return;
}

// prevent creation of groups for REST admin api
if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(actionGroup)) {
forbidden(channel, "Not allowed");
return;
}
super.handlePut(channel, request, client, content);
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfiguration,
final Object content,
final String resourceName) throws IOException {
if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) {
return false;
}
return true;
}

@Override
protected boolean isReadOnly(SecurityDynamicConfiguration<?> existingConfiguration, String name) {
if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) {
return true;
}
return super.isReadOnly(existingConfiguration, name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ public AllowlistApiAction(final Settings settings, final Path configPath, final
super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog);
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@Override
protected void handleApiRequest(final RestChannel channel, final RestRequest request, final Client client) throws IOException {
if (!isSuperAdmin()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ public AuditApiAction(final Settings settings,
}
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@Override
public List<Route> routes() {
return routes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.opensearch.security.dlic.rest.validation.NoOpValidator;
import org.opensearch.security.privileges.PrivilegesEvaluator;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
import org.opensearch.security.ssl.transport.PrincipalExtractor;
import org.opensearch.threadpool.ThreadPool;

Expand All @@ -53,6 +54,13 @@ public AuthTokenProcessorAction(final Settings settings, final Path configPath,
auditLog);
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@Override
public List<Route> routes() {
return routes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ public enum Endpoint {
VALIDATE,
WHITELIST,
ALLOWLIST,
NODESDN;
NODESDN,
SSL;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.opensearch.security.dlic.rest.validation.NoOpValidator;
import org.opensearch.security.privileges.PrivilegesEvaluator;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
import org.opensearch.security.ssl.transport.PrincipalExtractor;
import org.opensearch.threadpool.ThreadPool;

Expand All @@ -58,6 +59,13 @@ public FlushCacheApiAction(final Settings settings, final Path configPath, final
super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog);
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@Override
public List<Route> routes() {
return routes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ public InternalUsersApiAction(final Settings settings, final Path configPath, fi
auditLog);
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@Override
public List<Route> routes() {
return routes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ protected Endpoint getEndpoint() {
return Endpoint.MIGRATE;
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@SuppressWarnings("unchecked")
@Override
protected void handlePost(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ public NodesDnApiAction(final Settings settings, final Path configPath, final Re
this.staticNodesDnFromEsYml = settings.getAsList(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList());
}

@Override
protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration<?> dynamicConfigFactory,
final Object content,
final String resourceName) {
return true;
}

@Override
public List<Route> routes() {
if (settings.getAsBoolean(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public abstract class PatchableResourceApiAction extends AbstractApiAction {
protected final Logger log = LogManager.getLogger(this.getClass());

public PatchableResourceApiAction(Settings settings, Path configPath, RestController controller, Client client,
AdminDNs adminDNs, ConfigurationRepository cl, ClusterService cs,
PrincipalExtractor principalExtractor, PrivilegesEvaluator evaluator, ThreadPool threadPool,
AuditLog auditLog) {
AdminDNs adminDNs, ConfigurationRepository cl, ClusterService cs,
PrincipalExtractor principalExtractor, PrivilegesEvaluator evaluator, ThreadPool threadPool,
AuditLog auditLog) {
super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool,
auditLog);
}
Expand Down Expand Up @@ -146,7 +146,7 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client

if (!validator.validate()) {
request.params().clear();
badRequestResponse(channel, validator);
badRequestResponse(channel, validator);
return;
}

Expand All @@ -156,7 +156,7 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client
, existingConfiguration.getVersion(), existingConfiguration.getSeqNo(), existingConfiguration.getPrimaryTerm());

if (existingConfiguration.getCType().equals(CType.ACTIONGROUPS)) {
if(hasActionGroupSelfReference(mdc, name)) {
if (hasActionGroupSelfReference(mdc, name)) {
badRequestResponse(channel, name + " cannot be an allowed_action of itself");
return;
}
Expand Down Expand Up @@ -188,7 +188,6 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl
for (String resourceName : existingConfiguration.getCEntries().keySet()) {
JsonNode oldResource = existingAsObjectNode.get(resourceName);
JsonNode patchedResource = patchedAsJsonNode.get(resourceName);

if (oldResource != null && !oldResource.equals(patchedResource) && !isWriteable(channel, existingConfiguration, resourceName)) {
return;
}
Expand All @@ -206,7 +205,7 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl
if(originalValidator != null) {
if (!originalValidator.validate()) {
request.params().clear();
badRequestResponse(channel, originalValidator);
badRequestResponse(channel, originalValidator);
return;
}
}
Expand All @@ -222,7 +221,13 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl

if (!validator.validate()) {
request.params().clear();
badRequestResponse(channel, validator);
badRequestResponse(channel, validator);
return;
}
final Object newContent = DefaultObjectMapper.readTree(patchedResource, existingConfiguration.getImplementingClass());
if (!hasPermissionsToCreate(existingConfiguration, newContent, resourceName)) {
request.params().clear();
forbidden(channel, "No permissions");
return;
}
}
Expand Down
Loading