Skip to content

Commit 16ba59e

Browse files
authored
Expose more authentication info to ingest pipeline (#51305) (#52119)
The changes add more granularity for identiying the data ingestion user. The ingest pipeline can now be configure to record authentication realm and type. It can also record API key name and ID when one is in use. This improves traceability when data are being ingested from multiple agents and will become more relevant with the incoming support of required pipelines (#46847) Resolves: #49106
1 parent 343ced4 commit 16ba59e

File tree

8 files changed

+222
-17
lines changed

8 files changed

+222
-17
lines changed

docs/reference/ingest/processors/set-security-user.asciidoc

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
[[ingest-node-set-security-user-processor]]
22
=== Set Security User Processor
3-
Sets user-related details (such as `username`, `roles`, `email`, `full_name`
4-
and `metadata` ) from the current
3+
Sets user-related details (such as `username`, `roles`, `email`, `full_name`,
4+
`metadata`, `api_key`, `realm` and `authentication_type`) from the current
55
authenticated user to the current document by pre-processing the ingest.
6+
The `api_key` property exists only if the user authenticates with an
7+
API key. It is an object containing the `id` and `name` fields of the API key.
8+
The `realm` property is also an object with two fields, `name` and `type`.
9+
When using API key authentication, the `realm` property refers to the realm
10+
from which the API key is created.
11+
The `authentication_type` property is a string that can take value from
12+
`REALM`, `API_KEY`, `TOKEN` and `ANONYMOUS`.
613

714
IMPORTANT: Requires an authenticated user for the index request.
815

916
[[set-security-user-options]]
1017
.Set Security User Options
1118
[options="header"]
1219
|======
13-
| Name | Required | Default | Description
14-
| `field` | yes | - | The field to store the user information into.
15-
| `properties` | no | [`username`, `roles`, `email`, `full_name`, `metadata`] | Controls what user related properties are added to the `field`.
20+
| Name | Required | Default | Description
21+
| `field` | yes | - | The field to store the user information into.
22+
| `properties` | no | [`username`, `roles`, `email`, `full_name`, `metadata`, `api_key`, `realm`, `authentication_type`] | Controls what user related properties are added to the `field`.
1623
include::common-options.asciidoc[]
1724
|======
1825

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java

+4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ public RealmRef getLookedUpBy() {
8282
return lookedUpBy;
8383
}
8484

85+
public RealmRef getSourceRealm() {
86+
return lookedUpBy == null ? authenticatedBy : lookedUpBy;
87+
}
88+
8589
public Version getVersion() {
8690
return version;
8791
}

x-pack/plugin/core/src/main/resources/security-index-template-7.json

+3
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@
194194
},
195195
"realm" : {
196196
"type" : "keyword"
197+
},
198+
"realm_type" : {
199+
"type" : "keyword"
197200
}
198201
}
199202
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
*
3+
* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
4+
* * or more contributor license agreements. Licensed under the Elastic License;
5+
* * you may not use this file except in compliance with the Elastic License.
6+
*
7+
*/
8+
9+
package org.elasticsearch.xpack.core.security.authc;
10+
11+
import org.elasticsearch.test.ESTestCase;
12+
import org.elasticsearch.xpack.core.security.user.User;
13+
14+
public class AuthenticationTests extends ESTestCase {
15+
16+
public void testWillGetLookedUpByWhenItExists() {
17+
final Authentication.RealmRef authenticatedBy = new Authentication.RealmRef("auth_by", "auth_by_type", "node");
18+
final Authentication.RealmRef lookedUpBy = new Authentication.RealmRef("lookup_by", "lookup_by_type", "node");
19+
final Authentication authentication = new Authentication(
20+
new User("user"), authenticatedBy, lookedUpBy);
21+
22+
assertEquals(lookedUpBy, authentication.getSourceRealm());
23+
}
24+
25+
public void testWillGetAuthenticateByWhenLookupIsNull() {
26+
final Authentication.RealmRef authenticatedBy = new Authentication.RealmRef("auth_by", "auth_by_type", "node");
27+
final Authentication authentication = new Authentication(
28+
new User("user"), authenticatedBy, null);
29+
30+
assertEquals(authenticatedBy, authentication.getSourceRealm());
31+
}
32+
33+
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java

+9-5
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ public class ApiKeyService {
108108
private static final Logger logger = LogManager.getLogger(ApiKeyService.class);
109109
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
110110
public static final String API_KEY_ID_KEY = "_security_api_key_id";
111+
public static final String API_KEY_NAME_KEY = "_security_api_key_name";
111112
public static final String API_KEY_REALM_NAME = "_es_api_key";
112113
public static final String API_KEY_REALM_TYPE = "_es_api_key";
113-
public static final String API_KEY_CREATOR_REALM = "_security_api_key_creator_realm";
114+
public static final String API_KEY_CREATOR_REALM_NAME = "_security_api_key_creator_realm_name";
115+
public static final String API_KEY_CREATOR_REALM_TYPE = "_security_api_key_creator_realm_type";
114116
static final String API_KEY_ROLE_DESCRIPTORS_KEY = "_security_api_key_role_descriptors";
115117
static final String API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY = "_security_api_key_limited_by_role_descriptors";
116118

@@ -279,8 +281,8 @@ XContentBuilder newDocument(SecureString apiKey, String name, Authentication aut
279281
.startObject("creator")
280282
.field("principal", authentication.getUser().principal())
281283
.field("metadata", authentication.getUser().metadata())
282-
.field("realm", authentication.getLookedUpBy() == null ?
283-
authentication.getAuthenticatedBy().getName() : authentication.getLookedUpBy().getName())
284+
.field("realm", authentication.getSourceRealm().getName())
285+
.field("realm_type", authentication.getSourceRealm().getType())
284286
.endObject()
285287
.endObject();
286288

@@ -509,10 +511,12 @@ private void validateApiKeyExpiration(Map<String, Object> source, ApiKeyCredenti
509511
: limitedByRoleDescriptors.keySet().toArray(Strings.EMPTY_ARRAY);
510512
final User apiKeyUser = new User(principal, roleNames, null, null, metadata, true);
511513
final Map<String, Object> authResultMetadata = new HashMap<>();
512-
authResultMetadata.put(API_KEY_CREATOR_REALM, creator.get("realm"));
514+
authResultMetadata.put(API_KEY_CREATOR_REALM_NAME, creator.get("realm"));
515+
authResultMetadata.put(API_KEY_CREATOR_REALM_TYPE, creator.get("realm_type"));
513516
authResultMetadata.put(API_KEY_ROLE_DESCRIPTORS_KEY, roleDescriptors);
514517
authResultMetadata.put(API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY, limitedByRoleDescriptors);
515518
authResultMetadata.put(API_KEY_ID_KEY, credentials.getId());
519+
authResultMetadata.put(API_KEY_NAME_KEY, source.get("name"));
516520
listener.onResponse(AuthenticationResult.success(apiKeyUser, authResultMetadata));
517521
} else {
518522
listener.onResponse(AuthenticationResult.unsuccessful("api key is expired", null));
@@ -886,7 +890,7 @@ public void getApiKeys(String realmName, String username, String apiKeyName, Str
886890
*/
887891
public static String getCreatorRealmName(final Authentication authentication) {
888892
if (authentication.getAuthenticatedBy().getType().equals(API_KEY_REALM_TYPE)) {
889-
return (String) authentication.getMetadata().get(API_KEY_CREATOR_REALM);
893+
return (String) authentication.getMetadata().get(API_KEY_CREATOR_REALM_NAME);
890894
} else {
891895
return authentication.getAuthenticatedBy().getName();
892896
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessor.java

+53-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.xpack.core.security.SecurityContext;
1212
import org.elasticsearch.xpack.core.security.authc.Authentication;
1313
import org.elasticsearch.xpack.core.security.user.User;
14+
import org.elasticsearch.xpack.security.authc.ApiKeyService;
1415

1516
import java.util.Arrays;
1617
import java.util.EnumSet;
@@ -88,6 +89,54 @@ public IngestDocument execute(IngestDocument ingestDocument) throws Exception {
8889
userObject.put("metadata", user.metadata());
8990
}
9091
break;
92+
case API_KEY:
93+
final String apiKey = "api_key";
94+
final Object existingApiKeyField = userObject.get(apiKey);
95+
@SuppressWarnings("unchecked")
96+
final Map<String, Object> apiKeyField =
97+
existingApiKeyField instanceof Map ? (Map<String, Object>) existingApiKeyField : new HashMap<>();
98+
Object apiKeyName = authentication.getMetadata().get(ApiKeyService.API_KEY_NAME_KEY);
99+
if (apiKeyName != null) {
100+
apiKeyField.put("name", apiKeyName);
101+
}
102+
Object apiKeyId = authentication.getMetadata().get(ApiKeyService.API_KEY_ID_KEY);
103+
if (apiKeyId != null) {
104+
apiKeyField.put("id", apiKeyId);
105+
}
106+
if (false == apiKeyField.isEmpty()) {
107+
userObject.put(apiKey, apiKeyField);
108+
}
109+
break;
110+
case REALM:
111+
final String realmKey = "realm";
112+
final Object existingRealmField = userObject.get(realmKey);
113+
@SuppressWarnings("unchecked")
114+
final Map<String, Object> realmField =
115+
existingRealmField instanceof Map ? (Map<String, Object>) existingRealmField : new HashMap<>();
116+
117+
final Object realmName, realmType;
118+
if (Authentication.AuthenticationType.API_KEY == authentication.getAuthenticationType()) {
119+
realmName = authentication.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM_NAME);
120+
realmType = authentication.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM_TYPE);
121+
} else {
122+
realmName = authentication.getSourceRealm().getName();
123+
realmType = authentication.getSourceRealm().getType();
124+
}
125+
if (realmName != null) {
126+
realmField.put("name", realmName);
127+
}
128+
if (realmType != null) {
129+
realmField.put("type", realmType);
130+
}
131+
if (false == realmField.isEmpty()) {
132+
userObject.put(realmKey, realmField);
133+
}
134+
break;
135+
case AUTHENTICATION_TYPE:
136+
if (authentication.getAuthenticationType() != null) {
137+
userObject.put("authentication_type", authentication.getAuthenticationType().toString());
138+
}
139+
break;
91140
default:
92141
throw new UnsupportedOperationException("unsupported property [" + property + "]");
93142
}
@@ -141,7 +190,10 @@ public enum Property {
141190
FULL_NAME,
142191
EMAIL,
143192
ROLES,
144-
METADATA;
193+
METADATA,
194+
API_KEY,
195+
REALM,
196+
AUTHENTICATION_TYPE;
145197

146198
static Property parse(String tag, String value) {
147199
try {

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

+15-2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ public void testAuthenticateWithApiKey() throws Exception {
148148
assertThat(auth.getStatus(), is(AuthenticationResult.Status.SUCCESS));
149149
assertThat(auth.getUser(), notNullValue());
150150
assertThat(auth.getUser().principal(), is("hulk"));
151+
assertThat(auth.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM_NAME), is("realm1"));
152+
assertThat(auth.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM_TYPE), is("native"));
153+
assertThat(auth.getMetadata().get(ApiKeyService.API_KEY_ID_KEY), is(id));
154+
assertThat(auth.getMetadata().get(ApiKeyService.API_KEY_NAME_KEY), is("test"));
151155
}
152156

153157
public void testAuthenticationIsSkippedIfLicenseDoesNotAllowIt() throws Exception {
@@ -284,6 +288,7 @@ public void testValidateApiKey() throws Exception {
284288
Map<String, Object> creatorMap = new HashMap<>();
285289
creatorMap.put("principal", "test_user");
286290
creatorMap.put("realm", "realm1");
291+
creatorMap.put("realm_type", "realm_type1");
287292
creatorMap.put("metadata", Collections.emptyMap());
288293
sourceMap.put("creator", creatorMap);
289294
sourceMap.put("api_key_invalidated", false);
@@ -302,7 +307,7 @@ public void testValidateApiKey() throws Exception {
302307
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors")));
303308
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY),
304309
equalTo(sourceMap.get("limited_by_role_descriptors")));
305-
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM), is("realm1"));
310+
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM_NAME), is("realm1"));
306311

307312
sourceMap.put("expiration_time", Clock.systemUTC().instant().plus(1L, ChronoUnit.HOURS).toEpochMilli());
308313
future = new PlainActionFuture<>();
@@ -316,7 +321,7 @@ public void testValidateApiKey() throws Exception {
316321
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors")));
317322
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY),
318323
equalTo(sourceMap.get("limited_by_role_descriptors")));
319-
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM), is("realm1"));
324+
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM_NAME), is("realm1"));
320325

321326
sourceMap.put("expiration_time", Clock.systemUTC().instant().minus(1L, ChronoUnit.HOURS).toEpochMilli());
322327
future = new PlainActionFuture<>();
@@ -561,6 +566,14 @@ public void testApiKeyCacheDisabled() {
561566
assertNull(cachedApiKeyHashResult);
562567
}
563568

569+
public void testWillAlwaysGetAuthenticationRealmName() {
570+
final Authentication.RealmRef authenticatedBy = new Authentication.RealmRef("auth_by", "auth_by_type", "node");
571+
final Authentication.RealmRef lookedUpBy = new Authentication.RealmRef("lookup_by", "lookup_by_type", "node");
572+
final Authentication authentication = new Authentication(
573+
new User("user"), authenticatedBy, lookedUpBy);
574+
assertEquals("auth_by", ApiKeyService.getCreatorRealmName(authentication));
575+
}
576+
564577
private ApiKeyService createApiKeyService(Settings baseSettings) {
565578
final Settings settings = Settings.builder()
566579
.put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true)

0 commit comments

Comments
 (0)