Compare commits

...
Sign in to create a new pull request.

26 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
Mariano Cano
d604a900ed
Merge pull request #1482 from smallstep/fix-reload-tests
Wait for Accept in TestBootstrapClientServerRotation
2023-07-19 15:03:52 -07:00
Mariano Cano
0c3a1aea38
Wait for Accept in TestBootstrapClientServerRotation
The TestBootstrapClientServerRotation often fails because the reload
returns once the Server loop gets the new listener, but the server
hasn't really started yet. This commit makes the test pass, adding a
small sleep after the reload.

A proper fix might require a wrapper over the listener and an ACK
callback on a sync.Once on a custom Accept.
2023-07-19 14:56:09 -07:00
Mariano Cano
cbc46d11e5
Merge pull request #1477 from smallstep/badger-migration
Add tool to migrate data from badger to mysql or postgresql
2023-07-18 14:36:06 -07:00
Mariano Cano
1755c8d60f
Fix typo in comment 2023-07-18 14:21:55 -07:00
Mariano Cano
f7da9a6f30
Allow to resume badger migration using a given key 2023-07-18 13:11:19 -07:00
Mariano Cano
f7c33d0878
Fix typos in badger migration script 2023-07-18 10:27:36 -07:00
Mariano Cano
7bca0c2349
Add tool to migrate data from badger to mysql or postgresql 2023-07-17 17:40:43 -07:00
Mariano Cano
90bac46a00
Merge pull request #1476 from smallstep/fix-1463
Upgrade go.step.sm/crypto with yubikey fix
2023-07-17 09:53:58 -07:00
Mariano Cano
9edf43b188
Upgrade go.step.sm/crypto with yubikey fix
This commit upgrades the go.step.sm/crypto with a version that includes
a mutex on YubiKey sign and decrypt operations.

Fixes #1463
2023-07-17 09:45:40 -07:00
github-actions[bot]
f998b19bb3
Merge pull request #1474 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.32.3
Bump go.step.sm/crypto from 0.32.2 to 0.32.3
2023-07-17 18:35:23 +02:00
github-actions[bot]
41ff437a6b
Merge pull request #1475 from smallstep/dependabot/go_modules/google.golang.org/api-0.131.0
Bump google.golang.org/api from 0.130.0 to 0.131.0
2023-07-17 18:25:38 +02:00
dependabot[bot]
d1607e460d
Bump google.golang.org/api from 0.130.0 to 0.131.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.130.0 to 0.131.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.130.0...v0.131.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-17 15:19:47 +00:00
dependabot[bot]
b9a3031b84
Bump go.step.sm/crypto from 0.32.2 to 0.32.3
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.32.2 to 0.32.3.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.32.2...v0.32.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 15:19:33 +00:00
17 changed files with 691 additions and 63 deletions

View file

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

View file

@ -20,6 +20,7 @@ import (
"go.step.sm/linkedca"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
)
// azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens.
@ -403,7 +404,11 @@ func (p *Azure) AuthorizeSign(_ context.Context, token string) ([]SignOption, er
defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509),
p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithAuthorizationPrincipal(identityObjectID),
),
), 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())
}
_, name, _, _, _, err := p.authorizeToken(token)
_, name, _, _, identityObjectID, err := p.authorizeToken(token)
if err != nil {
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
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil),
// Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH),
p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithAuthorizationPrincipal(identityObjectID),
),
), nil
}

View file

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

View file

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

View file

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

View file

@ -30,6 +30,7 @@ type WebhookController struct {
client *http.Client
webhooks []*Webhook
certType linkedca.Webhook_CertType
options []webhook.RequestBodyOption
TemplateData WebhookSetter
}
@ -39,6 +40,14 @@ func (wc *WebhookController) Enrich(req *webhook.RequestBody) error {
if wc == 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 {
if wh.Kind != linkedca.Webhook_ENRICHING.String() {
continue
@ -63,6 +72,14 @@ func (wc *WebhookController) Authorize(req *webhook.RequestBody) error {
if wc == 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 {
if wh.Kind != linkedca.Webhook_AUTHORIZING.String() {
continue

View file

@ -4,6 +4,7 @@ import (
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
@ -16,6 +17,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/webhook"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"go.step.sm/linkedca"
)
@ -96,12 +98,18 @@ func TestWebhookController_isCertTypeOK(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 {
ctl *WebhookController
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
expectTemplateData any
assertRequest func(t *testing.T, req *webhook.RequestBody)
}
tests := map[string]test{
"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": {
ctl: &WebhookController{
client: http.DefaultClient,
@ -181,6 +212,20 @@ func TestWebhookController_Enrich(t *testing.T) {
expectErr: true,
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 {
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)
}
assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData)
if test.assertRequest != nil {
test.assertRequest(t, test.req)
}
})
}
}
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 {
ctl *WebhookController
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
ctl *WebhookController
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
assertRequest func(t *testing.T, req *webhook.RequestBody)
}
tests := map[string]test{
"ok/no enriching webhooks": {
@ -240,6 +294,27 @@ func TestWebhookController_Authorize(t *testing.T) {
responses: []*webhook.ResponseBody{{Allow: 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": {
ctl: &WebhookController{
client: http.DefaultClient,
@ -249,6 +324,18 @@ func TestWebhookController_Authorize(t *testing.T) {
responses: []*webhook.ResponseBody{{Allow: false}},
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 {
t.Run(name, func(t *testing.T) {
@ -267,6 +354,9 @@ func TestWebhookController_Authorize(t *testing.T) {
if (err != nil) != 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"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
)
// 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
// AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be
// 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)
if err != nil {
@ -238,7 +240,7 @@ func (p *X5C) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
newProvisionerExtensionOption(TypeX5C, p.Name, ""),
profileLimitDuration{
p.ctl.Claimer.DefaultTLSCertDuration(),
claims.chains[0][0].NotBefore, claims.chains[0][0].NotAfter,
x5cLeaf.NotBefore, x5cLeaf.NotAfter,
},
// validators
commonNameValidator(claims.Subject),
@ -246,7 +248,12 @@ func (p *X5C) AuthorizeSign(_ context.Context, token string) ([]SignOption, erro
defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
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
}
@ -305,7 +312,8 @@ func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e
// The X509 certificate will be available using the template variable
// AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be
// 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)
if err != nil {
@ -325,7 +333,7 @@ func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e
return append(signOptions,
p,
// 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.
&sshDefaultPublicKeyValidator{},
// 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
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()),
// 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
}

View file

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

View file

@ -606,7 +606,13 @@ func doReload(ca *CA) error {
}
// Use same address in new server
newCA.srv.Addr = ca.srv.Addr
return ca.srv.Reload(newCA.srv)
if err := ca.srv.Reload(newCA.srv); err != nil {
return err
}
// Wait a few ms until the http server calls listener.Accept()
time.Sleep(100 * time.Millisecond)
return nil
}
func TestBootstrapListener(t *testing.T) {

26
go.mod
View file

@ -6,6 +6,8 @@ require (
cloud.google.com/go/longrunning v0.5.1
cloud.google.com/go/security v1.15.1
github.com/Masterminds/sprig/v3 v3.2.3
github.com/dgraph-io/badger v1.6.2
github.com/dgraph-io/badger/v2 v2.2007.4
github.com/fxamacker/cbor/v2 v2.4.0
github.com/go-chi/chi v4.1.2+incompatible
github.com/golang/mock v1.6.0
@ -29,23 +31,23 @@ require (
github.com/urfave/cli v1.22.14
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
go.step.sm/cli-utils v0.7.6
go.step.sm/crypto v0.32.2
go.step.sm/linkedca v0.19.1
go.step.sm/crypto v0.32.4
go.step.sm/linkedca v0.20.0
golang.org/x/crypto v0.11.0
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0
golang.org/x/net v0.12.0
google.golang.org/api v0.130.0
google.golang.org/api v0.132.0
google.golang.org/grpc v1.56.2
google.golang.org/protobuf v1.31.0
gopkg.in/square/go-jose.v2 v2.6.0
)
require (
cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute v1.19.3 // indirect
cloud.google.com/go v0.110.4 // indirect
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.0 // indirect
cloud.google.com/go/kms v1.12.0 // indirect
cloud.google.com/go/kms v1.13.0 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 // indirect
@ -57,15 +59,13 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/aws/aws-sdk-go v1.44.281 // indirect
github.com/aws/aws-sdk-go v1.44.295 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
@ -129,14 +129,14 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/oauth2 v0.9.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.1.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc 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-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

44
go.sum
View file

@ -31,16 +31,16 @@ 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.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.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk=
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.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.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@ -48,8 +48,8 @@ cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94=
cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk=
cloud.google.com/go/kms v1.12.0 h1:IEYV44WsGc6yVO1PlvnRlYzsHM2ImpB598Cglh/3uGw=
cloud.google.com/go/kms v1.12.0/go.mod h1:syfpIBSOqQ/ZqK48RLPkwUhFhvbsA1SyGAq/vPohd20=
cloud.google.com/go/kms v1.13.0 h1:s+sRhcowXwuLsa2Z8g3Tmh5l0HWNBf//HogCgiuDs/0=
cloud.google.com/go/kms v1.13.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM=
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
cloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE=
@ -165,8 +165,8 @@ github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.281 h1:z/ptheJvINaIAsKXthxONM+toTKw2pxyk700Hfm6yUw=
github.com/aws/aws-sdk-go v1.44.281/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.295 h1:SGjU1+MqttXfRiWHD6WU0DRhaanJgAFY+xIhEaugV8Y=
github.com/aws/aws-sdk-go v1.44.295/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
@ -1063,10 +1063,10 @@ go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16g
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.step.sm/cli-utils v0.7.6 h1:YkpLVrepmy2c5+eaz/wduiGxlgrRx3YdAStE37if25g=
go.step.sm/cli-utils v0.7.6/go.mod h1:j+FxFZ2gbWkAJl0eded/rksuxmNqWpmyxbkXcukGJaY=
go.step.sm/crypto v0.32.2 h1:EhJpFRNgU3RaNEO3WZ62Kn2gF9NWNglNG4DvSPeuiTs=
go.step.sm/crypto v0.32.2/go.mod h1:JwarCq+Sn6N8IbRSKfSJfjUNKfO8c4N1mcNxYXuxXzc=
go.step.sm/linkedca v0.19.1 h1:uY0ByT/uB3FCQ8zIo9mU7MWG7HKf5sDXNEBeN94MuP8=
go.step.sm/linkedca v0.19.1/go.mod h1:vPV2ad3LFQJmV7XWt87VlnJSs6UOqgsbVGVWe3veEmI=
go.step.sm/crypto v0.32.4 h1:jSr5sB6vJCciqFB3BFKgK5ykRtuzKqdl4j9+CYkS8Hc=
go.step.sm/crypto v0.32.4/go.mod h1:A009Gtqx80nTz/9DreRMflMGgaSWTuhK8En6XycK9yA=
go.step.sm/linkedca v0.20.0 h1:bH41rvyDm3nSSJ5xgGsKUZOpzJcq5x2zacMIeqtq9oI=
go.step.sm/linkedca v0.20.0/go.mod h1:eybHw6ZTpuFmkUQnTBRWM2SPIGaP0VbYeo1bupfPT70=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -1233,8 +1233,8 @@ golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -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.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.130.0 h1:A50ujooa1h9iizvfzA4rrJr2B7uRmWexwbekQ2+5FPQ=
google.golang.org/api v0.130.0/go.mod h1:J/LCJMYSDFvAVREGCbrESb53n4++NMBDetSHGL5I5RY=
google.golang.org/api v0.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc=
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.2.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-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-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
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.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

View file

@ -2,3 +2,7 @@
Please note that `install-step-ra.sh` is referenced on the `files.smallstep.com` S3 website bucket as a redirect to `raw.githubusercontent.com`. If you move it, please update the S3 redirect.
## badger-migration
badger-migration is a tool that allows migrating data data from BadgerDB (v1 or
v2) to MySQL or PostgreSQL.

View file

@ -0,0 +1,352 @@
package main
import (
"bytes"
"encoding/base64"
"encoding/binary"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
badgerv1 "github.com/dgraph-io/badger"
badgerv2 "github.com/dgraph-io/badger/v2"
"github.com/smallstep/nosql"
)
var (
authorityTables = []string{
"x509_certs",
"x509_certs_data",
"revoked_x509_certs",
"x509_crl",
"revoked_ssh_certs",
"used_ott",
"ssh_certs",
"ssh_hosts",
"ssh_users",
"ssh_host_principals",
}
acmeTables = []string{
"acme_accounts",
"acme_keyID_accountID_index",
"acme_authzs",
"acme_challenges",
"nonces",
"acme_orders",
"acme_account_orders_index",
"acme_certs",
"acme_serial_certs_index",
"acme_external_account_keys",
"acme_external_account_keyID_reference_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) {
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.Fprintln(os.Stderr, "\nUsage:")
fmt.Fprintf(os.Stderr, " %s [-v1|-v2] -dir=<path> [-value-dir=<path>] -type=type -database=<source>\n", name)
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: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 -dry-run\"\n", name)
fmt.Fprintln(os.Stderr, "\nOptions:")
fs.PrintDefaults()
}
func main() {
var v1, v2, dryRun bool
var dir, valueDir string
var typ, database string
var key string
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
fs.BoolVar(&v1, "v1", false, "use badger v1 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(&valueDir, "value-dir", "", "badger database value directory")
fs.StringVar(&typ, "type", "", "the destination database type to use")
fs.StringVar(&database, "database", "", "the destination driver-specific data source name")
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.Parse(os.Args[1:])
switch {
case v1 == v2:
fatal("flag -v1 or -v2 are required")
case dir == "":
fatal("flag -dir is required")
case typ != "postgresql" && typ != "mysql" && !dryRun:
fatal(`flag -type must be "postgresql" or "mysql"`)
case database == "" && !dryRun:
fatal("flag --database required")
}
var (
err error
v1DB *badgerv1.DB
v2DB *badgerv2.DB
lastKey []byte
)
if key != "" {
if lastKey, err = base64.StdEncoding.DecodeString(key); err != nil {
fatal("error decoding key: %v", err)
}
}
if v1 {
if v1DB, err = badgerV1Open(dir, valueDir); err != nil {
fatal("error opening badger v1 database: %v", err)
}
} else {
if v2DB, err = badgerV2Open(dir, valueDir); err != nil {
fatal("error opening badger v2 database: %v", err)
}
}
var db DB
if dryRun {
db = &dryRunDB{}
} else {
db, err = nosql.New(typ, database)
if err != nil {
fatal("error opening %s database: %v", typ, err)
}
}
allTables := append([]string{}, authorityTables...)
allTables = append(allTables, acmeTables...)
allTables = append(allTables, adminTables...)
// Convert prefix names to badger key prefixes
badgerKeys := make([][]byte, len(allTables))
for i, name := range allTables {
badgerKeys[i], err = badgerEncode([]byte(name))
if err != nil {
fatal("error encoding table %s: %v", name, err)
}
}
for i, prefix := range badgerKeys {
table := allTables[i]
// With a key flag, resume from that table and prefix
if lastKey != nil {
bucket, _ := parseBadgerEncode(lastKey)
if table != string(bucket) {
fmt.Printf("skipping table %s\n", table)
continue
}
// Continue with a new prefix
prefix = lastKey
lastKey = nil
}
var n int64
fmt.Printf("migrating %s ...", table)
if err := db.CreateTable([]byte(table)); err != nil {
fatal("error creating table %s: %v", table, err)
}
if v1 {
if badgerKey, err := badgerV1Iterate(v1DB, prefix, func(bucket, key, value []byte) error {
n++
return db.Set(bucket, key, value)
}); err != nil {
fmt.Println()
fatal("error inserting into %s: %v\nLast key: %s", table, err, base64.StdEncoding.EncodeToString(badgerKey))
}
} else {
if badgerKey, err := badgerV2Iterate(v2DB, prefix, func(bucket, key, value []byte) error {
n++
return db.Set(bucket, key, value)
}); err != nil {
fmt.Println()
fatal("error inserting into %s: %v\nLast key: %s", table, err, base64.StdEncoding.EncodeToString(badgerKey))
}
}
fmt.Printf(" %d rows\n", n)
}
}
func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, format, args...)
fmt.Fprintln(os.Stderr)
os.Exit(1)
}
func badgerV1Open(dir, valueDir string) (*badgerv1.DB, error) {
opts := badgerv1.DefaultOptions(dir)
if valueDir != "" {
opts.ValueDir = valueDir
}
return badgerv1.Open(opts)
}
func badgerV2Open(dir, valueDir string) (*badgerv2.DB, error) {
opts := badgerv2.DefaultOptions(dir)
if valueDir != "" {
opts.ValueDir = valueDir
}
return badgerv2.Open(opts)
}
type Iterator interface {
Seek([]byte)
ValidForPrefix([]byte) bool
Next()
}
type Item interface {
KeyCopy([]byte) []byte
ValueCopy([]byte) ([]byte, error)
}
func badgerV1Iterate(db *badgerv1.DB, prefix []byte, fn func(bucket, key, value []byte) error) (badgerKey []byte, err error) {
err = db.View(func(txn *badgerv1.Txn) error {
it := txn.NewIterator(badgerv1.DefaultIteratorOptions)
defer it.Close()
badgerKey, err = badgerIterate(it, prefix, fn)
return err
})
return
}
func badgerV2Iterate(db *badgerv2.DB, prefix []byte, fn func(bucket, key, value []byte) error) (badgerKey []byte, err error) {
err = db.View(func(txn *badgerv2.Txn) error {
it := txn.NewIterator(badgerv2.DefaultIteratorOptions)
defer it.Close()
badgerKey, err = badgerIterate(it, prefix, fn)
return err
})
return
}
func badgerIterate(it Iterator, prefix []byte, fn func(bucket, key, value []byte) error) ([]byte, error) {
var badgerKey []byte
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
var item Item
switch itt := it.(type) {
case *badgerv1.Iterator:
item = itt.Item()
case *badgerv2.Iterator:
item = itt.Item()
default:
return badgerKey, fmt.Errorf("unexpected iterator type %T", it)
}
badgerKey = item.KeyCopy(nil)
if isBadgerTable(badgerKey) {
continue
}
bucket, key, err := fromBadgerKey(badgerKey)
if err != nil {
return badgerKey, fmt.Errorf("error converting from badger key %s", badgerKey)
}
value, err := item.ValueCopy(nil)
if err != nil {
return badgerKey, fmt.Errorf("error retrieving contents from database value: %w", err)
}
if err := fn(bucket, key, value); err != nil {
return badgerKey, fmt.Errorf("error exporting %s[%s]=%x", bucket, key, value)
}
}
return badgerKey, nil
}
// badgerEncode encodes a byte slice into a section of a BadgerKey. See
// documentation for toBadgerKey.
func badgerEncode(val []byte) ([]byte, error) {
l := len(val)
switch {
case l == 0:
return nil, errors.New("input cannot be empty")
case l > 65535:
return nil, errors.New("length of input cannot be greater than 65535")
default:
lb := new(bytes.Buffer)
if err := binary.Write(lb, binary.LittleEndian, uint16(l)); err != nil {
return nil, fmt.Errorf("error doing binary Write: %w", err)
}
return append(lb.Bytes(), val...), nil
}
}
// parseBadgerEncode decodes the badger key and returns the bucket and the rest.
func parseBadgerEncode(bk []byte) (value, rest []byte) {
var (
keyLen uint16
start = uint16(2)
length = uint16(len(bk))
)
if uint16(len(bk)) < start {
return nil, bk
}
// First 2 bytes stores the length of the value.
if err := binary.Read(bytes.NewReader(bk[:2]), binary.LittleEndian, &keyLen); err != nil {
return nil, bk
}
end := start + keyLen
switch {
case length < end:
return nil, bk
case length == end:
return bk[start:end], nil
default:
return bk[start:end], bk[end:]
}
}
// isBadgerTable returns True if the slice is a badgerTable token, false
// otherwise. badgerTable means that the slice contains only the [size|value] of
// one section of a badgerKey and no remainder. A badgerKey is [bucket|key],
// while a badgerTable is only the bucket section.
func isBadgerTable(bk []byte) bool {
if k, rest := parseBadgerEncode(bk); len(k) > 0 && len(rest) == 0 {
return true
}
return false
}
// fromBadgerKey returns the bucket and key encoded in a BadgerKey. See
// documentation for toBadgerKey.
func fromBadgerKey(bk []byte) ([]byte, []byte, error) {
bucket, rest := parseBadgerEncode(bk)
if len(bucket) == 0 || len(rest) == 0 {
return nil, nil, fmt.Errorf("invalid badger key: %v", bk)
}
key, rest2 := parseBadgerEncode(rest)
if len(key) == 0 || len(rest2) != 0 {
return nil, nil, fmt.Errorf("invalid badger key: %v", bk)
}
return bucket, key, nil
}

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 {
return func(rb *RequestBody) error {
rb.SSHCertificateRequest = &SSHCertificateRequest{
@ -95,3 +102,23 @@ func WithSSHCertificate(cert *sshutil.Certificate, certTpl *ssh.Certificate) Req
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"
"github.com/smallstep/assert"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/sshutil"
"go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh"
@ -16,6 +17,15 @@ func TestNewRequestBody(t *testing.T) {
t1 := time.Now()
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 {
options []RequestBodyOption
want *RequestBody
@ -103,6 +113,40 @@ func TestNewRequestBody(t *testing.T) {
},
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 {
t.Run(name, func(t *testing.T) {

View file

@ -56,6 +56,17 @@ type AttestationData struct {
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.
type RequestBody struct {
Timestamp time.Time `json:"timestamp"`
@ -71,4 +82,8 @@ type RequestBody struct {
// Only set for SCEP challenge validation requests
SCEPChallenge string `json:"scepChallenge,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"`
}