-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support bulk updates of API keys (#88856)
This PR adds a new API route to support bulk updates of API keys: `POST _security/api_key/_bulk_update` The route takes a list of IDs (`ids`) of API keys to update, along with the same request parameters as the single operation route: - `role_descriptors` - The list of role descriptors specified for the key. This is one of the two parts that determines an API key’s privileges. - `metadata_flattened` - The searchable metadata associated to an API key Analogously to the single operation route, a call to `_bulk_update` automatically updates the `limited_by_role_descriptors`, `creator`, and `version` fields for each API key. The implementation ports the single API key update operation to use the new bulk functionality under the hood, translating as necessary at the transport layer. Relates: #88758
- Loading branch information
Showing
24 changed files
with
1,446 additions
and
288 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pr: 88856 | ||
summary: Support bulk updates of API keys | ||
area: Security | ||
type: feature | ||
issues: [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
...ain/java/org/elasticsearch/xpack/core/security/action/apikey/BaseUpdateApiKeyRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
package org.elasticsearch.xpack.core.security.action.apikey; | ||
|
||
import org.elasticsearch.action.ActionRequest; | ||
import org.elasticsearch.action.ActionRequestValidationException; | ||
import org.elasticsearch.common.io.stream.StreamInput; | ||
import org.elasticsearch.common.io.stream.StreamOutput; | ||
import org.elasticsearch.core.Nullable; | ||
import org.elasticsearch.xpack.core.security.action.role.RoleDescriptorRequestValidator; | ||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; | ||
import org.elasticsearch.xpack.core.security.support.MetadataUtils; | ||
|
||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import static org.elasticsearch.action.ValidateActions.addValidationError; | ||
|
||
public abstract class BaseUpdateApiKeyRequest extends ActionRequest { | ||
|
||
@Nullable | ||
protected final List<RoleDescriptor> roleDescriptors; | ||
@Nullable | ||
protected final Map<String, Object> metadata; | ||
|
||
public BaseUpdateApiKeyRequest(@Nullable final List<RoleDescriptor> roleDescriptors, @Nullable final Map<String, Object> metadata) { | ||
this.roleDescriptors = roleDescriptors; | ||
this.metadata = metadata; | ||
} | ||
|
||
public BaseUpdateApiKeyRequest(StreamInput in) throws IOException { | ||
super(in); | ||
this.roleDescriptors = in.readOptionalList(RoleDescriptor::new); | ||
this.metadata = in.readMap(); | ||
} | ||
|
||
public Map<String, Object> getMetadata() { | ||
return metadata; | ||
} | ||
|
||
public List<RoleDescriptor> getRoleDescriptors() { | ||
return roleDescriptors; | ||
} | ||
|
||
@Override | ||
public ActionRequestValidationException validate() { | ||
ActionRequestValidationException validationException = null; | ||
if (metadata != null && MetadataUtils.containsReservedMetadata(metadata)) { | ||
validationException = addValidationError( | ||
"API key metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]", | ||
validationException | ||
); | ||
} | ||
if (roleDescriptors != null) { | ||
for (RoleDescriptor roleDescriptor : roleDescriptors) { | ||
validationException = RoleDescriptorRequestValidator.validate(roleDescriptor, validationException); | ||
} | ||
} | ||
return validationException; | ||
} | ||
|
||
@Override | ||
public void writeTo(StreamOutput out) throws IOException { | ||
super.writeTo(out); | ||
out.writeOptionalCollection(roleDescriptors); | ||
out.writeGenericMap(metadata); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...main/java/org/elasticsearch/xpack/core/security/action/apikey/BulkUpdateApiKeyAction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
package org.elasticsearch.xpack.core.security.action.apikey; | ||
|
||
import org.elasticsearch.action.ActionType; | ||
|
||
public final class BulkUpdateApiKeyAction extends ActionType<BulkUpdateApiKeyResponse> { | ||
|
||
public static final String NAME = "cluster:admin/xpack/security/api_key/bulk_update"; | ||
public static final BulkUpdateApiKeyAction INSTANCE = new BulkUpdateApiKeyAction(); | ||
|
||
private BulkUpdateApiKeyAction() { | ||
super(NAME, BulkUpdateApiKeyResponse::new); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
...ain/java/org/elasticsearch/xpack/core/security/action/apikey/BulkUpdateApiKeyRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
package org.elasticsearch.xpack.core.security.action.apikey; | ||
|
||
import org.elasticsearch.action.ActionRequestValidationException; | ||
import org.elasticsearch.common.io.stream.StreamInput; | ||
import org.elasticsearch.common.io.stream.StreamOutput; | ||
import org.elasticsearch.core.Nullable; | ||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; | ||
|
||
import java.io.IOException; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
import static org.elasticsearch.action.ValidateActions.addValidationError; | ||
|
||
public final class BulkUpdateApiKeyRequest extends BaseUpdateApiKeyRequest { | ||
|
||
public static BulkUpdateApiKeyRequest usingApiKeyIds(String... ids) { | ||
return new BulkUpdateApiKeyRequest(Arrays.stream(ids).toList(), null, null); | ||
} | ||
|
||
public static BulkUpdateApiKeyRequest wrap(final UpdateApiKeyRequest request) { | ||
return new BulkUpdateApiKeyRequest(List.of(request.getId()), request.getRoleDescriptors(), request.getMetadata()); | ||
} | ||
|
||
private final List<String> ids; | ||
|
||
public BulkUpdateApiKeyRequest( | ||
final List<String> ids, | ||
@Nullable final List<RoleDescriptor> roleDescriptors, | ||
@Nullable final Map<String, Object> metadata | ||
) { | ||
super(roleDescriptors, metadata); | ||
this.ids = Objects.requireNonNull(ids, "API key IDs must not be null"); | ||
} | ||
|
||
public BulkUpdateApiKeyRequest(StreamInput in) throws IOException { | ||
super(in); | ||
this.ids = in.readStringList(); | ||
} | ||
|
||
@Override | ||
public ActionRequestValidationException validate() { | ||
ActionRequestValidationException validationException = super.validate(); | ||
if (ids.isEmpty()) { | ||
validationException = addValidationError("Field [ids] cannot be empty", validationException); | ||
} | ||
return validationException; | ||
} | ||
|
||
@Override | ||
public void writeTo(StreamOutput out) throws IOException { | ||
super.writeTo(out); | ||
out.writeStringCollection(ids); | ||
} | ||
|
||
public List<String> getIds() { | ||
return ids; | ||
} | ||
} |
125 changes: 125 additions & 0 deletions
125
...in/java/org/elasticsearch/xpack/core/security/action/apikey/BulkUpdateApiKeyResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
package org.elasticsearch.xpack.core.security.action.apikey; | ||
|
||
import org.elasticsearch.ElasticsearchException; | ||
import org.elasticsearch.action.ActionResponse; | ||
import org.elasticsearch.common.io.stream.StreamInput; | ||
import org.elasticsearch.common.io.stream.StreamOutput; | ||
import org.elasticsearch.common.io.stream.Writeable; | ||
import org.elasticsearch.xcontent.ToXContentObject; | ||
import org.elasticsearch.xcontent.XContentBuilder; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public final class BulkUpdateApiKeyResponse extends ActionResponse implements ToXContentObject, Writeable { | ||
|
||
private final List<String> updated; | ||
private final List<String> noops; | ||
private final Map<String, Exception> errorDetails; | ||
|
||
public BulkUpdateApiKeyResponse(final List<String> updated, final List<String> noops, final Map<String, Exception> errorDetails) { | ||
this.updated = updated; | ||
this.noops = noops; | ||
this.errorDetails = errorDetails; | ||
} | ||
|
||
public BulkUpdateApiKeyResponse(StreamInput in) throws IOException { | ||
super(in); | ||
this.updated = in.readStringList(); | ||
this.noops = in.readStringList(); | ||
this.errorDetails = in.readMap(StreamInput::readString, StreamInput::readException); | ||
} | ||
|
||
public List<String> getUpdated() { | ||
return updated; | ||
} | ||
|
||
public List<String> getNoops() { | ||
return noops; | ||
} | ||
|
||
public Map<String, Exception> getErrorDetails() { | ||
return errorDetails; | ||
} | ||
|
||
public int getTotalResultCount() { | ||
return updated.size() + noops.size() + errorDetails.size(); | ||
} | ||
|
||
@Override | ||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { | ||
builder.startObject().stringListField("updated", updated).stringListField("noops", noops); | ||
if (errorDetails.isEmpty() == false) { | ||
builder.startObject("errors"); | ||
{ | ||
builder.field("count", errorDetails.size()); | ||
builder.startObject("details"); | ||
for (Map.Entry<String, Exception> idWithException : errorDetails.entrySet()) { | ||
builder.startObject(idWithException.getKey()); | ||
ElasticsearchException.generateThrowableXContent(builder, params, idWithException.getValue()); | ||
builder.endObject(); | ||
} | ||
builder.endObject(); | ||
} | ||
builder.endObject(); | ||
} | ||
return builder.endObject(); | ||
} | ||
|
||
@Override | ||
public void writeTo(StreamOutput out) throws IOException { | ||
out.writeStringCollection(updated); | ||
out.writeStringCollection(noops); | ||
out.writeMap(errorDetails, StreamOutput::writeString, StreamOutput::writeException); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "BulkUpdateApiKeyResponse{" + "updated=" + updated + ", noops=" + noops + ", errorDetails=" + errorDetails + '}'; | ||
} | ||
|
||
public static Builder builder() { | ||
return new Builder(); | ||
} | ||
|
||
public static class Builder { | ||
private final List<String> updated; | ||
private final List<String> noops; | ||
private final Map<String, Exception> errorDetails; | ||
|
||
public Builder() { | ||
updated = new ArrayList<>(); | ||
noops = new ArrayList<>(); | ||
errorDetails = new HashMap<>(); | ||
} | ||
|
||
public Builder updated(final String id) { | ||
updated.add(id); | ||
return this; | ||
} | ||
|
||
public Builder noop(final String id) { | ||
noops.add(id); | ||
return this; | ||
} | ||
|
||
public Builder error(final String id, final Exception ex) { | ||
errorDetails.put(id, ex); | ||
return this; | ||
} | ||
|
||
public BulkUpdateApiKeyResponse build() { | ||
return new BulkUpdateApiKeyResponse(updated, noops, errorDetails); | ||
} | ||
} | ||
} |
Oops, something went wrong.