Compare commits

..

13 commits

Author SHA1 Message Date
Josh Drake
a1350b14fb
Merge pull request #1489 from smallstep/josh/authorization-principal-in-webhook
Include authorization principal in provisioner webhooks.
2023-07-24 21:22:46 -05:00
Mariano Cano
c9df65ebae
Merge pull request #1490 from smallstep/dry-run-migration
Add option to dry-run the migration
2023-07-24 16:39:39 -07:00
Mariano Cano
d9d7c52997
Add option to dry-run the migration
This commit adds an option that runs the migration on a virtual database
that doesn't do anything. This option can be used to see how many rows
there are.
2023-07-24 16:35:22 -07:00
Josh Drake
ff424fa944
Fix tests. 2023-07-24 15:27:49 -05:00
github-actions[bot]
7282245e88
Merge pull request #1488 from smallstep/dependabot/go_modules/go.step.sm/linkedca-0.20.0
Bump go.step.sm/linkedca from 0.19.1 to 0.20.0
2023-07-24 18:21:34 +02:00
github-actions[bot]
9a7582d1d3
Merge pull request #1487 from smallstep/dependabot/go_modules/google.golang.org/api-0.132.0
Bump google.golang.org/api from 0.131.0 to 0.132.0
2023-07-24 18:20:32 +02:00
dependabot[bot]
7796ad8f90
Bump go.step.sm/linkedca from 0.19.1 to 0.20.0
Bumps [go.step.sm/linkedca](https://github.com/smallstep/linkedca) from 0.19.1 to 0.20.0.
- [Release notes](https://github.com/smallstep/linkedca/releases)
- [Commits](https://github.com/smallstep/linkedca/compare/v0.19.1...v0.20.0)

---
updated-dependencies:
- dependency-name: go.step.sm/linkedca
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 15:30:23 +00:00
dependabot[bot]
2d666cfc4f
Bump google.golang.org/api from 0.131.0 to 0.132.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.131.0 to 0.132.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.131.0...v0.132.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 15:30:12 +00:00
Josh Drake
904f416d20
Include authorization principal in provisioner webhooks. 2023-07-24 00:30:05 -05:00
Mariano Cano
d89c3a942e
Merge pull request #1486 from smallstep/migrate-admindb
Add to the migration script the admin tables
2023-07-20 20:55:35 -07:00
Mariano Cano
aa30c2c73c
Add to the migration script the admin tables 2023-07-20 18:07:28 -07:00
Mariano Cano
31533c4a15
Merge pull request #1485 from smallstep/webhooks-x5c
Send X5C leaf certificate to webhooks
2023-07-20 14:02:59 -07:00
Mariano Cano
5bfe96d8c7
Send X5C leaf certificate to webhooks
This commit adds a new property that will be sent to authorizing and
enriching webhooks when signing certificates using the X5C provisioner.
2023-07-20 13:03:45 -07:00
29 changed files with 400 additions and 171 deletions

View file

@ -35,9 +35,6 @@ var (
// DefaultEnableSSHCA enable SSH CA features per provisioner or globally // DefaultEnableSSHCA enable SSH CA features per provisioner or globally
// for all provisioners. // for all provisioners.
DefaultEnableSSHCA = false DefaultEnableSSHCA = false
// DefaultDisableSmallstepExtensions disables the Smallstep extensions in
// the certificate.
DefaultDisableSmallstepExtensions = false
// DefaultCRLCacheDuration is the default cache duration for the CRL. // DefaultCRLCacheDuration is the default cache duration for the CRL.
DefaultCRLCacheDuration = &provisioner.Duration{Duration: 24 * time.Hour} DefaultCRLCacheDuration = &provisioner.Duration{Duration: 24 * time.Hour}
// DefaultCRLExpiredDuration is the default duration in which expired // DefaultCRLExpiredDuration is the default duration in which expired
@ -58,7 +55,6 @@ var (
EnableSSHCA: &DefaultEnableSSHCA, EnableSSHCA: &DefaultEnableSSHCA,
DisableRenewal: &DefaultDisableRenewal, DisableRenewal: &DefaultDisableRenewal,
AllowRenewalAfterExpiry: &DefaultAllowRenewalAfterExpiry, AllowRenewalAfterExpiry: &DefaultAllowRenewalAfterExpiry,
DisableSmallstepExtensions: &DefaultDisableSmallstepExtensions,
} }
) )

View file

@ -257,7 +257,7 @@ func (p *ACME) AuthorizeSign(context.Context, string) ([]SignOption, error) {
opts := []SignOption{ opts := []SignOption{
p, p,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeACME, p.Name, "").WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeACME, p.Name, ""),
newForceCNOption(p.ForceCN), newForceCNOption(p.ForceCN),
profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators

View file

@ -24,6 +24,7 @@ import (
"go.step.sm/linkedca" "go.step.sm/linkedca"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
) )
// awsIssuer is the string used as issuer in the generated tokens. // awsIssuer is the string used as issuer in the generated tokens.
@ -514,14 +515,18 @@ func (p *AWS) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
p, p,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID).WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},
commonNameValidator(payload.Claims.Subject), commonNameValidator(payload.Claims.Subject),
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509), p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithAuthorizationPrincipal(doc.InstanceID),
),
), nil ), nil
} }
@ -804,6 +809,10 @@ func (p *AWS) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e
// Ensure that all principal names are allowed // Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil),
// Call webhooks // Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH), p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithAuthorizationPrincipal(doc.InstanceID),
),
), nil ), nil
} }

View file

@ -20,6 +20,7 @@ import (
"go.step.sm/linkedca" "go.step.sm/linkedca"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
) )
// azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens. // azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens.
@ -397,13 +398,17 @@ func (p *Azure) AuthorizeSign(_ context.Context, token string) ([]SignOption, er
p, p,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID).WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID),
profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509), p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithAuthorizationPrincipal(identityObjectID),
),
), nil ), nil
} }
@ -421,7 +426,7 @@ func (p *Azure) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption,
return nil, errs.Unauthorized("azure.AuthorizeSSHSign; sshCA is disabled for provisioner '%s'", p.GetName()) return nil, errs.Unauthorized("azure.AuthorizeSSHSign; sshCA is disabled for provisioner '%s'", p.GetName())
} }
_, name, _, _, _, err := p.authorizeToken(token) _, name, _, _, identityObjectID, err := p.authorizeToken(token)
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign") return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign")
} }
@ -473,7 +478,11 @@ func (p *Azure) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption,
// Ensure that all principal names are allowed // Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil),
// Call webhooks // Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH), p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithAuthorizationPrincipal(identityObjectID),
),
), nil ), nil
} }

View file

@ -26,9 +26,6 @@ type Claims struct {
// Renewal properties // Renewal properties
DisableRenewal *bool `json:"disableRenewal,omitempty"` DisableRenewal *bool `json:"disableRenewal,omitempty"`
AllowRenewalAfterExpiry *bool `json:"allowRenewalAfterExpiry,omitempty"` AllowRenewalAfterExpiry *bool `json:"allowRenewalAfterExpiry,omitempty"`
// Other properties
DisableSmallstepExtensions *bool `json:"disableSmallstepExtensions,omitempty"`
} }
// Claimer is the type that controls claims. It provides an interface around the // Claimer is the type that controls claims. It provides an interface around the
@ -50,7 +47,6 @@ func (c *Claimer) Claims() Claims {
disableRenewal := c.IsDisableRenewal() disableRenewal := c.IsDisableRenewal()
allowRenewalAfterExpiry := c.AllowRenewalAfterExpiry() allowRenewalAfterExpiry := c.AllowRenewalAfterExpiry()
enableSSHCA := c.IsSSHCAEnabled() enableSSHCA := c.IsSSHCAEnabled()
disableSmallstepExtensions := c.IsDisableSmallstepExtensions()
return Claims{ return Claims{
MinTLSDur: &Duration{c.MinTLSCertDuration()}, MinTLSDur: &Duration{c.MinTLSCertDuration()},
@ -65,7 +61,6 @@ func (c *Claimer) Claims() Claims {
EnableSSHCA: &enableSSHCA, EnableSSHCA: &enableSSHCA,
DisableRenewal: &disableRenewal, DisableRenewal: &disableRenewal,
AllowRenewalAfterExpiry: &allowRenewalAfterExpiry, AllowRenewalAfterExpiry: &allowRenewalAfterExpiry,
DisableSmallstepExtensions: &disableSmallstepExtensions,
} }
} }
@ -115,15 +110,6 @@ func (c *Claimer) IsDisableRenewal() bool {
return *c.claims.DisableRenewal return *c.claims.DisableRenewal
} }
// IsDisableSmallstepExtensions returns if the Smallstep extensions, like the
// provisioner extension, should be excluded from the certificate.
func (c *Claimer) IsDisableSmallstepExtensions() bool {
if c.claims == nil || c.claims.DisableSmallstepExtensions == nil {
return *c.global.DisableSmallstepExtensions
}
return *c.claims.DisableSmallstepExtensions
}
// AllowRenewalAfterExpiry returns if the renewal flow is authorized if the // AllowRenewalAfterExpiry returns if the renewal flow is authorized if the
// certificate is expired. If the property is not set within the provisioner // certificate is expired. If the property is not set within the provisioner
// then the global value from the authority configuration will be used. // then the global value from the authority configuration will be used.

View file

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -77,7 +78,7 @@ func (c *Controller) AuthorizeSSHRenew(ctx context.Context, cert *ssh.Certificat
return DefaultAuthorizeSSHRenew(ctx, c, cert) return DefaultAuthorizeSSHRenew(ctx, c, cert)
} }
func (c *Controller) newWebhookController(templateData WebhookSetter, certType linkedca.Webhook_CertType) *WebhookController { func (c *Controller) newWebhookController(templateData WebhookSetter, certType linkedca.Webhook_CertType, opts ...webhook.RequestBodyOption) *WebhookController {
client := c.webhookClient client := c.webhookClient
if client == nil { if client == nil {
client = http.DefaultClient client = http.DefaultClient
@ -87,6 +88,7 @@ func (c *Controller) newWebhookController(templateData WebhookSetter, certType l
client: client, client: client,
webhooks: c.webhooks, webhooks: c.webhooks,
certType: certType, certType: certType,
options: opts,
} }
} }

View file

@ -4,15 +4,18 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/webhook"
) )
var trueValue = true var trueValue = true
@ -449,16 +452,39 @@ func TestDefaultAuthorizeSSHRenew(t *testing.T) {
} }
func Test_newWebhookController(t *testing.T) { func Test_newWebhookController(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil {
t.Fatal(err)
}
opts := []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)}
type args struct {
templateData WebhookSetter
certType linkedca.Webhook_CertType
opts []webhook.RequestBodyOption
}
tests := []struct {
name string
args args
want *WebhookController
}{
{"ok", args{x509util.TemplateData{"foo": "bar"}, linkedca.Webhook_X509, nil}, &WebhookController{
TemplateData: x509util.TemplateData{"foo": "bar"},
certType: linkedca.Webhook_X509,
client: http.DefaultClient,
}},
{"ok with options", args{x509util.TemplateData{"foo": "bar"}, linkedca.Webhook_SSH, opts}, &WebhookController{
TemplateData: x509util.TemplateData{"foo": "bar"},
certType: linkedca.Webhook_SSH,
client: http.DefaultClient,
options: opts,
}},
}
for _, tt := range tests {
c := &Controller{} c := &Controller{}
data := x509util.TemplateData{"foo": "bar"} got := c.newWebhookController(tt.args.templateData, tt.args.certType, tt.args.opts...)
ctl := c.newWebhookController(data, linkedca.Webhook_X509) if !reflect.DeepEqual(got, tt.want) {
if !reflect.DeepEqual(ctl.TemplateData, data) { t.Errorf("newWebhookController() = %v, want %v", got, tt.want)
t.Error("Failed to set templateData")
} }
if ctl.certType != linkedca.Webhook_X509 {
t.Error("Failed to set certType")
}
if ctl.client == nil {
t.Error("Failed to set client")
} }
} }

View file

@ -21,6 +21,7 @@ import (
"go.step.sm/linkedca" "go.step.sm/linkedca"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
) )
// gcpCertsURL is the url that serves Google OAuth2 public keys. // gcpCertsURL is the url that serves Google OAuth2 public keys.
@ -269,13 +270,17 @@ func (p *GCP) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
p, p,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName).WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName),
profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509), p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithAuthorizationPrincipal(ce.InstanceID),
),
), nil ), nil
} }
@ -442,6 +447,10 @@ func (p *GCP) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e
// Ensure that all principal names are allowed // Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil),
// Call webhooks // Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH), p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithAuthorizationPrincipal(ce.InstanceID),
),
), nil ), nil
} }

View file

@ -187,7 +187,7 @@ func (p *JWK) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
self, self,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID).WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID),
profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators
commonNameValidator(claims.Subject), commonNameValidator(claims.Subject),

View file

@ -238,7 +238,7 @@ func (p *K8sSA) AuthorizeSign(_ context.Context, token string) ([]SignOption, er
p, p,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeK8sSA, p.Name, "").WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeK8sSA, p.Name, ""),
profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},

View file

@ -150,7 +150,7 @@ func (p *Nebula) AuthorizeSign(_ context.Context, token string) ([]SignOption, e
p, p,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeNebula, p.Name, "").WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeNebula, p.Name, ""),
profileLimitDuration{ profileLimitDuration{
def: p.ctl.Claimer.DefaultTLSCertDuration(), def: p.ctl.Claimer.DefaultTLSCertDuration(),
notBefore: crt.Details.NotBefore, notBefore: crt.Details.NotBefore,

View file

@ -351,7 +351,7 @@ func (o *OIDC) AuthorizeSign(_ context.Context, token string) ([]SignOption, err
o, o,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID).WithControllerOptions(o.ctl), newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID),
profileDefaultDuration(o.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(o.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},

View file

@ -190,7 +190,7 @@ func (s *SCEP) AuthorizeSign(context.Context, string) ([]SignOption, error) {
return []SignOption{ return []SignOption{
s, s,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeSCEP, s.Name, "").WithControllerOptions(s.ctl), newProvisionerExtensionOption(TypeSCEP, s.Name, ""),
newForceCNOption(s.ForceCN), newForceCNOption(s.ForceCN),
profileDefaultDuration(s.ctl.Claimer.DefaultTLSCertDuration()), profileDefaultDuration(s.ctl.Claimer.DefaultTLSCertDuration()),
// validators // validators

View file

@ -430,7 +430,6 @@ func (o *forceCNOption) Modify(cert *x509.Certificate, _ SignOptions) error {
type provisionerExtensionOption struct { type provisionerExtensionOption struct {
Extension Extension
Disabled bool
} }
func newProvisionerExtensionOption(typ Type, name, credentialID string, keyValuePairs ...string) *provisionerExtensionOption { func newProvisionerExtensionOption(typ Type, name, credentialID string, keyValuePairs ...string) *provisionerExtensionOption {
@ -444,18 +443,7 @@ func newProvisionerExtensionOption(typ Type, name, credentialID string, keyValue
} }
} }
// WithControllerOptions returns the provisionerExtensionOption options from the
// controller. Currently only the claim DisableSmallstepExtensions is used.
func (o *provisionerExtensionOption) WithControllerOptions(c *Controller) *provisionerExtensionOption {
o.Disabled = c.Claimer.IsDisableSmallstepExtensions()
return o
}
func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ SignOptions) error { func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ SignOptions) error {
if o.Disabled {
return nil
}
ext, err := o.ToExtension() ext, err := o.ToExtension()
if err != nil { if err != nil {
return errs.NewError(http.StatusInternalServerError, err, "error creating certificate") return errs.NewError(http.StatusInternalServerError, err, "error creating certificate")

View file

@ -604,23 +604,13 @@ func Test_newProvisionerExtension_Option(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Claims with smallstep extensions disabled.
claimer, err := NewClaimer(&Claims{
DisableSmallstepExtensions: &trueValue,
}, globalProvisionerClaims)
if err != nil {
t.Fatal(err)
}
type test struct { type test struct {
modifier *provisionerExtensionOption
cert *x509.Certificate cert *x509.Certificate
valid func(*x509.Certificate) valid func(*x509.Certificate)
} }
tests := map[string]func() test{ tests := map[string]func() test{
"ok/one-element": func() test { "ok/one-element": func() test {
return test{ return test{
modifier: newProvisionerExtensionOption(TypeJWK, "name", "credentialId", "key", "value"),
cert: new(x509.Certificate), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
if assert.Len(t, 1, cert.ExtraExtensions) { if assert.Len(t, 1, cert.ExtraExtensions) {
@ -635,7 +625,6 @@ func Test_newProvisionerExtension_Option(t *testing.T) {
}, },
"ok/replace": func() test { "ok/replace": func() test {
return test{ return test{
modifier: newProvisionerExtensionOption(TypeJWK, "name", "credentialId", "key", "value"),
cert: &x509.Certificate{ExtraExtensions: []pkix.Extension{{Id: StepOIDProvisioner, Critical: true}, {Id: []int{1, 2, 3}}}}, cert: &x509.Certificate{ExtraExtensions: []pkix.Extension{{Id: StepOIDProvisioner, Critical: true}, {Id: []int{1, 2, 3}}}},
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
if assert.Len(t, 2, cert.ExtraExtensions) { if assert.Len(t, 2, cert.ExtraExtensions) {
@ -647,22 +636,11 @@ func Test_newProvisionerExtension_Option(t *testing.T) {
}, },
} }
}, },
"ok/disabled": func() test {
return test{
modifier: newProvisionerExtensionOption(TypeJWK, "name", "credentialId", "key", "value").WithControllerOptions(&Controller{
Claimer: claimer,
}),
cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) {
assert.Len(t, 0, cert.ExtraExtensions)
},
}
},
} }
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() tt := run()
assert.FatalError(t, tt.modifier.Modify(tt.cert, SignOptions{})) assert.FatalError(t, newProvisionerExtensionOption(TypeJWK, "name", "credentialId", "key", "value").Modify(tt.cert, SignOptions{}))
tt.valid(tt.cert) tt.valid(tt.cert)
}) })
} }

View file

@ -27,7 +27,6 @@ var (
defaultDisableRenewal = false defaultDisableRenewal = false
defaultAllowRenewalAfterExpiry = false defaultAllowRenewalAfterExpiry = false
defaultEnableSSHCA = true defaultEnableSSHCA = true
defaultDisableSmallstepExtensions = false
globalProvisionerClaims = Claims{ globalProvisionerClaims = Claims{
MinTLSDur: &Duration{5 * time.Minute}, MinTLSDur: &Duration{5 * time.Minute},
MaxTLSDur: &Duration{24 * time.Hour}, MaxTLSDur: &Duration{24 * time.Hour},
@ -41,7 +40,6 @@ var (
EnableSSHCA: &defaultEnableSSHCA, EnableSSHCA: &defaultEnableSSHCA,
DisableRenewal: &defaultDisableRenewal, DisableRenewal: &defaultDisableRenewal,
AllowRenewalAfterExpiry: &defaultAllowRenewalAfterExpiry, AllowRenewalAfterExpiry: &defaultAllowRenewalAfterExpiry,
DisableSmallstepExtensions: &defaultDisableSmallstepExtensions,
} }
testAudiences = Audiences{ testAudiences = Audiences{
Sign: []string{"https://ca.smallstep.com/1.0/sign", "https://ca.smallstep.com/sign"}, Sign: []string{"https://ca.smallstep.com/1.0/sign", "https://ca.smallstep.com/sign"},

View file

@ -30,6 +30,7 @@ type WebhookController struct {
client *http.Client client *http.Client
webhooks []*Webhook webhooks []*Webhook
certType linkedca.Webhook_CertType certType linkedca.Webhook_CertType
options []webhook.RequestBodyOption
TemplateData WebhookSetter TemplateData WebhookSetter
} }
@ -39,6 +40,14 @@ func (wc *WebhookController) Enrich(req *webhook.RequestBody) error {
if wc == nil { if wc == nil {
return nil return nil
} }
// Apply extra options in the webhook controller
for _, fn := range wc.options {
if err := fn(req); err != nil {
return err
}
}
for _, wh := range wc.webhooks { for _, wh := range wc.webhooks {
if wh.Kind != linkedca.Webhook_ENRICHING.String() { if wh.Kind != linkedca.Webhook_ENRICHING.String() {
continue continue
@ -63,6 +72,14 @@ func (wc *WebhookController) Authorize(req *webhook.RequestBody) error {
if wc == nil { if wc == nil {
return nil return nil
} }
// Apply extra options in the webhook controller
for _, fn := range wc.options {
if err := fn(req); err != nil {
return err
}
}
for _, wh := range wc.webhooks { for _, wh := range wc.webhooks {
if wh.Kind != linkedca.Webhook_AUTHORIZING.String() { if wh.Kind != linkedca.Webhook_AUTHORIZING.String() {
continue continue

View file

@ -4,6 +4,7 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -16,6 +17,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/webhook" "github.com/smallstep/certificates/webhook"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"go.step.sm/linkedca" "go.step.sm/linkedca"
) )
@ -96,12 +98,18 @@ func TestWebhookController_isCertTypeOK(t *testing.T) {
} }
func TestWebhookController_Enrich(t *testing.T) { func TestWebhookController_Enrich(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil {
t.Fatal(err)
}
type test struct { type test struct {
ctl *WebhookController ctl *WebhookController
req *webhook.RequestBody req *webhook.RequestBody
responses []*webhook.ResponseBody responses []*webhook.ResponseBody
expectErr bool expectErr bool
expectTemplateData any expectTemplateData any
assertRequest func(t *testing.T, req *webhook.RequestBody)
} }
tests := map[string]test{ tests := map[string]test{
"ok/no enriching webhooks": { "ok/no enriching webhooks": {
@ -170,6 +178,29 @@ func TestWebhookController_Enrich(t *testing.T) {
}, },
}, },
}, },
"ok/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}},
expectErr: false,
expectTemplateData: x509util.TemplateData{"Webhooks": map[string]any{"people": map[string]any{"role": "bar"}}},
assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
assert.FatalError(t, err)
assert.Equals(t, &webhook.X5CCertificate{
Raw: cert.Raw,
PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
NotBefore: cert.NotBefore,
NotAfter: cert.NotAfter,
}, req.X5CCertificate)
},
},
"deny": { "deny": {
ctl: &WebhookController{ ctl: &WebhookController{
client: http.DefaultClient, client: http.DefaultClient,
@ -181,6 +212,20 @@ func TestWebhookController_Enrich(t *testing.T) {
expectErr: true, expectErr: true,
expectTemplateData: x509util.TemplateData{}, expectTemplateData: x509util.TemplateData{},
}, },
"fail/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(&x509.Certificate{
PublicKey: []byte("bad"),
})},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
expectTemplateData: x509util.TemplateData{},
},
} }
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@ -200,16 +245,25 @@ func TestWebhookController_Enrich(t *testing.T) {
t.Fatalf("Got err %v, want %v", err, test.expectErr) t.Fatalf("Got err %v, want %v", err, test.expectErr)
} }
assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData) assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData)
if test.assertRequest != nil {
test.assertRequest(t, test.req)
}
}) })
} }
} }
func TestWebhookController_Authorize(t *testing.T) { func TestWebhookController_Authorize(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil {
t.Fatal(err)
}
type test struct { type test struct {
ctl *WebhookController ctl *WebhookController
req *webhook.RequestBody req *webhook.RequestBody
responses []*webhook.ResponseBody responses []*webhook.ResponseBody
expectErr bool expectErr bool
assertRequest func(t *testing.T, req *webhook.RequestBody)
} }
tests := map[string]test{ tests := map[string]test{
"ok/no enriching webhooks": { "ok/no enriching webhooks": {
@ -240,6 +294,27 @@ func TestWebhookController_Authorize(t *testing.T) {
responses: []*webhook.ResponseBody{{Allow: false}}, responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: false, expectErr: false,
}, },
"ok/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true}},
expectErr: false,
assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
assert.FatalError(t, err)
assert.Equals(t, &webhook.X5CCertificate{
Raw: cert.Raw,
PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
NotBefore: cert.NotBefore,
NotAfter: cert.NotAfter,
}, req.X5CCertificate)
},
},
"deny": { "deny": {
ctl: &WebhookController{ ctl: &WebhookController{
client: http.DefaultClient, client: http.DefaultClient,
@ -249,6 +324,18 @@ func TestWebhookController_Authorize(t *testing.T) {
responses: []*webhook.ResponseBody{{Allow: false}}, responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true, expectErr: true,
}, },
"fail/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(&x509.Certificate{
PublicKey: []byte("bad"),
})},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
},
} }
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@ -267,6 +354,9 @@ func TestWebhookController_Authorize(t *testing.T) {
if (err != nil) != test.expectErr { if (err != nil) != test.expectErr {
t.Fatalf("Got err %v, want %v", err, test.expectErr) t.Fatalf("Got err %v, want %v", err, test.expectErr)
} }
if test.assertRequest != nil {
test.assertRequest(t, test.req)
}
}) })
} }
} }

View file

@ -15,6 +15,7 @@ import (
"go.step.sm/linkedca" "go.step.sm/linkedca"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
) )
// x5cPayload extends jwt.Claims with step attributes. // x5cPayload extends jwt.Claims with step attributes.
@ -215,7 +216,8 @@ func (p *X5C) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
// The X509 certificate will be available using the template variable // The X509 certificate will be available using the template variable
// AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be // AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be
// used to get all the domains. // used to get all the domains.
data.SetAuthorizationCertificate(claims.chains[0][0]) x5cLeaf := claims.chains[0][0]
data.SetAuthorizationCertificate(x5cLeaf)
templateOptions, err := TemplateOptions(p.Options, data) templateOptions, err := TemplateOptions(p.Options, data)
if err != nil { if err != nil {
@ -235,10 +237,10 @@ func (p *X5C) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
self, self,
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeX5C, p.Name, "").WithControllerOptions(p.ctl), newProvisionerExtensionOption(TypeX5C, p.Name, ""),
profileLimitDuration{ profileLimitDuration{
p.ctl.Claimer.DefaultTLSCertDuration(), p.ctl.Claimer.DefaultTLSCertDuration(),
claims.chains[0][0].NotBefore, claims.chains[0][0].NotAfter, x5cLeaf.NotBefore, x5cLeaf.NotAfter,
}, },
// validators // validators
commonNameValidator(claims.Subject), commonNameValidator(claims.Subject),
@ -246,7 +248,12 @@ func (p *X5C) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509), p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithX5CCertificate(x5cLeaf),
webhook.WithAuthorizationPrincipal(x5cLeaf.Subject.CommonName),
),
}, nil }, nil
} }
@ -305,7 +312,8 @@ func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e
// The X509 certificate will be available using the template variable // The X509 certificate will be available using the template variable
// AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be // AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be
// used to get all the domains. // used to get all the domains.
data.SetAuthorizationCertificate(claims.chains[0][0]) x5cLeaf := claims.chains[0][0]
data.SetAuthorizationCertificate(x5cLeaf)
templateOptions, err := TemplateSSHOptions(p.Options, data) templateOptions, err := TemplateSSHOptions(p.Options, data)
if err != nil { if err != nil {
@ -325,7 +333,7 @@ func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e
return append(signOptions, return append(signOptions,
p, 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, x5cLeaf.NotAfter},
// Validate public key. // Validate public key.
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.
@ -335,6 +343,11 @@ func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e
// Ensure that all principal names are allowed // Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()), newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()),
// Call webhooks // Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH), p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithX5CCertificate(x5cLeaf),
webhook.WithAuthorizationPrincipal(x5cLeaf.Subject.CommonName),
),
), nil ), nil
} }

View file

@ -12,6 +12,7 @@ import (
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/randutil" "go.step.sm/crypto/randutil"
"go.step.sm/linkedca"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/api/render"
@ -497,6 +498,8 @@ func TestX5C_AuthorizeSign(t *testing.T) {
assert.Equals(t, nil, v.policyEngine) assert.Equals(t, nil, v.policyEngine)
case *WebhookController: case *WebhookController:
assert.Len(t, 0, v.webhooks) assert.Len(t, 0, v.webhooks)
assert.Equals(t, linkedca.Webhook_X509, v.certType)
assert.Len(t, 2, v.options)
default: default:
assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v))
} }
@ -801,6 +804,8 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
case *sshDefaultPublicKeyValidator, *sshCertDefaultValidator, sshCertificateOptionsFunc: case *sshDefaultPublicKeyValidator, *sshCertDefaultValidator, sshCertificateOptionsFunc:
case *WebhookController: case *WebhookController:
assert.Len(t, 0, v.webhooks) assert.Len(t, 0, v.webhooks)
assert.Equals(t, linkedca.Webhook_SSH, v.certType)
assert.Len(t, 2, v.options)
default: default:
assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v))
} }

View file

@ -648,7 +648,6 @@ func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) {
pc := &provisioner.Claims{ pc := &provisioner.Claims{
DisableRenewal: &c.DisableRenewal, DisableRenewal: &c.DisableRenewal,
AllowRenewalAfterExpiry: &c.AllowRenewalAfterExpiry, AllowRenewalAfterExpiry: &c.AllowRenewalAfterExpiry,
DisableSmallstepExtensions: &c.DisableSmallstepExtensions,
} }
var err error var err error
@ -687,7 +686,6 @@ func claimsToLinkedca(c *provisioner.Claims) *linkedca.Claims {
disableRenewal := config.DefaultDisableRenewal disableRenewal := config.DefaultDisableRenewal
allowRenewalAfterExpiry := config.DefaultAllowRenewalAfterExpiry allowRenewalAfterExpiry := config.DefaultAllowRenewalAfterExpiry
disableSmallstepExtensions := config.DefaultDisableSmallstepExtensions
if c.DisableRenewal != nil { if c.DisableRenewal != nil {
disableRenewal = *c.DisableRenewal disableRenewal = *c.DisableRenewal
@ -695,14 +693,10 @@ func claimsToLinkedca(c *provisioner.Claims) *linkedca.Claims {
if c.AllowRenewalAfterExpiry != nil { if c.AllowRenewalAfterExpiry != nil {
allowRenewalAfterExpiry = *c.AllowRenewalAfterExpiry allowRenewalAfterExpiry = *c.AllowRenewalAfterExpiry
} }
if c.DisableSmallstepExtensions != nil {
disableSmallstepExtensions = *c.DisableSmallstepExtensions
}
lc := &linkedca.Claims{ lc := &linkedca.Claims{
DisableRenewal: disableRenewal, DisableRenewal: disableRenewal,
AllowRenewalAfterExpiry: allowRenewalAfterExpiry, AllowRenewalAfterExpiry: allowRenewalAfterExpiry,
DisableSmallstepExtensions: disableSmallstepExtensions,
} }
if c.DefaultTLSDur != nil || c.MinTLSDur != nil || c.MaxTLSDur != nil { if c.DefaultTLSDur != nil || c.MinTLSDur != nil || c.MaxTLSDur != nil {

10
go.mod
View file

@ -36,14 +36,14 @@ require (
golang.org/x/crypto v0.11.0 golang.org/x/crypto v0.11.0
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0
golang.org/x/net v0.12.0 golang.org/x/net v0.12.0
google.golang.org/api v0.131.0 google.golang.org/api v0.132.0
google.golang.org/grpc v1.56.2 google.golang.org/grpc v1.56.2
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.31.0
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
) )
require ( require (
cloud.google.com/go v0.110.2 // indirect cloud.google.com/go v0.110.4 // indirect
cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.0 // indirect cloud.google.com/go/iam v1.1.0 // indirect
@ -134,9 +134,9 @@ require (
golang.org/x/text v0.11.0 // indirect golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.1.0 // indirect golang.org/x/time v0.1.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

20
go.sum
View file

@ -31,8 +31,8 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk=
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -1488,8 +1488,8 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.131.0 h1:AcgWS2edQ4chVEt/SxgDKubVu/9/idCJy00tBGuGB4M= google.golang.org/api v0.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc=
google.golang.org/api v0.131.0/go.mod h1:7vtkbKv2REjJbxmHSkBTBQ5LUGvPdAqjjvt84XAfhpA= google.golang.org/api v0.132.0/go.mod h1:AeTBC6GpJnJSRJjktDcPX0QwtS8pGYZOV6MSuSCusw0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1567,12 +1567,12 @@ google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKr
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk= google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

View file

@ -23,7 +23,7 @@ inject:
authority: authority:
enableAdmin: false enableAdmin: false
provisioners: provisioners:
- {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false,"disableSmallstepExtensions":false},"options":{"x509":{},"ssh":{}}} - {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false},"options":{"x509":{},"ssh":{}}}
- {"type":"ACME","name":"acme"} - {"type":"ACME","name":"acme"}
- {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}} - {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}}
tls: tls:

View file

@ -23,7 +23,7 @@ inject:
authority: authority:
enableAdmin: false enableAdmin: false
provisioners: provisioners:
- {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false,"disableSmallstepExtensions":false},"options":{"x509":{},"ssh":{}}} - {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false},"options":{"x509":{},"ssh":{}}}
- {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}} - {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}}
tls: tls:
cipherSuites: cipherSuites:

View file

@ -43,8 +43,23 @@ var (
"acme_external_account_keyID_reference_index", "acme_external_account_keyID_reference_index",
"acme_external_account_keyID_provisionerID_index", "acme_external_account_keyID_provisionerID_index",
} }
adminTables = []string{
"admins",
"provisioners",
"authority_policies",
}
) )
type DB interface {
CreateTable([]byte) error
Set(bucket, key, value []byte) error
}
type dryRunDB struct{}
func (*dryRunDB) CreateTable([]byte) error { return nil }
func (*dryRunDB) Set(bucket, key, value []byte) error { return nil }
func usage(fs *flag.FlagSet) { func usage(fs *flag.FlagSet) {
name := filepath.Base(os.Args[0]) name := filepath.Base(os.Args[0])
fmt.Fprintf(os.Stderr, "%s is a tool to migrate data from BadgerDB to MySQL or PostgreSQL.\n", name) fmt.Fprintf(os.Stderr, "%s is a tool to migrate data from BadgerDB to MySQL or PostgreSQL.\n", name)
@ -52,14 +67,15 @@ func usage(fs *flag.FlagSet) {
fmt.Fprintf(os.Stderr, " %s [-v1|-v2] -dir=<path> [-value-dir=<path>] -type=type -database=<source>\n", name) fmt.Fprintf(os.Stderr, " %s [-v1|-v2] -dir=<path> [-value-dir=<path>] -type=type -database=<source>\n", name)
fmt.Fprintln(os.Stderr, "\nExamples:") fmt.Fprintln(os.Stderr, "\nExamples:")
fmt.Fprintf(os.Stderr, " %s -v1 -dir /var/lib/step-ca/db -type=mysql -database \"user@unix/step_ca\"\n", name) fmt.Fprintf(os.Stderr, " %s -v1 -dir /var/lib/step-ca/db -type=mysql -database \"user@unix/step_ca\"\n", name)
fmt.Fprintf(os.Stderr, " %s -v2 -dir /var/lib/step-ca/db -type=mysql -database \"user:password@tcp(localhost:3306)/step_ca\"\n", name) fmt.Fprintf(os.Stderr, " %s -v1 -dir /var/lib/step-ca/db -type=mysql -database \"user:password@tcp(localhost:3306)/step_ca\"\n", name)
fmt.Fprintf(os.Stderr, " %s -v2 -dir /var/lib/step-ca/db -type=postgresql -database \"user=postgres dbname=step_ca\"\n", name) fmt.Fprintf(os.Stderr, " %s -v2 -dir /var/lib/step-ca/db -type=postgresql -database \"user=postgres dbname=step_ca\"\n", name)
fmt.Fprintf(os.Stderr, " %s -v2 -dir /var/lib/step-ca/db -dry-run\"\n", name)
fmt.Fprintln(os.Stderr, "\nOptions:") fmt.Fprintln(os.Stderr, "\nOptions:")
fs.PrintDefaults() fs.PrintDefaults()
} }
func main() { func main() {
var v1, v2 bool var v1, v2, dryRun bool
var dir, valueDir string var dir, valueDir string
var typ, database string var typ, database string
var key string var key string
@ -67,12 +83,13 @@ func main() {
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
fs.BoolVar(&v1, "v1", false, "use badger v1 as the source database") fs.BoolVar(&v1, "v1", false, "use badger v1 as the source database")
fs.BoolVar(&v2, "v2", true, "use badger v2 as the source database") fs.BoolVar(&v2, "v2", false, "use badger v2 as the source database")
fs.StringVar(&dir, "dir", "", "badger database directory") fs.StringVar(&dir, "dir", "", "badger database directory")
fs.StringVar(&valueDir, "value-dir", "", "badger database value directory") fs.StringVar(&valueDir, "value-dir", "", "badger database value directory")
fs.StringVar(&typ, "type", "", "the destination database type to use") fs.StringVar(&typ, "type", "", "the destination database type to use")
fs.StringVar(&database, "database", "", "the destination driver-specific data source name") fs.StringVar(&database, "database", "", "the destination driver-specific data source name")
fs.StringVar(&key, "key", "", "the key used to resume the migration") fs.StringVar(&key, "key", "", "the key used to resume the migration")
fs.BoolVar(&dryRun, "dry-run", false, "runs the migration scripts without writing anything")
fs.Usage = func() { usage(fs) } fs.Usage = func() { usage(fs) }
fs.Parse(os.Args[1:]) fs.Parse(os.Args[1:])
@ -81,9 +98,9 @@ func main() {
fatal("flag -v1 or -v2 are required") fatal("flag -v1 or -v2 are required")
case dir == "": case dir == "":
fatal("flag -dir is required") fatal("flag -dir is required")
case typ != "postgresql" && typ != "mysql": case typ != "postgresql" && typ != "mysql" && !dryRun:
fatal(`flag -type must be "postgresql" or "mysql"`) fatal(`flag -type must be "postgresql" or "mysql"`)
case database == "": case database == "" && !dryRun:
fatal("flag --database required") fatal("flag --database required")
} }
@ -110,13 +127,19 @@ func main() {
} }
} }
db, err := nosql.New(typ, database) var db DB
if dryRun {
db = &dryRunDB{}
} else {
db, err = nosql.New(typ, database)
if err != nil { if err != nil {
fatal("error opening %s database: %v", typ, err) fatal("error opening %s database: %v", typ, err)
} }
}
allTables := append([]string{}, authorityTables...) allTables := append([]string{}, authorityTables...)
allTables = append(allTables, acmeTables...) allTables = append(allTables, acmeTables...)
allTables = append(allTables, adminTables...)
// Convert prefix names to badger key prefixes // Convert prefix names to badger key prefixes
badgerKeys := make([][]byte, len(allTables)) badgerKeys := make([][]byte, len(allTables))

View file

@ -68,6 +68,13 @@ func WithAttestationData(data *AttestationData) RequestBodyOption {
} }
} }
func WithAuthorizationPrincipal(p string) RequestBodyOption {
return func(rb *RequestBody) error {
rb.AuthorizationPrincipal = p
return nil
}
}
func WithSSHCertificateRequest(cr sshutil.CertificateRequest) RequestBodyOption { func WithSSHCertificateRequest(cr sshutil.CertificateRequest) RequestBodyOption {
return func(rb *RequestBody) error { return func(rb *RequestBody) error {
rb.SSHCertificateRequest = &SSHCertificateRequest{ rb.SSHCertificateRequest = &SSHCertificateRequest{
@ -95,3 +102,23 @@ func WithSSHCertificate(cert *sshutil.Certificate, certTpl *ssh.Certificate) Req
return nil return nil
} }
} }
func WithX5CCertificate(leaf *x509.Certificate) RequestBodyOption {
return func(rb *RequestBody) error {
rb.X5CCertificate = &X5CCertificate{
Raw: leaf.Raw,
PublicKeyAlgorithm: leaf.PublicKeyAlgorithm.String(),
NotBefore: leaf.NotBefore,
NotAfter: leaf.NotAfter,
}
if leaf.PublicKey != nil {
key, err := x509.MarshalPKIXPublicKey(leaf.PublicKey)
if err != nil {
return err
}
rb.X5CCertificate.PublicKey = key
}
return nil
}
}

View file

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/sshutil" "go.step.sm/crypto/sshutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -16,6 +17,15 @@ func TestNewRequestBody(t *testing.T) {
t1 := time.Now() t1 := time.Now()
t2 := t1.Add(time.Hour) t2 := t1.Add(time.Hour)
key, err := keyutil.GenerateDefaultSigner()
if err != nil {
t.Fatal(err)
}
keyBytes, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
t.Fatal(err)
}
type test struct { type test struct {
options []RequestBodyOption options []RequestBodyOption
want *RequestBody want *RequestBody
@ -103,6 +113,40 @@ func TestNewRequestBody(t *testing.T) {
}, },
wantErr: false, wantErr: false,
}, },
"X5C Certificate": {
options: []RequestBodyOption{
WithX5CCertificate(&x509.Certificate{
Raw: []byte("some raw data"),
NotBefore: t1,
NotAfter: t2,
PublicKeyAlgorithm: x509.ECDSA,
PublicKey: key.Public(),
}),
},
want: &RequestBody{
X5CCertificate: &X5CCertificate{
Raw: []byte("some raw data"),
PublicKeyAlgorithm: "ECDSA",
NotBefore: t1,
NotAfter: t2,
PublicKey: keyBytes,
},
},
wantErr: false,
},
"fail/X5C Certificate": {
options: []RequestBodyOption{
WithX5CCertificate(&x509.Certificate{
Raw: []byte("some raw data"),
NotBefore: t1,
NotAfter: t2,
PublicKeyAlgorithm: x509.ECDSA,
PublicKey: []byte("fail"),
}),
},
want: nil,
wantErr: true,
},
} }
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {

View file

@ -56,6 +56,17 @@ type AttestationData struct {
PermanentIdentifier string `json:"permanentIdentifier"` PermanentIdentifier string `json:"permanentIdentifier"`
} }
// X5CCertificate is the authorization certificate sent to webhook servers for
// enriching or authorizing webhooks when signing X509 or SSH certificates using
// the X5C provisioner.
type X5CCertificate struct {
Raw []byte `json:"raw"`
PublicKey []byte `json:"publicKey"`
PublicKeyAlgorithm string `json:"publicKeyAlgorithm"`
NotBefore time.Time `json:"notBefore"`
NotAfter time.Time `json:"notAfter"`
}
// RequestBody is the body sent to webhook servers. // RequestBody is the body sent to webhook servers.
type RequestBody struct { type RequestBody struct {
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
@ -71,4 +82,8 @@ type RequestBody struct {
// Only set for SCEP challenge validation requests // Only set for SCEP challenge validation requests
SCEPChallenge string `json:"scepChallenge,omitempty"` SCEPChallenge string `json:"scepChallenge,omitempty"`
SCEPTransactionID string `json:"scepTransactionID,omitempty"` SCEPTransactionID string `json:"scepTransactionID,omitempty"`
// Only set for X5C provisioners
X5CCertificate *X5CCertificate `json:"x5cCertificate,omitempty"`
// Set for X5C, AWS, GCP, and Azure provisioners
AuthorizationPrincipal string `json:"authorizationPrincipal,omitempty"`
} }