Skip to content

Commit

Permalink
add hierarchical path handling to static role endpoints (#102)
Browse files Browse the repository at this point in the history
* adds poc with nested role listing

* add make configure

* update regex to honor previous contraints

* refactor static role tests

* add tests for static role read and list

* enable hierarchical path for static-cred endpoint

* enable hierarchical path for rotate-role endpoint

* address review comments

* update makefile and test checks

---------

Co-authored-by: Austin Gebauer <agebauer@hashicorp.com>
  • Loading branch information
fairclothjm and austingebauer committed May 8, 2024
1 parent 6a3ca72 commit 3273e17
Show file tree
Hide file tree
Showing 8 changed files with 370 additions and 462 deletions.
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
REPO_DIR := $(shell basename $(CURDIR))

PLUGIN_NAME := $(shell command ls cmd/)
ifndef $(GOPATH)
GOPATH=$(shell go env GOPATH)
export GOPATH
endif
PLUGIN_DIR ?= $$GOPATH/vault-plugins
PLUGIN_PATH ?= local-secrets-ldap

.PHONY: default
default: dev
Expand Down Expand Up @@ -41,3 +47,9 @@ fmtcheck:
.PHONY: fmt
fmt:
gofumpt -l -w .

configure: dev
./bootstrap/configure.sh \
$(PLUGIN_DIR) \
$(PLUGIN_NAME) \
$(PLUGIN_PATH)
30 changes: 30 additions & 0 deletions bootstrap/configure.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

PLUGIN_DIR=$1
PLUGIN_NAME=$2
PLUGIN_PATH=$3

echo "==> PLUGIN_DIR: $PLUGIN_DIR"
echo "==> PLUGIN_NAME: $PLUGIN_NAME"
echo "==> PLUGIN_PATH: $PLUGIN_PATH"

# Try to clean-up previous runs
vault secrets disable "${PLUGIN_PATH}"
vault plugin deregister secret "${PLUGIN_NAME}"
killall "${PLUGIN_NAME}"

# Copy the binary so text file is not busy when rebuilding & the plugin is registered
cp ./bin/"$PLUGIN_NAME" "$PLUGIN_DIR"

# Sets up the binary with local changes
vault plugin register \
-sha256="$(shasum -a 256 "$PLUGIN_DIR"/"$PLUGIN_NAME" | awk '{print $1}')" \
-version="0.0.1" \
secret "${PLUGIN_NAME}"

if [ -e scripts/custom.sh ]
then
. scripts/custom.sh
fi

3 changes: 2 additions & 1 deletion path_rotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/hashicorp/vault/sdk/framework"
Expand Down Expand Up @@ -48,7 +49,7 @@ func (b *backend) pathRotateCredentials() []*framework.Path {
"(binddn) used by Vault to manage LDAP.",
},
{
Pattern: rotateRolePath + framework.GenericNameRegex("name"),
Pattern: strings.TrimSuffix(rotateRolePath, "/") + genericNameWithForwardSlashRegex("name"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixLDAP,
OperationVerb: "rotate",
Expand Down
136 changes: 64 additions & 72 deletions path_rotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import (
"time"

"github.com/go-ldap/ldif"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/hashicorp/vault-plugin-secrets-openldap/client"
"github.com/hashicorp/vault/sdk/helper/ldaputil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/assert"
)

func TestManualRotate(t *testing.T) {
t.Run("rotate root", func(t *testing.T) {
func TestManualRotateRoot(t *testing.T) {
t.Run("happy path rotate root", func(t *testing.T) {
b, storage := getBackend(false)
defer b.Cleanup(context.Background())

Expand Down Expand Up @@ -83,107 +84,99 @@ func TestManualRotate(t *testing.T) {
t.Fatal("should have got error, didn't")
}
})
}

t.Run("rotate role", func(t *testing.T) {
func TestManualRotateRole(t *testing.T) {
t.Run("happy path rotate role", func(t *testing.T) {
b, storage := getBackend(false)
defer b.Cleanup(context.Background())

data := map[string]interface{}{
"binddn": "tester",
"bindpass": "pa$$w0rd",
"url": "ldap://138.91.247.105",
"certificate": validCertificate,
}
roleName := "hashicorp"
configureOpenLDAPMount(t, b, storage)
createRole(t, b, storage, roleName)

req := &logical.Request{
Operation: logical.CreateOperation,
Path: configPath,
Storage: storage,
Data: data,
}
resp := readStaticCred(t, b, storage, roleName)

resp, err := b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
if resp.Data["password"] == "" {
t.Fatal("expected password to be set, it wasn't")
}
oldPassword := resp.Data["password"]

req = &logical.Request{
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: rotateRootPath,
Path: rotateRolePath + roleName,
Storage: storage,
Data: nil,
}

resp, err = b.HandleRequest(context.Background(), req)
resp, err := b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}

data = map[string]interface{}{
"username": "hashicorp",
"dn": "uid=hashicorp,ou=users,dc=hashicorp,dc=com",
"rotation_period": "60s",
}
resp = readStaticCred(t, b, storage, roleName)

req = &logical.Request{
Operation: logical.CreateOperation,
Path: staticRolePath + "hashicorp",
Storage: storage,
Data: data,
if resp.Data["password"] == "" {
t.Fatal("expected password to be set after rotate, it wasn't")
}

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
if oldPassword == resp.Data["password"] {
t.Fatal("expected passwords to be different after rotation, they weren't")
}
})

req = &logical.Request{
Operation: logical.ReadOperation,
Path: staticCredPath + "hashicorp",
Storage: storage,
Data: nil,
}
t.Run("happy path rotate role with hierarchical path", func(t *testing.T) {
b, storage := getBackend(false)
defer b.Cleanup(context.Background())

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
configureOpenLDAPMount(t, b, storage)

if resp.Data["password"] == "" {
t.Fatal("expected password to be set, it wasn't")
}
oldPassword := resp.Data["password"]
roles := []string{"org/secure", "org/platform/dev", "org/platform/support"}

req = &logical.Request{
Operation: logical.UpdateOperation,
Path: rotateRolePath + "hashicorp",
Storage: storage,
Data: nil,
// create all the roles
for _, role := range roles {
data := getTestStaticRoleConfig(role)
createStaticRoleWithData(t, b, storage, role, data)
}

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
passwords := make([]string, 0)
// rotate all the creds
for _, role := range roles {
resp := readStaticCred(t, b, storage, role)

req = &logical.Request{
Operation: logical.ReadOperation,
Path: staticCredPath + "hashicorp",
Storage: storage,
Data: nil,
}
if resp.Data["password"] == "" {
t.Fatal("expected password to be set, it wasn't")
}
oldPassword := resp.Data["password"]

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: rotateRolePath + role,
Storage: storage,
Data: nil,
}

if resp.Data["password"] == "" {
t.Fatal("expected password to be set after rotate, it wasn't")
resp, err := b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}

resp = readStaticCred(t, b, storage, role)

newPassword := resp.Data["password"]
if newPassword == "" {
t.Fatal("expected password to be set after rotate, it wasn't")
}

if oldPassword == newPassword {
t.Fatal("expected passwords to be different after rotation, they weren't")
}
passwords = append(passwords, newPassword.(string))
}

if oldPassword == resp.Data["password"] {
t.Fatal("expected passwords to be different after rotation, they weren't")
// extra pendantic check that the hierarchical paths don't return the same data
if len(passwords) != len(strutil.RemoveDuplicates(passwords, false)) {
t.Fatal("expected unique static-role paths to return unique passwords")
}
})

Expand Down Expand Up @@ -284,7 +277,6 @@ func TestRollbackPassword(t *testing.T) {
}
assert.Equal(t, testCase.expectedPassword, fclient.password)
assert.Equal(t, testCase.expectedRollbackCalls, fclient.count)

})
}
}
3 changes: 2 additions & 1 deletion path_static_creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package openldap

import (
"context"
"strings"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
Expand All @@ -15,7 +16,7 @@ const staticCredPath = "static-cred/"
func (b *backend) pathStaticCredsCreate() []*framework.Path {
return []*framework.Path{
{
Pattern: staticCredPath + framework.GenericNameRegex("name"),
Pattern: strings.TrimSuffix(staticCredPath, "/") + genericNameWithForwardSlashRegex("name"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixLDAP,
OperationVerb: "request",
Expand Down

0 comments on commit 3273e17

Please sign in to comment.