2020-01-19 11:49:49 +00:00
package main
2020-01-26 20:22:51 +00:00
// @FIXME: Proper handling of various upper/lower case combinations
2020-01-19 21:30:51 +00:00
// @FIXME: Implement missing search filters (in applyFilter)
// @FIXME: Add an initial prefix to the consul key value
2020-01-19 11:49:49 +00:00
import (
2020-01-26 18:27:17 +00:00
"crypto/tls"
2020-01-19 12:00:53 +00:00
"encoding/base64"
"encoding/json"
2020-01-26 18:27:17 +00:00
"flag"
2020-01-19 11:49:49 +00:00
"fmt"
2020-01-26 18:27:17 +00:00
"io/ioutil"
2020-01-19 11:49:49 +00:00
"log"
2020-01-19 12:00:53 +00:00
"math/rand"
2020-01-19 11:49:49 +00:00
"os"
"os/signal"
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"
)
2020-01-26 18:47:38 +00:00
const DEBUG = false
2020-01-26 20:22:51 +00:00
const ATTR_USERPASSWORD = "userpassword"
2020-01-26 21:22:38 +00:00
const ATTR_MEMBER = "member"
2020-01-26 20:22:51 +00:00
const ATTR_MEMBEROF = "memberof"
const ATTR_ENTRYUUID = "entryuuid"
const ATTR_CREATORSNAME = "creatorsname"
const ATTR_CREATETIMESTAMP = "createtimestamp"
const ATTR_MODIFIERSNAME = "modifiersname"
const ATTR_MODIFYTIMESTAMP = "modifytimestamp"
2020-01-26 18:27:17 +00:00
type ConfigFile struct {
Suffix string ` json:"suffix" `
BindAddress string ` json:"bind_address" `
ConsulHost string ` json:"consul_host" `
Acl [ ] string ` json:"acl" `
2020-01-27 15:08:35 +00:00
TLSCertFile string ` json:"tls_cert_file" `
TLSKeyFile string ` json:"tls_key_file" `
TLSServerName string ` json:"tls_server_name" `
UseStartTLS bool ` json:"use_starttls" `
2020-01-26 18:27:17 +00:00
}
2020-01-19 11:49:49 +00:00
type Config struct {
2020-01-26 18:27:17 +00:00
Suffix string
BindAddress string
ConsulHost string
Acl ACL
2020-01-27 15:08:35 +00:00
TLSConfig * tls . Config
UseStartTLS bool
2020-01-19 11:49:49 +00:00
}
type Server struct {
2020-01-26 18:47:38 +00:00
logger * log . Logger
2020-01-19 11:49:49 +00:00
config Config
2020-01-19 12:00:53 +00:00
kv * consul . KV
}
type State struct {
2020-01-26 17:42:04 +00:00
login Login
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
2020-01-26 18:27:17 +00:00
var configFlag = flag . String ( "config" , "./config.json" , "Configuration file path" )
2020-01-19 11:49:49 +00:00
2020-01-26 18:27:17 +00:00
func readConfig ( ) Config {
config_file := ConfigFile {
BindAddress : "0.0.0.0:389" ,
}
bytes , err := ioutil . ReadFile ( * configFlag )
2020-01-19 11:49:49 +00:00
if err != nil {
panic ( err )
}
2020-01-26 18:27:17 +00:00
err = json . Unmarshal ( bytes , & config_file )
if err != nil {
panic ( err )
2020-01-26 17:42:04 +00:00
}
2020-01-26 18:27:17 +00:00
acl , err := ParseACL ( config_file . Acl )
2020-01-26 17:42:04 +00:00
if err != nil {
panic ( err )
}
2020-01-26 18:27:17 +00:00
ret := Config {
Suffix : config_file . Suffix ,
BindAddress : config_file . BindAddress ,
ConsulHost : config_file . ConsulHost ,
Acl : acl ,
2020-01-27 15:08:35 +00:00
UseStartTLS : config_file . UseStartTLS ,
2020-01-26 18:27:17 +00:00
}
2020-01-27 15:08:35 +00:00
if config_file . TLSCertFile != "" && config_file . TLSKeyFile != "" && config_file . TLSServerName != "" {
cert_txt , err := ioutil . ReadFile ( config_file . TLSCertFile )
2020-01-26 18:27:17 +00:00
if err != nil {
panic ( err )
}
2020-01-27 15:08:35 +00:00
key_txt , err := ioutil . ReadFile ( config_file . TLSKeyFile )
2020-01-26 18:27:17 +00:00
if err != nil {
panic ( err )
}
cert , err := tls . X509KeyPair ( cert_txt , key_txt )
if err != nil {
panic ( err )
}
2020-01-27 15:08:35 +00:00
ret . TLSConfig = & tls . Config {
MinVersion : tls . VersionTLS10 ,
2020-01-26 18:27:17 +00:00
MaxVersion : tls . VersionTLS12 ,
Certificates : [ ] tls . Certificate { cert } ,
2020-01-27 15:08:35 +00:00
ServerName : config_file . TLSServerName ,
2020-01-26 18:27:17 +00:00
}
2020-01-27 15:08:35 +00:00
} else {
log . Printf ( "Warning: no TLS configuration provided, running an insecure server." )
2020-01-26 18:27:17 +00:00
}
return ret
}
func main ( ) {
2020-01-26 20:03:18 +00:00
flag . Parse ( )
2020-01-26 18:47:38 +00:00
ldap . Logger = log . New ( os . Stdout , "[ldapserver] " , log . LstdFlags )
2020-01-26 18:27:17 +00:00
config := readConfig ( )
// Connect to Consul
consul_config := consul . DefaultConfig ( )
if config . ConsulHost != "" {
consul_config . Address = config . ConsulHost
}
consul_client , err := consul . NewClient ( consul_config )
if err != nil {
panic ( err )
2020-01-19 11:49:49 +00:00
}
2020-01-26 18:27:17 +00:00
kv := consul_client . KV ( )
2020-01-19 11:49:49 +00:00
2020-01-26 18:27:17 +00:00
// Create gobottin server
2020-01-26 18:47:38 +00:00
gobottin := Server {
logger : log . New ( os . Stdout , "[gobottin] " , log . LstdFlags ) ,
config : config ,
kv : kv ,
}
2020-01-19 11:49:49 +00:00
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 {
2020-01-26 17:42:04 +00:00
return & State {
login : Login {
user : "ANONYMOUS" ,
groups : [ ] string { } ,
} ,
}
2020-01-19 12:00:53 +00:00
}
2020-01-19 11:49:49 +00:00
routes := ldap . NewRouteMux ( )
2020-01-27 15:08:35 +00:00
2020-01-19 11:49:49 +00:00
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 20:48:14 +00:00
routes . Compare ( gobottin . handleCompare )
routes . Delete ( gobottin . handleDelete )
2020-01-19 21:21:05 +00:00
routes . Modify ( gobottin . handleModify )
2020-01-27 15:08:35 +00:00
if config . TLSConfig != nil && config . UseStartTLS {
routes . Extended ( gobottin . handleStartTLS ) .
RequestName ( ldap . NoticeOfStartTLS ) . Label ( "StartTLS" )
}
2020-01-19 11:49:49 +00:00
ldapserver . Handle ( routes )
2020-01-27 15:08:35 +00:00
go func ( ) {
// 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 ( )
} ( )
if config . TLSConfig != nil && ! config . UseStartTLS {
2020-01-26 18:27:17 +00:00
secureConn := func ( s * ldap . Server ) {
2020-01-27 15:08:35 +00:00
s . Listener = tls . NewListener ( s . Listener , config . TLSConfig )
2020-01-26 18:27:17 +00:00
}
2020-01-27 15:08:35 +00:00
err = ldapserver . ListenAndServe ( config . BindAddress , secureConn )
2020-01-26 18:27:17 +00:00
} else {
2020-01-27 15:08:35 +00:00
err = ldapserver . ListenAndServe ( config . BindAddress )
}
if err != nil {
panic ( err )
2020-01-26 18:27:17 +00:00
}
2020-01-19 11:49:49 +00:00
}
func ( server * Server ) init ( ) error {
2020-01-19 21:27:54 +00:00
path , err := dnToConsul ( server . config . Suffix )
if err != nil {
return err
}
pair , _ , err := server . kv . Get ( path + "/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-26 22:12:00 +00:00
ATTR_CREATORSNAME : [ ] string { server . config . Suffix } ,
ATTR_CREATETIMESTAMP : [ ] string { genTimestamp ( ) } ,
ATTR_ENTRYUUID : [ ] string { genUuid ( ) } ,
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" } ,
"structuralObjectClass" : [ ] string { "organizationalRole" } ,
2020-01-26 22:12:00 +00:00
ATTR_USERPASSWORD : [ ] string { admin_pass_hash } ,
ATTR_CREATORSNAME : [ ] string { server . config . Suffix } ,
ATTR_CREATETIMESTAMP : [ ] string { genTimestamp ( ) } ,
ATTR_ENTRYUUID : [ ] string { genUuid ( ) } ,
2020-01-19 11:49:49 +00:00
}
err = server . addElements ( admin_dn , admin_attributes )
if err != nil {
return err
}
2020-01-26 18:47:38 +00:00
server . logger . Printf (
2020-01-19 11:49:49 +00:00
"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 21:27:54 +00:00
prefix , err := dnToConsul ( dn )
if err != nil {
return err
}
2020-01-19 11:49:49 +00:00
for k , v := range attrs {
2020-01-20 08:11:30 +00:00
path := prefix + "/attribute=" + k
if len ( v ) == 0 {
// If we have zero values, delete associated k/v pair
_ , err := server . kv . Delete ( path , nil )
if err != nil {
return err
}
} else {
json , err := json . Marshal ( v )
if err != nil {
return err
}
pair := & consul . KVPair { Key : path , Value : json }
_ , err = server . kv . Put ( pair , nil )
if err != nil {
return err
}
2020-01-19 11:49:49 +00:00
}
}
return nil
}
2020-01-19 18:10:38 +00:00
func ( server * Server ) getAttribute ( dn string , attr string ) ( [ ] string , error ) {
2020-01-19 21:27:54 +00:00
path , err := dnToConsul ( dn )
if err != nil {
return nil , err
}
2020-01-26 16:45:04 +00:00
pair , _ , err := server . kv . Get ( path + "/attribute=" + attr , nil )
2020-01-19 18:10:38 +00:00
if err != nil {
return nil , err
}
if pair == nil {
return nil , nil
}
return parseValue ( pair . Value )
}
func ( server * Server ) objectExists ( dn string ) ( bool , error ) {
2020-01-19 21:27:54 +00:00
prefix , err := dnToConsul ( dn )
if err != nil {
return false , err
}
2020-01-19 18:10:38 +00:00
2020-01-26 16:45:04 +00:00
data , _ , err := server . kv . List ( prefix + "/" , nil )
2020-01-19 18:10:38 +00:00
if err != nil {
return false , err
}
return len ( data ) > 0 , nil
}
2020-01-19 18:19:34 +00:00
func ( server * Server ) checkSuffix ( dn string , allow_extend bool ) ( string , error ) {
suffix := server . config . Suffix
if len ( dn ) < len ( suffix ) {
if dn != suffix [ - len ( dn ) : ] || ! allow_extend {
return suffix , fmt . Errorf (
"Only handling stuff under DN %s" , suffix )
}
return suffix , nil
} else {
if dn [ len ( dn ) - len ( suffix ) : ] != suffix {
return suffix , fmt . Errorf (
"Only handling stuff under DN %s" , suffix )
}
return dn , nil
}
}
2020-01-27 15:08:35 +00:00
func ( server * Server ) handleStartTLS ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
tlsConn := tls . Server ( m . Client . GetConn ( ) , server . config . TLSConfig )
res := ldap . NewExtendedResponse ( ldap . LDAPResultSuccess )
res . SetResponseName ( ldap . NoticeOfStartTLS )
w . Write ( res )
if err := tlsConn . Handshake ( ) ; err != nil {
log . Printf ( "StartTLS Handshake error %v" , err )
res . SetDiagnosticMessage ( fmt . Sprintf ( "StartTLS Handshake error : \"%s\"" , err . Error ( ) ) )
res . SetResultCode ( ldap . LDAPResultOperationsError )
w . Write ( res )
return
}
m . Client . SetConn ( tlsConn )
}
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 ( ) )
2020-01-26 18:47:38 +00:00
server . logger . Printf ( "Failed bind for %s: %s" , string ( r . Name ( ) ) , err . Error ( ) )
}
if result_code == ldap . LDAPResultSuccess {
server . logger . Printf ( "Successfully bound to %s" , string ( r . Name ( ) ) )
} else {
server . logger . Printf ( "Failed to bind to %s (%s)" , string ( r . Name ( ) ) , err )
2020-01-19 11:49:49 +00:00
}
w . Write ( res )
}
2020-01-19 17:24:21 +00:00
func ( server * Server ) handleBindInternal ( state * State , r * message . BindRequest ) ( int , error ) {
2020-01-26 17:42:04 +00:00
// Check permissions
if ! server . config . Acl . Check ( & state . login , "bind" , string ( r . Name ( ) ) , [ ] string { } ) {
return ldap . LDAPResultInsufficientAccessRights , nil
}
// Try to retrieve password and check for match
2020-01-26 20:22:51 +00:00
passwd , err := server . getAttribute ( string ( r . Name ( ) ) , ATTR_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 {
2020-01-26 20:22:51 +00:00
groups , err := server . getAttribute ( string ( r . Name ( ) ) , ATTR_MEMBEROF )
2020-01-26 17:42:04 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
state . login = Login {
user : string ( r . Name ( ) ) ,
groups : groups ,
}
2020-01-19 18:10:38 +00:00
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
}