Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gateway update #1

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
.attach_pid*
.flattened-pom.xml
.mvn/wrapper/maven-wrapper.jar

.factorypath
55 changes: 55 additions & 0 deletions docs/roles-mappings.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
== Adding additional roles

The Gateway sends a `sec-roles` HTTP request header to the backend services
with the role names provided by the authentication provider.

Sometimes, these roles are unsuficcient as some services may require additional
or different role names.

For example, let's say the OpenID Connect provider gives us a role namded `GDI.ADMIN`, and
we want users with that role to be GeoServer administrators, which requires the user to belong to the `ROLE_ADMINISTRATOR` role.

Hence, we can tell the Gateway to add the `ROLE_ADMINISTRATOR` role to any user logged in
with the `GDI.ADMIN` role.

This can be configured in the geOrchestra data directory's `gateway/gateway.yaml` file. Though
for the sake of separation of concerns across config files, we're using the `gateway/roles-mappings.yml` file, which is imported from `gateway/gateway.yml`.

This configuration file allows to extend the list of security role names
assigned to a user, from the role names extracted by the authentication
provider (e.g. LDAP, Oauth2, OpenID Connect).

Limited regular expression support: only the `*` character is allowed
as a wildcard on a source role name. For example, the following mapping
will add the `ROLE_USER` role to all authenticated users that already
have any role name starting with `ROLE_GP.GDI.`

Note that for the key names (source roles) to include special characters,
you must use the format '[role.name.*]' for the literal string role.name.*
to be interpreted correctly.

[source,yaml]
----
georchestra:
gateway:
role-mappings:
'[ROLE_GP.GDI.*]'
- ROLE_USER
----

If an authentication provider role name matches multiple mappings,
all the matching additional roles will be appended. For example, the
following mappings will add both `ROLE_USER` and `ROLE_ADMINISTRATOR`
to a user with role `ROLE_GP.GDI.ADMINISTRATOR`, but only `ROLE_USER`
to any other with a role starting with `ROLE_GP.GDI.`:

[source,yaml]
----
georchestra:
gateway:
role-mappings:
'[ROLE_GP.GDI.*]':
- ROLE_USER
'[ROLE_GP.GDI.ADMINISTRATOR]':
- ROLE_ADMINISTRATOR
----
99 changes: 99 additions & 0 deletions gateway/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
version: "3.1"

volumes:
postgresql_data:
datadir:
driver_opts:
type: none
o: bind
device: $PWD/datadir

secrets:
slapd_password:
file: ./datadir/secrets/slapd_password.txt
geoserver_privileged_user_passwd:
file: ./datadir/secrets/geoserver_privileged_user_passwd.txt

services:
database:
image: georchestra/database:latest
environment:
- POSTGRES_USER=georchestra
- POSTGRES_PASSWORD=georchestra
volumes:
- postgresql_data:/var/lib/postgresql/data
restart: always
ports:
- 54321:5432

ldap:
image: georchestra/ldap:latest
secrets:
- slapd_password
- geoserver_privileged_user_passwd
environment:
- SLAPD_ORGANISATION=georchestra
- SLAPD_DOMAIN=georchestra.org
- SLAPD_PASSWORD_FILE=/run/secrets/slapd_password
- SLAPD_PASSWORD=
- GEOSERVER_PRIVILEGED_USER_PASSWORD_FILE=/run/secrets/geoserver_privileged_user_passwd
- SLAPD_LOG_LEVEL=32768 # See https://www.openldap.org/doc/admin24/slapdconfig.html#loglevel%20%3Clevel%3E
restart: always
ports:
- 3891:389

gateway:
image: georchestra/gateway:latest
depends_on:
- ldap
- database
volumes:
- datadir:/etc/georchestra
environment:
- JAVA_TOOL_OPTIONS=-Dgeorchestra.datadir=/etc/georchestra -Dspring.profiles.active=docker -Xmx512M
restart: always
ports:
- 8080:8080
- 8090:8090

header:
image: georchestra/header:latest
volumes:
- datadir:/etc/georchestra
environment:
- JAVA_OPTIONS=-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF
- XMS=256M
- XMX=512M
restart: always
ports:
- 10003:8080

geoserver:
image: georchestra/geoserver:latest
depends_on:
- ldap
volumes:
- datadir:/etc/georchestra
environment:
- JAVA_OPTIONS=-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF
- XMS=256M
- XMX=8G
restart: always
ports:
- 10006:8080

console:
image: georchestra/console:latest
depends_on:
- ldap
- database
volumes:
- datadir:/etc/georchestra
environment:
- JAVA_OPTIONS=-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF
- XMS=256M
- XMX=1G
restart: always
ports:
- 10007:8080

16 changes: 13 additions & 3 deletions gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,21 @@
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>5.0.0</version>
<executions>
<execution>
<id>get-the-git-infos</id>
<phase>initialize</phase>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<dotGitDirectory>${project.basedir}/../.git</dotGitDirectory>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
</configuration>
</plugin>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ public class GeorchestraUserHeadersContributor extends HeaderContributor {
add(headers, "sec-lastname", mappings.getLastname(), user.map(GeorchestraUser::getLastName));
add(headers, "sec-tel", mappings.getTel(), user.map(GeorchestraUser::getTelephoneNumber));

List<String> roles = user.map(GeorchestraUser::getRoles).orElse(List.of()).stream()
.map(r -> r.startsWith("ROLE_") ? r : "ROLE_" + r).collect(Collectors.toList());
List<String> roles = user.map(GeorchestraUser::getRoles).orElse(List.of());

add(headers, "sec-roles", mappings.getRoles(), roles);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
@ConfigurationProperties("georchestra.gateway")
public class GatewayConfigProperties {

private Map<String, List<String>> rolesMappings = Map.of();

/**
* Configures the global security headers to append to all proxied http requests
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.georchestra.gateway.security;

import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.georchestra.gateway.model.GatewayConfigProperties;
Expand Down Expand Up @@ -78,12 +79,23 @@ private Stream<ServerHttpSecurityCustomizer> sortedCustomizers(List<ServerHttpSe
return customizers.stream().sorted((c1, c2) -> Integer.compare(c1.getOrder(), c2.getOrder()));
}

public @Bean GeorchestraUserMapper georchestraUserResolver(List<GeorchestraUserMapperExtension> resolvers) {
return new GeorchestraUserMapper(resolvers);
public @Bean GeorchestraUserMapper georchestraUserResolver(List<GeorchestraUserMapperExtension> resolvers,
List<GeorchestraUserCustomizerExtension> customizers) {
return new GeorchestraUserMapper(resolvers, customizers);
}

public @Bean ResolveGeorchestraUserGlobalFilter resolveGeorchestraUserGlobalFilter(GeorchestraUserMapper resolver) {
return new ResolveGeorchestraUserGlobalFilter(resolver);
}

/**
* Extension to make {@link GeorchestraUserMapper} append user roles based on
* {@link GatewayConfigProperties#getRolesMappings()}
*/
public @Bean RolesMappingsUserCustomizer rolesMappingsUserCustomizer(GatewayConfigProperties config) {
Map<String, List<String>> rolesMappings = config.getRolesMappings();
log.info("Creating {}", RolesMappingsUserCustomizer.class.getSimpleName());
return new RolesMappingsUserCustomizer(rolesMappings);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2022 by the geOrchestra PSC
*
* This file is part of geOrchestra.
*
* geOrchestra is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* geOrchestra is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* geOrchestra. If not, see <http://www.gnu.org/licenses/>.
*/

package org.georchestra.gateway.security;

import java.util.function.Function;

import org.georchestra.security.model.GeorchestraUser;
import org.springframework.core.Ordered;

/**
* Extension point to customize the state of a {@link GeorchestraUser} once it
* was obtained from an authentication provider by means of a
* {@link GeorchestraUserMapperExtension}.
*
* @see GeorchestraUserMapper
*/
public interface GeorchestraUserCustomizerExtension extends Ordered, Function<GeorchestraUser, GeorchestraUser> {

default int getOrder() {
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,22 @@
* token.
* <p>
* Relies on the provided {@link GeorchestraUserMapperExtension}s to map an
* {@link Authentication} to a {@link GeorchestraUsers}.
* {@link Authentication} to a {@link GeorchestraUsers}, and on
* {@link GeorchestraUserCustomizerExtension} to apply additional user
* customizations once resolved from {@link Authentication} to
* {@link GeorchestraUser}.
* <p>
* {@literal GeorchestraUserMapperExtension} beans specialize in mapping auth
* tokens for specific authentication sources (e.g. LDAP, OAuth2, OAuth2+OpenID,
* etc).
* <p>
* {@literal GeorchestraUserCustomizerExtension} beans specialize in applying
* any additional customization to the {@link GeorchestraUser} object after it
* has been extracted from the {@link Authentication} created by the actual
* authentication provider.
*
* @see GeorchestraUserMapperExtension
* @see GeorchestraUserCustomizerExtension
*/
@RequiredArgsConstructor
public class GeorchestraUserMapper {
Expand All @@ -49,6 +60,16 @@ public class GeorchestraUserMapper {
*/
private final @NonNull List<GeorchestraUserMapperExtension> resolvers;

private final @NonNull List<GeorchestraUserCustomizerExtension> customizers;

GeorchestraUserMapper() {
this(List.of(), List.of());
}

GeorchestraUserMapper(List<GeorchestraUserMapperExtension> resolvers) {
this(resolvers, List.of());
}

/**
* @return the first non-empty user from
* {@link GeorchestraUserMapperExtension#resolve asking} the extension
Expand All @@ -60,8 +81,15 @@ public Optional<GeorchestraUser> resolve(@NonNull Authentication authToken) {
return resolvers.stream()//
.map(resolver -> resolver.resolve(authToken))//
.filter(Optional::isPresent)//
.map(Optional::get)//
.findFirst();
.map(Optional::orElseThrow)//
.map(this::customize).findFirst();
}

private GeorchestraUser customize(GeorchestraUser user) {
GeorchestraUser customized = user;
for (GeorchestraUserCustomizerExtension customizer : customizers) {
customized = customizer.apply(customized);
}
return customized;
}
}
Loading