Skip to content

Commit

Permalink
Avoid exceptions when gateway-shared-auth is disabled after being ena…
Browse files Browse the repository at this point in the history
…bled

If the `gateway-shared-auth` geoserver authentication filter was
enabled, and it was automatically added to the filter chains, when
disabled and the applications restart, would produce an error message
and the webui Authentication configuration page would be broken.

This patch adds a no-op filter when `gateway-shared-auth` is disabled by
externalized configuration.
  • Loading branch information
groldan committed Jul 15, 2024
1 parent b25f699 commit 1709846
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 16 deletions.
1 change: 1 addition & 0 deletions compose/.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ COMPOSE_PROJECT_NAME=gscloud_dev
TAG=1.9-SNAPSHOT
ACL_TAG=2.3-SNAPSHOT
GS_USER="1000:1000"
GATEWAY_SHARED_AUTH=false

# geoserver entry point for the gateway
GEOSERVER_BASE_PATH=/geoserver/cloud
Expand Down
1 change: 1 addition & 0 deletions compose/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ services:
# eat our own dogfood and set a base path
GEOSERVER_BASE_PATH: ${GEOSERVER_BASE_PATH}
SPRING_PROFILES_ACTIVE: "${GATEWAY_DEFAULT_PROFILES}"
GATEWAY_SHARED_AUTH: "${GATEWAY_SHARED_AUTH}" #same as in gstemplate
ports:
- 9090:8080
deploy:
Expand Down
1 change: 1 addition & 0 deletions compose/templates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
# enable the postgis jndi datasource. This is just an example used for development
JNDI_POSTGIS_ENABLED: true
GEOWEBCACHE_CACHE_DIR: /data/geowebcache
GATEWAY_SHARED_AUTH: "${GATEWAY_SHARED_AUTH}"
volumes:
- geowebcache_data:/data/geowebcache
deploy:
Expand Down
2 changes: 1 addition & 1 deletion config
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.autoconfigure.authzn;

import org.geoserver.cloud.autoconfigure.security.ConditionalOnGeoServerSecurityEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Conditional to check if gateway/webui shared authentication is disabled. */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
@ConditionalOnGeoServerSecurityEnabled
@ConditionalOnProperty(
name = GatewaySharedAuthConfigProperties.ENABLED_PROP,
havingValue = "false",
matchIfMissing = true)
public @interface ConditionalOnGatewaySharedAuthDisabled {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.autoconfigure.authzn;

import org.geoserver.cloud.autoconfigure.security.ConditionalOnGeoServerSecurityEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Conditional to enable gateway/webui shared authentication mechanism. It must also be enabled in
* the gateway with the same config property {@code
* geoserver.security.gateway-shared-auth.enabled=true}
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
@ConditionalOnGeoServerSecurityEnabled
@ConditionalOnProperty(
name = GatewaySharedAuthConfigProperties.ENABLED_PROP,
havingValue = "true",
matchIfMissing = false)
public @interface ConditionalOnGatewaySharedAuthEnabled {}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ public class GatewaySharedAuthConfigProperties {
static final String AUTO_PROP = PREFIX + ".auto";
static final String SERVER_PROP = PREFIX + ".server";

/** Whether the gateway-shared-auth webui authentication conveyor protocol is enabled */
/**
* Whether the gateway-shared-auth webui authentication conveyor protocol is enabled. Note the
* same configuration must be applied to the gateway-service.
*/
private boolean enabled = true;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@

import lombok.extern.slf4j.Slf4j;

import org.geoserver.cloud.autoconfigure.security.ConditionalOnGeoServerSecurityEnabled;
import org.geoserver.cloud.autoconfigure.security.GeoServerSecurityAutoConfiguration;
import org.geoserver.cloud.security.gateway.sharedauth.ClientConfiguration;
import org.geoserver.cloud.security.gateway.sharedauth.DisabledConfiguration;
import org.geoserver.cloud.security.gateway.sharedauth.GatewaySharedAuthenticationInitializer;
import org.geoserver.cloud.security.gateway.sharedauth.ServerConfiguration;
import org.geoserver.platform.ModuleStatusImpl;
import org.geoserver.security.GeoServerSecurityManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.web.servlet.server.Session;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -53,11 +55,6 @@
// GeoServerSecurityManager calls GeoServerExtensions.extensions(GeoServerSecurityProvider.class)
@AutoConfiguration(before = GeoServerSecurityAutoConfiguration.class)
@EnableConfigurationProperties(GatewaySharedAuthConfigProperties.class)
@ConditionalOnGeoServerSecurityEnabled
@ConditionalOnProperty(
name = GatewaySharedAuthConfigProperties.ENABLED_PROP,
havingValue = "true",
matchIfMissing = false)
@Import({
GatewaySharedAuthenticationAutoConfiguration.Server.class,
GatewaySharedAuthenticationAutoConfiguration.Client.class
Expand All @@ -67,6 +64,33 @@
public class GatewaySharedAuthenticationAutoConfiguration {

@Bean
ModuleStatusImpl gatewaySharedAuthModuleInfo(
GatewaySharedAuthConfigProperties config, BuildProperties buildProperties) {
ModuleStatusImpl m = new ModuleStatusImpl();
m.setAvailable(true);
m.setEnabled(config.isEnabled());
m.setVersion(buildProperties.getVersion());
m.setName("GeoServer Cloud gateway shared authentication");
m.setModule("gs-cloud-starter-security");
m.setComponent("GatewaySharedAuthenticationFilter");
m.setMessage(
"""
The GatewaySharedAuthenticationFilter, specific to GeoServer Cloud,
implements a mechanism in collaboration with the GeoServer Cloud
Gateway application, for the microservices to share the authentication
username and roles obtained when the user logs in through the WebUI.
When enabled both in the gateway and the GeoServer microservices, and
logged in through the WebUI, calls to other services (for example, in
the Layer Preview page), will be performed with the same user as in
the WebUI.
""");
m.setDocumentation("documentation");
return m;
}

@Bean
@ConditionalOnGatewaySharedAuthEnabled
@ConditionalOnProperty(
name = GatewaySharedAuthConfigProperties.AUTO_PROP,
havingValue = "true",
Expand All @@ -77,6 +101,7 @@ GatewaySharedAuthenticationInitializer gatewaySharedAuthenticationInitializer(
}

@Configuration
@ConditionalOnGatewaySharedAuthEnabled
@ConditionalOnProperty(
name = GatewaySharedAuthConfigProperties.SERVER_PROP,
havingValue = "true",
Expand All @@ -85,11 +110,12 @@ GatewaySharedAuthenticationInitializer gatewaySharedAuthenticationInitializer(
static class Server {
@PostConstruct
void log() {
log.info("Gateway shared authentication method available in server mode");
log.info("gateway-shared-auth enabled in server mode");
}
}

@Configuration
@ConditionalOnGatewaySharedAuthEnabled
@ConditionalOnProperty(
name = GatewaySharedAuthConfigProperties.SERVER_PROP,
havingValue = "false",
Expand All @@ -98,7 +124,17 @@ void log() {
static class Client {
@PostConstruct
void log() {
log.info("Gateway shared authentication method available in client mode");
log.info("gateway-shared-auth enabled in client mode");
}
}

@Configuration
@ConditionalOnGatewaySharedAuthDisabled
@Import(DisabledConfiguration.class)
static class Disabled {
@PostConstruct
void log() {
log.info("gateway-shared-auth disabled");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Contributes a {@link GatewaySharedAuthenticationProvider} in client mode.
*
* @see ServerConfiguration
* @see DisabledConfiguration
* @since 1.9
*/
@Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.security.gateway.sharedauth;

import static org.geoserver.cloud.security.gateway.sharedauth.GatewaySharedAuthenticationProvider.Mode.DISABLED;

import org.geoserver.security.filter.AbstractFilterProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Contributes a {@link GatewaySharedAuthenticationProvider} in disabled mode, essentially a no-op
* {@link AbstractFilterProvider} to avoid starup failure when the gateway shared auth was enabled,
* then disabled, and geoserver restarted.
*
* @see ClientConfiguration
* @see ServerConfiguration
* @since 1.9
*/
@Configuration
public class DisabledConfiguration {

@Bean
GatewaySharedAuthenticationProvider gatewaySharedAuthenticationProvider() {
return new GatewaySharedAuthenticationProvider(DISABLED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.geoserver.security.GeoServerRoleConverter;
import org.geoserver.security.config.PreAuthenticatedUserNameFilterConfig.PreAuthenticatedUserNameRoleSource;
Expand Down Expand Up @@ -40,6 +41,7 @@
* @since 1.9
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
class GatewaySharedAuthenticationFilter extends GeoServerSecurityFilter
implements GeoServerAuthenticationFilter {

Expand Down Expand Up @@ -72,6 +74,16 @@ public static GeoServerSecurityFilter client() {
return new GatewaySharedAuthenticationFilter(new ClientFilter());
}

/**
* @return a {@link GatewaySharedAuthenticationFilter} proxy filter with a
* <strong>no-op</strong> {@link DisabledFilter} delegate. This prevents startup failures
* and WebUI security settings editting failures, when the filter has been disabled through
* {@code geoserver.security.gateway-shared-auth.enabled=false} after it's been enabled.
*/
public static GeoServerSecurityFilter disabled() {
return new GatewaySharedAuthenticationFilter(new DisabledFilter());
}

@Override
public boolean applicableForHtml() {
return true;
Expand Down Expand Up @@ -126,7 +138,8 @@ static class ServerFilter extends GeoServerSecurityFilter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {

// if the user is authenticated by some method, set the response headers for the
// Gateway to act as the middle man and send the user and roles to the other services
// Gateway to act as the middle man and send the user and roles to the other
// services
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || auth instanceof AnonymousAuthenticationToken) {
removeGatewayResponseHeaders((HttpServletResponse) response);
Expand Down Expand Up @@ -157,4 +170,15 @@ private void setGatewayResponseHeaders(
}
}
}

/** No-op GeoServerSecurityFilter */
static class DisabledFilter extends GeoServerSecurityFilter {

@Override
@SneakyThrows({ServletException.class, IOException.class})
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
log.debug("gateway shared auth filter pass-through, functionality disabled");
chain.doFilter(request, response);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public class GatewaySharedAuthenticationProvider extends AbstractFilterProvider
*/
public enum Mode {
SERVER,
CLIENT
CLIENT,
DISABLED
}

private final @NonNull Mode mode;
Expand All @@ -51,9 +52,11 @@ public Class<GatewaySharedAuthenticationFilter> getFilterClass() {

@Override
public GeoServerSecurityFilter createFilter(SecurityNamedServiceConfig config) {
if (Mode.SERVER == mode) {
return GatewaySharedAuthenticationFilter.server();
}
return GatewaySharedAuthenticationFilter.client();
return switch (mode) {
case SERVER -> GatewaySharedAuthenticationFilter.server();
case CLIENT -> GatewaySharedAuthenticationFilter.client();
case DISABLED -> GatewaySharedAuthenticationFilter.disabled();
default -> throw new IllegalArgumentException("Unexpected value: " + mode);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* Contributes a {@link GatewaySharedAuthenticationProvider} in server mode.
*
* @see ClientConfiguration
* @see DisabledConfiguration
* @since 1.9
*/
@Configuration
Expand Down

0 comments on commit 1709846

Please sign in to comment.