Introduce ExternalAuthenticator and isExternal flag on PolarisCredential#3250
Introduce ExternalAuthenticator and isExternal flag on PolarisCredential#3250sungwy wants to merge 1 commit intoapache:mainfrom
ExternalAuthenticator and isExternal flag on PolarisCredential#3250Conversation
| // if the principal was not found, we can end right there | ||
| if (this.resolvedCallerPrincipal == null | ||
| || this.resolvedCallerPrincipal.getEntity().isDropped()) { | ||
| if (isExternalPrincipal()) { |
There was a problem hiding this comment.
can this be true without returning on line 759?
There was a problem hiding this comment.
It cannot - this was before I moved the condition upto line 748 to avoid principal resolution altogether if it is external. I'll remove this check here to remove redundancy
| List<ResolvedPolarisEntity> toValidate) { | ||
|
|
||
| if (isExternalPrincipal()) { | ||
| PrincipalEntity externalPrincipal = createExternalPrincipalEntity(); |
There was a problem hiding this comment.
This is an entity without a (real) ID... Some it's a kind of ephemeral entity... Would it be possible to avoid creating it at all?
There was a problem hiding this comment.
That's a good question - I'm a bit confused about the role of PrincipalEntity. We do have a public method for getResolvedCallerPrincipal() that returns resolvedCallerPrincipal. It's not being used anywhere in the codebase today, but I thought it'd be safe to populate it as it requires it to be @Nonnull: https://github.com/apache/polaris/blob/23ba2a05adc9c75f3e72aaf2ca370b4886964328/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java#L272C19-L280
There was a problem hiding this comment.
Conceptually, IMHO, the principal entity should only be required for AuthZ checks (which do not actually require it if it's external). So, any intermediate code that requires it may need to be refactored... but TBH, I do not remember that code very well 😅
There was a problem hiding this comment.
I think the ideal solution would be to not materialize this entity at all – and very likely, pass an empty set of activatedEntities to the PolarisAuthorizer.
But I also understand that this may increase the blast radius of this PR, and also interfere with #3228.
So, I think having a temporary entity being created here can be a good compromise, as long as we don't forget to revisit this later.
There was a problem hiding this comment.
In fact I think that #3427 and resolvePathsOnly could help here.
| } | ||
|
|
||
| private boolean isExternalPrincipal() { | ||
| return Boolean.parseBoolean(polarisPrincipal.getProperties().getOrDefault("external", "false")); |
There was a problem hiding this comment.
WDYT about making isExternal() a method of PolarisPrincipal?
There was a problem hiding this comment.
I think that's worth considering. I put it into PolarisPrincipal as a part of the property for now because it was already available, but I think adding it as an attribute or adding a class method that infers the property value is up for discussion
There was a problem hiding this comment.
Same remark: maybe we should expose a getType method in PolarisPrincipal that returns PolarisPrincipalType?
| String grantType, | ||
| String scope, | ||
| TokenType requestedTokenType) { | ||
| return TokenResponse.of(OAuthError.invalid_request); |
There was a problem hiding this comment.
I'm not sure about invalid_request... it may actually be well-formed... How about unsupported_grant_type?
There was a problem hiding this comment.
That's a great suggestion - thank you!
| private ResolverStatus resolveCallerPrincipalAndPrincipalRoles( | ||
| List<ResolvedPolarisEntity> toValidate) { | ||
|
|
||
| if (isExternalPrincipal()) { |
|
|
||
| @Override | ||
| public PolarisPrincipal authenticate(PolarisCredential credentials) { | ||
| PolarisCredential externalCredentials = ensureExternal(credentials); |
There was a problem hiding this comment.
instead of overriding the Principal to be external here, maybe we could validate that it is external and raise an exception if it isn't instead. Then we have a clearer separation of responsibility between:
- detecting whether a
PolarisCredential's origin was internal or external - and determining if a
PolarisPrincipalshould be internal, federated or external.
There was a problem hiding this comment.
I think indeed it would be better if this authenticator rejected any non-external PolarisCredential.
However, the DefaultAuthenticator should not do the opposite, as it is valid to have persisted principals with an external IDP so it should accept any credential, internal or external.
There was a problem hiding this comment.
In fact I'm wondering whether we need this second Authenticator.
We could instead introduce a polaris.authentication.principal.type setting (overridable per realm). Valid values would be the constants of the PolarisPrincipalType enum (e.g. persisted, federated, external).
Then the DefaultAuthenticator should be able to handle all kinds of principals. It would simply read this configuration setting to determine whether it should fetch the PolarisEntity or not. (It would also, of course, validate that if the principal type is external then the PolarisCredential must be external too.)
WDYT?
|
This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days. |
|
|
||
| /** True if the credential represents an external principal. */ | ||
| @Value.Default | ||
| default boolean isExternal() { |
There was a problem hiding this comment.
Should this property have a default value at all? I think it's safer if the code building the credential explicitly decides if it's external or not.
There was a problem hiding this comment.
Also thinking forward, maybe we should have an enum PolarisPrincipalType + method getPrincipalType() ?
If federated principals are implemented eventually, it would be cumbersome to have two methods isExternal and isFederated as they are mutually exclusive.
There was a problem hiding this comment.
Update: maybe not here... a credential can only be internal or external, there is no "federated" credential. Please disregard my comment above.
| /** A no-op token broker factory used when authentication is delegated to an external IdP. */ | ||
| @ApplicationScoped | ||
| @Identifier("none") | ||
| public class NoneTokenBrokerFactory implements TokenBrokerFactory { |
There was a problem hiding this comment.
Hmm this token broker existed and was deleted a while ago by #2634. Why are we adding it back?
| @Override | ||
| public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() { | ||
| return Collections.singleton(TokenAuthenticationRequest.class); | ||
| return Collections.singleton(InternalAuthenticationRequest.class); |
There was a problem hiding this comment.
Whoops this is an interesting bug that you fixed 😅 But I wonder how it worked before 🤔
|
|
||
| @Override | ||
| public PolarisPrincipal authenticate(PolarisCredential credentials) { | ||
| PolarisCredential externalCredentials = ensureExternal(credentials); |
There was a problem hiding this comment.
I think indeed it would be better if this authenticator rejected any non-external PolarisCredential.
However, the DefaultAuthenticator should not do the opposite, as it is valid to have persisted principals with an external IDP so it should accept any credential, internal or external.
|
|
||
| @Override | ||
| public PolarisPrincipal authenticate(PolarisCredential credentials) { | ||
| PolarisCredential externalCredentials = ensureExternal(credentials); |
There was a problem hiding this comment.
In fact I'm wondering whether we need this second Authenticator.
We could instead introduce a polaris.authentication.principal.type setting (overridable per realm). Valid values would be the constants of the PolarisPrincipalType enum (e.g. persisted, federated, external).
Then the DefaultAuthenticator should be able to handle all kinds of principals. It would simply read this configuration setting to determine whether it should fetch the PolarisEntity or not. (It would also, of course, validate that if the principal type is external then the PolarisCredential must be external too.)
WDYT?
| } | ||
|
|
||
| private boolean isExternalPrincipal() { | ||
| return Boolean.parseBoolean(polarisPrincipal.getProperties().getOrDefault("external", "false")); |
There was a problem hiding this comment.
Same remark: maybe we should expose a getType method in PolarisPrincipal that returns PolarisPrincipalType?
| List<ResolvedPolarisEntity> toValidate) { | ||
|
|
||
| if (isExternalPrincipal()) { | ||
| PrincipalEntity externalPrincipal = createExternalPrincipalEntity(); |
There was a problem hiding this comment.
I think the ideal solution would be to not materialize this entity at all – and very likely, pass an empty set of activatedEntities to the PolarisAuthorizer.
But I also understand that this may increase the blast radius of this PR, and also interfere with #3228.
So, I think having a temporary entity being created here can be a good compromise, as long as we don't forget to revisit this later.
|
Hello, I'm looking to use Polaris with a keycloak integration but would appreciate this feature. Any idea in which release this PR would be made available? |
|
Hi @SBylemans - we are working on a refactor in the PolarisAuthorizer that will introduce this functionality. I'm doing a pass through an RFC doc that will be shared in the dev mailing list, and once the proposal is accepted, we'll progress on with the implementation. I can cross post the RFC here as well when it is ready. |
Draft PR to introduce an
Externalmode in the PolarisPrincipal. The goal of this PR is to introduce an authentication flow that skips principal resolution in the metastore for enhanced compatibility with authentication -> authorization flows that rely on external IDPs and PDPs.This PR is motivated by @adutra 's mailing list proposal
Checklist
CHANGELOG.md(if needed)site/content/in-dev/unreleased(if needed)