mirror of
https://github.com/outbackdingo/certificates.git
synced 2026-01-27 10:18:34 +00:00
Merge branch 'master' into herman/scep-provisioner-decrypter
This commit is contained in:
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -20,8 +20,8 @@ jobs:
|
||||
ci:
|
||||
uses: smallstep/workflows/.github/workflows/goCI.yml@main
|
||||
with:
|
||||
os-dependencies: "libpcsclite-dev"
|
||||
run-gitleaks: true
|
||||
only-latest-golang: false
|
||||
os-dependencies: 'libpcsclite-dev'
|
||||
run-codeql: true
|
||||
make-test: true # run `make test` instead of the default test workflow
|
||||
test-command: 'V=1 make test'
|
||||
secrets: inherit
|
||||
|
||||
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
@@ -55,38 +55,12 @@ jobs:
|
||||
prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }}
|
||||
|
||||
goreleaser:
|
||||
name: Upload Assets To Github w/ goreleaser
|
||||
runs-on: ubuntu-latest
|
||||
needs: create_release
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
check-latest: true
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@v2
|
||||
with:
|
||||
cosign-release: 'v1.13.1'
|
||||
- name: Get Release Date
|
||||
id: release_date
|
||||
run: |
|
||||
RELEASE_DATE=$(date +"%y-%m-%d")
|
||||
echo "RELEASE_DATE=${RELEASE_DATE}" >> ${GITHUB_ENV}
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: 'latest'
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GORELEASER_PAT }}
|
||||
RELEASE_DATE: ${{ env.RELEASE_DATE }}
|
||||
COSIGN_EXPERIMENTAL: 1
|
||||
uses: smallstep/workflows/.github/workflows/goreleaser.yml@main
|
||||
secrets: inherit
|
||||
|
||||
build_upload_docker:
|
||||
name: Build & Upload Docker Images
|
||||
|
||||
@@ -31,7 +31,7 @@ builds:
|
||||
- -w -X main.Version={{.Version}} -X main.BuildTime={{.Date}}
|
||||
|
||||
archives:
|
||||
-
|
||||
- &ARCHIVE
|
||||
# Can be used to change the archive formats for specific GOOSs.
|
||||
# Most common use case is to archive as zip on Windows.
|
||||
# Default is empty.
|
||||
@@ -45,6 +45,11 @@ archives:
|
||||
- README.md
|
||||
- LICENSE
|
||||
allow_different_binary_count: true
|
||||
-
|
||||
<< : *ARCHIVE
|
||||
id: unversioned
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
||||
|
||||
|
||||
nfpms:
|
||||
# Configure nFPM for .deb and .rpm releases
|
||||
@@ -56,7 +61,7 @@ nfpms:
|
||||
# List file contents: dpkg -c dist/step_...deb
|
||||
# Package metadata: dpkg --info dist/step_....deb
|
||||
#
|
||||
-
|
||||
- &NFPM
|
||||
builds:
|
||||
- step-ca
|
||||
package_name: step-ca
|
||||
@@ -76,6 +81,10 @@ nfpms:
|
||||
contents:
|
||||
- src: debian/copyright
|
||||
dst: /usr/share/doc/step-ca/copyright
|
||||
-
|
||||
<< : *NFPM
|
||||
id: unversioned
|
||||
file_name_template: "{{ .PackageName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
||||
|
||||
source:
|
||||
enabled: true
|
||||
@@ -190,39 +199,40 @@ release:
|
||||
# - glob: ./glob/**/to/**/file/**/*
|
||||
# - glob: ./glob/foo/to/bar/file/foobar/override_from_previous
|
||||
|
||||
scoop:
|
||||
# Template for the url which is determined by the given Token (github or gitlab)
|
||||
# Default for github is "https://github.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
# Default for gitlab is "https://gitlab.com/<repo_owner>/<repo_name>/uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}"
|
||||
# Default for gitea is "https://gitea.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
url_template: "http://github.com/smallstep/certificates/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
scoops:
|
||||
-
|
||||
ids: [ default ]
|
||||
# Template for the url which is determined by the given Token (github or gitlab)
|
||||
# Default for github is "https://github.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
# Default for gitlab is "https://gitlab.com/<repo_owner>/<repo_name>/uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}"
|
||||
# Default for gitea is "https://gitea.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
url_template: "http://github.com/smallstep/certificates/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
# Repository to push the app manifest to.
|
||||
bucket:
|
||||
owner: smallstep
|
||||
name: scoop-bucket
|
||||
|
||||
# Repository to push the app manifest to.
|
||||
bucket:
|
||||
owner: smallstep
|
||||
name: scoop-bucket
|
||||
# Git author used to commit to the repository.
|
||||
# Defaults are shown.
|
||||
commit_author:
|
||||
name: goreleaserbot
|
||||
email: goreleaser@smallstep.com
|
||||
|
||||
# Git author used to commit to the repository.
|
||||
# Defaults are shown.
|
||||
commit_author:
|
||||
name: goreleaserbot
|
||||
email: goreleaser@smallstep.com
|
||||
# The project name and current git tag are used in the format string.
|
||||
commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
|
||||
|
||||
# The project name and current git tag are used in the format string.
|
||||
commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
|
||||
# Your app's homepage.
|
||||
# Default is empty.
|
||||
homepage: "https://smallstep.com/docs/step-ca"
|
||||
|
||||
# Your app's homepage.
|
||||
# Default is empty.
|
||||
homepage: "https://smallstep.com/docs/step-ca"
|
||||
# Skip uploads for prerelease.
|
||||
skip_upload: auto
|
||||
|
||||
# Skip uploads for prerelease.
|
||||
skip_upload: auto
|
||||
# Your app's description.
|
||||
# Default is empty.
|
||||
description: "A private certificate authority (X.509 & SSH) & ACME server for secure automated certificate management, so you can use TLS everywhere & SSO for SSH."
|
||||
|
||||
# Your app's description.
|
||||
# Default is empty.
|
||||
description: "A private certificate authority (X.509 & SSH) & ACME server for secure automated certificate management, so you can use TLS everywhere & SSO for SSH."
|
||||
|
||||
# Your app's license
|
||||
# Default is empty.
|
||||
license: "Apache-2.0"
|
||||
# Your app's license
|
||||
# Default is empty.
|
||||
license: "Apache-2.0"
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improved authentication for ACME requests using kid and provisioner name
|
||||
(smallstep/certificates#1386).
|
||||
|
||||
|
||||
## [v0.24.2] - 2023-05-11
|
||||
|
||||
### Added
|
||||
|
||||
@@ -74,7 +74,7 @@ sudo yum install pcsc-lite-devel
|
||||
To build `step-ca`, clone this repository and run the following:
|
||||
|
||||
```shell
|
||||
make bootstrap && make build GOFLAGS=""
|
||||
make bootstrap && make build GO_ENVS="CGO_ENABLED=1"
|
||||
```
|
||||
|
||||
When the build is complete, you will find binaries in `bin/`.
|
||||
|
||||
26
Makefile
26
Makefile
@@ -61,7 +61,23 @@ endif
|
||||
|
||||
DATE := $(shell date -u '+%Y-%m-%d %H:%M UTC')
|
||||
LDFLAGS := -ldflags='-w -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"'
|
||||
GOFLAGS := CGO_ENABLED=0
|
||||
|
||||
# Always explicitly enable or disable cgo,
|
||||
# so that go doesn't silently fall back on
|
||||
# non-cgo when gcc is not found.
|
||||
ifeq (,$(findstring CGO_ENABLED,$(GO_ENVS)))
|
||||
ifneq ($(origin GOFLAGS),undefined)
|
||||
# This section is for backward compatibility with
|
||||
#
|
||||
# $ make build GOFLAGS=""
|
||||
#
|
||||
# which is how we recommended building step-ca with cgo support
|
||||
# until June 2023.
|
||||
GO_ENVS := $(GO_ENVS) CGO_ENABLED=1
|
||||
else
|
||||
GO_ENVS := $(GO_ENVS) CGO_ENABLED=0
|
||||
endif
|
||||
endif
|
||||
|
||||
download:
|
||||
$Q go mod download
|
||||
@@ -71,7 +87,7 @@ build: $(PREFIX)bin/$(BINNAME)
|
||||
|
||||
$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go)
|
||||
$Q mkdir -p $(@D)
|
||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG)
|
||||
$Q $(GOOS_OVERRIDE) GOFLAGS="$(GOFLAGS)" $(GO_ENVS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG)
|
||||
|
||||
# Target to force a build of step-ca without running tests
|
||||
simple: build
|
||||
@@ -93,10 +109,10 @@ generate:
|
||||
test: testdefault testtpmsimulator combinecoverage
|
||||
|
||||
testdefault:
|
||||
$Q $(GOFLAGS) gotestsum -- -coverprofile=defaultcoverage.out -short -covermode=atomic ./...
|
||||
$Q $(GO_ENVS) gotestsum -- -coverprofile=defaultcoverage.out -short -covermode=atomic ./...
|
||||
|
||||
testtpmsimulator:
|
||||
$Q CGO_ENALBED=1 gotestsum -- -coverprofile=tpmsimulatorcoverage.out -short -covermode=atomic -tags tpmsimulator ./acme
|
||||
$Q CGO_ENABLED=1 gotestsum -- -coverprofile=tpmsimulatorcoverage.out -short -covermode=atomic -tags tpmsimulator ./acme
|
||||
|
||||
testcgo:
|
||||
$Q gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./...
|
||||
@@ -109,7 +125,7 @@ combinecoverage:
|
||||
integrate: integration
|
||||
|
||||
integration: bin/$(BINNAME)
|
||||
$Q $(GOFLAGS) gotestsum -- -tags=integration ./integration/...
|
||||
$Q $(GO_ENVS) gotestsum -- -tags=integration ./integration/...
|
||||
|
||||
.PHONY: integrate integration
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -119,18 +119,12 @@ See our installation docs [here](https://smallstep.com/docs/step-ca/installation
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation can be found in a handful of different places:
|
||||
|
||||
1. On the web at https://smallstep.com/docs/step-ca.
|
||||
|
||||
2. On the command line with `step help ca xxx` where `xxx` is the subcommand
|
||||
you are interested in. Ex: `step help ca provisioner list`.
|
||||
|
||||
3. In your browser, by running `step help --http=:8080 ca` from the command line
|
||||
* [Official documentation](https://smallstep.com/docs/step-ca) is on smallstep.com
|
||||
* The `step` command reference is available via `step help`,
|
||||
[on smallstep.com](https://smallstep.com/docs/step-cli/reference/),
|
||||
or by running `step help --http=:8080` from the command line
|
||||
and visiting http://localhost:8080.
|
||||
|
||||
4. The [docs](./docs/README.md) folder is being deprecated, but it still has some documentation and tutorials.
|
||||
|
||||
## Feedback?
|
||||
|
||||
* Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space.
|
||||
|
||||
@@ -20,6 +20,16 @@ type Account struct {
|
||||
Status Status `json:"status"`
|
||||
OrdersURL string `json:"orders"`
|
||||
ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"`
|
||||
LocationPrefix string `json:"-"`
|
||||
ProvisionerName string `json:"-"`
|
||||
}
|
||||
|
||||
// GetLocation returns the URL location of the given account.
|
||||
func (a *Account) GetLocation() string {
|
||||
if a.LocationPrefix == "" {
|
||||
return ""
|
||||
}
|
||||
return a.LocationPrefix + a.ID
|
||||
}
|
||||
|
||||
// ToLog enables response logging.
|
||||
@@ -72,6 +82,7 @@ func (p *Policy) GetAllowedNameOptions() *policy.X509NameOptions {
|
||||
IPRanges: p.X509.Allowed.IPRanges,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Policy) GetDeniedNameOptions() *policy.X509NameOptions {
|
||||
if p == nil {
|
||||
return nil
|
||||
|
||||
@@ -66,6 +66,23 @@ func TestKeyToID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccount_GetLocation(t *testing.T) {
|
||||
locationPrefix := "https://test.ca.smallstep.com/acme/foo/account/"
|
||||
type test struct {
|
||||
acc *Account
|
||||
exp string
|
||||
}
|
||||
tests := map[string]test{
|
||||
"empty": {acc: &Account{LocationPrefix: ""}, exp: ""},
|
||||
"not-empty": {acc: &Account{ID: "bar", LocationPrefix: locationPrefix}, exp: locationPrefix + "bar"},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equals(t, tc.acc.GetLocation(), tc.exp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccount_IsValid(t *testing.T) {
|
||||
type test struct {
|
||||
acc *Account
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
@@ -67,6 +68,12 @@ func (u *UpdateAccountRequest) Validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
// getAccountLocationPath returns the current account URL location.
|
||||
// Returned location will be of the form: https://<ca-url>/acme/<provisioner>/account/<accID>
|
||||
func getAccountLocationPath(ctx context.Context, linker acme.Linker, accID string) string {
|
||||
return linker.GetLink(ctx, acme.AccountLinkType, accID)
|
||||
}
|
||||
|
||||
// NewAccount is the handler resource for creating new ACME accounts.
|
||||
func NewAccount(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
@@ -125,9 +132,11 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
acc = &acme.Account{
|
||||
Key: jwk,
|
||||
Contact: nar.Contact,
|
||||
Status: acme.StatusValid,
|
||||
Key: jwk,
|
||||
Contact: nar.Contact,
|
||||
Status: acme.StatusValid,
|
||||
LocationPrefix: getAccountLocationPath(ctx, linker, ""),
|
||||
ProvisionerName: prov.GetName(),
|
||||
}
|
||||
if err := db.CreateAccount(ctx, acc); err != nil {
|
||||
render.Error(w, acme.WrapErrorISE(err, "error creating account"))
|
||||
@@ -152,7 +161,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
linker.LinkAccount(ctx, acc)
|
||||
|
||||
w.Header().Set("Location", linker.GetLink(r.Context(), acme.AccountLinkType, acc.ID))
|
||||
w.Header().Set("Location", getAccountLocationPath(ctx, linker, acc.ID))
|
||||
render.JSONStatus(w, acc, httpStatus)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"go.step.sm/crypto/jose"
|
||||
@@ -16,7 +17,6 @@ import (
|
||||
"github.com/smallstep/certificates/api/render"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/nosql"
|
||||
)
|
||||
|
||||
type nextHTTP = func(http.ResponseWriter, *http.Request)
|
||||
@@ -293,7 +293,6 @@ func lookupJWK(next nextHTTP) nextHTTP {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
db := acme.MustDatabaseFromContext(ctx)
|
||||
linker := acme.MustLinkerFromContext(ctx)
|
||||
|
||||
jws, err := jwsFromContext(ctx)
|
||||
if err != nil {
|
||||
@@ -301,19 +300,16 @@ func lookupJWK(next nextHTTP) nextHTTP {
|
||||
return
|
||||
}
|
||||
|
||||
kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "")
|
||||
kid := jws.Signatures[0].Protected.KeyID
|
||||
if !strings.HasPrefix(kid, kidPrefix) {
|
||||
render.Error(w, acme.NewError(acme.ErrorMalformedType,
|
||||
"kid does not have required prefix; expected %s, but got %s",
|
||||
kidPrefix, kid))
|
||||
if kid == "" {
|
||||
render.Error(w, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"))
|
||||
return
|
||||
}
|
||||
|
||||
accID := strings.TrimPrefix(kid, kidPrefix)
|
||||
accID := path.Base(kid)
|
||||
acc, err := db.GetAccount(ctx, accID)
|
||||
switch {
|
||||
case nosql.IsErrNotFound(err):
|
||||
case acme.IsErrNotFound(err):
|
||||
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID))
|
||||
return
|
||||
case err != nil:
|
||||
@@ -324,6 +320,45 @@ func lookupJWK(next nextHTTP) nextHTTP {
|
||||
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
|
||||
return
|
||||
}
|
||||
|
||||
if storedLocation := acc.GetLocation(); storedLocation != "" {
|
||||
if kid != storedLocation {
|
||||
// ACME accounts should have a stored location equivalent to the
|
||||
// kid in the ACME request.
|
||||
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
|
||||
"kid does not match stored account location; expected %s, but got %s",
|
||||
storedLocation, kid))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify that the provisioner with which the account was created
|
||||
// matches the provisioner in the request URL.
|
||||
reqProv := acme.MustProvisionerFromContext(ctx)
|
||||
reqProvName := reqProv.GetName()
|
||||
accProvName := acc.ProvisionerName
|
||||
if reqProvName != accProvName {
|
||||
// Provisioner in the URL must match the provisioner with
|
||||
// which the account was created.
|
||||
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
|
||||
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
|
||||
accProvName, reqProvName))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// This code will only execute for old ACME accounts that do
|
||||
// not have a cached location. The following validation was
|
||||
// the original implementation of the `kid` check which has
|
||||
// since been deprecated. However, the code will remain to
|
||||
// ensure consistent behavior for old ACME accounts.
|
||||
linker := acme.MustLinkerFromContext(ctx)
|
||||
kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "")
|
||||
if !strings.HasPrefix(kid, kidPrefix) {
|
||||
render.Error(w, acme.NewError(acme.ErrorMalformedType,
|
||||
"kid does not have required prefix; expected %s, but got %s",
|
||||
kidPrefix, kid))
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||
ctx = context.WithValue(ctx, jwkContextKey, acc.Key)
|
||||
next(w, r.WithContext(ctx))
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/nosql/database"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
)
|
||||
@@ -678,31 +677,7 @@ func TestHandler_lookupJWK(t *testing.T) {
|
||||
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
|
||||
ctx: ctx,
|
||||
statusCode: 400,
|
||||
err: acme.NewError(acme.ErrorMalformedType, "kid does not have required prefix; expected %s, but got ", prefix),
|
||||
}
|
||||
},
|
||||
"fail/bad-kid-prefix": func(t *testing.T) test {
|
||||
_so := new(jose.SignerOptions)
|
||||
_so.WithHeader("kid", "foo")
|
||||
_signer, err := jose.NewSigner(jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
|
||||
Key: jwk.Key,
|
||||
}, _so)
|
||||
assert.FatalError(t, err)
|
||||
_jws, err := _signer.Sign([]byte("baz"))
|
||||
assert.FatalError(t, err)
|
||||
_raw, err := _jws.CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
_parsed, err := jose.ParseJWS(_raw)
|
||||
assert.FatalError(t, err)
|
||||
ctx := acme.NewProvisionerContext(context.Background(), prov)
|
||||
ctx = context.WithValue(ctx, jwsContextKey, _parsed)
|
||||
return test{
|
||||
db: &acme.MockDB{},
|
||||
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
|
||||
ctx: ctx,
|
||||
statusCode: 400,
|
||||
err: acme.NewError(acme.ErrorMalformedType, "kid does not have required prefix; expected %s, but got foo", prefix),
|
||||
err: acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"),
|
||||
}
|
||||
},
|
||||
"fail/account-not-found": func(t *testing.T) test {
|
||||
@@ -713,7 +688,7 @@ func TestHandler_lookupJWK(t *testing.T) {
|
||||
db: &acme.MockDB{
|
||||
MockGetAccount: func(ctx context.Context, accID string) (*acme.Account, error) {
|
||||
assert.Equals(t, accID, accID)
|
||||
return nil, database.ErrNotFound
|
||||
return nil, acme.ErrNotFound
|
||||
},
|
||||
},
|
||||
ctx: ctx,
|
||||
@@ -754,7 +729,77 @@ func TestHandler_lookupJWK(t *testing.T) {
|
||||
err: acme.NewError(acme.ErrorUnauthorizedType, "account is not active"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
"fail/account-with-location-prefix/bad-kid": func(t *testing.T) test {
|
||||
acc := &acme.Account{LocationPrefix: "foobar", Status: "valid"}
|
||||
ctx := acme.NewProvisionerContext(context.Background(), prov)
|
||||
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
|
||||
return test{
|
||||
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
|
||||
db: &acme.MockDB{
|
||||
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
|
||||
assert.Equals(t, id, accID)
|
||||
return acc, nil
|
||||
},
|
||||
},
|
||||
ctx: ctx,
|
||||
statusCode: http.StatusUnauthorized,
|
||||
err: acme.NewError(acme.ErrorUnauthorizedType, "kid does not match stored account location; expected foobar, but %q", prefix+accID),
|
||||
}
|
||||
},
|
||||
"fail/account-with-location-prefix/bad-provisioner": func(t *testing.T) test {
|
||||
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerName: "other"}
|
||||
ctx := acme.NewProvisionerContext(context.Background(), prov)
|
||||
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
|
||||
return test{
|
||||
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
|
||||
db: &acme.MockDB{
|
||||
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
|
||||
assert.Equals(t, id, accID)
|
||||
return acc, nil
|
||||
},
|
||||
},
|
||||
ctx: ctx,
|
||||
next: func(w http.ResponseWriter, r *http.Request) {
|
||||
_acc, err := accountFromContext(r.Context())
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, _acc, acc)
|
||||
_jwk, err := jwkFromContext(r.Context())
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, _jwk, jwk)
|
||||
w.Write(testBody)
|
||||
},
|
||||
statusCode: http.StatusUnauthorized,
|
||||
err: acme.NewError(acme.ErrorUnauthorizedType,
|
||||
"account provisioner does not match requested provisioner; account provisioner = %s, reqested provisioner = %s",
|
||||
prov.GetName(), "other"),
|
||||
}
|
||||
},
|
||||
"ok/account-with-location-prefix": func(t *testing.T) test {
|
||||
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerName: prov.GetName()}
|
||||
ctx := acme.NewProvisionerContext(context.Background(), prov)
|
||||
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
|
||||
return test{
|
||||
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
|
||||
db: &acme.MockDB{
|
||||
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
|
||||
assert.Equals(t, id, accID)
|
||||
return acc, nil
|
||||
},
|
||||
},
|
||||
ctx: ctx,
|
||||
next: func(w http.ResponseWriter, r *http.Request) {
|
||||
_acc, err := accountFromContext(r.Context())
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, _acc, acc)
|
||||
_jwk, err := jwkFromContext(r.Context())
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, _jwk, jwk)
|
||||
w.Write(testBody)
|
||||
},
|
||||
statusCode: http.StatusOK,
|
||||
}
|
||||
},
|
||||
"ok/account-without-location-prefix": func(t *testing.T) test {
|
||||
acc := &acme.Account{Status: "valid", Key: jwk}
|
||||
ctx := acme.NewProvisionerContext(context.Background(), prov)
|
||||
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
|
||||
|
||||
@@ -49,8 +49,9 @@ func withSimulator(t *testing.T) tpm.NewTPMOption {
|
||||
err := sim.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
sim = simulator.New()
|
||||
err := sim.Open()
|
||||
sim, err := simulator.New()
|
||||
require.NoError(t, err)
|
||||
err = sim.Open()
|
||||
require.NoError(t, err)
|
||||
return tpm.WithSimulator(sim)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ import (
|
||||
// account.
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
// IsErrNotFound returns true if the error is a "not found" error. Returns false
|
||||
// otherwise.
|
||||
func IsErrNotFound(err error) bool {
|
||||
return errors.Is(err, ErrNotFound)
|
||||
}
|
||||
|
||||
// DB is the DB interface expected by the step-ca ACME API.
|
||||
type DB interface {
|
||||
CreateAccount(ctx context.Context, acc *Account) error
|
||||
|
||||
@@ -13,12 +13,14 @@ import (
|
||||
|
||||
// dbAccount represents an ACME account.
|
||||
type dbAccount struct {
|
||||
ID string `json:"id"`
|
||||
Key *jose.JSONWebKey `json:"key"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
Status acme.Status `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
DeactivatedAt time.Time `json:"deactivatedAt"`
|
||||
ID string `json:"id"`
|
||||
Key *jose.JSONWebKey `json:"key"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
Status acme.Status `json:"status"`
|
||||
LocationPrefix string `json:"locationPrefix"`
|
||||
ProvisionerName string `json:"provisionerName"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
DeactivatedAt time.Time `json:"deactivatedAt"`
|
||||
}
|
||||
|
||||
func (dba *dbAccount) clone() *dbAccount {
|
||||
@@ -62,10 +64,12 @@ func (db *DB) GetAccount(ctx context.Context, id string) (*acme.Account, error)
|
||||
}
|
||||
|
||||
return &acme.Account{
|
||||
Status: dbacc.Status,
|
||||
Contact: dbacc.Contact,
|
||||
Key: dbacc.Key,
|
||||
ID: dbacc.ID,
|
||||
Status: dbacc.Status,
|
||||
Contact: dbacc.Contact,
|
||||
Key: dbacc.Key,
|
||||
ID: dbacc.ID,
|
||||
LocationPrefix: dbacc.LocationPrefix,
|
||||
ProvisionerName: dbacc.ProvisionerName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -87,11 +91,13 @@ func (db *DB) CreateAccount(ctx context.Context, acc *acme.Account) error {
|
||||
}
|
||||
|
||||
dba := &dbAccount{
|
||||
ID: acc.ID,
|
||||
Key: acc.Key,
|
||||
Contact: acc.Contact,
|
||||
Status: acc.Status,
|
||||
CreatedAt: clock.Now(),
|
||||
ID: acc.ID,
|
||||
Key: acc.Key,
|
||||
Contact: acc.Contact,
|
||||
Status: acc.Status,
|
||||
CreatedAt: clock.Now(),
|
||||
LocationPrefix: acc.LocationPrefix,
|
||||
ProvisionerName: acc.ProvisionerName,
|
||||
}
|
||||
|
||||
kid, err := acme.KeyToID(dba.Key)
|
||||
|
||||
@@ -197,6 +197,8 @@ func TestDB_getAccountIDByKeyID(t *testing.T) {
|
||||
|
||||
func TestDB_GetAccount(t *testing.T) {
|
||||
accID := "accID"
|
||||
locationPrefix := "https://test.ca.smallstep.com/acme/foo/account/"
|
||||
provisionerName := "foo"
|
||||
type test struct {
|
||||
db nosql.DB
|
||||
err error
|
||||
@@ -222,12 +224,14 @@ func TestDB_GetAccount(t *testing.T) {
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
dbacc := &dbAccount{
|
||||
ID: accID,
|
||||
Status: acme.StatusDeactivated,
|
||||
CreatedAt: now,
|
||||
DeactivatedAt: now,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
ID: accID,
|
||||
Status: acme.StatusDeactivated,
|
||||
CreatedAt: now,
|
||||
DeactivatedAt: now,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
LocationPrefix: locationPrefix,
|
||||
ProvisionerName: provisionerName,
|
||||
}
|
||||
b, err := json.Marshal(dbacc)
|
||||
assert.FatalError(t, err)
|
||||
@@ -266,6 +270,8 @@ func TestDB_GetAccount(t *testing.T) {
|
||||
assert.Equals(t, acc.ID, tc.dbacc.ID)
|
||||
assert.Equals(t, acc.Status, tc.dbacc.Status)
|
||||
assert.Equals(t, acc.Contact, tc.dbacc.Contact)
|
||||
assert.Equals(t, acc.LocationPrefix, tc.dbacc.LocationPrefix)
|
||||
assert.Equals(t, acc.ProvisionerName, tc.dbacc.ProvisionerName)
|
||||
assert.Equals(t, acc.Key.KeyID, tc.dbacc.Key.KeyID)
|
||||
}
|
||||
})
|
||||
@@ -379,6 +385,7 @@ func TestDB_GetAccountByKeyID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDB_CreateAccount(t *testing.T) {
|
||||
locationPrefix := "https://test.ca.smallstep.com/acme/foo/account/"
|
||||
type test struct {
|
||||
db nosql.DB
|
||||
acc *acme.Account
|
||||
@@ -390,9 +397,10 @@ func TestDB_CreateAccount(t *testing.T) {
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
acc := &acme.Account{
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
LocationPrefix: locationPrefix,
|
||||
}
|
||||
return test{
|
||||
db: &db.MockNoSQLDB{
|
||||
@@ -413,9 +421,10 @@ func TestDB_CreateAccount(t *testing.T) {
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
acc := &acme.Account{
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
LocationPrefix: locationPrefix,
|
||||
}
|
||||
return test{
|
||||
db: &db.MockNoSQLDB{
|
||||
@@ -436,9 +445,10 @@ func TestDB_CreateAccount(t *testing.T) {
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
acc := &acme.Account{
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
LocationPrefix: locationPrefix,
|
||||
}
|
||||
return test{
|
||||
db: &db.MockNoSQLDB{
|
||||
@@ -456,6 +466,8 @@ func TestDB_CreateAccount(t *testing.T) {
|
||||
assert.FatalError(t, json.Unmarshal(nu, dbacc))
|
||||
assert.Equals(t, dbacc.ID, string(key))
|
||||
assert.Equals(t, dbacc.Contact, acc.Contact)
|
||||
assert.Equals(t, dbacc.LocationPrefix, acc.LocationPrefix)
|
||||
assert.Equals(t, dbacc.ProvisionerName, acc.ProvisionerName)
|
||||
assert.Equals(t, dbacc.Key.KeyID, acc.Key.KeyID)
|
||||
assert.True(t, clock.Now().Add(-time.Minute).Before(dbacc.CreatedAt))
|
||||
assert.True(t, clock.Now().Add(time.Minute).After(dbacc.CreatedAt))
|
||||
@@ -479,9 +491,10 @@ func TestDB_CreateAccount(t *testing.T) {
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
acc := &acme.Account{
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
Status: acme.StatusValid,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
LocationPrefix: locationPrefix,
|
||||
}
|
||||
return test{
|
||||
db: &db.MockNoSQLDB{
|
||||
@@ -500,6 +513,8 @@ func TestDB_CreateAccount(t *testing.T) {
|
||||
assert.FatalError(t, json.Unmarshal(nu, dbacc))
|
||||
assert.Equals(t, dbacc.ID, string(key))
|
||||
assert.Equals(t, dbacc.Contact, acc.Contact)
|
||||
assert.Equals(t, dbacc.LocationPrefix, acc.LocationPrefix)
|
||||
assert.Equals(t, dbacc.ProvisionerName, acc.ProvisionerName)
|
||||
assert.Equals(t, dbacc.Key.KeyID, acc.Key.KeyID)
|
||||
assert.True(t, clock.Now().Add(-time.Minute).Before(dbacc.CreatedAt))
|
||||
assert.True(t, clock.Now().Add(time.Minute).After(dbacc.CreatedAt))
|
||||
@@ -539,12 +554,14 @@ func TestDB_UpdateAccount(t *testing.T) {
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
dbacc := &dbAccount{
|
||||
ID: accID,
|
||||
Status: acme.StatusDeactivated,
|
||||
CreatedAt: now,
|
||||
DeactivatedAt: now,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
ID: accID,
|
||||
Status: acme.StatusDeactivated,
|
||||
CreatedAt: now,
|
||||
DeactivatedAt: now,
|
||||
Contact: []string{"foo", "bar"},
|
||||
LocationPrefix: "foo",
|
||||
ProvisionerName: "alpha",
|
||||
Key: jwk,
|
||||
}
|
||||
b, err := json.Marshal(dbacc)
|
||||
assert.FatalError(t, err)
|
||||
@@ -644,10 +661,12 @@ func TestDB_UpdateAccount(t *testing.T) {
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
acc := &acme.Account{
|
||||
ID: accID,
|
||||
Status: acme.StatusDeactivated,
|
||||
Contact: []string{"foo", "bar"},
|
||||
Key: jwk,
|
||||
ID: accID,
|
||||
Status: acme.StatusDeactivated,
|
||||
Contact: []string{"baz", "zap"},
|
||||
LocationPrefix: "bar",
|
||||
ProvisionerName: "beta",
|
||||
Key: jwk,
|
||||
}
|
||||
return test{
|
||||
acc: acc,
|
||||
@@ -666,7 +685,10 @@ func TestDB_UpdateAccount(t *testing.T) {
|
||||
assert.FatalError(t, json.Unmarshal(nu, dbNew))
|
||||
assert.Equals(t, dbNew.ID, dbacc.ID)
|
||||
assert.Equals(t, dbNew.Status, acc.Status)
|
||||
assert.Equals(t, dbNew.Contact, dbacc.Contact)
|
||||
assert.Equals(t, dbNew.Contact, acc.Contact)
|
||||
// LocationPrefix should not change.
|
||||
assert.Equals(t, dbNew.LocationPrefix, dbacc.LocationPrefix)
|
||||
assert.Equals(t, dbNew.ProvisionerName, dbacc.ProvisionerName)
|
||||
assert.Equals(t, dbNew.Key.KeyID, dbacc.Key.KeyID)
|
||||
assert.Equals(t, dbNew.CreatedAt, dbacc.CreatedAt)
|
||||
assert.True(t, dbNew.DeactivatedAt.Add(-time.Minute).Before(now))
|
||||
@@ -686,12 +708,7 @@ func TestDB_UpdateAccount(t *testing.T) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.Equals(t, tc.acc.ID, dbacc.ID)
|
||||
assert.Equals(t, tc.acc.Status, dbacc.Status)
|
||||
assert.Equals(t, tc.acc.Contact, dbacc.Contact)
|
||||
assert.Equals(t, tc.acc.Key.KeyID, dbacc.Key.KeyID)
|
||||
}
|
||||
assert.Nil(t, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 +77,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 +87,7 @@ func (c *Controller) newWebhookController(templateData WebhookSetter, certType l
|
||||
client: client,
|
||||
webhooks: c.webhooks,
|
||||
certType: certType,
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,20 +116,18 @@ func DefaultIdentityFunc(_ context.Context, p Interface, email string) (*Identit
|
||||
switch k := p.(type) {
|
||||
case *OIDC:
|
||||
// OIDC principals would be:
|
||||
// ~~1. Preferred usernames.~~ Note: Under discussion, currently disabled
|
||||
// 2. Sanitized local.
|
||||
// 3. Raw local (if different).
|
||||
// 4. Email address.
|
||||
// ~~1. Preferred usernames.~~ Note: Under discussion, currently disabled
|
||||
// 2. Sanitized local.
|
||||
// 3. Raw local (if different).
|
||||
// 4. Email address.
|
||||
name := SanitizeSSHUserPrincipal(email)
|
||||
if !sshUserRegex.MatchString(name) {
|
||||
return nil, errors.Errorf("invalid principal '%s' from email '%s'", name, email)
|
||||
}
|
||||
usernames := []string{name}
|
||||
if i := strings.LastIndex(email, "@"); i >= 0 {
|
||||
usernames = append(usernames, email[:i])
|
||||
}
|
||||
usernames = append(usernames, email)
|
||||
return &Identity{
|
||||
// Remove duplicated and empty usernames.
|
||||
Usernames: SanitizeStringSlices(usernames),
|
||||
}, nil
|
||||
default:
|
||||
@@ -178,8 +177,6 @@ func DefaultAuthorizeSSHRenew(_ context.Context, p *Controller, cert *ssh.Certif
|
||||
return nil
|
||||
}
|
||||
|
||||
var sshUserRegex = regexp.MustCompile("^[a-z][-a-z0-9_]*$")
|
||||
|
||||
// SanitizeStringSlices removes duplicated an empty strings.
|
||||
func SanitizeStringSlices(original []string) []string {
|
||||
output := []string{}
|
||||
|
||||
@@ -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
|
||||
@@ -167,6 +170,12 @@ func TestController_GetIdentity(t *testing.T) {
|
||||
}}, args{ctx, "jane@doe.org"}, &Identity{
|
||||
Usernames: []string{"jane"},
|
||||
}, false},
|
||||
{"ok badname", fields{&OIDC{}, nil}, args{ctx, "1000@doe.org"}, &Identity{
|
||||
Usernames: []string{"1000", "1000@doe.org"},
|
||||
}, false},
|
||||
{"ok sanitized badname", fields{&OIDC{}, nil}, args{ctx, "1000+10@doe.org"}, &Identity{
|
||||
Usernames: []string{"1000_10", "1000+10", "1000+10@doe.org"},
|
||||
}, false},
|
||||
{"fail provisioner", fields{&JWK{}, nil}, args{ctx, "jane@doe.org"}, nil, true},
|
||||
{"fail custom", fields{&OIDC{}, func(ctx context.Context, p Interface, email string) (*Identity, error) {
|
||||
return nil, fmt.Errorf("an error")
|
||||
@@ -449,16 +458,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -76,13 +76,6 @@ func TestDefaultIdentityFunc(t *testing.T) {
|
||||
err: errors.New("provisioner type '*provisioner.X5C' not supported by identity function"),
|
||||
}
|
||||
},
|
||||
"fail/bad-ssh-regex": func(t *testing.T) test {
|
||||
return test{
|
||||
p: &OIDC{},
|
||||
email: "$%^#_>@smallstep.com",
|
||||
err: errors.New("invalid principal '______' from email '$%^#_>@smallstep.com'"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
return test{
|
||||
p: &OIDC{},
|
||||
@@ -142,6 +135,13 @@ func TestDefaultIdentityFunc(t *testing.T) {
|
||||
identity: &Identity{Usernames: []string{"john", "john@smallstep.com"}},
|
||||
}
|
||||
},
|
||||
"ok/badname": func(t *testing.T) test {
|
||||
return test{
|
||||
p: &OIDC{},
|
||||
email: "$%^#_>@smallstep.com",
|
||||
identity: &Identity{Usernames: []string{"______", "$%^#_>", "$%^#_>@smallstep.com"}},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, get := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -229,7 +229,7 @@ func TestClient_GetServerTLSConfig_http(t *testing.T) {
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("ioutil.RealAdd() error = %v", err)
|
||||
t.Fatalf("io.ReadAll() error = %v", err)
|
||||
}
|
||||
if !bytes.Equal(b, []byte("ok")) {
|
||||
t.Errorf("response body unexpected, got %s, want ok", b)
|
||||
@@ -343,7 +343,7 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) {
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("ioutil.RealAdd() error = %v", err)
|
||||
t.Errorf("io.ReadAll() error = %v", err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(b, []byte("ok")) {
|
||||
|
||||
@@ -37,6 +37,7 @@ type VaultOptions struct {
|
||||
PKIRoleEd25519 string `json:"pkiRoleEd25519,omitempty"`
|
||||
AuthType string `json:"authType,omitempty"`
|
||||
AuthMountPath string `json:"authMountPath,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
AuthOptions json.RawMessage `json:"authOptions,omitempty"`
|
||||
}
|
||||
|
||||
@@ -90,6 +91,10 @@ func New(ctx context.Context, opts apiv1.Options) (*VaultCAS, error) {
|
||||
return nil, fmt.Errorf("unable to configure %s auth method: %w", vc.AuthType, err)
|
||||
}
|
||||
|
||||
if vc.Namespace != "" {
|
||||
client.SetNamespace(vc.Namespace)
|
||||
}
|
||||
|
||||
authInfo, err := client.Auth().Login(ctx, method)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to login to %s auth method: %w", vc.AuthType, err)
|
||||
|
||||
@@ -6,7 +6,7 @@ COPY . .
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
gcc pkgconf libpcsclite-dev libcap2-bin
|
||||
RUN make V=1 GOFLAGS="" bin/step-ca
|
||||
RUN make V=1 GO_ENVS="CGO_ENABLED=1" bin/step-ca
|
||||
RUN setcap CAP_NET_BIND_SERVICE=+eip bin/step-ca
|
||||
|
||||
FROM smallstep/step-kms-plugin:bullseye AS kms
|
||||
|
||||
43
go.mod
43
go.mod
@@ -3,46 +3,48 @@ module github.com/smallstep/certificates
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
cloud.google.com/go/longrunning v0.4.2
|
||||
cloud.google.com/go/security v1.14.1
|
||||
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
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/go-tpm v0.3.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/googleapis/gax-go/v2 v2.11.0
|
||||
github.com/googleapis/gax-go/v2 v2.12.0
|
||||
github.com/hashicorp/vault/api v1.9.2
|
||||
github.com/hashicorp/vault/api/auth/approle v0.4.1
|
||||
github.com/hashicorp/vault/api/auth/kubernetes v0.4.1
|
||||
github.com/micromdm/scep/v2 v2.1.0
|
||||
github.com/newrelic/go-agent/v3 v3.21.1
|
||||
github.com/newrelic/go-agent/v3 v3.23.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/sirupsen/logrus v1.9.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/slackhq/nebula v1.6.1
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20230509120429-e17291421738
|
||||
github.com/smallstep/nosql v0.6.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/urfave/cli v1.22.13
|
||||
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.4
|
||||
go.step.sm/linkedca v0.19.1
|
||||
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/grpc v1.56.1
|
||||
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.13.0 // indirect
|
||||
@@ -64,8 +66,6 @@ require (
|
||||
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
|
||||
@@ -106,8 +106,6 @@ require (
|
||||
github.com/jackc/pgx/v4 v4.18.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
@@ -121,10 +119,8 @@ require (
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/ryboe/q v1.0.19 // indirect
|
||||
github.com/schollz/jsonstore v1.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
@@ -133,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
|
||||
)
|
||||
|
||||
@@ -151,8 +147,7 @@ require (
|
||||
// replace go.step.sm/linkedca => ../linkedca
|
||||
|
||||
// use github.com/smallstep/pkcs7 fork with patches applied
|
||||
//replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20230615175518-7ce6486b74eb
|
||||
|
||||
replace go.mozilla.org/pkcs7 => ./../pkcs7
|
||||
replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20230615175518-7ce6486b74eb
|
||||
|
||||
// temporary replace until https://github.com/smallstep/linkedca/pull/55 is merged
|
||||
replace go.step.sm/linkedca => ./../linkedca
|
||||
|
||||
68
go.sum
68
go.sum
@@ -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=
|
||||
@@ -50,16 +50,16 @@ 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.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.4.2 h1:WDKiiNXFTaQ6qz/G8FCOkuY9kJmOJGY67wPUC1M2RbE=
|
||||
cloud.google.com/go/longrunning v0.4.2/go.mod h1:OHrnaYyLUV6oqwh0xiS7e5sLQhP1m0QU9R+WhGDMgIQ=
|
||||
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=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w=
|
||||
cloud.google.com/go/security v1.14.1 h1:ZN+MFf1djt4VhuVd+JYoBjRftics3qKParPAXT5l4Uo=
|
||||
cloud.google.com/go/security v1.14.1/go.mod h1:ItQAI0zVZd1OkHh+raoef892dsr7VY2QzMDJ4nOPtOs=
|
||||
cloud.google.com/go/security v1.15.1 h1:jR3itwycg/TgGA0uIgTItcVhA55hKWiNJxaNNpQJaZE=
|
||||
cloud.google.com/go/security v1.15.1/go.mod h1:MvTnnbsWnehoizHi09zoiZob0iCHVcL4AUBj76h9fXA=
|
||||
cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk=
|
||||
cloud.google.com/go/spanner v1.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs=
|
||||
cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA=
|
||||
@@ -104,7 +104,7 @@ github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
@@ -493,8 +493,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/goreleaser/goreleaser v0.134.0/go.mod h1:ZT6Y2rSYa6NxQzIsdfWWNWAlYGXGbreo66NmE+3X3WQ=
|
||||
@@ -681,8 +681,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
@@ -780,8 +778,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/newrelic/go-agent/v3 v3.21.1 h1:nSLaQK+w/BHPUEpkPB+fX3ikgaRR2qyQiTECrcY+AmQ=
|
||||
github.com/newrelic/go-agent/v3 v3.21.1/go.mod h1:AGagR69YHzamnvfxq9aDHnImvZwxr7C+4w7UN0Bm3UM=
|
||||
github.com/newrelic/go-agent/v3 v3.23.1 h1:n4CK4EEod2A47T74wQFztavh9g3wHxxmlndj53ksbVg=
|
||||
github.com/newrelic/go-agent/v3 v3.23.1/go.mod h1:dG7Q7yLUrqOo7SYVJADVDN9+P8c/87xp9axldPxmdHM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ=
|
||||
github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
|
||||
@@ -826,7 +824,6 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -878,8 +875,6 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
@@ -895,8 +890,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/ryboe/q v1.0.19 h1:1dO1anK4gorZRpXBD/edBZkMxIC1tFIwN03nfyOV13A=
|
||||
github.com/ryboe/q v1.0.19/go.mod h1:IoEB3Q2/p6n1qbhIQVuNyakxtnV4rNJ/XJPK+jsEa0M=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
@@ -916,8 +909,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
|
||||
github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
||||
@@ -926,6 +919,8 @@ github.com/smallstep/go-attestation v0.4.4-0.20230509120429-e17291421738 h1:h+cZ
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20230509120429-e17291421738/go.mod h1:mk2hyNbyai1oon+ilW9t42BuBVw7ee8elDdgrPq4394=
|
||||
github.com/smallstep/nosql v0.6.0 h1:ur7ysI8s9st0cMXnTvB8tA3+x5Eifmkb6hl4uqNV5jc=
|
||||
github.com/smallstep/nosql v0.6.0/go.mod h1:jOXwLtockXORUPPZ2MCUcIkGR6w0cN1QGZniY9DITQA=
|
||||
github.com/smallstep/pkcs7 v0.0.0-20230615175518-7ce6486b74eb h1:wWc8z37baPz2oyusY9BVuM+uPtq6XAOb7qSegevnRs0=
|
||||
github.com/smallstep/pkcs7 v0.0.0-20230615175518-7ce6486b74eb/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
@@ -974,7 +969,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
@@ -998,8 +992,8 @@ github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.13 h1:wsLILXG8qCJNse/qAgLNf23737Cx05GflHg/PJGe1Ok=
|
||||
github.com/urfave/cli v1.22.13/go.mod h1:VufqObjsMTF2BBwKawpx9R8eAneNEWhoO0yx8Vd+FkE=
|
||||
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
||||
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
|
||||
@@ -1237,8 +1231,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=
|
||||
@@ -1492,8 +1486,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=
|
||||
@@ -1571,12 +1565,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=
|
||||
@@ -1612,8 +1606,8 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
|
||||
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
||||
@@ -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.
|
||||
|
||||
352
scripts/badger-migration/main.go
Normal file
352
scripts/badger-migration/main.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user