Skip to content

Commit

Permalink
Add lease information to DB credentials (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredpetersen authored May 13, 2022
1 parent a5a5f77 commit d969399
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.4] - 2022-05-13
### Added
- Lease information to Database credentials so that you know when the secret expires

## [0.0.3] - 2022-05-05
### Changed
- vaultx client uses functions for nested client gateways instead of struct fields for improved testability
Expand Down
19 changes: 18 additions & 1 deletion db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package db
import (
"context"
"fmt"
"time"

"github.com/jaredpetersen/vaultx/api"
"github.com/jaredpetersen/vaultx/auth"
Expand All @@ -20,6 +21,14 @@ type Client struct {
type Credentials struct {
Username string
Password string
Lease Lease
}

// Lease contains information about how long the secret is valid.
type Lease struct {
ID string
Renewable bool
Expiration time.Duration
}

const httpPathDBCredentials = "/v1/database/creds/"
Expand All @@ -32,7 +41,10 @@ func (db *Client) GenerateCredentials(ctx context.Context, role string) (*Creden
}

type credentialsResponseWrapper struct {
Data credentialsResponse `json:"data"`
LeaseID string `json:"lease_id"`
LaseDuration int `json:"lease_duration"`
Renewable bool `json:"renewable"`
Data credentialsResponse `json:"data"`
}

res, err := db.API.Read(ctx, httpPathDBCredentials+role, db.TokenManager.GetToken().Value)
Expand All @@ -53,6 +65,11 @@ func (db *Client) GenerateCredentials(ctx context.Context, role string) (*Creden
credentials := Credentials{
Username: resBody.Data.Username,
Password: resBody.Data.Password,
Lease: Lease{
ID: resBody.LeaseID,
Renewable: resBody.Renewable,
Expiration: time.Duration(resBody.LaseDuration) * time.Second,
},
}

return &credentials, nil
Expand Down
52 changes: 40 additions & 12 deletions db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"io"
"strings"
"testing"
"time"

"github.com/hashicorp/go-cleanhttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/jaredpetersen/vaultx/api"
Expand All @@ -30,12 +32,27 @@ func TestGenerateCredentialsReturnsCredentials(t *testing.T) {
tokenManager := authmocks.TokenManager{}
tokenManager.On("GetToken").Return(token, nil)

generatedLeaseID := "somelease"
generatedLeaseExpiration := 400
generatedLeaseRenewable := true
generatedUsername := "someusername"
generatedPassword := "somepassword"

// Set up mocked API response
resBodyFmt := `{
"lease_id": "%s",
"lease_duration": %d,
"renewable": %t,
"data": {
"username": "%s",
"password": "%s"
}
}`
resBody := fmt.Sprintf(
"{\"data\": {\"username\": \"%s\", \"password\":\"%s\"}}",
resBodyFmt,
generatedLeaseID,
generatedLeaseExpiration,
generatedLeaseRenewable,
generatedUsername,
generatedPassword)
res := api.Response{
Expand All @@ -57,10 +74,11 @@ func TestGenerateCredentialsReturnsCredentials(t *testing.T) {
dbCredentials, err := dbc.GenerateCredentials(ctx, dbRole)
require.NoError(t, err, "Credential generation failure")
require.NotEmpty(t, dbCredentials, "Credentials are empty")
require.NotEmpty(t, dbCredentials.Username, "Username is empty")
require.Equal(t, generatedUsername, dbCredentials.Username, "Username matches original credentials")
require.NotEmpty(t, dbCredentials.Password, "Password is empty")
require.Equal(t, generatedPassword, dbCredentials.Password, "Password matches original credentials")
assert.Equal(t, generatedUsername, dbCredentials.Username, "Username is incorrect")
assert.Equal(t, generatedPassword, dbCredentials.Password, "Password is incorrect")
assert.Equal(t, generatedLeaseID, dbCredentials.Lease.ID, "Lease ID is incorrect")
assert.Equal(t, generatedLeaseRenewable, dbCredentials.Lease.Renewable, "Lease renewable is incorrect")
assert.Equal(t, time.Duration(generatedLeaseExpiration)*time.Second, dbCredentials.Lease.Expiration, "Lease expiration is incorrect")
}

func TestGenerateCredentialsReturnsErrorOnRequestFailure(t *testing.T) {
Expand Down Expand Up @@ -88,7 +106,6 @@ func TestGenerateCredentialsReturnsErrorOnRequestFailure(t *testing.T) {
}

dbCredentials, err := dbc.GenerateCredentials(ctx, dbRole)
require.Error(t, err, "Error does not exist")
require.ErrorIs(t, err, resErr, "Error is incorrect")
require.Empty(t, dbCredentials, "Credentials are not empty")
}
Expand All @@ -104,7 +121,15 @@ func TestGenerateCredentialsReturnsErrorOnInvalidResponseCode(t *testing.T) {
tokenManager.On("GetToken").Return(token, nil)

// Set up mocked API response with valid body but incorrect status code
resBody := "{\"data\": {\"username\": \"someusername\", \"password\": \"somepassword\"}}"
resBody := `{
"lease_id": "someid",
"lease_duration": 300,
"renewable": true,
"data": {
"username": "someusername",
"password": "somepassword"
}
}`
res := api.Response{
StatusCode: 418,
RawBody: io.NopCloser(strings.NewReader(resBody)),
Expand Down Expand Up @@ -212,11 +237,14 @@ func TestIntegrationGenerateCredentialsReturnsCredentials(t *testing.T) {
dbCredentials, err := dbc.GenerateCredentials(ctx, dbRole)
require.NoError(t, err, "Credential generation failure")
require.NotEmpty(t, dbCredentials, "Credentials are empty")
require.NotEmpty(t, dbCredentials.Username, "Username is empty")
require.NotEqual(t, dbUser, dbCredentials.Username, "Username matches original credentials")
require.True(t, strings.HasPrefix(dbCredentials.Username, "v-token-"+dbRole))
require.NotEmpty(t, dbCredentials.Password, "Password is empty")
require.NotEqual(t, dbPassword, dbCredentials.Password, "Password matches original credentials")
assert.NotEmpty(t, dbCredentials.Username, "Username is empty")
assert.NotEqual(t, dbUser, dbCredentials.Username, "Username matches original credentials")
assert.True(t, strings.HasPrefix(dbCredentials.Username, "v-token-"+dbRole))
assert.NotEmpty(t, dbCredentials.Password, "Password is empty")
assert.NotEqual(t, dbPassword, dbCredentials.Password, "Password matches original credentials")
assert.NotEmpty(t, dbCredentials.Lease.ID, "Lease ID is empty")
assert.True(t, dbCredentials.Lease.Renewable, "Lease is not renewable")
assert.NotEmpty(t, dbCredentials.Lease.Expiration, "Lease expiration is empty")
}

func TestIntegrationGenerateCredentialsReturnsErrorOnInvalidDBEngineConfig(t *testing.T) {
Expand Down

0 comments on commit d969399

Please sign in to comment.