Skip to content

Commit

Permalink
Add allowlist configuration and deprecate whitelist (#1808)
Browse files Browse the repository at this point in the history
Signed-off-by: cliu123 <[email protected]>
  • Loading branch information
cliu123 authored May 12, 2022
1 parent ad90eff commit ea90f30
Show file tree
Hide file tree
Showing 22 changed files with 999 additions and 111 deletions.
69 changes: 69 additions & 0 deletions securityconfig/allowlist.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
_meta:
type: "allowlist"
config_version: 2

# Description:
# enabled - feature flag.
# if enabled is false, the allowlisting feature is removed.
# This is like removing the check that checks if an API is allowlisted.
# This is equivalent to continuing with the usual access control checks, and removing all the code that implements allowlisting.
# if enabled is true, then all users except SuperAdmin can access only the APIs in requests
# SuperAdmin can access all APIs.
# SuperAdmin is defined by the SuperAdmin certificate, which is configured in the opensearch.yml setting: plugins.security.authcz.admin_dn:
# Refer to the example setting in opensearch.yml.example, and the opendistro documentation to know more about configuring SuperAdmin.
#
# requests - map of allowlisted endpoints, and the allowlisted HTTP requests for those endpoints

# Examples showing how to configure this yml file (make sure the _meta data from above is also there):
# Example 1:
# To enable allowlisting and allowlist GET /_cluster/settings
#
#config:
# enabled: true
# requests:
# /_cluster/settings:
# - GET
#
# Example 2:
# If you want to allowlist multiple request methods for /_cluster/settings (GET,PUT):
#
#config:
# enabled: true
# requests:
# /_cluster/settings:
# - GET
# - PUT
#
# Example 3:
# If you want to allowlist other APIs as well, for example GET /_cat/nodes, and GET /_cat/shards:
#
#config:
# enabled: true
# requests:
# /_cluster/settings:
# - GET
# - PUT
# /_cat/nodes:
# - GET
# /_cat/shards:
# - GET
#
# Example 4:
# If you want to disable the allowlisting feature, set enabled to false.
# enabled: false
# requests:
# /_cluster/settings:
# - GET
#
#At this point, all APIs become allowlisted because the feature to allowlist is off, so requests is irrelevant.


#this name must be config
config:
enabled: false
requests:
/_cluster/settings:
- GET
/_cat/nodes:
- GET
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ public void noData(String id) {

// Since NODESDN is newly introduced data-type applying for existing clusters as well, we make it backward compatible by returning valid empty
// SecurityDynamicConfiguration.
// Same idea for new setting WHITELIST
if (cType == CType.NODESDN || cType == CType.WHITELIST) {
// Same idea for new setting WHITELIST/ALLOWLIST
if (cType == CType.NODESDN || cType == CType.WHITELIST || cType == CType.ALLOWLIST) {
try {
SecurityDynamicConfiguration<?> empty = ConfigHelper.createEmptySdc(cType, ConfigurationRepository.getDefaultConfigVersion());
rs.put(cType, empty);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public void run() {
final boolean populateEmptyIfFileMissing = true;
ConfigHelper.uploadFile(client, cd+"nodes_dn.yml", securityIndex, CType.NODESDN, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing);
ConfigHelper.uploadFile(client, cd + "whitelist.yml", securityIndex, CType.WHITELIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing);
ConfigHelper.uploadFile(client, cd + "allowlist.yml", securityIndex, CType.ALLOWLIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing);

// audit.yml is not packaged by default
final String auditConfigPath = cd + "audit.yml";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/


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

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableList;

import org.opensearch.action.index.IndexResponse;
import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.bytes.BytesReference;
import org.opensearch.common.inject.Inject;
import org.opensearch.common.settings.Settings;
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestController;
import org.opensearch.rest.RestRequest;
import org.opensearch.security.DefaultObjectMapper;
import org.opensearch.security.auditlog.AuditLog;
import org.opensearch.security.configuration.AdminDNs;
import org.opensearch.security.configuration.ConfigurationRepository;
import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator;
import org.opensearch.security.dlic.rest.validation.AllowlistValidator;
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.security.support.ConfigConstants;
import org.opensearch.security.tools.SecurityAdmin;
import org.opensearch.threadpool.ThreadPool;

/**
* This class implements GET and PUT operations to manage dynamic AllowlistingSettings.
* <p>
* These APIs are only accessible to SuperAdmin since the configuration controls what APIs are accessible by normal users.
* Eg: If allowlisting is enabled, and a specific API like "/_cat/nodes" is not allowlisted, then only the SuperAdmin can use "/_cat/nodes"
* These APIs allow the SuperAdmin to enable/disable allowlisting, and also change the list of allowlisted APIs.
* <p>
* A SuperAdmin is identified by a certificate which represents a distinguished name(DN).
* SuperAdmin DN's can be set in {@link ConfigConstants#SECURITY_AUTHCZ_ADMIN_DN}
* SuperAdmin certificate for the default superuser is stored as a kirk.pem file in config folder of OpenSearch
* <p>
* Example calling the PUT API as SuperAdmin using curl (if http basic auth is on):
* curl -v --cacert path_to_config/root-ca.pem --cert path_to_config/kirk.pem --key path_to_config/kirk-key.pem -XPUT https://localhost:9200/_plugins/_security/api/allowlist -H "Content-Type: application/json" -d’
* {
* "enabled" : false,
* "requests" : {"/_cat/nodes": ["GET"], "/_plugins/_security/api/allowlist": ["GET"]}
* }
*
* Example using the PATCH API to change the requests as SuperAdmin:
* curl -v --cacert path_to_config/root-ca.pem --cert path_to_config/kirk.pem --key path_to_config/kirk-key.pem -XPATCH https://localhost:9200/_plugins/_security/api/allowlist -H "Content-Type: application/json" -d’
* {
* "op":"replace",
* "path":"/config/requests",
* "value": {"/_cat/nodes": ["GET"], "/_plugins/_security/api/allowlist": ["GET"]}
* }
*
* To update enabled, use the "add" operation instead of the "replace" operation, since boolean variables are not recognized as valid paths when they are false.
* eg:
* curl -v --cacert path_to_config/root-ca.pem --cert path_to_config/kirk.pem --key path_to_config/kirk-key.pem -XPATCH https://localhost:9200/_plugins/_security/api/allowlist -H "Content-Type: application/json" -d’
* {
* "op":"add",
* "path":"/config/enabled",
* "value": true
* }
*
* The backing data is stored in {@link ConfigConstants#SECURITY_CONFIG_INDEX_NAME} which is populated during bootstrap.
* For existing clusters, {@link SecurityAdmin} tool can
* be used to populate the index.
* <p>
*/
public class AllowlistApiAction extends PatchableResourceApiAction {
private static final List<Route> routes = ImmutableList.of(
new Route(RestRequest.Method.GET, "/_plugins/_security/api/allowlist"),
new Route(RestRequest.Method.PUT, "/_plugins/_security/api/allowlist"),
new Route(RestRequest.Method.PATCH, "/_plugins/_security/api/allowlist")
);

private static final String name = "config";

@Inject
public AllowlistApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client,
final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs,
final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) {
super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog);
}

@Override
protected void handleApiRequest(final RestChannel channel, final RestRequest request, final Client client) throws IOException {
if (!isSuperAdmin()) {
forbidden(channel, "API allowed only for super admin.");
return;
}
super.handleApiRequest(channel, request, client);
}

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


final SecurityDynamicConfiguration<?> configuration = load(getConfigName(), true);
filter(configuration);
successResponse(channel, configuration);
}

@Override
protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException {
notImplemented(channel, RestRequest.Method.DELETE);
}

@Override
protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException {
final SecurityDynamicConfiguration<?> existingConfiguration = load(getConfigName(), false);

if (existingConfiguration.getSeqNo() < 0) {
forbidden(channel, "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate.");
return;
}

boolean existed = existingConfiguration.exists(name);
existingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass()));

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

@Override
public void onResponse(IndexResponse response) {
if (existed) {
successResponse(channel, "'" + name + "' updated.");
} else {
createdResponse(channel, "'" + name + "' created.");
}
}
});
}


@Override
public List<Route> routes() {
return routes;
}

@Override
protected Endpoint getEndpoint() {
return Endpoint.ALLOWLIST;
}

@Override
protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) {
return new AllowlistValidator(request, ref, this.settings, param);
}

@Override
protected String getResourceName() {
return name;
}

@Override
protected CType getConfigName() {
return CType.ALLOWLIST;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ public enum Endpoint {
MIGRATE,
VALIDATE,
WHITELIST,
ALLOWLIST,
NODESDN;
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public static Collection<RestHandler> getHandler(Settings settings, Path configP
handlers.add(new AccountApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new NodesDnApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new WhitelistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new AllowlistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new AuditApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
return Collections.unmodifiableCollection(handlers);
}
Expand Down
Loading

0 comments on commit ea90f30

Please sign in to comment.