From 48347ea8f72cd55303ea5e5fb85497b6bca1bafd Mon Sep 17 00:00:00 2001 From: lucio Date: Tue, 30 Apr 2024 16:55:26 +0200 Subject: [PATCH] added configuration of accepted secret per app --- .../application/ApplicationConfiguration.java | 24 +++++++-- .../ApplicationConfigurationBinder.java | 2 + .../application/ApplicationContext.java | 10 ++++ .../DefaultApplicationContext.java | 44 ++++++++++++++- .../application/request/RequestValidator.java | 54 ++++++------------- .../security/AuthorizationProvider.java | 3 -- .../secrets/GCubeKeyCloakSecretFactory.java | 28 ++++++++++ .../LegacyGCubeTokenSecretFactory.java | 26 +++++++++ .../security/secrets/SecretFactory.java | 14 +++++ .../exceptions/SecretNotFoundException.java | 7 +++ src/test/java/test/BinderTest.java | 15 ++++-- src/test/resources/application.yaml | 4 ++ 12 files changed, 183 insertions(+), 48 deletions(-) create mode 100644 src/main/java/org/gcube/smartgears/security/secrets/GCubeKeyCloakSecretFactory.java create mode 100644 src/main/java/org/gcube/smartgears/security/secrets/LegacyGCubeTokenSecretFactory.java create mode 100644 src/main/java/org/gcube/smartgears/security/secrets/SecretFactory.java create mode 100644 src/main/java/org/gcube/smartgears/security/secrets/exceptions/SecretNotFoundException.java diff --git a/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfiguration.java b/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfiguration.java index 8f46c9d..58f1278 100644 --- a/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfiguration.java +++ b/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfiguration.java @@ -55,6 +55,9 @@ public class ApplicationConfiguration { @NotEmpty @JsonProperty("persistence") PersistenceConfiguration persistenceConfiguration; + @JsonProperty("allowed-secrets") + List allowedSecretClasses = null; + public Set excludes() { return excludes; } @@ -83,6 +86,15 @@ public class ApplicationConfiguration { return this; } + public ApplicationConfiguration allowedSecrets(String ... classNames) { + this.allowedSecretClasses = Arrays.asList(classNames); + return this; + } + + public List allowedSecrets(){ + return this.allowedSecretClasses; + } + public ApplicationConfiguration context(String context) { this.context = context; return this; @@ -154,12 +166,11 @@ public class ApplicationConfiguration { if (!msgs.isEmpty()) throw new IllegalStateException("invalid configuration: "+msgs); - } @Override public int hashCode() { - return Objects.hash(description, excludes, group, includes, name, proxable, version); + return Objects.hash(description, excludes, group, includes, name, proxable, version, allowedSecretClasses); } @Override @@ -176,7 +187,14 @@ public class ApplicationConfiguration { && Objects.equals(includes, other.includes) && Objects.equals(name, other.name) && proxable == other.proxable && Objects.equals(version, other.version); } - + + @Override + public String toString() { + return "ApplicationConfiguration [name=" + name + ", group=" + group + ", version=" + version + ", description=" + + description + ", context=" + context + ", proxable=" + proxable + ", excludes=" + excludes + + ", includes=" + includes + ", persistenceConfiguration=" + persistenceConfiguration + + ", allowedSecretClasses=" + this.allowedSecretClasses + "]"; + } } \ No newline at end of file diff --git a/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfigurationBinder.java b/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfigurationBinder.java index f7699a1..ef968a9 100644 --- a/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfigurationBinder.java +++ b/src/main/java/org/gcube/smartgears/configuration/application/ApplicationConfigurationBinder.java @@ -39,6 +39,8 @@ public class ApplicationConfigurationBinder { ObjectMapper mapper = new ObjectMapper(); String mapAsString = mapper.writeValueAsString(yaml.load(stream)); + System.out.println(mapAsString); + ApplicationConfiguration conf = mapper.readValue(mapAsString, ApplicationConfiguration.class); if (conf.persistenceConfiguration() == null) { diff --git a/src/main/java/org/gcube/smartgears/context/application/ApplicationContext.java b/src/main/java/org/gcube/smartgears/context/application/ApplicationContext.java index 3ad198e..f6b06a5 100644 --- a/src/main/java/org/gcube/smartgears/context/application/ApplicationContext.java +++ b/src/main/java/org/gcube/smartgears/context/application/ApplicationContext.java @@ -1,14 +1,17 @@ package org.gcube.smartgears.context.application; import java.nio.file.Path; +import java.util.List; import org.gcube.common.events.Hub; +import org.gcube.common.security.secrets.Secret; import org.gcube.smartgears.configuration.application.ApplicationConfiguration; import org.gcube.smartgears.context.Properties; import org.gcube.smartgears.context.container.ContainerContext; import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle; import org.gcube.smartgears.persistence.PersistenceWriter; import org.gcube.smartgears.security.AuthorizationProvider; +import org.gcube.smartgears.security.secrets.SecretFactory; import jakarta.servlet.ServletContext; @@ -92,4 +95,11 @@ public interface ApplicationContext { */ Path appSpecificConfigurationFolder(); + /** + * returns the list of factory secrets ordered by priority + * + * @returnA Linked List + */ + List> allowedSecretFactories(); + } diff --git a/src/main/java/org/gcube/smartgears/context/application/DefaultApplicationContext.java b/src/main/java/org/gcube/smartgears/context/application/DefaultApplicationContext.java index d566fd7..ad85dcf 100644 --- a/src/main/java/org/gcube/smartgears/context/application/DefaultApplicationContext.java +++ b/src/main/java/org/gcube/smartgears/context/application/DefaultApplicationContext.java @@ -5,11 +5,16 @@ import static org.gcube.smartgears.Constants.profile_file_path; import java.io.File; import java.io.FileInputStream; import java.io.ObjectInputStream; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.UUID; import org.gcube.common.events.Hub; +import org.gcube.common.security.secrets.Secret; import org.gcube.smartgears.configuration.PersistenceConfiguration; import org.gcube.smartgears.configuration.application.ApplicationConfiguration; import org.gcube.smartgears.context.Properties; @@ -17,6 +22,9 @@ import org.gcube.smartgears.context.container.ContainerContext; import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle; import org.gcube.smartgears.persistence.PersistenceWriter; import org.gcube.smartgears.security.AuthorizationProvider; +import org.gcube.smartgears.security.secrets.GCubeKeyCloakSecretFactory; +import org.gcube.smartgears.security.secrets.LegacyGCubeTokenSecretFactory; +import org.gcube.smartgears.security.secrets.SecretFactory; import org.gcube.smartgears.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +53,11 @@ public class DefaultApplicationContext implements ApplicationContext { private final static String APPS_CONFIG__DIR = "config/apps"; + private static final List> defaultSecretFactories = + new LinkedList>(Arrays.asList(new GCubeKeyCloakSecretFactory(), new LegacyGCubeTokenSecretFactory())); + + private List> allowedSecretFactories = null; + /** * Crates an intance with mandatory parameters * @param container the container context @@ -86,6 +99,27 @@ public class DefaultApplicationContext implements ApplicationContext { this.lifecycle = lifecycle; this.appSpecificConfigurationFolder = getApplicationSpecificConfig(); this.properties=properties; + + if (this.configuration.allowedSecrets()!=null && this.configuration.allowedSecrets().size()>0) { + this.allowedSecretFactories = new LinkedList>(); + for (String clazz : this.configuration.allowedSecrets() ) + try { + Object obj = Class.forName(clazz).getConstructor().newInstance(); + @SuppressWarnings("unchecked") + SecretFactory factory = SecretFactory.class.cast(obj); + this.allowedSecretFactories.add(factory); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | NoSuchMethodException | SecurityException + | ClassNotFoundException e) { + log.warn("declared secret factory {} instantiation error",clazz, e); + } catch (ClassCastException cc) { + log.warn("declared secret factory {} is not implementation of SecretFacory class",clazz, cc); + } + } + + if (this.allowedSecretFactories == null || this.allowedSecretFactories.size()==0 ) + this.allowedSecretFactories = defaultSecretFactories; + } /** @@ -93,10 +127,10 @@ public class DefaultApplicationContext implements ApplicationContext { * @param context the other instance */ public DefaultApplicationContext(ApplicationContext context) { - this(context.id(), context.persistence(), context.container(),context.application(),context.configuration(),context.events(), context.lifecycle(), new Properties(context.properties())); + this(context.id(), context.persistence(), context.container(),context.application(),context.configuration(),context.events(), context.lifecycle(), new Properties(context.properties()), context.allowedSecretFactories()); } - private DefaultApplicationContext(String id, PersistenceWriter writer, ContainerContext container,ServletContext sctx,ApplicationConfiguration configuration, Hub hub, ApplicationLifecycle lifecycle, Properties properties) { + private DefaultApplicationContext(String id, PersistenceWriter writer, ContainerContext container,ServletContext sctx,ApplicationConfiguration configuration, Hub hub, ApplicationLifecycle lifecycle, Properties properties, List> allowedSecretFactories) { this.id = id; this.container=container; this.sctx = sctx; @@ -105,6 +139,7 @@ public class DefaultApplicationContext implements ApplicationContext { this.lifecycle = lifecycle; this.properties=properties; this.persistenceWriter = writer; + this.allowedSecretFactories = allowedSecretFactories; } @@ -189,4 +224,9 @@ public class DefaultApplicationContext implements ApplicationContext { return appSpecificConfigurationPath; } + + @Override + public List> allowedSecretFactories() { + return this.allowedSecretFactories; + } } diff --git a/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java index 042ba4a..91bdd95 100644 --- a/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java +++ b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java @@ -1,21 +1,16 @@ package org.gcube.smartgears.handlers.application.request; -import static org.gcube.smartgears.Constants.token_header; import static org.gcube.smartgears.handlers.application.request.RequestError.application_failed_error; import static org.gcube.smartgears.handlers.application.request.RequestError.application_unavailable_error; import static org.gcube.smartgears.handlers.application.request.RequestError.invalid_request_error; +import java.util.Objects; import java.util.Set; -import org.gcube.common.keycloak.KeycloakClient; -import org.gcube.common.keycloak.KeycloakClientException; -import org.gcube.common.keycloak.KeycloakClientFactory; import org.gcube.common.security.ContextBean; import org.gcube.common.security.ContextBean.Type; import org.gcube.common.security.providers.SecretManagerProvider; -import org.gcube.common.security.secrets.GCubeSecret; import org.gcube.common.security.secrets.Secret; -import org.gcube.common.security.secrets.UmaTokenSecret; import org.gcube.smartgears.Constants; import org.gcube.smartgears.configuration.Mode; import org.gcube.smartgears.configuration.container.ContainerConfiguration; @@ -23,7 +18,8 @@ import org.gcube.smartgears.context.application.ApplicationContext; import org.gcube.smartgears.handlers.application.RequestEvent; import org.gcube.smartgears.handlers.application.RequestHandler; import org.gcube.smartgears.handlers.application.ResponseEvent; -import org.gcube.smartgears.security.SimpleCredentials; +import org.gcube.smartgears.security.secrets.SecretFactory; +import org.gcube.smartgears.security.secrets.exceptions.SecretNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,8 +27,6 @@ public class RequestValidator extends RequestHandler { private static Logger log = LoggerFactory.getLogger(RequestValidator.class); - private static final String BEARER_AUTH_PREFIX ="Bearer"; - private ApplicationContext appContext; @Override @@ -124,37 +118,23 @@ public class RequestValidator extends RequestHandler { } private Secret getSecret(RequestEvent call){ - String token = call.request().getParameter(token_header)==null? call.request().getHeader(token_header):call.request().getParameter(token_header); - String authHeader = call.request().getHeader(Constants.authorization_header); - - - log.trace("authorization header is {}",authHeader); - log.trace("token header is {}", token); - - log.info("d4s-user set to {} ", call.request().getHeader("d4s-user")); - - String umaToken = null; - if (authHeader!=null && !authHeader.isEmpty()) - if (authHeader.startsWith(BEARER_AUTH_PREFIX)) - umaToken = authHeader.substring(BEARER_AUTH_PREFIX.length()).trim(); - + Secret secret = null; - if (umaToken!=null) { - secret = new UmaTokenSecret(umaToken); - SimpleCredentials credentials = (SimpleCredentials)appContext.authorizationProvider().getCredentials(); - KeycloakClient client = KeycloakClientFactory.newInstance(); + for (SecretFactory factory: call.context().allowedSecretFactories()) { try { - if(!client.isAccessTokenVerified(secret.getContext(), credentials.getClientID(), credentials.getSecret(), umaToken)) - RequestError.request_not_authorized_error.fire("access token verification error"); - }catch (KeycloakClientException e) { - RequestError.internal_server_error.fire("error contacting keycloak client", e); - } - } else if (token!=null && !token.isEmpty()) - try { - secret = new GCubeSecret(token); - }catch(Throwable t) { - RequestError.request_not_authorized_error.fire("gcube token verification error ("+t.getMessage()+")"); + secret = factory.create(call.request()); + if (!secret.isValid()) + RequestError.request_not_authorized_error.fire("authorization with secret "+factory.getSecretClass().getName()+" is not valid "); + } catch (SecretNotFoundException e) { + log.info("authorization with secret {} not found", factory.getSecretClass().getName()); + } catch (Throwable t) { + log.warn("geenric error with secret {}", factory.getSecretClass().getName(), t); } + } + + if (Objects.isNull(secret)) + RequestError.request_not_authorized_error.fire("call not authorized"); + return secret; } diff --git a/src/main/java/org/gcube/smartgears/security/AuthorizationProvider.java b/src/main/java/org/gcube/smartgears/security/AuthorizationProvider.java index d18e436..2393a5f 100644 --- a/src/main/java/org/gcube/smartgears/security/AuthorizationProvider.java +++ b/src/main/java/org/gcube/smartgears/security/AuthorizationProvider.java @@ -2,7 +2,6 @@ package org.gcube.smartgears.security; import java.util.Set; -import org.gcube.common.security.credentials.Credentials; import org.gcube.common.security.secrets.Secret; public interface AuthorizationProvider { @@ -10,6 +9,4 @@ public interface AuthorizationProvider { Set getContexts(); Secret getSecretForContext(String context); - - Credentials getCredentials(); } diff --git a/src/main/java/org/gcube/smartgears/security/secrets/GCubeKeyCloakSecretFactory.java b/src/main/java/org/gcube/smartgears/security/secrets/GCubeKeyCloakSecretFactory.java new file mode 100644 index 0000000..d66c795 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/security/secrets/GCubeKeyCloakSecretFactory.java @@ -0,0 +1,28 @@ +package org.gcube.smartgears.security.secrets; + +import org.gcube.common.security.secrets.UmaTokenSecret; +import org.gcube.smartgears.Constants; +import org.gcube.smartgears.security.secrets.exceptions.SecretNotFoundException; + +import jakarta.servlet.http.HttpServletRequest; + +public class GCubeKeyCloakSecretFactory implements SecretFactory { + + private static final String BEARER_AUTH_PREFIX ="Bearer"; + + @Override + public UmaTokenSecret create(HttpServletRequest request) throws SecretNotFoundException { + String authHeader = request.getHeader(Constants.authorization_header); + String umaToken = null; + if (authHeader!=null && !authHeader.isEmpty() && authHeader.startsWith(BEARER_AUTH_PREFIX)) { + umaToken = authHeader.substring(BEARER_AUTH_PREFIX.length()).trim(); + return new UmaTokenSecret(umaToken); + } else throw new SecretNotFoundException(); + } + + @Override + public Class getSecretClass() { + return UmaTokenSecret.class; + } + +} diff --git a/src/main/java/org/gcube/smartgears/security/secrets/LegacyGCubeTokenSecretFactory.java b/src/main/java/org/gcube/smartgears/security/secrets/LegacyGCubeTokenSecretFactory.java new file mode 100644 index 0000000..ad39fca --- /dev/null +++ b/src/main/java/org/gcube/smartgears/security/secrets/LegacyGCubeTokenSecretFactory.java @@ -0,0 +1,26 @@ +package org.gcube.smartgears.security.secrets; + +import static org.gcube.smartgears.Constants.token_header; + +import java.util.Objects; + +import org.gcube.common.security.secrets.GCubeSecret; +import org.gcube.smartgears.security.secrets.exceptions.SecretNotFoundException; + +import jakarta.servlet.http.HttpServletRequest; + +public class LegacyGCubeTokenSecretFactory implements SecretFactory { + + @Override + public GCubeSecret create(HttpServletRequest request) throws SecretNotFoundException { + String token = request.getParameter(token_header)==null? request.getHeader(token_header):request.getParameter(token_header); + if (Objects.isNull(token) || token.isBlank()) throw new SecretNotFoundException(); + return new GCubeSecret(token); + } + + @Override + public Class getSecretClass() { + return GCubeSecret.class; + } + +} diff --git a/src/main/java/org/gcube/smartgears/security/secrets/SecretFactory.java b/src/main/java/org/gcube/smartgears/security/secrets/SecretFactory.java new file mode 100644 index 0000000..313f684 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/security/secrets/SecretFactory.java @@ -0,0 +1,14 @@ +package org.gcube.smartgears.security.secrets; + +import org.gcube.common.security.secrets.Secret; +import org.gcube.smartgears.security.secrets.exceptions.SecretNotFoundException; + +import jakarta.servlet.http.HttpServletRequest; + +public interface SecretFactory { + + Class getSecretClass(); + + T create(HttpServletRequest request) throws SecretNotFoundException; + +} diff --git a/src/main/java/org/gcube/smartgears/security/secrets/exceptions/SecretNotFoundException.java b/src/main/java/org/gcube/smartgears/security/secrets/exceptions/SecretNotFoundException.java new file mode 100644 index 0000000..f20ee24 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/security/secrets/exceptions/SecretNotFoundException.java @@ -0,0 +1,7 @@ +package org.gcube.smartgears.security.secrets.exceptions; + +public class SecretNotFoundException extends Exception { + + private static final long serialVersionUID = 1L; + +} diff --git a/src/test/java/test/BinderTest.java b/src/test/java/test/BinderTest.java index 8c059c1..f9ad829 100644 --- a/src/test/java/test/BinderTest.java +++ b/src/test/java/test/BinderTest.java @@ -2,19 +2,28 @@ package test; import java.io.InputStream; +import org.gcube.smartgears.configuration.application.ApplicationConfiguration; +import org.gcube.smartgears.configuration.application.ApplicationConfigurationBinder; import org.gcube.smartgears.configuration.container.ContainerConfiguration; import org.gcube.smartgears.configuration.container.ContainerConfigurationBinder; import org.junit.Test; public class BinderTest { - @Test - public void bindConfig() throws Exception { - + public void bindContainerConfig() throws Exception { InputStream stream = BinderTest.class.getResourceAsStream("/container.ini"); ContainerConfiguration conf = new ContainerConfigurationBinder().load(stream); System.out.println(conf.toString()); } + + + @Test + public void bindApplicationConfig() throws Exception { + InputStream stream = BinderTest.class.getResourceAsStream("/application.yaml"); + + ApplicationConfiguration conf = new ApplicationConfigurationBinder().load(stream); + System.out.println(conf.toString()); + } } diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index c684c27..1db9ec0 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -11,6 +11,10 @@ excludes: - handlers: [H1, H2] path: /trip #not mandatory +allowed-secrets: + - org.gcube.smartgears.security.secrets.GCubeKeyCloakSecretFactory + - org.gcube.smartgears.security.secrets.LegacyGCubeTokenSecretFactory +#not mandatory persistence: implementationClass: org.gcube.smartgears.persistence.LocalWriter writerConfiguration: