Skip to content

Commit efc2c2b

Browse files
author
Yogesh Gaikwad
committed
add tests
1 parent aa623c3 commit efc2c2b

File tree

5 files changed

+63
-10
lines changed

5 files changed

+63
-10
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilege.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@
2626
public class ManageOwnApiKeyClusterPrivilege implements NamedClusterPrivilege {
2727
public static final ManageOwnApiKeyClusterPrivilege INSTANCE = new ManageOwnApiKeyClusterPrivilege();
2828
private static final Predicate<String> ACTION_PREDICATE = Automatons.predicate("cluster:admin/xpack/security/api_key/*");
29-
private final String name;
29+
private static final String PRIVILEGE_NAME = "manage_own_api_key";
3030
private final BiPredicate<TransportRequest, Authentication> requestAuthnPredicate;
3131

3232
private ManageOwnApiKeyClusterPrivilege() {
33-
this.name = "manage_own_api_key";
3433
this.requestAuthnPredicate = (request, authentication) -> {
3534
if (request instanceof CreateApiKeyRequest) {
3635
return true;
@@ -52,8 +51,8 @@ private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, Strin
5251
return true;
5352
} else {
5453
/*
55-
* TODO ygaikwad we need to think on how we can propagate appropriate error message to the end user when username, realm name
56-
* is missing. This is similar to the problem of propagating proper error messages in case of access denied.
54+
* TODO bizybot we need to think on how we can propagate appropriate error message to the end user when username, realm name
55+
* is missing. This is similar to the problem of propagating right error messages in case of access denied.
5756
*/
5857
String authenticatedUserPrincipal = authentication.getUser().principal();
5958
String authenticatedUserRealm = authentication.getAuthenticatedBy().getName();
@@ -65,7 +64,6 @@ private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, Strin
6564
}
6665

6766
private boolean isCurrentAuthenticationUsingSameApiKeyIdFromRequest(Authentication authentication, String apiKeyId) {
68-
// TODO ygaikwad replace with constants after merging other change
6967
if (authentication.getAuthenticatedBy().getType().equals("_es_api_key")) {
7068
// API key id from authentication must match the id from request
7169
String authenticatedApiKeyId = (String) authentication.getMetadata().get("_security_api_key_id");
@@ -78,7 +76,7 @@ private boolean isCurrentAuthenticationUsingSameApiKeyIdFromRequest(Authenticati
7876

7977
@Override
8078
public String name() {
81-
return name;
79+
return PRIVILEGE_NAME;
8280
}
8381

8482
@Override

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ protected void doExecute(Task task, GetApiKeyRequest request, ActionListener<Get
4848
if (request.ownedByAuthenticatedUser()) {
4949
assert username == null;
5050
assert realm == null;
51+
if (authentication.getAuthenticatedBy().getType().equals(ApiKeyService.API_KEY_REALM_TYPE)) {
52+
throw new IllegalArgumentException(
53+
"failed to retrieve owned API keys for a user authenticated by API key as they cannot own any API keys");
54+
}
55+
5156
// restrict username and realm to current authenticated user.
5257
username = authentication.getUser().principal();
5358
realm = authentication.getAuthenticatedBy().getName();

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ protected void doExecute(Task task, InvalidateApiKeyRequest request, ActionListe
4848
if (request.ownedByAuthenticatedUser()) {
4949
assert username == null;
5050
assert realm == null;
51+
if (authentication.getAuthenticatedBy().getType().equals(ApiKeyService.API_KEY_REALM_TYPE)) {
52+
throw new IllegalArgumentException(
53+
"failed to invalidate owned API keys for a user authenticated by API key as they cannot own any API keys");
54+
}
5155
// restrict username and realm to current authenticated user.
5256
username = authentication.getUser().principal();
5357
realm = authentication.getAuthenticatedBy().getName();

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.elasticsearch.xpack.security.audit.AuditLevel;
6565
import org.elasticsearch.xpack.security.audit.AuditTrailService;
6666
import org.elasticsearch.xpack.security.audit.AuditUtil;
67+
import org.elasticsearch.xpack.security.authc.ApiKeyService;
6768
import org.elasticsearch.xpack.security.authz.interceptor.RequestInterceptor;
6869
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
6970

@@ -572,6 +573,14 @@ private ElasticsearchSecurityException denialException(Authentication authentica
572573
return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", cause, action, authUser.principal(),
573574
authentication.getUser().principal());
574575
}
576+
// check for authentication by API key
577+
if (authentication.getAuthenticatedBy().getType().equals(ApiKeyService.API_KEY_REALM_TYPE)) {
578+
final String apiKeyId = (String) authentication.getMetadata().get(ApiKeyService.API_KEY_ID_KEY);
579+
assert apiKeyId != null : "api key id must be present in the metadata";
580+
logger.debug("action [{}] is unauthorized for API key id [{}] of user [{}]", action, apiKeyId, authUser.principal());
581+
return authorizationError("action [{}] is unauthorized for API key id [{}] of user [{}]", cause, action, apiKeyId,
582+
authUser.principal());
583+
}
575584
logger.debug("action [{}] is unauthorized for user [{}]", action, authUser.principal());
576585
return authorizationError("action [{}] is unauthorized for user [{}]", cause, action, authUser.principal());
577586
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -545,9 +545,37 @@ public void testApiKeyAuthorizationApiKeyMustBeAbleToRetrieveItsOwnInformation()
545545

546546
final PlainActionFuture<GetApiKeyResponse> failureListener = new PlainActionFuture<>();
547547
// for any other API key id, it must deny access
548-
client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(1).getId(), false), failureListener);
548+
client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(1).getId(), false),
549+
failureListener);
549550
ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet());
550-
assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER);
551+
assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER,
552+
responses.get(0).getId());
553+
}
554+
555+
public void testApiKeyWithManageOwnPrivilegeIsAbleToInvalidateItselfButNotAnyOtherKeysCreatedBySameOwner() throws InterruptedException
556+
, ExecutionException {
557+
List<CreateApiKeyResponse> responses = createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, 2, null, "manage_own_api_key");
558+
final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString(
559+
(responses.get(0).getId() + ":" + responses.get(0).getKey().toString()).getBytes(StandardCharsets.UTF_8));
560+
Client client = client().filterWithHeader(Map.of("Authorization", "ApiKey " + base64ApiKeyKeyValue));
561+
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
562+
563+
final PlainActionFuture<InvalidateApiKeyResponse> failureListener = new PlainActionFuture<>();
564+
// for any other API key id, it must deny access
565+
client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(1).getId(), false),
566+
failureListener);
567+
ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet());
568+
assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", SecuritySettingsSource.TEST_SUPERUSER,
569+
responses.get(0).getId());
570+
571+
client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false),
572+
listener);
573+
InvalidateApiKeyResponse invalidateResponse = listener.get();
574+
575+
assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(1));
576+
assertThat(invalidateResponse.getInvalidatedApiKeys(), containsInAnyOrder(responses.get(0).getId()));
577+
assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
578+
assertThat(invalidateResponse.getErrors().size(), equalTo(0));
551579
}
552580

553581
private void verifyGetResponse(int expectedNumberOfApiKeys, List<CreateApiKeyResponse> responses,
@@ -582,13 +610,17 @@ private void verifyGetResponse(String user, int expectedNumberOfApiKeys, List<Cr
582610
}
583611

584612
private List<CreateApiKeyResponse> createApiKeys(int noOfApiKeys, TimeValue expiration) {
585-
return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, expiration);
613+
return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, expiration, "monitor");
586614
}
587615

588616
private List<CreateApiKeyResponse> createApiKeys(String user, int noOfApiKeys, TimeValue expiration) {
617+
return createApiKeys(user, noOfApiKeys, expiration, "monitor");
618+
}
619+
620+
private List<CreateApiKeyResponse> createApiKeys(String user, int noOfApiKeys, TimeValue expiration, String role) {
589621
List<CreateApiKeyResponse> responses = new ArrayList<>();
590622
for (int i = 0; i < noOfApiKeys; i++) {
591-
final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null);
623+
final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { role }, null, null);
592624
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
593625
.basicAuthHeaderValue(user, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
594626
final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client)
@@ -602,6 +634,11 @@ private List<CreateApiKeyResponse> createApiKeys(String user, int noOfApiKeys, T
602634
return responses;
603635
}
604636

637+
private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName, String apiKeyId) {
638+
assertThat(ese.getMessage(),
639+
is("action [" + action + "] is unauthorized for API key id [" + apiKeyId + "] of user [" + userName + "]"));
640+
}
641+
605642
private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName) {
606643
assertThat(ese.getMessage(), is("action [" + action + "] is unauthorized for user [" + userName + "]"));
607644
}

0 commit comments

Comments
 (0)