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

Starter PKI CA Storage API #14796

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions builtin/logical/pki/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package pki

import (
"context"
"fmt"

"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/logical"
)

const (
pkiKeyPrefix = "/config/key/"
pkiIssuerPrefix = "/config/issuer/"
)

var (
emptyPkiKey = pkiKey{}
emptyIssuer = pkiIssuer{}
)

type pkiKeyId string
cipherboy marked this conversation as resolved.
Show resolved Hide resolved

func (p pkiKeyId) String() string {
return string(p)
}

type pkiIssuerId string

func (p pkiIssuerId) String() string {
return string(p)
}

type pkiKey struct {
ID pkiKeyId `json:"id" structs:"id" mapstructure:"id"`
stevendpclark marked this conversation as resolved.
Show resolved Hide resolved
PrivateKeyType certutil.PrivateKeyType `json:"private_key_type" structs:"private_key_type" mapstructure:"private_key_type"`
PrivateKey string `json:"private_key" structs:"private_key" mapstructure:"private_key"`
}

type pkiIssuer struct {
ID pkiIssuerId `json:"id" structs:"id" mapstructure:"id"`
PKIKeyID pkiKeyId `json:"pki_key_id" structs:"pki_key_id" mapstructure:"pki_key_id"`
Certificate string `json:"certificate" structs:"certificate" mapstructure:"certificate"`
CAChain []string `json:"ca_chain" structs:"ca_chain" mapstructure:"ca_chain"`
SerialNumber string `json:"serial_number" structs:"serial_number" mapstructure:"serial_number"`
}

func fetchPKIKeyById(ctx context.Context, s logical.Storage, keyId pkiKeyId) (pkiKey, error) {
keyEntry, err := s.Get(ctx, pkiKeyPrefix+keyId.String())
if err != nil {
return emptyPkiKey, errutil.InternalError{Err: fmt.Sprintf("unable to fetch pki key: %v", err)}
}
if keyEntry == nil {
// FIXME: Dedicated/specific error for this?
return emptyPkiKey, errutil.UserError{Err: fmt.Sprintf("pki key id %s does not exist", keyId.String())}
}

var pkiKey pkiKey
if err := keyEntry.DecodeJSON(&pkiKey); err != nil {
return emptyPkiKey, errutil.InternalError{Err: fmt.Sprintf("unable to decode pki key with id %s: %v", keyId.String(), err)}
}

return pkiKey, nil
}

func writePKIKey(ctx context.Context, s logical.Storage, pkiKey pkiKey) error {
keyId := pkiKey.ID

json, err := logical.StorageEntryJSON(pkiKeyPrefix+keyId.String(), pkiKey)
if err != nil {
return err
}

return s.Put(ctx, json)
}
stevendpclark marked this conversation as resolved.
Show resolved Hide resolved

func fetchPKIIssuerById(ctx context.Context, s logical.Storage, issuerId pkiIssuerId) (pkiIssuer, error) {
issuerEntry, err := s.Get(ctx, pkiIssuerPrefix+issuerId.String())
if err != nil {
return emptyIssuer, errutil.InternalError{Err: fmt.Sprintf("unable to fetch pki issuer: %v", err)}
}
if issuerEntry == nil {
// FIXME: Dedicated/specific error for this?
return emptyIssuer, errutil.UserError{Err: fmt.Sprintf("pki issuer id %s does not exist", issuerId.String())}
}

var pkiIssuer pkiIssuer
if err := issuerEntry.DecodeJSON(&pkiIssuer); err != nil {
return emptyIssuer, errutil.InternalError{Err: fmt.Sprintf("unable to decode pki issuer with id %s: %v", issuerId.String(), err)}
}

return pkiIssuer, nil
}

func writePKIIssuer(ctx context.Context, s logical.Storage, pkiIssuer pkiIssuer) error {
issuerId := pkiIssuer.ID

json, err := logical.StorageEntryJSON(pkiIssuerPrefix+issuerId.String(), pkiIssuer)
if err != nil {
return err
}

return s.Put(ctx, json)
}
102 changes: 102 additions & 0 deletions builtin/logical/pki/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package pki

import (
"context"
"crypto/rand"
"testing"

"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)

var ctx = context.Background()

func Test_PKIIssuerRoundTrip(t *testing.T) {
b, s := createBackendWithStorage(t)
pkiIssuer, pkiKey := genIssuerAndKey(t, b)

// We get an error when issuer id not found
_, err := fetchPKIIssuerById(ctx, s, pkiIssuer.ID)
require.Error(t, err)

// We get an error when pkiKey id not found
_, err = fetchPKIKeyById(ctx, s, pkiKey.ID)
require.Error(t, err)

// Now write out our issuer and key
err = writePKIKey(ctx, s, pkiKey)
require.NoError(t, err)
err = writePKIIssuer(ctx, s, pkiIssuer)
require.NoError(t, err)

pkiKey2, err := fetchPKIKeyById(ctx, s, pkiKey.ID)
require.NoError(t, err)

pkiIssuer2, err := fetchPKIIssuerById(ctx, s, pkiIssuer.ID)
require.NoError(t, err)

require.Equal(t, pkiKey, pkiKey2)
require.Equal(t, pkiIssuer, pkiIssuer2)
}

func genIssuerAndKey(t *testing.T, b *backend) (pkiIssuer, pkiKey) {
certBundle, err := genCertBundle(t, b)

keyId, err := uuid.GenerateUUID()
require.NoError(t, err)

pkiKey := pkiKey{
ID: pkiKeyId(keyId),
PrivateKeyType: certBundle.PrivateKeyType,
PrivateKey: certBundle.PrivateKey,
}

issuerId, err := uuid.GenerateUUID()
require.NoError(t, err)

pkiIssuer := pkiIssuer{
ID: pkiIssuerId(issuerId),
PKIKeyID: pkiKeyId(keyId),
Certificate: certBundle.Certificate,
CAChain: certBundle.CAChain,
SerialNumber: certBundle.SerialNumber,
}

return pkiIssuer, pkiKey
}

func genCertBundle(t *testing.T, b *backend) (*certutil.CertBundle, error) {
// Pretty gross just to generate a cert bundle, but
fields := addCACommonFields(map[string]*framework.FieldSchema{})
fields = addCAKeyGenerationFields(fields)
fields = addCAIssueFields(fields)
apiData := &framework.FieldData{
Schema: fields,
Raw: map[string]interface{}{
"exported": "internal",
"cn": "example.com",
"ttl": 3600,
},
}
_, _, role, respErr := b.getGenerationParams(ctx, apiData, "/pki")
require.Nil(t, respErr)

input := &inputBundle{
req: &logical.Request{
Operation: logical.UpdateOperation,
Path: "issue/testrole",
Storage: b.storage,
},
apiData: apiData,
role: role,
}
parsedCertBundle, err := generateCert(ctx, b, input, nil, true, rand.Reader)

require.NoError(t, err)
certBundle, err := parsedCertBundle.ToCertBundle()
require.NoError(t, err)
return certBundle, err
}