added configuration of accepted secret per app

This commit is contained in:
lucio 2024-04-30 16:55:26 +02:00
parent 51fbda1161
commit 48347ea8f7
12 changed files with 183 additions and 48 deletions

View File

@ -55,6 +55,9 @@ public class ApplicationConfiguration {
@NotEmpty @JsonProperty("persistence")
PersistenceConfiguration persistenceConfiguration;
@JsonProperty("allowed-secrets")
List<String> allowedSecretClasses = null;
public Set<GCubeExclude> 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<String> 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 + "]";
}
}

View File

@ -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) {

View File

@ -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<SecretFactory<? extends Secret>> allowedSecretFactories();
}

View File

@ -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<SecretFactory<? extends Secret>> defaultSecretFactories =
new LinkedList<SecretFactory<? extends Secret>>(Arrays.asList(new GCubeKeyCloakSecretFactory(), new LegacyGCubeTokenSecretFactory()));
private List<SecretFactory<? extends Secret>> 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<SecretFactory<? extends Secret>>();
for (String clazz : this.configuration.allowedSecrets() )
try {
Object obj = Class.forName(clazz).getConstructor().newInstance();
@SuppressWarnings("unchecked")
SecretFactory<? extends Secret> 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<SecretFactory<? extends Secret>> 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<SecretFactory<? extends Secret>> allowedSecretFactories() {
return this.allowedSecretFactories;
}
}

View File

@ -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<? extends Secret> 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;
}

View File

@ -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<String> getContexts();
Secret getSecretForContext(String context);
Credentials getCredentials();
}

View File

@ -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<UmaTokenSecret> {
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<UmaTokenSecret> getSecretClass() {
return UmaTokenSecret.class;
}
}

View File

@ -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<GCubeSecret> {
@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<GCubeSecret> getSecretClass() {
return GCubeSecret.class;
}
}

View File

@ -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<T extends Secret> {
Class<T> getSecretClass();
T create(HttpServletRequest request) throws SecretNotFoundException;
}

View File

@ -0,0 +1,7 @@
package org.gcube.smartgears.security.secrets.exceptions;
public class SecretNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
}

View File

@ -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());
}
}

View File

@ -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: