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.

178 lines
3.5 KiB

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "time"
  7. uuid "github.com/google/uuid"
  8. consul "github.com/hashicorp/consul/api"
  9. log "github.com/sirupsen/logrus"
  10. )
  11. func dnToConsul(dn string) (string, error) {
  12. if strings.Contains(dn, "/") {
  13. return "", fmt.Errorf("DN %s contains a /", dn)
  14. }
  15. rdns := strings.Split(dn, ",")
  16. // Reverse rdns
  17. for i, j := 0, len(rdns)-1; i < j; i, j = i+1, j-1 {
  18. rdns[i], rdns[j] = rdns[j], rdns[i]
  19. }
  20. return strings.Join(rdns, "/"), nil
  21. }
  22. func consulToDN(key string) (string, string, error) {
  23. path := strings.Split(key, "/")
  24. dn := ""
  25. for _, cpath := range path {
  26. if cpath == "" {
  27. continue
  28. }
  29. kv := strings.Split(cpath, "=")
  30. if len(kv) == 2 && kv[0] == "attribute" {
  31. return dn, kv[1], nil
  32. }
  33. if dn != "" {
  34. dn = "," + dn
  35. }
  36. dn = cpath + dn
  37. }
  38. return "", "", fmt.Errorf("Consul key %s does not end with attribute=something", key)
  39. }
  40. func parseValue(value []byte) ([]string, error) {
  41. val := []string{}
  42. err := json.Unmarshal(value, &val)
  43. if err == nil {
  44. return val, nil
  45. }
  46. val2 := ""
  47. err = json.Unmarshal(value, &val2)
  48. if err == nil {
  49. return []string{val2}, nil
  50. }
  51. return nil, fmt.Errorf("Not a string or list of strings: %s", value)
  52. }
  53. func parseConsulResult(data []*consul.KVPair) (map[string]Entry, error) {
  54. aggregator := map[string]Entry{}
  55. for _, kv := range data {
  56. dn, attr, err := consulToDN(kv.Key)
  57. if err != nil {
  58. continue
  59. }
  60. if _, exists := aggregator[dn]; !exists {
  61. aggregator[dn] = Entry{}
  62. }
  63. value, err := parseValue(kv.Value)
  64. if err != nil {
  65. return nil, err
  66. }
  67. aggregator[dn][attr] = value
  68. }
  69. return aggregator, nil
  70. }
  71. type DNComponent struct {
  72. Type string
  73. Value string
  74. }
  75. func parseDN(dn string) ([]DNComponent, error) {
  76. rdns := strings.Split(dn, ",")
  77. ret := []DNComponent{}
  78. for _, rdn := range rdns {
  79. splits := strings.Split(rdn, "=")
  80. if len(splits) != 2 {
  81. return nil, fmt.Errorf("Wrong DN component: %s (expected type=value)", rdn)
  82. }
  83. ret = append(ret, DNComponent{
  84. Type: strings.ToLower(strings.TrimSpace(splits[0])),
  85. Value: strings.ToLower(strings.TrimSpace(splits[1])),
  86. })
  87. }
  88. return ret, nil
  89. }
  90. func unparseDN(path []DNComponent) string {
  91. ret := ""
  92. for _, c := range path {
  93. if ret != "" {
  94. ret = ret + ","
  95. }
  96. ret = ret + c.Type + "=" + c.Value
  97. }
  98. return ret
  99. }
  100. func canonicalDN(dn string) (string, error) {
  101. path, err := parseDN(dn)
  102. if err != nil {
  103. return "", err
  104. }
  105. return unparseDN(path), nil
  106. }
  107. func checkRestrictedAttr(attr string) error {
  108. RESTRICTED_ATTRS := []string{
  109. ATTR_MEMBEROF,
  110. ATTR_ENTRYUUID,
  111. ATTR_CREATORSNAME,
  112. ATTR_CREATETIMESTAMP,
  113. ATTR_MODIFIERSNAME,
  114. ATTR_MODIFYTIMESTAMP,
  115. }
  116. if strings.EqualFold(attr, ATTR_MEMBEROF) {
  117. return fmt.Errorf("memberOf cannot be defined directly, membership must be specified in the group itself")
  118. }
  119. for _, s := range RESTRICTED_ATTRS {
  120. if strings.EqualFold(attr, s) {
  121. return fmt.Errorf("Attribute %s is restricted and may only be set by the system", s)
  122. }
  123. }
  124. return nil
  125. }
  126. func genTimestamp() string {
  127. return time.Now().Format("20060102150405Z")
  128. }
  129. func genUuid() string {
  130. uuid, err := uuid.NewRandom()
  131. if err != nil {
  132. log.Panicf("UUID generation error: %s", err)
  133. }
  134. return uuid.String()
  135. }
  136. func valueMatch(attr, val1, val2 string) bool {
  137. if strings.EqualFold(attr, ATTR_USERPASSWORD) {
  138. return val1 == val2
  139. } else {
  140. return strings.EqualFold(val1, val2)
  141. }
  142. }
  143. func listContains(list []string, key string) bool {
  144. for _, v := range list {
  145. if key == v {
  146. return true
  147. }
  148. }
  149. return false
  150. }