19
19
import org .elasticsearch .common .CharArrays ;
20
20
import org .elasticsearch .common .Strings ;
21
21
import org .elasticsearch .common .UUIDs ;
22
+ import org .elasticsearch .common .bytes .BytesReference ;
23
+ import org .elasticsearch .common .logging .DeprecationLogger ;
22
24
import org .elasticsearch .common .settings .SecureString ;
23
25
import org .elasticsearch .common .settings .Setting ;
24
26
import org .elasticsearch .common .settings .Settings ;
25
27
import org .elasticsearch .common .util .concurrent .ThreadContext ;
28
+ import org .elasticsearch .common .xcontent .DeprecationHandler ;
29
+ import org .elasticsearch .common .xcontent .NamedXContentRegistry ;
26
30
import org .elasticsearch .common .xcontent .XContentBuilder ;
27
31
import org .elasticsearch .common .xcontent .XContentFactory ;
32
+ import org .elasticsearch .common .xcontent .XContentParser ;
33
+ import org .elasticsearch .common .xcontent .XContentType ;
28
34
import org .elasticsearch .xpack .core .XPackSettings ;
29
35
import org .elasticsearch .xpack .core .security .action .CreateApiKeyRequest ;
30
36
import org .elasticsearch .xpack .core .security .action .CreateApiKeyResponse ;
31
37
import org .elasticsearch .xpack .core .security .authc .Authentication ;
32
38
import org .elasticsearch .xpack .core .security .authc .AuthenticationResult ;
33
39
import org .elasticsearch .xpack .core .security .authc .support .Hasher ;
40
+ import org .elasticsearch .xpack .core .security .authz .RoleDescriptor ;
41
+ import org .elasticsearch .xpack .core .security .authz .permission .FieldPermissionsCache ;
42
+ import org .elasticsearch .xpack .core .security .authz .permission .Role ;
34
43
import org .elasticsearch .xpack .core .security .user .User ;
44
+ import org .elasticsearch .xpack .security .authz .store .CompositeRolesStore ;
35
45
import org .elasticsearch .xpack .security .support .SecurityIndexManager ;
36
46
37
47
import javax .crypto .SecretKeyFactory ;
38
48
import java .io .Closeable ;
39
49
import java .io .IOException ;
50
+ import java .io .UncheckedIOException ;
40
51
import java .security .NoSuchAlgorithmException ;
41
52
import java .time .Clock ;
42
53
import java .time .Instant ;
43
54
import java .util .Arrays ;
44
55
import java .util .Base64 ;
56
+ import java .util .HashMap ;
45
57
import java .util .List ;
46
58
import java .util .Locale ;
47
59
import java .util .Map ;
55
67
public class ApiKeyService {
56
68
57
69
private static final Logger logger = LogManager .getLogger (ApiKeyService .class );
70
+ private static final DeprecationLogger deprecationLogger = new DeprecationLogger (logger );
58
71
private static final String TYPE = "doc" ;
72
+ static final String API_KEY_ID_KEY = "_security_api_key_id" ;
73
+ static final String API_KEY_ROLE_DESCRIPTORS_KEY = "_security_api_key_role_descriptors" ;
74
+ static final String API_KEY_ROLE_KEY = "_security_api_key_role" ;
75
+
59
76
public static final Setting <String > PASSWORD_HASHING_ALGORITHM = new Setting <>(
60
77
"xpack.security.authc.api_key_hashing.algorithm" , "pbkdf2" , Function .identity (), (v , s ) -> {
61
78
if (Hasher .getAvailableAlgoStoredHash ().contains (v .toLowerCase (Locale .ROOT )) == false ) {
@@ -126,8 +143,12 @@ public void createApiKey(Authentication authentication, CreateApiKeyRequest requ
126
143
}
127
144
}
128
145
129
- builder .array ("role_descriptors" , request .getRoleDescriptors ())
130
- .field ("name" , request .getName ())
146
+ builder .startObject ("role_descriptors" );
147
+ for (RoleDescriptor descriptor : request .getRoleDescriptors ()) {
148
+ builder .field (descriptor .getName (), (contentBuilder , params ) -> descriptor .toXContent (contentBuilder , params , true ));
149
+ }
150
+ builder .endObject ();
151
+ builder .field ("name" , request .getName ())
131
152
.field ("version" , version .id )
132
153
.startObject ("creator" )
133
154
.field ("principal" , authentication .getUser ().principal ())
@@ -174,7 +195,8 @@ void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener<Authentic
174
195
executeAsyncWithOrigin (ctx , SECURITY_ORIGIN , getRequest , ActionListener .<GetResponse >wrap (response -> {
175
196
if (response .isExists ()) {
176
197
try (ApiKeyCredentials ignore = credentials ) {
177
- validateApiKeyCredentials (response .getSource (), credentials , clock , listener );
198
+ final Map <String , Object > source = response .getSource ();
199
+ validateApiKeyCredentials (source , credentials , clock , listener );
178
200
}
179
201
} else {
180
202
credentials .close ();
@@ -194,6 +216,56 @@ void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener<Authentic
194
216
}
195
217
}
196
218
219
+ /**
220
+ * The current request has been authenticated by an API key and this method enables the
221
+ * retrieval of role descriptors that are associated with the api key and triggers the building
222
+ * of the {@link Role} to authorize the request.
223
+ */
224
+ public void getRoleForApiKey (Authentication authentication , ThreadContext threadContext , CompositeRolesStore rolesStore ,
225
+ FieldPermissionsCache fieldPermissionsCache , ActionListener <Role > listener ) {
226
+ if (authentication .getAuthenticationType () != Authentication .AuthenticationType .API_KEY ) {
227
+ throw new IllegalStateException ("authentication type must be api key but is " + authentication .getAuthenticationType ());
228
+ }
229
+
230
+ final Map <String , Object > metadata = authentication .getMetadata ();
231
+ final String apiKeyId = (String ) metadata .get (API_KEY_ID_KEY );
232
+ final String contextKeyId = threadContext .getTransient (API_KEY_ID_KEY );
233
+ if (apiKeyId .equals (contextKeyId )) {
234
+ final Role preBuiltRole = threadContext .getTransient (API_KEY_ROLE_KEY );
235
+ if (preBuiltRole != null ) {
236
+ listener .onResponse (preBuiltRole );
237
+ return ;
238
+ }
239
+ } else if (contextKeyId != null ) {
240
+ throw new IllegalStateException ("authentication api key id [" + apiKeyId + "] does not match context value [" +
241
+ contextKeyId + "]" );
242
+ }
243
+
244
+ final Map <String , Object > roleDescriptors = (Map <String , Object >) metadata .get (API_KEY_ROLE_DESCRIPTORS_KEY );
245
+ final List <RoleDescriptor > roleDescriptorList = roleDescriptors .entrySet ().stream ()
246
+ .map (entry -> {
247
+ final String name = entry .getKey ();
248
+ final Map <String , Object > rdMap = (Map <String , Object >) entry .getValue ();
249
+ try (XContentBuilder builder = XContentBuilder .builder (XContentType .JSON .xContent ())) {
250
+ builder .map (rdMap );
251
+ try (XContentParser parser = XContentType .JSON .xContent ().createParser (NamedXContentRegistry .EMPTY ,
252
+ new ApiKeyLoggingDeprecationHandler (deprecationLogger , apiKeyId ),
253
+ BytesReference .bytes (builder ).streamInput ())) {
254
+ return RoleDescriptor .parse (name , parser , false );
255
+ }
256
+ } catch (IOException e ) {
257
+ throw new UncheckedIOException (e );
258
+ }
259
+ }).collect (Collectors .toList ());
260
+
261
+ rolesStore .buildRoleFromDescriptors (roleDescriptorList , fieldPermissionsCache , ActionListener .wrap (role -> {
262
+ threadContext .putTransient (API_KEY_ID_KEY , apiKeyId );
263
+ threadContext .putTransient (API_KEY_ROLE_KEY , role );
264
+ listener .onResponse (role );
265
+ }, listener ::onFailure ));
266
+
267
+ }
268
+
197
269
/**
198
270
* Validates the ApiKey using the source map
199
271
* @param source the source map from a get of the ApiKey document
@@ -214,13 +286,13 @@ static void validateApiKeyCredentials(Map<String, Object> source, ApiKeyCredenti
214
286
final Map <String , Object > creator = Objects .requireNonNull ((Map <String , Object >) source .get ("creator" ));
215
287
final String principal = Objects .requireNonNull ((String ) creator .get ("principal" ));
216
288
final Map <String , Object > metadata = (Map <String , Object >) creator .get ("metadata" );
217
- final List <Map <String , Object >> roleDescriptors = (List <Map <String , Object >>) source .get ("role_descriptors" );
218
- final String [] roleNames = roleDescriptors .stream ()
219
- .map (rdSource -> (String ) rdSource .get ("name" ))
220
- .collect (Collectors .toList ())
221
- .toArray (Strings .EMPTY_ARRAY );
289
+ final Map <String , Object > roleDescriptors = (Map <String , Object >) source .get ("role_descriptors" );
290
+ final String [] roleNames = roleDescriptors .keySet ().toArray (Strings .EMPTY_ARRAY );
222
291
final User apiKeyUser = new User (principal , roleNames , null , null , metadata , true );
223
- listener .onResponse (AuthenticationResult .success (apiKeyUser ));
292
+ final Map <String , Object > authResultMetadata = new HashMap <>();
293
+ authResultMetadata .put (API_KEY_ROLE_DESCRIPTORS_KEY , roleDescriptors );
294
+ authResultMetadata .put (API_KEY_ID_KEY , credentials .getId ());
295
+ listener .onResponse (AuthenticationResult .success (apiKeyUser , authResultMetadata ));
224
296
} else {
225
297
listener .onResponse (AuthenticationResult .terminate ("api key is expired" , null ));
226
298
}
@@ -310,4 +382,27 @@ public void close() {
310
382
key .close ();
311
383
}
312
384
}
385
+
386
+ private static class ApiKeyLoggingDeprecationHandler implements DeprecationHandler {
387
+
388
+ private final DeprecationLogger deprecationLogger ;
389
+ private final String apiKeyId ;
390
+
391
+ private ApiKeyLoggingDeprecationHandler (DeprecationLogger logger , String apiKeyId ) {
392
+ this .deprecationLogger = logger ;
393
+ this .apiKeyId = apiKeyId ;
394
+ }
395
+
396
+ @ Override
397
+ public void usedDeprecatedName (String usedName , String modernName ) {
398
+ deprecationLogger .deprecated ("Deprecated field [{}] used in api key [{}], expected [{}] instead" ,
399
+ usedName , apiKeyId , modernName );
400
+ }
401
+
402
+ @ Override
403
+ public void usedDeprecatedField (String usedName , String replacedWith ) {
404
+ deprecationLogger .deprecated ("Deprecated field [{}] used in api key [{}], replaced by [{}]" ,
405
+ usedName , apiKeyId , replacedWith );
406
+ }
407
+ }
313
408
}
0 commit comments