Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add hierarchical path handling to static role endpoints #102

Merged
merged 9 commits into from
May 8, 2024
Merged
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
REPO_DIR := $(shell basename $(CURDIR))

PLUGIN_NAME := $(shell command ls cmd/)
PLUGIN_DIR ?= $$GOPATH/vault-plugins
fairclothjm marked this conversation as resolved.
Show resolved Hide resolved
PLUGIN_PATH ?= local-secrets-ldap

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

configure: dev
@./bootstrap/configure.sh \
fairclothjm marked this conversation as resolved.
Show resolved Hide resolved
$(PLUGIN_DIR) \
$(PLUGIN_NAME) \
$(PLUGIN_PATH)
31 changes: 31 additions & 0 deletions bootstrap/configure.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
fairclothjm marked this conversation as resolved.
Show resolved Hide resolved
# 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)
fairclothjm marked this conversation as resolved.
Show resolved Hide resolved

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"] == "" {
fairclothjm marked this conversation as resolved.
Show resolved Hide resolved
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
Loading