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.

183 lines
3.5 KiB

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