Vendor OpenLDAP dynamic secrets (#10818)
This commit is contained in:
parent
4a6e00081c
commit
ec18926754
4
go.mod
4
go.mod
|
@ -95,9 +95,9 @@ require (
|
|||
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.7.0
|
||||
github.com/hashicorp/vault-plugin-secrets-kv v0.7.0
|
||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.3.0
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20201001211907-38d91b749c77
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20201022214319-d87657199d4b
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c
|
||||
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4
|
||||
github.com/jcmturner/gokrb5/v8 v8.0.0
|
||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f
|
||||
|
|
7
go.sum
7
go.sum
|
@ -338,6 +338,8 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ER
|
|||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
|
||||
github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
|
@ -347,8 +349,11 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-ldap/ldap/v3 v3.1.10 h1:7WsKqasmPThNvdl0Q5GPpbTDD/ZD98CfuawrMIuh7qQ=
|
||||
github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 h1:sfz1YppV05y4sYaW7kXZtrocU/+vimnIWt4cxAYh7+o=
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3/go.mod h1:ZXFhGda43Z2TVbfGZefXyMJzsDHhCh0go3bZUcwTx7o=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
|
@ -665,6 +670,8 @@ github.com/hashicorp/vault-plugin-secrets-kv v0.7.0 h1:Sq5CmKWxQu+MtO6AXYM+STPHG
|
|||
github.com/hashicorp/vault-plugin-secrets-kv v0.7.0/go.mod h1:B/Cybh5aVF7LNAMHwVBxY8t7r2eL0C6HVGgTyP4nKK4=
|
||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0 h1:uTtKxt5qfwTj6PqwnwPdU0fg1lIaaoqTtauuNpI2Epc=
|
||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0/go.mod h1:JOqn2mWJJbTp9NaC0CSCc3q5HQA99LfeSqgpC3YS+oA=
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798 h1:G3S7rF/zHfQnYZglk+WvjzBuJyjQAnP0xdGL/4i3jzM=
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798/go.mod h1:GiFI8Bxwx3+fn0A3SyVp9XdYQhm3cOgN8GzwKxyJ9So=
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.3.0 h1:aDdWZMdr93OtwZRE3TPKJyZgY6ZTe09G7bb2GL1HeAo=
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap v0.3.0/go.mod h1:pRE6pJzEVTSn3reeEn6YOV73R/6PcyylwQBz33zZKds=
|
||||
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw=
|
||||
|
|
|
@ -160,6 +160,10 @@ func PrintBytes(out io.Writer, buf []byte, indent string) {
|
|||
}
|
||||
}
|
||||
|
||||
func WritePacket(out io.Writer, p *Packet) {
|
||||
printPacket(out, p, 0, false)
|
||||
}
|
||||
|
||||
func PrintPacket(p *Packet) {
|
||||
printPacket(os.Stdout, p, 0, false)
|
||||
}
|
||||
|
@ -468,6 +472,22 @@ func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description
|
|||
return p
|
||||
}
|
||||
|
||||
// NewLDAPBoolean returns a RFC 4511-compliant Boolean packet
|
||||
func NewLDAPBoolean(Value bool, Description string) *Packet {
|
||||
intValue := int64(0)
|
||||
|
||||
if Value {
|
||||
intValue = 255
|
||||
}
|
||||
|
||||
p := Encode(ClassUniversal, TypePrimitive, TagBoolean, nil, Description)
|
||||
|
||||
p.Value = Value
|
||||
p.Data.Write(encodeInteger(intValue))
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
|
||||
p := Encode(ClassType, TagType, Tag, nil, Description)
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -0,0 +1,22 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.11
|
||||
- 1.13
|
||||
- 1.14
|
||||
- tip
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
go_import_path: github.com/go-ldap/ldif
|
||||
install:
|
||||
- go get github.com/go-ldap/ldap/v3
|
||||
- go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
|
||||
- go get golang.org/x/lint/golint || true
|
||||
- go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
|
||||
- go build -v ./...
|
||||
script:
|
||||
- make test
|
||||
- make fmt
|
||||
- make vet
|
||||
- if [ "${TRAVIS_GO_VERSION}" != "1.6" ]; then make lint; fi
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 go-ldap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,41 @@
|
|||
.PHONY: default install build test quicktest fmt vet lint
|
||||
|
||||
default: fmt vet lint build quicktest
|
||||
|
||||
install:
|
||||
go get -t -v ./...
|
||||
|
||||
build:
|
||||
go build -v ./...
|
||||
|
||||
test:
|
||||
go test -v -cover ./...
|
||||
|
||||
quicktest:
|
||||
go test ./...
|
||||
|
||||
# Capture output and force failure when there is non-empty output
|
||||
fmt:
|
||||
@echo gofmt -l .
|
||||
@OUTPUT=`gofmt -l . 2>&1`; \
|
||||
if [ "$$OUTPUT" ]; then \
|
||||
echo "gofmt must be run on the following files:"; \
|
||||
echo "$$OUTPUT"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Only run on go1.5+
|
||||
vet:
|
||||
go vet -vettool=$(which shadow) -atomic -bool -copylocks -nilfunc -printf -rangeloops -unreachable -unsafeptr -unusedresult .
|
||||
|
||||
# https://github.com/golang/lint
|
||||
# go get github.com/golang/lint/golint
|
||||
# Capture output and force failure when there is non-empty output
|
||||
lint:
|
||||
@echo golint ./...
|
||||
@OUTPUT=`golint ./... 2>&1`; \
|
||||
if [ "$$OUTPUT" ]; then \
|
||||
echo "golint errors:"; \
|
||||
echo "$$OUTPUT"; \
|
||||
exit 1; \
|
||||
fi
|
|
@ -0,0 +1,20 @@
|
|||
# ldif
|
||||
|
||||
Utilities for working with ldif data. This implements most of RFC 2849.
|
||||
|
||||
## Change Entries
|
||||
|
||||
Support for moddn / modrdn changes is missing (in Unmarshal and
|
||||
Marshal) - github.com/go-ldap/ldap/v3 does not support it currently
|
||||
|
||||
## Controls
|
||||
|
||||
Only simple controls without control value are supported, currently
|
||||
just
|
||||
Manage DSA IT - oid: 2.16.840.1.113730.3.4.2
|
||||
|
||||
## URLs
|
||||
|
||||
URL schemes in an LDIF like
|
||||
jpegPhoto;binary:< file:///usr/share/photos/someone.jpg
|
||||
are only supported for the "file" scheme like in the example above
|
|
@ -0,0 +1,57 @@
|
|||
package ldif
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
// Apply sends the LDIF entries to the server and does the changes as
|
||||
// given by the entries.
|
||||
//
|
||||
// All *ldap.Entry are converted to an *ldap.AddRequest.
|
||||
//
|
||||
// By default, it returns on the first error. To continue with applying the
|
||||
// LDIF, set the continueOnErr argument to true - in this case the errors
|
||||
// are logged with log.Printf()
|
||||
func (l *LDIF) Apply(conn ldap.Client, continueOnErr bool) error {
|
||||
for _, entry := range l.Entries {
|
||||
switch {
|
||||
case entry.Entry != nil:
|
||||
add := ldap.NewAddRequest(entry.Entry.DN, entry.Add.Controls)
|
||||
for _, attr := range entry.Entry.Attributes {
|
||||
add.Attribute(attr.Name, attr.Values)
|
||||
}
|
||||
entry.Add = add
|
||||
fallthrough
|
||||
case entry.Add != nil:
|
||||
if err := conn.Add(entry.Add); err != nil {
|
||||
if continueOnErr {
|
||||
log.Printf("ERROR: Failed to add %s: %s", entry.Add.DN, err)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to add %s: %s", entry.Add.DN, err)
|
||||
}
|
||||
|
||||
case entry.Del != nil:
|
||||
if err := conn.Del(entry.Del); err != nil {
|
||||
if continueOnErr {
|
||||
log.Printf("ERROR: Failed to delete %s: %s", entry.Del.DN, err)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to delete %s: %s", entry.Del.DN, err)
|
||||
}
|
||||
|
||||
case entry.Modify != nil:
|
||||
if err := conn.Modify(entry.Modify); err != nil {
|
||||
if continueOnErr {
|
||||
log.Printf("ERROR: Failed to modify %s: %s", entry.Modify.DN, err)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to modify %s: %s", entry.Modify.DN, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package ldif contains utilities for working with ldif data
|
||||
package ldif
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/go-ldap/ldif
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect
|
||||
github.com/go-ldap/ldap/v3 v3.1.7
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
|
||||
github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7 h1:aHjuWTgZsnxjMgqzx0JHwNqz4jBYZTcNarbPFkW1Oww=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
|
@ -0,0 +1,534 @@
|
|||
// Package ldif contains an LDIF parser and marshaller (RFC 2849).
|
||||
package ldif
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
// Entry is one entry in the LDIF
|
||||
type Entry struct {
|
||||
Entry *ldap.Entry
|
||||
Add *ldap.AddRequest
|
||||
Del *ldap.DelRequest
|
||||
Modify *ldap.ModifyRequest
|
||||
}
|
||||
|
||||
// The LDIF struct is used for parsing an LDIF. The Controls
|
||||
// is used to tell the parser to ignore any controls found
|
||||
// when parsing (default: false to ignore the controls).
|
||||
// FoldWidth is used for the line lenght when marshalling.
|
||||
type LDIF struct {
|
||||
Entries []*Entry
|
||||
Version int
|
||||
changeType string
|
||||
FoldWidth int
|
||||
Controls bool
|
||||
firstEntry bool
|
||||
}
|
||||
|
||||
// The ParseError holds the error message and the line in the ldif
|
||||
// where the error occurred.
|
||||
type ParseError struct {
|
||||
Line int
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *ParseError) Error() string {
|
||||
return fmt.Sprintf("Error in line %d: %s", e.Line, e.Message)
|
||||
}
|
||||
|
||||
var cr byte = '\x0D'
|
||||
var lf byte = '\x0A'
|
||||
var sep = string([]byte{cr, lf})
|
||||
var comment byte = '#'
|
||||
var space byte = ' '
|
||||
var spaces = string(space)
|
||||
|
||||
// Parse wraps Unmarshal to parse an LDIF from a string
|
||||
func Parse(str string) (l *LDIF, err error) {
|
||||
buf := bytes.NewBuffer([]byte(str))
|
||||
l = &LDIF{}
|
||||
err = Unmarshal(buf, l)
|
||||
return
|
||||
}
|
||||
|
||||
// ParseWithControls wraps Unmarshal to parse an LDIF from
|
||||
// a string, controls are added to change records
|
||||
func ParseWithControls(str string) (l *LDIF, err error) {
|
||||
buf := bytes.NewBuffer([]byte(str))
|
||||
l = &LDIF{Controls: true}
|
||||
err = Unmarshal(buf, l)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal parses the LDIF from the given io.Reader into the LDIF struct.
|
||||
// The caller is responsible for closing the io.Reader if that is
|
||||
// needed.
|
||||
func Unmarshal(r io.Reader, l *LDIF) (err error) {
|
||||
if r == nil {
|
||||
return &ParseError{Line: 0, Message: "No reader present"}
|
||||
}
|
||||
curLine := 0
|
||||
l.Version = 0
|
||||
l.changeType = ""
|
||||
isComment := false
|
||||
|
||||
reader := bufio.NewReader(r)
|
||||
|
||||
var lines []string
|
||||
var line, nextLine string
|
||||
l.firstEntry = true
|
||||
|
||||
for {
|
||||
curLine++
|
||||
nextLine, err = reader.ReadString(lf)
|
||||
nextLine = strings.TrimRight(nextLine, sep)
|
||||
|
||||
switch err {
|
||||
case nil, io.EOF:
|
||||
switch len(nextLine) {
|
||||
case 0:
|
||||
if len(line) == 0 && err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if len(line) == 0 && len(lines) == 0 {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
entry, perr := l.parseEntry(lines)
|
||||
if perr != nil {
|
||||
return &ParseError{Line: curLine, Message: perr.Error()}
|
||||
}
|
||||
l.Entries = append(l.Entries, entry)
|
||||
line = ""
|
||||
lines = []string{}
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
switch nextLine[0] {
|
||||
case comment:
|
||||
isComment = true
|
||||
continue
|
||||
|
||||
case space:
|
||||
if isComment {
|
||||
continue
|
||||
}
|
||||
line += nextLine[1:]
|
||||
continue
|
||||
|
||||
default:
|
||||
isComment = false
|
||||
if len(line) != 0 {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
line = nextLine
|
||||
continue
|
||||
}
|
||||
}
|
||||
default:
|
||||
return &ParseError{Line: curLine, Message: err.Error()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LDIF) parseEntry(lines []string) (entry *Entry, err error) {
|
||||
if len(lines) == 0 {
|
||||
return nil, errors.New("empty entry?")
|
||||
}
|
||||
|
||||
if l.firstEntry && strings.HasPrefix(lines[0], "version:") {
|
||||
l.firstEntry = false
|
||||
line := strings.TrimLeft(lines[0][8:], spaces)
|
||||
if l.Version, err = strconv.Atoi(line); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l.Version != 1 {
|
||||
return nil, errors.New("Invalid version spec " + string(line))
|
||||
}
|
||||
|
||||
l.Version = 1
|
||||
if len(lines) == 1 {
|
||||
return nil, nil
|
||||
}
|
||||
lines = lines[1:]
|
||||
}
|
||||
l.firstEntry = false
|
||||
|
||||
if len(lines) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(lines[0], "dn:") {
|
||||
return nil, errors.New("missing 'dn:'")
|
||||
}
|
||||
_, val, err := l.parseLine(lines[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dn := val
|
||||
|
||||
if len(lines) == 1 {
|
||||
return nil, errors.New("only a dn: line")
|
||||
}
|
||||
lines = lines[1:]
|
||||
|
||||
var controls []ldap.Control
|
||||
controls, lines, err = l.parseControls(lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lines[0], "changetype:") {
|
||||
_, val, err := l.parseLine(lines[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.changeType = val
|
||||
if len(lines) > 1 {
|
||||
lines = lines[1:]
|
||||
}
|
||||
}
|
||||
switch l.changeType {
|
||||
case "":
|
||||
if len(controls) != 0 {
|
||||
return nil, errors.New("controls found without changetype")
|
||||
}
|
||||
attrs, err := l.parseAttrs(lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Entry{Entry: ldap.NewEntry(dn, attrs)}, nil
|
||||
|
||||
case "add":
|
||||
attrs, err := l.parseAttrs(lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// FIXME: controls for add - see https://github.com/go-ldap/ldap/issues/81
|
||||
add := ldap.NewAddRequest(dn, controls)
|
||||
for attr, vals := range attrs {
|
||||
add.Attribute(attr, vals)
|
||||
}
|
||||
return &Entry{Add: add}, nil
|
||||
|
||||
case "delete":
|
||||
if len(lines) > 1 {
|
||||
return nil, errors.New("no attributes allowed for changetype delete")
|
||||
}
|
||||
return &Entry{Del: ldap.NewDelRequest(dn, controls)}, nil
|
||||
|
||||
case "modify":
|
||||
// FIXME: controls for modify - see https://github.com/go-ldap/ldap/issues/81
|
||||
mod := ldap.NewModifyRequest(dn, controls)
|
||||
var op, attribute string
|
||||
var values []string
|
||||
if lines[len(lines)-1] != "-" {
|
||||
return nil, errors.New("modify request does not close with a single dash")
|
||||
}
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if lines[i] == "-" {
|
||||
switch op {
|
||||
case "":
|
||||
return nil, fmt.Errorf("empty operation")
|
||||
case "add":
|
||||
mod.Add(attribute, values)
|
||||
op = ""
|
||||
attribute = ""
|
||||
values = nil
|
||||
case "replace":
|
||||
mod.Replace(attribute, values)
|
||||
op = ""
|
||||
attribute = ""
|
||||
values = nil
|
||||
case "delete":
|
||||
mod.Delete(attribute, values)
|
||||
op = ""
|
||||
attribute = ""
|
||||
values = nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operation %s in modify request", op)
|
||||
}
|
||||
continue
|
||||
}
|
||||
attr, val, err := l.parseLine(lines[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if op == "" {
|
||||
op = attr
|
||||
attribute = val
|
||||
} else {
|
||||
if attr != attribute {
|
||||
return nil, fmt.Errorf("invalid attribute %s in %s request for %s", attr, op, attribute)
|
||||
}
|
||||
values = append(values, val)
|
||||
}
|
||||
}
|
||||
return &Entry{Modify: mod}, nil
|
||||
|
||||
case "moddn", "modrdn":
|
||||
return nil, fmt.Errorf("unsupported changetype %s", l.changeType)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid changetype %s", l.changeType)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LDIF) parseAttrs(lines []string) (map[string][]string, error) {
|
||||
attrs := make(map[string][]string)
|
||||
for i := 0; i < len(lines); i++ {
|
||||
attr, val, err := l.parseLine(lines[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrs[attr] = append(attrs[attr], val)
|
||||
}
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func (l *LDIF) parseLine(line string) (attr, val string, err error) {
|
||||
off := 0
|
||||
for len(line) > off && line[off] != ':' {
|
||||
off++
|
||||
if off >= len(line) {
|
||||
err = fmt.Errorf("Missing : in line `%s`", line)
|
||||
return
|
||||
}
|
||||
}
|
||||
if off == len(line) {
|
||||
err = fmt.Errorf("Missing : in the line `%s`", line)
|
||||
return
|
||||
}
|
||||
|
||||
if off > len(line)-2 {
|
||||
err = errors.New("empty value")
|
||||
// FIXME: this is allowed for some attributes, e.g. seeAlso
|
||||
return
|
||||
}
|
||||
|
||||
attr = line[0:off]
|
||||
if err = validAttr(attr); err != nil {
|
||||
attr = ""
|
||||
val = ""
|
||||
return
|
||||
}
|
||||
|
||||
switch line[off+1] {
|
||||
case ':':
|
||||
val, err = decodeBase64(strings.TrimLeft(line[off+2:], spaces))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case '<':
|
||||
val, err = readURLValue(strings.TrimLeft(line[off+2:], spaces))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
val = strings.TrimLeft(line[off+1:], spaces)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LDIF) parseControls(lines []string) ([]ldap.Control, []string, error) {
|
||||
var controls []ldap.Control
|
||||
for {
|
||||
if !strings.HasPrefix(lines[0], "control:") {
|
||||
break
|
||||
}
|
||||
if !l.Controls {
|
||||
if len(lines) == 1 {
|
||||
return nil, nil, errors.New("only controls found")
|
||||
}
|
||||
lines = lines[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
_, val, err := l.parseLine(lines[0])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var oid, ctrlValue string
|
||||
criticality := false
|
||||
|
||||
parts := strings.SplitN(val, " ", 3)
|
||||
if err = validOID(parts[0]); err != nil {
|
||||
return nil, nil, fmt.Errorf("%s is not a valid oid: %s", oid, err)
|
||||
}
|
||||
oid = parts[0]
|
||||
|
||||
if len(parts) > 1 {
|
||||
switch parts[1] {
|
||||
case "true":
|
||||
criticality = true
|
||||
if len(parts) > 2 {
|
||||
parts[1] = parts[2]
|
||||
parts = parts[0:2]
|
||||
}
|
||||
case "false":
|
||||
criticality = false
|
||||
if len(parts) > 2 {
|
||||
parts[1] = parts[2]
|
||||
parts = parts[0:2]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
ctrlValue = parts[1]
|
||||
}
|
||||
if ctrlValue == "" {
|
||||
switch oid {
|
||||
case ldap.ControlTypeManageDsaIT:
|
||||
controls = append(controls, &ldap.ControlManageDsaIT{Criticality: criticality})
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported control found: %s", oid)
|
||||
}
|
||||
} else {
|
||||
switch ctrlValue[0] { // where is this documented?
|
||||
case ':':
|
||||
if len(ctrlValue) == 1 {
|
||||
return nil, nil, errors.New("missing value for base64 encoded control value")
|
||||
}
|
||||
ctrlValue, err = decodeBase64(strings.TrimLeft(ctrlValue[1:], spaces))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ctrlValue == "" {
|
||||
return nil, nil, errors.New("base64 decoded to empty value")
|
||||
}
|
||||
|
||||
case '<':
|
||||
if len(ctrlValue) == 1 {
|
||||
return nil, nil, errors.New("missing value for url control value")
|
||||
}
|
||||
ctrlValue, err = readURLValue(strings.TrimLeft(ctrlValue[1:], spaces))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ctrlValue == "" {
|
||||
return nil, nil, errors.New("url resolved to an empty value")
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
// convert ctrlValue to *ber.Packet and decode with something like
|
||||
// ctrl := ldap.DecodeControl()
|
||||
// ... FIXME: the controls need a Decode() interface
|
||||
// so we can just do a
|
||||
// ctrl := ldap.ControlByOID(oid) // returns an empty &ControlSomething{}
|
||||
// ctrl.Decode((*ber.Packet)(ctrlValue))
|
||||
// ctrl.Criticality = criticality
|
||||
// that should be usable in github.com/go-ldap/ldap/control.go also
|
||||
// to decode the incoming control
|
||||
// controls = append(controls, ctrl)
|
||||
return nil, nil, fmt.Errorf("controls with values are not supported, oid: %s", oid)
|
||||
}
|
||||
|
||||
if len(lines) == 1 {
|
||||
return nil, nil, errors.New("only controls found")
|
||||
}
|
||||
lines = lines[1:]
|
||||
}
|
||||
return controls, lines, nil
|
||||
}
|
||||
|
||||
func readURLValue(val string) (string, error) {
|
||||
u, err := url.Parse(val)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse URL: %s", err)
|
||||
}
|
||||
if u.Scheme != "file" {
|
||||
return "", fmt.Errorf("unsupported URL scheme %s", u.Scheme)
|
||||
}
|
||||
data, err := ioutil.ReadFile(toPath(u))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read %s: %s", u.Path, err)
|
||||
}
|
||||
val = string(data) // FIXME: safe?
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func decodeBase64(enc string) (string, error) {
|
||||
dec := make([]byte, base64.StdEncoding.DecodedLen(len([]byte(enc))))
|
||||
n, err := base64.StdEncoding.Decode(dec, []byte(enc))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(dec[:n]), nil
|
||||
}
|
||||
|
||||
func validOID(oid string) error {
|
||||
lastDot := true
|
||||
for _, c := range oid {
|
||||
switch {
|
||||
case c == '.' && lastDot:
|
||||
return errors.New("OID with at least 2 consecutive dots")
|
||||
case c == '.':
|
||||
lastDot = true
|
||||
case c >= '0' && c <= '9':
|
||||
lastDot = false
|
||||
default:
|
||||
return errors.New("Invalid character in OID")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validAttr(attr string) error {
|
||||
if len(attr) == 0 {
|
||||
return errors.New("empty attribute name")
|
||||
}
|
||||
switch {
|
||||
case attr[0] >= 'A' && attr[0] <= 'Z':
|
||||
// A-Z
|
||||
case attr[0] >= 'a' && attr[0] <= 'z':
|
||||
// a-z
|
||||
default:
|
||||
if attr[0] >= '0' && attr[0] <= '9' {
|
||||
return validOID(attr)
|
||||
}
|
||||
return errors.New("invalid first character in attribute")
|
||||
}
|
||||
for i := 1; i < len(attr); i++ {
|
||||
c := attr[i]
|
||||
switch {
|
||||
case c >= '0' && c <= '9':
|
||||
case c >= 'A' && c <= 'Z':
|
||||
case c >= 'a' && c <= 'z':
|
||||
case c == '-':
|
||||
case c == ';':
|
||||
default:
|
||||
return errors.New("invalid character in attribute name")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllEntries returns all *ldap.Entries in the LDIF
|
||||
func (l *LDIF) AllEntries() (entries []*ldap.Entry) {
|
||||
for _, entry := range l.Entries {
|
||||
if entry.Entry != nil {
|
||||
entries = append(entries, entry.Entry)
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
package ldif
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
var foldWidth = 76
|
||||
|
||||
// ErrMixed is the error, that we cannot mix change records and content
|
||||
// records in one LDIF
|
||||
var ErrMixed = errors.New("cannot mix change records and content records")
|
||||
|
||||
// Marshal returns an LDIF string from the given LDIF.
|
||||
//
|
||||
// The default line lenght is 76 characters. This can be changed by setting
|
||||
// the fw parameter to something else than 0.
|
||||
// For a fold width < 0, no folding will be done, with 0, the default is used.
|
||||
func Marshal(l *LDIF) (data string, err error) {
|
||||
hasEntry := false
|
||||
hasChange := false
|
||||
|
||||
if l.Version > 0 {
|
||||
data = "version: 1\n"
|
||||
}
|
||||
|
||||
fw := l.FoldWidth
|
||||
if fw == 0 {
|
||||
fw = foldWidth
|
||||
}
|
||||
|
||||
for _, e := range l.Entries {
|
||||
switch {
|
||||
case e.Add != nil:
|
||||
hasChange = true
|
||||
if hasEntry {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Add.DN, fw) + "\n"
|
||||
data += "changetype: add\n"
|
||||
for _, add := range e.Add.Attributes {
|
||||
if len(add.Vals) == 0 {
|
||||
return "", errors.New("changetype 'add' requires non empty value list")
|
||||
}
|
||||
for _, v := range add.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(add.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
case e.Del != nil:
|
||||
hasChange = true
|
||||
if hasEntry {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Del.DN, fw) + "\n"
|
||||
data += "changetype: delete\n"
|
||||
|
||||
case e.Modify != nil:
|
||||
hasChange = true
|
||||
if hasEntry {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Modify.DN, fw) + "\n"
|
||||
data += "changetype: modify\n"
|
||||
for _, mod := range e.Modify.Changes {
|
||||
switch mod.Operation {
|
||||
// add operation - https://tools.ietf.org/html/rfc4511#section-4.6
|
||||
case 0:
|
||||
if len(mod.Modification.Vals) == 0 {
|
||||
return "", errors.New("changetype 'modify', op 'add' requires non empty value list")
|
||||
}
|
||||
|
||||
data += "add: " + mod.Modification.Type + "\n"
|
||||
for _, v := range mod.Modification.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(mod.Modification.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
data += "-\n"
|
||||
// delete operation - https://tools.ietf.org/html/rfc4511#section-4.6
|
||||
case 1:
|
||||
data += "delete: " + mod.Modification.Type + "\n"
|
||||
for _, v := range mod.Modification.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(mod.Modification.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
data += "-\n"
|
||||
// replace operation - https://tools.ietf.org/html/rfc4511#section-4.6
|
||||
case 2:
|
||||
if len(mod.Modification.Vals) == 0 {
|
||||
return "", errors.New("changetype 'modify', op 'replace' requires non empty value list")
|
||||
}
|
||||
data += "replace: " + mod.Modification.Type + "\n"
|
||||
for _, v := range mod.Modification.Vals {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(mod.Modification.Type+col+ev, fw) + "\n"
|
||||
}
|
||||
data += "-\n"
|
||||
default:
|
||||
return "", fmt.Errorf("invalid type %s in modify request", mod.Modification.Type)
|
||||
}
|
||||
}
|
||||
default:
|
||||
hasEntry = true
|
||||
if hasChange {
|
||||
return "", ErrMixed
|
||||
}
|
||||
data += foldLine("dn: "+e.Entry.DN, fw) + "\n"
|
||||
for _, av := range e.Entry.Attributes {
|
||||
for _, v := range av.Values {
|
||||
ev, t := encodeValue(v)
|
||||
col := ": "
|
||||
if t {
|
||||
col = ":: "
|
||||
}
|
||||
data += foldLine(av.Name+col+ev, fw) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
data += "\n"
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func encodeValue(value string) (string, bool) {
|
||||
required := false
|
||||
for _, r := range value {
|
||||
if r < ' ' || r > '~' { // ~ = 0x7E, <DEL> = 0x7F
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !required {
|
||||
return value, false
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString([]byte(value)), true
|
||||
}
|
||||
|
||||
func foldLine(line string, fw int) (folded string) {
|
||||
if fw < 0 {
|
||||
return line
|
||||
}
|
||||
if len(line) <= fw {
|
||||
return line
|
||||
}
|
||||
|
||||
folded = line[:fw] + "\n"
|
||||
line = line[fw:]
|
||||
|
||||
for len(line) > fw-1 {
|
||||
folded += " " + line[:fw-1] + "\n"
|
||||
line = line[fw-1:]
|
||||
}
|
||||
|
||||
if len(line) > 0 {
|
||||
folded += " " + line
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dump writes the given entries to the io.Writer.
|
||||
//
|
||||
// The entries argument can be *ldap.Entry or a mix of *ldap.AddRequest,
|
||||
// *ldap.DelRequest, *ldap.ModifyRequest and *ldap.ModifyDNRequest or slices
|
||||
// of any of those.
|
||||
//
|
||||
// See Marshal() for the fw argument.
|
||||
func Dump(fh io.Writer, fw int, entries ...interface{}) error {
|
||||
l, err := ToLDIF(entries...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.FoldWidth = fw
|
||||
str, err := Marshal(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fh.Write([]byte(str))
|
||||
return err
|
||||
}
|
||||
|
||||
// ToLDIF puts the given arguments in an LDIF struct and returns it.
|
||||
//
|
||||
// The entries argument can be *ldap.Entry or a mix of *ldap.AddRequest,
|
||||
// *ldap.DelRequest, *ldap.ModifyRequest and *ldap.ModifyDNRequest or slices
|
||||
// of any of those.
|
||||
func ToLDIF(entries ...interface{}) (*LDIF, error) {
|
||||
l := &LDIF{}
|
||||
for _, e := range entries {
|
||||
switch e.(type) {
|
||||
case []*ldap.Entry:
|
||||
for _, en := range e.([]*ldap.Entry) {
|
||||
l.Entries = append(l.Entries, &Entry{Entry: en})
|
||||
}
|
||||
|
||||
case *ldap.Entry:
|
||||
l.Entries = append(l.Entries, &Entry{Entry: e.(*ldap.Entry)})
|
||||
|
||||
case []*ldap.AddRequest:
|
||||
for _, en := range e.([]*ldap.AddRequest) {
|
||||
l.Entries = append(l.Entries, &Entry{Add: en})
|
||||
}
|
||||
|
||||
case *ldap.AddRequest:
|
||||
l.Entries = append(l.Entries, &Entry{Add: e.(*ldap.AddRequest)})
|
||||
|
||||
case []*ldap.DelRequest:
|
||||
for _, en := range e.([]*ldap.DelRequest) {
|
||||
l.Entries = append(l.Entries, &Entry{Del: en})
|
||||
}
|
||||
|
||||
case *ldap.DelRequest:
|
||||
l.Entries = append(l.Entries, &Entry{Del: e.(*ldap.DelRequest)})
|
||||
|
||||
case []*ldap.ModifyRequest:
|
||||
for _, en := range e.([]*ldap.ModifyRequest) {
|
||||
l.Entries = append(l.Entries, &Entry{Modify: en})
|
||||
}
|
||||
case *ldap.ModifyRequest:
|
||||
l.Entries = append(l.Entries, &Entry{Modify: e.(*ldap.ModifyRequest)})
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type %T", e)
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// +build !windows
|
||||
|
||||
package ldif
|
||||
|
||||
import "net/url"
|
||||
|
||||
func toPath(u *url.URL) string {
|
||||
return u.Path
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ldif
|
||||
|
||||
import "net/url"
|
||||
import "strings"
|
||||
|
||||
// toPath get the file path
|
||||
// We use ioutil.ReadFile to read the content file.
|
||||
// On windows,
|
||||
// https://github.com/golang/go/blob/95a11c7381e01fdaaf34e25b82db0632081ab74e/src/net/url/url_test.go#L283-L292
|
||||
func toPath(u *url.URL) string {
|
||||
return strings.TrimPrefix(u.Path, "/")
|
||||
}
|
|
@ -36,15 +36,19 @@ func Backend(client ldapClient) *backend {
|
|||
},
|
||||
},
|
||||
Paths: framework.PathAppend(
|
||||
b.pathListRoles(),
|
||||
b.pathRoles(),
|
||||
b.pathCredsCreate(),
|
||||
b.pathListStaticRoles(),
|
||||
b.pathStaticRoles(),
|
||||
b.pathStaticCredsCreate(),
|
||||
b.pathRotateCredentials(),
|
||||
b.pathConfig(),
|
||||
|
||||
b.pathDynamicRoles(),
|
||||
),
|
||||
InitializeFunc: b.initialize,
|
||||
|
||||
Secrets: []*framework.Secret{},
|
||||
Secrets: []*framework.Secret{
|
||||
dynamicSecretCreds(&b),
|
||||
},
|
||||
Clean: b.clean,
|
||||
BackendType: logical.TypeLogical,
|
||||
}
|
||||
|
|
|
@ -3,13 +3,19 @@ package openldap
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/go-ldap/ldif"
|
||||
"github.com/hashicorp/vault-plugin-secrets-openldap/client"
|
||||
)
|
||||
|
||||
type ldapClient interface {
|
||||
Add(conf *client.Config, req *ldap.AddRequest) error
|
||||
Get(conf *client.Config, dn string) (*client.Entry, error)
|
||||
Del(conf *client.Config, req *ldap.DelRequest) error
|
||||
UpdatePassword(conf *client.Config, dn string, newPassword string) error
|
||||
UpdateRootPassword(conf *client.Config, newPassword string) error
|
||||
|
||||
Execute(conf *client.Config, entries []*ldif.Entry, continueOnError bool) (err error)
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
|
@ -18,6 +24,8 @@ func NewClient() *Client {
|
|||
}
|
||||
}
|
||||
|
||||
var _ ldapClient = (*Client)(nil)
|
||||
|
||||
type Client struct {
|
||||
ldap client.Client
|
||||
}
|
||||
|
@ -58,3 +66,15 @@ func (c *Client) UpdateRootPassword(conf *client.Config, newPassword string) err
|
|||
|
||||
return c.ldap.UpdatePassword(conf, conf.BindDN, newValues, filters)
|
||||
}
|
||||
|
||||
func (c *Client) Add(conf *client.Config, req *ldap.AddRequest) error {
|
||||
return c.ldap.Add(conf, req)
|
||||
}
|
||||
|
||||
func (c *Client) Del(conf *client.Config, req *ldap.DelRequest) error {
|
||||
return c.ldap.Del(conf, req)
|
||||
}
|
||||
|
||||
func (c *Client) Execute(conf *client.Config, entries []*ldif.Entry, continueOnError bool) (err error) {
|
||||
return c.ldap.Execute(conf, entries, continueOnError)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/go-ldap/ldif"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/sdk/helper/ldaputil"
|
||||
)
|
||||
|
||||
|
@ -143,3 +145,121 @@ func shouldTryLastPwd(lastPwd string, lastBindPasswordRotation time.Time) bool {
|
|||
}
|
||||
return lastBindPasswordRotation.Add(10 * time.Minute).After(time.Now())
|
||||
}
|
||||
|
||||
func (c *Client) Add(cfg *Config, req *ldap.AddRequest) error {
|
||||
if req == nil {
|
||||
return fmt.Errorf("invalid request: request is nil")
|
||||
}
|
||||
if req.DN == "" {
|
||||
return fmt.Errorf("invalid request: DN is empty")
|
||||
}
|
||||
conn, err := c.ldap.DialLDAP(cfg.ConfigEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := bind(cfg, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Add(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Del(cfg *Config, req *ldap.DelRequest) error {
|
||||
if req == nil {
|
||||
return fmt.Errorf("invalid request: request is nil")
|
||||
}
|
||||
if req.DN == "" {
|
||||
return fmt.Errorf("invalid request: DN is empty")
|
||||
}
|
||||
conn, err := c.ldap.DialLDAP(cfg.ConfigEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := bind(cfg, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.Del(req)
|
||||
}
|
||||
|
||||
func (c *Client) Execute(cfg *Config, entries []*ldif.Entry, continueOnFailure bool) (err error) {
|
||||
if len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := c.ldap.DialLDAP(cfg.ConfigEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := bind(cfg, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merr := new(multierror.Error)
|
||||
for _, entry := range entries {
|
||||
if entry == nil {
|
||||
// Skip entries that are nil since they don't indicate an error in execution. Since these entries
|
||||
// are usually coming from an ldif parse, this should generally not happen so it's mainly to
|
||||
// protect against developers from screwing up and creating a panic due to a nil reference
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
switch {
|
||||
case entry.Entry != nil:
|
||||
addReq := coerceToAddRequest(entry.Entry)
|
||||
err = errorf("failed to run AddRequest: %w", conn.Add(addReq))
|
||||
case entry.Add != nil:
|
||||
err = errorf("failed to run AddRequest: %w", conn.Add(entry.Add))
|
||||
case entry.Modify != nil:
|
||||
err = errorf("failed to run ModifyRequest: %w", conn.Modify(entry.Modify))
|
||||
case entry.Del != nil:
|
||||
err = errorf("failed to run DelRequest: %w", conn.Del(entry.Del))
|
||||
default:
|
||||
err = fmt.Errorf("unrecognized or missing LDIF command")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if continueOnFailure {
|
||||
merr = multierror.Append(merr, err)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return merr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func coerceToAddRequest(entry *ldap.Entry) *ldap.AddRequest {
|
||||
attributes := make([]ldap.Attribute, 0, len(entry.Attributes))
|
||||
for _, entryAttribute := range entry.Attributes {
|
||||
attribute := ldap.Attribute{
|
||||
Type: entryAttribute.Name,
|
||||
Vals: entryAttribute.Values,
|
||||
}
|
||||
attributes = append(attributes, attribute)
|
||||
}
|
||||
addReq := &ldap.AddRequest{
|
||||
DN: entry.DN,
|
||||
Attributes: attributes,
|
||||
Controls: nil,
|
||||
}
|
||||
return addReq
|
||||
}
|
||||
|
||||
func errorf(format string, wrappedErr error) error {
|
||||
if wrappedErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf(format, wrappedErr)
|
||||
}
|
||||
|
|
63
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/dynamic_role.go
generated
vendored
Normal file
63
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/dynamic_role.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
package openldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
type dynamicRole struct {
|
||||
// required fields
|
||||
Name string `json:"name" mapstructure:"name"`
|
||||
CreationLDIF string `json:"creation_ldif" mapstructure:"creation_ldif"`
|
||||
DeletionLDIF string `json:"deletion_ldif" mapstructure:"deletion_ldif"`
|
||||
|
||||
// optional fields
|
||||
RollbackLDIF string `json:"rollback_ldif" mapstructure:"rollback_ldif,omitempty"`
|
||||
UsernameTemplate string `json:"username_template,omitempty" mapstructure:"username_template,omitempty"`
|
||||
DefaultTTL time.Duration `json:"default_ttl,omitempty" mapstructure:"default_ttl,omitempty"`
|
||||
MaxTTL time.Duration `json:"max_ttl,omitempty" mapstructure:"max_ttl,omitempty"`
|
||||
}
|
||||
|
||||
func retrieveDynamicRole(ctx context.Context, s logical.Storage, roleName string) (*dynamicRole, error) {
|
||||
entry, err := s.Get(ctx, path.Join(dynamicRolePath, roleName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := new(dynamicRole)
|
||||
if err := entry.DecodeJSON(result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func storeDynamicRole(ctx context.Context, s logical.Storage, role *dynamicRole) error {
|
||||
if role.Name == "" {
|
||||
return fmt.Errorf("missing role name")
|
||||
}
|
||||
entry, err := logical.StorageEntryJSON(path.Join(dynamicRolePath, role.Name), role)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal storage entry: %w", err)
|
||||
}
|
||||
|
||||
err = s.Put(ctx, entry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store dynamic role: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDynamicRole(ctx context.Context, s logical.Storage, roleName string) error {
|
||||
if roleName == "" {
|
||||
return fmt.Errorf("missing role name")
|
||||
}
|
||||
return s.Delete(ctx, path.Join(dynamicRolePath, roleName))
|
||||
}
|
|
@ -4,11 +4,15 @@ go 1.13
|
|||
|
||||
require (
|
||||
github.com/go-ldap/ldap/v3 v3.1.10
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
|
||||
github.com/hashicorp/errwrap v1.0.0
|
||||
github.com/hashicorp/go-hclog v0.14.1
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200826195146-c03009a7e370
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200826195146-c03009a7e370
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.2
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/text v0.3.2
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
|||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
|
||||
github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro=
|
||||
github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
|
@ -60,12 +61,16 @@ github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq
|
|||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
|
||||
github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ=
|
||||
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-ldap/ldap/v3 v3.1.10 h1:7WsKqasmPThNvdl0Q5GPpbTDD/ZD98CfuawrMIuh7qQ=
|
||||
github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 h1:sfz1YppV05y4sYaW7kXZtrocU/+vimnIWt4cxAYh7+o=
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3/go.mod h1:ZXFhGda43Z2TVbfGZefXyMJzsDHhCh0go3bZUcwTx7o=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
|
@ -99,6 +104,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
|
@ -108,7 +115,6 @@ github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVo
|
|||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
|
@ -151,10 +157,9 @@ github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:eu
|
|||
github.com/hashicorp/vault/api v1.0.5-0.20200826195146-c03009a7e370 h1:Q0Nx5sfSDTDr1WjQhV8DCZC8e47EiRittzuhXq4ES2U=
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200826195146-c03009a7e370/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zGrWJW91rzRroyl5nRNqraaBe4d5hiKcVZuHM=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200826195146-c03009a7e370 h1:gw9lQAYoyNb9IKxuvtbLg9E6ehD+4NwdrShZmwAIw8A=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200826195146-c03009a7e370/go.mod h1:+S2qzS1Tex9JgbHxb/Jv7CdZyKydxqg09G/qVvyVmUc=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c h1:CSvbHEivYEK8njYzPB1Wn972h4U0z+xMGFZnTdVK+s4=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
|
@ -173,6 +178,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
|||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
@ -222,8 +229,8 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
|
|||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
|
@ -261,6 +268,7 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv
|
|||
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
|
@ -275,7 +283,6 @@ golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
@ -370,8 +377,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
|
281
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_dynamic_creds.go
generated
vendored
Normal file
281
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_dynamic_creds.go
generated
vendored
Normal file
|
@ -0,0 +1,281 @@
|
|||
package openldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-ldap/ldif"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault-plugin-secrets-openldap/client"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/template"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
func (b *backend) pathDynamicCredsRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("name").(string)
|
||||
|
||||
// Get the role and LDAP configs
|
||||
dRole, err := retrieveDynamicRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve dynamic role: %w", err)
|
||||
}
|
||||
if dRole == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
config, err := readConfig(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("missing OpenLDAP configuration")
|
||||
}
|
||||
|
||||
// Generate dynamic data
|
||||
username, err := generateUsername(req, dRole)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate username: %w", err)
|
||||
}
|
||||
password, err := b.GeneratePassword(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply the template & execute
|
||||
now := time.Now()
|
||||
exp := now.Add(dRole.DefaultTTL)
|
||||
templateData := dynamicTemplateData{
|
||||
Username: username,
|
||||
Password: password,
|
||||
DisplayName: req.DisplayName,
|
||||
RoleName: roleName,
|
||||
IssueTime: now.Format(time.RFC3339),
|
||||
IssueTimeSeconds: now.Unix(),
|
||||
ExpirationTime: exp.Format(time.RFC3339),
|
||||
ExpirationTimeSeconds: exp.Unix(),
|
||||
}
|
||||
dns, err := b.executeLDIF(config.LDAP, dRole.CreationLDIF, templateData, false)
|
||||
if err != nil {
|
||||
// Creation failed, attempt a rollback if one is specified
|
||||
if dRole.RollbackLDIF == "" {
|
||||
return nil, fmt.Errorf("failed to create user: %w", err)
|
||||
}
|
||||
|
||||
merr := multierror.Append(fmt.Errorf("failed to create user: %w", err))
|
||||
_, err = b.executeLDIF(config.LDAP, dRole.RollbackLDIF, templateData, true)
|
||||
if err != nil {
|
||||
merr = multierror.Append(fmt.Errorf("failed to roll back user creation: %w", err))
|
||||
}
|
||||
return nil, merr
|
||||
}
|
||||
respData := map[string]interface{}{
|
||||
"username": username,
|
||||
"password": password,
|
||||
"distinguished_names": dns,
|
||||
}
|
||||
internal := map[string]interface{}{
|
||||
"name": roleName,
|
||||
// Including the deletion_ldif in the event that the role is deleted while
|
||||
// leases are active otherwise leases will fail to revoke
|
||||
"deletion_ldif": dRole.DeletionLDIF,
|
||||
"template_data": templateData,
|
||||
}
|
||||
resp := b.Secret(secretCredsType).Response(respData, internal)
|
||||
resp.Secret.TTL = dRole.DefaultTTL
|
||||
resp.Secret.MaxTTL = dRole.MaxTTL
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// executeLDIF applies the template data against the LDIF template & executes the LDIF statements against the LDAP
|
||||
// server. If more than one statement is specified within the LDIF string, this will result in multiple operations
|
||||
// against the LDAP server. This is due to the fact that LDAP does not have transactions, nor any other form of
|
||||
// atomic operations across multiple LDIF entries. If `continueOnError` is false, this will exit immediately upon
|
||||
// any error occurring. If true, this will attempt to execute all of the specified LDIF statements and returns an error
|
||||
// upon completion if any occurred.
|
||||
func (b *backend) executeLDIF(config *client.Config, ldifTemplate string, templateData dynamicTemplateData, continueOnError bool) (dns []string, err error) {
|
||||
rawLDIF, err := applyTemplate(ldifTemplate, templateData)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to apply template: %w", err)
|
||||
}
|
||||
|
||||
// Parse the raw LDIF & run it against the LDAP client
|
||||
entries, err := ldif.Parse(rawLDIF)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse generated LDIF: %w", err)
|
||||
}
|
||||
|
||||
err = b.client.Execute(config, entries.Entries, continueOnError)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute statements: %w", err)
|
||||
}
|
||||
dns = getDNs(entries.Entries)
|
||||
return dns, nil
|
||||
}
|
||||
|
||||
func getDNs(entries []*ldif.Entry) []string {
|
||||
dns := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case entry.Entry != nil:
|
||||
dns = append(dns, entry.Entry.DN)
|
||||
case entry.Add != nil:
|
||||
dns = append(dns, entry.Add.DN)
|
||||
case entry.Modify != nil:
|
||||
dns = append(dns, entry.Modify.DN)
|
||||
case entry.Del != nil:
|
||||
dns = append(dns, entry.Del.DN)
|
||||
}
|
||||
}
|
||||
return dns
|
||||
}
|
||||
|
||||
func (b *backend) secretCredsRenew() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// Retrieve the role to ensure it still exists. If it doesn't, this will reject the renewal request.
|
||||
roleNameRaw, ok := req.Secret.InternalData["name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing role name")
|
||||
}
|
||||
|
||||
roleName := roleNameRaw.(string)
|
||||
dRole, err := retrieveDynamicRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve dynamic role: %w", err)
|
||||
}
|
||||
if dRole == nil {
|
||||
return nil, fmt.Errorf("unable to renew: role does not exist")
|
||||
}
|
||||
|
||||
// Update the default TTL & MaxTTL to the latest from the role in the event the role definition has changed
|
||||
secret := req.Secret
|
||||
secret.TTL = dRole.DefaultTTL
|
||||
secret.MaxTTL = dRole.MaxTTL
|
||||
|
||||
resp := &logical.Response{
|
||||
Secret: req.Secret,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) secretCredsRevoke() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
config, err := readConfig(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("missing OpenLDAP configuration")
|
||||
}
|
||||
|
||||
deletionTemplate, err := getString(req.Secret.InternalData, "deletion_ldif")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("broken internal data: unable to retrieve deletion_ldif: %w", err)
|
||||
}
|
||||
|
||||
if deletionTemplate == "" {
|
||||
return nil, fmt.Errorf("broken internal data: missing deletion_ldif")
|
||||
}
|
||||
|
||||
var templateData dynamicTemplateData
|
||||
rawTemplateData := req.Secret.InternalData["template_data"]
|
||||
switch td := rawTemplateData.(type) {
|
||||
case dynamicTemplateData:
|
||||
templateData = td
|
||||
case map[string]interface{}:
|
||||
err := mapstructure.WeakDecode(td, &templateData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode internal data: %w", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to revoke OpenLDAP dynamic credentials: unrecognized internal data type: %T", td)
|
||||
}
|
||||
|
||||
_, err = b.executeLDIF(config.LDAP, deletionTemplate, templateData, true)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type usernameTemplateData struct {
|
||||
DisplayName string
|
||||
RoleName string
|
||||
}
|
||||
|
||||
const defaultUsernameTemplate = "v_{{.DisplayName}}_{{.RoleName}}_{{random 10}}_{{unix_time}}"
|
||||
|
||||
func generateUsername(req *logical.Request, role *dynamicRole) (string, error) {
|
||||
usernameTemplate := role.UsernameTemplate
|
||||
if role.UsernameTemplate == "" {
|
||||
usernameTemplate = defaultUsernameTemplate
|
||||
}
|
||||
tmpl, err := template.NewTemplate(
|
||||
template.Template(usernameTemplate),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
usernameData := usernameTemplateData{
|
||||
DisplayName: req.DisplayName,
|
||||
RoleName: role.Name,
|
||||
}
|
||||
return tmpl.Generate(usernameData)
|
||||
}
|
||||
|
||||
type dynamicTemplateData struct {
|
||||
Username string
|
||||
Password string
|
||||
DisplayName string
|
||||
RoleName string
|
||||
IssueTime string
|
||||
IssueTimeSeconds int64
|
||||
ExpirationTime string
|
||||
ExpirationTimeSeconds int64
|
||||
}
|
||||
|
||||
func applyTemplate(rawTemplate string, data dynamicTemplateData) (string, error) {
|
||||
tmpl, err := template.NewTemplate(
|
||||
template.Template(rawTemplate),
|
||||
template.Function("utf16le", encodeUTF16LE),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse template: %w", err)
|
||||
}
|
||||
str, err := tmpl.Generate(data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute template: %w", err)
|
||||
}
|
||||
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func encodeUTF16LE(str string) (string, error) {
|
||||
enc := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
|
||||
return enc.String(str)
|
||||
}
|
||||
|
||||
func getString(m map[string]interface{}, key string) (string, error) {
|
||||
if m == nil {
|
||||
return "", fmt.Errorf("nil map")
|
||||
}
|
||||
|
||||
val, exists := m[key]
|
||||
if !exists {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
str, ok := val.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("key %s has %T value, not string", key, val)
|
||||
}
|
||||
return str, nil
|
||||
}
|
325
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_dynamic_roles.go
generated
vendored
Normal file
325
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_dynamic_roles.go
generated
vendored
Normal file
|
@ -0,0 +1,325 @@
|
|||
package openldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/go-ldap/ldif"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/parseutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const (
|
||||
secretCredsType = "creds"
|
||||
|
||||
dynamicRolePath = "role/"
|
||||
dynamicCredPath = "cred/"
|
||||
)
|
||||
|
||||
func (b *backend) pathDynamicRoles() []*framework.Path {
|
||||
return []*framework.Path{
|
||||
// POST/GET/DELETE role/:name
|
||||
{
|
||||
Pattern: path.Join(dynamicRolePath, framework.GenericNameRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeLowerCaseString,
|
||||
Description: "Name of the role (lowercase)",
|
||||
Required: true,
|
||||
},
|
||||
"creation_ldif": {
|
||||
Type: framework.TypeString,
|
||||
Description: "LDIF string used to create new entities within OpenLDAP. This LDIF can be templated.",
|
||||
Required: true,
|
||||
},
|
||||
"deletion_ldif": {
|
||||
Type: framework.TypeString,
|
||||
Description: "LDIF string used to delete entities created within OpenLDAP. This LDIF can be templated.",
|
||||
Required: true,
|
||||
},
|
||||
"rollback_ldif": {
|
||||
Type: framework.TypeString,
|
||||
Description: "LDIF string used to rollback changes in the event of a failure to create credentials. This LDIF can be templated.",
|
||||
},
|
||||
"username_template": {
|
||||
Type: framework.TypeString,
|
||||
Description: "The template used to create a username",
|
||||
},
|
||||
"default_ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: "Default TTL for dynamic credentials",
|
||||
},
|
||||
"max_ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: "Max TTL a dynamic credential can be extended to",
|
||||
},
|
||||
},
|
||||
ExistenceCheck: b.pathDynamicRoleExistenceCheck,
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.pathDynamicRoleCreateUpdate,
|
||||
},
|
||||
logical.CreateOperation: &framework.PathOperation{
|
||||
Callback: b.pathDynamicRoleCreateUpdate,
|
||||
},
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
Callback: b.pathDynamicRoleRead,
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
Callback: b.pathDynamicRoleDelete,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: staticRoleHelpSynopsis,
|
||||
HelpDescription: staticRoleHelpDescription,
|
||||
},
|
||||
// LIST role
|
||||
{
|
||||
Pattern: dynamicRolePath + "?$",
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ListOperation: &framework.PathOperation{
|
||||
Callback: b.pathDynamicRoleList,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: "List all the dynamic roles Vault is currently managing in OpenLDAP.",
|
||||
HelpDescription: "List all the dynamic roles being managed by Vault.",
|
||||
},
|
||||
// GET credentials
|
||||
{
|
||||
Pattern: path.Join(dynamicCredPath, framework.MatchAllRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeLowerCaseString,
|
||||
Description: "Name of the dynamic role.",
|
||||
},
|
||||
},
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
Callback: b.pathDynamicCredsRead,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: "Request LDAP credentials for a dynamic role. These credentials are " +
|
||||
"created within OpenLDAP when querying this endpoint.",
|
||||
HelpDescription: "This path requests new LDAP credentials for a certain dynamic role. " +
|
||||
"The credentials are created within OpenLDAP based on the creation_ldif specified " +
|
||||
"within the dynamic role configuration.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dynamicSecretCreds(b *backend) *framework.Secret {
|
||||
return &framework.Secret{
|
||||
Type: secretCredsType,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"username": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Username of the generated account",
|
||||
},
|
||||
"password": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Password to access the generated account",
|
||||
},
|
||||
"distinguished_names": {
|
||||
Type: framework.TypeStringSlice,
|
||||
Description: "List of the distinguished names (DN) created. Each name in this list corresponds to" +
|
||||
"each action taken within the creation_ldif statements. This does not de-duplicate entries, " +
|
||||
"so this will have one entry for each LDIF statement within creation_ldif.",
|
||||
},
|
||||
},
|
||||
|
||||
Renew: b.secretCredsRenew(),
|
||||
Revoke: b.secretCredsRevoke(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathDynamicRoleCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
rawData := data.Raw
|
||||
err := convertToDuration(rawData, "default_ttl", "max_ttl")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert TTLs to duration: %w", err)
|
||||
}
|
||||
|
||||
roleName := data.Get("name").(string)
|
||||
dRole, err := retrieveDynamicRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to look for existing role: %w", err)
|
||||
}
|
||||
if dRole == nil {
|
||||
if req.Operation == logical.UpdateOperation {
|
||||
return nil, fmt.Errorf("unable to update role: role does not exist")
|
||||
}
|
||||
dRole = &dynamicRole{}
|
||||
}
|
||||
err = mapstructure.WeakDecode(rawData, dRole)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode request: %w", err)
|
||||
}
|
||||
|
||||
dRole.CreationLDIF = decodeBase64(dRole.CreationLDIF)
|
||||
dRole.RollbackLDIF = decodeBase64(dRole.RollbackLDIF)
|
||||
dRole.DeletionLDIF = decodeBase64(dRole.DeletionLDIF)
|
||||
|
||||
err = validateDynamicRole(dRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = storeDynamicRole(ctx, req.Storage, dRole)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to save dynamic role: %w", err)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func validateDynamicRole(dRole *dynamicRole) error {
|
||||
if dRole.CreationLDIF == "" {
|
||||
return fmt.Errorf("missing creation_ldif")
|
||||
}
|
||||
|
||||
if dRole.DeletionLDIF == "" {
|
||||
return fmt.Errorf("missing deletion_ldif")
|
||||
}
|
||||
|
||||
err := assertValidLDIFTemplate(dRole.CreationLDIF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid creation_ldif: %w", err)
|
||||
}
|
||||
|
||||
err = assertValidLDIFTemplate(dRole.DeletionLDIF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid deletion_ldif: %w", err)
|
||||
}
|
||||
|
||||
if dRole.RollbackLDIF != "" {
|
||||
err = assertValidLDIFTemplate(dRole.RollbackLDIF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid rollback_ldif: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertToDuration all keys in the data map into time.Duration objects. Keys not found in the map will be ignored
|
||||
func convertToDuration(data map[string]interface{}, keys ...string) error {
|
||||
merr := new(multierror.Error)
|
||||
for _, key := range keys {
|
||||
val, exists := data[key]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
dur, err := parseutil.ParseDurationSecond(val)
|
||||
if err != nil {
|
||||
merr = multierror.Append(merr, fmt.Errorf("invalid duration %s: %w", key, err))
|
||||
continue
|
||||
}
|
||||
data[key] = dur
|
||||
}
|
||||
return merr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// decodeBase64 attempts to base64 decode the provided string. If the string is not base64 encoded, this
|
||||
// returns the original string.
|
||||
// This is equivalent to "if string is base64 encoded, decode it and return, otherwise return the original string"
|
||||
func decodeBase64(str string) string {
|
||||
if str == "" {
|
||||
return ""
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
return str
|
||||
}
|
||||
return string(decoded)
|
||||
}
|
||||
|
||||
func assertValidLDIFTemplate(rawTemplate string) error {
|
||||
// Test the template to ensure there aren't any errors in the template syntax
|
||||
now := time.Now()
|
||||
exp := now.Add(24 * time.Hour)
|
||||
testTemplateData := dynamicTemplateData{
|
||||
Username: "testuser",
|
||||
Password: "testpass",
|
||||
DisplayName: "testdisplayname",
|
||||
RoleName: "testrolename",
|
||||
IssueTime: now.Format(time.RFC3339),
|
||||
IssueTimeSeconds: now.Unix(),
|
||||
ExpirationTime: exp.Format(time.RFC3339),
|
||||
ExpirationTimeSeconds: exp.Unix(),
|
||||
}
|
||||
|
||||
testLDIF, err := applyTemplate(rawTemplate, testTemplateData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid template: %w", err)
|
||||
}
|
||||
|
||||
// Test the LDIF to ensure there aren't any errors in the syntax
|
||||
entries, err := ldif.Parse(testLDIF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("LDIF is invalid: %w", err)
|
||||
}
|
||||
|
||||
if len(entries.Entries) == 0 {
|
||||
return fmt.Errorf("must specify at least one LDIF entry")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) pathDynamicRoleRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("name").(string)
|
||||
|
||||
dRole, err := retrieveDynamicRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve dynamic role: %w", err)
|
||||
}
|
||||
if dRole == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"creation_ldif": dRole.CreationLDIF,
|
||||
"deletion_ldif": dRole.DeletionLDIF,
|
||||
"rollback_ldif": dRole.RollbackLDIF,
|
||||
"username_template": dRole.UsernameTemplate,
|
||||
"default_ttl": dRole.DefaultTTL.Seconds(),
|
||||
"max_ttl": dRole.MaxTTL.Seconds(),
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathDynamicRoleList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roles, err := req.Storage.List(ctx, dynamicRolePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list roles: %w", err)
|
||||
}
|
||||
|
||||
return logical.ListResponse(roles), nil
|
||||
}
|
||||
|
||||
func (b *backend) pathDynamicRoleExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
roleName := data.Get("name").(string)
|
||||
role, err := retrieveDynamicRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error finding role: %w", err)
|
||||
}
|
||||
return role != nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathDynamicRoleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("name").(string)
|
||||
|
||||
err := deleteDynamicRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete role: %w", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
|
@ -116,7 +116,7 @@ func (b *backend) pathRotateRoleCredentialsUpdate(ctx context.Context, req *logi
|
|||
return logical.ErrorResponse("empty role name attribute given"), nil
|
||||
}
|
||||
|
||||
role, err := b.StaticRole(ctx, req.Storage, name)
|
||||
role, err := b.staticRole(ctx, req.Storage, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
const staticCredPath = "static-cred/"
|
||||
|
||||
func (b *backend) pathCredsCreate() []*framework.Path {
|
||||
func (b *backend) pathStaticCredsCreate() []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: staticCredPath + framework.GenericNameRegex("name"),
|
||||
|
@ -33,7 +33,7 @@ func (b *backend) pathCredsCreate() []*framework.Path {
|
|||
func (b *backend) pathStaticCredsRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
name := data.Get("name").(string)
|
||||
|
||||
role, err := b.StaticRole(ctx, req.Storage, name)
|
||||
role, err := b.staticRole(ctx, req.Storage, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
|
@ -2,6 +2,8 @@ package openldap
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
|
@ -14,13 +16,13 @@ const (
|
|||
staticRolePath = "static-role/"
|
||||
)
|
||||
|
||||
func (b *backend) pathListRoles() []*framework.Path {
|
||||
func (b *backend) pathListStaticRoles() []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: staticRolePath + "?$",
|
||||
Pattern: path.Join(staticRolePath, framework.OptionalParamRegex("prefix")),
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ListOperation: &framework.PathOperation{
|
||||
Callback: b.pathRoleList,
|
||||
Callback: b.pathStaticRoleList,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: staticRolesListHelpSynopsis,
|
||||
|
@ -29,7 +31,7 @@ func (b *backend) pathListRoles() []*framework.Path {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathRoles() []*framework.Path {
|
||||
func (b *backend) pathStaticRoles() []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: staticRolePath + framework.GenericNameRegex("name"),
|
||||
|
@ -109,7 +111,7 @@ func staticFields() map[string]*framework.FieldSchema {
|
|||
}
|
||||
|
||||
func (b *backend) pathStaticRoleExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
role, err := b.StaticRole(ctx, req.Storage, data.Get("name").(string))
|
||||
role, err := b.staticRole(ctx, req.Storage, data.Get("name").(string))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -132,7 +134,7 @@ func (b *backend) pathStaticRoleDelete(ctx context.Context, req *logical.Request
|
|||
return nil, err
|
||||
}
|
||||
|
||||
role, err := b.StaticRole(ctx, req.Storage, name)
|
||||
role, err := b.staticRole(ctx, req.Storage, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -149,7 +151,7 @@ func (b *backend) pathStaticRoleDelete(ctx context.Context, req *logical.Request
|
|||
}
|
||||
|
||||
func (b *backend) pathStaticRoleRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
role, err := b.StaticRole(ctx, req.Storage, d.Get("name").(string))
|
||||
role, err := b.staticRole(ctx, req.Storage, d.Get("name").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -179,7 +181,7 @@ func (b *backend) pathStaticRoleCreateUpdate(ctx context.Context, req *logical.R
|
|||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
role, err := b.StaticRole(ctx, req.Storage, data.Get("name").(string))
|
||||
role, err := b.staticRole(ctx, req.Storage, data.Get("name").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -311,22 +313,16 @@ func (s *staticAccount) PasswordTTL() time.Duration {
|
|||
return ttl
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleList(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
path := staticRolePath
|
||||
entries, err := req.Storage.List(ctx, path)
|
||||
func (b *backend) pathStaticRoleList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roles, err := req.Storage.List(ctx, staticRolePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to list roles: %w", err)
|
||||
}
|
||||
|
||||
return logical.ListResponse(entries), nil
|
||||
return logical.ListResponse(roles), nil
|
||||
}
|
||||
|
||||
func (b *backend) StaticRole(ctx context.Context, s logical.Storage, roleName string) (*roleEntry, error) {
|
||||
return b.roleAtPath(ctx, s, roleName, staticRolePath)
|
||||
}
|
||||
|
||||
func (b *backend) roleAtPath(ctx context.Context, s logical.Storage, roleName string, pathPrefix string) (*roleEntry, error) {
|
||||
entry, err := s.Get(ctx, pathPrefix+roleName)
|
||||
func (b *backend) staticRole(ctx context.Context, s logical.Storage, roleName string) (*roleEntry, error) {
|
||||
entry, err := s.Get(ctx, staticRolePath+roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
|
@ -54,7 +54,7 @@ func (b *backend) populateQueue(ctx context.Context, s logical.Storage) {
|
|||
default:
|
||||
}
|
||||
|
||||
role, err := b.StaticRole(ctx, s, roleName)
|
||||
role, err := b.staticRole(ctx, s, roleName)
|
||||
if err != nil {
|
||||
log.Warn("unable to read static role", "error", err, "role", roleName)
|
||||
continue
|
||||
|
@ -159,7 +159,7 @@ func (b *backend) rotateCredential(ctx context.Context, s logical.Storage) bool
|
|||
defer lock.Unlock()
|
||||
|
||||
// Validate the role still exists
|
||||
role, err := b.StaticRole(ctx, s, item.Key)
|
||||
role, err := b.staticRole(ctx, s, item.Key)
|
||||
if err != nil {
|
||||
b.Logger().Error("unable to load role", "role", item.Key, "error", err)
|
||||
item.Priority = time.Now().Add(10 * time.Second).Unix()
|
||||
|
@ -457,7 +457,7 @@ func (b *backend) loadStaticWALs(ctx context.Context, s logical.Storage) (map[st
|
|||
|
||||
// Verify the static role still exists
|
||||
roleName := walEntry.RoleName
|
||||
role, err := b.StaticRole(ctx, s, roleName)
|
||||
role, err := b.staticRole(ctx, s, roleName)
|
||||
if err != nil {
|
||||
b.Logger().Warn("unable to read static role", "error", err, "role", roleName)
|
||||
continue
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
UUID "github.com/hashicorp/go-uuid"
|
||||
)
|
||||
|
||||
func unixTime() string {
|
||||
return strconv.FormatInt(time.Now().Unix(), 10)
|
||||
}
|
||||
|
||||
func unixTimeMillis() string {
|
||||
return strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
}
|
||||
|
||||
func timestamp(format string) string {
|
||||
return time.Now().Format(format)
|
||||
}
|
||||
|
||||
func truncate(maxLen int, str string) (string, error) {
|
||||
if maxLen <= 0 {
|
||||
return "", fmt.Errorf("max length must be > 0 but was %d", maxLen)
|
||||
}
|
||||
if len(str) > maxLen {
|
||||
return str[:maxLen], nil
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
const (
|
||||
sha256HashLen = 8
|
||||
)
|
||||
|
||||
func truncateSHA256(maxLen int, str string) (string, error) {
|
||||
if maxLen <= 8 {
|
||||
return "", fmt.Errorf("max length must be > 8 but was %d", maxLen)
|
||||
}
|
||||
|
||||
if len(str) <= maxLen {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
truncIndex := maxLen - sha256HashLen
|
||||
hash := hashSHA256(str[truncIndex:])
|
||||
result := fmt.Sprintf("%s%s", str[:truncIndex], hash[:sha256HashLen])
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func hashSHA256(str string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(str)))
|
||||
}
|
||||
|
||||
func encodeBase64(str string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
func uppercase(str string) string {
|
||||
return strings.ToUpper(str)
|
||||
}
|
||||
|
||||
func lowercase(str string) string {
|
||||
return strings.ToLower(str)
|
||||
}
|
||||
|
||||
func replace(find string, replace string, str string) string {
|
||||
return strings.ReplaceAll(str, find, replace)
|
||||
}
|
||||
|
||||
func uuid() (string, error) {
|
||||
return UUID.GenerateUUID()
|
||||
}
|
141
vendor/github.com/hashicorp/vault/sdk/helper/template/template.go
generated
vendored
Normal file
141
vendor/github.com/hashicorp/vault/sdk/helper/template/template.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/sdk/helper/base62"
|
||||
)
|
||||
|
||||
type Opt func(*StringTemplate) error
|
||||
|
||||
func Template(rawTemplate string) Opt {
|
||||
return func(up *StringTemplate) error {
|
||||
up.rawTemplate = rawTemplate
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Function allows the user to specify functions for use in the template. If the name provided is a function that
|
||||
// already exists in the function map, this will override the previously specified function.
|
||||
func Function(name string, f interface{}) Opt {
|
||||
return func(up *StringTemplate) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("missing function name")
|
||||
}
|
||||
if f == nil {
|
||||
return fmt.Errorf("missing function")
|
||||
}
|
||||
up.funcMap[name] = f
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringTemplate creates strings based on the provided template.
|
||||
// This uses the go templating language, so anything that adheres to that language will function in this struct.
|
||||
// There are several custom functions available for use in the template:
|
||||
// - random
|
||||
// - Randomly generated characters. This uses the charset specified in RandomCharset. Must include a length.
|
||||
// Example: {{ rand 20 }}
|
||||
// - truncate
|
||||
// - Truncates the previous value to the specified length. Must include a maximum length.
|
||||
// Example: {{ .DisplayName | truncate 10 }}
|
||||
// - truncate_sha256
|
||||
// - Truncates the previous value to the specified length. If the original length is greater than the length
|
||||
// specified, the remaining characters will be sha256 hashed and appended to the end. The hash will be only the first 8 characters The maximum length will
|
||||
// be no longer than the length specified.
|
||||
// Example: {{ .DisplayName | truncate_sha256 30 }}
|
||||
// - uppercase
|
||||
// - Uppercases the previous value.
|
||||
// Example: {{ .RoleName | uppercase }}
|
||||
// - lowercase
|
||||
// - Lowercases the previous value.
|
||||
// Example: {{ .DisplayName | lowercase }}
|
||||
// - replace
|
||||
// - Performs a string find & replace
|
||||
// Example: {{ .DisplayName | replace - _ }}
|
||||
// - sha256
|
||||
// - SHA256 hashes the previous value.
|
||||
// Example: {{ .DisplayName | sha256 }}
|
||||
// - base64
|
||||
// - base64 encodes the previous value.
|
||||
// Example: {{ .DisplayName | base64 }}
|
||||
// - unix_time
|
||||
// - Provides the current unix time in seconds.
|
||||
// Example: {{ unix_time }}
|
||||
// - unix_time_millis
|
||||
// - Provides the current unix time in milliseconds.
|
||||
// Example: {{ unix_time_millis }}
|
||||
// - timestamp
|
||||
// - Provides the current time. Must include a standard Go format string
|
||||
// - uuid
|
||||
// - Generates a UUID
|
||||
// Example: {{ uuid }}
|
||||
type StringTemplate struct {
|
||||
rawTemplate string
|
||||
tmpl *template.Template
|
||||
funcMap template.FuncMap
|
||||
}
|
||||
|
||||
// NewTemplate creates a StringTemplate. No arguments are required
|
||||
// as this has reasonable defaults for all values.
|
||||
// The default template is specified in the DefaultTemplate constant.
|
||||
func NewTemplate(opts ...Opt) (up StringTemplate, err error) {
|
||||
up = StringTemplate{
|
||||
funcMap: map[string]interface{}{
|
||||
"random": base62.Random,
|
||||
"truncate": truncate,
|
||||
"truncate_sha256": truncateSHA256,
|
||||
"uppercase": uppercase,
|
||||
"lowercase": lowercase,
|
||||
"replace": replace,
|
||||
"sha256": hashSHA256,
|
||||
"base64": encodeBase64,
|
||||
|
||||
"unix_time": unixTime,
|
||||
"unix_time_millis": unixTimeMillis,
|
||||
"timestamp": timestamp,
|
||||
"uuid": uuid,
|
||||
},
|
||||
}
|
||||
|
||||
merr := &multierror.Error{}
|
||||
for _, opt := range opts {
|
||||
merr = multierror.Append(merr, opt(&up))
|
||||
}
|
||||
|
||||
err = merr.ErrorOrNil()
|
||||
if err != nil {
|
||||
return up, err
|
||||
}
|
||||
|
||||
if up.rawTemplate == "" {
|
||||
return StringTemplate{}, fmt.Errorf("missing template")
|
||||
}
|
||||
|
||||
tmpl, err := template.New("template").
|
||||
Funcs(up.funcMap).
|
||||
Parse(up.rawTemplate)
|
||||
if err != nil {
|
||||
return StringTemplate{}, fmt.Errorf("unable to parse template: %w", err)
|
||||
}
|
||||
up.tmpl = tmpl
|
||||
|
||||
return up, nil
|
||||
}
|
||||
|
||||
// Generate based on the provided template
|
||||
func (up StringTemplate) Generate(data interface{}) (string, error) {
|
||||
if up.tmpl == nil || up.rawTemplate == "" {
|
||||
return "", fmt.Errorf("failed to generate: template not initialized")
|
||||
}
|
||||
str := &strings.Builder{}
|
||||
err := up.tmpl.Execute(str, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to apply template: %w", err)
|
||||
}
|
||||
|
||||
return str.String(), nil
|
||||
}
|
|
@ -333,12 +333,14 @@ github.com/gammazero/deque
|
|||
github.com/gammazero/workerpool
|
||||
# github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/ghodss/yaml
|
||||
# github.com/go-asn1-ber/asn1-ber v1.3.1
|
||||
# github.com/go-asn1-ber/asn1-ber v1.4.1
|
||||
github.com/go-asn1-ber/asn1-ber
|
||||
# github.com/go-errors/errors v1.0.1
|
||||
github.com/go-errors/errors
|
||||
# github.com/go-ldap/ldap/v3 v3.1.10
|
||||
github.com/go-ldap/ldap/v3
|
||||
# github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
|
||||
github.com/go-ldap/ldif
|
||||
# github.com/go-ole/go-ole v1.2.4
|
||||
github.com/go-ole/go-ole
|
||||
github.com/go-ole/go-ole/oleutil
|
||||
|
@ -580,12 +582,12 @@ github.com/hashicorp/vault-plugin-secrets-gcpkms
|
|||
github.com/hashicorp/vault-plugin-secrets-kv
|
||||
# github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0
|
||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas
|
||||
# github.com/hashicorp/vault-plugin-secrets-openldap v0.3.0
|
||||
# github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap
|
||||
github.com/hashicorp/vault-plugin-secrets-openldap/client
|
||||
# github.com/hashicorp/vault/api v1.0.5-0.20201001211907-38d91b749c77 => ./api
|
||||
github.com/hashicorp/vault/api
|
||||
# github.com/hashicorp/vault/sdk v0.1.14-0.20201022214319-d87657199d4b => ./sdk
|
||||
# github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c => ./sdk
|
||||
github.com/hashicorp/vault/sdk/database/dbplugin
|
||||
github.com/hashicorp/vault/sdk/database/dbplugin/v5
|
||||
github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto
|
||||
|
@ -622,6 +624,7 @@ github.com/hashicorp/vault/sdk/helper/pointerutil
|
|||
github.com/hashicorp/vault/sdk/helper/policyutil
|
||||
github.com/hashicorp/vault/sdk/helper/salt
|
||||
github.com/hashicorp/vault/sdk/helper/strutil
|
||||
github.com/hashicorp/vault/sdk/helper/template
|
||||
github.com/hashicorp/vault/sdk/helper/tlsutil
|
||||
github.com/hashicorp/vault/sdk/helper/tokenutil
|
||||
github.com/hashicorp/vault/sdk/helper/useragent
|
||||
|
|
Loading…
Reference in New Issue