1919import org .elasticsearch .common .CharArrays ;
2020import org .elasticsearch .common .Strings ;
2121import org .elasticsearch .common .UUIDs ;
22+ import org .elasticsearch .common .bytes .BytesReference ;
23+ import org .elasticsearch .common .logging .DeprecationLogger ;
2224import org .elasticsearch .common .settings .SecureString ;
2325import org .elasticsearch .common .settings .Setting ;
2426import org .elasticsearch .common .settings .Settings ;
2527import org .elasticsearch .common .util .concurrent .ThreadContext ;
28+ import org .elasticsearch .common .xcontent .DeprecationHandler ;
29+ import org .elasticsearch .common .xcontent .NamedXContentRegistry ;
2630import org .elasticsearch .common .xcontent .XContentBuilder ;
2731import org .elasticsearch .common .xcontent .XContentFactory ;
32+ import org .elasticsearch .common .xcontent .XContentParser ;
33+ import org .elasticsearch .common .xcontent .XContentType ;
2834import org .elasticsearch .xpack .core .XPackSettings ;
2935import org .elasticsearch .xpack .core .security .action .CreateApiKeyRequest ;
3036import org .elasticsearch .xpack .core .security .action .CreateApiKeyResponse ;
3137import org .elasticsearch .xpack .core .security .authc .Authentication ;
3238import org .elasticsearch .xpack .core .security .authc .AuthenticationResult ;
3339import 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 ;
3443import org .elasticsearch .xpack .core .security .user .User ;
44+ import org .elasticsearch .xpack .security .authz .store .CompositeRolesStore ;
3545import org .elasticsearch .xpack .security .support .SecurityIndexManager ;
3646
3747import javax .crypto .SecretKeyFactory ;
3848import java .io .Closeable ;
3949import java .io .IOException ;
50+ import java .io .UncheckedIOException ;
4051import java .security .NoSuchAlgorithmException ;
4152import java .time .Clock ;
4253import java .time .Instant ;
4354import java .util .Arrays ;
4455import java .util .Base64 ;
56+ import java .util .HashMap ;
4557import java .util .List ;
4658import java .util .Locale ;
4759import java .util .Map ;
5567public class ApiKeyService {
5668
5769 private static final Logger logger = LogManager .getLogger (ApiKeyService .class );
70+ private static final DeprecationLogger deprecationLogger = new DeprecationLogger (logger );
5871 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+
5976 public static final Setting <String > PASSWORD_HASHING_ALGORITHM = new Setting <>(
6077 "xpack.security.authc.api_key_hashing.algorithm" , "pbkdf2" , Function .identity (), (v , s ) -> {
6178 if (Hasher .getAvailableAlgoStoredHash ().contains (v .toLowerCase (Locale .ROOT )) == false ) {
@@ -126,8 +143,12 @@ public void createApiKey(Authentication authentication, CreateApiKeyRequest requ
126143 }
127144 }
128145
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 ())
131152 .field ("version" , version .id )
132153 .startObject ("creator" )
133154 .field ("principal" , authentication .getUser ().principal ())
@@ -174,7 +195,8 @@ void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener<Authentic
174195 executeAsyncWithOrigin (ctx , SECURITY_ORIGIN , getRequest , ActionListener .<GetResponse >wrap (response -> {
175196 if (response .isExists ()) {
176197 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 );
178200 }
179201 } else {
180202 credentials .close ();
@@ -194,6 +216,56 @@ void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener<Authentic
194216 }
195217 }
196218
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+
197269 /**
198270 * Validates the ApiKey using the source map
199271 * @param source the source map from a get of the ApiKey document
@@ -214,13 +286,13 @@ static void validateApiKeyCredentials(Map<String, Object> source, ApiKeyCredenti
214286 final Map <String , Object > creator = Objects .requireNonNull ((Map <String , Object >) source .get ("creator" ));
215287 final String principal = Objects .requireNonNull ((String ) creator .get ("principal" ));
216288 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 );
222291 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 ));
224296 } else {
225297 listener .onResponse (AuthenticationResult .terminate ("api key is expired" , null ));
226298 }
@@ -310,4 +382,27 @@ public void close() {
310382 key .close ();
311383 }
312384 }
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+ }
313408}
0 commit comments