From 00c4aea7c4f25e08eb10cfdb18420364a83f4472 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Tue, 19 Nov 2024 07:26:29 +0100 Subject: [PATCH] Introduce basic LDAP authentication. --- .gitignore | 2 + config.local.yml | 131 ++++++++++++++++++++++++++++++ config.yml | 12 +++ docs/configuration/admin-panel.md | 2 + docs/configuration/cheat-sheet.md | 5 ++ go.mod | 3 + go.sum | 78 +++++++++++++++++- internal/config/config.go | 8 ++ internal/db/admin_setting.go | 1 + internal/db/db.go | 1 + internal/i18n/locales/en-US.yml | 2 + internal/ldap/ldap.go | 60 ++++++++++++++ internal/web/auth.go | 35 ++++++++ templates/pages/admin_config.html | 24 ++++++ 14 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 config.local.yml create mode 100644 internal/ldap/ldap.go diff --git a/.gitignore b/.gitignore index 762d863..6a0189b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules/ gist.db .idea/ +.vscode/ .DS_Store /**/.DS_Store public/assets/* @@ -9,3 +10,4 @@ opengist build/ docs/.vitepress/dist/ docs/.vitepress/cache/ +vendor/ diff --git a/config.local.yml b/config.local.yml new file mode 100644 index 0000000..74313fd --- /dev/null +++ b/config.local.yml @@ -0,0 +1,131 @@ +# Learn more about Opengist configuration here: +# https://github.com/thomiceli/opengist/blob/master/docs/configuration/configure.md +# https://github.com/thomiceli/opengist/blob/master/docs/configuration/cheat-sheet.md + +# Set the log level to one of the following: debug, info, warn, error, fatal. Default: warn +log-level: info + +# Set the log output to one or more of the following: `stdout`, `file`. Default: stdout,file +log-output: stdout,file + +# Public URL to access to Opengist +external-url: + +# Directory where Opengist will store its data. Default: ~/.opengist/ +opengist-home: + +# Secret key used for session store & encrypt MFA data on database. Default: +secret-key: + +# URI of the database. Default: opengist.db (SQLite) +# SQLite: file name +# PostgreSQL: postgres://user:password@host:port/database +# MySQL/MariaDB: mysql://user:password@host:port/database +db-uri: opengist.db + +# Enable or disable the code search index (either `true` or `false`). Default: true +index.enabled: true + +# Name of the directory where the code search index is stored. Default: opengist.index +index.dirname: opengist.index + +# Default branch name used by Opengist when initializing Git repositories. +# If not set, uses the Git default branch name. See https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch +git.default-branch: + +# Set the journal mode for SQLite. Default: WAL +# See https://www.sqlite.org/pragma.html#pragma_journal_mode +# For SQLite databases only. +sqlite.journal-mode: WAL + + +# HTTP server configuration +# Host to bind to. Default: 0.0.0.0 +http.host: 0.0.0.0 + +# Port to bind to. Default: 6157 +http.port: 6157 + +# Enable or disable git operations (clone, pull, push) via HTTP (either `true` or `false`). Default: true +http.git-enabled: true + +# SSH built-in server configuration +# Note: it is not using the SSH daemon from your machine (yet) + +# Enable or disable SSH built-in server +# for git operations (clone, pull, push) via SSH (either `true` or `false`). Default: true +ssh.git-enabled: true + +# Host to bind to. Default: 0.0.0.0 +ssh.host: 0.0.0.0 + +# Port to bind to. Default: 2222 +# Note: it cannot be the same port as the SSH daemon if it's currently running +# If you want to use the port 22 for the built-in SSH server, +# you can either change the port of the SSH daemon or stop it +ssh.port: 2222 + +# Public domain for the Git SSH connection, if it has to be different from the HTTP one. +# If not set, uses the URL from the request +ssh.external-domain: + +# Path or alias to ssh-keygen executable. Default: ssh-keygen +ssh.keygen-executable: ssh-keygen + +# OAuth2 configuration +# The callback/redirect URL must be http://opengist.url/oauth//callback + +# To create a new OAuth2 application using GitHub : https://github.com/settings/applications/new +github.client-key: +github.secret: + +# To create a new OAuth2 application using Gitlab : https://gitlab.com/-/user_settings/applications +gitlab.client-key: +gitlab.secret: +# URL of the Gitlab instance. Default: https://gitlab.com/ +gitlab.url: https://gitlab.com/ +# The name of the GitLab instance. It is displayed in the OAuth login button. Default: GitLab +gitlab.name: GitLab + +# To create a new OAuth2 application using Gitea : https://gitea.domain/user/settings/applications +gitea.client-key: +gitea.secret: +# URL of the Gitea instance. Default: https://gitea.com/ +gitea.url: https://gitea.com/ +# The name of the Gitea instance. It is displayed in the OAuth login button. Default: Gitea +gitea.name: Gitea + +# To create a new OAuth2 application using OpenID Connect: +oidc.client-key: "opengist" +oidc.secret: "WrtxMEoXCoE4M.52WQ4ea_Z0A6zISELu8zU4iN_4T3BEdLGHkc23mSqhr0R7a6-svSjgyC4l" +# Discovery endpoint of the OpenID provider. Generally something like http://auth.example.com/.well-known/openid-configuration +oidc.discovery-url: "https://auth.planet-express.in/.well-known/openid-configuration" + +# LDAP authentication configuration +# URL of the LDAP instance e.g: ldap://ldap.example.com:389 +ldap.url: "ldap://localhost:8888" +# Bind DN to authenticate against the LDAP e.g: cn=read-only-admin,dc=example,dc=com +ldap.bind-dn: "cn=ReadOnlyBind,ou=Applications,dc=planet-express,dc=in" +# The password for the Bind DN. +ldap.bind-credentials: "qdfpvQJ7xfUl7hzJxVgd" +# The Base DN to start search from e.g: dc=example,dc=com +ldap.search-base: "ou=people,dc=planet-express,dc=in" +# The filter to search against (the format string %s will be replaced with the username) e.g: (uid=%s) +ldap.search-filter: "(uid=%s)" + +# Instance name +# Set your own custom name to be displayed instead of 'Opengist' +custom.name: + +# Custom assets +# Add your own custom assets, that are files relatives to $opengist-home/custom/ +custom.logo: +custom.favicon: + +# Static pages in footer (like legal notices, privacy policy, etc.) +# The path can be a URL or a relative path to a file in the $opengist-home/custom/ directory +custom.static-links: +# - name: Gitea +# path: https://gitea.com +# - name: Legal notices +# path: legal.html diff --git a/config.yml b/config.yml index e9b10d7..1ba4591 100644 --- a/config.yml +++ b/config.yml @@ -100,6 +100,18 @@ oidc.secret: # Discovery endpoint of the OpenID provider. Generally something like http://auth.example.com/.well-known/openid-configuration oidc.discovery-url: +# LDAP authentication configuration +# URL of the LDAP instance e.g: ldap://ldap.example.com:389 +ldap.url: +# Bind DN to authenticate against the LDAP e.g: cn=read-only-admin,dc=example,dc=com +ldap.bind-dn: +# The password for the Bind DN. +ldap.bind-credentials: +# The Base DN to start search from e.g: dc=example,dc=com +ldap.search-base: +# The filter to search against (the format string %s will be replaced with the username) e.g: (uid=%s) +ldap.search-filter: + # Instance name # Set your own custom name to be displayed instead of 'Opengist' custom.name: diff --git a/docs/configuration/admin-panel.md b/docs/configuration/admin-panel.md index 42a0731..41603ee 100644 --- a/docs/configuration/admin-panel.md +++ b/docs/configuration/admin-panel.md @@ -45,6 +45,8 @@ Here you can change a limited number of settings without restarting the instance - Forbid the creation of new accounts. - Require login - Enforce users to be logged in to see gists. +- Enable LDAP + - Allow users to use LDAP authentication. If LDAP authentication fails, it will try local authentication. - Allow individual gists without login - Allow individual gists to be viewed and downloaded without login, while requiring login for discovering gists. - Disable login form diff --git a/docs/configuration/cheat-sheet.md b/docs/configuration/cheat-sheet.md index a80e77c..2d4df6d 100644 --- a/docs/configuration/cheat-sheet.md +++ b/docs/configuration/cheat-sheet.md @@ -37,6 +37,11 @@ aside: false | oidc.client-key | OG_OIDC_CLIENT_KEY | none | The client key for the OpenID application. | | oidc.secret | OG_OIDC_SECRET | none | The secret for the OpenID application. | | oidc.discovery-url | OG_OIDC_DISCOVERY_URL | none | Discovery endpoint of the OpenID provider. | +| ldap.url | OG_LDAP_URL | `ldap://0.0.0.0:389` | URL of the LDAP instance. | +| ldap.bind-dn | OG_LDAP_BIND_DN | none | Bind DN to authenticate against the LDAP. | +| ldap.bind-credentials | OG_LDAP_BIND_CREDENTIALS | none | The password for the Bind DN. | +| ldap.search-base | OG_LDAP_SEARCH_BASE | none | The Base DN to start search from. | +| ldap.search-filter | OG_LDAP_SEARCH_FILTER | none | The filter to search against (the format string %s will be replaced with the username). | | custom.name | OG_CUSTOM_NAME | none | The name of your instance, to be displayed in the tab title | | custom.logo | OG_CUSTOM_LOGO | none | Path to an image, relative to $opengist-home/custom. | | custom.favicon | OG_CUSTOM_FAVICON | none | Path to an image, relative to $opengist-home/custom. | diff --git a/go.mod b/go.mod index d2616b3..984b359 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/blevesearch/bleve/v2 v2.4.3 github.com/dustin/go-humanize v1.0.1 github.com/glebarez/sqlite v1.11.0 + github.com/go-ldap/ldap/v3 v3.4.8 github.com/go-playground/validator/v10 v10.23.0 github.com/go-webauthn/webauthn v0.11.2 github.com/google/uuid v1.6.0 @@ -34,6 +35,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/RoaringBitmap/roaring v1.9.4 // indirect github.com/bits-and-blooms/bitset v1.17.0 // indirect github.com/blevesearch/bleve_index_api v1.1.13 // indirect @@ -60,6 +62,7 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect diff --git a/go.sum b/go.sum index 4486e60..31da8ef 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ= @@ -12,6 +14,8 @@ github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4E github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI= github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= @@ -81,6 +85,10 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= +github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -126,8 +134,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -135,8 +145,10 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= -github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= @@ -151,6 +163,18 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -210,8 +234,14 @@ github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= @@ -224,6 +254,7 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= @@ -236,37 +267,82 @@ go.abhg.dev/goldmark/mermaid v0.5.0 h1:mDkykpSPJ+5wCQ8bSXgzJ2KQskjXkI5Ndxz7JYDHW go.abhg.dev/goldmark/mermaid v0.5.0/go.mod h1:OCyk2o85TX2drWHH+HRy6bih2yZlUwbbv/R1MMh1YLs= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go index 6093149..80642c9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -71,6 +71,12 @@ type config struct { OIDCSecret string `yaml:"oidc.secret" env:"OG_OIDC_SECRET"` OIDCDiscoveryUrl string `yaml:"oidc.discovery-url" env:"OG_OIDC_DISCOVERY_URL"` + LDAPUrl string `yaml:"ldap.url" env:"OG_LDAP_URL"` + LDAPBindDn string `yaml:"ldap.bind-dn" env:"OG_LDAP_BIND_DN"` + LDAPBindCredentials string `yaml:"ldap.bind-credentials" env:"OG_LDAP_BIND_CREDENTIALS"` + LDAPSearchBase string `yaml:"ldap.search-base" env:"OG_LDAP_SEARCH_BASE"` + LDAPSearchFilter string `yaml:"ldap.search-filter" env:"OG_LDAP_SEARCH_FILTER"` + CustomName string `yaml:"custom.name" env:"OG_CUSTOM_NAME"` CustomLogo string `yaml:"custom.logo" env:"OG_CUSTOM_LOGO"` CustomFavicon string `yaml:"custom.favicon" env:"OG_CUSTOM_FAVICON"` @@ -105,6 +111,8 @@ func configWithDefaults() (*config, error) { c.SshPort = "2222" c.SshKeygen = "ssh-keygen" + c.LDAPUrl = "ldap://0.0.0.0:389" + c.GitlabName = "GitLab" c.GiteaUrl = "https://gitea.com" diff --git a/internal/db/admin_setting.go b/internal/db/admin_setting.go index d8586ee..24c6618 100644 --- a/internal/db/admin_setting.go +++ b/internal/db/admin_setting.go @@ -12,6 +12,7 @@ type AdminSetting struct { const ( SettingDisableSignup = "disable-signup" SettingRequireLogin = "require-login" + SettingEnableLDAP = "enable-ldap" SettingAllowGistsWithoutLogin = "allow-gists-without-login" SettingDisableLoginForm = "disable-login-form" SettingDisableGravatar = "disable-gravatar" diff --git a/internal/db/db.go b/internal/db/db.go index 3b5c027..1e7b7b9 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -150,6 +150,7 @@ func Setup(dbUri string, sharedCache bool) error { return initAdminSettings(map[string]string{ SettingDisableSignup: "0", SettingRequireLogin: "0", + SettingEnableLDAP: "0", SettingAllowGistsWithoutLogin: "0", SettingDisableLoginForm: "0", SettingDisableGravatar: "0", diff --git a/internal/i18n/locales/en-US.yml b/internal/i18n/locales/en-US.yml index 144cc7d..62e8b20 100644 --- a/internal/i18n/locales/en-US.yml +++ b/internal/i18n/locales/en-US.yml @@ -239,6 +239,8 @@ admin.disable-signup: Disable signup admin.disable-signup_help: Forbid the creation of new accounts. admin.require-login: Require login admin.require-login_help: Enforce users to be logged in to see gists. +admin.enable-ldap: Enable LDAP +admin.enable-ldap_help: Allow users to use LDAP authentication. If LDAP authentication fails, it will try local authentication. admin.allow-gists-without-login: Allow individual gists without login admin.allow-gists-without-login_help: Allow individual gists to be viewed and downloaded without login, while requiring login for discovering gists. admin.disable-login: Disable login form diff --git a/internal/ldap/ldap.go b/internal/ldap/ldap.go new file mode 100644 index 0000000..09db17b --- /dev/null +++ b/internal/ldap/ldap.go @@ -0,0 +1,60 @@ +package ldap + +import ( + "fmt" + "github.com/go-ldap/ldap/v3" + "github.com/thomiceli/opengist/internal/config" +) + +// Authenticate attempts to authenticate a user against the configured LDAP instance. +func Authenticate(username, password string) (bool, error) { + l, err := ldap.DialURL(config.C.LDAPUrl) + if err != nil { + return false, fmt.Errorf("unable to connect to URI: %v", config.C.LDAPUrl) + } + defer func(l *ldap.Conn) { + _ = l.Close() + }(l) + + // First bind with a read only user + err = l.Bind(config.C.LDAPBindDn, config.C.LDAPBindCredentials) + if err != nil { + return false, err + } + + searchFilter := fmt.Sprintf(config.C.LDAPSearchFilter, username) + searchRequest := ldap.NewSearchRequest( + config.C.LDAPSearchBase, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + searchFilter, + []string{"dn"}, + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + return false, err + } + + if len(sr.Entries) != 1 { + return false, nil + } + + // Bind as the user to verify their password + err = l.Bind(sr.Entries[0].DN, password) + if err != nil { + return false, nil + } + + // Rebind as the read only user for any further queries + err = l.Bind(config.C.LDAPBindDn, config.C.LDAPBindCredentials) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/internal/web/auth.go b/internal/web/auth.go index 39d3d28..d26bd08 100644 --- a/internal/web/auth.go +++ b/internal/web/auth.go @@ -20,6 +20,7 @@ import ( "github.com/thomiceli/opengist/internal/config" "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/i18n" + "github.com/thomiceli/opengist/internal/ldap" "github.com/thomiceli/opengist/internal/utils" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -140,6 +141,8 @@ func processLogin(ctx echo.Context) error { return errorRes(403, tr(ctx, "error.login-disabled-form"), nil) } + enableLDAP := getData(ctx, "EnableLdap") + var err error sess := getSession(ctx) @@ -160,6 +163,38 @@ func processLogin(ctx echo.Context) error { return redirect(ctx, "/login") } + if enableLDAP == true { + ok, err := ldap.Authenticate(user.Username, password) + if err != nil { + log.Warn().Msgf("Cannot check for LDAP password: %v", err) + log.Info().Msg("LDAP authentication failed for user: " + user.Username) + } + + if !ok { + log.Warn().Msg("Invalid LDAP authentication attempt from " + ctx.RealIP()) + log.Info().Msg("LDAP authentication failed for user: " + user.Username) + } + + if ok { + hashedPassword, err := utils.Argon2id.Hash(password) + if err != nil { + log.Info().Msg("LDAP authentication failed for user: " + user.Username) + return errorRes(500, "Cannot hash password for user", err) + } + + user.Password = hashedPassword + + err = user.Update() + if err != nil { + log.Info().Msg("LDAP authentication failed for " + user.Username) + return errorRes(500, "Cannot update LDAP user "+user.Username, err) + } + + log.Info().Msg("Synced local password from LDAP for user: " + user.Username) + log.Info().Msg("LDAP authentication succeeded for user: " + user.Username) + } + } + if ok, err := utils.Argon2id.Verify(password, user.Password); !ok { if err != nil { return errorRes(500, "Cannot check for password", err) diff --git a/templates/pages/admin_config.html b/templates/pages/admin_config.html index c6f8b40..607d365 100644 --- a/templates/pages/admin_config.html +++ b/templates/pages/admin_config.html @@ -67,6 +67,19 @@
OIDC client Key
{{ if .c.OIDCClientKey }}<defined>{{ end }}
OIDC Secret
{{ if .c.OIDCSecret }}<defined>{{ end }}
OIDC Discovery URL
{{ if .c.OIDCDiscoveryUrl }}<defined>{{ end }}
+
+ +
+ LDAP +
+
+
LDAP URL
{{ .c.LDAPUrl }}
+
LDAP Bind DN
{{ .c.LDAPBindDn }}
+
LDAP Bind Credentials
{{ if .c.LDAPBindCredentials }}<defined>{{ end }}
+
LDAP Search Base
{{ .c.LDAPSearchBase }}
+
LDAP Search Filter
{{ .c.LDAPSearchFilter }}
@@ -93,6 +106,17 @@
+
  • +
    + + {{ .locale.Tr "admin.enable-ldap" }} + {{ .locale.Tr "admin.enable-ldap_help" }} + + +
    +