-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Allow Transport Actions to indicate authN realm #45767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
73c5067
aa1c22e
c7a5837
83a4d65
b66d6b2
470b0a9
3b4790e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -33,28 +33,28 @@ and <<security-api-oidc-logout,OpenID Connect logout API>>. | |||||
| The following parameters can be specified in the body of the request: | ||||||
|
|
||||||
| `realm`:: | ||||||
| The name of the OpenID Connect realm in {es} the configuration of which should | ||||||
| (Optional, string) The name of the OpenID Connect realm in {es} the configuration of which should | ||||||
| be used in order to generate the authentication request. Cannot be specified | ||||||
| when `iss` is specified. | ||||||
| when `iss` is specified. One of `realm`, `iss` needs to be specified. | ||||||
|
|
||||||
| `state`:: | ||||||
| String value used to maintain state between the authentication request and the | ||||||
| (Optional, string) Value used to maintain state between the authentication request and the | ||||||
| response, typically used as a Cross-Site Request Forgery mitigation. If the | ||||||
| caller of the API doesn't provide a value, {es} will generate one with | ||||||
| sufficient entropy itself and return it in the response. | ||||||
|
|
||||||
| `nonce`:: | ||||||
| String value used to associate a Client session with an ID Token and to mitigate | ||||||
| (Optional, string) Value used to associate a Client session with an ID Token and to mitigate | ||||||
| replay attacks. If the caller of the API doesn't provide a value, {es} will | ||||||
| generate one with sufficient entropy itself and return it in the response. | ||||||
|
|
||||||
| `issuer`:: | ||||||
| In the case of a 3rd Party initiated Single Sign On, this is the Issuer | ||||||
| `iss`:: | ||||||
| (Optional, string) In the case of a 3rd Party initiated Single Sign On, this is the Issuer | ||||||
| Identifier for the OP that the RP is to send the Authentication Request to. | ||||||
| Cannot be specified when `realm` is specified. | ||||||
| Cannot be specified when `realm` is specified. One of `realm`, `iss` needs to be specified. | ||||||
|
||||||
| Cannot be specified when `realm` is specified. One of `realm`, `iss` needs to be specified. | |
| Cannot be specified when `realm` is specified. One of `realm`, `iss` is required. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |||||
| */ | ||||||
| package org.elasticsearch.xpack.core.security.action.oidc; | ||||||
|
|
||||||
| import org.elasticsearch.Version; | ||||||
| import org.elasticsearch.action.ActionRequest; | ||||||
| import org.elasticsearch.action.ActionRequestValidationException; | ||||||
| import org.elasticsearch.common.Strings; | ||||||
|
|
@@ -38,6 +39,11 @@ public class OpenIdConnectAuthenticateRequest extends ActionRequest { | |||||
| */ | ||||||
| private String nonce; | ||||||
|
|
||||||
| /** | ||||||
| * The name of the OIDC Realm that should consume the authentication request | ||||||
| */ | ||||||
| private String realm; | ||||||
|
|
||||||
| public OpenIdConnectAuthenticateRequest() { | ||||||
|
|
||||||
| } | ||||||
|
|
@@ -47,6 +53,10 @@ public OpenIdConnectAuthenticateRequest(StreamInput in) throws IOException { | |||||
| redirectUri = in.readString(); | ||||||
| state = in.readString(); | ||||||
| nonce = in.readString(); | ||||||
| if (in.getVersion().onOrAfter(Version.V_7_4_0)){ | ||||||
|
||||||
| if (in.getVersion().onOrAfter(Version.V_7_4_0)){ | |
| if (in.getVersion().onOrAfter(Version.V_7_4_0)) { |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (out.getVersion().onOrAfter(Version.V_7_4_0)){ | |
| if (out.getVersion().onOrAfter(Version.V_7_4_0)) { |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
|
|
||
| import org.elasticsearch.action.ActionRequest; | ||
| import org.elasticsearch.action.ActionRequestValidationException; | ||
| import org.elasticsearch.common.Nullable; | ||
| import org.elasticsearch.common.io.stream.StreamInput; | ||
|
|
||
| import java.io.IOException; | ||
|
|
@@ -19,6 +20,8 @@ public final class SamlAuthenticateRequest extends ActionRequest { | |
|
|
||
| private byte[] saml; | ||
| private List<String> validRequestIds; | ||
| @Nullable | ||
| private String realm; | ||
|
|
||
| public SamlAuthenticateRequest(StreamInput in) throws IOException { | ||
| super(in); | ||
|
|
@@ -47,4 +50,12 @@ public List<String> getValidRequestIds() { | |
| public void setValidRequestIds(List<String> validRequestIds) { | ||
| this.validRequestIds = validRequestIds; | ||
| } | ||
|
|
||
| public String getRealm() { | ||
| return realm; | ||
| } | ||
|
|
||
| public void setRealm(String realm) { | ||
| this.realm = realm; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Erk! This class really ought to have serialization logic in it. Can we add that as a followup PR?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will do |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |
| import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField; | ||
| import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; | ||
| import org.elasticsearch.xpack.core.security.authc.Realm; | ||
| import org.elasticsearch.xpack.core.security.authc.RealmConfig; | ||
| import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo; | ||
| import org.elasticsearch.xpack.core.security.support.Exceptions; | ||
| import org.elasticsearch.xpack.core.security.user.AnonymousUser; | ||
|
|
@@ -56,6 +57,7 @@ | |
| import java.util.concurrent.atomic.AtomicLong; | ||
| import java.util.function.BiConsumer; | ||
| import java.util.function.Consumer; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isIndexDeleted; | ||
| import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isMoveFromRedToNonRed; | ||
|
|
@@ -140,16 +142,33 @@ public void authenticate(String action, TransportMessage message, User fallbackU | |
| } | ||
|
|
||
| /** | ||
| * Authenticates the username and password that are provided as parameters. This will not look | ||
| * at the values in the ThreadContext for Authentication. | ||
| * Authenticates the user based on the contents of the token that is provided as parameter.If {@code preferredRealm} can't | ||
| * authenticate the token, no other realms will be attempted. | ||
| * This will not look at the values in the ThreadContext for Authentication. | ||
| * | ||
| * @param action The action of the message | ||
| * @param message The message that resulted in this authenticate call | ||
| * @param token The token (credentials) to be authenticated | ||
| */ | ||
| public void authenticate(String action, TransportMessage message, | ||
| AuthenticationToken token, ActionListener<Authentication> listener) { | ||
| new Authenticator(action, message, null, listener).authenticateToken(token); | ||
| new Authenticator(action, message, null, listener).authenticateToken(token, null); | ||
| } | ||
|
|
||
| /** | ||
| * Authenticates the user based on the contents of the token that is provided as parameter only at the realm that is indicated with | ||
| * {@code preferredRealm}. If {@code preferredRealm} can't authenticate the token, no other realms will be attempted. | ||
| * This will not look at the values in the ThreadContext for Authentication. | ||
| * | ||
| * @param action The action of the message | ||
| * @param message The message that resulted in this authenticate call | ||
| * @param token The token (credentials) to be authenticated | ||
| * @param preferredRealm The realm that should be used to authenticate the token | ||
| */ | ||
| public void authenticate(String action, TransportMessage message, | ||
|
||
| AuthenticationToken token, @Nullable RealmConfig.RealmIdentifier preferredRealm, | ||
| ActionListener<Authentication> listener) { | ||
| new Authenticator(action, message, null, listener).authenticateToken(token, preferredRealm); | ||
| } | ||
|
|
||
| public void expire(String principal) { | ||
|
|
@@ -345,18 +364,25 @@ void extractToken(Consumer<AuthenticationToken> consumer) { | |
| action.run(); | ||
| } | ||
|
|
||
| private void consumeToken(AuthenticationToken token){ | ||
| consumeToken(token, null); | ||
| } | ||
|
|
||
| /** | ||
| * Consumes the {@link AuthenticationToken} provided by the caller. In the case of a {@code null} token, {@link #handleNullToken()} | ||
| * is called. In the case of a {@code non-null} token, the realms are iterated over and the first realm that returns a non-null | ||
| * {@link User} is the authenticating realm and iteration is stopped. This user is then passed to {@link #consumeUser(User, Map)} | ||
| * if no exception was caught while trying to authenticate the token | ||
| * is called. In the case of a {@code non-null} token, the realms are iterated over in the order defined in the configuration | ||
| * while possible also taking into consideration the last realm that authenticated this principal. If {@code preferredRealm} is | ||
| * not null, only this realm will be attempted, regardless of its order/applicability. When consulting multiple realms, the | ||
| * first realm that returns a non-null {@link User} is the authenticating realm and iteration is stopped. This user is then | ||
| * passed to {@link #consumeUser(User, Map)} if no exception was caught while trying to authenticate the token | ||
| */ | ||
| private void consumeToken(AuthenticationToken token) { | ||
| private void consumeToken(AuthenticationToken token, @Nullable RealmConfig.RealmIdentifier preferredRealm) { | ||
|
||
| if (token == null) { | ||
| handleNullToken(); | ||
| } else { | ||
| authenticationToken = token; | ||
| final List<Realm> realmsList = getRealmList(authenticationToken.principal()); | ||
| final List<Realm> realmsList = getRealmList(authenticationToken.principal(), preferredRealm); | ||
| logger.debug("Realms [{}] will be used for authentication in the specified order.", realmsList); | ||
| final long startInvalidation = numInvalidation.get(); | ||
| final Map<Realm, Tuple<String, Exception>> messages = new LinkedHashMap<>(); | ||
| final BiConsumer<Realm, ActionListener<User>> realmAuthenticatingConsumer = (realm, userListener) -> { | ||
|
|
@@ -411,8 +437,20 @@ private void consumeToken(AuthenticationToken token) { | |
| } | ||
| } | ||
|
|
||
| private List<Realm> getRealmList(String principal) { | ||
| /** | ||
| * Possible filters the realm list that is defined in the node's configuration based on {@code preferredRealm} and also | ||
| * possibly reorders the realm list depending on whether this principal has been recently authenticated by a specific realm | ||
| * @param principal The principal of the {@link AuthenticationToken} to be authenticated by a realm | ||
| * @param preferredRealm The realm that should authenticate the current {@link AuthenticationToken} | ||
| * @return a list of realms ordered based on which realm should authenticate the current {@link AuthenticationToken} | ||
| */ | ||
| private List<Realm> getRealmList(String principal, @Nullable RealmConfig.RealmIdentifier preferredRealm) { | ||
| final List<Realm> orderedRealmList = this.defaultOrderedRealmList; | ||
| if (preferredRealm != null){ | ||
| return orderedRealmList.stream() | ||
| .filter(realm -> realm.name().equals(preferredRealm.getName()) && realm.type().equals(preferredRealm.getType())) | ||
| .collect(Collectors.toUnmodifiableList()); | ||
| } | ||
| if (lastSuccessfulAuthCache != null) { | ||
| final Realm lastSuccess = lastSuccessfulAuthCache.get(principal); | ||
| if (lastSuccess != null) { | ||
|
|
@@ -519,7 +557,7 @@ private void consumeUser(User user, Map<Realm, Tuple<String, Exception>> message | |
| * names of users that exist using a timing attack | ||
| */ | ||
| private void lookupRunAsUser(final User user, String runAsUsername, Consumer<User> userConsumer) { | ||
| final RealmUserLookup lookup = new RealmUserLookup(getRealmList(runAsUsername), threadContext); | ||
| final RealmUserLookup lookup = new RealmUserLookup(getRealmList(runAsUsername, null), threadContext); | ||
| final long startInvalidationNum = numInvalidation.get(); | ||
| lookup.lookup(runAsUsername, ActionListener.wrap(tuple -> { | ||
| if (tuple == null) { | ||
|
|
@@ -572,8 +610,8 @@ void writeAuthToContext(Authentication authentication) { | |
| action.run(); | ||
| } | ||
|
|
||
| private void authenticateToken(AuthenticationToken token) { | ||
| this.consumeToken(token); | ||
| private void authenticateToken(AuthenticationToken token, @Nullable RealmConfig.RealmIdentifier preferredRealm) { | ||
| this.consumeToken(token, preferredRealm); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.