diff --git a/logical/framework/backend.go b/logical/framework/backend.go index 495dec9e60..17134681eb 100644 --- a/logical/framework/backend.go +++ b/logical/framework/backend.go @@ -106,6 +106,17 @@ func (b *Backend) Route(path string) *Path { return result } +// Secret is used to look up the secret with the given type. +func (b *Backend) Secret(k string) *Secret { + for _, s := range b.Secrets { + if s.Type == k { + return s + } + } + + return nil +} + func (b *Backend) init() { b.pathsRe = make([]*regexp.Regexp, len(b.Paths)) for i, p := range b.Paths { diff --git a/logical/framework/backend_test.go b/logical/framework/backend_test.go index 52b1a8822c..de7ecdbfe1 100644 --- a/logical/framework/backend_test.go +++ b/logical/framework/backend_test.go @@ -319,6 +319,37 @@ func TestBackendRoute(t *testing.T) { } } +func TestBackendSecret(t *testing.T) { + cases := map[string]struct { + Secrets []*Secret + Search string + Match bool + }{ + "no match": { + []*Secret{&Secret{Type: "foo"}}, + "bar", + false, + }, + + "match": { + []*Secret{&Secret{Type: "foo"}}, + "foo", + true, + }, + } + + for n, tc := range cases { + b := &Backend{Secrets: tc.Secrets} + result := b.Secret(tc.Search) + if tc.Match != (result != nil) { + t.Fatalf("bad: %s\n\nExpected match: %v", n, tc.Match) + } + if result != nil && result.Type != tc.Search { + t.Fatalf("bad: %s\n\nExpected matching type: %#v", n, result) + } + } +} + func TestFieldSchemaDefaultOrZero(t *testing.T) { cases := map[string]struct { Schema *FieldSchema diff --git a/logical/framework/request.go b/logical/framework/request.go index 8c5ef89ef5..b78780e9ed 100644 --- a/logical/framework/request.go +++ b/logical/framework/request.go @@ -7,7 +7,25 @@ import ( // Request is a single request for a backend that wraps a logical.Request // to provide some extra functionality. type Request struct { - Backend *Backend - Data *FieldData + // Backend is the backend that generated this request. + Backend *Backend + + // Data is any parameters that were passed into the request according + // to the path schema. If this request is to a secret operation + // (revoke, renew), then Data is according to the schema of the + // secret. + Data *FieldData + + // The fields below are only set for secret-related requests (renew, + // revoke). + // + // SecretType is the string type of the secret. + // + // SecretId is the ID of the secret that was given when generating + // the secret, or is otherwise just the UUID that was generated. + SecretType string + SecretId string + + // LogicalRequest is the raw logical.Request structure for this request. LogicalRequest *logical.Request } diff --git a/logical/framework/secret.go b/logical/framework/secret.go index f4ad74cb97..c0158d9ffe 100644 --- a/logical/framework/secret.go +++ b/logical/framework/secret.go @@ -1,13 +1,62 @@ package framework +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/vault/logical" +) + // Secret is a type of secret that can be returned from a backend. type Secret struct { // Type is the name of this secret type. This is used to setup the // vault ID and to look up the proper secret structure when revocation/ // renewal happens. Once this is set this should not be changed. + // + // The format of this must match (case insensitive): ^a-Z0-9_$ Type string // Fields is the mapping of data fields and schema that comprise // the structure of this secret. Fields map[string]*FieldSchema + + // Renewable is whether or not this secret type can be renewed. + Renewable bool + + // DefaultDuration and DefaultGracePeriod are the default values for + // the duration of the lease for this secret and its grace period. These + // can be manually overwritten with the result of Response(). + DefaultDuration time.Duration + DefaultGracePeriod time.Duration +} + +// SecretType is the type of the secret with the given ID. +func SecretType(id string) string { + idx := strings.Index(id, "-") + if idx < 0 { + return "" + } + + return id[:idx] +} + +func (s *Secret) Response( + data map[string]interface{}) (*logical.Response, error) { + uuid, err := logical.UUID() + if err != nil { + return nil, err + } + + id := fmt.Sprintf("%s-%s", s.Type, uuid) + return &logical.Response{ + IsSecret: true, + Lease: &logical.Lease{ + VaultID: id, + Renewable: s.Renewable, + Duration: s.DefaultDuration, + GracePeriod: s.DefaultGracePeriod, + }, + Data: data, + }, nil } diff --git a/logical/framework/secret_test.go b/logical/framework/secret_test.go new file mode 100644 index 0000000000..9c22be86d0 --- /dev/null +++ b/logical/framework/secret_test.go @@ -0,0 +1,19 @@ +package framework + +import ( + "testing" +) + +func TestSecretType(t *testing.T) { + cases := [][2]string{ + {"foo-bar", "foo"}, + {"foo", ""}, + } + + for _, tc := range cases { + actual := SecretType(tc[0]) + if actual != tc[1] { + t.Fatalf("Input: %s, Output: %s, Expected: %s", tc[0], actual, tc[1]) + } + } +}