Skip to content

Expose owner realm_type in the returned API key information #105629

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

Merged
Merged
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
5 changes: 5 additions & 0 deletions docs/changelog/105629.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 105629
summary: Show owner `realm_type` for returned API keys
area: Security
type: enhancement
issues: []
2 changes: 2 additions & 0 deletions docs/reference/rest-api/security/get-api-keys.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ A successful call returns a JSON structure that contains the information of the
"invalidated": false, <6>
"username": "myuser", <7>
"realm": "native1", <8>
"realm_type": "native",
"metadata": { <9>
"application": "myapp"
},
Expand Down Expand Up @@ -289,6 +290,7 @@ A successful call returns a JSON structure that contains the information of one
"invalidated": false,
"username": "myuser",
"realm": "native1",
"realm_type": "native",
"metadata": {
"application": "myapp"
},
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/rest-api/security/query-api-key.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ retrieved from one or more API keys:
"invalidated": false,
"username": "elastic",
"realm": "reserved",
"realm_type": "reserved",
"metadata": {
"letter": "a"
},
Expand Down Expand Up @@ -411,6 +412,7 @@ A successful call returns a JSON structure for API key information including its
"invalidated": false,
"username": "myuser",
"realm": "native1",
"realm_type": "native",
"metadata": {
"application": "my-application"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;

Expand Down Expand Up @@ -88,6 +89,8 @@ public String value() {
private final Instant invalidation;
private final String username;
private final String realm;
@Nullable
private final String realmType;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: @Nullable here too

private final Map<String, Object> metadata;
@Nullable
private final List<RoleDescriptor> roleDescriptors;
Expand All @@ -104,6 +107,7 @@ public ApiKey(
@Nullable Instant invalidation,
String username,
String realm,
@Nullable String realmType,
@Nullable Map<String, Object> metadata,
@Nullable List<RoleDescriptor> roleDescriptors,
@Nullable List<RoleDescriptor> limitedByRoleDescriptors
Expand All @@ -118,6 +122,7 @@ public ApiKey(
invalidation,
username,
realm,
realmType,
metadata,
roleDescriptors,
limitedByRoleDescriptors == null ? null : new RoleDescriptorsIntersection(List.of(Set.copyOf(limitedByRoleDescriptors)))
Expand All @@ -134,6 +139,7 @@ private ApiKey(
Instant invalidation,
String username,
String realm,
@Nullable String realmType,
@Nullable Map<String, Object> metadata,
@Nullable List<RoleDescriptor> roleDescriptors,
@Nullable RoleDescriptorsIntersection limitedBy
Expand All @@ -150,6 +156,7 @@ private ApiKey(
this.invalidation = (invalidation != null) ? Instant.ofEpochMilli(invalidation.toEpochMilli()) : null;
this.username = username;
this.realm = realm;
this.realmType = realmType;
this.metadata = metadata == null ? Map.of() : metadata;
this.roleDescriptors = roleDescriptors != null ? List.copyOf(roleDescriptors) : null;
// This assertion will need to be changed (or removed) when derived keys are properly supported
Expand Down Expand Up @@ -193,6 +200,17 @@ public String getRealm() {
return realm;
}

public @Nullable String getRealmType() {
return realmType;
}

public @Nullable RealmConfig.RealmIdentifier getRealmIdentifier() {
if (realm != null && realmType != null) {
return new RealmConfig.RealmIdentifier(realmType, realm);
}
return null;
}

public Map<String, Object> getMetadata() {
return metadata;
}
Expand Down Expand Up @@ -223,7 +241,11 @@ public XContentBuilder innerToXContent(XContentBuilder builder, Params params) t
if (invalidation != null) {
builder.field("invalidation", invalidation.toEpochMilli());
}
builder.field("username", username).field("realm", realm).field("metadata", (metadata == null ? Map.of() : metadata));
builder.field("username", username).field("realm", realm);
if (realmType != null) {
builder.field("realm_type", realmType);
}
builder.field("metadata", (metadata == null ? Map.of() : metadata));
if (roleDescriptors != null) {
builder.startObject("role_descriptors");
for (var roleDescriptor : roleDescriptors) {
Expand Down Expand Up @@ -287,6 +309,7 @@ public int hashCode() {
invalidation,
username,
realm,
realmType,
metadata,
roleDescriptors,
limitedBy
Expand Down Expand Up @@ -314,6 +337,7 @@ public boolean equals(Object obj) {
&& Objects.equals(invalidation, other.invalidation)
&& Objects.equals(username, other.username)
&& Objects.equals(realm, other.realm)
&& Objects.equals(realmType, other.realmType)
&& Objects.equals(metadata, other.metadata)
&& Objects.equals(roleDescriptors, other.roleDescriptors)
&& Objects.equals(limitedBy, other.limitedBy);
Expand All @@ -331,9 +355,10 @@ public boolean equals(Object obj) {
(args[6] == null) ? null : Instant.ofEpochMilli((Long) args[6]),
(String) args[7],
(String) args[8],
(args[9] == null) ? null : (Map<String, Object>) args[9],
(List<RoleDescriptor>) args[10],
(RoleDescriptorsIntersection) args[11]
(String) args[9],
(args[10] == null) ? null : (Map<String, Object>) args[10],
(List<RoleDescriptor>) args[11],
(RoleDescriptorsIntersection) args[12]
);
});
static {
Expand All @@ -346,6 +371,7 @@ public boolean equals(Object obj) {
PARSER.declareLong(optionalConstructorArg(), new ParseField("invalidation"));
PARSER.declareString(constructorArg(), new ParseField("username"));
PARSER.declareString(constructorArg(), new ParseField("realm"));
PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("realm_type"));
PARSER.declareObject(optionalConstructorArg(), (p, c) -> p.map(), new ParseField("metadata"));
PARSER.declareNamedObjects(optionalConstructorArg(), (p, c, n) -> {
p.nextToken();
Expand Down Expand Up @@ -383,6 +409,8 @@ public String toString() {
+ username
+ ", realm="
+ realm
+ ", realm_type="
+ realmType
+ ", metadata="
+ metadata
+ ", role_descriptors="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public void testXContent() throws IOException {
assertThat(map.get("invalidated"), is(apiKey.isInvalidated()));
assertThat(map.get("username"), equalTo(apiKey.getUsername()));
assertThat(map.get("realm"), equalTo(apiKey.getRealm()));
assertThat(map.get("realm_type"), equalTo(apiKey.getRealmType()));
assertThat(map.get("metadata"), equalTo(Objects.requireNonNullElseGet(apiKey.getMetadata(), Map::of)));

if (apiKey.getRoleDescriptors() == null) {
Expand Down Expand Up @@ -172,6 +173,7 @@ public static ApiKey randomApiKeyInstance() {
: null;
final String username = randomAlphaOfLengthBetween(4, 10);
final String realmName = randomAlphaOfLengthBetween(3, 8);
final String realmType = randomFrom(randomAlphaOfLengthBetween(3, 8), null);
final Map<String, Object> metadata = randomMetadata();
final List<RoleDescriptor> roleDescriptors = type == ApiKey.Type.CROSS_CLUSTER
? List.of(randomCrossClusterAccessRoleDescriptor())
Expand All @@ -190,6 +192,7 @@ public static ApiKey randomApiKeyInstance() {
invalidation,
username,
realmName,
realmType,
metadata,
roleDescriptors,
limitedByRoleDescriptors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void testToXContent() throws IOException {
"realm-x",
null,
null,
null,
List.of() // empty limited-by role descriptor to simulate derived keys
);
ApiKey apiKeyInfo2 = createApiKeyInfo(
Expand All @@ -73,6 +74,7 @@ public void testToXContent() throws IOException {
Instant.ofEpochMilli(100000000L),
"user-b",
"realm-y",
"realm-type-y",
Map.of(),
List.of(),
limitedByRoleDescriptors
Expand All @@ -87,6 +89,7 @@ public void testToXContent() throws IOException {
Instant.ofEpochMilli(100000000L),
"user-c",
"realm-z",
"realm-type-z",
Map.of("foo", "bar"),
roleDescriptors,
limitedByRoleDescriptors
Expand All @@ -111,6 +114,7 @@ public void testToXContent() throws IOException {
Instant.ofEpochMilli(100000000L),
"user-c",
"realm-z",
"realm-type-z",
Map.of("foo", "bar"),
crossClusterAccessRoleDescriptors,
null
Expand Down Expand Up @@ -145,6 +149,7 @@ public void testToXContent() throws IOException {
"invalidation": 100000000,
"username": "user-b",
"realm": "realm-y",
"realm_type": "realm-type-y",
"metadata": {},
"role_descriptors": {},
"limited_by": [
Expand Down Expand Up @@ -185,6 +190,7 @@ public void testToXContent() throws IOException {
"invalidation": 100000000,
"username": "user-c",
"realm": "realm-z",
"realm_type": "realm-type-z",
"metadata": {
"foo": "bar"
},
Expand Down Expand Up @@ -252,6 +258,7 @@ public void testToXContent() throws IOException {
"invalidation": 100000000,
"username": "user-c",
"realm": "realm-z",
"realm_type": "realm-type-z",
"metadata": {
"foo": "bar"
},
Expand Down Expand Up @@ -321,6 +328,7 @@ private ApiKey createApiKeyInfo(
Instant invalidation,
String username,
String realm,
String realmType,
Map<String, Object> metadata,
List<RoleDescriptor> roleDescriptors,
List<RoleDescriptor> limitedByRoleDescriptors
Expand All @@ -335,6 +343,7 @@ private ApiKey createApiKeyInfo(
invalidation,
username,
realm,
realmType,
metadata,
roleDescriptors,
limitedByRoleDescriptors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ public void testGrantApiKeyForOtherUserWithPassword() throws IOException {

ApiKey apiKey = getApiKey((String) responseBody.get("id"));
assertThat(apiKey.getUsername(), equalTo(END_USER));
assertThat(apiKey.getRealm(), equalTo("default_native"));
assertThat(apiKey.getRealmType(), equalTo("native"));
}

public void testGrantApiKeyForOtherUserWithAccessToken() throws IOException {
Expand Down Expand Up @@ -329,6 +331,8 @@ public void testGrantApiKeyForOtherUserWithAccessToken() throws IOException {

ApiKey apiKey = getApiKey((String) responseBody.get("id"));
assertThat(apiKey.getUsername(), equalTo(END_USER));
assertThat(apiKey.getRealm(), equalTo("default_native"));
assertThat(apiKey.getRealmType(), equalTo("native"));

Instant minExpiry = before.plus(2, ChronoUnit.HOURS);
Instant maxExpiry = after.plus(2, ChronoUnit.HOURS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,10 @@ public void testGrantApiKeyForUserWithRunAs() throws IOException {
final String apiKeyId = createApiKeyResponse.getId();
final String base64ApiKeyKeyValue = Base64.getEncoder()
.encodeToString((apiKeyId + ":" + createApiKeyResponse.getKey().toString()).getBytes(StandardCharsets.UTF_8));
assertThat(securityClient.getApiKey(apiKeyId).getUsername(), equalTo("user2"));
ApiKey apiKey = securityClient.getApiKey(apiKeyId);
assertThat(apiKey.getUsername(), equalTo("user2"));
assertThat(apiKey.getRealm(), equalTo("index"));
assertThat(apiKey.getRealmType(), equalTo("native"));
final Client clientWithGrantedKey = client().filterWithHeader(Map.of("Authorization", "ApiKey " + base64ApiKeyKeyValue));
// The API key has privileges (inherited from user2) to check cluster health
clientWithGrantedKey.execute(TransportClusterHealthAction.TYPE, new ClusterHealthRequest()).actionGet();
Expand Down Expand Up @@ -618,6 +621,7 @@ public void testCreateCrossClusterApiKey() throws IOException {
assertThat(getApiKeyInfo.getMetadata(), anEmptyMap());
assertThat(getApiKeyInfo.getUsername(), equalTo("test_user"));
assertThat(getApiKeyInfo.getRealm(), equalTo("file"));
assertThat(getApiKeyInfo.getRealmType(), equalTo("file"));

// Check the API key attributes with Query API
final QueryApiKeyRequest queryApiKeyRequest = new QueryApiKeyRequest(
Expand All @@ -638,6 +642,7 @@ public void testCreateCrossClusterApiKey() throws IOException {
assertThat(queryApiKeyInfo.getMetadata(), anEmptyMap());
assertThat(queryApiKeyInfo.getUsername(), equalTo("test_user"));
assertThat(queryApiKeyInfo.getRealm(), equalTo("file"));
assertThat(queryApiKeyInfo.getRealmType(), equalTo("file"));
}

public void testUpdateCrossClusterApiKey() throws IOException {
Expand Down Expand Up @@ -672,6 +677,7 @@ public void testUpdateCrossClusterApiKey() throws IOException {
assertThat(getApiKeyInfo.getMetadata(), anEmptyMap());
assertThat(getApiKeyInfo.getUsername(), equalTo("test_user"));
assertThat(getApiKeyInfo.getRealm(), equalTo("file"));
assertThat(getApiKeyInfo.getRealmType(), equalTo("file"));

final CrossClusterApiKeyRoleDescriptorBuilder roleDescriptorBuilder;
final boolean shouldUpdateAccess = randomBoolean();
Expand Down Expand Up @@ -745,6 +751,7 @@ public void testUpdateCrossClusterApiKey() throws IOException {
assertThat(queryApiKeyInfo.getMetadata(), equalTo(updateMetadata == null ? Map.of() : updateMetadata));
assertThat(queryApiKeyInfo.getUsername(), equalTo("test_user"));
assertThat(queryApiKeyInfo.getRealm(), equalTo("file"));
assertThat(queryApiKeyInfo.getRealmType(), equalTo("file"));
}

// Cross-cluster API keys cannot be created by an API key even if it has manage_security privilege
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1937,7 +1937,6 @@ public void getApiKeys(

public void queryApiKeys(SearchRequest searchRequest, boolean withLimitedBy, ActionListener<QueryApiKeyResponse> listener) {
ensureEnabled();

final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy();
if (frozenSecurityIndex.indexExists() == false) {
logger.debug("security index does not exist");
Expand Down Expand Up @@ -2004,6 +2003,7 @@ private ApiKey convertSearchHitToApiKeyInfo(SearchHit hit, boolean withLimitedBy
apiKeyDoc.invalidation != -1 ? Instant.ofEpochMilli(apiKeyDoc.invalidation) : null,
(String) apiKeyDoc.creator.get("principal"),
(String) apiKeyDoc.creator.get("realm"),
(String) apiKeyDoc.creator.get("realm_type"),
metadata,
roleDescriptors,
limitedByRoleDescriptors
Expand Down
Loading