Skip to content

Commit

Permalink
Merge pull request #2454 from atlanhq/PLT-245
Browse files Browse the repository at this point in the history
Add online validation for keycloak apikeys
  • Loading branch information
n5nk authored Nov 9, 2023
2 parents 0288f19 + 9b0f24e commit 9967308
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -174,6 +175,10 @@ public List<EventRepresentation> getEvents(List<String> 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..");
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -121,4 +128,16 @@ public Response<List<EventRepresentation>> getEvents(List<String> 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<TokenMetadataRepresentation> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand Down Expand Up @@ -142,4 +143,10 @@ Call<List<EventRepresentation>> getEvents(@Path("realmId") String realmId, @Quer
@POST("realms/{realmId}/protocol/openid-connect/token")
Call<AccessTokenResponse> 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<TokenMetadataRepresentation> introspectToken(@Path("realmId") String realmId, @Body RequestBody request);


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -130,6 +131,8 @@ public Authentication authenticate(Authentication authentication)
} else if (keycloakAuthenticationEnabled) {
try {
authentication = atlasKeycloakAuthenticationProvider.authenticate(authentication);
} catch (KeycloakAuthenticationException ex) {
throw new AtlasAuthenticationException("Authentication failed.");
} catch (Exception ex) {
LOG.error("Error while Keycloak authentication", ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -65,10 +75,31 @@ public Authentication authenticate(Authentication authentication) {
authentication = new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), grantedAuthorities);
}
}
if(authentication.getName().startsWith("service-account")) {
LOG.info("Validating request for clientId: {}", 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.error("Invalid clientId: {}", authentication.getName().substring("service-account-".length()));
throw new KeycloakAuthenticationException("Invalid ClientId");
}

@Override
public boolean supports(Class<?> aClass) {
return keycloakAuthenticationProvider.supports(aClass);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 9967308

Please sign in to comment.