forked from TrueCloudLab/certificates
Merge branch 'master' into context-authority
This commit is contained in:
commit
26dd97e718
44 changed files with 855 additions and 399 deletions
56
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
56
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: File a bug report
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug", "needs triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: Tell us how to reproduce this issue.
|
||||||
|
placeholder: These are the steps!
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: your-env
|
||||||
|
attributes:
|
||||||
|
label: Your Environment
|
||||||
|
value: |-
|
||||||
|
* OS -
|
||||||
|
* `step-ca` Version -
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: What did you expect to happen?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: actual-behavior
|
||||||
|
attributes:
|
||||||
|
label: Actual Behavior
|
||||||
|
description: What happens instead?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Add any other context about the problem here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: contributing
|
||||||
|
attributes:
|
||||||
|
label: Contributing
|
||||||
|
value: |
|
||||||
|
Vote on this issue by adding a 👍 reaction.
|
||||||
|
To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).
|
||||||
|
validations:
|
||||||
|
required: false
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,27 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: bug, needs triage
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Subject of the issue
|
|
||||||
Describe your issue here.
|
|
||||||
|
|
||||||
### Your environment
|
|
||||||
* OS -
|
|
||||||
* Version -
|
|
||||||
|
|
||||||
### Steps to reproduce
|
|
||||||
Tell us how to reproduce this issue. Please provide a working demo, you can use [this template](https://plnkr.co/edit/XorWgI?p=preview) as a base.
|
|
||||||
|
|
||||||
### Expected behaviour
|
|
||||||
Tell us what should happen
|
|
||||||
|
|
||||||
### Actual behaviour
|
|
||||||
Tell us what happens instead
|
|
||||||
|
|
||||||
### Additional context
|
|
||||||
Add any other context about the problem here.
|
|
12
.github/ISSUE_TEMPLATE/documentation-request.md
vendored
12
.github/ISSUE_TEMPLATE/documentation-request.md
vendored
|
@ -1,12 +1,20 @@
|
||||||
---
|
---
|
||||||
name: Documentation Request
|
name: Documentation Request
|
||||||
about: Request documentation for a feature
|
about: Request documentation for a feature
|
||||||
title: ''
|
title: '[Docs]:'
|
||||||
labels: documentation, needs triage
|
labels: docs, needs triage
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Hello!
|
||||||
|
<!-- Please leave this section as-is, it's designed to help others in the community know how to interact with our GitHub issues. -->
|
||||||
|
|
||||||
|
- Vote on this issue by adding a 👍 reaction
|
||||||
|
- If you want to document this feature, comment to let us know (we'll work with you on design, scheduling, etc.)
|
||||||
|
|
||||||
|
## Affected area/feature
|
||||||
|
|
||||||
<!---
|
<!---
|
||||||
Tell us which feature you'd like to see documented.
|
Tell us which feature you'd like to see documented.
|
||||||
- Where would you like that documentation to live (command line usage output, website, github markdown on the repo)?
|
- Where would you like that documentation to live (command line usage output, website, github markdown on the repo)?
|
||||||
|
|
17
.github/ISSUE_TEMPLATE/enhancement.md
vendored
17
.github/ISSUE_TEMPLATE/enhancement.md
vendored
|
@ -1,13 +1,24 @@
|
||||||
---
|
---
|
||||||
name: Enhancement
|
name: Enhancement
|
||||||
about: Suggest an enhancement to step certificates
|
about: Suggest an enhancement to step-ca
|
||||||
title: ''
|
title: ''
|
||||||
labels: enhancement, needs triage
|
labels: enhancement, needs triage
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### What would you like to be added
|
## Hello!
|
||||||
|
<!-- Please leave this section as-is,
|
||||||
|
it's designed to help others in the community know how to interact with our GitHub issues. -->
|
||||||
|
|
||||||
|
- Vote on this issue by adding a 👍 reaction
|
||||||
|
- If you want to implement this feature, comment to let us know (we'll work with you on design, scheduling, etc.)
|
||||||
|
|
||||||
### Why this is needed
|
## Issue details
|
||||||
|
|
||||||
|
<!-- Enhancement requests are most helpful when they describe the problem you're having
|
||||||
|
as well as articulating the potential solution you'd like to see built. -->
|
||||||
|
|
||||||
|
## Why is this needed?
|
||||||
|
|
||||||
|
<!-- Let us know why you think this enhancement would be good for the project or community. -->
|
||||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||||
version: 'v1.45.0'
|
version: 'v1.45.2'
|
||||||
|
|
||||||
# Optional: working directory, useful for monorepos
|
# Optional: working directory, useful for monorepos
|
||||||
# working-directory: somedir
|
# working-directory: somedir
|
||||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||||
version: 'v1.45.0'
|
version: 'v1.45.2'
|
||||||
|
|
||||||
# Optional: working directory, useful for monorepos
|
# Optional: working directory, useful for monorepos
|
||||||
# working-directory: somedir
|
# working-directory: somedir
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -151,7 +151,7 @@ integration: bin/$(BINNAME)
|
||||||
#########################################
|
#########################################
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
$Q gofmt -l -w $(SRC)
|
$Q gofmt -l -s -w $(SRC)
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
$Q golangci-lint run --timeout=30m
|
$Q golangci-lint run --timeout=30m
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (a *Authority) StoreAdmin(ctx context.Context, adm *linkedca.Admin, prov pr
|
||||||
return admin.WrapErrorISE(err, "error creating admin")
|
return admin.WrapErrorISE(err, "error creating admin")
|
||||||
}
|
}
|
||||||
if err := a.admins.Store(adm, prov); err != nil {
|
if err := a.admins.Store(adm, prov); err != nil {
|
||||||
if err := a.reloadAdminResources(ctx); err != nil {
|
if err := a.ReloadAdminResources(ctx); err != nil {
|
||||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed admin store")
|
return admin.WrapErrorISE(err, "error reloading admin resources on failed admin store")
|
||||||
}
|
}
|
||||||
return admin.WrapErrorISE(err, "error storing admin in authority cache")
|
return admin.WrapErrorISE(err, "error storing admin in authority cache")
|
||||||
|
@ -66,7 +66,7 @@ func (a *Authority) UpdateAdmin(ctx context.Context, id string, nu *linkedca.Adm
|
||||||
return nil, admin.WrapErrorISE(err, "error updating cached admin %s", id)
|
return nil, admin.WrapErrorISE(err, "error updating cached admin %s", id)
|
||||||
}
|
}
|
||||||
if err := a.adminDB.UpdateAdmin(ctx, adm); err != nil {
|
if err := a.adminDB.UpdateAdmin(ctx, adm); err != nil {
|
||||||
if err := a.reloadAdminResources(ctx); err != nil {
|
if err := a.ReloadAdminResources(ctx); err != nil {
|
||||||
return nil, admin.WrapErrorISE(err, "error reloading admin resources on failed admin update")
|
return nil, admin.WrapErrorISE(err, "error reloading admin resources on failed admin update")
|
||||||
}
|
}
|
||||||
return nil, admin.WrapErrorISE(err, "error updating admin %s", id)
|
return nil, admin.WrapErrorISE(err, "error updating admin %s", id)
|
||||||
|
@ -88,7 +88,7 @@ func (a *Authority) removeAdmin(ctx context.Context, id string) error {
|
||||||
return admin.WrapErrorISE(err, "error removing admin %s from authority cache", id)
|
return admin.WrapErrorISE(err, "error removing admin %s from authority cache", id)
|
||||||
}
|
}
|
||||||
if err := a.adminDB.DeleteAdmin(ctx, id); err != nil {
|
if err := a.adminDB.DeleteAdmin(ctx, id); err != nil {
|
||||||
if err := a.reloadAdminResources(ctx); err != nil {
|
if err := a.ReloadAdminResources(ctx); err != nil {
|
||||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed admin remove")
|
return admin.WrapErrorISE(err, "error reloading admin resources on failed admin remove")
|
||||||
}
|
}
|
||||||
return admin.WrapErrorISE(err, "error deleting admin %s", id)
|
return admin.WrapErrorISE(err, "error deleting admin %s", id)
|
||||||
|
|
|
@ -84,8 +84,12 @@ type Authority struct {
|
||||||
policyEngine *policy.Engine
|
policyEngine *policy.Engine
|
||||||
|
|
||||||
adminMutex sync.RWMutex
|
adminMutex sync.RWMutex
|
||||||
|
|
||||||
|
// Do Not initialize the authority
|
||||||
|
skipInit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Info contains information about the authority.
|
||||||
type Info struct {
|
type Info struct {
|
||||||
StartTime time.Time
|
StartTime time.Time
|
||||||
RootX509Certs []*x509.Certificate
|
RootX509Certs []*x509.Certificate
|
||||||
|
@ -113,9 +117,11 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize authority from options or configuration.
|
if !a.skipInit {
|
||||||
if err := a.init(); err != nil {
|
// Initialize authority from options or configuration.
|
||||||
return nil, err
|
if err := a.init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, nil
|
return a, nil
|
||||||
|
@ -151,9 +157,11 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
|
||||||
// Initialize config required fields.
|
// Initialize config required fields.
|
||||||
a.config.Init()
|
a.config.Init()
|
||||||
|
|
||||||
// Initialize authority from options or configuration.
|
if !a.skipInit {
|
||||||
if err := a.init(); err != nil {
|
// Initialize authority from options or configuration.
|
||||||
return nil, err
|
if err := a.init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, nil
|
return a, nil
|
||||||
|
@ -182,8 +190,8 @@ func MustFromContext(ctx context.Context) *Authority {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reloadAdminResources reloads admins and provisioners from the DB.
|
// ReloadAdminResources reloads admins and provisioners from the DB.
|
||||||
func (a *Authority) reloadAdminResources(ctx context.Context) error {
|
func (a *Authority) ReloadAdminResources(ctx context.Context) error {
|
||||||
var (
|
var (
|
||||||
provList provisioner.List
|
provList provisioner.List
|
||||||
adminList []*linkedca.Admin
|
adminList []*linkedca.Admin
|
||||||
|
@ -582,7 +590,7 @@ func (a *Authority) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Provisioners and Admins
|
// Load Provisioners and Admins
|
||||||
if err := a.reloadAdminResources(ctx); err != nil {
|
if err := a.ReloadAdminResources(context.Background()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,6 +640,12 @@ func (a *Authority) GetAdminDatabase() admin.DB {
|
||||||
return a.adminDB
|
return a.adminDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfig returns the config.
|
||||||
|
func (a *Authority) GetConfig() *config.Config {
|
||||||
|
return a.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfo returns information about the authority.
|
||||||
func (a *Authority) GetInfo() Info {
|
func (a *Authority) GetInfo() Info {
|
||||||
ai := Info{
|
ai := Info{
|
||||||
StartTime: a.startTime,
|
StartTime: a.startTime,
|
||||||
|
|
|
@ -1034,7 +1034,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if assert.Nil(t, tc.err) {
|
if assert.Nil(t, tc.err) {
|
||||||
assert.Len(t, 8, got) // number of provisioner.SignOptions returned
|
assert.Len(t, 9, got) // number of provisioner.SignOptions returned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -289,18 +289,29 @@ func (c *linkedCaClient) StoreRenewedCertificate(parent *x509.Certificate, fullc
|
||||||
PemCertificateChain: serializeCertificateChain(fullchain[1:]...),
|
PemCertificateChain: serializeCertificateChain(fullchain[1:]...),
|
||||||
PemParentCertificate: serializeCertificateChain(parent),
|
PemParentCertificate: serializeCertificateChain(parent),
|
||||||
})
|
})
|
||||||
return errors.Wrap(err, "error posting certificate")
|
return errors.Wrap(err, "error posting renewed certificate")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linkedCaClient) StoreSSHCertificate(crt *ssh.Certificate) error {
|
func (c *linkedCaClient) StoreSSHCertificate(prov provisioner.Interface, crt *ssh.Certificate) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_, err := c.client.PostSSHCertificate(ctx, &linkedca.SSHCertificateRequest{
|
_, err := c.client.PostSSHCertificate(ctx, &linkedca.SSHCertificateRequest{
|
||||||
Certificate: string(ssh.MarshalAuthorizedKey(crt)),
|
Certificate: string(ssh.MarshalAuthorizedKey(crt)),
|
||||||
|
Provisioner: createProvisionerIdentity(prov),
|
||||||
})
|
})
|
||||||
return errors.Wrap(err, "error posting ssh certificate")
|
return errors.Wrap(err, "error posting ssh certificate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *linkedCaClient) StoreRenewedSSHCertificate(parent, crt *ssh.Certificate) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
_, err := c.client.PostSSHCertificate(ctx, &linkedca.SSHCertificateRequest{
|
||||||
|
Certificate: string(ssh.MarshalAuthorizedKey(crt)),
|
||||||
|
ParentCertificate: string(ssh.MarshalAuthorizedKey(parent)),
|
||||||
|
})
|
||||||
|
return errors.Wrap(err, "error posting renewed ssh certificate")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *linkedCaClient) Revoke(crt *x509.Certificate, rci *db.RevokedCertificateInfo) error {
|
func (c *linkedCaClient) Revoke(crt *x509.Certificate, rci *db.RevokedCertificateInfo) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
@ -266,6 +266,16 @@ func WithAdminDB(d admin.DB) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithProvisioners is an option to set the provisioner collection.
|
||||||
|
//
|
||||||
|
// Deprecated: provisioner collections will likely change
|
||||||
|
func WithProvisioners(ps *provisioner.Collection) Option {
|
||||||
|
return func(a *Authority) error {
|
||||||
|
a.provisioners = ps
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithLinkedCAToken is an option to set the authentication token used to enable
|
// WithLinkedCAToken is an option to set the authentication token used to enable
|
||||||
// linked ca.
|
// linked ca.
|
||||||
func WithLinkedCAToken(token string) Option {
|
func WithLinkedCAToken(token string) Option {
|
||||||
|
@ -284,6 +294,15 @@ func WithX509Enforcers(ces ...provisioner.CertificateEnforcer) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSkipInit is an option that allows the constructor to skip initializtion
|
||||||
|
// of the authority.
|
||||||
|
func WithSkipInit() Option {
|
||||||
|
return func(a *Authority) error {
|
||||||
|
a.skipInit = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
|
func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
|
||||||
var block *pem.Block
|
var block *pem.Block
|
||||||
var certs []*x509.Certificate
|
var certs []*x509.Certificate
|
||||||
|
|
|
@ -747,6 +747,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
||||||
signOptions = append(signOptions, templateOptions)
|
signOptions = append(signOptions, templateOptions)
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
p,
|
||||||
// Validate user SignSSHOptions.
|
// Validate user SignSSHOptions.
|
||||||
sshCertOptionsValidator(defaults),
|
sshCertOptionsValidator(defaults),
|
||||||
// Set the validity bounds if not set.
|
// Set the validity bounds if not set.
|
||||||
|
|
|
@ -813,7 +813,6 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) {
|
||||||
} else if assert.NotNil(t, got) {
|
} else if assert.NotNil(t, got) {
|
||||||
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
||||||
if (err != nil) != tt.wantSignErr {
|
if (err != nil) != tt.wantSignErr {
|
||||||
|
|
||||||
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
||||||
} else {
|
} else {
|
||||||
if tt.wantSignErr {
|
if tt.wantSignErr {
|
||||||
|
|
|
@ -418,6 +418,7 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
|
||||||
signOptions = append(signOptions, templateOptions)
|
signOptions = append(signOptions, templateOptions)
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
p,
|
||||||
// Validate user SignSSHOptions.
|
// Validate user SignSSHOptions.
|
||||||
sshCertOptionsValidator(defaults),
|
sshCertOptionsValidator(defaults),
|
||||||
// Set the validity bounds if not set.
|
// Set the validity bounds if not set.
|
||||||
|
|
|
@ -3,6 +3,7 @@ package provisioner
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -131,7 +132,9 @@ func DefaultAuthorizeRenew(ctx context.Context, p *Controller, cert *x509.Certif
|
||||||
return errs.Unauthorized("certificate is not yet valid" + " " + now.UTC().Format(time.RFC3339Nano) + " vs " + cert.NotBefore.Format(time.RFC3339Nano))
|
return errs.Unauthorized("certificate is not yet valid" + " " + now.UTC().Format(time.RFC3339Nano) + " vs " + cert.NotBefore.Format(time.RFC3339Nano))
|
||||||
}
|
}
|
||||||
if now.After(cert.NotAfter) && !p.Claimer.AllowRenewalAfterExpiry() {
|
if now.After(cert.NotAfter) && !p.Claimer.AllowRenewalAfterExpiry() {
|
||||||
return errs.Unauthorized("certificate has expired")
|
// return a custom 401 Unauthorized error with a clearer message for the client
|
||||||
|
// TODO(hs): these errors likely need to be refactored as a whole; HTTP status codes shouldn't be in this layer.
|
||||||
|
return errs.New(http.StatusUnauthorized, "The request lacked necessary authorization to be completed: certificate expired on %s", cert.NotAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -425,6 +425,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
||||||
signOptions = append(signOptions, templateOptions)
|
signOptions = append(signOptions, templateOptions)
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
p,
|
||||||
// Validate user SignSSHOptions.
|
// Validate user SignSSHOptions.
|
||||||
sshCertOptionsValidator(defaults),
|
sshCertOptionsValidator(defaults),
|
||||||
// Set the validity bounds if not set.
|
// Set the validity bounds if not set.
|
||||||
|
|
|
@ -257,6 +257,7 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
p,
|
||||||
// Set the validity bounds if not set.
|
// Set the validity bounds if not set.
|
||||||
&sshDefaultDuration{p.ctl.Claimer},
|
&sshDefaultDuration{p.ctl.Claimer},
|
||||||
// Validate public key
|
// Validate public key
|
||||||
|
|
|
@ -275,6 +275,7 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
|
||||||
signOptions := []SignOption{templateOptions}
|
signOptions := []SignOption{templateOptions}
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
p,
|
||||||
// Require type, key-id and principals in the SignSSHOptions.
|
// Require type, key-id and principals in the SignSSHOptions.
|
||||||
&sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true},
|
&sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true},
|
||||||
// Set the validity bounds if not set.
|
// Set the validity bounds if not set.
|
||||||
|
|
|
@ -368,9 +368,10 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) {
|
||||||
} else {
|
} else {
|
||||||
if assert.Nil(t, tc.err) {
|
if assert.Nil(t, tc.err) {
|
||||||
if assert.NotNil(t, opts) {
|
if assert.NotNil(t, opts) {
|
||||||
assert.Len(t, 7, opts)
|
assert.Len(t, 8, opts)
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
switch v := o.(type) {
|
switch v := o.(type) {
|
||||||
|
case Interface:
|
||||||
case sshCertificateOptionsFunc:
|
case sshCertificateOptionsFunc:
|
||||||
case *sshCertOptionsRequireValidator:
|
case *sshCertOptionsRequireValidator:
|
||||||
assert.Equals(t, v, &sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true})
|
assert.Equals(t, v, &sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true})
|
||||||
|
|
|
@ -250,6 +250,7 @@ func (p *Nebula) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOpti
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
p,
|
||||||
templateOptions,
|
templateOptions,
|
||||||
// Checks the validity bounds, and set the validity if has not been set.
|
// Checks the validity bounds, and set the validity if has not been set.
|
||||||
&sshLimitDuration{p.ctl.Claimer, crt.Details.NotAfter},
|
&sshLimitDuration{p.ctl.Claimer, crt.Details.NotAfter},
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (p *noop) AuthorizeRevoke(ctx context.Context, token string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *noop) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
func (p *noop) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||||
return []SignOption{}, nil
|
return []SignOption{p}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *noop) AuthorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) {
|
func (p *noop) AuthorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) {
|
||||||
|
|
|
@ -434,6 +434,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
o,
|
||||||
// Set the validity bounds if not set.
|
// Set the validity bounds if not set.
|
||||||
&sshDefaultDuration{o.ctl.Claimer},
|
&sshDefaultDuration{o.ctl.Claimer},
|
||||||
// Validate public key
|
// Validate public key
|
||||||
|
|
|
@ -53,6 +53,7 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
|
||||||
|
|
||||||
for _, op := range signOpts {
|
for _, op := range signOpts {
|
||||||
switch o := op.(type) {
|
switch o := op.(type) {
|
||||||
|
case Interface:
|
||||||
// add options to NewCertificate
|
// add options to NewCertificate
|
||||||
case SSHCertificateOptions:
|
case SSHCertificateOptions:
|
||||||
certOptions = append(certOptions, o.Options(opts)...)
|
certOptions = append(certOptions, o.Options(opts)...)
|
||||||
|
|
|
@ -312,6 +312,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(signOptions,
|
return append(signOptions,
|
||||||
|
p,
|
||||||
// Checks the validity bounds, and set the validity if has not been set.
|
// Checks the validity bounds, and set the validity if has not been set.
|
||||||
&sshLimitDuration{p.ctl.Claimer, claims.chains[0][0].NotAfter},
|
&sshLimitDuration{p.ctl.Claimer, claims.chains[0][0].NotAfter},
|
||||||
// Validate public key.
|
// Validate public key.
|
||||||
|
|
|
@ -769,6 +769,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
|
||||||
nw := now()
|
nw := now()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
switch v := o.(type) {
|
switch v := o.(type) {
|
||||||
|
case Interface:
|
||||||
case sshCertOptionsValidator:
|
case sshCertOptionsValidator:
|
||||||
tc.claims.Step.SSH.ValidAfter.t = time.Time{}
|
tc.claims.Step.SSH.ValidAfter.t = time.Time{}
|
||||||
tc.claims.Step.SSH.ValidBefore.t = time.Time{}
|
tc.claims.Step.SSH.ValidBefore.t = time.Time{}
|
||||||
|
@ -799,9 +800,9 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
|
||||||
tot++
|
tot++
|
||||||
}
|
}
|
||||||
if len(tc.claims.Step.SSH.CertType) > 0 {
|
if len(tc.claims.Step.SSH.CertType) > 0 {
|
||||||
assert.Equals(t, tot, 10)
|
assert.Equals(t, tot, 11)
|
||||||
} else {
|
} else {
|
||||||
assert.Equals(t, tot, 8)
|
assert.Equals(t, tot, 9)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreProvisioner stores an provisioner.Interface to the authority.
|
// StoreProvisioner stores a provisioner to the authority.
|
||||||
func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
||||||
a.adminMutex.Lock()
|
a.adminMutex.Lock()
|
||||||
defer a.adminMutex.Unlock()
|
defer a.adminMutex.Unlock()
|
||||||
|
@ -198,7 +198,7 @@ func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisi
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.provisioners.Store(certProv); err != nil {
|
if err := a.provisioners.Store(certProv); err != nil {
|
||||||
if err := a.reloadAdminResources(ctx); err != nil {
|
if err := a.ReloadAdminResources(ctx); err != nil {
|
||||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner store")
|
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner store")
|
||||||
}
|
}
|
||||||
return admin.WrapErrorISE(err, "error storing provisioner in authority cache")
|
return admin.WrapErrorISE(err, "error storing provisioner in authority cache")
|
||||||
|
@ -234,7 +234,7 @@ func (a *Authority) UpdateProvisioner(ctx context.Context, nu *linkedca.Provisio
|
||||||
return admin.WrapErrorISE(err, "error updating provisioner '%s' in authority cache", nu.Name)
|
return admin.WrapErrorISE(err, "error updating provisioner '%s' in authority cache", nu.Name)
|
||||||
}
|
}
|
||||||
if err := a.adminDB.UpdateProvisioner(ctx, nu); err != nil {
|
if err := a.adminDB.UpdateProvisioner(ctx, nu); err != nil {
|
||||||
if err := a.reloadAdminResources(ctx); err != nil {
|
if err := a.ReloadAdminResources(ctx); err != nil {
|
||||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner update")
|
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner update")
|
||||||
}
|
}
|
||||||
return admin.WrapErrorISE(err, "error updating provisioner '%s'", nu.Name)
|
return admin.WrapErrorISE(err, "error updating provisioner '%s'", nu.Name)
|
||||||
|
@ -254,31 +254,33 @@ func (a *Authority) RemoveProvisioner(ctx context.Context, id string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
provName, provID := p.GetName(), p.GetID()
|
provName, provID := p.GetName(), p.GetID()
|
||||||
// Validate
|
if a.IsAdminAPIEnabled() {
|
||||||
// - Check that there will be SUPER_ADMINs that remain after we
|
// Validate
|
||||||
// remove this provisioner.
|
// - Check that there will be SUPER_ADMINs that remain after we
|
||||||
if a.admins.SuperCount() == a.admins.SuperCountByProvisioner(provName) {
|
// remove this provisioner.
|
||||||
return admin.NewError(admin.ErrorBadRequestType,
|
if a.IsAdminAPIEnabled() && a.admins.SuperCount() == a.admins.SuperCountByProvisioner(provName) {
|
||||||
"cannot remove provisioner %s because no super admins will remain", provName)
|
return admin.NewError(admin.ErrorBadRequestType,
|
||||||
}
|
"cannot remove provisioner %s because no super admins will remain", provName)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete all admins associated with the provisioner.
|
// Delete all admins associated with the provisioner.
|
||||||
admins, ok := a.admins.LoadByProvisioner(provName)
|
admins, ok := a.admins.LoadByProvisioner(provName)
|
||||||
if ok {
|
if ok {
|
||||||
for _, adm := range admins {
|
for _, adm := range admins {
|
||||||
if err := a.removeAdmin(ctx, adm.Id); err != nil {
|
if err := a.removeAdmin(ctx, adm.Id); err != nil {
|
||||||
return admin.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, provName)
|
return admin.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, provName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove provisioner from authority caches.
|
// Remove provisioner from authority caches.
|
||||||
if err := a.provisioners.Remove(provID); err != nil {
|
if err := a.provisioners.Remove(provID); err != nil {
|
||||||
return admin.WrapErrorISE(err, "error removing admin from authority cache")
|
return admin.WrapErrorISE(err, "error removing provisioner from authority cache")
|
||||||
}
|
}
|
||||||
// Remove provisioner from database.
|
// Remove provisioner from database.
|
||||||
if err := a.adminDB.DeleteProvisioner(ctx, provID); err != nil {
|
if err := a.adminDB.DeleteProvisioner(ctx, provID); err != nil {
|
||||||
if err := a.reloadAdminResources(ctx); err != nil {
|
if err := a.ReloadAdminResources(ctx); err != nil {
|
||||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner remove")
|
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner remove")
|
||||||
}
|
}
|
||||||
return admin.WrapErrorISE(err, "error deleting provisioner %s", provName)
|
return admin.WrapErrorISE(err, "error deleting provisioner %s", provName)
|
||||||
|
|
|
@ -161,8 +161,13 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
|
||||||
// Set backdate with the configured value
|
// Set backdate with the configured value
|
||||||
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
|
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
|
||||||
|
|
||||||
|
var prov provisioner.Interface
|
||||||
for _, op := range signOpts {
|
for _, op := range signOpts {
|
||||||
switch o := op.(type) {
|
switch o := op.(type) {
|
||||||
|
// Capture current provisioner
|
||||||
|
case provisioner.Interface:
|
||||||
|
prov = o
|
||||||
|
|
||||||
// add options to NewCertificate
|
// add options to NewCertificate
|
||||||
case provisioner.SSHCertificateOptions:
|
case provisioner.SSHCertificateOptions:
|
||||||
certOptions = append(certOptions, o.Options(opts)...)
|
certOptions = append(certOptions, o.Options(opts)...)
|
||||||
|
@ -276,7 +281,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
|
if err = a.storeSSHCertificate(prov, cert); err != nil && err != db.ErrNotImplemented {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +345,7 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
|
if err = a.storeRenewedSSHCertificate(oldCert, cert); err != nil && err != db.ErrNotImplemented {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,21 +424,59 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
|
if err = a.storeRenewedSSHCertificate(oldCert, cert); err != nil && err != db.ErrNotImplemented {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authority) storeSSHCertificate(cert *ssh.Certificate) error {
|
func (a *Authority) storeSSHCertificate(prov provisioner.Interface, cert *ssh.Certificate) error {
|
||||||
type sshCertificateStorer interface {
|
type sshCertificateStorer interface {
|
||||||
StoreSSHCertificate(crt *ssh.Certificate) error
|
StoreSSHCertificate(provisioner.Interface, *ssh.Certificate) error
|
||||||
}
|
}
|
||||||
if s, ok := a.adminDB.(sshCertificateStorer); ok {
|
|
||||||
|
// Store certificate in admindb or linkedca
|
||||||
|
switch s := a.adminDB.(type) {
|
||||||
|
case sshCertificateStorer:
|
||||||
|
return s.StoreSSHCertificate(prov, cert)
|
||||||
|
case db.CertificateStorer:
|
||||||
return s.StoreSSHCertificate(cert)
|
return s.StoreSSHCertificate(cert)
|
||||||
}
|
}
|
||||||
return a.db.StoreSSHCertificate(cert)
|
|
||||||
|
// Store certificate in localdb
|
||||||
|
switch s := a.db.(type) {
|
||||||
|
case sshCertificateStorer:
|
||||||
|
return s.StoreSSHCertificate(prov, cert)
|
||||||
|
case db.CertificateStorer:
|
||||||
|
return s.StoreSSHCertificate(cert)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authority) storeRenewedSSHCertificate(parent, cert *ssh.Certificate) error {
|
||||||
|
type sshRenewerCertificateStorer interface {
|
||||||
|
StoreRenewedSSHCertificate(parent, cert *ssh.Certificate) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store certificate in admindb or linkedca
|
||||||
|
switch s := a.adminDB.(type) {
|
||||||
|
case sshRenewerCertificateStorer:
|
||||||
|
return s.StoreRenewedSSHCertificate(parent, cert)
|
||||||
|
case db.CertificateStorer:
|
||||||
|
return s.StoreSSHCertificate(cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store certificate in localdb
|
||||||
|
switch s := a.db.(type) {
|
||||||
|
case sshRenewerCertificateStorer:
|
||||||
|
return s.StoreRenewedSSHCertificate(parent, cert)
|
||||||
|
case db.CertificateStorer:
|
||||||
|
return s.StoreSSHCertificate(cert)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValidForAddUser checks if a user provisioner certificate can be issued to
|
// IsValidForAddUser checks if a user provisioner certificate can be issued to
|
||||||
|
@ -511,7 +554,7 @@ func (a *Authority) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, subje
|
||||||
}
|
}
|
||||||
cert.Signature = sig
|
cert.Signature = sig
|
||||||
|
|
||||||
if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
|
if err = a.storeRenewedSSHCertificate(subject, cert); err != nil && err != db.ErrNotImplemented {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSHAddUser: error storing certificate in db")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSHAddUser: error storing certificate in db")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -365,28 +365,31 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
|
||||||
// `StoreCertificate(...*x509.Certificate) error` instead of just
|
// `StoreCertificate(...*x509.Certificate) error` instead of just
|
||||||
// `StoreCertificate(*x509.Certificate) error`.
|
// `StoreCertificate(*x509.Certificate) error`.
|
||||||
func (a *Authority) storeCertificate(prov provisioner.Interface, fullchain []*x509.Certificate) error {
|
func (a *Authority) storeCertificate(prov provisioner.Interface, fullchain []*x509.Certificate) error {
|
||||||
type linkedChainStorer interface {
|
type certificateChainStorer interface {
|
||||||
StoreCertificateChain(provisioner.Interface, ...*x509.Certificate) error
|
StoreCertificateChain(provisioner.Interface, ...*x509.Certificate) error
|
||||||
}
|
}
|
||||||
type certificateChainStorer interface {
|
type certificateChainSimpleStorer interface {
|
||||||
StoreCertificateChain(...*x509.Certificate) error
|
StoreCertificateChain(...*x509.Certificate) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store certificate in linkedca
|
// Store certificate in linkedca
|
||||||
switch s := a.adminDB.(type) {
|
switch s := a.adminDB.(type) {
|
||||||
case linkedChainStorer:
|
|
||||||
return s.StoreCertificateChain(prov, fullchain...)
|
|
||||||
case certificateChainStorer:
|
case certificateChainStorer:
|
||||||
|
return s.StoreCertificateChain(prov, fullchain...)
|
||||||
|
case certificateChainSimpleStorer:
|
||||||
return s.StoreCertificateChain(fullchain...)
|
return s.StoreCertificateChain(fullchain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(prov, fullchain...)
|
||||||
|
case certificateChainSimpleStorer:
|
||||||
return s.StoreCertificateChain(fullchain...)
|
return s.StoreCertificateChain(fullchain...)
|
||||||
|
case db.CertificateStorer:
|
||||||
|
return s.StoreCertificate(fullchain[0])
|
||||||
default:
|
default:
|
||||||
return a.db.StoreCertificate(fullchain[0])
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,15 +401,21 @@ func (a *Authority) storeRenewedCertificate(oldCert *x509.Certificate, fullchain
|
||||||
type renewedCertificateChainStorer interface {
|
type renewedCertificateChainStorer interface {
|
||||||
StoreRenewedCertificate(*x509.Certificate, ...*x509.Certificate) error
|
StoreRenewedCertificate(*x509.Certificate, ...*x509.Certificate) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store certificate in linkedca
|
// Store certificate in linkedca
|
||||||
if s, ok := a.adminDB.(renewedCertificateChainStorer); ok {
|
if s, ok := a.adminDB.(renewedCertificateChainStorer); ok {
|
||||||
return s.StoreRenewedCertificate(oldCert, fullchain...)
|
return s.StoreRenewedCertificate(oldCert, fullchain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store certificate in local db
|
// Store certificate in local db
|
||||||
if s, ok := a.db.(renewedCertificateChainStorer); ok {
|
switch s := a.db.(type) {
|
||||||
|
case renewedCertificateChainStorer:
|
||||||
return s.StoreRenewedCertificate(oldCert, fullchain...)
|
return s.StoreRenewedCertificate(oldCert, fullchain...)
|
||||||
|
case db.CertificateStorer:
|
||||||
|
return s.StoreCertificate(fullchain[0])
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return a.db.StoreCertificate(fullchain[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeOptions are the options for the Revoke API.
|
// RevokeOptions are the options for the Revoke API.
|
||||||
|
|
|
@ -366,19 +366,19 @@ retry:
|
||||||
// GetProvisioner performs the GET /admin/provisioners/{name} request to the CA.
|
// GetProvisioner performs the GET /admin/provisioners/{name} request to the CA.
|
||||||
func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provisioner, error) {
|
func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provisioner, error) {
|
||||||
var retried bool
|
var retried bool
|
||||||
o := new(provisionerOptions)
|
o := new(ProvisionerOptions)
|
||||||
if err := o.apply(opts); err != nil {
|
if err := o.Apply(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var u *url.URL
|
var u *url.URL
|
||||||
switch {
|
switch {
|
||||||
case len(o.id) > 0:
|
case o.ID != "":
|
||||||
u = c.endpoint.ResolveReference(&url.URL{
|
u = c.endpoint.ResolveReference(&url.URL{
|
||||||
Path: "/admin/provisioners/id",
|
Path: "/admin/provisioners/id",
|
||||||
RawQuery: o.rawQuery(),
|
RawQuery: o.rawQuery(),
|
||||||
})
|
})
|
||||||
case len(o.name) > 0:
|
case o.Name != "":
|
||||||
u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)})
|
u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.Name)})
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
@ -413,8 +413,8 @@ retry:
|
||||||
// GetProvisionersPaginate performs the GET /admin/provisioners request to the CA.
|
// GetProvisionersPaginate performs the GET /admin/provisioners request to the CA.
|
||||||
func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*adminAPI.GetProvisionersResponse, error) {
|
func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*adminAPI.GetProvisionersResponse, error) {
|
||||||
var retried bool
|
var retried bool
|
||||||
o := new(provisionerOptions)
|
o := new(ProvisionerOptions)
|
||||||
if err := o.apply(opts); err != nil {
|
if err := o.Apply(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{
|
u := c.endpoint.ResolveReference(&url.URL{
|
||||||
|
@ -475,19 +475,19 @@ func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error {
|
||||||
retried bool
|
retried bool
|
||||||
)
|
)
|
||||||
|
|
||||||
o := new(provisionerOptions)
|
o := new(ProvisionerOptions)
|
||||||
if err := o.apply(opts); err != nil {
|
if err := o.Apply(opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(o.id) > 0:
|
case o.ID != "":
|
||||||
u = c.endpoint.ResolveReference(&url.URL{
|
u = c.endpoint.ResolveReference(&url.URL{
|
||||||
Path: path.Join(adminURLPrefix, "provisioners/id"),
|
Path: path.Join(adminURLPrefix, "provisioners/id"),
|
||||||
RawQuery: o.rawQuery(),
|
RawQuery: o.rawQuery(),
|
||||||
})
|
})
|
||||||
case len(o.name) > 0:
|
case o.Name != "":
|
||||||
u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)})
|
u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.Name)})
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -374,6 +375,9 @@ func TestBootstrapClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBootstrapClientServerRotation(t *testing.T) {
|
func TestBootstrapClientServerRotation(t *testing.T) {
|
||||||
|
if os.Getenv("CI") == "true" {
|
||||||
|
t.Skipf("skip until we fix https://github.com/smallstep/certificates/issues/873")
|
||||||
|
}
|
||||||
reset := setMinCertDuration(1 * time.Second)
|
reset := setMinCertDuration(1 * time.Second)
|
||||||
defer reset()
|
defer reset()
|
||||||
|
|
||||||
|
|
54
ca/client.go
54
ca/client.go
|
@ -425,16 +425,18 @@ func parseEndpoint(endpoint string) (*url.URL, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvisionerOption is the type of options passed to the Provisioner method.
|
// ProvisionerOption is the type of options passed to the Provisioner method.
|
||||||
type ProvisionerOption func(o *provisionerOptions) error
|
type ProvisionerOption func(o *ProvisionerOptions) error
|
||||||
|
|
||||||
type provisionerOptions struct {
|
// ProvisionerOptions stores options for the provisioner CRUD API.
|
||||||
cursor string
|
type ProvisionerOptions struct {
|
||||||
limit int
|
Cursor string
|
||||||
id string
|
Limit int
|
||||||
name string
|
ID string
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) {
|
// Apply caches provisioner options on a struct for later use.
|
||||||
|
func (o *ProvisionerOptions) Apply(opts []ProvisionerOption) (err error) {
|
||||||
for _, fn := range opts {
|
for _, fn := range opts {
|
||||||
if err = fn(o); err != nil {
|
if err = fn(o); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -443,51 +445,51 @@ func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *provisionerOptions) rawQuery() string {
|
func (o *ProvisionerOptions) rawQuery() string {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
if len(o.cursor) > 0 {
|
if o.Cursor != "" {
|
||||||
v.Set("cursor", o.cursor)
|
v.Set("cursor", o.Cursor)
|
||||||
}
|
}
|
||||||
if o.limit > 0 {
|
if o.Limit > 0 {
|
||||||
v.Set("limit", strconv.Itoa(o.limit))
|
v.Set("limit", strconv.Itoa(o.Limit))
|
||||||
}
|
}
|
||||||
if len(o.id) > 0 {
|
if o.ID != "" {
|
||||||
v.Set("id", o.id)
|
v.Set("id", o.ID)
|
||||||
}
|
}
|
||||||
if len(o.name) > 0 {
|
if o.Name != "" {
|
||||||
v.Set("name", o.name)
|
v.Set("name", o.Name)
|
||||||
}
|
}
|
||||||
return v.Encode()
|
return v.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithProvisionerCursor will request the provisioners starting with the given cursor.
|
// WithProvisionerCursor will request the provisioners starting with the given cursor.
|
||||||
func WithProvisionerCursor(cursor string) ProvisionerOption {
|
func WithProvisionerCursor(cursor string) ProvisionerOption {
|
||||||
return func(o *provisionerOptions) error {
|
return func(o *ProvisionerOptions) error {
|
||||||
o.cursor = cursor
|
o.Cursor = cursor
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithProvisionerLimit will request the given number of provisioners.
|
// WithProvisionerLimit will request the given number of provisioners.
|
||||||
func WithProvisionerLimit(limit int) ProvisionerOption {
|
func WithProvisionerLimit(limit int) ProvisionerOption {
|
||||||
return func(o *provisionerOptions) error {
|
return func(o *ProvisionerOptions) error {
|
||||||
o.limit = limit
|
o.Limit = limit
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithProvisionerID will request the given provisioner.
|
// WithProvisionerID will request the given provisioner.
|
||||||
func WithProvisionerID(id string) ProvisionerOption {
|
func WithProvisionerID(id string) ProvisionerOption {
|
||||||
return func(o *provisionerOptions) error {
|
return func(o *ProvisionerOptions) error {
|
||||||
o.id = id
|
o.ID = id
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithProvisionerName will request the given provisioner.
|
// WithProvisionerName will request the given provisioner.
|
||||||
func WithProvisionerName(name string) ProvisionerOption {
|
func WithProvisionerName(name string) ProvisionerOption {
|
||||||
return func(o *provisionerOptions) error {
|
return func(o *ProvisionerOptions) error {
|
||||||
o.name = name
|
o.Name = name
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -810,8 +812,8 @@ retry:
|
||||||
// paginate the provisioners.
|
// paginate the provisioners.
|
||||||
func (c *Client) Provisioners(opts ...ProvisionerOption) (*api.ProvisionersResponse, error) {
|
func (c *Client) Provisioners(opts ...ProvisionerOption) (*api.ProvisionersResponse, error) {
|
||||||
var retried bool
|
var retried bool
|
||||||
o := new(provisionerOptions)
|
o := new(ProvisionerOptions)
|
||||||
if err := o.apply(opts); err != nil {
|
if err := o.Apply(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{
|
u := c.endpoint.ResolveReference(&url.URL{
|
||||||
|
|
67
cas/vaultcas/auth/approle/approle.go
Normal file
67
cas/vaultcas/auth/approle/approle.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package approle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api/auth/approle"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthOptions defines the configuration options added using the
|
||||||
|
// VaultOptions.AuthOptions field when AuthType is approle
|
||||||
|
type AuthOptions struct {
|
||||||
|
RoleID string `json:"roleID,omitempty"`
|
||||||
|
SecretID string `json:"secretID,omitempty"`
|
||||||
|
SecretIDFile string `json:"secretIDFile,omitempty"`
|
||||||
|
SecretIDEnv string `json:"secretIDEnv,omitempty"`
|
||||||
|
IsWrappingToken bool `json:"isWrappingToken,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApproleAuthMethod(mountPath string, options json.RawMessage) (*approle.AppRoleAuth, error) {
|
||||||
|
var opts *AuthOptions
|
||||||
|
|
||||||
|
err := json.Unmarshal(options, &opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding AppRole auth options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var approleAuth *approle.AppRoleAuth
|
||||||
|
|
||||||
|
var loginOptions []approle.LoginOption
|
||||||
|
if mountPath != "" {
|
||||||
|
loginOptions = append(loginOptions, approle.WithMountPath(mountPath))
|
||||||
|
}
|
||||||
|
if opts.IsWrappingToken {
|
||||||
|
loginOptions = append(loginOptions, approle.WithWrappingToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.RoleID == "" {
|
||||||
|
return nil, errors.New("you must set roleID")
|
||||||
|
}
|
||||||
|
|
||||||
|
var sid approle.SecretID
|
||||||
|
switch {
|
||||||
|
case opts.SecretID != "" && opts.SecretIDFile == "" && opts.SecretIDEnv == "":
|
||||||
|
sid = approle.SecretID{
|
||||||
|
FromString: opts.SecretID,
|
||||||
|
}
|
||||||
|
case opts.SecretIDFile != "" && opts.SecretID == "" && opts.SecretIDEnv == "":
|
||||||
|
sid = approle.SecretID{
|
||||||
|
FromFile: opts.SecretIDFile,
|
||||||
|
}
|
||||||
|
case opts.SecretIDEnv != "" && opts.SecretIDFile == "" && opts.SecretID == "":
|
||||||
|
sid = approle.SecretID{
|
||||||
|
FromEnv: opts.SecretIDEnv,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("you must set one of secretID, secretIDFile or secretIDEnv")
|
||||||
|
}
|
||||||
|
|
||||||
|
approleAuth, err = approle.NewAppRoleAuth(opts.RoleID, &sid, loginOptions...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize Kubernetes auth method: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return approleAuth, nil
|
||||||
|
}
|
195
cas/vaultcas/auth/approle/approle_test.go
Normal file
195
cas/vaultcas/auth/approle/approle_test.go
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
package approle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
vault "github.com/hashicorp/vault/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testCAHelper(t *testing.T) (*url.URL, *vault.Client) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.RequestURI == "/v1/auth/approle/login":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `{
|
||||||
|
"auth": {
|
||||||
|
"client_token": "hvs.0000"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
case r.RequestURI == "/v1/auth/custom-approle/login":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `{
|
||||||
|
"auth": {
|
||||||
|
"client_token": "hvs.9999"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, `{"error":"not found"}`)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
t.Cleanup(func() {
|
||||||
|
srv.Close()
|
||||||
|
})
|
||||||
|
u, err := url.Parse(srv.URL)
|
||||||
|
if err != nil {
|
||||||
|
srv.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := vault.DefaultConfig()
|
||||||
|
config.Address = srv.URL
|
||||||
|
|
||||||
|
client, err := vault.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
srv.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApprole_LoginMountPaths(t *testing.T) {
|
||||||
|
caURL, _ := testCAHelper(t)
|
||||||
|
|
||||||
|
config := vault.DefaultConfig()
|
||||||
|
config.Address = caURL.String()
|
||||||
|
client, _ := vault.NewClient(config)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mountPath string
|
||||||
|
token string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok default mount path",
|
||||||
|
mountPath: "",
|
||||||
|
token: "hvs.0000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok explicit mount path",
|
||||||
|
mountPath: "approle",
|
||||||
|
token: "hvs.0000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok custom mount path",
|
||||||
|
mountPath: "custom-approle",
|
||||||
|
token: "hvs.9999",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
method, err := NewApproleAuthMethod(tt.mountPath, json.RawMessage(`{"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewApproleAuthMethod() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := client.Auth().Login(context.Background(), method)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Login() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, _ := secret.TokenID()
|
||||||
|
if token != tt.token {
|
||||||
|
t.Errorf("Token error got %v, expected %v", token, tt.token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApprole_NewApproleAuthMethod(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mountPath string
|
||||||
|
raw string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ok secret-id string",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000"}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok secret-id string and wrapped",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "isWrappedToken": true}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok secret-id string and wrapped with custom mountPath",
|
||||||
|
"approle2",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "isWrappedToken": true}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok secret-id file",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id"}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok secret-id env",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail mandatory role-id",
|
||||||
|
"",
|
||||||
|
`{}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail mandatory secret-id any",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000"}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail multiple secret-id types id and env",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail multiple secret-id types id and file",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id"}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail multiple secret-id types env and file",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail multiple secret-id types all",
|
||||||
|
"",
|
||||||
|
`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := NewApproleAuthMethod(tt.mountPath, json.RawMessage(tt.raw))
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Approle.NewApproleAuthMethod() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
49
cas/vaultcas/auth/kubernetes/kubernetes.go
Normal file
49
cas/vaultcas/auth/kubernetes/kubernetes.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api/auth/kubernetes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthOptions defines the configuration options added using the
|
||||||
|
// VaultOptions.AuthOptions field when AuthType is kubernetes
|
||||||
|
type AuthOptions struct {
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
TokenPath string `json:"tokenPath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKubernetesAuthMethod(mountPath string, options json.RawMessage) (*kubernetes.KubernetesAuth, error) {
|
||||||
|
var opts *AuthOptions
|
||||||
|
|
||||||
|
err := json.Unmarshal(options, &opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding Kubernetes auth options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kubernetesAuth *kubernetes.KubernetesAuth
|
||||||
|
|
||||||
|
var loginOptions []kubernetes.LoginOption
|
||||||
|
if mountPath != "" {
|
||||||
|
loginOptions = append(loginOptions, kubernetes.WithMountPath(mountPath))
|
||||||
|
}
|
||||||
|
if opts.TokenPath != "" {
|
||||||
|
loginOptions = append(loginOptions, kubernetes.WithServiceAccountTokenPath(opts.TokenPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Role == "" {
|
||||||
|
return nil, errors.New("you must set role")
|
||||||
|
}
|
||||||
|
|
||||||
|
kubernetesAuth, err = kubernetes.NewKubernetesAuth(
|
||||||
|
opts.Role,
|
||||||
|
loginOptions...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize Kubernetes auth method: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return kubernetesAuth, nil
|
||||||
|
}
|
149
cas/vaultcas/auth/kubernetes/kubernetes_test.go
Normal file
149
cas/vaultcas/auth/kubernetes/kubernetes_test.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
vault "github.com/hashicorp/vault/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testCAHelper(t *testing.T) (*url.URL, *vault.Client) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.RequestURI == "/v1/auth/kubernetes/login":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `{
|
||||||
|
"auth": {
|
||||||
|
"client_token": "hvs.0000"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
case r.RequestURI == "/v1/auth/custom-kubernetes/login":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `{
|
||||||
|
"auth": {
|
||||||
|
"client_token": "hvs.9999"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, `{"error":"not found"}`)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
t.Cleanup(func() {
|
||||||
|
srv.Close()
|
||||||
|
})
|
||||||
|
u, err := url.Parse(srv.URL)
|
||||||
|
if err != nil {
|
||||||
|
srv.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := vault.DefaultConfig()
|
||||||
|
config.Address = srv.URL
|
||||||
|
|
||||||
|
client, err := vault.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
srv.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApprole_LoginMountPaths(t *testing.T) {
|
||||||
|
caURL, _ := testCAHelper(t)
|
||||||
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
|
tokenPath := filepath.Join(path.Dir(filename), "token")
|
||||||
|
|
||||||
|
config := vault.DefaultConfig()
|
||||||
|
config.Address = caURL.String()
|
||||||
|
client, _ := vault.NewClient(config)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mountPath string
|
||||||
|
token string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok default mount path",
|
||||||
|
mountPath: "",
|
||||||
|
token: "hvs.0000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok explicit mount path",
|
||||||
|
mountPath: "kubernetes",
|
||||||
|
token: "hvs.0000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok custom mount path",
|
||||||
|
mountPath: "custom-kubernetes",
|
||||||
|
token: "hvs.9999",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
method, err := NewKubernetesAuthMethod(tt.mountPath, json.RawMessage(`{"role": "SomeRoleName", "tokenPath": "`+tokenPath+`"}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewApproleAuthMethod() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := client.Auth().Login(context.Background(), method)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Login() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, _ := secret.TokenID()
|
||||||
|
if token != tt.token {
|
||||||
|
t.Errorf("Token error got %v, expected %v", token, tt.token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApprole_NewApproleAuthMethod(t *testing.T) {
|
||||||
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
|
tokenPath := filepath.Join(path.Dir(filename), "token")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mountPath string
|
||||||
|
raw string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ok secret-id string",
|
||||||
|
"",
|
||||||
|
`{"role": "SomeRoleName", "tokenPath": "` + tokenPath + `"}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail mandatory role",
|
||||||
|
"",
|
||||||
|
`{}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := NewKubernetesAuthMethod(tt.mountPath, json.RawMessage(tt.raw))
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Kubernetes.NewKubernetesAuthMethod() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1
cas/vaultcas/auth/kubernetes/token
Normal file
1
cas/vaultcas/auth/kubernetes/token
Normal file
|
@ -0,0 +1 @@
|
||||||
|
token
|
|
@ -15,9 +15,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/smallstep/certificates/cas/apiv1"
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
|
"github.com/smallstep/certificates/cas/vaultcas/auth/approle"
|
||||||
|
"github.com/smallstep/certificates/cas/vaultcas/auth/kubernetes"
|
||||||
|
|
||||||
vault "github.com/hashicorp/vault/api"
|
vault "github.com/hashicorp/vault/api"
|
||||||
auth "github.com/hashicorp/vault/api/auth/approle"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -29,15 +30,14 @@ func init() {
|
||||||
// VaultOptions defines the configuration options added using the
|
// VaultOptions defines the configuration options added using the
|
||||||
// apiv1.Options.Config field.
|
// apiv1.Options.Config field.
|
||||||
type VaultOptions struct {
|
type VaultOptions struct {
|
||||||
PKI string `json:"pki,omitempty"`
|
PKIMountPath string `json:"pkiMountPath,omitempty"`
|
||||||
PKIRoleDefault string `json:"pkiRoleDefault,omitempty"`
|
PKIRoleDefault string `json:"pkiRoleDefault,omitempty"`
|
||||||
PKIRoleRSA string `json:"pkiRoleRSA,omitempty"`
|
PKIRoleRSA string `json:"pkiRoleRSA,omitempty"`
|
||||||
PKIRoleEC string `json:"pkiRoleEC,omitempty"`
|
PKIRoleEC string `json:"pkiRoleEC,omitempty"`
|
||||||
PKIRoleEd25519 string `json:"pkiRoleEd25519,omitempty"`
|
PKIRoleEd25519 string `json:"pkiRoleEd25519,omitempty"`
|
||||||
RoleID string `json:"roleID,omitempty"`
|
AuthType string `json:"authType,omitempty"`
|
||||||
SecretID auth.SecretID `json:"secretID,omitempty"`
|
AuthMountPath string `json:"authMountPath,omitempty"`
|
||||||
AppRole string `json:"appRole,omitempty"`
|
AuthOptions json.RawMessage `json:"authOptions,omitempty"`
|
||||||
IsWrappingToken bool `json:"isWrappingToken,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VaultCAS implements a Certificate Authority Service using Hashicorp Vault.
|
// VaultCAS implements a Certificate Authority Service using Hashicorp Vault.
|
||||||
|
@ -77,28 +77,22 @@ func New(ctx context.Context, opts apiv1.Options) (*VaultCAS, error) {
|
||||||
return nil, fmt.Errorf("unable to initialize vault client: %w", err)
|
return nil, fmt.Errorf("unable to initialize vault client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var appRoleAuth *auth.AppRoleAuth
|
var method vault.AuthMethod
|
||||||
if vc.IsWrappingToken {
|
switch vc.AuthType {
|
||||||
appRoleAuth, err = auth.NewAppRoleAuth(
|
case "kubernetes":
|
||||||
vc.RoleID,
|
method, err = kubernetes.NewKubernetesAuthMethod(vc.AuthMountPath, vc.AuthOptions)
|
||||||
&vc.SecretID,
|
case "approle":
|
||||||
auth.WithWrappingToken(),
|
method, err = approle.NewApproleAuthMethod(vc.AuthMountPath, vc.AuthOptions)
|
||||||
auth.WithMountPath(vc.AppRole),
|
default:
|
||||||
)
|
return nil, fmt.Errorf("unknown auth type: %s, only 'kubernetes' and 'approle' currently supported", vc.AuthType)
|
||||||
} else {
|
|
||||||
appRoleAuth, err = auth.NewAppRoleAuth(
|
|
||||||
vc.RoleID,
|
|
||||||
&vc.SecretID,
|
|
||||||
auth.WithMountPath(vc.AppRole),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to initialize AppRole auth method: %w", err)
|
return nil, fmt.Errorf("unable to configure %s auth method: %w", vc.AuthType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authInfo, err := client.Auth().Login(ctx, appRoleAuth)
|
authInfo, err := client.Auth().Login(ctx, method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to login to AppRole auth method: %w", err)
|
return nil, fmt.Errorf("unable to login to %s auth method: %w", vc.AuthType, err)
|
||||||
}
|
}
|
||||||
if authInfo == nil {
|
if authInfo == nil {
|
||||||
return nil, errors.New("no auth info was returned after login")
|
return nil, errors.New("no auth info was returned after login")
|
||||||
|
@ -134,7 +128,7 @@ func (v *VaultCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv
|
||||||
// GetCertificateAuthority returns the root certificate of the certificate
|
// GetCertificateAuthority returns the root certificate of the certificate
|
||||||
// authority using the configured fingerprint.
|
// authority using the configured fingerprint.
|
||||||
func (v *VaultCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequest) (*apiv1.GetCertificateAuthorityResponse, error) {
|
func (v *VaultCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequest) (*apiv1.GetCertificateAuthorityResponse, error) {
|
||||||
secret, err := v.client.Logical().Read(v.config.PKI + "/cert/ca_chain")
|
secret, err := v.client.Logical().Read(v.config.PKIMountPath + "/cert/ca_chain")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading ca chain: %w", err)
|
return nil, fmt.Errorf("error reading ca chain: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -190,7 +184,7 @@ func (v *VaultCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv
|
||||||
vaultReq := map[string]interface{}{
|
vaultReq := map[string]interface{}{
|
||||||
"serial_number": formatSerialNumber(sn),
|
"serial_number": formatSerialNumber(sn),
|
||||||
}
|
}
|
||||||
_, err := v.client.Logical().Write(v.config.PKI+"/revoke/", vaultReq)
|
_, err := v.client.Logical().Write(v.config.PKIMountPath+"/revoke/", vaultReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error revoking certificate: %w", err)
|
return nil, fmt.Errorf("error revoking certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -224,7 +218,7 @@ func (v *VaultCAS) createCertificate(cr *x509.CertificateRequest, lifetime time.
|
||||||
"ttl": lifetime.Seconds(),
|
"ttl": lifetime.Seconds(),
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := v.client.Logical().Write(v.config.PKI+"/sign/"+vaultPKIRole, vaultReq)
|
secret, err := v.client.Logical().Write(v.config.PKIMountPath+"/sign/"+vaultPKIRole, vaultReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("error signing certificate: %w", err)
|
return nil, nil, fmt.Errorf("error signing certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -247,21 +241,17 @@ func (v *VaultCAS) createCertificate(cr *x509.CertificateRequest, lifetime time.
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadOptions(config json.RawMessage) (*VaultOptions, error) {
|
func loadOptions(config json.RawMessage) (*VaultOptions, error) {
|
||||||
var vc *VaultOptions
|
// setup default values
|
||||||
|
vc := VaultOptions{
|
||||||
|
PKIMountPath: "pki",
|
||||||
|
PKIRoleDefault: "default",
|
||||||
|
}
|
||||||
|
|
||||||
err := json.Unmarshal(config, &vc)
|
err := json.Unmarshal(config, &vc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error decoding vaultCAS config: %w", err)
|
return nil, fmt.Errorf("error decoding vaultCAS config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vc.PKI == "" {
|
|
||||||
vc.PKI = "pki" // use default pki vault name
|
|
||||||
}
|
|
||||||
|
|
||||||
if vc.PKIRoleDefault == "" {
|
|
||||||
vc.PKIRoleDefault = "default" // use default pki role name
|
|
||||||
}
|
|
||||||
|
|
||||||
if vc.PKIRoleRSA == "" {
|
if vc.PKIRoleRSA == "" {
|
||||||
vc.PKIRoleRSA = vc.PKIRoleDefault
|
vc.PKIRoleRSA = vc.PKIRoleDefault
|
||||||
}
|
}
|
||||||
|
@ -272,23 +262,7 @@ func loadOptions(config json.RawMessage) (*VaultOptions, error) {
|
||||||
vc.PKIRoleEd25519 = vc.PKIRoleDefault
|
vc.PKIRoleEd25519 = vc.PKIRoleDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
if vc.RoleID == "" {
|
return &vc, nil
|
||||||
return nil, errors.New("vaultCAS config options must define `roleID`")
|
|
||||||
}
|
|
||||||
|
|
||||||
if vc.SecretID.FromEnv == "" && vc.SecretID.FromFile == "" && vc.SecretID.FromString == "" {
|
|
||||||
return nil, errors.New("vaultCAS config options must define `secretID` object with one of `FromEnv`, `FromFile` or `FromString`")
|
|
||||||
}
|
|
||||||
|
|
||||||
if vc.PKI == "" {
|
|
||||||
vc.PKI = "pki" // use default pki vault name
|
|
||||||
}
|
|
||||||
|
|
||||||
if vc.AppRole == "" {
|
|
||||||
vc.AppRole = "auth/approle"
|
|
||||||
}
|
|
||||||
|
|
||||||
return vc, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCertificates(pemCert string) []*x509.Certificate {
|
func parseCertificates(pemCert string) []*x509.Certificate {
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
vault "github.com/hashicorp/vault/api"
|
vault "github.com/hashicorp/vault/api"
|
||||||
auth "github.com/hashicorp/vault/api/auth/approle"
|
|
||||||
"github.com/smallstep/certificates/cas/apiv1"
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
"go.step.sm/crypto/pemutil"
|
"go.step.sm/crypto/pemutil"
|
||||||
)
|
)
|
||||||
|
@ -99,7 +98,7 @@ func testCAHelper(t *testing.T) (*url.URL, *vault.Client) {
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
case r.RequestURI == "/v1/auth/auth/approle/login":
|
case r.RequestURI == "/v1/auth/approle/login":
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, `{
|
fmt.Fprintf(w, `{
|
||||||
"auth": {
|
"auth": {
|
||||||
|
@ -183,11 +182,8 @@ func TestNew_register(t *testing.T) {
|
||||||
CertificateAuthority: caURL.String(),
|
CertificateAuthority: caURL.String(),
|
||||||
CertificateAuthorityFingerprint: testRootFingerprint,
|
CertificateAuthorityFingerprint: testRootFingerprint,
|
||||||
Config: json.RawMessage(`{
|
Config: json.RawMessage(`{
|
||||||
"PKI": "pki",
|
"AuthType": "approle",
|
||||||
"PKIRoleDefault": "pki-role",
|
"AuthOptions": {"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false}
|
||||||
"RoleID": "roleID",
|
|
||||||
"SecretID": {"FromString": "secretID"},
|
|
||||||
"IsWrappingToken": false
|
|
||||||
}`),
|
}`),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -201,15 +197,11 @@ func TestVaultCAS_CreateCertificate(t *testing.T) {
|
||||||
_, client := testCAHelper(t)
|
_, client := testCAHelper(t)
|
||||||
|
|
||||||
options := VaultOptions{
|
options := VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "role",
|
PKIRoleDefault: "role",
|
||||||
PKIRoleRSA: "rsa",
|
PKIRoleRSA: "rsa",
|
||||||
PKIRoleEC: "ec",
|
PKIRoleEC: "ec",
|
||||||
PKIRoleEd25519: "ed25519",
|
PKIRoleEd25519: "ed25519",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromString: "secretID"},
|
|
||||||
AppRole: "approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
|
@ -291,7 +283,7 @@ func TestVaultCAS_GetCertificateAuthority(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
options := VaultOptions{
|
options := VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCert := parseCertificates(testRootCertificate)[0]
|
rootCert := parseCertificates(testRootCertificate)[0]
|
||||||
|
@ -335,15 +327,11 @@ func TestVaultCAS_RevokeCertificate(t *testing.T) {
|
||||||
_, client := testCAHelper(t)
|
_, client := testCAHelper(t)
|
||||||
|
|
||||||
options := VaultOptions{
|
options := VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "role",
|
PKIRoleDefault: "role",
|
||||||
PKIRoleRSA: "rsa",
|
PKIRoleRSA: "rsa",
|
||||||
PKIRoleEC: "ec",
|
PKIRoleEC: "ec",
|
||||||
PKIRoleEd25519: "ed25519",
|
PKIRoleEd25519: "ed25519",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromString: "secretID"},
|
|
||||||
AppRole: "approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
|
@ -407,15 +395,11 @@ func TestVaultCAS_RenewCertificate(t *testing.T) {
|
||||||
_, client := testCAHelper(t)
|
_, client := testCAHelper(t)
|
||||||
|
|
||||||
options := VaultOptions{
|
options := VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "role",
|
PKIRoleDefault: "role",
|
||||||
PKIRoleRSA: "rsa",
|
PKIRoleRSA: "rsa",
|
||||||
PKIRoleEC: "ec",
|
PKIRoleEC: "ec",
|
||||||
PKIRoleEd25519: "ed25519",
|
PKIRoleEd25519: "ed25519",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromString: "secretID"},
|
|
||||||
AppRole: "approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
|
@ -464,202 +448,66 @@ func TestVaultCAS_loadOptions(t *testing.T) {
|
||||||
want *VaultOptions
|
want *VaultOptions
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
"ok mandatory with SecretID FromString",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromString": "secretID"}}`,
|
|
||||||
&VaultOptions{
|
|
||||||
PKI: "pki",
|
|
||||||
PKIRoleDefault: "default",
|
|
||||||
PKIRoleRSA: "default",
|
|
||||||
PKIRoleEC: "default",
|
|
||||||
PKIRoleEd25519: "default",
|
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromString: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ok mandatory with SecretID FromFile",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromFile": "secretID"}}`,
|
|
||||||
&VaultOptions{
|
|
||||||
PKI: "pki",
|
|
||||||
PKIRoleDefault: "default",
|
|
||||||
PKIRoleRSA: "default",
|
|
||||||
PKIRoleEC: "default",
|
|
||||||
PKIRoleEd25519: "default",
|
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromFile: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ok mandatory with SecretID FromEnv",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
|
||||||
&VaultOptions{
|
|
||||||
PKI: "pki",
|
|
||||||
PKIRoleDefault: "default",
|
|
||||||
PKIRoleRSA: "default",
|
|
||||||
PKIRoleEC: "default",
|
|
||||||
PKIRoleEd25519: "default",
|
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromEnv: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ok mandatory PKIRole PKIRoleEd25519",
|
"ok mandatory PKIRole PKIRoleEd25519",
|
||||||
`{"PKIRoleDefault": "role", "PKIRoleEd25519": "ed25519" , "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
`{"PKIRoleDefault": "role", "PKIRoleEd25519": "ed25519"}`,
|
||||||
&VaultOptions{
|
&VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "role",
|
PKIRoleDefault: "role",
|
||||||
PKIRoleRSA: "role",
|
PKIRoleRSA: "role",
|
||||||
PKIRoleEC: "role",
|
PKIRoleEC: "role",
|
||||||
PKIRoleEd25519: "ed25519",
|
PKIRoleEd25519: "ed25519",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromEnv: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ok mandatory PKIRole PKIRoleEC",
|
"ok mandatory PKIRole PKIRoleEC",
|
||||||
`{"PKIRoleDefault": "role", "PKIRoleEC": "ec" , "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
`{"PKIRoleDefault": "role", "PKIRoleEC": "ec"}`,
|
||||||
&VaultOptions{
|
&VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "role",
|
PKIRoleDefault: "role",
|
||||||
PKIRoleRSA: "role",
|
PKIRoleRSA: "role",
|
||||||
PKIRoleEC: "ec",
|
PKIRoleEC: "ec",
|
||||||
PKIRoleEd25519: "role",
|
PKIRoleEd25519: "role",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromEnv: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ok mandatory PKIRole PKIRoleRSA",
|
"ok mandatory PKIRole PKIRoleRSA",
|
||||||
`{"PKIRoleDefault": "role", "PKIRoleRSA": "rsa" , "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
`{"PKIRoleDefault": "role", "PKIRoleRSA": "rsa"}`,
|
||||||
&VaultOptions{
|
&VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "role",
|
PKIRoleDefault: "role",
|
||||||
PKIRoleRSA: "rsa",
|
PKIRoleRSA: "rsa",
|
||||||
PKIRoleEC: "role",
|
PKIRoleEC: "role",
|
||||||
PKIRoleEd25519: "role",
|
PKIRoleEd25519: "role",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromEnv: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ok mandatory PKIRoleRSA PKIRoleEC PKIRoleEd25519",
|
"ok mandatory PKIRoleRSA PKIRoleEC PKIRoleEd25519",
|
||||||
`{"PKIRoleRSA": "rsa", "PKIRoleEC": "ec", "PKIRoleEd25519": "ed25519", "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
`{"PKIRoleRSA": "rsa", "PKIRoleEC": "ec", "PKIRoleEd25519": "ed25519"}`,
|
||||||
&VaultOptions{
|
&VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "default",
|
PKIRoleDefault: "default",
|
||||||
PKIRoleRSA: "rsa",
|
PKIRoleRSA: "rsa",
|
||||||
PKIRoleEC: "ec",
|
PKIRoleEC: "ec",
|
||||||
PKIRoleEd25519: "ed25519",
|
PKIRoleEd25519: "ed25519",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromEnv: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ok mandatory PKIRoleRSA PKIRoleEC PKIRoleEd25519 with useless PKIRoleDefault",
|
"ok mandatory PKIRoleRSA PKIRoleEC PKIRoleEd25519 with useless PKIRoleDefault",
|
||||||
`{"PKIRoleDefault": "role", "PKIRoleRSA": "rsa", "PKIRoleEC": "ec", "PKIRoleEd25519": "ed25519", "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
`{"PKIRoleDefault": "role", "PKIRoleRSA": "rsa", "PKIRoleEC": "ec", "PKIRoleEd25519": "ed25519"}`,
|
||||||
&VaultOptions{
|
&VaultOptions{
|
||||||
PKI: "pki",
|
PKIMountPath: "pki",
|
||||||
PKIRoleDefault: "role",
|
PKIRoleDefault: "role",
|
||||||
PKIRoleRSA: "rsa",
|
PKIRoleRSA: "rsa",
|
||||||
PKIRoleEC: "ec",
|
PKIRoleEC: "ec",
|
||||||
PKIRoleEd25519: "ed25519",
|
PKIRoleEd25519: "ed25519",
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromEnv: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ok mandatory with AppRole",
|
|
||||||
`{"AppRole": "test", "RoleID": "roleID", "SecretID": {"FromString": "secretID"}}`,
|
|
||||||
&VaultOptions{
|
|
||||||
PKI: "pki",
|
|
||||||
PKIRoleDefault: "default",
|
|
||||||
PKIRoleRSA: "default",
|
|
||||||
PKIRoleEC: "default",
|
|
||||||
PKIRoleEd25519: "default",
|
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromString: "secretID"},
|
|
||||||
AppRole: "test",
|
|
||||||
IsWrappingToken: false,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ok mandatory with IsWrappingToken",
|
|
||||||
`{"IsWrappingToken": true, "RoleID": "roleID", "SecretID": {"FromString": "secretID"}}`,
|
|
||||||
&VaultOptions{
|
|
||||||
PKI: "pki",
|
|
||||||
PKIRoleDefault: "default",
|
|
||||||
PKIRoleRSA: "default",
|
|
||||||
PKIRoleEC: "default",
|
|
||||||
PKIRoleEd25519: "default",
|
|
||||||
RoleID: "roleID",
|
|
||||||
SecretID: auth.SecretID{FromString: "secretID"},
|
|
||||||
AppRole: "auth/approle",
|
|
||||||
IsWrappingToken: true,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fail with SecretID FromFail",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromFail": "secretID"}}`,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fail with SecretID empty FromEnv",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromEnv": ""}}`,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fail with SecretID empty FromFile",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromFile": ""}}`,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fail with SecretID empty FromString",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromString": ""}}`,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fail mandatory with SecretID FromFail",
|
|
||||||
`{"RoleID": "roleID", "SecretID": {"FromFail": "secretID"}}`,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fail missing RoleID",
|
|
||||||
`{"SecretID": {"FromString": "secretID"}}`,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
9
db/db.go
9
db/db.go
|
@ -51,10 +51,8 @@ type AuthDB interface {
|
||||||
Revoke(rci *RevokedCertificateInfo) error
|
Revoke(rci *RevokedCertificateInfo) error
|
||||||
RevokeSSH(rci *RevokedCertificateInfo) error
|
RevokeSSH(rci *RevokedCertificateInfo) error
|
||||||
GetCertificate(serialNumber string) (*x509.Certificate, error)
|
GetCertificate(serialNumber string) (*x509.Certificate, error)
|
||||||
StoreCertificate(crt *x509.Certificate) error
|
|
||||||
UseToken(id, tok string) (bool, error)
|
UseToken(id, tok string) (bool, error)
|
||||||
IsSSHHost(name string) (bool, error)
|
IsSSHHost(name string) (bool, error)
|
||||||
StoreSSHCertificate(crt *ssh.Certificate) error
|
|
||||||
GetSSHHostPrincipals() ([]string, error)
|
GetSSHHostPrincipals() ([]string, error)
|
||||||
Shutdown() error
|
Shutdown() error
|
||||||
}
|
}
|
||||||
|
@ -82,6 +80,13 @@ func MustFromContext(ctx context.Context) AuthDB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CertificateStorer is an extension of AuthDB that allows to store
|
||||||
|
// certificates.
|
||||||
|
type CertificateStorer interface {
|
||||||
|
StoreCertificate(crt *x509.Certificate) error
|
||||||
|
StoreSSHCertificate(crt *ssh.Certificate) error
|
||||||
|
}
|
||||||
|
|
||||||
// DB is a wrapper over the nosql.DB interface.
|
// DB is a wrapper over the nosql.DB interface.
|
||||||
type DB struct {
|
type DB struct {
|
||||||
nosql.DB
|
nosql.DB
|
||||||
|
|
|
@ -20,7 +20,7 @@ type SimpleDB struct {
|
||||||
usedTokens *sync.Map
|
usedTokens *sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSimpleDB(c *Config) (AuthDB, error) {
|
func newSimpleDB(c *Config) (*SimpleDB, error) {
|
||||||
db := &SimpleDB{}
|
db := &SimpleDB{}
|
||||||
db.usedTokens = new(sync.Map)
|
db.usedTokens = new(sync.Map)
|
||||||
return db, nil
|
return db, nil
|
||||||
|
|
|
@ -654,7 +654,7 @@ preferably not all - meaning it never leaves the server on which it was created.
|
||||||
|
|
||||||
### Passwords
|
### Passwords
|
||||||
|
|
||||||
When you intialize your PKI (`step ca init`) the root and intermediate
|
When you initialize your PKI (`step ca init`) the root and intermediate
|
||||||
private keys will be encrypted with the same password. We recommend that you
|
private keys will be encrypted with the same password. We recommend that you
|
||||||
change the password with which the intermediate is encrypted at your earliest
|
change the password with which the intermediate is encrypted at your earliest
|
||||||
convenience.
|
convenience.
|
||||||
|
@ -681,7 +681,7 @@ to divide the root private key password across a handful of trusted parties.
|
||||||
|
|
||||||
### Provisioners
|
### Provisioners
|
||||||
|
|
||||||
When you intialize your PKI (`step ca init`) a default provisioner will be created
|
When you initialize your PKI (`step ca init`) a default provisioner will be created
|
||||||
and it's private key will be encrypted using the same password used to encrypt
|
and it's private key will be encrypted using the same password used to encrypt
|
||||||
the root private key. Before deploying the Step CA you should remove this
|
the root private key. Before deploying the Step CA you should remove this
|
||||||
provisioner and add new ones that are encrypted with new, secure, random passwords.
|
provisioner and add new ones that are encrypted with new, secure, random passwords.
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -29,6 +29,7 @@ require (
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1
|
github.com/googleapis/gax-go/v2 v2.1.1
|
||||||
github.com/hashicorp/vault/api v1.3.1
|
github.com/hashicorp/vault/api v1.3.1
|
||||||
github.com/hashicorp/vault/api/auth/approle v0.1.1
|
github.com/hashicorp/vault/api/auth/approle v0.1.1
|
||||||
|
github.com/hashicorp/vault/api/auth/kubernetes v0.1.0
|
||||||
github.com/jhump/protoreflect v1.9.0 // indirect
|
github.com/jhump/protoreflect v1.9.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
|
@ -46,7 +47,7 @@ require (
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||||
go.step.sm/cli-utils v0.7.0
|
go.step.sm/cli-utils v0.7.0
|
||||||
go.step.sm/crypto v0.16.1
|
go.step.sm/crypto v0.16.2
|
||||||
go.step.sm/linkedca v0.16.1
|
go.step.sm/linkedca v0.16.1
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
||||||
|
@ -64,3 +65,6 @@ require (
|
||||||
// replace go.step.sm/crypto => ../crypto
|
// replace go.step.sm/crypto => ../crypto
|
||||||
// replace go.step.sm/cli-utils => ../cli-utils
|
// replace go.step.sm/cli-utils => ../cli-utils
|
||||||
// replace go.step.sm/linkedca => ../linkedca
|
// replace go.step.sm/linkedca => ../linkedca
|
||||||
|
|
||||||
|
// use github.com/smallstep/pkcs7 fork with patches applied
|
||||||
|
replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6
|
||||||
|
|
13
go.sum
13
go.sum
|
@ -449,6 +449,8 @@ github.com/hashicorp/vault/api v1.3.1 h1:pkDkcgTh47PRjY1NEFeofqR4W/HkNUi9qIakESO
|
||||||
github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw=
|
github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw=
|
||||||
github.com/hashicorp/vault/api/auth/approle v0.1.1 h1:R5yA+xcNvw1ix6bDuWOaLOq2L4L77zDCVsethNw97xQ=
|
github.com/hashicorp/vault/api/auth/approle v0.1.1 h1:R5yA+xcNvw1ix6bDuWOaLOq2L4L77zDCVsethNw97xQ=
|
||||||
github.com/hashicorp/vault/api/auth/approle v0.1.1/go.mod h1:mHOLgh//xDx4dpqXoq6tS8Ob0FoCFWLU2ibJ26Lfmag=
|
github.com/hashicorp/vault/api/auth/approle v0.1.1/go.mod h1:mHOLgh//xDx4dpqXoq6tS8Ob0FoCFWLU2ibJ26Lfmag=
|
||||||
|
github.com/hashicorp/vault/api/auth/kubernetes v0.1.0 h1:6BtyahbF4aQp8gg3ww0A/oIoqzbhpNP1spXU3nHE0n0=
|
||||||
|
github.com/hashicorp/vault/api/auth/kubernetes v0.1.0/go.mod h1:Pdgk78uIs0mgDOLvc3a+h/vYIT9rznw2sz+ucuH9024=
|
||||||
github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY=
|
github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY=
|
||||||
github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
|
github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||||
|
@ -735,6 +737,8 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1
|
||||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||||
github.com/smallstep/nosql v0.4.0 h1:Go3WYwttUuvwqMtFiiU4g7kBIlY+hR0bIZAqVdakQ3M=
|
github.com/smallstep/nosql v0.4.0 h1:Go3WYwttUuvwqMtFiiU4g7kBIlY+hR0bIZAqVdakQ3M=
|
||||||
github.com/smallstep/nosql v0.4.0/go.mod h1:yKZT5h7cdIVm6wEKM9+jN5dgK80Hljpuy8HNsnI7Gzo=
|
github.com/smallstep/nosql v0.4.0/go.mod h1:yKZT5h7cdIVm6wEKM9+jN5dgK80Hljpuy8HNsnI7Gzo=
|
||||||
|
github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6 h1:8Rjy6IZbSM/jcYgBWCoLIGjug7QcoLtF9sUuhDrHD2U=
|
||||||
|
github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
@ -796,9 +800,6 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210730143726-725912489c62/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak=
|
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
@ -813,10 +814,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
||||||
go.step.sm/cli-utils v0.7.0 h1:2GvY5Muid1yzp7YQbfCCS+gK3q7zlHjjLL5Z0DXz8ds=
|
go.step.sm/cli-utils v0.7.0 h1:2GvY5Muid1yzp7YQbfCCS+gK3q7zlHjjLL5Z0DXz8ds=
|
||||||
go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
||||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||||
go.step.sm/crypto v0.16.1 h1:4mnZk21cSxyMGxsEpJwZKKvJvDu1PN09UVrWWFNUBdk=
|
go.step.sm/crypto v0.16.2 h1:Pr9aazTwWBBZNogUsOqhOrPSdwAa9pPs+lMB602lnDA=
|
||||||
go.step.sm/crypto v0.16.1/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
go.step.sm/crypto v0.16.2/go.mod h1:1WkTOTY+fOX/RY4TnZREp6trQAsBHRQ7nu6QJBiNQF8=
|
||||||
go.step.sm/linkedca v0.16.0 h1:9xdE150lRTEoBq1gJl+prePpSmNqXRXsez3qzRs3Lic=
|
|
||||||
go.step.sm/linkedca v0.16.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
|
||||||
go.step.sm/linkedca v0.16.1 h1:CdbMV5SjnlRsgeYTXaaZmQCkYIgJq8BOzpewri57M2k=
|
go.step.sm/linkedca v0.16.1 h1:CdbMV5SjnlRsgeYTXaaZmQCkYIgJq8BOzpewri57M2k=
|
||||||
go.step.sm/linkedca v0.16.1/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
go.step.sm/linkedca v0.16.1/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
|
Loading…
Add table
Reference in a new issue