From 82b603531c11da75f8c034ed95928e244802db1f Mon Sep 17 00:00:00 2001 From: ektavarma10 Date: Thu, 9 Nov 2023 16:21:30 +0530 Subject: [PATCH] Add online validation for keycloak apikeys --- .../keycloak/client/AtlasKeycloakClient.java | 5 +++ .../keycloak/client/KeycloakRestClient.java | 19 ++++++++++++ .../client/RetrofitKeycloakClient.java | 7 +++++ pom.xml | 1 + webapp/pom.xml | 6 ++++ .../security/AtlasAuthenticationProvider.java | 7 +++++ .../AtlasKeycloakAuthenticationProvider.java | 31 +++++++++++++++++++ .../KeycloakAuthenticationException.java | 12 +++++++ 8 files changed, 88 insertions(+) create mode 100644 webapp/src/main/java/org/apache/atlas/web/security/KeycloakAuthenticationException.java diff --git a/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/AtlasKeycloakClient.java b/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/AtlasKeycloakClient.java index 5eec02b7c6..fe723bbce1 100644 --- a/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/AtlasKeycloakClient.java +++ b/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/AtlasKeycloakClient.java @@ -9,6 +9,7 @@ import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.keycloak.representations.idm.*; +import org.keycloak.representations.oidc.TokenMetadataRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -174,6 +175,10 @@ public List getEvents(List type, String client, Str return KEYCLOAK.getEvents(type, client, user, dateFrom, dateTo, ipAddress, first, max).body(); } + public TokenMetadataRepresentation introspectToken(String token) throws AtlasBaseException { + return KEYCLOAK.introspectToken(token).body(); + } + public static AtlasKeycloakClient getKeycloakClient() throws AtlasBaseException { if (Objects.isNull(KEYCLOAK_CLIENT)) { LOG.info("Initializing Keycloak client.."); diff --git a/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/KeycloakRestClient.java b/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/KeycloakRestClient.java index 73c8c207a9..b669b929d4 100644 --- a/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/KeycloakRestClient.java +++ b/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/KeycloakRestClient.java @@ -1,9 +1,12 @@ package org.apache.atlas.keycloak.client; +import okhttp3.FormBody; import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.keycloak.client.config.KeycloakConfig; import org.keycloak.representations.idm.*; +import org.keycloak.representations.oidc.TokenMetadataRepresentation; import retrofit2.Response; +import okhttp3.RequestBody; import java.util.List; import java.util.Set; @@ -13,6 +16,10 @@ */ public final class KeycloakRestClient extends AbstractKeycloakClient { + private static final String TOKEN = "token"; + private static final String CLIENT_ID = "client_id"; + private static final String CLIENT_SECRET = "client_secret"; + public KeycloakRestClient(final KeycloakConfig keycloakConfig) { super(keycloakConfig); } @@ -121,4 +128,16 @@ public Response> getEvents(List type, String c String dateTo, String ipAddress, Integer first, Integer max) throws AtlasBaseException { return processResponse(this.retrofit.getEvents(this.keycloakConfig.getRealmId(), type, client, user, dateFrom, dateTo, ipAddress, first, max)); } + + public Response introspectToken(String token) throws AtlasBaseException { + return processResponse(this.retrofit.introspectToken(this.keycloakConfig.getRealmId(), getIntrospectTokenRequest(token))); + } + + private RequestBody getIntrospectTokenRequest(String token) { + return new FormBody.Builder() + .addEncoded(TOKEN, token) + .addEncoded(CLIENT_ID, this.keycloakConfig.getClientId()) + .addEncoded(CLIENT_SECRET, this.keycloakConfig.getClientSecret()) + .build(); + } } diff --git a/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/RetrofitKeycloakClient.java b/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/RetrofitKeycloakClient.java index cb813c0095..caa34bd8ac 100644 --- a/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/RetrofitKeycloakClient.java +++ b/client-keycloak/src/main/java/org/apache/atlas/keycloak/client/RetrofitKeycloakClient.java @@ -3,6 +3,7 @@ import okhttp3.RequestBody; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.idm.*; +import org.keycloak.representations.oidc.TokenMetadataRepresentation; import retrofit2.Call; import retrofit2.http.*; @@ -142,4 +143,10 @@ Call> getEvents(@Path("realmId") String realmId, @Quer @POST("realms/{realmId}/protocol/openid-connect/token") Call grantToken(@Path("realmId") String realmId, @Body RequestBody request); + + @Headers({"Accept: application/json", "Content-Type: application/x-www-form-urlencoded", "Cache-Control: no-store", "Cache-Control: no-cache"}) + @POST("realms/{realmId}/protocol/openid-connect/token/introspect") + Call introspectToken(@Path("realmId") String realmId, @Body RequestBody request); + + } diff --git a/pom.xml b/pom.xml index 7d0bb1abec..ffe7aa7bfe 100644 --- a/pom.xml +++ b/pom.xml @@ -735,6 +735,7 @@ 2.12 2.8.1 15.0.2.1 + 15.0.2 20220608.1 6.0.5 1.2.19 diff --git a/webapp/pom.xml b/webapp/pom.xml index 35a0e8a010..01705fe2ed 100755 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -531,6 +531,12 @@ + + org.keycloak + keycloak-admin-client + ${keycloak-admin.version} + + io.sentry diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java index dff3d8d31c..f2d45d9839 100644 --- a/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java @@ -19,6 +19,7 @@ import org.apache.atlas.ApplicationProperties; import org.apache.commons.configuration.Configuration; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; @@ -130,6 +131,12 @@ public Authentication authenticate(Authentication authentication) } else if (keycloakAuthenticationEnabled) { try { authentication = atlasKeycloakAuthenticationProvider.authenticate(authentication); + } catch (KeycloakAuthenticationException ex) { + if(authentication instanceof KeycloakAuthenticationToken) { + throw new AtlasAuthenticationException("Authentication failed."); + } else { + LOG.error("Error while Introspecting Token for Keycloak Authentication", ex); + } } catch (Exception ex) { LOG.error("Error while Keycloak authentication", ex); } diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasKeycloakAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasKeycloakAuthenticationProvider.java index 367839b82a..7491edf2e6 100644 --- a/webapp/src/main/java/org/apache/atlas/web/security/AtlasKeycloakAuthenticationProvider.java +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasKeycloakAuthenticationProvider.java @@ -17,9 +17,13 @@ package org.apache.atlas.web.security; import org.apache.atlas.ApplicationProperties; +import org.apache.atlas.keycloak.client.AtlasKeycloakClient; import org.apache.commons.configuration.Configuration; import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.keycloak.representations.oidc.TokenMetadataRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -28,6 +32,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; @Component public class AtlasKeycloakAuthenticationProvider extends AtlasAbstractAuthenticationProvider { @@ -36,8 +41,13 @@ public class AtlasKeycloakAuthenticationProvider extends AtlasAbstractAuthentica private final KeycloakAuthenticationProvider keycloakAuthenticationProvider; + private final AtlasKeycloakClient atlasKeycloakClient; + + private static final Logger LOG = LoggerFactory.getLogger(AtlasKeycloakAuthenticationProvider.class); + public AtlasKeycloakAuthenticationProvider() throws Exception { this.keycloakAuthenticationProvider = new KeycloakAuthenticationProvider(); + this.atlasKeycloakClient = AtlasKeycloakClient.getKeycloakClient(); Configuration configuration = ApplicationProperties.get(); this.groupsFromUGI = configuration.getBoolean("atlas.authentication.method.keycloak.ugi-groups", true); @@ -65,10 +75,31 @@ public Authentication authenticate(Authentication authentication) { authentication = new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), grantedAuthorities); } } + if(authentication.getName().startsWith("service-account-apikey")) { + LOG.info("validating request for service-account-apikey: {}", authentication.getName().substring("service-account-".length())); + try{ + KeycloakAuthenticationToken keycloakToken = (KeycloakAuthenticationToken)authentication; + String bearerToken = keycloakToken.getAccount().getKeycloakSecurityContext().getTokenString(); + TokenMetadataRepresentation introspectToken = atlasKeycloakClient.introspectToken(bearerToken); + if(Objects.nonNull(introspectToken) && introspectToken.isActive()) { + authentication.setAuthenticated(true); + } else { + handleInvalidApiKey(authentication); + } + } catch (Exception e) { + throw new KeycloakAuthenticationException("Keycloak Authentication failed", e.getCause()); + } + } return authentication; } + private void handleInvalidApiKey(Authentication authentication) { + authentication.setAuthenticated(false); + LOG.info("Invalid API Key: {}", authentication.getName().substring("service-account-".length())); + throw new KeycloakAuthenticationException("Invalid Api Key"); + } + @Override public boolean supports(Class aClass) { return keycloakAuthenticationProvider.supports(aClass); diff --git a/webapp/src/main/java/org/apache/atlas/web/security/KeycloakAuthenticationException.java b/webapp/src/main/java/org/apache/atlas/web/security/KeycloakAuthenticationException.java new file mode 100644 index 0000000000..0d9cd1d1fa --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/KeycloakAuthenticationException.java @@ -0,0 +1,12 @@ +package org.apache.atlas.web.security; + +import org.springframework.security.core.AuthenticationException; +public class KeycloakAuthenticationException extends AuthenticationException { + public KeycloakAuthenticationException(String msg, Throwable cause) { + super(msg, cause); + } + + public KeycloakAuthenticationException(String msg) { + super(msg); + } +}