Skip to content

Commit

Permalink
Merge pull request #15 from groldan/server/auth
Browse files Browse the repository at this point in the history
API security: define authentication methods and secure the APIs
  • Loading branch information
groldan authored May 14, 2023
2 parents e0e7407 + 58f27fd commit c1b9f3d
Show file tree
Hide file tree
Showing 30 changed files with 967 additions and 93 deletions.
38 changes: 31 additions & 7 deletions docker-compose-acl-config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


jndi:
datasources:
acl:
Expand All @@ -14,11 +12,33 @@ jndi:
connection-timeout: 3000
idle-timeout: 60000

acl:
db:
jndiName: java:comp/env/jdbc/acl
hbm2ddl.auto: update
schema: acl
acl.db.jndiName: java:comp/env/jdbc/acl
acl.db.schema: acl
acl.db.hbm2ddl.auto: update

geoserver:
acl:
security:
headers:
enabled: true
user-header: sec-username
roles-header: sec-roles
admin-roles: ["ROLE_ADMINISTRATOR"]
internal:
enabled: true
users:
admin:
admin: true
enabled: ${acl.admin.enabled:true}
# password is a bcrypt encoded value for s3cr3t
password: "${acl.admin.password:{bcrypt}$2a$10$FE62N3ejbKm56EX5VrtSQeDDka8YjwgjwF9sSEKbatGZuZ8e7S9v.}"
#for a plain-text password (e.g. coming from a docker or kubernetes secret,
# use the {noop} prefix, as in: password: "{noop}plaintextpwd}", or password: "{noop}${ACL_ADMIN_PASSWORD}"
user:
admin: false
enabled: true
# password is a bcrypt encoded value for s3cr3t
password: "{bcrypt}$2a$10$eMyaZRLZBAZdor8nOX.qwuwOyWazXjR2hddGLCT6f6c382WiwdQGG"

logging:
level:
Expand All @@ -37,3 +57,7 @@ management:
exposure:
include:
- '*'
---
# local profile, used for development only. Other settings like config and eureka urls in gs_cloud_bootstrap_profiles.yml
spring.config.activate.on-profile: local
server.port: 9091
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ services:
cpus: '4.0'
memory: 2G
ports:
- 5432:5432
- 6432:5432

acl:
image: geoservercloud/geoserver-acl:1.0-SNAPSHOT
Expand Down
39 changes: 0 additions & 39 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,42 +35,3 @@ access rules. So if you're familiar with GeoFence, it'll be easy to reason
about GeoServer ACL.


## Dependency graph

```mermaid
flowchart LR
subgraph domain
adminrule-management --> object-model & rule-management
authorization --> adminrule-management & rule-management
rule-management --> object-model
end
subgraph openapi-codegen
openapi-server --> openapi-model
openapi-client --> openapi-model
end
subgraph integration
subgraph persistence-jpa
jpa-integration --> jpa-persistence
end
subgraph spring-integration
domain-spring-integration -. optional> .-> rule-management & adminrule-management & authorization
end
subgraph openapi-integration
api-model-mapper --> object-model & openapi-model
api-impl --> api-model-mapper & openapi-server & domain-spring-integration & rule-management & adminrule-management
api-client --> api-model-mapper & openapi-client
end
subgraph spring-boot-integration
spring-boot-autoconfiguration --> domain-spring-integration & api-impl & jpa-integration
end
end
subgraph geoserver-plugin
plugin-webui --> plugin-accessmanager
plugin-rest --> plugin-accessmanager & api-impl
plugin --> plugin-accessmanager & plugin-rest & plugin-webui
end
subgraph application
rest-app --> spring-boot-autoconfiguration
end
```

11 changes: 9 additions & 2 deletions src/artifacts/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ FROM eclipse-temurin:17-jre
LABEL maintainer="GeoServer PSC <[email protected]>"

WORKDIR /opt/app/bin
ENV JAVA_TOOL_OPTS="\
ENV DEF_JAVA_TOOL_OPTS="\
--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
Expand All @@ -22,6 +22,9 @@ ENV JAVA_TOOL_OPTS="\
--add-opens=java.naming/com.sun.jndi.ldap=ALL-UNNAMED \
-Djava.awt.headless=true"
ENV JAVA_OPTS="-XX:MaxRAMPercentage=80 -XshowSettings:system"

ENV JAVA_TOOL_OPTS="$DEF_JAVA_TOOL_OPTS $JAVA_OPTS"

EXPOSE 8080

COPY --from=builder dependencies/ ./
Expand All @@ -36,4 +39,8 @@ HEALTHCHECK \
--retries=5 \
CMD curl -f -s -o /dev/null localhost:8080/actuator/health || exit 1

CMD exec java $JAVA_OPTS $JAVA_TOOL_OPTS org.springframework.boot.loader.JarLauncher
ARG APP_ARGS=""

ENTRYPOINT [ "/bin/bash", "-c", "exec java $JAVA_TOOL_OPTS org.springframework.boot.loader.JarLauncher \"${@}\"", "--" ]

CMD ["${APP_ARGS}"]
4 changes: 2 additions & 2 deletions src/artifacts/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ With the application running at [http://localhost:8080/api](http://localhost:808
mvn clean install
```

will create a single-jar executable at `target/gs-cloud-acl-service-<version>-bin.jar`.
will create a single-jar executable at `target/gs-acl-service-<version>-bin.jar`.

## Run

Expand All @@ -27,7 +27,7 @@ Run in development mode with an in-memory H2 database, either with

or

java -jar target/gs-cloud-acl-service-1.0-SNAPSHOT-bin.jar --spring.profiles.active=dev
java -jar target/gs-acl-service-1.0-SNAPSHOT-bin.jar --spring.profiles.active=dev


## Dependency graph
Expand Down
8 changes: 8 additions & 0 deletions src/artifacts/api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
Expand All @@ -57,6 +61,10 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,41 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Arrays;
import java.util.List;

@SpringBootApplication
public class AccesControlListApplication {

private static final String ENCODEPASSWORD = "encodepassword";

public static void main(String... args) {
List<String> arglist = Arrays.asList(args);

if (arglist.contains(ENCODEPASSWORD)) {
System.exit(encodePassword(arglist));
}

try {
SpringApplication.run(AccesControlListApplication.class, args);
} catch (RuntimeException e) {
System.exit(-1);
}
}

private static int encodePassword(List<String> arglist) {
int pwdIndex = 1 + arglist.indexOf(ENCODEPASSWORD);
if (arglist.size() < pwdIndex) {
System.err.println("Usage: encodepassword <password>");
return -1;
}
String pwd = arglist.get(pwdIndex);
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encoded = encoder.encode(pwd);
System.out.println(encoded);
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* (c) 2023 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.acl.autoconfigure.security;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;

@AutoConfiguration
@EnableWebSecurity
@EnableConfigurationProperties(SecurityConfigProperties.class)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j(topic = "org.geoserver.acl.autoconfigure.security")
public class AclServiceSecurityAutoConfiguration {

private @Autowired(required = false) RequestHeaderAuthenticationFilter preAuthFilter;

@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
AuthenticationManager authenticationManager,
SecurityConfigProperties config)
throws Exception {

http.csrf().disable();

if (!config.enabled()) {
log.warn("No security authentication method is defined!");
return http.build();
}

http.authenticationManager(authenticationManager);

if (null == preAuthFilter) {
log.info("Pre-authentication headers disabled");
} else {
log.info(
"Pre-authentication headers enabled for {}/{}. Admin roles: {}",
config.getHeaders().getUserHeader(),
config.getHeaders().getRolesHeader(),
config.getHeaders().getAdminRoles());
http.addFilterAfter(preAuthFilter, RequestHeaderAuthenticationFilter.class);
}

http.authorizeRequests()
.antMatchers("/", "/api/api-docs/**", "/api/swagger-ui.html", "/api/swagger-ui/**")
.permitAll()
.anyRequest()
.authenticated()
.and();

if (config.getInternal().isEnabled()) {
http.httpBasic();
}
return http.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* (c) 2023 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.acl.autoconfigure.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;

import java.util.List;

@Configuration
public class AuthenticationManagerAutoConfiguration {

@Bean
AuthenticationManager authenticationManager(List<AuthenticationProvider> providers)
throws Exception {
return new ProviderManager(providers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* (c) 2023 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.acl.autoconfigure.security;

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;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnProperty(
prefix = ConditionalOnInternalAuthenticationEnabled.PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = false)
public @interface ConditionalOnInternalAuthenticationEnabled {

public static final String PREFIX = SecurityConfigProperties.PREFIX + ".internal";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* (c) 2023 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.acl.autoconfigure.security;

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;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnProperty(
prefix = ConditionalOnPreAuthenticationEnabled.PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = false)
public @interface ConditionalOnPreAuthenticationEnabled {

public static final String PREFIX = SecurityConfigProperties.PREFIX + ".headers";
}
Loading

0 comments on commit c1b9f3d

Please sign in to comment.