From 17be64549845a53f3954f0f2190c085affe7a13f Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Thu, 20 May 2021 09:29:57 +0100
Subject: [PATCH] Encrypt LDAP bind password in db with SECRET_KEY (#15547)

* Encrypt LDAP bind password in db with SECRET_KEY

The LDAP source bind password are currently stored in plaintext in the db
This PR simply encrypts them with the setting.SECRET_KEY.

Fix #15460

Signed-off-by: Andrew Thornton <art27@cantab.net>

* remove ui warning regarding unencrypted password

Co-authored-by: silverwind <me@silverwind.io>
---
 .../doc/features/authentication.en-us.md        |  4 ++--
 models/login_source.go                          | 17 ++++++++++++++++-
 modules/auth/ldap/ldap.go                       |  1 +
 options/locale/locale_en-US.ini                 |  1 -
 templates/admin/auth/edit.tmpl                  |  1 -
 templates/admin/auth/source/ldap.tmpl           |  1 -
 6 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/docs/content/doc/features/authentication.en-us.md b/docs/content/doc/features/authentication.en-us.md
index 0c83fa4d2f..223d7aa4fb 100644
--- a/docs/content/doc/features/authentication.en-us.md
+++ b/docs/content/doc/features/authentication.en-us.md
@@ -88,8 +88,8 @@ Adds the following fields:
 - Bind Password (optional)
 
   - The password for the Bind DN specified above, if any. _Note: The password
-    is stored in plaintext at the server. As such, ensure that the Bind DN
-    has as few privileges as possible._
+    is stored encrypted with the SECRET_KEY on the server. It is still recommended
+    to ensure that the Bind DN has as few privileges as possible._
 
 - User Search Base **(required)**
 
diff --git a/models/login_source.go b/models/login_source.go
index 57b1d56bb2..098b48a8cd 100644
--- a/models/login_source.go
+++ b/models/login_source.go
@@ -18,6 +18,7 @@ import (
 	"code.gitea.io/gitea/modules/auth/oauth2"
 	"code.gitea.io/gitea/modules/auth/pam"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/secret"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
@@ -77,11 +78,25 @@ type LDAPConfig struct {
 // FromDB fills up a LDAPConfig from serialized format.
 func (cfg *LDAPConfig) FromDB(bs []byte) error {
 	json := jsoniter.ConfigCompatibleWithStandardLibrary
-	return json.Unmarshal(bs, &cfg)
+	err := json.Unmarshal(bs, &cfg)
+	if err != nil {
+		return err
+	}
+	if cfg.BindPasswordEncrypt != "" {
+		cfg.BindPassword, err = secret.DecryptSecret(setting.SecretKey, cfg.BindPasswordEncrypt)
+		cfg.BindPasswordEncrypt = ""
+	}
+	return err
 }
 
 // ToDB exports a LDAPConfig to a serialized format.
 func (cfg *LDAPConfig) ToDB() ([]byte, error) {
+	var err error
+	cfg.BindPasswordEncrypt, err = secret.EncryptSecret(setting.SecretKey, cfg.BindPassword)
+	if err != nil {
+		return nil, err
+	}
+	cfg.BindPassword = ""
 	json := jsoniter.ConfigCompatibleWithStandardLibrary
 	return json.Marshal(cfg)
 }
diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go
index 6c557de018..91ad33a60f 100644
--- a/modules/auth/ldap/ldap.go
+++ b/modules/auth/ldap/ldap.go
@@ -35,6 +35,7 @@ type Source struct {
 	SecurityProtocol      SecurityProtocol
 	SkipVerify            bool
 	BindDN                string // DN to bind with
+	BindPasswordEncrypt   string // Encrypted Bind BN password
 	BindPassword          string // Bind DN password
 	UserBase              string // Base search path for users
 	UserDN                string // Template for the DN of the user for simple auth
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index ac1a0d9726..ab7367ba7a 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2283,7 +2283,6 @@ auths.host = Host
 auths.port = Port
 auths.bind_dn = Bind DN
 auths.bind_password = Bind Password
-auths.bind_password_helper = Warning: This password is stored in plain text. Use a read-only account if possible.
 auths.user_base = User Search Base
 auths.user_dn = User DN
 auths.attribute_username = Username Attribute
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index e4d7a2e1e1..d825cd7d12 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -53,7 +53,6 @@
 						<div class="field">
 							<label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label>
 							<input id="bind_password" name="bind_password" type="password" value="{{$cfg.BindPassword}}">
-							<p class="help text red">{{.i18n.Tr "admin.auths.bind_password_helper"}}</p>
 						</div>
 					{{end}}
 					<div class="{{if .Source.IsLDAP}}required{{end}} field">
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl
index 584538f53b..1cbcb2fd41 100644
--- a/templates/admin/auth/source/ldap.tmpl
+++ b/templates/admin/auth/source/ldap.tmpl
@@ -28,7 +28,6 @@
 	<div class="ldap field {{if not (eq .type 2)}}hide{{end}}">
 		<label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label>
 		<input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}">
-		<p class="help text red">{{.i18n.Tr "admin.auths.bind_password_helper"}}</p>
 	</div>
 	<div class="binddnrequired {{if (eq .type 2)}}required{{end}} field">
 		<label for="user_base">{{.i18n.Tr "admin.auths.user_base"}}</label>