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

Change storage of PKI entries from colons to hyphens #2575

Merged
merged 11 commits into from
May 3, 2017
43 changes: 40 additions & 3 deletions builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,32 +185,69 @@ func fetchCAInfo(req *logical.Request) (*caInfoBundle, error) {
// separate pathing for CA, CRL, and revoked certificates.
func fetchCertBySerial(req *logical.Request, prefix, serial string) (*logical.StorageEntry, error) {
var path string
var err error
var certEntry *logical.StorageEntry

switch {
// Revoked goes first as otherwise ca/crl get hardcoded paths which fail if
// we actually want revocation info
case strings.HasPrefix(prefix, "revoked/"):
path = "revoked/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
path = "revoked/" + strings.Replace(strings.ToLower(serial), ":", "-", -1)
case serial == "ca":
path = "ca"
case serial == "crl":
path = "crl"
default:
path = "certs/" + strings.Replace(strings.ToLower(serial), ":", "-", -1)
}

certEntry, err = req.Storage.Get(path)
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching certificate %s: %s", serial, err)}
}
if certEntry != nil {
if certEntry.Value == nil || len(certEntry.Value) == 0 {
return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)}
}
return certEntry, nil
}

// No point checking these, no old/new style colons/hyphens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this comment makes sense any more. We should keep the check with path for clarity, or update the comment to say why return if legacyPath is empty.

if path == "ca" || path == "crl" {
return nil, nil
}

// Save the desired path
desiredPath := path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path is no longer needed. You can use hyphenSerial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nevermind. That does not work. That was also one of the reason I wanted to move away from the multiple replacements... Maybe it makes sense to create the legacyPath and path instead of just tracking the serial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, path in this case includes the prefix, so I think we have to save it as desiredPath instead of simply using hyphenSerial.


// If we get here we need to check for old-style paths using colons
switch {
case strings.HasPrefix(prefix, "revoked/"):
path = "revoked/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of flipping the path back and forth, could we store two path variables and use them as we need them? I think this would improve the readability of this code.

default:
path = "certs/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
}

certEntry, err := req.Storage.Get(path)
certEntry, err = req.Storage.Get(path)
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching certificate %s: %s", serial, err)}
}
if certEntry == nil {
return nil, nil
}

if certEntry.Value == nil || len(certEntry.Value) == 0 {
return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)}
}

// Update old-style paths to new-style paths
certEntry.Key = desiredPath
if err = req.Storage.Put(certEntry); err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error saving certificate with serial %s to new location", serial)}
}
if err = req.Storage.Delete(path); err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error deleting certificate with serial %s from old location", serial)}
}

return certEntry, nil
}

Expand Down
124 changes: 124 additions & 0 deletions builtin/logical/pki/cert_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package pki

import (
"fmt"
"testing"

"strings"

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

func TestPki_FetchCertBySerial(t *testing.T) {
storage := &logical.InmemStorage{}

cases := map[string]struct {
Req *logical.Request
Prefix string
Serial string
}{
"valid cert": {
&logical.Request{
Storage: storage,
},
"certs/",
"00:00:00:00:00:00:00:00",
},
"revoked cert": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add some cases that do not need the upgrade?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/hashicorp/vault/pull/2575/files/74965a87af47099b859552fdf2674038228a2c2e#diff-d9cca2f9a1b12f082084eaa700fff503R72 handles the cases for valid/revoked certs where the underlying path is already hyphenated so that there is no need to update the paths.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, missed that.

&logical.Request{
Storage: storage,
},
"revoked/",
"11:11:11:11:11:11:11:11",
},
}

// Test for colon-based paths in storage
for name, tc := range cases {
storageKey := fmt.Sprintf("%s%s", tc.Prefix, tc.Serial)
err := storage.Put(&logical.StorageEntry{
Key: storageKey,
Value: []byte("some data"),
})
if err != nil {
t.Fatalf("error writing to storage on %s colon-based storage path: %s", name, err)
}

certEntry, err := fetchCertBySerial(tc.Req, tc.Prefix, tc.Serial)
if err != nil {
t.Fatalf("error on %s for colon-based storage path: %s", name, err)
}

// Check for non-nil on valid/revoked certs
if certEntry == nil {
t.Fatalf("nil on %s for colon-based storage path", name)
}

// Ensure that cert serials are converted/updated after fetch
expectedKey := fmt.Sprintf("%s%s", tc.Prefix, strings.Replace(strings.ToLower(tc.Serial), ":", "-", -1))
se, err := storage.Get(expectedKey)
if err != nil {
t.Fatalf("error on %s for colon-based storage path:%s", name, err)
}
if strings.Compare(expectedKey, se.Key) != 0 {
t.Fatalf("expected: %s, got: %s", expectedKey, certEntry.Key)
}
}

// Reset storage
storage = &logical.InmemStorage{}

// Test for hyphen-base paths in storage
for name, tc := range cases {
storageKey := fmt.Sprintf("%s%s", tc.Prefix, strings.Replace(strings.ToLower(tc.Serial), ":", "-", -1))
err := storage.Put(&logical.StorageEntry{
Key: storageKey,
Value: []byte("some data"),
})
if err != nil {
t.Fatalf("error writing to storage on %s hyphen-based storage path: %s", name, err)
}

certEntry, err := fetchCertBySerial(tc.Req, tc.Prefix, tc.Serial)
if err != nil || certEntry == nil {
t.Fatalf("error on %s for hyphen-based storage path: err: %v, entry: %v", name, err, certEntry)
}
}

noConvCases := map[string]struct {
Req *logical.Request
Prefix string
Serial string
}{
"ca": {
&logical.Request{
Storage: storage,
},
"",
"ca",
},
"crl": {
&logical.Request{
Storage: storage,
},
"",
"crl",
},
}

// Test for ca and crl case
for name, tc := range noConvCases {
err := storage.Put(&logical.StorageEntry{
Key: tc.Serial,
Value: []byte("some data"),
})
if err != nil {
t.Fatalf("error writing to storage on %s: %s", name, err)
}

certEntry, err := fetchCertBySerial(tc.Req, tc.Prefix, tc.Serial)
if err != nil || certEntry == nil {
t.Fatalf("error on %s: err: %v, entry: %v", name, err, certEntry)
}
}
}
5 changes: 3 additions & 2 deletions builtin/logical/pki/crl_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"strings"
"time"

"github.com/hashicorp/vault/helper/errutil"
Expand Down Expand Up @@ -65,7 +66,7 @@ func revokeCert(b *backend, req *logical.Request, serial string, fromLease bool)

cert, err := x509.ParseCertificate(certEntry.Value)
if err != nil {
return nil, fmt.Errorf("Error parsing certificate")
return nil, fmt.Errorf("Error parsing certificate: %s", err)
}
if cert == nil {
return nil, fmt.Errorf("Got a nil certificate")
Expand All @@ -86,7 +87,7 @@ func revokeCert(b *backend, req *logical.Request, serial string, fromLease bool)
revInfo.RevocationTime = currTime.Unix()
revInfo.RevocationTimeUTC = currTime.UTC()

revEntry, err = logical.StorageEntryJSON("revoked/"+serial, revInfo)
revEntry, err = logical.StorageEntryJSON("revoked/"+strings.ToLower(strings.Replace(serial, ":", "-", -1)), revInfo)
if err != nil {
return nil, fmt.Errorf("Error creating revocation entry")
}
Expand Down
3 changes: 2 additions & 1 deletion builtin/logical/pki/path_intermediate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pki
import (
"encoding/base64"
"fmt"
"strings"

"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/helper/errutil"
Expand Down Expand Up @@ -196,7 +197,7 @@ func (b *backend) pathSetSignedIntermediate(
return nil, err
}

entry.Key = "certs/" + cb.SerialNumber
entry.Key = "certs/" + strings.ToLower(strings.Replace(cb.SerialNumber, ":", "-", -1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have this creation of hyphenSerial in a function and use it in all the places? Seems like we are doing it in a couple of places. Easy to miss out if some if we make changes to this.

entry.Value = inputBundle.CertificateBytes
err = req.Storage.Put(entry)
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions builtin/logical/pki/path_issue_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pki
import (
"encoding/base64"
"fmt"
"strings"
"time"

"github.com/hashicorp/vault/helper/certutil"
Expand Down Expand Up @@ -242,11 +243,11 @@ func (b *backend) pathIssueSignCert(

if !role.NoStore {
err = req.Storage.Put(&logical.StorageEntry{
Key: "certs/" + cb.SerialNumber,
Key: "certs/" + strings.ToLower(strings.Replace(cb.SerialNumber, ":", "-", -1)),
Value: parsedBundle.CertificateBytes,
})
if err != nil {
return nil, fmt.Errorf("Unable to store certificate locally: %v", err)
return nil, fmt.Errorf("unable to store certificate locally: %v", err)
}
}

Expand Down
5 changes: 3 additions & 2 deletions builtin/logical/pki/path_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pki
import (
"encoding/base64"
"fmt"
"strings"

"github.com/hashicorp/vault/helper/errutil"
"github.com/hashicorp/vault/logical"
Expand Down Expand Up @@ -145,7 +146,7 @@ func (b *backend) pathCAGenerateRoot(
// Also store it as just the certificate identified by serial number, so it
// can be revoked
err = req.Storage.Put(&logical.StorageEntry{
Key: "certs/" + cb.SerialNumber,
Key: "certs/" + strings.ToLower(strings.Replace(cb.SerialNumber, ":", "-", -1)),
Value: parsedBundle.CertificateBytes,
})
if err != nil {
Expand Down Expand Up @@ -277,7 +278,7 @@ func (b *backend) pathCASignIntermediate(
}

err = req.Storage.Put(&logical.StorageEntry{
Key: "certs/" + cb.SerialNumber,
Key: "certs/" + strings.ToLower(strings.Replace(cb.SerialNumber, ":", "-", -1)),
Value: parsedBundle.CertificateBytes,
})
if err != nil {
Expand Down
5 changes: 1 addition & 4 deletions builtin/logical/pki/secret_certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package pki

import (
"fmt"
"strings"

"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
Expand Down Expand Up @@ -46,10 +45,8 @@ func (b *backend) secretCredsRevoke(
return nil, fmt.Errorf("could not find serial in internal secret data")
}

serial := strings.Replace(strings.ToLower(serialInt.(string)), "-", ":", -1)

b.revokeStorageLock.Lock()
defer b.revokeStorageLock.Unlock()

return revokeCert(b, req, serial, true)
return revokeCert(b, req, serialInt.(string), true)
}