Skip to content

Commit

Permalink
Add checkout status endpoint (#50)
Browse files Browse the repository at this point in the history
* add checkout status endpoint

* only return the available field if its checked in

* order imports
  • Loading branch information
tyrannosaurus-becks authored Sep 30, 2019
1 parent 9ab3699 commit 79bda87
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugin/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func newBackend(client secretsClient) *backend {
adBackend.pathRotateCredentials(),

// The following paths are for AD credential checkout.
adBackend.pathReserveStatus(),
adBackend.pathReserves(),
adBackend.pathListReserves(),
},
Expand Down
23 changes: 23 additions & 0 deletions plugin/backend_checkouts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestCheckOuts(t *testing.T) {
// Exercise all reserve endpoints.
t.Run("write reserve", WriteReserve)
t.Run("read reserve", ReadReserve)
t.Run("read reserve status", ReadReserveStatus)
t.Run("write conflicting reserve", WriteReserveWithConflictingServiceAccounts)
t.Run("list reserves", ListReserves)
t.Run("delete reserve", DeleteReserve)
Expand Down Expand Up @@ -100,6 +101,28 @@ func ReadReserve(t *testing.T) {
}
}

func ReadReserveStatus(t *testing.T) {
req := &logical.Request{
Operation: logical.ReadOperation,
Path: libraryPrefix + "test-reserve/status",
Storage: testStorage,
}
resp, err := testBackend.HandleRequest(ctx, req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected a response")
}
if len(resp.Data) != 2 {
t.Fatal("length should be 2 because there are two service accounts in this reserve")
}
testerStatus := resp.Data["[email protected]"].(map[string]interface{})
if !testerStatus["available"].(bool) {
t.Fatal("should be available for checkout")
}
}

func WriteReserveWithConflictingServiceAccounts(t *testing.T) {
req := &logical.Request{
Operation: logical.CreateOperation,
Expand Down
77 changes: 77 additions & 0 deletions plugin/path_checkouts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package plugin

import (
"context"
"fmt"
"time"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)

func (b *backend) pathReserveStatus() *framework.Path {
return &framework.Path{
Pattern: libraryPrefix + framework.GenericNameRegex("name") + "/status$",
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeLowerCaseString,
Description: "Name of the reserve",
Required: true,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.operationReserveStatus,
Summary: "Check the status of the service accounts in a library reserve.",
},
},
HelpSynopsis: `Check the status of the service accounts in a library.`,
}
}

func (b *backend) operationReserveStatus(ctx context.Context, req *logical.Request, fieldData *framework.FieldData) (*logical.Response, error) {
reserveName := fieldData.Get("name").(string)
reserve, err := readReserve(ctx, req.Storage, reserveName)
if err != nil {
return nil, err
}
if reserve == nil {
return logical.ErrorResponse(`"%s" doesn't exist`, reserveName), nil
}
respData := make(map[string]interface{})
for _, serviceAccountName := range reserve.ServiceAccountNames {
checkOut, err := b.checkOutHandler.Status(ctx, req.Storage, serviceAccountName)
if err != nil {
return nil, err
}
if checkOut == nil {
// This should never happen because for every service account, it should have
// been checked in when it was first created.
b.Logger().Warn(fmt.Sprintf("%s should have been checked in, but wasn't, checking it in now", serviceAccountName))
if err := b.checkOutHandler.CheckIn(ctx, req.Storage, serviceAccountName); err != nil {
return nil, err
}
}

status := map[string]interface{}{
"available": checkOut.IsAvailable,
}
if checkOut.IsAvailable {
// We only omit all other fields if the checkout is currently available,
// because they're only relevant to accounts that aren't checked out.
respData[serviceAccountName] = status
continue
}
status["lending_period"] = int64(checkOut.LendingPeriod.Seconds())
status["due"] = checkOut.Due.Format(time.RFC3339Nano)
if checkOut.BorrowerClientToken != "" {
status["borrower_client_token"] = checkOut.BorrowerClientToken
} else {
status["borrower_entity_id"] = checkOut.BorrowerEntityID
}
respData[serviceAccountName] = status
}
return &logical.Response{
Data: respData,
}, nil
}

0 comments on commit 79bda87

Please sign in to comment.