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
}
func parseConsulResult ( data [ ] * consul . KVPair ) ( map [ string ] Entry , error ) {
aggregator := map [ string ] Entry { }
for _ , kv := range data {
log . Printf ( "%s %s" , kv . Key , string ( kv . Value ) )
dn , attr , val := consulToDN ( kv )
if attr == "" || val == nil {
continue
}
if _ , exists := aggregator [ dn ] ; ! exists {
aggregator [ dn ] = Entry { }
}
var value interface { }
err := json . Unmarshal ( val , & value )
if err != nil {
return nil , err
}
if vlist , ok := value . ( [ ] interface { } ) ; ok {
vlist2 := [ ] string { }
for _ , v := range vlist {
if vstr , ok := v . ( string ) ; ok {
vlist2 = append ( vlist2 , vstr )
} else {
return nil , fmt . Errorf ( "Not a string: %#v" , v )
}
}
aggregator [ dn ] [ attr ] = vlist2
} else if vstr , ok := value . ( string ) ; ok {
aggregator [ dn ] [ attr ] = vstr
} else {
return nil , fmt . Errorf ( "Not a string or a list of strings: %#v" , value )
}
}
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 16:55:25 +00:00
type Entry map [ string ] interface { }
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 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 11:49:49 +00:00
"structuralObjectClass" : "Organization" ,
}
suffix_dn , err := parseDN ( server . config . Suffix )
if err != nil {
return err
}
base_attributes [ suffix_dn [ 0 ] . Type ] = suffix_dn [ 0 ] . Value
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" } ,
"description" : "LDAP administrator" ,
"cn" : "admin" ,
"userpassword" : admin_pass_hash ,
2020-01-19 11:49:49 +00:00
"structuralObjectClass" : "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 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 16:55:25 +00:00
result_code , err := server . handleBindInternal ( state , w , & 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 16:55:25 +00:00
func ( server * Server ) handleBindInternal ( state * State , w ldap . ResponseWriter , r * message . BindRequest ) ( int , error ) {
2020-01-19 11:49:49 +00:00
2020-01-19 12:00:53 +00:00
pair , _ , err := server . kv . Get ( dnToConsul ( string ( r . Name ( ) ) ) + "/attribute=userpassword" , nil )
2020-01-19 11:49:49 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if pair == nil {
return ldap . LDAPResultNoSuchObject , nil
}
hash := ""
err = json . Unmarshal ( pair . Value , & hash )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
valid := SSHAMatches ( hash , [ ] byte ( r . AuthenticationSimple ( ) ) )
if valid {
2020-01-19 12:00:53 +00:00
state . bindDn = string ( r . Name ( ) )
2020-01-19 11:49:49 +00:00
return ldap . LDAPResultSuccess , nil
} else {
return ldap . LDAPResultInvalidCredentials , nil
}
}
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
basePath := dnToConsul ( string ( r . BaseObject ( ) ) ) + "/"
data , _ , err := server . kv . List ( basePath , nil )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
entries , err := parseConsulResult ( data )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
log . Printf ( "in %s: %#v" , basePath , data )
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
if val_str , ok := val . ( string ) ; ok {
e . AddAttribute ( message . AttributeDescription ( attr ) ,
message . AttributeValue ( val_str ) )
} else if val_strlist , ok := val . ( [ ] string ) ; ok {
for _ , v := range val_strlist {
e . AddAttribute ( message . AttributeDescription ( attr ) ,
message . AttributeValue ( v ) )
}
} else {
panic ( fmt . Sprintf ( "Invalid value: %#v" , val ) )
}
}
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 ) {
if vstr , ok := value . ( string ) ; ok {
// If we have one value for the key, match exactly
return vstr == target , nil
} else if vlist , ok := value . ( [ ] string ) ; ok {
// If we have several values for the key, one must match
for _ , val := range vlist {
if val == target {
return true , nil
}
2020-01-19 16:55:25 +00:00
}
2020-01-19 17:04:42 +00:00
return false , nil
} else {
panic ( fmt . Sprintf ( "Invalid value: %#v" , value ) )
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 )
}
}