A cloud-native LDAP server backed by a Consul datastore
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

506 lines
12 KiB

  1. package main
  2. import (
  3. "crypto/rand"
  4. "crypto/tls"
  5. "encoding/base64"
  6. "encoding/json"
  7. "flag"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "os/signal"
  12. "strings"
  13. "syscall"
  14. ldap "bottin/ldapserver"
  15. consul "github.com/hashicorp/consul/api"
  16. message "github.com/lor00x/goldap/message"
  17. log "github.com/sirupsen/logrus"
  18. )
  19. // System managed attributes (cannot be changed by user, see checkRestrictedAttr)
  20. const ATTR_MEMBEROF = "memberof"
  21. const ATTR_ENTRYUUID = "entryuuid"
  22. const ATTR_CREATORSNAME = "creatorsname"
  23. const ATTR_CREATETIMESTAMP = "createtimestamp"
  24. const ATTR_MODIFIERSNAME = "modifiersname"
  25. const ATTR_MODIFYTIMESTAMP = "modifytimestamp"
  26. // Attributes that we are interested in at various points
  27. const ATTR_OBJECTCLASS = "objectclass"
  28. const ATTR_MEMBER = "member"
  29. const ATTR_USERPASSWORD = "userpassword"
  30. type ConfigFile struct {
  31. Suffix string `json:"suffix"`
  32. Bind string `json:"bind"`
  33. BindSecure string `json:"bind_secure"`
  34. ConsulHost string `json:"consul_host"`
  35. Acl []string `json:"acl"`
  36. TLSCertFile string `json:"tls_cert_file"`
  37. TLSKeyFile string `json:"tls_key_file"`
  38. TLSServerName string `json:"tls_server_name"`
  39. LogLevel string `json:"log_level"`
  40. }
  41. type Config struct {
  42. Suffix string
  43. Bind string
  44. BindSecure string
  45. ConsulHost string
  46. LogLevel log.Level
  47. Acl ACL
  48. TLSConfig *tls.Config
  49. }
  50. type Server struct {
  51. logger *log.Logger
  52. config Config
  53. kv *consul.KV
  54. }
  55. type State struct {
  56. login Login
  57. }
  58. type Entry map[string][]string
  59. var configFlag = flag.String("config", "./config.json", "Configuration file path")
  60. var resyncFlag = flag.Bool("resync", false, "Check and re-synchronize memberOf values before launch")
  61. func readConfig(logger *log.Logger) Config {
  62. config_file := ConfigFile{
  63. Bind: "0.0.0.0:389",
  64. BindSecure: "0.0.0.0:636",
  65. }
  66. bytes, err := ioutil.ReadFile(*configFlag)
  67. if err != nil {
  68. logger.Fatal(err)
  69. }
  70. err = json.Unmarshal(bytes, &config_file)
  71. if err != nil {
  72. logger.Fatal(err)
  73. }
  74. acl, err := ParseACL(config_file.Acl)
  75. if err != nil {
  76. logger.Fatal(err)
  77. }
  78. log_level := log.InfoLevel
  79. if config_file.LogLevel != "" {
  80. log_level, err = log.ParseLevel(config_file.LogLevel)
  81. if err != nil {
  82. logger.Fatal(err)
  83. }
  84. }
  85. ret := Config{
  86. Suffix: config_file.Suffix,
  87. Bind: config_file.Bind,
  88. BindSecure: config_file.BindSecure,
  89. ConsulHost: config_file.ConsulHost,
  90. Acl: acl,
  91. LogLevel: log_level,
  92. }
  93. if config_file.TLSCertFile != "" && config_file.TLSKeyFile != "" && config_file.TLSServerName != "" {
  94. cert_txt, err := ioutil.ReadFile(config_file.TLSCertFile)
  95. if err != nil {
  96. logger.Fatal(err)
  97. }
  98. key_txt, err := ioutil.ReadFile(config_file.TLSKeyFile)
  99. if err != nil {
  100. logger.Fatal(err)
  101. }
  102. cert, err := tls.X509KeyPair(cert_txt, key_txt)
  103. if err != nil {
  104. logger.Fatal(err)
  105. }
  106. ret.TLSConfig = &tls.Config{
  107. MinVersion: tls.VersionTLS10,
  108. MaxVersion: tls.VersionTLS12,
  109. Certificates: []tls.Certificate{cert},
  110. ServerName: config_file.TLSServerName,
  111. }
  112. }
  113. return ret
  114. }
  115. func main() {
  116. flag.Parse()
  117. logger := log.New()
  118. logger.SetOutput(os.Stdout)
  119. logger.SetFormatter(&log.TextFormatter{})
  120. config := readConfig(logger)
  121. if log_level := os.Getenv("BOTTIN_LOG_LEVEL"); log_level != "" {
  122. level, err := log.ParseLevel(log_level)
  123. if err != nil {
  124. logger.Fatal(err)
  125. }
  126. logger.SetLevel(level)
  127. } else {
  128. logger.SetLevel(config.LogLevel)
  129. }
  130. ldap.Logger = logger
  131. // Connect to Consul
  132. consul_config := consul.DefaultConfig()
  133. if config.ConsulHost != "" {
  134. consul_config.Address = config.ConsulHost
  135. }
  136. consul_client, err := consul.NewClient(consul_config)
  137. if err != nil {
  138. logger.Fatal(err)
  139. }
  140. kv := consul_client.KV()
  141. // Create bottin server
  142. bottin := Server{
  143. logger: logger,
  144. config: config,
  145. kv: kv,
  146. }
  147. err = bottin.init()
  148. if err != nil {
  149. logger.Fatal(err)
  150. }
  151. if *resyncFlag {
  152. err = bottin.memberOfResync()
  153. if err != nil {
  154. logger.Fatal(err)
  155. }
  156. }
  157. // Create routes
  158. routes := ldap.NewRouteMux()
  159. routes.Bind(bottin.handleBind)
  160. routes.Search(bottin.handleSearch)
  161. routes.Add(bottin.handleAdd)
  162. routes.Compare(bottin.handleCompare)
  163. routes.Delete(bottin.handleDelete)
  164. routes.Modify(bottin.handleModify)
  165. if config.TLSConfig != nil {
  166. routes.Extended(bottin.handleStartTLS).
  167. RequestName(ldap.NoticeOfStartTLS).Label("StartTLS")
  168. }
  169. // Create LDAP servers
  170. var ldapServer, ldapServerSecure *ldap.Server = nil, nil
  171. // Bind on standard LDAP port without TLS
  172. if config.Bind != "" {
  173. ldapServer = ldap.NewServer()
  174. ldapServer.Handle(routes)
  175. ldapServer.NewUserState = bottin.newUserState
  176. go func() {
  177. err := ldapServer.ListenAndServe(config.Bind)
  178. if err != nil {
  179. logger.Fatal(err)
  180. }
  181. }()
  182. }
  183. // Bind on LDAP secure port with TLS
  184. if config.BindSecure != "" {
  185. if config.TLSConfig != nil {
  186. ldapServerSecure := ldap.NewServer()
  187. ldapServerSecure.Handle(routes)
  188. ldapServerSecure.NewUserState = bottin.newUserState
  189. secureConn := func(s *ldap.Server) {
  190. s.Listener = tls.NewListener(s.Listener, config.TLSConfig)
  191. }
  192. go func() {
  193. err := ldapServerSecure.ListenAndServe(config.BindSecure, secureConn)
  194. if err != nil {
  195. logger.Fatal(err)
  196. }
  197. }()
  198. } else {
  199. logger.Warnf("Warning: no valid TLS configuration was provided, not binding on %s", config.BindSecure)
  200. }
  201. }
  202. if ldapServer == nil && ldapServerSecure == nil {
  203. logger.Fatal("Not doing anything.")
  204. }
  205. // When CTRL+C, SIGINT and SIGTERM signal occurs
  206. // Then stop server gracefully
  207. ch := make(chan os.Signal)
  208. signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
  209. <-ch
  210. close(ch)
  211. if ldapServer != nil {
  212. ldapServer.Stop()
  213. }
  214. if ldapServerSecure != nil {
  215. ldapServerSecure.Stop()
  216. }
  217. }
  218. func (server *Server) newUserState() ldap.UserState {
  219. return &State{
  220. login: Login{
  221. user: "ANONYMOUS",
  222. groups: []string{},
  223. },
  224. }
  225. }
  226. func (server *Server) init() error {
  227. // Check that suffix is in canonical format in config file
  228. suffix_canonical, err := server.checkDN(server.config.Suffix, false)
  229. if err != nil {
  230. return err
  231. }
  232. if suffix_canonical != server.config.Suffix {
  233. return fmt.Errorf("Please write suffix in canonical format: %s", suffix_canonical)
  234. }
  235. // Check that root object exists.
  236. // If it does, we're done. Otherwise, we have some initialization to do.
  237. exists, err := server.objectExists(server.config.Suffix)
  238. if err != nil {
  239. return err
  240. }
  241. if exists {
  242. return nil
  243. }
  244. // We have to initialize the server.
  245. // Create a root object and an admin object.
  246. base_attributes := Entry{
  247. ATTR_OBJECTCLASS: []string{"top", "dcObject", "organization"},
  248. "structuralobjectclass": []string{"organization"},
  249. ATTR_CREATORSNAME: []string{server.config.Suffix},
  250. ATTR_CREATETIMESTAMP: []string{genTimestamp()},
  251. ATTR_ENTRYUUID: []string{genUuid()},
  252. }
  253. suffix_dn, err := parseDN(server.config.Suffix)
  254. if err != nil {
  255. return err
  256. }
  257. base_attributes[suffix_dn[0].Type] = []string{suffix_dn[0].Value}
  258. err = server.addElements(server.config.Suffix, base_attributes)
  259. if err != nil {
  260. return err
  261. }
  262. admin_pass := make([]byte, 8)
  263. _, err = rand.Read(admin_pass)
  264. if err != nil {
  265. return err
  266. }
  267. admin_pass_str := base64.RawURLEncoding.EncodeToString(admin_pass)
  268. admin_pass_hash := SSHAEncode([]byte(admin_pass_str))
  269. admin_dn := "cn=admin," + server.config.Suffix
  270. admin_attributes := Entry{
  271. ATTR_OBJECTCLASS: []string{"simpleSecurityObject", "organizationalRole"},
  272. "displayname": []string{"LDAP administrator"},
  273. "description": []string{"Administrator account automatically created by Bottin"},
  274. "cn": []string{"admin"},
  275. "structuralobjectclass": []string{"organizationalRole"},
  276. ATTR_USERPASSWORD: []string{admin_pass_hash},
  277. ATTR_CREATORSNAME: []string{server.config.Suffix},
  278. ATTR_CREATETIMESTAMP: []string{genTimestamp()},
  279. ATTR_ENTRYUUID: []string{genUuid()},
  280. }
  281. err = server.addElements(admin_dn, admin_attributes)
  282. if err != nil {
  283. return err
  284. }
  285. server.logger.Printf(
  286. "It seems to be a new installation, we created a default user for you:\n\n dn: %s\n password: %s\n\nWe recommend replacing it as soon as possible.",
  287. admin_dn,
  288. admin_pass_str,
  289. )
  290. return nil
  291. }
  292. func (server *Server) addElements(dn string, attrs Entry) error {
  293. prefix, err := dnToConsul(dn)
  294. if err != nil {
  295. return err
  296. }
  297. for k, valuesNC := range attrs {
  298. path := prefix + "/attribute=" + k
  299. // Trim spaces and remove empty values
  300. values := []string{}
  301. for _, v := range valuesNC {
  302. vv := strings.TrimSpace(v)
  303. if len(vv) > 0 {
  304. values = append(values, vv)
  305. }
  306. }
  307. // If we have zero values, delete associated k/v pair
  308. // Otherwise, write new values
  309. if len(values) == 0 {
  310. _, err := server.kv.Delete(path, nil)
  311. if err != nil {
  312. return err
  313. }
  314. } else {
  315. json, err := json.Marshal(values)
  316. if err != nil {
  317. return err
  318. }
  319. pair := &consul.KVPair{Key: path, Value: json}
  320. _, err = server.kv.Put(pair, nil)
  321. if err != nil {
  322. return err
  323. }
  324. }
  325. }
  326. return nil
  327. }
  328. func (server *Server) getAttribute(dn string, attr string) ([]string, error) {
  329. path, err := dnToConsul(dn)
  330. if err != nil {
  331. return nil, err
  332. }
  333. pairs, _, err := server.kv.List(path+"/attribute=", nil)
  334. if err != nil {
  335. return nil, err
  336. }
  337. values := []string{}
  338. for _, pair := range pairs {
  339. if strings.EqualFold(pair.Key, path+"/attribute="+attr) {
  340. newVals, err := parseValue(pair.Value)
  341. if err != nil {
  342. return nil, err
  343. }
  344. values = append(values, newVals...)
  345. }
  346. }
  347. return values, nil
  348. }
  349. func (server *Server) objectExists(dn string) (bool, error) {
  350. prefix, err := dnToConsul(dn)
  351. if err != nil {
  352. return false, err
  353. }
  354. data, _, err := server.kv.List(prefix+"/attribute=", nil)
  355. if err != nil {
  356. return false, err
  357. }
  358. return len(data) > 0, nil
  359. }
  360. func (server *Server) checkDN(dn string, allow_extend bool) (string, error) {
  361. // 1. Canonicalize: remove spaces between things and put all in lower case
  362. dn, err := canonicalDN(dn)
  363. if err != nil {
  364. return "", err
  365. }
  366. // 2. Check suffix (add it if allow_extend is set)
  367. suffix := server.config.Suffix
  368. if len(dn) < len(suffix) {
  369. if dn != suffix[len(suffix)-len(dn):] || !allow_extend {
  370. return suffix, fmt.Errorf(
  371. "Only handling stuff under DN %s", suffix)
  372. }
  373. return suffix, nil
  374. } else {
  375. if dn[len(dn)-len(suffix):] != suffix {
  376. return suffix, fmt.Errorf(
  377. "Only handling stuff under DN %s", suffix)
  378. }
  379. return dn, nil
  380. }
  381. }
  382. func (server *Server) handleStartTLS(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) {
  383. tlsConn := tls.Server(m.Client.GetConn(), server.config.TLSConfig)
  384. res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
  385. res.SetResponseName(ldap.NoticeOfStartTLS)
  386. w.Write(res)
  387. if err := tlsConn.Handshake(); err != nil {
  388. server.logger.Printf("StartTLS Handshake error %v", err)
  389. res.SetDiagnosticMessage(fmt.Sprintf("StartTLS Handshake error : \"%s\"", err.Error()))
  390. res.SetResultCode(ldap.LDAPResultOperationsError)
  391. w.Write(res)
  392. return
  393. }
  394. m.Client.SetConn(tlsConn)
  395. }
  396. func (server *Server) handleBind(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) {
  397. state := s.(*State)
  398. r := m.GetBindRequest()
  399. result_code, err := server.handleBindInternal(state, &r)
  400. res := ldap.NewBindResponse(result_code)
  401. if err != nil {
  402. res.SetDiagnosticMessage(err.Error())
  403. }
  404. if result_code == ldap.LDAPResultSuccess {
  405. server.logger.Printf("Successfully bound to %s", string(r.Name()))
  406. } else {
  407. server.logger.Printf("Failed to bind to %s (%s)", string(r.Name()), err)
  408. }
  409. w.Write(res)
  410. }
  411. func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (int, error) {
  412. // Check permissions
  413. if !server.config.Acl.Check(&state.login, "bind", string(r.Name()), []string{}) {
  414. return ldap.LDAPResultInsufficientAccessRights, fmt.Errorf("Insufficient access rights for %#v", state.login)
  415. }
  416. // Try to retrieve password and check for match
  417. passwd, err := server.getAttribute(string(r.Name()), ATTR_USERPASSWORD)
  418. if err != nil {
  419. return ldap.LDAPResultOperationsError, err
  420. }
  421. for _, hash := range passwd {
  422. valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
  423. if valid {
  424. groups, err := server.getAttribute(string(r.Name()), ATTR_MEMBEROF)
  425. if err != nil {
  426. return ldap.LDAPResultOperationsError, err
  427. }
  428. state.login = Login{
  429. user: string(r.Name()),
  430. groups: groups,
  431. }
  432. return ldap.LDAPResultSuccess, nil
  433. }
  434. }
  435. return ldap.LDAPResultInvalidCredentials, fmt.Errorf("No password match")
  436. }