add an example auth program that allow to authenticate against LDAP

External authentication is the way to go to authenticate against LDAP,
at least for now.

Closes #99
This commit is contained in:
Nicola Murino 2020-04-11 22:30:41 +02:00
parent 37357b2d63
commit b0ed190591
5 changed files with 209 additions and 1 deletions

View file

@ -47,4 +47,6 @@ else
fi
```
If you have an external authentication hook that could be useful for others too, please let us know and/or send a pull request.
An example authentication program that allow SFTPGo to authenticate against LDAP can be found inside the source tree [ldapauth](../examples/ldapauth) directory.
If you have an external authentication hook that could be useful to others too, please let us know and/or please send a pull request.

View file

@ -0,0 +1,48 @@
## LDAPAuth
This is an example for an external authentication program that performs authentication against an LDAP server.
It is tested against [389ds](https://directory.fedoraproject.org/) and can be used as starting point to authenticate using any LDAP server including Active Directory.
You need to change the LDAP connection parameters and the user search query to match your environment.
You can build this example using the following command:
```
go build -i -ldflags "-s -w" -o ldapauth
```
This program assumes that the 389ds schema was extended to add support for public keys using the following ldif file placed in `/etc/dirsrv/schema/98openssh-ldap.ldif`:
```
dn: cn=schema
changetype: modify
add: attributetypes
attributetypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' DESC 'MANDATORY: OpenSSH Public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
-
add: objectclasses
objectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY DESC 'MANDATORY: OpenSSH LPK objectclass' MUST ( uid ) MAY ( sshPublicKey ) )
-
dn: cn=sshpublickey,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config
changetype: add
cn: sshpublickey
nsIndexType: eq
nsIndexType: pres
nsSystemIndex: false
objectClass: top
objectClass: nsIndex
dn: cn=sshpublickey_self_manage,ou=groups,dc=example,dc=com
changetype: add
objectClass: top
objectClass: groupofuniquenames
cn: sshpublickey_self_manage
description: Members of this group gain the ability to edit their own sshPublicKey field
dn: dc=example,dc=com
changetype: modify
add: aci
aci: (targetattr = "sshPublicKey") (version 3.0; acl "Allow members of sshpublickey_self_manage to edit their keys"; allow(write) (groupdn = "ldap:///cn=sshpublickey_self_manage,ou=groups,dc=example,dc=com" and userdn="ldap:///self" ); )
-
```
Please feel free to send pull requests to improve this example authentication program, thanks!

10
examples/ldapauth/go.mod Normal file
View file

@ -0,0 +1,10 @@
module github.com/drakkan/ldapauth
go 1.14
require (
github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect
github.com/go-ldap/ldap/v3 v3.1.8
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
)

13
examples/ldapauth/go.sum Normal file
View file

@ -0,0 +1,13 @@
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.1.8 h1:5vU/2jOh9HqprwXp8aF915s9p6Z8wmbSEVF7/gdTFhM=
github.com/go-ldap/ldap/v3 v3.1.8/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

135
examples/ldapauth/main.go Normal file
View file

@ -0,0 +1,135 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"strconv"
"github.com/go-ldap/ldap/v3"
"golang.org/x/crypto/ssh"
)
const (
bindUsername = "cn=Directory Manager"
bindPassword = "YOUR_ADMIN_PASSWORD_HERE"
bindURL = "ldap://192.168.1.103:389"
)
type userFilters struct {
DeniedLoginMethods []string `json:"denied_login_methods,omitempty"`
}
type minimalSFTPGoUser struct {
Status int `json:"status,omitempty"`
Username string `json:"username"`
HomeDir string `json:"home_dir,omitempty"`
UID int `json:"uid,omitempty"`
GID int `json:"gid,omitempty"`
Permissions map[string][]string `json:"permissions"`
Filters userFilters `json:"filters"`
}
func exitError() {
u := minimalSFTPGoUser{
Username: "",
}
json, _ := json.Marshal(u)
fmt.Printf("%v\n", string(json))
os.Exit(1)
}
func printSuccessResponse(username, homeDir string, uid, gid int) {
u := minimalSFTPGoUser{
Username: username,
HomeDir: homeDir,
UID: uid,
GID: gid,
Status: 1,
}
u.Permissions = make(map[string][]string)
u.Permissions["/"] = []string{"*"}
// uncomment the next line to require publickey+password authentication
//u.Filters.DeniedLoginMethods = []string{"publickey", "password", "keyboard-interactive", "publickey+keyboard-interactive"}
json, _ := json.Marshal(u)
fmt.Printf("%v\n", string(json))
os.Exit(0)
}
func main() {
// get credentials from env vars
username := os.Getenv("SFTPGO_AUTHD_USERNAME")
password := os.Getenv("SFTPGO_AUTHD_PASSWORD")
publickey := os.Getenv("SFTPGO_AUTHD_PUBLIC_KEY")
l, err := ldap.DialURL(bindURL)
if err != nil {
exitError()
}
defer l.Close()
// bind to the ldap server with an account that can read users
err = l.Bind(bindUsername, bindPassword)
if err != nil {
exitError()
}
// search the user trying to login and fetch some attributes, this search string is tested against 389ds using the default configuration
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=nsPerson)(uid=%s))", username),
[]string{"dn", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
exitError()
}
// we expect exactly one user
if len(sr.Entries) != 1 {
exitError()
}
if len(publickey) > 0 {
// check public key
userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publickey))
if err != nil {
exitError()
}
authOk := false
for _, k := range sr.Entries[0].GetAttributeValues("nsSshPublicKey") {
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
// we skip an invalid public key stored inside the LDAP server
if err != nil {
continue
}
if bytes.Equal(key.Marshal(), userKey.Marshal()) {
authOk = true
break
}
}
if !authOk {
exitError()
}
} else {
// bind to the LDAP server with the user dn and the given password to check the password
userdn := sr.Entries[0].DN
err = l.Bind(userdn, password)
if err != nil {
exitError()
}
}
uid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("uidNumber"))
if err != nil {
exitError()
}
gid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("gidNumber"))
if err != nil {
exitError()
}
// return the authenticated user
printSuccessResponse(username, sr.Entries[0].GetAttributeValue("homeDirectory"), uid, gid)
}