Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Bug Fixes
- Create a WildcardMatcher.NONE when creating a WildcardMatcher with an empty string ([#5694](https://github.com/opensearch-project/security/pull/5694))
- Use RestRequestFilter.getFilteredRequest to declare sensitive API params ([#5710](https://github.com/opensearch-project/security/pull/5710))

### Refactoring

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestRequest.Method;
import org.opensearch.rest.RestRequestFilter;
import org.opensearch.security.action.configupdate.ConfigUpdateAction;
import org.opensearch.security.action.configupdate.ConfigUpdateRequest;
import org.opensearch.security.action.configupdate.ConfigUpdateResponse;
Expand Down Expand Up @@ -79,7 +80,7 @@
import static org.opensearch.security.dlic.rest.api.Responses.payload;
import static org.opensearch.security.dlic.rest.support.Utils.withIOException;

public abstract class AbstractApiAction extends BaseRestHandler {
public abstract class AbstractApiAction extends BaseRestHandler implements RestRequestFilter {

private final static Logger LOGGER = LogManager.getLogger(AbstractApiAction.class);

Expand Down Expand Up @@ -578,6 +579,10 @@ public void onFailure(Exception e) {
@Override
protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {

RestRequest filteredRequest = getFilteredRequest(request);

RestRequest auditLogRequest = (request.method() != Method.PATCH) ? filteredRequest : request;
Copy link
Member

Choose a reason for hiding this comment

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

Would you mind adding a brief comment on why PATCH is skipped?


// consume all parameters first so we can return a correct HTTP status,
// not 400
consumeParameters(request);
Expand All @@ -594,12 +599,12 @@ protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClie
final String userName = user == null ? null : user.getName();
if (authError != null) {
LOGGER.error("No permission to access REST API: " + authError);
securityApiDependencies.auditLog().logMissingPrivileges(authError, userName, SecurityRequestFactory.from(request));
securityApiDependencies.auditLog().logMissingPrivileges(authError, userName, SecurityRequestFactory.from(auditLogRequest));
// for rest request
request.params().clear();
return channel -> forbidden(channel, "No permission to access REST API: " + authError);
} else {
securityApiDependencies.auditLog().logGrantedPrivileges(userName, SecurityRequestFactory.from(request));
securityApiDependencies.auditLog().logGrantedPrivileges(userName, SecurityRequestFactory.from(auditLogRequest));
}

final var originalUserAndRemoteAddress = Utils.userAndRemoteAddressFrom(threadPool.getThreadContext());
Expand Down Expand Up @@ -651,4 +656,9 @@ public boolean canTripCircuitBreaker() {
return false;
}

@Override
public Set<String> getFilteredFields() {
return Set.of("password", "current_password");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

package org.opensearch.security.filter;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
Expand All @@ -49,6 +50,7 @@
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestHandler;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestRequestFilter;
import org.opensearch.security.auditlog.AuditLog;
import org.opensearch.security.auditlog.AuditLog.Origin;
import org.opensearch.security.auth.BackendRegistry;
Expand Down Expand Up @@ -156,7 +158,11 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
}
});

RestRequest filteredRequest = maybeFilterRestRequest(request);

final SecurityRequestChannel requestChannel = SecurityRequestFactory.from(request, channel);
// for audit logging
final SecurityRequestChannel filteredRequestChannel = SecurityRequestFactory.from(filteredRequest, channel);

// Authenticate request
if (!NettyAttribute.popFrom(request, Netty4HttpRequestHeaderVerifier.IS_AUTHENTICATED).orElse(false)) {
Expand All @@ -177,7 +183,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
String intiatingUser = threadContext.getTransient(OPENDISTRO_SECURITY_INITIATING_USER);
if (userIsSuperAdmin(user, adminDNs)) {
// Super admins are always authorized
auditLog.logSucceededLogin(user.getName(), true, intiatingUser, requestChannel);
auditLog.logSucceededLogin(user.getName(), true, intiatingUser, filteredRequestChannel);
if (performPermissionCheck) {
log.debug("Permission check skipped: Super admin has full access");
handleSuperAdminPermissionCheck(channel);
Expand All @@ -187,7 +193,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
return;
}
if (user != null) {
auditLog.logSucceededLogin(user.getName(), false, intiatingUser, requestChannel);
auditLog.logSucceededLogin(user.getName(), false, intiatingUser, filteredRequestChannel);
}
final Optional<SecurityResponse> deniedResponse = allowlistingSettings.checkRequestIsAllowed(requestChannel);

Expand All @@ -196,9 +202,9 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
return;
}

authorizeRequest(delegate, requestChannel, user);
if (requestChannel.getQueuedResponse().isPresent()) {
channel.sendResponse(requestChannel.getQueuedResponse().get().asRestResponse());
authorizeRequest(delegate, filteredRequestChannel, user);
if (filteredRequestChannel.getQueuedResponse().isPresent()) {
channel.sendResponse(filteredRequestChannel.getQueuedResponse().get().asRestResponse());
return;
}

Expand All @@ -216,6 +222,13 @@ private void handleSuperAdminPermissionCheck(RestChannel channel) throws Excepti

channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder));
}

RestRequest maybeFilterRestRequest(RestRequest restRequest) throws IOException {
if (delegate instanceof RestRequestFilter) {
return ((RestRequestFilter) delegate).getFilteredRequest(restRequest);
}
return restRequest;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,40 +962,50 @@ public void testSensitiveMethodRedaction() throws Exception {
.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false)
.build();
setup(settings);
rh.sendAdminCertificate = true;
final String expectedRequestBody = "\"audit_request_body\" : \"__SENSITIVE__\"";
final String expectedChangePasswordRequestBody = "{}";

// test PUT accounts API
TestAuditlogImpl.clear();
rh.executePutRequest("/_opendistro/_security/api/account", "{\"password\":\"new-pass\", \"current_password\":\"curr-passs\"}");
rh.sendAdminCertificate = false;
final Header adminHeader = encodeBasicHeader("admin", "admin");
rh.executePutRequest(
"/_opendistro/_security/api/account",
"{\"password\":\"myNewPassword123!\", \"current_password\":\"myCurrentPassword123!\"}",
adminHeader
);
assertThat(TestAuditlogImpl.messages.size(), is(1));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedRequestBody));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedChangePasswordRequestBody));

final String expectedUpdateUserRequestBody = "{\\\"backend_roles\\\":[],\\\"attributes\\\":{}}";
// test PUT internal users API
TestAuditlogImpl.clear();
rh.executePutRequest(
"/_opendistro/_security/api/internalusers/test1",
"{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}"
"{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}",
adminHeader
);
assertThat(TestAuditlogImpl.messages.size(), is(1));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedRequestBody));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedUpdateUserRequestBody));

// test PATCH internal users API
rh.sendAdminCertificate = true;
TestAuditlogImpl.clear();
rh.executePatchRequest(
"/_opendistro/_security/api/internalusers/test1",
"[{\"op\":\"add\", \"path\":\"/password\", \"value\": \"test-pass\"}]"
);
assertThat(TestAuditlogImpl.messages.size(), is(1));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedRequestBody));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("__SENSITIVE__"));

// test PUT users API
rh.sendAdminCertificate = false;
TestAuditlogImpl.clear();
rh.executePutRequest(
"/_opendistro/_security/api/user/test2",
"{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}"
"{\"password\":\"new-pass\", \"backend_roles\":[], \"attributes\": {}}",
adminHeader
);
assertThat(TestAuditlogImpl.messages.size(), is(1));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedRequestBody));
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains(expectedUpdateUserRequestBody));
}
}
Loading