Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.git
node_modules
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ GEMINI.md
AGENTS.md
.copilot/
.github/copilot-instructions.md
mapstore.war
mapstore**.war
.env

# Local auth overrides and runtime bootstrap files
/docker/keycloak/realm-mapstore.json
# datadir runtime files
datadir/
datadir/*
2 changes: 1 addition & 1 deletion docker-compose-override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ services:
profiles:
- test
networks:
- mapstore-network
- mapstore-network
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,3 @@ services:

volumes:
pg_data:

27 changes: 27 additions & 0 deletions docker/.env.auth.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copy to .env and adjust if needed.
# The variables below are SAMPLE values for local development only.
# Do NOT use these credentials in production.
# The auth compose overlay provides sample defaults, but for any real deployment
# you should create a `.env` file with secure values and override defaults.

# LDAP sample values
LDAP_ORGANISATION=Acme
LDAP_DOMAIN=acme.org
LDAP_ADMIN_PASSWORD=changeme-ldap-admin-pw-123
LDAP_CONFIG_PASSWORD=changeme-ldap-config-pw-123
LDAP_READONLY_USER=true
LDAP_READONLY_USER_USERNAME=svc_mapstore_ldap
LDAP_READONLY_PASSWORD=changeme-ldap-bind-pw-123
LDAP_TLS=false

# Keycloak dev admin (admin console at http://localhost/keycloak/admin/)
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=changeme-keycloak-admin-pw-123
KEYCLOAK_HTTP_RELATIVE_PATH=/keycloak
KEYCLOAK_HOSTNAME=http://localhost/keycloak
KEYCLOAK_HOSTNAME_STRICT=false
KEYCLOAK_PROXY_HEADERS=xforwarded
KEYCLOAK_REALM_FILE=realm-mapstore.json
# The auth compose expects a realm file named `realm-mapstore.json` in `docker/keycloak/` by default.
# To use the shipped sample, copy `docker/keycloak/realm-mapstore.sample.json` to
# `docker/keycloak/realm-mapstore.json` or set `KEYCLOAK_REALM_FILE` to the sample filename.
103 changes: 103 additions & 0 deletions docker/docker-compose.auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
version: "3.8"
networks:
mapstore-network:
driver: bridge

services:
ldap:
build:
context: ./docker/openldap/
image: geosolutions-mapstore/openldap
container_name: ldap
restart: on-failure
entrypoint:
- /bin/sh
- -lc
- >
rm -rf /container/service/slapd/assets/config/bootstrap/ldif/custom/* &&
cp /ldif-src/ldap-init.ldif /container/service/slapd/assets/config/bootstrap/ldif/custom/ldap-init.ldif &&
exec /container/tool/run
environment:
LDAP_ORGANISATION: "${LDAP_ORGANISATION:-Acme}"
LDAP_DOMAIN: "${LDAP_DOMAIN:-acme.org}"
LDAP_ADMIN_PASSWORD: "${LDAP_ADMIN_PASSWORD:-changeme-ldap-admin-pw-123}"
LDAP_CONFIG_PASSWORD: "${LDAP_CONFIG_PASSWORD:-changeme-ldap-config-pw-123}"
LDAP_READONLY_USER: "true"
LDAP_READONLY_USER_USERNAME: "${LDAP_READONLY_USER_USERNAME:-svc_mapstore_ldap}"
LDAP_READONLY_USER_PASSWORD: "${LDAP_READONLY_PASSWORD:-changeme-ldap-bind-pw-123}"
LDAP_TLS: "${LDAP_TLS:-false}"
volumes:
- ldap_data:/var/lib/ldap
- ldap_config:/etc/ldap/slapd.d
- ./docker/openldap/ldap-init.ldif:/ldif-src/ldap-init.ldif:ro
healthcheck:
test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -b \"\" -s base \"(objectClass=*)\" namingContexts >/dev/null 2>&1"]
interval: 5s
timeout: 5s
retries: 30
start_period: 20s
networks:
- mapstore-network

keycloak:
image: quay.io/keycloak/keycloak:26.0.7
container_name: keycloak
restart: on-failure
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: "${KEYCLOAK_ADMIN:-admin}"
KC_BOOTSTRAP_ADMIN_PASSWORD: "${KEYCLOAK_ADMIN_PASSWORD}"
KC_HTTP_ENABLED: "true"
KC_HTTP_RELATIVE_PATH: "${KEYCLOAK_HTTP_RELATIVE_PATH:-/keycloak}"
KC_HOSTNAME: "${KEYCLOAK_HOSTNAME:-http://localhost/keycloak}"
KC_HOSTNAME_STRICT: "${KEYCLOAK_HOSTNAME_STRICT:-false}"
KC_PROXY_HEADERS: "${KEYCLOAK_PROXY_HEADERS:-xforwarded}"
KC_HEALTH_ENABLED: "true"
KC_HTTP_MANAGEMENT_RELATIVE_PATH: /
command: start-dev --import-realm
volumes:
- ./docker/keycloak/realm-mapstore.json:/opt/keycloak/data/import/realm-mapstore.json:ro
- keycloak_data:/opt/keycloak/data
healthcheck:
test: ["CMD-SHELL", "{ printf 'HEAD /health/ready HTTP/1.0\\r\\n\\r\\n' >&0; grep 'HTTP/1.0 200'; } 0<>/dev/tcp/127.0.0.1/9000"]
interval: 5s
timeout: 3s
retries: 12
start_period: 30s
networks:
- mapstore-network

proxy:
image: nginx
container_name: proxy
volumes:
- ./docker/mapstore.auth.conf:/etc/nginx/conf.d/default.conf:rw
ports:
- 80:80
depends_on:
- mapstore
- keycloak
networks:
- mapstore-network

mapstore:
build:
context: .
dockerfile: Dockerfile
args:
MAPSTORE_WEBAPP_SRC: "mapstore.war"
depends_on:
ldap:
condition: service_healthy
keycloak:
condition: service_healthy
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- "${DATADIR_PATH:-./datadir}:/usr/local/tomcat/datadir:ro"
networks:
- mapstore-network

volumes:
ldap_data:
ldap_config:
keycloak_data:
102 changes: 102 additions & 0 deletions docker/keycloak/realm-mapstore.sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"realm": "mapstore",
"enabled": true,
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": false,
"editUsernameAllowed": false,
"sslRequired": "external",
"roles": {
"realm": [
{
"name": "admin",
"description": "Mapped to MapStore ADMIN (see mapstore-ovr.properties roleMappings)"
},
{
"name": "user",
"description": "Optional; default MapStore role is USER if unmapped"
}
]
},
"groups": [
{
"name": "kc-admins",
"path": "/kc-admins"
},
{
"name": "kc-users",
"path": "/kc-users"
}
],
"users": [
{
"username": "kcuser",
"enabled": true,
"emailVerified": true,
"firstName": "Keycloak",
"lastName": "User",
"email": "kcuser@acme.org",
"realmRoles": ["user"],
"groups": ["/kc-users"],
"credentials": [
{
"type": "password",
"value": "changeme-kcuser-pw-123",
"temporary": false
}
]
},
{
"username": "kcadmin",
"enabled": true,
"emailVerified": true,
"firstName": "Keycloak",
"lastName": "Admin",
"email": "kcadmin@acme.org",
"realmRoles": ["admin"],
"groups": ["/kc-admins"],
"credentials": [
{
"type": "password",
"value": "changeme-kcadmin-pw-123",
"temporary": false
}
]
}
],
"clients": [
{
"clientId": "mapstore-server",
"name": "MapStore OpenID (server)",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"clientAuthenticatorType": "client-secret",
"secret": "changeme-mapstore-oidc-client-secret-123",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"redirectUris": ["http://localhost/mapstore/*"],
"webOrigins": ["http://localhost", "+"],
"protocolMappers": [
{
"name": "groups",
"protocol": "openid-connect",
"protocolMapper": "oidc-group-membership-mapper",
"consentRequired": false,
"config": {
"claim.name": "groups",
"full.path": "false",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
],
"attributes": {
"post.logout.redirect.uris": "http://localhost/mapstore/*"
}
}
]
}
115 changes: 115 additions & 0 deletions docker/mapstore.auth.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
server {
server_name localhost;

listen 80;
listen [::]:80;

server_tokens off;

root /usr/share/nginx/html;
index index.html;

gzip on;
gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
application/json
application/javascript
application/x-javascript
text/xml application/xm
application/xml+rss
text/javascript;

underscores_in_headers on;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering on;
proxy_buffer_size 1k;
proxy_buffers 24 4k;
proxy_busy_buffers_size 8k;
proxy_max_temp_file_size 2048m;
proxy_temp_file_write_size 32k;

location / {
deny all;
return 301 /mapstore;
}

# Keycloak (same host as MapStore — required for OIDC redirects and MapStore backend)
location /keycloak/ {
proxy_pass http://keycloak:8080/keycloak/;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Prefix /keycloak;

# Keycloak auth responses set large Set-Cookie headers; default 1k buffers cause 502.
proxy_buffer_size 128k;
proxy_buffers 8 256k;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;
}

location /mapstore {
proxy_pass http://mapstore:8080/mapstore;

# OIDC callback returns 302 + Set-Cookie; large request Cookie headers are common too.
proxy_buffer_size 128k;
proxy_buffers 8 256k;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;

if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'DELETE') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'PUT') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
}
4 changes: 4 additions & 0 deletions docker/openldap/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM osixia/openldap:1.5.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

osixia/openldap is unmaintained

Alternatives:

  • bitnami/openldap similar API
  • gh.io/nitnelave/lldap light, web UI

We are using 3 different LDAP alternatives...

  • acme
  • osixia/openldap
  • geosolutions-it/openldap

I'd suggest to use only one implemntation, probably based to a maintained one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bitnami/openldap is private only, there seems an option to use bitnamilegacy/openldap but it's again legacy and no better than osixia/openldap. osixia/openldap does have v2 but it's in beta.

Writing a custom Dockerfile and init script for building image from scratch might be too much here. Maybe a seperate geosolutions-it/openldap would be nice, if we plan to use dockerized ldap setups.

For now i kept is the same.


# Bootstrap LDIF is baked into the image (runs once when ldap_data volume is empty).
COPY ldap-init.ldif /container/service/slapd/assets/config/bootstrap/ldif/custom/ldap-init.ldif
Loading
Loading