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 , "/" )
}
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
}
type Attributes map [ string ] interface { }
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 )
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
}
base_attributes := Attributes {
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
admin_attributes := Attributes {
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
}
func ( server * Server ) addElements ( dn string , attrs Attributes ) error {
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 12:00:53 +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 12:00:53 +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
}
}