From c1f0247586c98bbdfc42c4a8db3410d34dfbc500 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Sun, 26 Jan 2020 19:27:17 +0100 Subject: [PATCH] Externalize config --- README.md | 16 +++++++ config.json | 13 ++++++ main.go | 125 ++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 120 insertions(+), 34 deletions(-) create mode 100644 README.md create mode 100644 config.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..b4a95bf --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +## ACL examples + +``` +// Anybody (before binding) can bind to an entity under ou=users,dc=gobottin,dc=eu +"ANONYMOUS::bind:*,ou=users,dc=gobottin,dc=eu:", +// Anybody (before binding) can bind to the specific admin entity +"ANONYMOUS::bind:cn=admin,dc=gobottin,dc=eu:", +// Anybody who is logged in can read anything that is not a userpassword attribute +"*,dc=gobottin,dc=eu::read:*:* !userpassword", +// Anybody can read and modify anything from their own entry +"*::read modify:SELF:*", +// The admin can read, add, modify, delete anything +"cn=admin,dc=gobottin,dc=eu::read add modify delete:*:*", +// Members of the admin group can read, add, modify, delete anything +"*:cn=admin,ou=groups,dc=gobottin,dc=eu:read add modify delete:*:*" +``` diff --git a/config.json b/config.json new file mode 100644 index 0000000..f4c2781 --- /dev/null +++ b/config.json @@ -0,0 +1,13 @@ +{ + "suffix": "dc=gobottin,dc=eu", + "bind_address": "127.0.0.1:10389", + "acl": [ + "ANONYMOUS::bind:*,ou=users,dc=gobottin,dc=eu:", + "ANONYMOUS::bind:cn=admin,dc=gobottin,dc=eu:", + "*,dc=gobottin,dc=eu::read:*:* !userpassword", + "*::read modify:SELF:*", + "cn=admin,dc=gobottin,dc=eu::read add modify delete:*:*", + "*:cn=admin,ou=groups,dc=gobottin,dc=eu:read add modify delete:*:*" + ] +} + diff --git a/main.go b/main.go index 6516724..8894f26 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,15 @@ package main -// @FIXME: Implement a real permission system: limit read/write scope/attributes, possibly based on group membership // @FIXME: Implement missing search filters (in applyFilter) // @FIXME: Add an initial prefix to the consul key value -// @FIXME: Add TLS connections import ( + "crypto/tls" "encoding/base64" "encoding/json" + "flag" "fmt" + "io/ioutil" "log" "math/rand" "os" @@ -21,9 +22,24 @@ import ( message "github.com/vjeantet/goldap/message" ) +type ConfigFile struct { + Suffix string `json:"suffix"` + BindAddress string `json:"bind_address"` + ConsulHost string `json:"consul_host"` + Acl []string `json:"acl"` + SSLCertFile string `json:"ssl_cert_file"` + SSLKeyFile string `json:"ssl_key_file"` + SSLServerName string `json:"ssl_server_name"` +} + type Config struct { - Suffix string - Acl ACL + Suffix string + BindAddress string + ConsulHost string + + Acl ACL + + TlsConfig *tls.Config } type Server struct { @@ -37,42 +53,77 @@ type State struct { type Entry map[string][]string +var configFlag = flag.String("config", "./config.json", "Configuration file path") + +func readConfig() Config { + config_file := ConfigFile{ + BindAddress: "0.0.0.0:389", + } + + bytes, err := ioutil.ReadFile(*configFlag) + if err != nil { + panic(err) + } + + err = json.Unmarshal(bytes, &config_file) + if err != nil { + panic(err) + } + + acl, err := ParseACL(config_file.Acl) + if err != nil { + panic(err) + } + + ret := Config{ + Suffix: config_file.Suffix, + BindAddress: config_file.BindAddress, + ConsulHost: config_file.ConsulHost, + Acl: acl, + } + + if config_file.SSLCertFile != "" && config_file.SSLKeyFile != "" && config_file.SSLServerName != "" { + cert_txt, err := ioutil.ReadFile(config_file.SSLCertFile) + if err != nil { + panic(err) + } + key_txt, err := ioutil.ReadFile(config_file.SSLKeyFile) + if err != nil { + panic(err) + } + cert, err := tls.X509KeyPair(cert_txt, key_txt) + if err != nil { + panic(err) + } + ret.TlsConfig = &tls.Config{ + MinVersion: tls.VersionSSL30, + MaxVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{cert}, + ServerName: config_file.SSLServerName, + } + + } + + return ret +} + func main() { - //ldap logger ldap.Logger = log.New(os.Stdout, "[server] ", log.LstdFlags) + config := readConfig() + // Connect to Consul - client, err := consul.NewClient(consul.DefaultConfig()) + consul_config := consul.DefaultConfig() + if config.ConsulHost != "" { + consul_config.Address = config.ConsulHost + } + consul_client, err := consul.NewClient(consul_config) if err != nil { panic(err) } - kv := client.KV() - - aclStr := []string{ - // Anybody (before binding) can bind to an entity under ou=users,dc=gobottin,dc=eu - "ANONYMOUS::bind:*,ou=users,dc=gobottin,dc=eu:", - // Anybody (before binding) can bind to the specific admin entity - "ANONYMOUS::bind:cn=admin,dc=gobottin,dc=eu:", - // Anybody who is logged in can read anything that is not a userpassword attribute - "*,dc=gobottin,dc=eu::read:*:* !userpassword", - // Anybody can read and modify anything from their own entry - "*::read modify:SELF:*", - // The admin can read, add, modify, delete anything - "cn=admin,dc=gobottin,dc=eu::read add modify delete:*:*", - // Members of the admin group can read, add, modify, delete anything - "*:cn=admin,ou=groups,dc=gobottin,dc=eu:read add modify delete:*:*", - } - acl, err := ParseACL(aclStr) - if err != nil { - panic(err) - } - - // TODO read config from somewhere - config := Config{ - Suffix: "dc=gobottin,dc=eu", - Acl: acl, - } + kv := consul_client.KV() + // Create gobottin server gobottin := Server{config: config, kv: kv} err = gobottin.init() if err != nil { @@ -99,8 +150,14 @@ func main() { routes.Modify(gobottin.handleModify) ldapserver.Handle(routes) - // listen on 10389 - go ldapserver.ListenAndServe("127.0.0.1:10389") + if config.TlsConfig != nil { + secureConn := func(s *ldap.Server) { + s.Listener = tls.NewListener(s.Listener, config.TlsConfig) + } + go ldapserver.ListenAndServe(config.BindAddress, secureConn) + } else { + go ldapserver.ListenAndServe(config.BindAddress) + } // When CTRL+C, SIGINT and SIGTERM signal occurs // Then stop server gracefully