commit
50a271edca
16 changed files with 570 additions and 79 deletions
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
|
@ -59,8 +59,9 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Codecov
|
name: Codecov
|
||||||
if: matrix.go == '1.18'
|
if: matrix.go == '1.18'
|
||||||
uses: codecov/codecov-action@v1.2.1
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
file: ./coverage.out # optional
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
files: ./coverage.out # optional
|
||||||
name: codecov-umbrella # optional
|
name: codecov-umbrella # optional
|
||||||
fail_ci_if_error: true # optional (default = false)
|
fail_ci_if_error: true # optional (default = false)
|
||||||
|
|
|
@ -8,12 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||||
### Added
|
### Added
|
||||||
- Added support for certificate renewals after expiry using the claim `allowRenewalAfterExpiry`.
|
- Added support for certificate renewals after expiry using the claim `allowRenewalAfterExpiry`.
|
||||||
- Added support for `extraNames` in X.509 templates.
|
- Added support for `extraNames` in X.509 templates.
|
||||||
|
- Added support for automatic configuration of linked RAs.
|
||||||
### Changed
|
### Changed
|
||||||
- Made SCEP CA URL paths dynamic
|
- Made SCEP CA URL paths dynamic
|
||||||
- Support two latest versions of Go (1.17, 1.18)
|
- Support two latest versions of Go (1.17, 1.18)
|
||||||
### Deprecated
|
### Deprecated
|
||||||
### Removed
|
### Removed
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Fixed admin credentials on RAs.
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
## [0.18.2] - 2022-03-01
|
## [0.18.2] - 2022-03-01
|
||||||
|
|
|
@ -130,22 +130,24 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
|
||||||
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
||||||
// more than a few minutes.
|
// more than a few minutes.
|
||||||
if err := claims.ValidateWithLeeway(jose.Expected{
|
if err := claims.ValidateWithLeeway(jose.Expected{
|
||||||
Issuer: prov.GetName(),
|
Time: time.Now().UTC(),
|
||||||
Time: time.Now().UTC(),
|
|
||||||
}, time.Minute); err != nil {
|
}, time.Minute); err != nil {
|
||||||
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "x5c.authorizeToken; invalid x5c claims")
|
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "x5c.authorizeToken; invalid x5c claims")
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate audience: path matches the current path
|
// validate audience: path matches the current path
|
||||||
if r.URL.Path != claims.Audience[0] {
|
if !matchesAudience(claims.Audience, a.config.Audience(r.URL.Path)) {
|
||||||
return nil, admin.NewError(admin.ErrorUnauthorizedType,
|
return nil, admin.NewError(admin.ErrorUnauthorizedType, "x5c.authorizeToken; x5c token has invalid audience claim (aud)")
|
||||||
"x5c.authorizeToken; x5c token has invalid audience "+
|
}
|
||||||
"claim (aud); expected %s, but got %s", r.URL.Path, claims.Audience)
|
|
||||||
|
// validate issuer: old versions used the provisioner name, new version uses
|
||||||
|
// 'step-admin-client/1.0'
|
||||||
|
if claims.Issuer != "step-admin-client/1.0" && claims.Issuer != prov.GetName() {
|
||||||
|
return nil, admin.NewError(admin.ErrorUnauthorizedType, "x5c.authorizeToken; x5c token has invalid issuer claim (iss)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims.Subject == "" {
|
if claims.Subject == "" {
|
||||||
return nil, admin.NewError(admin.ErrorUnauthorizedType,
|
return nil, admin.NewError(admin.ErrorUnauthorizedType, "x5c.authorizeToken; x5c token subject cannot be empty")
|
||||||
"x5c.authorizeToken; x5c token subject cannot be empty")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -156,7 +158,7 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
|
||||||
adminSANs := append([]string{leaf.Subject.CommonName}, leaf.DNSNames...)
|
adminSANs := append([]string{leaf.Subject.CommonName}, leaf.DNSNames...)
|
||||||
adminSANs = append(adminSANs, leaf.EmailAddresses...)
|
adminSANs = append(adminSANs, leaf.EmailAddresses...)
|
||||||
for _, san := range adminSANs {
|
for _, san := range adminSANs {
|
||||||
if adm, ok = a.LoadAdminBySubProv(san, claims.Issuer); ok {
|
if adm, ok = a.LoadAdminBySubProv(san, prov.GetName()); ok {
|
||||||
adminFound = true
|
adminFound = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -285,9 +287,16 @@ func (a *Authority) authorizeRenew(cert *x509.Certificate) error {
|
||||||
if isRevoked {
|
if isRevoked {
|
||||||
return errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...)
|
return errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...)
|
||||||
}
|
}
|
||||||
p, ok := a.provisioners.LoadByCertificate(cert)
|
p, err := a.LoadProvisionerByCertificate(cert)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...)
|
var ok bool
|
||||||
|
// For backward compatibility this method will also succeed if the
|
||||||
|
// certificate does not have a provisioner extension. LoadByCertificate
|
||||||
|
// returns the noop provisioner if this happens, and it allows
|
||||||
|
// certificate renewals.
|
||||||
|
if p, ok = a.provisioners.LoadByCertificate(cert); !ok {
|
||||||
|
return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := p.AuthorizeRenew(context.Background(), cert); err != nil {
|
if err := p.AuthorizeRenew(context.Background(), cert); err != nil {
|
||||||
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
|
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
|
||||||
|
@ -386,8 +395,8 @@ func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.
|
||||||
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating renew token"))
|
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating renew token"))
|
||||||
}
|
}
|
||||||
|
|
||||||
p, ok := a.provisioners.LoadByCertificate(leaf)
|
p, err := a.LoadProvisionerByCertificate(leaf)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, errs.Unauthorized("error validating renew token: cannot get provisioner from certificate")
|
return nil, errs.Unauthorized("error validating renew token: cannot get provisioner from certificate")
|
||||||
}
|
}
|
||||||
if err := a.UseToken(ott, p); err != nil {
|
if err := a.UseToken(ott, p); err != nil {
|
||||||
|
@ -395,7 +404,6 @@ func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := claims.ValidateWithLeeway(jose.Expected{
|
if err := claims.ValidateWithLeeway(jose.Expected{
|
||||||
Issuer: p.GetName(),
|
|
||||||
Subject: leaf.Subject.CommonName,
|
Subject: leaf.Subject.CommonName,
|
||||||
Time: time.Now().UTC(),
|
Time: time.Now().UTC(),
|
||||||
}, time.Minute); err != nil {
|
}, time.Minute); err != nil {
|
||||||
|
@ -420,6 +428,12 @@ func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.
|
||||||
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating renew token: invalid audience claim (aud)"))
|
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating renew token: invalid audience claim (aud)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate issuer: old versions used the provisioner name, new version uses
|
||||||
|
// 'step-ca-client/1.0'
|
||||||
|
if claims.Issuer != "step-ca-client/1.0" && claims.Issuer != p.GetName() {
|
||||||
|
return nil, admin.NewError(admin.ErrorUnauthorizedType, "error validating renew token: invalid issuer claim (iss)")
|
||||||
|
}
|
||||||
|
|
||||||
return leaf, nil
|
return leaf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -847,6 +847,29 @@ func TestAuthority_authorizeRenew(t *testing.T) {
|
||||||
cert: fooCrt,
|
cert: fooCrt,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ok/from db": func(t *testing.T) *authorizeTest {
|
||||||
|
a := testAuthority(t)
|
||||||
|
a.db = &db.MockAuthDB{
|
||||||
|
MIsRevoked: func(key string) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
MGetCertificateData: func(serialNumber string) (*db.CertificateData, error) {
|
||||||
|
p, ok := a.provisioners.LoadByName("step-cli")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("provisioner step-cli not found")
|
||||||
|
}
|
||||||
|
return &db.CertificateData{
|
||||||
|
Provisioner: &db.ProvisionerData{
|
||||||
|
ID: p.GetID(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &authorizeTest{
|
||||||
|
auth: a,
|
||||||
|
cert: fooCrt,
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, genTestCase := range tests {
|
for name, genTestCase := range tests {
|
||||||
|
@ -1381,7 +1404,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
t1, c1 := generateX5cToken(a1, signer, jose.Claims{
|
t1, c1 := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/renew"},
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now),
|
NotBefore: jose.NewNumericDate(now),
|
||||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
@ -1400,7 +1423,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
t2, c2 := generateX5cToken(a1, signer, jose.Claims{
|
t2, c2 := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/renew"},
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now),
|
NotBefore: jose.NewNumericDate(now),
|
||||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
IssuedAt: jose.NewNumericDate(now),
|
IssuedAt: jose.NewNumericDate(now),
|
||||||
|
@ -1417,12 +1440,31 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
badSigner, _ := generateX5cToken(a1, otherSigner, jose.Claims{
|
t3, c3 := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/renew"},
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-cli",
|
||||||
NotBefore: jose.NewNumericDate(now),
|
NotBefore: jose.NewNumericDate(now),
|
||||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badSigner, _ := generateX5cToken(a1, otherSigner, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-ca-client/1.0",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
cert.NotBefore = now
|
cert.NotBefore = now
|
||||||
cert.NotAfter = now.Add(time.Hour)
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
@ -1439,7 +1481,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
badProvisioner, _ := generateX5cToken(a1, signer, jose.Claims{
|
badProvisioner, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/renew"},
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now),
|
NotBefore: jose.NewNumericDate(now),
|
||||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
@ -1477,7 +1519,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
badSubject, _ := generateX5cToken(a1, signer, jose.Claims{
|
badSubject, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/renew"},
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
Subject: "bad-subject",
|
Subject: "bad-subject",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now),
|
NotBefore: jose.NewNumericDate(now),
|
||||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
@ -1496,7 +1538,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
badNotBefore, _ := generateX5cToken(a1, signer, jose.Claims{
|
badNotBefore, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/sign"},
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
NotBefore: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
Expiry: jose.NewNumericDate(now.Add(10 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(10 * time.Minute)),
|
||||||
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
@ -1515,7 +1557,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
badExpiry, _ := generateX5cToken(a1, signer, jose.Claims{
|
badExpiry, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/sign"},
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now.Add(-5 * time.Minute)),
|
NotBefore: jose.NewNumericDate(now.Add(-5 * time.Minute)),
|
||||||
Expiry: jose.NewNumericDate(now.Add(-time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(-time.Minute)),
|
||||||
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
@ -1534,7 +1576,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
badIssuedAt, _ := generateX5cToken(a1, signer, jose.Claims{
|
badIssuedAt, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/sign"},
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now),
|
NotBefore: jose.NewNumericDate(now),
|
||||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
IssuedAt: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
IssuedAt: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
@ -1554,7 +1596,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
badAudience, _ := generateX5cToken(a1, signer, jose.Claims{
|
badAudience, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
Audience: []string{"https://example.com/1.0/sign"},
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
Subject: "test.example.com",
|
Subject: "test.example.com",
|
||||||
Issuer: "step-cli",
|
Issuer: "step-ca-client/1.0",
|
||||||
NotBefore: jose.NewNumericDate(now),
|
NotBefore: jose.NewNumericDate(now),
|
||||||
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
@ -1584,6 +1626,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"ok", a1, args{ctx, t1}, c1, false},
|
{"ok", a1, args{ctx, t1}, c1, false},
|
||||||
{"ok expired cert", a1, args{ctx, t2}, c2, false},
|
{"ok expired cert", a1, args{ctx, t2}, c2, false},
|
||||||
|
{"ok provisioner issuer", a1, args{ctx, t3}, c3, false},
|
||||||
{"fail token", a1, args{ctx, "not.a.token"}, nil, true},
|
{"fail token", a1, args{ctx, "not.a.token"}, nil, true},
|
||||||
{"fail token reuse", a1, args{ctx, t1}, nil, true},
|
{"fail token reuse", a1, args{ctx, t1}, nil, true},
|
||||||
{"fail token signature", a1, args{ctx, badSigner}, nil, true},
|
{"fail token signature", a1, args{ctx, badSigner}, nil, true},
|
||||||
|
|
|
@ -304,6 +304,18 @@ func (c *Config) GetAudiences() provisioner.Audiences {
|
||||||
return audiences
|
return audiences
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Audience returns the list of audiences for a given path.
|
||||||
|
func (c *Config) Audience(path string) []string {
|
||||||
|
audiences := make([]string, len(c.DNSNames)+1)
|
||||||
|
for i, name := range c.DNSNames {
|
||||||
|
hostname := toHostname(name)
|
||||||
|
audiences[i] = "https://" + hostname + path
|
||||||
|
}
|
||||||
|
// For backward compatibility
|
||||||
|
audiences[len(c.DNSNames)] = path
|
||||||
|
return audiences
|
||||||
|
}
|
||||||
|
|
||||||
func toHostname(name string) string {
|
func toHostname(name string) string {
|
||||||
// ensure an IPv6 address is represented with square brackets when used as hostname
|
// ensure an IPv6 address is represented with square brackets when used as hostname
|
||||||
if ip := net.ParseIP(name); ip != nil && ip.To4() == nil {
|
if ip := net.ParseIP(name); ip != nil && ip.To4() == nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -317,3 +318,38 @@ func Test_toHostname(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_Audience(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
DNSNames []string
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"ok", fields{[]string{
|
||||||
|
"ca", "ca.example.com", "127.0.0.1", "::1",
|
||||||
|
}}, args{"/path"}, []string{
|
||||||
|
"https://ca/path",
|
||||||
|
"https://ca.example.com/path",
|
||||||
|
"https://127.0.0.1/path",
|
||||||
|
"https://[::1]/path",
|
||||||
|
"/path",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Config{
|
||||||
|
DNSNames: tt.fields.DNSNames,
|
||||||
|
}
|
||||||
|
if got := c.Audience(tt.args.path); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Config.Audience() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -235,6 +235,28 @@ func (c *linkedCaClient) DeleteAdmin(ctx context.Context, id string) error {
|
||||||
return errors.Wrap(err, "error deleting admin")
|
return errors.Wrap(err, "error deleting admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *linkedCaClient) GetCertificateData(serial string) (*db.CertificateData, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := c.client.GetCertificate(ctx, &linkedca.GetCertificateRequest{
|
||||||
|
Serial: serial,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pd *db.ProvisionerData
|
||||||
|
if p := resp.Provisioner; p != nil {
|
||||||
|
pd = &db.ProvisionerData{
|
||||||
|
ID: p.Id, Name: p.Name, Type: p.Type.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &db.CertificateData{
|
||||||
|
Provisioner: pd,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *linkedCaClient) StoreCertificateChain(prov provisioner.Interface, fullchain ...*x509.Certificate) error {
|
func (c *linkedCaClient) StoreCertificateChain(prov provisioner.Interface, fullchain ...*x509.Certificate) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/db"
|
|
||||||
"github.com/smallstep/certificates/errs"
|
"github.com/smallstep/certificates/errs"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
@ -212,8 +211,6 @@ type Config struct {
|
||||||
Claims Claims
|
Claims Claims
|
||||||
// Audiences are the audiences used in the default provisioner, (JWK).
|
// Audiences are the audiences used in the default provisioner, (JWK).
|
||||||
Audiences Audiences
|
Audiences Audiences
|
||||||
// DB is the interface to the authority DB client.
|
|
||||||
DB db.AuthDB
|
|
||||||
// SSHKeys are the root SSH public keys
|
// SSHKeys are the root SSH public keys
|
||||||
SSHKeys *SSHKeys
|
SSHKeys *SSHKeys
|
||||||
// GetIdentityFunc is a function that returns an identity that will be
|
// GetIdentityFunc is a function that returns an identity that will be
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/smallstep/certificates/authority/admin"
|
"github.com/smallstep/certificates/authority/admin"
|
||||||
"github.com/smallstep/certificates/authority/config"
|
"github.com/smallstep/certificates/authority/config"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
|
"github.com/smallstep/certificates/db"
|
||||||
"github.com/smallstep/certificates/errs"
|
"github.com/smallstep/certificates/errs"
|
||||||
"go.step.sm/cli-utils/step"
|
"go.step.sm/cli-utils/step"
|
||||||
"go.step.sm/cli-utils/ui"
|
"go.step.sm/cli-utils/ui"
|
||||||
|
@ -46,13 +47,43 @@ func (a *Authority) GetProvisioners(cursor string, limit int) (provisioner.List,
|
||||||
func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisioner.Interface, error) {
|
func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisioner.Interface, error) {
|
||||||
a.adminMutex.RLock()
|
a.adminMutex.RLock()
|
||||||
defer a.adminMutex.RUnlock()
|
defer a.adminMutex.RUnlock()
|
||||||
|
if p, err := a.unsafeLoadProvisionerFromDatabase(crt); err == nil {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
return a.unsafeLoadProvisionerFromExtension(crt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authority) unsafeLoadProvisionerFromExtension(crt *x509.Certificate) (provisioner.Interface, error) {
|
||||||
p, ok := a.provisioners.LoadByCertificate(crt)
|
p, ok := a.provisioners.LoadByCertificate(crt)
|
||||||
if !ok {
|
if !ok || p.GetType() == 0 {
|
||||||
return nil, admin.NewError(admin.ErrorNotFoundType, "unable to load provisioner from certificate")
|
return nil, admin.NewError(admin.ErrorNotFoundType, "unable to load provisioner from certificate")
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Authority) unsafeLoadProvisionerFromDatabase(crt *x509.Certificate) (provisioner.Interface, error) {
|
||||||
|
// certificateDataGetter is an interface that can be used to retrieve the
|
||||||
|
// provisioner from a db or a linked ca.
|
||||||
|
type certificateDataGetter interface {
|
||||||
|
GetCertificateData(string) (*db.CertificateData, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var data *db.CertificateData
|
||||||
|
|
||||||
|
if cdg, ok := a.adminDB.(certificateDataGetter); ok {
|
||||||
|
data, err = cdg.GetCertificateData(crt.SerialNumber.String())
|
||||||
|
} else if cdg, ok := a.db.(certificateDataGetter); ok {
|
||||||
|
data, err = cdg.GetCertificateData(crt.SerialNumber.String())
|
||||||
|
}
|
||||||
|
if err == nil && data != nil && data.Provisioner != nil {
|
||||||
|
if p, ok := a.provisioners.Load(data.Provisioner.ID); ok {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, admin.NewError(admin.ErrorNotFoundType, "unable to load provisioner from certificate")
|
||||||
|
}
|
||||||
|
|
||||||
// LoadProvisionerByToken returns an interface to the provisioner that
|
// LoadProvisionerByToken returns an interface to the provisioner that
|
||||||
// provisioned the token.
|
// provisioned the token.
|
||||||
func (a *Authority) LoadProvisionerByToken(token *jwt.JSONWebToken, claims *jwt.Claims) (provisioner.Interface, error) {
|
func (a *Authority) LoadProvisionerByToken(token *jwt.JSONWebToken, claims *jwt.Claims) (provisioner.Interface, error) {
|
||||||
|
@ -103,7 +134,6 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner.
|
||||||
return provisioner.Config{
|
return provisioner.Config{
|
||||||
Claims: claimer.Claims(),
|
Claims: claimer.Claims(),
|
||||||
Audiences: a.config.GetAudiences(),
|
Audiences: a.config.GetAudiences(),
|
||||||
DB: a.db,
|
|
||||||
SSHKeys: &provisioner.SSHKeys{
|
SSHKeys: &provisioner.SSHKeys{
|
||||||
UserKeys: sshKeys.UserKeys,
|
UserKeys: sshKeys.UserKeys,
|
||||||
HostKeys: sshKeys.HostKeys,
|
HostKeys: sshKeys.HostKeys,
|
||||||
|
@ -247,7 +277,7 @@ func (a *Authority) RemoveProvisioner(ctx context.Context, id string) error {
|
||||||
|
|
||||||
// CreateFirstProvisioner creates and stores the first provisioner when using
|
// CreateFirstProvisioner creates and stores the first provisioner when using
|
||||||
// admin database provisioner storage.
|
// admin database provisioner storage.
|
||||||
func CreateFirstProvisioner(ctx context.Context, db admin.DB, password string) (*linkedca.Provisioner, error) {
|
func CreateFirstProvisioner(ctx context.Context, adminDB admin.DB, password string) (*linkedca.Provisioner, error) {
|
||||||
if password == "" {
|
if password == "" {
|
||||||
pass, err := ui.PromptPasswordGenerate("Please enter the password to encrypt your first provisioner, leave empty and we'll generate one")
|
pass, err := ui.PromptPasswordGenerate("Please enter the password to encrypt your first provisioner, leave empty and we'll generate one")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -290,7 +320,7 @@ func CreateFirstProvisioner(ctx context.Context, db admin.DB, password string) (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := db.CreateProvisioner(ctx, p); err != nil {
|
if err := adminDB.CreateProvisioner(ctx, p); err != nil {
|
||||||
return nil, admin.WrapErrorISE(err, "error creating provisioner")
|
return nil, admin.WrapErrorISE(err, "error creating provisioner")
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
package authority
|
package authority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/smallstep/assert"
|
"github.com/smallstep/assert"
|
||||||
"github.com/smallstep/certificates/api/render"
|
"github.com/smallstep/certificates/api/render"
|
||||||
|
"github.com/smallstep/certificates/authority/admin"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
|
"github.com/smallstep/certificates/db"
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
|
"go.step.sm/crypto/keyutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetEncryptedKey(t *testing.T) {
|
func TestGetEncryptedKey(t *testing.T) {
|
||||||
|
@ -67,6 +75,15 @@ func TestGetEncryptedKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockAdminDB struct {
|
||||||
|
admin.MockDB
|
||||||
|
MGetCertificateData func(string) (*db.CertificateData, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockAdminDB) GetCertificateData(sn string) (*db.CertificateData, error) {
|
||||||
|
return c.MGetCertificateData(sn)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetProvisioners(t *testing.T) {
|
func TestGetProvisioners(t *testing.T) {
|
||||||
type gp struct {
|
type gp struct {
|
||||||
a *Authority
|
a *Authority
|
||||||
|
@ -104,3 +121,133 @@ func TestGetProvisioners(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthority_LoadProvisionerByCertificate(t *testing.T) {
|
||||||
|
_, priv, err := keyutil.GenerateDefaultKeyPair()
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
csr := getCSR(t, priv)
|
||||||
|
|
||||||
|
sign := func(a *Authority, extraOpts ...provisioner.SignOption) *x509.Certificate {
|
||||||
|
key, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.SignMethod)
|
||||||
|
opts, err := a.Authorize(ctx, token)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
opts = append(opts, extraOpts...)
|
||||||
|
certs, err := a.Sign(csr, provisioner.SignOptions{}, opts...)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
return certs[0]
|
||||||
|
}
|
||||||
|
getProvisioner := func(a *Authority, name string) provisioner.Interface {
|
||||||
|
p, ok := a.provisioners.LoadByName(name)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("provisioner %s does not exists", name)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
removeExtension := provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
for i, ext := range cert.ExtraExtensions {
|
||||||
|
if ext.Id.Equal(provisioner.StepOIDProvisioner) {
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions[:i], cert.ExtraExtensions[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
a0 := testAuthority(t)
|
||||||
|
|
||||||
|
a1 := testAuthority(t)
|
||||||
|
a1.db = &db.MockAuthDB{
|
||||||
|
MUseToken: func(id, tok string) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
MGetCertificateData: func(serialNumber string) (*db.CertificateData, error) {
|
||||||
|
p, err := a1.LoadProvisionerByName("dev")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return &db.CertificateData{
|
||||||
|
Provisioner: &db.ProvisionerData{
|
||||||
|
ID: p.GetID(),
|
||||||
|
Name: p.GetName(),
|
||||||
|
Type: p.GetType().String(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
a2 := testAuthority(t)
|
||||||
|
a2.adminDB = &mockAdminDB{
|
||||||
|
MGetCertificateData: (func(s string) (*db.CertificateData, error) {
|
||||||
|
p, err := a2.LoadProvisionerByName("dev")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return &db.CertificateData{
|
||||||
|
Provisioner: &db.ProvisionerData{
|
||||||
|
ID: p.GetID(),
|
||||||
|
Name: p.GetName(),
|
||||||
|
Type: p.GetType().String(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
a3 := testAuthority(t)
|
||||||
|
a3.db = &db.MockAuthDB{
|
||||||
|
MUseToken: func(id, tok string) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
MGetCertificateData: func(serialNumber string) (*db.CertificateData, error) {
|
||||||
|
return &db.CertificateData{
|
||||||
|
Provisioner: &db.ProvisionerData{
|
||||||
|
ID: "foo", Name: "foo", Type: "foo",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
a4 := testAuthority(t)
|
||||||
|
a4.adminDB = &mockAdminDB{
|
||||||
|
MGetCertificateData: func(serialNumber string) (*db.CertificateData, error) {
|
||||||
|
return &db.CertificateData{
|
||||||
|
Provisioner: &db.ProvisionerData{
|
||||||
|
ID: "foo", Name: "foo", Type: "foo",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
crt *x509.Certificate
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
authority *Authority
|
||||||
|
args args
|
||||||
|
want provisioner.Interface
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok from certificate", a0, args{sign(a0)}, getProvisioner(a0, "step-cli"), false},
|
||||||
|
{"ok from db", a1, args{sign(a1)}, getProvisioner(a1, "dev"), false},
|
||||||
|
{"ok from admindb", a2, args{sign(a2)}, getProvisioner(a2, "dev"), false},
|
||||||
|
{"fail from certificate", a0, args{sign(a0, removeExtension)}, nil, true},
|
||||||
|
{"fail from db", a3, args{sign(a3, removeExtension)}, nil, true},
|
||||||
|
{"fail from admindb", a4, args{sign(a4, removeExtension)}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := tt.authority.LoadProvisionerByCertificate(tt.args.crt)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Authority.LoadProvisionerByCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Authority.LoadProvisionerByCertificate() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -347,6 +347,8 @@ func (a *Authority) storeCertificate(prov provisioner.Interface, fullchain []*x5
|
||||||
|
|
||||||
// Store certificate in local db
|
// Store certificate in local db
|
||||||
switch s := a.db.(type) {
|
switch s := a.db.(type) {
|
||||||
|
case linkedChainStorer:
|
||||||
|
return s.StoreCertificateChain(prov, fullchain...)
|
||||||
case certificateChainStorer:
|
case certificateChainStorer:
|
||||||
return s.StoreCertificateChain(fullchain...)
|
return s.StoreCertificateChain(fullchain...)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -23,7 +23,10 @@ import (
|
||||||
"google.golang.org/protobuf/encoding/protojson"
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adminURLPrefix = "admin"
|
const (
|
||||||
|
adminURLPrefix = "admin"
|
||||||
|
adminIssuer = "step-admin-client/1.0"
|
||||||
|
)
|
||||||
|
|
||||||
// AdminClient implements an HTTP client for the CA server.
|
// AdminClient implements an HTTP client for the CA server.
|
||||||
type AdminClient struct {
|
type AdminClient struct {
|
||||||
|
@ -35,7 +38,6 @@ type AdminClient struct {
|
||||||
x5cCertFile string
|
x5cCertFile string
|
||||||
x5cCertStrs []string
|
x5cCertStrs []string
|
||||||
x5cCert *x509.Certificate
|
x5cCert *x509.Certificate
|
||||||
x5cIssuer string
|
|
||||||
x5cSubject string
|
x5cSubject string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,12 +79,11 @@ func NewAdminClient(endpoint string, opts ...ClientOption) (*AdminClient, error)
|
||||||
x5cCertFile: o.x5cCertFile,
|
x5cCertFile: o.x5cCertFile,
|
||||||
x5cCertStrs: o.x5cCertStrs,
|
x5cCertStrs: o.x5cCertStrs,
|
||||||
x5cCert: o.x5cCert,
|
x5cCert: o.x5cCert,
|
||||||
x5cIssuer: o.x5cIssuer,
|
|
||||||
x5cSubject: o.x5cSubject,
|
x5cSubject: o.x5cSubject,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AdminClient) generateAdminToken(urlPath string) (string, error) {
|
func (c *AdminClient) generateAdminToken(aud *url.URL) (string, error) {
|
||||||
// A random jwt id will be used to identify duplicated tokens
|
// A random jwt id will be used to identify duplicated tokens
|
||||||
jwtID, err := randutil.Hex(64) // 256 bits
|
jwtID, err := randutil.Hex(64) // 256 bits
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,8 +94,8 @@ func (c *AdminClient) generateAdminToken(urlPath string) (string, error) {
|
||||||
tokOptions := []token.Options{
|
tokOptions := []token.Options{
|
||||||
token.WithJWTID(jwtID),
|
token.WithJWTID(jwtID),
|
||||||
token.WithKid(c.x5cJWK.KeyID),
|
token.WithKid(c.x5cJWK.KeyID),
|
||||||
token.WithIssuer(c.x5cIssuer),
|
token.WithIssuer(adminIssuer),
|
||||||
token.WithAudience(urlPath),
|
token.WithAudience(aud.String()),
|
||||||
token.WithValidity(now, now.Add(token.DefaultValidity)),
|
token.WithValidity(now, now.Add(token.DefaultValidity)),
|
||||||
token.WithX5CCerts(c.x5cCertStrs),
|
token.WithX5CCerts(c.x5cCertStrs),
|
||||||
}
|
}
|
||||||
|
@ -205,7 +206,7 @@ func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*adminAPI.GetAdmin
|
||||||
Path: "/admin/admins",
|
Path: "/admin/admins",
|
||||||
RawQuery: o.rawQuery(),
|
RawQuery: o.rawQuery(),
|
||||||
})
|
})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -260,7 +261,7 @@ func (c *AdminClient) CreateAdmin(createAdminRequest *adminAPI.CreateAdminReques
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admins"})
|
u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admins"})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -292,7 +293,7 @@ retry:
|
||||||
func (c *AdminClient) RemoveAdmin(id string) error {
|
func (c *AdminClient) RemoveAdmin(id string) error {
|
||||||
var retried bool
|
var retried bool
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error generating admin token")
|
return errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -324,7 +325,7 @@ func (c *AdminClient) UpdateAdmin(id string, uar *adminAPI.UpdateAdminRequest) (
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -371,7 +372,7 @@ func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provi
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("must set either name or id in method options")
|
return nil, errors.New("must set either name or id in method options")
|
||||||
}
|
}
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -410,7 +411,7 @@ func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*admin
|
||||||
Path: "/admin/provisioners",
|
Path: "/admin/provisioners",
|
||||||
RawQuery: o.rawQuery(),
|
RawQuery: o.rawQuery(),
|
||||||
})
|
})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -480,7 +481,7 @@ func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error {
|
||||||
default:
|
default:
|
||||||
return errors.New("must set either name or id in method options")
|
return errors.New("must set either name or id in method options")
|
||||||
}
|
}
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error generating admin token")
|
return errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -512,7 +513,7 @@ func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.P
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners")})
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners")})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -548,7 +549,7 @@ func (c *AdminClient) UpdateProvisioner(name string, prov *linkedca.Provisioner)
|
||||||
return errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
return errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)})
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error generating admin token")
|
return errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -587,7 +588,7 @@ func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName, reference
|
||||||
Path: p,
|
Path: p,
|
||||||
RawQuery: o.rawQuery(),
|
RawQuery: o.rawQuery(),
|
||||||
})
|
})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -623,7 +624,7 @@ func (c *AdminClient) CreateExternalAccountKey(provisionerName string, eakReques
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab/", provisionerName)})
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab/", provisionerName)})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating admin token")
|
return nil, errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
@ -655,7 +656,7 @@ retry:
|
||||||
func (c *AdminClient) RemoveExternalAccountKey(provisionerName, keyID string) error {
|
func (c *AdminClient) RemoveExternalAccountKey(provisionerName, keyID string) error {
|
||||||
var retried bool
|
var retried bool
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab", provisionerName, "/", keyID)})
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab", provisionerName, "/", keyID)})
|
||||||
tok, err := c.generateAdminToken(u.Path)
|
tok, err := c.generateAdminToken(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error generating admin token")
|
return errors.Wrapf(err, "error generating admin token")
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,7 @@ func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Han
|
||||||
for _, s := range nonAuthenticatedPaths {
|
for _, s := range nonAuthenticatedPaths {
|
||||||
if strings.HasPrefix(r.URL.Path, s) || strings.HasPrefix(r.URL.Path, "/1.0"+s) {
|
if strings.HasPrefix(r.URL.Path, s) || strings.HasPrefix(r.URL.Path, "/1.0"+s) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0
|
isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0
|
||||||
|
|
34
ca/client.go
34
ca/client.go
|
@ -10,7 +10,6 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
@ -116,7 +115,6 @@ type clientOptions struct {
|
||||||
x5cCertFile string
|
x5cCertFile string
|
||||||
x5cCertStrs []string
|
x5cCertStrs []string
|
||||||
x5cCert *x509.Certificate
|
x5cCert *x509.Certificate
|
||||||
x5cIssuer string
|
|
||||||
x5cSubject string
|
x5cSubject string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,18 +292,6 @@ func WithCertificate(cert tls.Certificate) ClientOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
stepOIDRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
|
|
||||||
stepOIDProvisioner = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 1)...)
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepProvisionerASN1 struct {
|
|
||||||
Type int
|
|
||||||
Name []byte
|
|
||||||
CredentialID []byte
|
|
||||||
KeyValuePairs []string `asn1:"optional,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAdminX5C will set the given file as the X5C certificate for use
|
// WithAdminX5C will set the given file as the X5C certificate for use
|
||||||
// by the client.
|
// by the client.
|
||||||
func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile string) ClientOption {
|
func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile string) ClientOption {
|
||||||
|
@ -332,19 +318,13 @@ func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile strin
|
||||||
}
|
}
|
||||||
|
|
||||||
o.x5cCert = certs[0]
|
o.x5cCert = certs[0]
|
||||||
o.x5cSubject = o.x5cCert.Subject.CommonName
|
switch leaf := certs[0]; {
|
||||||
|
case leaf.Subject.CommonName != "":
|
||||||
for _, e := range o.x5cCert.Extensions {
|
o.x5cSubject = leaf.Subject.CommonName
|
||||||
if e.Id.Equal(stepOIDProvisioner) {
|
case len(leaf.DNSNames) > 0:
|
||||||
var prov stepProvisionerASN1
|
o.x5cSubject = leaf.DNSNames[0]
|
||||||
if _, err := asn1.Unmarshal(e.Value, &prov); err != nil {
|
case len(leaf.EmailAddresses) > 0:
|
||||||
return errors.Wrap(err, "error unmarshaling provisioner OID from certificate")
|
o.x5cSubject = leaf.EmailAddresses[0]
|
||||||
}
|
|
||||||
o.x5cIssuer = string(prov.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if o.x5cIssuer == "" {
|
|
||||||
return errors.New("provisioner extension not found in certificate")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
70
db/db.go
70
db/db.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/nosql"
|
"github.com/smallstep/nosql"
|
||||||
"github.com/smallstep/nosql/database"
|
"github.com/smallstep/nosql/database"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
certsTable = []byte("x509_certs")
|
certsTable = []byte("x509_certs")
|
||||||
|
certsDataTable = []byte("x509_certs_data")
|
||||||
revokedCertsTable = []byte("revoked_x509_certs")
|
revokedCertsTable = []byte("revoked_x509_certs")
|
||||||
revokedSSHCertsTable = []byte("revoked_ssh_certs")
|
revokedSSHCertsTable = []byte("revoked_ssh_certs")
|
||||||
usedOTTTable = []byte("used_ott")
|
usedOTTTable = []byte("used_ott")
|
||||||
|
@ -82,7 +84,7 @@ func New(c *Config) (AuthDB, error) {
|
||||||
tables := [][]byte{
|
tables := [][]byte{
|
||||||
revokedCertsTable, certsTable, usedOTTTable,
|
revokedCertsTable, certsTable, usedOTTTable,
|
||||||
sshCertsTable, sshHostsTable, sshHostPrincipalsTable, sshUsersTable,
|
sshCertsTable, sshHostsTable, sshHostPrincipalsTable, sshUsersTable,
|
||||||
revokedSSHCertsTable,
|
revokedSSHCertsTable, certsDataTable,
|
||||||
}
|
}
|
||||||
for _, b := range tables {
|
for _, b := range tables {
|
||||||
if err := db.CreateTable(b); err != nil {
|
if err := db.CreateTable(b); err != nil {
|
||||||
|
@ -202,6 +204,19 @@ func (db *DB) GetCertificate(serialNumber string) (*x509.Certificate, error) {
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCertificateData returns the data stored for a provisioner
|
||||||
|
func (db *DB) GetCertificateData(serialNumber string) (*CertificateData, error) {
|
||||||
|
b, err := db.Get(certsDataTable, []byte(serialNumber))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "database Get error")
|
||||||
|
}
|
||||||
|
var data CertificateData
|
||||||
|
if err := json.Unmarshal(b, &data); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error unmarshaling json")
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
// StoreCertificate stores a certificate PEM.
|
// StoreCertificate stores a certificate PEM.
|
||||||
func (db *DB) StoreCertificate(crt *x509.Certificate) error {
|
func (db *DB) StoreCertificate(crt *x509.Certificate) error {
|
||||||
if err := db.Set(certsTable, []byte(crt.SerialNumber.String()), crt.Raw); err != nil {
|
if err := db.Set(certsTable, []byte(crt.SerialNumber.String()), crt.Raw); err != nil {
|
||||||
|
@ -210,6 +225,47 @@ func (db *DB) StoreCertificate(crt *x509.Certificate) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CertificateData is the JSON representation of the data stored in
|
||||||
|
// x509_certs_data table.
|
||||||
|
type CertificateData struct {
|
||||||
|
Provisioner *ProvisionerData `json:"provisioner,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProvisionerData is the JSON representation of the provisioner stored in the
|
||||||
|
// x509_certs_data table.
|
||||||
|
type ProvisionerData struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreCertificateChain stores the leaf certificate and the provisioner that
|
||||||
|
// authorized the certificate.
|
||||||
|
func (db *DB) StoreCertificateChain(p provisioner.Interface, chain ...*x509.Certificate) error {
|
||||||
|
leaf := chain[0]
|
||||||
|
serialNumber := []byte(leaf.SerialNumber.String())
|
||||||
|
data := &CertificateData{}
|
||||||
|
if p != nil {
|
||||||
|
data.Provisioner = &ProvisionerData{
|
||||||
|
ID: p.GetID(),
|
||||||
|
Name: p.GetName(),
|
||||||
|
Type: p.GetType().String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error marshaling json")
|
||||||
|
}
|
||||||
|
// Add certificate and certificate data in one transaction.
|
||||||
|
tx := new(database.Tx)
|
||||||
|
tx.Set(certsTable, serialNumber, leaf.Raw)
|
||||||
|
tx.Set(certsDataTable, serialNumber, b)
|
||||||
|
if err := db.Update(tx); err != nil {
|
||||||
|
return errors.Wrap(err, "database Update error")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// UseToken returns true if we were able to successfully store the token for
|
// UseToken returns true if we were able to successfully store the token for
|
||||||
// for the first time, false otherwise.
|
// for the first time, false otherwise.
|
||||||
func (db *DB) UseToken(id, tok string) (bool, error) {
|
func (db *DB) UseToken(id, tok string) (bool, error) {
|
||||||
|
@ -304,6 +360,7 @@ type MockAuthDB struct {
|
||||||
MRevoke func(rci *RevokedCertificateInfo) error
|
MRevoke func(rci *RevokedCertificateInfo) error
|
||||||
MRevokeSSH func(rci *RevokedCertificateInfo) error
|
MRevokeSSH func(rci *RevokedCertificateInfo) error
|
||||||
MGetCertificate func(serialNumber string) (*x509.Certificate, error)
|
MGetCertificate func(serialNumber string) (*x509.Certificate, error)
|
||||||
|
MGetCertificateData func(serialNumber string) (*CertificateData, error)
|
||||||
MStoreCertificate func(crt *x509.Certificate) error
|
MStoreCertificate func(crt *x509.Certificate) error
|
||||||
MUseToken func(id, tok string) (bool, error)
|
MUseToken func(id, tok string) (bool, error)
|
||||||
MIsSSHHost func(principal string) (bool, error)
|
MIsSSHHost func(principal string) (bool, error)
|
||||||
|
@ -363,6 +420,17 @@ func (m *MockAuthDB) GetCertificate(serialNumber string) (*x509.Certificate, err
|
||||||
return m.Ret1.(*x509.Certificate), m.Err
|
return m.Ret1.(*x509.Certificate), m.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCertificateData mock.
|
||||||
|
func (m *MockAuthDB) GetCertificateData(serialNumber string) (*CertificateData, error) {
|
||||||
|
if m.MGetCertificateData != nil {
|
||||||
|
return m.MGetCertificateData(serialNumber)
|
||||||
|
}
|
||||||
|
if cd, ok := m.Ret1.(*CertificateData); ok {
|
||||||
|
return cd, m.Err
|
||||||
|
}
|
||||||
|
return nil, m.Err
|
||||||
|
}
|
||||||
|
|
||||||
// StoreCertificate mock.
|
// StoreCertificate mock.
|
||||||
func (m *MockAuthDB) StoreCertificate(crt *x509.Certificate) error {
|
func (m *MockAuthDB) StoreCertificate(crt *x509.Certificate) error {
|
||||||
if m.MStoreCertificate != nil {
|
if m.MStoreCertificate != nil {
|
||||||
|
|
135
db/db_test.go
135
db/db_test.go
|
@ -1,10 +1,15 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/smallstep/assert"
|
"github.com/smallstep/assert"
|
||||||
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
|
"github.com/smallstep/nosql"
|
||||||
"github.com/smallstep/nosql/database"
|
"github.com/smallstep/nosql/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -158,3 +163,133 @@ func TestUseToken(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDB_StoreCertificateChain(t *testing.T) {
|
||||||
|
p := &provisioner.JWK{
|
||||||
|
ID: "some-id",
|
||||||
|
Name: "admin",
|
||||||
|
Type: "JWK",
|
||||||
|
}
|
||||||
|
chain := []*x509.Certificate{
|
||||||
|
{Raw: []byte("the certificate"), SerialNumber: big.NewInt(1234)},
|
||||||
|
}
|
||||||
|
type fields struct {
|
||||||
|
DB nosql.DB
|
||||||
|
isUp bool
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
p provisioner.Interface
|
||||||
|
chain []*x509.Certificate
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", fields{&MockNoSQLDB{
|
||||||
|
MUpdate: func(tx *database.Tx) error {
|
||||||
|
if len(tx.Operations) != 2 {
|
||||||
|
t.Fatal("unexpected number of operations")
|
||||||
|
}
|
||||||
|
assert.Equals(t, []byte("x509_certs"), tx.Operations[0].Bucket)
|
||||||
|
assert.Equals(t, []byte("1234"), tx.Operations[0].Key)
|
||||||
|
assert.Equals(t, []byte("the certificate"), tx.Operations[0].Value)
|
||||||
|
assert.Equals(t, []byte("x509_certs_data"), tx.Operations[1].Bucket)
|
||||||
|
assert.Equals(t, []byte("1234"), tx.Operations[1].Key)
|
||||||
|
assert.Equals(t, []byte(`{"provisioner":{"id":"some-id","name":"admin","type":"JWK"}}`), tx.Operations[1].Value)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, true}, args{p, chain}, false},
|
||||||
|
{"ok no provisioner", fields{&MockNoSQLDB{
|
||||||
|
MUpdate: func(tx *database.Tx) error {
|
||||||
|
if len(tx.Operations) != 2 {
|
||||||
|
t.Fatal("unexpected number of operations")
|
||||||
|
}
|
||||||
|
assert.Equals(t, []byte("x509_certs"), tx.Operations[0].Bucket)
|
||||||
|
assert.Equals(t, []byte("1234"), tx.Operations[0].Key)
|
||||||
|
assert.Equals(t, []byte("the certificate"), tx.Operations[0].Value)
|
||||||
|
assert.Equals(t, []byte("x509_certs_data"), tx.Operations[1].Bucket)
|
||||||
|
assert.Equals(t, []byte("1234"), tx.Operations[1].Key)
|
||||||
|
assert.Equals(t, []byte(`{}`), tx.Operations[1].Value)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, true}, args{nil, chain}, false},
|
||||||
|
{"fail store certificate", fields{&MockNoSQLDB{
|
||||||
|
MUpdate: func(tx *database.Tx) error {
|
||||||
|
return errors.New("test error")
|
||||||
|
},
|
||||||
|
}, true}, args{p, chain}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
d := &DB{
|
||||||
|
DB: tt.fields.DB,
|
||||||
|
isUp: tt.fields.isUp,
|
||||||
|
}
|
||||||
|
if err := d.StoreCertificateChain(tt.args.p, tt.args.chain...); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("DB.StoreCertificateChain() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDB_GetCertificateData(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
DB nosql.DB
|
||||||
|
isUp bool
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
serialNumber string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want *CertificateData
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", fields{&MockNoSQLDB{
|
||||||
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||||
|
assert.Equals(t, bucket, []byte("x509_certs_data"))
|
||||||
|
assert.Equals(t, key, []byte("1234"))
|
||||||
|
return []byte(`{"provisioner":{"id":"some-id","name":"admin","type":"JWK"}}`), nil
|
||||||
|
},
|
||||||
|
}, true}, args{"1234"}, &CertificateData{
|
||||||
|
Provisioner: &ProvisionerData{
|
||||||
|
ID: "some-id", Name: "admin", Type: "JWK",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"fail not found", fields{&MockNoSQLDB{
|
||||||
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||||
|
return nil, database.ErrNotFound
|
||||||
|
},
|
||||||
|
}, true}, args{"1234"}, nil, true},
|
||||||
|
{"fail db", fields{&MockNoSQLDB{
|
||||||
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||||
|
return nil, errors.New("an error")
|
||||||
|
},
|
||||||
|
}, true}, args{"1234"}, nil, true},
|
||||||
|
{"fail unmarshal", fields{&MockNoSQLDB{
|
||||||
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||||
|
return []byte(`{"bad-json"}`), nil
|
||||||
|
},
|
||||||
|
}, true}, args{"1234"}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
db := &DB{
|
||||||
|
DB: tt.fields.DB,
|
||||||
|
isUp: tt.fields.isUp,
|
||||||
|
}
|
||||||
|
got, err := db.GetCertificateData(tt.args.serialNumber)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("DB.GetCertificateData() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("DB.GetCertificateData() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue