2020-01-19 11:49:49 +00:00
package main
import (
2020-01-19 12:00:53 +00:00
"encoding/base64"
"encoding/json"
2020-01-19 11:49:49 +00:00
"fmt"
"log"
2020-01-19 12:00:53 +00:00
"math/rand"
2020-01-19 11:49:49 +00:00
"os"
"os/signal"
"strings"
2020-01-19 12:00:53 +00:00
"syscall"
2020-01-19 11:49:49 +00:00
2020-01-19 12:00:53 +00:00
ldap "./ldapserver"
2020-01-19 11:49:49 +00:00
consul "github.com/hashicorp/consul/api"
message "github.com/vjeantet/goldap/message"
)
func dnToConsul ( dn string ) string {
rdns := strings . Split ( dn , "," )
// Reverse rdns
for i , j := 0 , len ( rdns ) - 1 ; i < j ; i , j = i + 1 , j - 1 {
rdns [ i ] , rdns [ j ] = rdns [ j ] , rdns [ i ]
}
return strings . Join ( rdns , "/" )
}
2020-01-19 16:55:25 +00:00
func consulToDN ( pair * consul . KVPair ) ( string , string , [ ] byte ) {
path := strings . Split ( pair . Key , "/" )
dn := ""
for _ , cpath := range path {
if cpath == "" {
continue
}
kv := strings . Split ( cpath , "=" )
if len ( kv ) == 2 && kv [ 0 ] == "attribute" {
return dn , kv [ 1 ] , pair . Value
}
if dn != "" {
dn = "," + dn
}
dn = cpath + dn
}
return dn , "" , nil
}
2020-01-19 18:10:38 +00:00
func parseValue ( value [ ] byte ) ( [ ] string , error ) {
val := [ ] string { }
err := json . Unmarshal ( value , & val )
if err == nil {
return val , nil
}
val2 := ""
err = json . Unmarshal ( value , & val2 )
if err == nil {
return [ ] string { val2 } , nil
}
return nil , fmt . Errorf ( "Not a string or list of strings: %s" , value )
}
2020-01-19 16:55:25 +00:00
func parseConsulResult ( data [ ] * consul . KVPair ) ( map [ string ] Entry , error ) {
aggregator := map [ string ] Entry { }
for _ , kv := range data {
2020-01-19 18:10:38 +00:00
log . Printf ( "(parseConsulResult) %s %s" , kv . Key , string ( kv . Value ) )
2020-01-19 16:55:25 +00:00
dn , attr , val := consulToDN ( kv )
if attr == "" || val == nil {
continue
}
if _ , exists := aggregator [ dn ] ; ! exists {
aggregator [ dn ] = Entry { }
}
2020-01-19 18:10:38 +00:00
value , err := parseValue ( val )
2020-01-19 16:55:25 +00:00
if err != nil {
return nil , err
}
2020-01-19 17:24:21 +00:00
aggregator [ dn ] [ attr ] = value
2020-01-19 16:55:25 +00:00
}
return aggregator , nil
}
2020-01-19 11:49:49 +00:00
type DNComponent struct {
2020-01-19 12:00:53 +00:00
Type string
2020-01-19 11:49:49 +00:00
Value string
}
func parseDN ( dn string ) ( [ ] DNComponent , error ) {
rdns := strings . Split ( dn , "," )
ret := [ ] DNComponent { }
for _ , rdn := range rdns {
splits := strings . Split ( rdn , "=" )
if len ( splits ) != 2 {
return nil , fmt . Errorf ( "Wrong DN component: %s (expected type=value)" , rdn )
}
ret = append ( ret , DNComponent {
2020-01-19 12:00:53 +00:00
Type : splits [ 0 ] ,
2020-01-19 11:49:49 +00:00
Value : splits [ 1 ] ,
} )
}
return ret , nil
}
type Config struct {
Suffix string
}
type Server struct {
config Config
2020-01-19 12:00:53 +00:00
kv * consul . KV
}
type State struct {
bindDn string
2020-01-19 11:49:49 +00:00
}
2020-01-19 17:24:21 +00:00
type Entry map [ string ] [ ] string
2020-01-19 11:49:49 +00:00
func main ( ) {
//ldap logger
ldap . Logger = log . New ( os . Stdout , "[server] " , log . LstdFlags )
// Connect to Consul
client , err := consul . NewClient ( consul . DefaultConfig ( ) )
if err != nil {
panic ( err )
}
kv := client . KV ( )
// TODO read config from somewhere
2020-01-19 12:00:53 +00:00
config := Config {
2020-01-19 11:49:49 +00:00
Suffix : "dc=gobottin,dc=eu" ,
}
gobottin := Server { config : config , kv : kv }
err = gobottin . init ( )
if err != nil {
panic ( err )
}
//Create a new LDAP Server
ldapserver := ldap . NewServer ( )
2020-01-19 12:00:53 +00:00
ldapserver . NewUserState = func ( ) ldap . UserState {
return & State { }
}
2020-01-19 11:49:49 +00:00
routes := ldap . NewRouteMux ( )
routes . Bind ( gobottin . handleBind )
2020-01-19 16:55:25 +00:00
routes . Search ( gobottin . handleSearch )
2020-01-19 17:24:21 +00:00
routes . Add ( gobottin . handleAdd )
2020-01-19 11:49:49 +00:00
ldapserver . Handle ( routes )
// listen on 10389
go ldapserver . ListenAndServe ( "127.0.0.1:10389" )
// When CTRL+C, SIGINT and SIGTERM signal occurs
// Then stop server gracefully
ch := make ( chan os . Signal )
signal . Notify ( ch , syscall . SIGINT , syscall . SIGTERM )
<- ch
close ( ch )
ldapserver . Stop ( )
}
func ( server * Server ) init ( ) error {
2020-01-19 12:00:53 +00:00
pair , _ , err := server . kv . Get ( dnToConsul ( server . config . Suffix ) + "/attribute=objectClass" , nil )
2020-01-19 11:49:49 +00:00
if err != nil {
return err
}
if pair != nil {
return nil
}
2020-01-19 16:55:25 +00:00
base_attributes := Entry {
2020-01-19 12:00:53 +00:00
"objectClass" : [ ] string { "top" , "dcObject" , "organization" } ,
2020-01-19 17:24:21 +00:00
"structuralObjectClass" : [ ] string { "Organization" } ,
2020-01-19 11:49:49 +00:00
}
suffix_dn , err := parseDN ( server . config . Suffix )
if err != nil {
return err
}
2020-01-19 17:24:21 +00:00
base_attributes [ suffix_dn [ 0 ] . Type ] = [ ] string { suffix_dn [ 0 ] . Value }
2020-01-19 11:49:49 +00:00
err = server . addElements ( server . config . Suffix , base_attributes )
if err != nil {
return err
}
admin_pass := make ( [ ] byte , 8 )
rand . Read ( admin_pass )
admin_pass_str := base64 . RawURLEncoding . EncodeToString ( admin_pass )
admin_pass_hash := SSHAEncode ( [ ] byte ( admin_pass_str ) )
admin_dn := "cn=admin," + server . config . Suffix
2020-01-19 16:55:25 +00:00
admin_attributes := Entry {
2020-01-19 12:00:53 +00:00
"objectClass" : [ ] string { "simpleSecurityObject" , "organizationalRole" } ,
2020-01-19 17:24:21 +00:00
"description" : [ ] string { "LDAP administrator" } ,
"cn" : [ ] string { "admin" } ,
"userpassword" : [ ] string { admin_pass_hash } ,
"structuralObjectClass" : [ ] string { "organizationalRole" } ,
2020-01-19 12:00:53 +00:00
"permissions" : [ ] string { "read" , "write" } ,
2020-01-19 11:49:49 +00:00
}
err = server . addElements ( admin_dn , admin_attributes )
if err != nil {
return err
}
log . Printf (
"It seems to be a new installation, we created a default user for you:\n\n dn: %s\n password: %s\n\nWe didn't use true random, you should replace it as soon as possible." ,
admin_dn ,
admin_pass_str ,
)
return nil
}
2020-01-19 16:55:25 +00:00
func ( server * Server ) addElements ( dn string , attrs Entry ) error {
2020-01-19 11:49:49 +00:00
prefix := dnToConsul ( dn )
for k , v := range attrs {
json , err := json . Marshal ( v )
if err != nil {
return err
}
pair := & consul . KVPair { Key : prefix + "/attribute=" + k , Value : json }
_ , err = server . kv . Put ( pair , nil )
if err != nil {
return err
}
}
return nil
}
2020-01-19 18:10:38 +00:00
func ( server * Server ) getAttribute ( dn string , attr string ) ( [ ] string , error ) {
pair , _ , err := server . kv . Get ( dnToConsul ( dn ) + "/attribute=" + attr , nil )
if err != nil {
return nil , err
}
if pair == nil {
return nil , nil
}
return parseValue ( pair . Value )
}
func ( server * Server ) objectExists ( dn string ) ( bool , error ) {
prefix := dnToConsul ( dn ) + "/"
data , _ , err := server . kv . List ( prefix , nil )
if err != nil {
return false , err
}
return len ( data ) > 0 , nil
}
2020-01-19 12:00:53 +00:00
func ( server * Server ) handleBind ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
2020-01-19 11:49:49 +00:00
r := m . GetBindRequest ( )
2020-01-19 17:24:21 +00:00
result_code , err := server . handleBindInternal ( state , & r )
2020-01-19 11:49:49 +00:00
res := ldap . NewBindResponse ( result_code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
log . Printf ( "Failed bind for %s: %s" , string ( r . Name ( ) ) , err . Error ( ) )
}
w . Write ( res )
}
2020-01-19 17:24:21 +00:00
func ( server * Server ) handleBindInternal ( state * State , r * message . BindRequest ) ( int , error ) {
2020-01-19 11:49:49 +00:00
2020-01-19 18:10:38 +00:00
passwd , err := server . getAttribute ( string ( r . Name ( ) ) , "userpassword" )
2020-01-19 11:49:49 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if passwd == nil {
2020-01-19 11:49:49 +00:00
return ldap . LDAPResultNoSuchObject , nil
}
2020-01-19 18:10:38 +00:00
for _ , hash := range passwd {
valid := SSHAMatches ( hash , [ ] byte ( r . AuthenticationSimple ( ) ) )
if valid {
state . bindDn = string ( r . Name ( ) )
return ldap . LDAPResultSuccess , nil
}
2020-01-19 11:49:49 +00:00
}
2020-01-19 18:10:38 +00:00
return ldap . LDAPResultInvalidCredentials , nil
2020-01-19 11:49:49 +00:00
}
2020-01-19 16:55:25 +00:00
func ( server * Server ) handleSearch ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetSearchRequest ( )
code , err := server . handleSearchInternal ( state , w , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . SearchResultDone ( res ) )
}
func ( server * Server ) handleSearchInternal ( state * State , w ldap . ResponseWriter , r * message . SearchRequest ) ( int , error ) {
log . Printf ( "-- SEARCH REQUEST: --" )
log . Printf ( "Request BaseDn=%s" , r . BaseObject ( ) )
log . Printf ( "Request Filter=%s" , r . Filter ( ) )
log . Printf ( "Request FilterString=%s" , r . FilterString ( ) )
log . Printf ( "Request Attributes=%s" , r . Attributes ( ) )
log . Printf ( "Request TimeLimit=%d" , r . TimeLimit ( ) . Int ( ) )
// TODO check authorizations
2020-01-19 18:10:38 +00:00
baseObject := dnToConsul ( string ( r . BaseObject ( ) ) )
minimalBaseObject := dnToConsul ( server . config . Suffix )
if len ( baseObject ) <= len ( minimalBaseObject ) {
if baseObject != minimalBaseObject [ : len ( baseObject ) ] {
return ldap . LDAPResultInvalidDNSyntax , fmt . Errorf (
"Only handling search results under DN=%s" ,
server . config . Suffix )
}
baseObject = minimalBaseObject
} else {
if baseObject [ : len ( minimalBaseObject ) ] != minimalBaseObject {
return ldap . LDAPResultInvalidDNSyntax , fmt . Errorf (
"Only handling search results under DN=%s" ,
server . config . Suffix )
}
}
2020-01-19 16:55:25 +00:00
2020-01-19 18:10:38 +00:00
data , _ , err := server . kv . List ( baseObject + "/" , nil )
2020-01-19 16:55:25 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
entries , err := parseConsulResult ( data )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
log . Printf ( "in %s: %#v" , baseObject + "/" , data )
2020-01-19 16:55:25 +00:00
log . Printf ( "%#v" , entries )
for dn , entry := range entries {
// TODO filter out if no permission to read this
matched , err := applyFilter ( entry , r . Filter ( ) )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! matched {
continue
}
e := ldap . NewSearchResultEntry ( dn )
for attr , val := range entry {
// If attribute is not in request, exclude it from returned entry
if len ( r . Attributes ( ) ) > 0 {
found := false
2020-01-19 17:04:42 +00:00
for _ , requested := range r . Attributes ( ) {
if strings . EqualFold ( string ( requested ) , attr ) {
2020-01-19 16:55:25 +00:00
found = true
break
}
}
if ! found {
continue
}
}
// Send result
2020-01-19 17:24:21 +00:00
for _ , v := range val {
2020-01-19 16:55:25 +00:00
e . AddAttribute ( message . AttributeDescription ( attr ) ,
2020-01-19 17:24:21 +00:00
message . AttributeValue ( v ) )
2020-01-19 16:55:25 +00:00
}
}
w . Write ( e )
}
return ldap . LDAPResultSuccess , nil
}
func applyFilter ( entry Entry , filter message . Filter ) ( bool , error ) {
if fAnd , ok := filter . ( message . FilterAnd ) ; ok {
for _ , cond := range fAnd {
res , err := applyFilter ( entry , cond )
if err != nil {
return false , err
}
if ! res {
return false , nil
}
}
return true , nil
} else if fOr , ok := filter . ( message . FilterOr ) ; ok {
for _ , cond := range fOr {
res , err := applyFilter ( entry , cond )
if err != nil {
return false , err
}
if res {
return true , nil
}
}
return false , nil
} else if fNot , ok := filter . ( message . FilterNot ) ; ok {
res , err := applyFilter ( entry , fNot . Filter )
if err != nil {
return false , err
}
return ! res , nil
} else if fPresent , ok := filter . ( message . FilterPresent ) ; ok {
what := string ( fPresent )
2020-01-19 17:04:42 +00:00
// Case insensitive search
for desc := range entry {
if strings . EqualFold ( what , desc ) {
return true , nil
}
2020-01-19 16:55:25 +00:00
}
return false , nil
} else if fEquality , ok := filter . ( message . FilterEqualityMatch ) ; ok {
desc := string ( fEquality . AttributeDesc ( ) )
target := string ( fEquality . AssertionValue ( ) )
2020-01-19 17:04:42 +00:00
// Case insensitive attribute search
for entry_desc , value := range entry {
if strings . EqualFold ( entry_desc , desc ) {
2020-01-19 17:24:21 +00:00
for _ , val := range value {
if val == target {
return true , nil
2020-01-19 16:55:25 +00:00
}
}
2020-01-19 17:24:21 +00:00
return false , nil
2020-01-19 16:55:25 +00:00
}
}
2020-01-19 17:04:42 +00:00
return false , nil
2020-01-19 16:55:25 +00:00
} else {
return false , fmt . Errorf ( "Unsupported filter: %#v %T" , filter , filter )
}
}
2020-01-19 17:24:21 +00:00
func ( server * Server ) handleAdd ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetAddRequest ( )
code , err := server . handleAddInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . AddResponse ( res ) )
}
func ( server * Server ) handleAddInternal ( state * State , r * message . AddRequest ) ( int , error ) {
dn := string ( r . Entry ( ) )
2020-01-19 18:10:38 +00:00
exists , err := server . objectExists ( dn )
2020-01-19 17:24:21 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if exists {
2020-01-19 17:24:21 +00:00
return ldap . LDAPResultEntryAlreadyExists , nil
}
// TODO check permissions
2020-01-19 18:10:38 +00:00
var members [ ] string = nil
2020-01-19 17:24:21 +00:00
entry := Entry { }
for _ , attribute := range r . Attributes ( ) {
key := string ( attribute . Type_ ( ) )
2020-01-19 18:10:38 +00:00
if strings . EqualFold ( key , "memberOf" ) {
return ldap . LDAPResultObjectClassViolation , fmt . Errorf (
"memberOf cannot be defined directly, membership must be specified in the group itself" )
}
2020-01-19 17:24:21 +00:00
vals_str := [ ] string { }
for _ , val := range attribute . Vals ( ) {
vals_str = append ( vals_str , string ( val ) )
}
2020-01-19 18:10:38 +00:00
if strings . EqualFold ( key , "member" ) {
members = vals_str
for _ , member := range members {
exists , err = server . objectExists ( member )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! exists {
return ldap . LDAPResultNoSuchObject , fmt . Errorf (
"Cannot add %s to members, it does not exist!" ,
member )
}
}
}
2020-01-19 17:24:21 +00:00
entry [ key ] = vals_str
}
err = server . addElements ( dn , entry )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if members != nil {
for _ , member := range members {
memberGroups , err := server . getAttribute ( member , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if memberGroups == nil {
memberGroups = [ ] string { }
}
memberGroups = append ( memberGroups , dn )
err = server . addElements ( member , Entry {
"memberOf" : memberGroups ,
} )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
}
}
2020-01-19 17:24:21 +00:00
return ldap . LDAPResultSuccess , nil
}