Skip to content

Commit f01f07f

Browse files
authored
Handle API key type in Get/Query API key responses (#96014)
This PR makes the Get/Query API key APIs to return the new API key type information in their responses. For cross-cluster type API keys, the limited-by field is not shown because they do not use the limited-by model as existing API keys. Relates: #95714
1 parent 1f8d6eb commit f01f07f

File tree

10 files changed

+196
-43
lines changed

10 files changed

+196
-43
lines changed

server/src/main/java/org/elasticsearch/TransportVersion.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,13 @@ private static TransportVersion registerTransportVersion(int id, String uniqueId
121121
* Detached transport versions added below here.
122122
*/
123123
public static final TransportVersion V_8_500_000 = registerTransportVersion(8_500_000, "dc3cbf06-3ed5-4e1b-9978-ee1d04d235bc");
124+
public static final TransportVersion V_8_500_001 = registerTransportVersion(8_500_001, "c943cfe5-c89d-4eae-989f-f5f4537e84e0");
124125

125126
/**
126127
* Reference to the most recent transport version.
127128
* This should be the transport version with the highest id.
128129
*/
129-
public static final TransportVersion CURRENT = V_8_500_000;
130+
public static final TransportVersion CURRENT = V_8_500_001;
130131

131132
/**
132133
* Reference to the earliest compatible transport version to this version of the codebase.

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/ApiKey.java

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.elasticsearch.common.io.stream.Writeable;
1414
import org.elasticsearch.common.xcontent.XContentParserUtils;
1515
import org.elasticsearch.core.Nullable;
16+
import org.elasticsearch.transport.TcpTransport;
1617
import org.elasticsearch.xcontent.ConstructingObjectParser;
1718
import org.elasticsearch.xcontent.ObjectParser;
1819
import org.elasticsearch.xcontent.ParseField;
@@ -80,6 +81,7 @@ public String value() {
8081

8182
private final String name;
8283
private final String id;
84+
private final Type type;
8385
private final Instant creation;
8486
private final Instant expiration;
8587
private final boolean invalidated;
@@ -94,6 +96,7 @@ public String value() {
9496
public ApiKey(
9597
String name,
9698
String id,
99+
Type type,
97100
Instant creation,
98101
Instant expiration,
99102
boolean invalidated,
@@ -106,6 +109,7 @@ public ApiKey(
106109
this(
107110
name,
108111
id,
112+
type,
109113
creation,
110114
expiration,
111115
invalidated,
@@ -120,6 +124,7 @@ public ApiKey(
120124
private ApiKey(
121125
String name,
122126
String id,
127+
Type type,
123128
Instant creation,
124129
Instant expiration,
125130
boolean invalidated,
@@ -131,6 +136,7 @@ private ApiKey(
131136
) {
132137
this.name = name;
133138
this.id = id;
139+
this.type = type;
134140
// As we do not yet support the nanosecond precision when we serialize to JSON,
135141
// here creating the 'Instant' of milliseconds precision.
136142
// This Instant can then be used for date comparison.
@@ -153,6 +159,14 @@ public ApiKey(StreamInput in) throws IOException {
153159
this.name = in.readString();
154160
}
155161
this.id = in.readString();
162+
if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_500_001)) {
163+
this.type = in.readEnum(Type.class);
164+
} else {
165+
// This default is safe because
166+
// 1. ApiKey objects never transfer between nodes
167+
// 2. Creating cross-cluster API keys mandates minimal node version that understands the API key type
168+
this.type = Type.REST;
169+
}
156170
this.creation = in.readInstant();
157171
this.expiration = in.readOptionalInstant();
158172
this.invalidated = in.readBoolean();
@@ -181,6 +195,10 @@ public String getName() {
181195
return name;
182196
}
183197

198+
public Type getType() {
199+
return type;
200+
}
201+
184202
public Instant getCreation() {
185203
return creation;
186204
}
@@ -221,7 +239,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
221239
}
222240

223241
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
224-
builder.field("id", id).field("name", name).field("creation", creation.toEpochMilli());
242+
builder.field("id", id).field("name", name);
243+
if (TcpTransport.isUntrustedRemoteClusterEnabled()) {
244+
builder.field("type", type.value());
245+
}
246+
builder.field("creation", creation.toEpochMilli());
225247
if (expiration != null) {
226248
builder.field("expiration", expiration.toEpochMilli());
227249
}
@@ -237,6 +259,7 @@ public XContentBuilder innerToXContent(XContentBuilder builder, Params params) t
237259
builder.endObject();
238260
}
239261
if (limitedBy != null) {
262+
assert type != Type.CROSS_CLUSTER;
240263
builder.field("limited_by", limitedBy);
241264
}
242265
return builder;
@@ -250,6 +273,9 @@ public void writeTo(StreamOutput out) throws IOException {
250273
out.writeString(name);
251274
}
252275
out.writeString(id);
276+
if (out.getTransportVersion().onOrAfter(TransportVersion.V_8_500_001)) {
277+
out.writeEnum(type);
278+
}
253279
out.writeInstant(creation);
254280
out.writeOptionalInstant(expiration);
255281
out.writeBoolean(invalidated);
@@ -266,7 +292,7 @@ public void writeTo(StreamOutput out) throws IOException {
266292

267293
@Override
268294
public int hashCode() {
269-
return Objects.hash(name, id, creation, expiration, invalidated, username, realm, metadata, roleDescriptors, limitedBy);
295+
return Objects.hash(name, id, type, creation, expiration, invalidated, username, realm, metadata, roleDescriptors, limitedBy);
270296
}
271297

272298
@Override
@@ -283,6 +309,7 @@ public boolean equals(Object obj) {
283309
ApiKey other = (ApiKey) obj;
284310
return Objects.equals(name, other.name)
285311
&& Objects.equals(id, other.id)
312+
&& Objects.equals(type, other.type)
286313
&& Objects.equals(creation, other.creation)
287314
&& Objects.equals(expiration, other.expiration)
288315
&& Objects.equals(invalidated, other.invalidated)
@@ -298,19 +325,22 @@ public boolean equals(Object obj) {
298325
return new ApiKey(
299326
(String) args[0],
300327
(String) args[1],
301-
Instant.ofEpochMilli((Long) args[2]),
302-
(args[3] == null) ? null : Instant.ofEpochMilli((Long) args[3]),
303-
(Boolean) args[4],
304-
(String) args[5],
328+
// TODO: remove null check once TcpTransport.isUntrustedRemoteClusterEnabled() is removed
329+
args[2] == null ? Type.REST : (Type) args[2],
330+
Instant.ofEpochMilli((Long) args[3]),
331+
(args[4] == null) ? null : Instant.ofEpochMilli((Long) args[4]),
332+
(Boolean) args[5],
305333
(String) args[6],
306-
(args[7] == null) ? null : (Map<String, Object>) args[7],
307-
(List<RoleDescriptor>) args[8],
308-
(RoleDescriptorsIntersection) args[9]
334+
(String) args[7],
335+
(args[8] == null) ? null : (Map<String, Object>) args[8],
336+
(List<RoleDescriptor>) args[9],
337+
(RoleDescriptorsIntersection) args[10]
309338
);
310339
});
311340
static {
312341
PARSER.declareString(constructorArg(), new ParseField("name"));
313342
PARSER.declareString(constructorArg(), new ParseField("id"));
343+
PARSER.declareField(optionalConstructorArg(), Type::fromXContent, new ParseField("type"), ObjectParser.ValueType.STRING);
314344
PARSER.declareLong(constructorArg(), new ParseField("creation"));
315345
PARSER.declareLong(optionalConstructorArg(), new ParseField("expiration"));
316346
PARSER.declareBoolean(constructorArg(), new ParseField("invalidated"));
@@ -339,6 +369,8 @@ public String toString() {
339369
+ name
340370
+ ", id="
341371
+ id
372+
+ ", type="
373+
+ type.value()
342374
+ ", creation="
343375
+ creation
344376
+ ", expiration="

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/apikey/ApiKeyTests.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.common.xcontent.XContentHelper;
1212
import org.elasticsearch.test.ESTestCase;
1313
import org.elasticsearch.test.XContentTestUtils;
14+
import org.elasticsearch.transport.TcpTransport;
1415
import org.elasticsearch.xcontent.ToXContent;
1516
import org.elasticsearch.xcontent.XContentBuilder;
1617
import org.elasticsearch.xcontent.XContentFactory;
@@ -32,13 +33,15 @@
3233
import static org.hamcrest.Matchers.is;
3334
import static org.hamcrest.Matchers.not;
3435
import static org.hamcrest.Matchers.notNullValue;
36+
import static org.hamcrest.Matchers.nullValue;
3537

3638
public class ApiKeyTests extends ESTestCase {
3739

3840
@SuppressWarnings("unchecked")
3941
public void testXContent() throws IOException {
4042
final String name = randomAlphaOfLengthBetween(4, 10);
4143
final String id = randomAlphaOfLength(20);
44+
final ApiKey.Type type = TcpTransport.isUntrustedRemoteClusterEnabled() ? randomFrom(ApiKey.Type.values()) : ApiKey.Type.REST;
4245
// between 1970 and 2065
4346
final Instant creation = Instant.ofEpochSecond(randomLongBetween(0, 3000000000L), randomLongBetween(0, 999999999));
4447
final Instant expiration = randomBoolean()
@@ -49,11 +52,14 @@ public void testXContent() throws IOException {
4952
final String realmName = randomAlphaOfLengthBetween(3, 8);
5053
final Map<String, Object> metadata = randomMetadata();
5154
final List<RoleDescriptor> roleDescriptors = randomBoolean() ? null : randomUniquelyNamedRoleDescriptors(0, 3);
52-
final List<RoleDescriptor> limitedByRoleDescriptors = randomUniquelyNamedRoleDescriptors(0, 3);
55+
final List<RoleDescriptor> limitedByRoleDescriptors = type == ApiKey.Type.CROSS_CLUSTER
56+
? null
57+
: randomUniquelyNamedRoleDescriptors(0, 3);
5358

5459
final ApiKey apiKey = new ApiKey(
5560
name,
5661
id,
62+
type,
5763
creation,
5864
expiration,
5965
invalidated,
@@ -77,6 +83,9 @@ public void testXContent() throws IOException {
7783

7884
assertThat(map.get("name"), equalTo(name));
7985
assertThat(map.get("id"), equalTo(id));
86+
if (TcpTransport.isUntrustedRemoteClusterEnabled()) {
87+
assertThat(map.get("type"), equalTo(type.value()));
88+
}
8089
assertThat(Long.valueOf(map.get("creation").toString()), equalTo(creation.toEpochMilli()));
8190
if (expiration != null) {
8291
assertThat(Long.valueOf(map.get("expiration").toString()), equalTo(expiration.toEpochMilli()));
@@ -100,12 +109,16 @@ public void testXContent() throws IOException {
100109
}
101110

102111
final var limitedByList = (List<Map<String, Object>>) map.get("limited_by");
103-
assertThat(limitedByList.size(), equalTo(1));
104-
final Map<String, Object> limitedByMap = limitedByList.get(0);
105-
assertThat(limitedByMap.size(), equalTo(limitedByRoleDescriptors.size()));
106-
for (RoleDescriptor roleDescriptor : limitedByRoleDescriptors) {
107-
assertThat(limitedByMap, hasKey(roleDescriptor.getName()));
108-
assertThat(XContentTestUtils.convertToMap(roleDescriptor), equalTo(limitedByMap.get(roleDescriptor.getName())));
112+
if (type != ApiKey.Type.CROSS_CLUSTER) {
113+
assertThat(limitedByList.size(), equalTo(1));
114+
final Map<String, Object> limitedByMap = limitedByList.get(0);
115+
assertThat(limitedByMap.size(), equalTo(limitedByRoleDescriptors.size()));
116+
for (RoleDescriptor roleDescriptor : limitedByRoleDescriptors) {
117+
assertThat(limitedByMap, hasKey(roleDescriptor.getName()));
118+
assertThat(XContentTestUtils.convertToMap(roleDescriptor), equalTo(limitedByMap.get(roleDescriptor.getName())));
119+
}
120+
} else {
121+
assertThat(limitedByList, nullValue());
109122
}
110123
}
111124

@@ -130,7 +143,6 @@ private ApiKey.Type parseTypeString(String typeString) throws IOException {
130143
}
131144
}
132145

133-
@SuppressWarnings("unchecked")
134146
public static Map<String, Object> randomMetadata() {
135147
return randomFrom(
136148
Map.of(

0 commit comments

Comments
 (0)