From 42bed4c90c5a82ddc20b9881c34d63ca128f2bd9 Mon Sep 17 00:00:00 2001 From: Kevin Thompson Date: Fri, 6 Jun 2025 12:18:24 -0500 Subject: [PATCH] First commit --- Dockerfile | 51 +++++++++ README.md | 180 ++++++++++++++++++++++++++++++++ entrypoint.sh | 41 ++++++++ supervisord.conf | 26 +++++ templates/bootstrap.ldif.tpl | 19 ++++ templates/hydra-config.yaml.tpl | 24 +++++ templates/lam.conf.php.tpl | 20 ++++ 7 files changed, 361 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 entrypoint.sh create mode 100644 supervisord.conf create mode 100644 templates/bootstrap.ldif.tpl create mode 100644 templates/hydra-config.yaml.tpl create mode 100644 templates/lam.conf.php.tpl diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1a67a9b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +FROM debian:bookworm-slim + +ENV DEBIAN_FRONTEND=noninteractive + +# 1) Install Supervisor, slapd, ldap-utils, Apache2+PHP, wget, unzip, envsubst +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + supervisor \ + slapd ldap-utils \ + wget unzip gnupg \ + apache2 libapache2-mod-php php php-ldap php-mbstring php-xml \ + gettext-base \ + && rm -rf /var/lib/apt/lists/* + +# 2) Download and install Hydra binary (v1.16.4 as example) +RUN HYDRA_VERSION=v1.16.4 && \ + wget -qO /tmp/hydra.tar.gz \ + https://github.com/ory/hydra/releases/download/${HYDRA_VERSION}/hydra_${HYDRA_VERSION}_linux_amd64.tar.gz && \ + mkdir -p /usr/local/bin/hydra_tmp && \ + tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin/hydra_tmp && \ + mv /usr/local/bin/hydra_tmp/hydra /usr/local/bin/hydra && \ + chmod +x /usr/local/bin/hydra && \ + rm -rf /usr/local/bin/hydra_tmp /tmp/hydra.tar.gz + +# 3) Install LAM (osixia/ldap-account-manager) under /var/www/lam +RUN LAM_VERSION=1.2.1 && \ + wget -qO /tmp/lam.zip https://github.com/osixia/ldap-account-manager/releases/download/${LAM_VERSION}/ldap-account-manager_${LAM_VERSION}.zip && \ + unzip /tmp/lam.zip -d /var/www/html && \ + mv /var/www/html/ldap-account-manager /var/www/html/lam && \ + rm /tmp/lam.zip + +# 4) Enable Apache modules required by LAM +RUN a2enmod php8.2 ldap rewrite + +# 5) Copy Supervisor config and entrypoint +COPY supervisord.conf /etc/supervisor/supervisord.conf +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# 6) Copy all template files into /templates +COPY templates/ /templates/ + +# 7) Create empty folder for LAM config (will be filled at runtime) +RUN mkdir -p /var/www/lam/config +RUN chown -R www-data:www-data /var/www/lam + +# 8) Expose ports +EXPOSE 389 636 4444 4445 80 + +# 9) At runtime, entrypoint.sh does all the envsubst + slapd reconfiguration + supervisord +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d15cbe2 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# Hydra-LDAP-LAM All-in-One Docker Image + +## Overview + +This repository contains all necessary configuration and templates to build a single Docker image that bundles and configures: + +- **OpenLDAP** (slapd) as the LDAP directory. +- **Ory Hydra** (an OAuth 2.0 / OIDC server). +- **LDAP Account Manager (LAM)** under Apache2 + PHP as the web-based LDAP UI. + +The container is orchestrated using **Supervisor** to launch and manage all three services. All credentials and configuration values are provided at runtime via environment variables, ensuring no sensitive data is baked into the image. + +--- + +## Repository Structure + +``` +hydra-ldap-lam/ +├─ Dockerfile +├─ entrypoint.sh +├─ supervisord.conf +└─ templates/ + ├─ bootstrap.ldif.tpl + ├─ hydra-config.yaml.tpl + └─ lam.conf.php.tpl +``` + +- **Dockerfile**: Builds the Docker image, installs dependencies, and copies template files. +- **entrypoint.sh**: Entry-point script that performs runtime configuration substitution (`envsubst`), bootstraps LDAP, and starts Supervisor. +- **supervisord.conf**: Supervisor configuration to run `slapd`, `hydra`, and Apache2 in the foreground. +- **templates/**: + - **bootstrap.ldif.tpl**: LDIF template for initializing the LDAP data (domain, organization, and a sample user). + - **hydra-config.yaml.tpl**: Hydra configuration template pointing to in-memory SQLite and defining login/consent URLs. + - **lam.conf.php.tpl**: LAM configuration template specifying LDAP bind settings. + +--- + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) (version 20.10+ recommended) +- (Optional) A local or remote Gitea instance for committing the `README.md`. + +--- + +## Building the Image + +1. Clone (or place) this directory locally: + + ```bash + git clone + cd hydra-ldap-lam + ``` + +2. Build the Docker image: + + ```bash + docker build -t hydra-ldap-lam:latest . + ``` + + This will produce an image named `hydra-ldap-lam:latest` containing: + + - OpenLDAP (slapd) + - Ory Hydra binary + - Apache2 + PHP + LAM (LDAP Account Manager) + - Supervisor and supporting scripts + +--- + +## Running the Container + +To run the container, you must supply all required environment variables. Example: + +```bash +docker run -d --name hydra_ldap_lam -e LDAP_DOMAIN="example.com" -e LDAP_ORGANISATION="Example Corp" -e LDAP_ADMIN_PASSWORD="adminpassword" -e LDAP_USER_PASSWORD="password123" -e LAM_BIND_DN="cn=admin,dc=example,dc=com" -e LAM_BIND_PASSWORD="adminpassword" -e HYDRA_SECRETS_SYSTEM="a-very-long-32+-character-secret" -e HYDRA_ISSUER_URL="http://127.0.0.1:4444" -p 389:389 -p 636:636 -p 4444:4444 -p 4445:4445 -p 80:80 hydra-ldap-lam:latest +``` + +- `-p 389:389`: Exposes LDAP (OpenLDAP) on host port 389. +- `-p 636:636`: Exposes LDAPS (OpenLDAP over TLS) on host port 636 (if TLS is configured). +- `-p 4444:4444`: Exposes Hydra Public (OAuth2) endpoints. +- `-p 4445:4445`: Exposes Hydra Admin endpoints (client registration, introspection, etc.). +- `-p 80:80`: Exposes Apache + LAM (web UI). + +--- + +### Environment Variables + +| Variable | Description | +|--------------------------|------------------------------------------------------------------------------------------------------------------------| +| `LDAP_DOMAIN` | LDAP base domain (e.g., `example.com`). Used to initialize LDAP DIT (`dc=example,dc=com`). | +| `LDAP_ORGANISATION` | Organization name (e.g., `"Example Corp"`). Populates LDAP `o` attribute. | +| `LDAP_ADMIN_PASSWORD` | Password for the LDAP admin user (`cn=admin,dc=${LDAP_DOMAIN}`). | +| `LDAP_USER_PASSWORD` | Password for the sample LDAP user (`uid=jdoe`). | +| `LAM_BIND_DN` | LDAP Bind DN for LAM (e.g., `cn=admin,dc=${LDAP_DOMAIN}`). | +| `LAM_BIND_PASSWORD` | Password for the above `LAM_BIND_DN`. | +| `HYDRA_SECRETS_SYSTEM` | A 32+ character random secret for Hydra’s HMAC and session encryption. | +| `HYDRA_ISSUER_URL` | The issuer URL for Hydra (e.g., `http://127.0.0.1:4444`). Hydra’s config will use this to set `urls.self.base`. | + +> **Note**: For production, consider replacing `LDAP_USER_PASSWORD` with an SSHA hash in `bootstrap.ldif.tpl`, and configuring a proper database (e.g., Postgres) for Hydra instead of in-memory SQLite. + +--- + +## Service URLs and Testing + +1. **LDAP (OpenLDAP)** + - Host: `ldap://localhost:389` + - Admin Bind DN: `cn=admin,dc=${LDAP_DOMAIN}` + - Example query: + ```bash + ldapsearch -x -H ldap://localhost:389 -D "cn=admin,dc=example,dc=com" -w adminpassword -b "dc=example,dc=com" "(uid=jdoe)" + ``` + - Should return the entry for `cn=John Doe, uid=jdoe`. + +2. **LDAP Account Manager (LAM)** + - URL: `http://localhost/lam` + - Login with: + - Bind DN: `cn=admin,dc=${LDAP_DOMAIN}` + - Password: `${LAM_BIND_PASSWORD}` + - Browse the DIT at `dc=${LDAP_DOMAIN} → ou=users → cn=John Doe`. + +3. **Hydra Admin API** + - Base URL: `http://localhost:4445` + - Register a client (example): + ```bash + docker exec -it hydra_ldap_lam hydra clients create --endpoint http://127.0.0.1:4445 --id my-client --secret my-secret --grant-types authorization_code,refresh_token --response-types code,id_token --scope openid,offline --callbacks http://localhost/callback + ``` + +4. **Hydra OAuth2 Public Endpoints** + - Authorization endpoint: + ``` + http://localhost:4444/oauth2/auth?response_type=code&client_id=my-client &scope=openid&redirect_uri=http://localhost/callback&state=xyz + ``` + - Token endpoint (example): + ```bash + curl -X POST http://localhost:4444/oauth2/token -u my-client:my-secret -d grant_type=authorization_code -d code= -d redirect_uri=http://localhost/callback + ``` + - Successful response returns `access_token`, `id_token`, etc. + +--- + +## Customization & Next Steps + +- **Switch to a Persistent Database for Hydra** + Modify `templates/hydra-config.yaml.tpl` to use a `${HYDRA_DSN}` (e.g., `postgres://hydrabox:secret@postgres:5432/hydra?sslmode=disable`) and pass `-e HYDRA_DSN="your-database-url"` at runtime. Add a separate Postgres container or external DB. + +- **Enable TLS for OpenLDAP** + - Generate or supply certificates and adjust `/etc/ldap/slapd.d/` configuration to enable LDAPS (`ldaps://`). + - Update `templates/bootstrap.ldif.tpl` or dynamically configure TLS. + +- **Secure Apache (LAM)** + - Obtain a TLS certificate (e.g., via Let’s Encrypt) and configure Apache’s SSL vhost. + - Adjust `entrypoint.sh` and the Dockerfile to copy in certificates or mount them at runtime. + +- **Multiple LDAP Users / Additional LDIFs** + - Add more `.tpl` LDIF files under `templates/` (e.g., `custom-groups.ldif.tpl`) and apply them in `entrypoint.sh`. + +--- + +## Development + +1. **Modify Templates** + - Update `templates/bootstrap.ldif.tpl` to add more objects (groups, additional users). + - Update `templates/lam.conf.php.tpl` for advanced LAM settings (database, theming, etc.). + - Update `templates/hydra-config.yaml.tpl` for production-ready features (HTTPS, introspection limits, etc.). + +2. **Rebuild the Docker Image** + ```bash + docker build -t hydra-ldap-lam:latest . + ``` + +3. **Run with New Environment Variables** + ```bash + docker run -d -e LDAP_DOMAIN="yourdomain.com" -e LDAP_ORGANISATION="Your Org" -e LDAP_ADMIN_PASSWORD="YourAdminPassword" -e LDAP_USER_PASSWORD="YourUserPassword" -e LAM_BIND_DN="cn=admin,dc=yourdomain,dc=com" -e LAM_BIND_PASSWORD="YourAdminPassword" -e HYDRA_SECRETS_SYSTEM="your-long-32+-char-secret" -e HYDRA_ISSUER_URL="https://your-hydra-host" -p 389:389 -p 636:636 -p 4444:4444 -p 4445:4445 -p 80:80 hydra-ldap-lam:latest + ``` + +--- + +## License + +This project is provided “as-is” under the MIT License. See [LICENSE](LICENSE) for details. + diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..af1c9ce --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -e + +# 1) Ensure required env vars are set: +: "${LDAP_DOMAIN:?Need to set LDAP_DOMAIN (e.g. example.com)}" +: "${LDAP_ORGANISATION:?Need to set LDAP_ORGANISATION}" +: "${LDAP_ADMIN_PASSWORD:?Need to set LDAP_ADMIN_PASSWORD}" +: "${LDAP_USER_PASSWORD:?Need to set LDAP_USER_PASSWORD}" +: "${LAM_BIND_DN:?Need to set LAM_BIND_DN}" +: "${LAM_BIND_PASSWORD:?Need to set LAM_BIND_PASSWORD}" +: "${HYDRA_SECRETS_SYSTEM:?Need to set HYDRA_SECRETS_SYSTEM}" +: "${HYDRA_ISSUER_URL:?Need to set HYDRA_ISSUER_URL}" + +# 2) Reconfigure slapd via debconf: +debconf-set-selections < /tmp/bootstrap.ldif +ldapadd -Y EXTERNAL -H ldapi:/// -D "cn=admin,dc=${LDAP_DOMAIN}" -w "${LDAP_ADMIN_PASSWORD}" -f /tmp/bootstrap.ldif || true +# (ignore “already exists” errors if rerunning) + +# 4) Render Hydra’s config: +envsubst < /templates/hydra-config.yaml.tpl > /etc/hydra/config.yaml + +# 5) Render LAM’s PHP config: +envsubst < /templates/lam.conf.php.tpl > /var/www/lam/config/lam.conf.php + +# 6) Finally, launch supervisord (which starts slapd, hydra, and apache2): +exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..19bbe9f --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,26 @@ +; supervisord.conf +[supervisord] +nodaemon=true +logfile=/var/log/supervisord.log +loglevel=info + +[program:slapd] +command=/usr/sbin/slapd -h "ldap://0.0.0.0:389 ldaps://0.0.0.0:636" -u openldap -g openldap -d -1 +autostart=true +autorestart=true +stdout_logfile=/var/log/slapd.log +stderr_logfile=/var/log/slapd.err + +[program:hydra] +command=/usr/local/bin/hydra serve all --config /etc/hydra/config.yaml +autostart=true +autorestart=true +stdout_logfile=/var/log/hydra.log +stderr_logfile=/var/log/hydra.err + +[program:apache2] +command=/usr/sbin/apache2ctl -D FOREGROUND +autostart=true +autorestart=true +stdout_logfile=/var/log/apache2.log +stderr_logfile=/var/log/apache2.err diff --git a/templates/bootstrap.ldif.tpl b/templates/bootstrap.ldif.tpl new file mode 100644 index 0000000..edef150 --- /dev/null +++ b/templates/bootstrap.ldif.tpl @@ -0,0 +1,19 @@ +dn: dc=${LDAP_DOMAIN} +objectClass: top +objectClass: dcObject +objectClass: organization +o: ${LDAP_ORGANISATION} +dc: ${LDAP_DOMAIN} + +dn: ou=users,dc=${LDAP_DOMAIN} +objectClass: organizationalUnit +ou: users + +dn: cn=John Doe,ou=users,dc=${LDAP_DOMAIN} +objectClass: inetOrgPerson +cn: John Doe +sn: Doe +givenName: John +uid: jdoe +mail: jdoe@${LDAP_DOMAIN} +userPassword: ${LDAP_USER_PASSWORD} diff --git a/templates/hydra-config.yaml.tpl b/templates/hydra-config.yaml.tpl new file mode 100644 index 0000000..eb028ca --- /dev/null +++ b/templates/hydra-config.yaml.tpl @@ -0,0 +1,24 @@ +serve: + public: + port: 4444 + admin: + port: 4445 + +dsn: memory + +log: + level: debug + +oauth2: + expose_internal_errors: true + skip_consent_screen: false + +urls: + self: + base: ${HYDRA_ISSUER_URL}/ + login: + auth_url: http://127.0.0.1/login.php?login_challenge={{login_challenge}} + consent: + consent_url: http://127.0.0.1/consent.php?consent_challenge={{consent_challenge}} + post_logout_redirect: + to: http://127.0.0.1/logout.php diff --git a/templates/lam.conf.php.tpl b/templates/lam.conf.php.tpl new file mode 100644 index 0000000..12f27ae --- /dev/null +++ b/templates/lam.conf.php.tpl @@ -0,0 +1,20 @@ +db['type'] = "none"; // No SQL DB for LAM (read-only mode) +$config->auth_mode = "session"; // LAM’s own login screen (used for Hydra’s login) + +$config->ldap_host = "ldap://127.0.0.1"; +$config->ldap_port = 389; +$config->ldap_base = "dc=${LDAP_DOMAIN}"; +$config->ldap_bind_id = "${LAM_BIND_DN}"; +$config->ldap_bind_pass = "${LAM_BIND_PASSWORD}"; + +$config->language = "en"; +$config->realm = "IdentityManagement"; +?>