6868import static org .elasticsearch .xpack .core .security .authc .Authentication .RealmRef .newServiceAccountRealmRef ;
6969import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .ANONYMOUS_REALM_NAME ;
7070import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .ANONYMOUS_REALM_TYPE ;
71+ import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY ;
7172import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .API_KEY_REALM_NAME ;
7273import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .API_KEY_REALM_TYPE ;
74+ import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .API_KEY_ROLE_DESCRIPTORS_KEY ;
7375import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .ATTACH_REALM_NAME ;
7476import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .ATTACH_REALM_TYPE ;
7577import static org .elasticsearch .xpack .core .security .authc .AuthenticationField .CLOUD_API_KEY_REALM_NAME ;
@@ -569,6 +571,11 @@ public boolean supportsRunAs(@Nullable AnonymousUser anonymousUser) {
569571 return false ;
570572 }
571573
574+ // We may allow cloud API keys to run-as in the future, but for now there is no requirement
575+ if (isCloudApiKey ()) {
576+ return false ;
577+ }
578+
572579 // There is no reason for internal users to run-as. This check prevents either internal user itself
573580 // or a token created for it (though no such thing in current code) to run-as.
574581 if (getEffectiveSubject ().getUser () instanceof InternalUser ) {
@@ -748,14 +755,15 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
748755 */
749756 public void toXContentFragment (XContentBuilder builder ) throws IOException {
750757 final User user = effectiveSubject .getUser ();
758+ final Map <String , Object > metadata = getAuthenticatingSubject ().getMetadata ();
751759 builder .field (User .Fields .USERNAME .getPreferredName (), user .principal ());
752760 builder .array (User .Fields .ROLES .getPreferredName (), user .roles ());
753761 builder .field (User .Fields .FULL_NAME .getPreferredName (), user .fullName ());
754762 builder .field (User .Fields .EMAIL .getPreferredName (), user .email ());
755763 if (isServiceAccount ()) {
756- final String tokenName = (String ) getAuthenticatingSubject (). getMetadata () .get (ServiceAccountSettings .TOKEN_NAME_FIELD );
764+ final String tokenName = (String ) metadata .get (ServiceAccountSettings .TOKEN_NAME_FIELD );
757765 assert tokenName != null : "token name cannot be null" ;
758- final String tokenSource = (String ) getAuthenticatingSubject (). getMetadata () .get (ServiceAccountSettings .TOKEN_SOURCE_FIELD );
766+ final String tokenSource = (String ) metadata .get (ServiceAccountSettings .TOKEN_SOURCE_FIELD );
759767 assert tokenSource != null : "token source cannot be null" ;
760768 builder .field (
761769 User .Fields .TOKEN .getPreferredName (),
@@ -790,16 +798,31 @@ public void toXContentFragment(XContentBuilder builder) throws IOException {
790798 }
791799 builder .endObject ();
792800 builder .field (User .Fields .AUTHENTICATION_TYPE .getPreferredName (), getAuthenticationType ().name ().toLowerCase (Locale .ROOT ));
801+
793802 if (isApiKey () || isCrossClusterAccess ()) {
794- final String apiKeyId = (String ) getAuthenticatingSubject ().getMetadata ().get (AuthenticationField .API_KEY_ID_KEY );
795- final String apiKeyName = (String ) getAuthenticatingSubject ().getMetadata ().get (AuthenticationField .API_KEY_NAME_KEY );
796- if (apiKeyName == null ) {
797- builder .field ("api_key" , Map .of ("id" , apiKeyId ));
798- } else {
799- builder .field ("api_key" , Map .of ("id" , apiKeyId , "name" , apiKeyName ));
803+ final String apiKeyId = (String ) metadata .get (AuthenticationField .API_KEY_ID_KEY );
804+ final String apiKeyName = (String ) metadata .get (AuthenticationField .API_KEY_NAME_KEY );
805+ final Map <String , Object > apiKeyField = new HashMap <>();
806+ apiKeyField .put ("id" , apiKeyId );
807+ if (apiKeyName != null ) {
808+ apiKeyField .put ("name" , apiKeyName );
809+ }
810+ apiKeyField .put ("managed_by" , CredentialManagedBy .ELASTICSEARCH .getDisplayName ());
811+ builder .field ("api_key" , Collections .unmodifiableMap (apiKeyField ));
812+
813+ } else if (isCloudApiKey ()) {
814+ final String apiKeyId = user .principal ();
815+ final String apiKeyName = (String ) user .metadata ().get (AuthenticationField .API_KEY_NAME_KEY );
816+ final boolean internal = (boolean ) user .metadata ().get (AuthenticationField .API_KEY_INTERNAL_KEY );
817+ final Map <String , Object > apiKeyField = new HashMap <>();
818+ apiKeyField .put ("id" , apiKeyId );
819+ if (apiKeyName != null ) {
820+ apiKeyField .put ("name" , apiKeyName );
800821 }
822+ apiKeyField .put ("internal" , internal );
823+ apiKeyField .put ("managed_by" , CredentialManagedBy .CLOUD .getDisplayName ());
824+ builder .field ("api_key" , Collections .unmodifiableMap (apiKeyField ));
801825 }
802- // TODO cloud API key fields such as managed_by
803826 }
804827
805828 public static Authentication getAuthenticationFromCrossClusterAccessMetadata (Authentication authentication ) {
@@ -924,10 +947,11 @@ private void checkConsistencyForApiKeyAuthenticationType() {
924947 Strings .format ("API key authentication cannot have realm type [%s]" , authenticatingRealm .type )
925948 );
926949 }
927- if (authenticatingRealm . isCloudApiKeyRealm () ) {
928- // TODO consistency check for cloud API keys
950+ if (authenticatingSubject . getType () == Subject . Type . CLOUD_API_KEY ) {
951+ checkConsistencyForCloudApiKeyAuthenticatingSubject ( "Cloud API key" );
929952 return ;
930953 }
954+
931955 checkConsistencyForApiKeyAuthenticatingSubject ("API key" );
932956 if (Subject .Type .CROSS_CLUSTER_ACCESS == authenticatingSubject .getType ()) {
933957 if (authenticatingSubject .getMetadata ().get (CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY ) == null ) {
@@ -1021,6 +1045,18 @@ private void checkConsistencyForApiKeyAuthenticatingSubject(String prefixMessage
10211045 }
10221046 }
10231047
1048+ private void checkConsistencyForCloudApiKeyAuthenticatingSubject (String prefixMessage ) {
1049+ final RealmRef authenticatingRealm = authenticatingSubject .getRealm ();
1050+ checkNoDomain (authenticatingRealm , prefixMessage );
1051+ checkNoInternalUser (authenticatingSubject , prefixMessage );
1052+ checkNoRunAs (this , prefixMessage );
1053+ if (authenticatingSubject .getMetadata ().get (CROSS_CLUSTER_ACCESS_ROLE_DESCRIPTORS_KEY ) != null
1054+ || authenticatingSubject .getMetadata ().get (API_KEY_ROLE_DESCRIPTORS_KEY ) != null
1055+ || authenticatingSubject .getMetadata ().get (API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY ) != null ) {
1056+ throw new IllegalArgumentException (prefixMessage + " authentication cannot contain a role descriptors metadata field" );
1057+ }
1058+ }
1059+
10241060 private static void checkNoInternalUser (Subject subject , String prefixMessage ) {
10251061 if (subject .getUser () instanceof InternalUser ) {
10261062 throw new IllegalArgumentException (
@@ -1057,7 +1093,8 @@ private static boolean hasSyntheticRealmNameOrType(@Nullable RealmRef realmRef)
10571093 ANONYMOUS_REALM_NAME ,
10581094 FALLBACK_REALM_NAME ,
10591095 ATTACH_REALM_NAME ,
1060- CROSS_CLUSTER_ACCESS_REALM_NAME
1096+ CROSS_CLUSTER_ACCESS_REALM_NAME ,
1097+ CLOUD_API_KEY_REALM_NAME
10611098 ).contains (realmRef .getName ())) {
10621099 return true ;
10631100 }
@@ -1067,7 +1104,8 @@ private static boolean hasSyntheticRealmNameOrType(@Nullable RealmRef realmRef)
10671104 ANONYMOUS_REALM_TYPE ,
10681105 FALLBACK_REALM_TYPE ,
10691106 ATTACH_REALM_TYPE ,
1070- CROSS_CLUSTER_ACCESS_REALM_TYPE
1107+ CROSS_CLUSTER_ACCESS_REALM_TYPE ,
1108+ CLOUD_API_KEY_REALM_TYPE
10711109 ).contains (realmRef .getType ())) {
10721110 return true ;
10731111 }
@@ -1649,6 +1687,20 @@ public enum AuthenticationType {
16491687 INTERNAL
16501688 }
16511689
1690+ /**
1691+ * Indicates if credentials are managed by Elasticsearch or by the Cloud.
1692+ */
1693+ public enum CredentialManagedBy {
1694+
1695+ CLOUD ,
1696+
1697+ ELASTICSEARCH ;
1698+
1699+ public String getDisplayName () {
1700+ return name ().toLowerCase (Locale .ROOT );
1701+ }
1702+ }
1703+
16521704 public static class AuthenticationSerializationHelper {
16531705
16541706 private AuthenticationSerializationHelper () {}
0 commit comments