diff --git a/.devcontainer/.env b/.devcontainer/.env new file mode 100644 index 000000000..15181a657 --- /dev/null +++ b/.devcontainer/.env @@ -0,0 +1,4 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=postgres +POSTGRES_HOSTNAME=localhost diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..4feec8622 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,19 @@ +# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.18, 1.17, 1-bullseye, 1.18-bullseye, 1.17-bullseye, 1-buster, 1.18-buster, 1.17-buster +ARG VARIANT=1-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT} + +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment the next lines to use go get to install anything else you need +# USER vscode +# RUN go get -x +# USER root + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..04480f801 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,36 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/go-postgres +{ + "name": "Go & PostgreSQL", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace", + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "go.toolsManagement.checkForUpdates": "local", + "go.useLanguageServer": true, + "go.gopath": "/go", + "go.goroot": "/usr/local/go" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "golang.Go" + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5432], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "go version", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 000000000..d299ccd67 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,53 @@ +version: '3.8' + +volumes: + postgres-data: + + null +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + # [Choice] Go version 1, 1.18, 1.17 + # Append -bullseye or -buster to pin to an OS version. + # Use -bullseye variants on local arm64/Apple Silicon. + VARIANT: 1.18-bullseye + # Options + NODE_VERSION: "lts/*" + env_file: + # Ensure that the variables in .env match the same variables in devcontainer.json + - .env + + # Security Opt and cap_add allow for C++ based debuggers to work. + # See `runArgs`: https://github.com/Microsoft/vscode-docs/blob/main/docs/remote/devcontainerjson-reference.md + # security_opt: + # - seccomp:unconfined + # cap_add: + # - SYS_PTRACE + + volumes: + - ..:/workspace:cached + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. + network_mode: service:db + # Uncomment the next line to use a non-root user for all processes. + # user: vscode + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + + db: + image: postgres:latest + restart: unless-stopped + volumes: + - postgres-data:/var/lib/postgresql/data + env_file: + # Ensure that the variables in .env match the same variables in devcontainer.json + - .env + # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) diff --git a/.github/pull.yml b/.github/pull.yml new file mode 100644 index 000000000..cd633663e --- /dev/null +++ b/.github/pull.yml @@ -0,0 +1,7 @@ +version: "1" +rules: + - base: master + upstream: smallstep:master + mergeMethod: merge + assignees: + - fritterhoff diff --git a/.github/workflows/code-scan-cron.yml b/.github/workflows/code-scan-cron.yml deleted file mode 100644 index 9a35b7fe6..000000000 --- a/.github/workflows/code-scan-cron.yml +++ /dev/null @@ -1,7 +0,0 @@ -on: - schedule: - - cron: '0 0 * * *' - -jobs: - code-scan: - uses: smallstep/workflows/.github/workflows/code-scan.yml@main diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml deleted file mode 100644 index 8ca265e0f..000000000 --- a/.github/workflows/dependabot-auto-merge.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Dependabot auto-merge -on: pull_request - -permissions: - contents: write - pull-requests: write - -jobs: - dependabot: - runs-on: ubuntu-latest - if: ${{ github.actor == 'dependabot[bot]' }} - steps: - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v1.6.0 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Enable auto-merge for Dependabot PRs - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000..227169878 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,91 @@ +name: Docker Images + +on: + push: + pull_request: + branches: + - 'main' + +jobs: + build: + runs-on: ubuntu-latest + env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=raw,value={{branch}}-{{sha}}-{{date 'X'}},enable=${{ github.event_name != 'pull_request' }} + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + file: docker/Dockerfile + build-agent: + runs-on: ubuntu-latest + env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-agent + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=raw,value={{branch}}-{{sha}}-{{date 'X'}},enable=${{ github.event_name != 'pull_request' }} + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + file: docker/Dockerfile.agent diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..c6db162df --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/step-ca", + } + ] +} \ No newline at end of file diff --git a/acme/api/account.go b/acme/api/account.go index 25d923c77..cc4381afe 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -148,7 +148,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) { render.Error(w, err) return } - if err := db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil { + if err := db.UpdateExternalAccountKey(ctx, prov.GetID(), eak); err != nil { render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key")) return } diff --git a/acme/api/account_test.go b/acme/api/account_test.go index 7d799c883..8febbf5d4 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -314,14 +314,14 @@ func TestHandler_GetOrdersByAccountID(t *testing.T) { "fail/nil-account": func(t *testing.T) test { return test{ db: &acme.MockDB{}, - ctx: context.WithValue(context.Background(), accContextKey, http.NoBody), + ctx: context.WithValue(context.Background(), AccContextKey, http.NoBody), statusCode: 400, err: acme.NewError(acme.ErrorAccountDoesNotExistType, "account does not exist"), } }, "fail/account-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "foo"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{}, @@ -332,7 +332,7 @@ func TestHandler_GetOrdersByAccountID(t *testing.T) { }, "fail/db.GetOrdersByAccountID-error": func(t *testing.T) test { acc := &acme.Account{ID: accID} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -347,7 +347,7 @@ func TestHandler_GetOrdersByAccountID(t *testing.T) { acc := &acme.Account{ID: accID} ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) ctx = acme.NewProvisionerContext(ctx, prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) return test{ db: &acme.MockDB{ MockGetOrdersByAccountID: func(ctx context.Context, id string) ([]string, error) { @@ -682,7 +682,7 @@ func TestHandler_NewAccount(t *testing.T) { } ctx := acme.NewProvisionerContext(context.Background(), prov) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -863,7 +863,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { } }, "fail/nil-account": func(t *testing.T) test { - ctx := context.WithValue(context.Background(), accContextKey, nil) + ctx := context.WithValue(context.Background(), AccContextKey, nil) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -872,7 +872,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { } }, "fail/no-payload": func(t *testing.T) test { - ctx := context.WithValue(context.Background(), accContextKey, &acc) + ctx := context.WithValue(context.Background(), AccContextKey, &acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -881,7 +881,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { } }, "fail/nil-payload": func(t *testing.T) test { - ctx := context.WithValue(context.Background(), accContextKey, &acc) + ctx := context.WithValue(context.Background(), AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, nil) return test{ db: &acme.MockDB{}, @@ -891,7 +891,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { } }, "fail/unmarshal-payload-error": func(t *testing.T) test { - ctx := context.WithValue(context.Background(), accContextKey, &acc) + ctx := context.WithValue(context.Background(), AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{}) return test{ db: &acme.MockDB{}, @@ -906,7 +906,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { } b, err := json.Marshal(uar) assert.FatalError(t, err) - ctx := context.WithValue(context.Background(), accContextKey, &acc) + ctx := context.WithValue(context.Background(), AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ db: &acme.MockDB{}, @@ -921,7 +921,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { } b, err := json.Marshal(uar) assert.FatalError(t, err) - ctx := context.WithValue(context.Background(), accContextKey, &acc) + ctx := context.WithValue(context.Background(), AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ db: &acme.MockDB{ @@ -943,7 +943,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { b, err := json.Marshal(uar) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, &acc) + ctx = context.WithValue(ctx, AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ db: &acme.MockDB{ @@ -962,7 +962,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { b, err := json.Marshal(uar) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, &acc) + ctx = context.WithValue(ctx, AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ db: &acme.MockDB{}, @@ -977,7 +977,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { b, err := json.Marshal(uar) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, &acc) + ctx = context.WithValue(ctx, AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ db: &acme.MockDB{ @@ -993,7 +993,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) { }, "ok/post-as-get": func(t *testing.T) test { ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, &acc) + ctx = context.WithValue(ctx, AccContextKey, &acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isPostAsGet: true}) return test{ db: &acme.MockDB{}, diff --git a/acme/api/eab.go b/acme/api/eab.go index 26854595b..8026ae829 100644 --- a/acme/api/eab.go +++ b/acme/api/eab.go @@ -51,27 +51,27 @@ func validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) } db := acme.MustDatabaseFromContext(ctx) - externalAccountKey, err := db.GetExternalAccountKey(ctx, acmeProv.ID, keyID) + externalAccountKey, err := db.GetExternalAccountKey(ctx, acmeProv.GetID(), keyID) if err != nil { var ae *acme.Error if errors.As(err, &ae) { return nil, acme.WrapError(acme.ErrorUnauthorizedType, err, "the field 'kid' references an unknown key") } - return nil, acme.WrapErrorISE(err, "error retrieving external account key") + return nil, acme.NewError(acme.ErrorEabDoesNotExistType, "error retrieving external account key") } if externalAccountKey == nil { return nil, acme.NewError(acme.ErrorUnauthorizedType, "the field 'kid' references an unknown key") } - if len(externalAccountKey.HmacKey) == 0 { - return nil, acme.NewError(acme.ErrorServerInternalType, "external account binding key with id '%s' does not have secret bytes", keyID) - } - if externalAccountKey.AlreadyBound() { return nil, acme.NewError(acme.ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", keyID, externalAccountKey.AccountID, externalAccountKey.BoundAt) } + if len(externalAccountKey.HmacKey) == 0 { + return nil, acme.NewError(acme.ErrorEabAlreadyUsedType, "external account binding key with id '%s' does not have secret bytes", keyID) + } + payload, err := eabJWS.Verify(externalAccountKey.HmacKey) if err != nil { return nil, acme.WrapErrorISE(err, "error verifying externalAccountBinding signature") diff --git a/acme/api/eab_test.go b/acme/api/eab_test.go index 14dbdad1f..ec1906dcd 100644 --- a/acme/api/eab_test.go +++ b/acme/api/eab_test.go @@ -312,7 +312,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) { ExternalAccountBinding: eab, }, eak: nil, - err: acme.NewErrorISE("error retrieving external account key"), + err: acme.NewError(acme.ErrorEabDoesNotExistType, "error retrieving external account key"), } }, "fail/db.GetExternalAccountKey-not-found": func(t *testing.T) test { @@ -361,7 +361,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) { ExternalAccountBinding: eab, }, eak: nil, - err: acme.NewErrorISE("error retrieving external account key"), + err: acme.NewError(acme.ErrorEabDoesNotExistType, "error retrieving external account key"), } }, "fail/db.GetExternalAccountKey-error": func(t *testing.T) test { @@ -410,7 +410,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) { ExternalAccountBinding: eab, }, eak: nil, - err: acme.NewErrorISE("error retrieving external account key"), + err: acme.NewError(acme.ErrorEabDoesNotExistType, "error retrieving external account key"), } }, "fail/db.GetExternalAccountKey-nil": func(t *testing.T) test { @@ -516,7 +516,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) { ExternalAccountBinding: eab, }, eak: nil, - err: acme.NewError(acme.ErrorServerInternalType, "external account binding key with id 'eakID' does not have secret bytes"), + err: acme.NewError(acme.ErrorEabAlreadyUsedType, "external account binding key with id 'eakID' does not have secret bytes"), } }, "fail/db.GetExternalAccountKey-wrong-provisioner": func(t *testing.T) test { diff --git a/acme/api/handler.go b/acme/api/handler.go index d2940f49d..c32fd88c7 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -5,16 +5,22 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" + "errors" "fmt" "net/http" "time" "github.com/go-chi/chi/v5" + "github.com/sirupsen/logrus" + + pb "github.com/hm-edu/portal-apis" + "github.com/smallstep/certificates/cas/sectigocas/eab" "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" ) @@ -68,6 +74,7 @@ type HandlerOptions struct { // PrerequisitesChecker checks if all prerequisites for serving ACME are // met by the CA configuration. PrerequisitesChecker func(ctx context.Context) (bool, error) + Cfg *config.Config } var mustAuthority = func(ctx context.Context) acme.CertificateAuthority { @@ -102,7 +109,6 @@ func (h *handler) Route(r api.Router) { } // NewHandler returns a new ACME API handler. -// // Note: this method is deprecated in step-ca, other applications can still use // this to support ACME, but the recommendation is to use use // api.Route(api.Router) and acme.NewContext() instead. @@ -397,3 +403,23 @@ func GetCertificate(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/pem-certificate-chain") w.Write(certBytes) } + +func checkPermission(ctx context.Context, identifiers []acme.Identifier, eak *acme.ExternalAccountKey) ([]string, error) { + if eak == nil { + logrus.Warn("No external account key given. Cannot check permissions") + return nil, nil + } + var domains []string + for _, x := range identifiers { + domains = append(domains, x.Value) + } + client, ok := eab.FromContext(ctx) + if !ok { + return nil, errors.New("no external account client available") + } + result, err := client.CheckEABPermissions(ctx, &pb.CheckEABPermissionRequest{Domains: domains, EabKey: eak.ID}) + if err != nil { + return nil, err + } + return result.Missing, nil +} diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index bd7bb50e3..7fb2d2c3b 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -17,6 +17,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/go-cmp/cmp" + pb "github.com/hm-edu/portal-apis" "github.com/pkg/errors" "go.step.sm/crypto/jose" @@ -25,8 +26,21 @@ import ( "github.com/smallstep/assert" "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/authority/provisioner" + "google.golang.org/grpc" ) +type MockClient struct { + Missing []string +} + +func (c *MockClient) CheckEABPermissions(ctx context.Context, in *pb.CheckEABPermissionRequest, opts ...grpc.CallOption) (*pb.CheckEABPermissionResponse, error) { + return &pb.CheckEABPermissionResponse{Missing: c.Missing}, nil +} + +func (c *MockClient) ResolveAccountId(ctx context.Context, in *pb.ResolveAccountIdRequest, opts ...grpc.CallOption) (*pb.ResolveAccountIdResponse, error) { //nolint + return nil, nil +} + type mockClient struct { get func(url string) (*http.Response, error) lookupTxt func(name string) ([]string, error) @@ -263,7 +277,7 @@ func TestHandler_GetAuthorization(t *testing.T) { }, "fail/nil-account": func(t *testing.T) test { ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, nil) + ctx = context.WithValue(ctx, AccContextKey, nil) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -273,7 +287,7 @@ func TestHandler_GetAuthorization(t *testing.T) { }, "fail/db.GetAuthorization-error": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -286,7 +300,7 @@ func TestHandler_GetAuthorization(t *testing.T) { }, "fail/account-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -304,7 +318,7 @@ func TestHandler_GetAuthorization(t *testing.T) { }, "fail/db.UpdateAuthorization-error": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -329,7 +343,7 @@ func TestHandler_GetAuthorization(t *testing.T) { "ok": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -426,7 +440,7 @@ func TestHandler_GetCertificate(t *testing.T) { } }, "fail/nil-account": func(t *testing.T) test { - ctx := context.WithValue(context.Background(), accContextKey, nil) + ctx := context.WithValue(context.Background(), AccContextKey, nil) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -436,7 +450,7 @@ func TestHandler_GetCertificate(t *testing.T) { }, "fail/db.GetCertificate-error": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -449,7 +463,7 @@ func TestHandler_GetCertificate(t *testing.T) { }, "fail/account-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -465,7 +479,7 @@ func TestHandler_GetCertificate(t *testing.T) { }, "ok": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -549,14 +563,14 @@ func TestHandler_GetChallenge(t *testing.T) { "fail/nil-account": func(t *testing.T) test { return test{ db: &acme.MockDB{}, - ctx: context.WithValue(context.Background(), accContextKey, nil), + ctx: context.WithValue(context.Background(), AccContextKey, nil), statusCode: 400, err: acme.NewError(acme.ErrorAccountDoesNotExistType, "account does not exist"), } }, "fail/no-payload": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -567,7 +581,7 @@ func TestHandler_GetChallenge(t *testing.T) { "fail/nil-payload": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, nil) return test{ db: &acme.MockDB{}, @@ -579,7 +593,7 @@ func TestHandler_GetChallenge(t *testing.T) { "fail/db.GetChallenge-error": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ @@ -598,7 +612,7 @@ func TestHandler_GetChallenge(t *testing.T) { "fail/account-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ @@ -617,7 +631,7 @@ func TestHandler_GetChallenge(t *testing.T) { "fail/no-jwk": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ @@ -636,7 +650,7 @@ func TestHandler_GetChallenge(t *testing.T) { "fail/nil-jwk": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true}) ctx = context.WithValue(ctx, jwkContextKey, nil) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) @@ -656,7 +670,7 @@ func TestHandler_GetChallenge(t *testing.T) { "fail/validate-challenge-error": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true}) _jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) assert.FatalError(t, err) @@ -696,7 +710,7 @@ func TestHandler_GetChallenge(t *testing.T) { "ok": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true}) _jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) assert.FatalError(t, err) diff --git a/acme/api/middleware.go b/acme/api/middleware.go index ab2ab9086..7bcc7b69f 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -258,7 +258,7 @@ func extractJWK(next nextHTTP) nextHTTP { render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active")) return } - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) } next(w, r.WithContext(ctx)) } @@ -359,7 +359,7 @@ func lookupJWK(next nextHTTP) nextHTTP { return } } - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, jwkContextKey, acc.Key) next(w, r.WithContext(ctx)) return @@ -458,7 +458,7 @@ type ContextKey string const ( // accContextKey account key - accContextKey = ContextKey("acc") + AccContextKey = ContextKey("acc") // jwsContextKey jws key jwsContextKey = ContextKey("jws") // jwkContextKey jwk key @@ -470,7 +470,7 @@ const ( // accountFromContext searches the context for an ACME account. Returns the // account or an error. func accountFromContext(ctx context.Context) (*acme.Account, error) { - val, ok := ctx.Value(accContextKey).(*acme.Account) + val, ok := ctx.Value(AccContextKey).(*acme.Account) if !ok || val == nil { return nil, acme.NewError(acme.ErrorAccountDoesNotExistType, "account not in context") } diff --git a/acme/api/order.go b/acme/api/order.go index b207f87ce..0709bf0c4 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -137,7 +137,7 @@ func NewOrder(w http.ResponseWriter, r *http.Request) { var eak *acme.ExternalAccountKey if acmeProv.RequireEAB { if eak, err = db.GetExternalAccountKeyByAccountID(ctx, prov.GetID(), acc.ID); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving external account binding key")) + render.Error(w, acme.NewError(acme.ErrorEabAccountBindingDoesNotExistType, "error retrieving external account binding key")) return } } @@ -180,6 +180,15 @@ func NewOrder(w http.ResponseWriter, r *http.Request) { NotAfter: nor.NotAfter, } + if missing, err := checkPermission(ctx, o.Identifiers, eak); len(missing) != 0 || err != nil { + if err != nil { + render.Error(w, acme.NewError(acme.ErrorServerInternalType, "Internal server error")) + return + } + render.Error(w, acme.NewError(acme.ErrorRejectedIdentifierType, "Missing registration for domain(s) %v", missing)) + return + } + for i, identifier := range o.Identifiers { az := &acme.Authorization{ AccountID: acc.ID, @@ -320,6 +329,15 @@ func GetOrder(w http.ResponseWriter, r *http.Request) { linker.LinkOrder(ctx, o) w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) + if o.Status == acme.StatusProcessing { + // Due to the bad behavior of the k8s cert-manager we must catch this client using the User-Agent and handle it in a special way. + // The cert-manager does not repect the retry after flags and will retry too fast. + if strings.Contains(r.UserAgent(), "cert-manager") { + render.Error(w, acme.NewErrorISE("Request is processing")) + return + } + w.Header().Set("Retry-After", "10") + } render.JSON(w, o) } @@ -372,14 +390,14 @@ func FinalizeOrder(w http.ResponseWriter, r *http.Request) { } ca := mustAuthority(ctx) - if err = o.Finalize(ctx, db, fr.csr, ca, prov); err != nil { + if _, err = o.Finalize(ctx, db, fr.csr, ca, prov); err != nil { render.Error(w, acme.WrapErrorISE(err, "error finalizing order")) return } linker.LinkOrder(ctx, o) - w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) + w.Header().Set("Retry-After", "20") render.JSON(w, o) } diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 36de975a3..f78600bd3 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -15,6 +15,8 @@ import ( "testing" "time" + "github.com/smallstep/certificates/cas/sectigocas/eab" + "github.com/go-chi/chi/v5" "github.com/pkg/errors" @@ -328,7 +330,7 @@ func TestHandler_GetOrder(t *testing.T) { }, "fail/nil-account": func(t *testing.T) test { ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, nil) + ctx = context.WithValue(ctx, AccContextKey, nil) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -338,7 +340,7 @@ func TestHandler_GetOrder(t *testing.T) { }, "fail/no-provisioner": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -349,7 +351,7 @@ func TestHandler_GetOrder(t *testing.T) { "fail/nil-provisioner": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), nil) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -360,7 +362,7 @@ func TestHandler_GetOrder(t *testing.T) { "fail/db.GetOrder-error": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -374,7 +376,7 @@ func TestHandler_GetOrder(t *testing.T) { "fail/account-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -390,7 +392,7 @@ func TestHandler_GetOrder(t *testing.T) { "fail/provisioner-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -406,7 +408,7 @@ func TestHandler_GetOrder(t *testing.T) { "fail/order-update-error": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -430,7 +432,7 @@ func TestHandler_GetOrder(t *testing.T) { "ok": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ db: &acme.MockDB{ @@ -799,6 +801,7 @@ func TestHandler_NewOrder(t *testing.T) { ctx context.Context nor *NewOrderRequest statusCode int + missing []string vr func(t *testing.T, o *acme.Order) err *acme.Error } @@ -813,7 +816,7 @@ func TestHandler_NewOrder(t *testing.T) { }, "fail/nil-account": func(t *testing.T) test { ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, nil) + ctx = context.WithValue(ctx, AccContextKey, nil) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -823,7 +826,7 @@ func TestHandler_NewOrder(t *testing.T) { }, "fail/no-provisioner": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -834,7 +837,7 @@ func TestHandler_NewOrder(t *testing.T) { "fail/nil-provisioner": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -844,7 +847,7 @@ func TestHandler_NewOrder(t *testing.T) { }, "fail/no-payload": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = acme.NewProvisionerContext(ctx, prov) return test{ db: &acme.MockDB{}, @@ -856,7 +859,7 @@ func TestHandler_NewOrder(t *testing.T) { "fail/nil-payload": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, nil) return test{ db: &acme.MockDB{}, @@ -868,7 +871,7 @@ func TestHandler_NewOrder(t *testing.T) { "fail/unmarshal-payload-error": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{}) return test{ db: &acme.MockDB{}, @@ -883,7 +886,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ db: &acme.MockDB{}, @@ -902,7 +905,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), &acme.MockProvisioner{}) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, @@ -930,11 +933,11 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), acmeProv) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, - statusCode: 500, + statusCode: 400, ca: &mockCA{}, db: &acme.MockDB{ MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) { @@ -943,7 +946,7 @@ func TestHandler_NewOrder(t *testing.T) { return nil, errors.New("force") }, }, - err: acme.NewErrorISE("error retrieving external account binding key: force"), + err: acme.NewError(acme.ErrorEabAccountBindingDoesNotExistType, "The used external account binding seems to be deleted"), } }, "fail/newACMEPolicyEngine-error": func(t *testing.T) test { @@ -958,7 +961,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), acmeProv) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, @@ -994,7 +997,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), acmeProv) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, @@ -1037,7 +1040,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), provWithPolicy) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, @@ -1080,7 +1083,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), provWithPolicy) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, @@ -1118,7 +1121,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, @@ -1152,7 +1155,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( ch1, ch2, ch3 **acme.Challenge @@ -1217,6 +1220,36 @@ func TestHandler_NewOrder(t *testing.T) { err: acme.NewErrorISE("error creating order: force"), } }, + "fail/missingDomain": func(t *testing.T) test { + acc := &acme.Account{ID: "accID"} + nor := &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "dns", Value: "zap.internal"}, + {Type: "dns", Value: "*.zar.internal"}, + }, + } + b, err := json.Marshal(nor) + assert.FatalError(t, err) + p := newACMEProv(t) + p.RequireEAB = true + ctx := acme.NewProvisionerContext(context.Background(), p) + ctx = context.WithValue(ctx, AccContextKey, acc) + ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) + return test{ + ctx: ctx, + statusCode: 400, + ca: &mockCA{}, + missing: []string{"zap.internal"}, + db: &acme.MockDB{MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) { + assert.Equals(t, prov.GetID(), provisionerID) + assert.Equals(t, "accID", accountID) + return &acme.ExternalAccountKey{ + ID: "test", + }, nil + }}, + err: acme.NewError(acme.ErrorRejectedIdentifierType, "account does not exist"), + } + }, "ok/multiple-authz": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} nor := &NewOrderRequest{ @@ -1228,7 +1261,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(nor) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( ch1, ch2, ch3, ch4 **acme.Challenge @@ -1348,7 +1381,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(nor) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( ch1, ch2, ch3 **acme.Challenge @@ -1445,7 +1478,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(nor) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( ch1, ch2, ch3 **acme.Challenge @@ -1541,7 +1574,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(nor) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( ch1, ch2, ch3 **acme.Challenge @@ -1638,7 +1671,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(nor) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( ch1, ch2, ch3 **acme.Challenge @@ -1738,7 +1771,7 @@ func TestHandler_NewOrder(t *testing.T) { b, err := json.Marshal(nor) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), provWithPolicy) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( ch1, ch2, ch3 **acme.Challenge @@ -1828,6 +1861,7 @@ func TestHandler_NewOrder(t *testing.T) { t.Run(name, func(t *testing.T) { mockMustAuthority(t, tc.ca) ctx := newBaseContext(tc.ctx, tc.db, acme.NewLinker("test.ca.smallstep.com", "acme")) + ctx = eab.NewContext(ctx, &MockClient{Missing: tc.missing}) req := httptest.NewRequest("GET", u, http.NoBody) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -1930,7 +1964,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { }, "fail/nil-account": func(t *testing.T) test { ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, nil) + ctx = context.WithValue(ctx, AccContextKey, nil) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -1940,7 +1974,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { }, "fail/no-provisioner": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -1951,7 +1985,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { "fail/nil-provisioner": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) return test{ db: &acme.MockDB{}, ctx: ctx, @@ -1961,7 +1995,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { }, "fail/no-payload": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} - ctx := context.WithValue(context.Background(), accContextKey, acc) + ctx := context.WithValue(context.Background(), AccContextKey, acc) ctx = acme.NewProvisionerContext(ctx, prov) return test{ db: &acme.MockDB{}, @@ -1973,7 +2007,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { "fail/nil-payload": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, nil) return test{ db: &acme.MockDB{}, @@ -1985,7 +2019,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { "fail/unmarshal-payload-error": func(t *testing.T) test { acc := &acme.Account{ID: "accID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{}) return test{ db: &acme.MockDB{}, @@ -2000,7 +2034,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { b, err := json.Marshal(fr) assert.FatalError(t, err) ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ db: &acme.MockDB{}, @@ -2013,7 +2047,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ @@ -2028,7 +2062,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { "fail/account-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ @@ -2045,7 +2079,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { "fail/provisioner-id-mismatch": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ @@ -2062,7 +2096,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { "fail/order-finalize-error": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ @@ -2087,7 +2121,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { "ok": func(t *testing.T) test { acc := &acme.Account{ID: "accountID"} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) return test{ diff --git a/acme/api/revoke_test.go b/acme/api/revoke_test.go index 1c472e6e9..6033c2304 100644 --- a/acme/api/revoke_test.go +++ b/acme/api/revoke_test.go @@ -281,7 +281,7 @@ type mockCA struct { MockAreSANsallowed func(ctx context.Context, sans []string) error } -func (m *mockCA) Sign(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (m *mockCA) Sign(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { return nil, nil } @@ -688,7 +688,7 @@ func TestHandler_RevokeCert(t *testing.T) { ctx := acme.NewProvisionerContext(context.Background(), prov) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) - ctx = context.WithValue(ctx, accContextKey, nil) + ctx = context.WithValue(ctx, AccContextKey, nil) db := &acme.MockDB{ MockGetCertificateBySerial: func(ctx context.Context, serial string) (*acme.Certificate, error) { assert.Equals(t, cert.SerialNumber.String(), serial) @@ -707,7 +707,7 @@ func TestHandler_RevokeCert(t *testing.T) { "fail/account-not-valid": func(t *testing.T) test { acc := &acme.Account{ID: "accountID", Status: acme.StatusInvalid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) @@ -736,7 +736,7 @@ func TestHandler_RevokeCert(t *testing.T) { "fail/account-not-authorized": func(t *testing.T) test { acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) @@ -815,7 +815,7 @@ func TestHandler_RevokeCert(t *testing.T) { "fail/certificate-revoked-check-fails": func(t *testing.T) test { acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) @@ -848,7 +848,7 @@ func TestHandler_RevokeCert(t *testing.T) { "fail/certificate-already-revoked": func(t *testing.T) test { acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) db := &acme.MockDB{ @@ -886,7 +886,7 @@ func TestHandler_RevokeCert(t *testing.T) { assert.FatalError(t, err) acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: invalidReasonCodePayloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) db := &acme.MockDB{ @@ -924,7 +924,7 @@ func TestHandler_RevokeCert(t *testing.T) { } acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), mockACMEProv) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) db := &acme.MockDB{ @@ -956,7 +956,7 @@ func TestHandler_RevokeCert(t *testing.T) { "fail/ca.Revoke": func(t *testing.T) test { acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) db := &acme.MockDB{ @@ -988,7 +988,7 @@ func TestHandler_RevokeCert(t *testing.T) { "fail/ca.Revoke-already-revoked": func(t *testing.T) test { acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) db := &acme.MockDB{ @@ -1019,7 +1019,7 @@ func TestHandler_RevokeCert(t *testing.T) { "ok/using-account-key": func(t *testing.T) test { acc := &acme.Account{ID: "accountID", Status: acme.StatusValid} ctx := acme.NewProvisionerContext(context.Background(), prov) - ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, AccContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payloadBytes}) ctx = context.WithValue(ctx, jwsContextKey, jws) ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx) diff --git a/acme/challenge.go b/acme/challenge.go index b8294ef0c..e7e9f996e 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -27,6 +27,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/google/go-tpm/legacy/tpm2" + "github.com/sirupsen/logrus" "golang.org/x/exp/slices" "github.com/smallstep/go-attestation/attest" @@ -36,6 +37,7 @@ import ( "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" + "github.com/smallstep/certificates/acme/validation" "github.com/smallstep/certificates/authority/provisioner" ) @@ -117,11 +119,40 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb if InsecurePortHTTP01 != 0 { u.Host += ":" + strconv.Itoa(InsecurePortHTTP01) } - + go func() { + mqtt, ok := validation.FromContext(ctx) + if !ok { + return + } + req := validation.ValidationRequest{ + Authz: ch.AuthorizationID, + Challenge: ch.ID, + Target: u.String(), + } + data, err := json.Marshal(req) + if err != nil { + logrus.Warn(err) + return + } + if token := mqtt.GetClient().Publish(fmt.Sprintf("%s/jobs", mqtt.GetOrganization()), 1, false, data); token.Wait() && token.Error() != nil { + logrus.Warn(token.Error()) + } + logrus.Info("published validation request") + }() vc := MustClientFromContext(ctx) - resp, err := vc.Get(u.String()) - if err != nil { - return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err, + resp, errHttp := vc.Get(u.String()) + // get challenge again and check if it was already validated + chDb, errDb := db.GetChallenge(ctx, ch.ID, ch.AuthorizationID) + if errDb == nil { + logrus.WithField("challenge", chDb.ID).WithField("authz", chDb.AuthorizationID).Infof("challenge has status %s", chDb.Status) + if chDb.Status == StatusValid { + return nil + } + } else { + logrus.WithError(errDb).WithField("challenge", ch.ID).WithField("authz", ch.AuthorizationID).Warn("error getting challenge from db") + } + if errHttp != nil { + return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, errHttp, "error doing http GET for url %s", u)) } defer resp.Body.Close() diff --git a/acme/common.go b/acme/common.go index 7d58305fa..778c33ee8 100644 --- a/acme/common.go +++ b/acme/common.go @@ -21,7 +21,7 @@ var clock Clock // CertificateAuthority is the interface implemented by a CA authority. type CertificateAuthority interface { - Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + Sign(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) AreSANsAllowed(ctx context.Context, sans []string) error IsRevoked(sn string) (bool, error) Revoke(context.Context, *authority.RevokeOptions) error diff --git a/acme/db/nosql/eab.go b/acme/db/nosql/eab.go index e2a437ddf..1b58907e5 100644 --- a/acme/db/nosql/eab.go +++ b/acme/db/nosql/eab.go @@ -4,9 +4,12 @@ import ( "context" "crypto/rand" "encoding/json" + "fmt" "sync" "time" + "github.com/sirupsen/logrus" + "github.com/pkg/errors" "github.com/smallstep/certificates/acme" @@ -229,9 +232,52 @@ func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerI return db.GetExternalAccountKey(ctx, provisionerID, dbExternalAccountKeyReference.ExternalAccountKeyID) } -func (db *DB) GetExternalAccountKeyByAccountID(context.Context, string, string) (*acme.ExternalAccountKey, error) { - //nolint:nilnil // legacy - return nil, nil +func (db *DB) GetExternalAccountKeyByAccountID(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) { + externalAccountKeyMutex.RLock() + defer externalAccountKeyMutex.RUnlock() + + logrus.Debug(fmt.Sprintf("searching for eak keys bount to provisioner %v", provisionerID)) + // cursor and limit are ignored in open source, at least for now. + + var eakIDs []string + r, err := db.db.Get(externalAccountKeyIDsByProvisionerIDTable, []byte(provisionerID)) + if err != nil { + if !nosqlDB.IsErrNotFound(err) { + return nil, errors.Wrapf(err, "error loading ACME EAB Key IDs for provisioner %s", provisionerID) + } + // it may happen that no record is found; we'll continue with an empty slice + } else { + if err := json.Unmarshal(r, &eakIDs); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling ACME EAB Key IDs for provisioner %s", provisionerID) + } + } + logrus.Debug(fmt.Sprintf("found %v eak keys (%v)", len(eakIDs), eakIDs)) + for _, eakID := range eakIDs { + if eakID == "" { + continue // shouldn't happen; just in case + } + eak, err := db.getDBExternalAccountKey(ctx, eakID) + if err != nil { + if !nosqlDB.IsErrNotFound(err) { + return nil, errors.Wrapf(err, "error retrieving ACME EAB Key for provisioner %s and keyID %s", provisionerID, eakID) + } + } + logrus.Debug(fmt.Sprintf("loaded %v", eak)) + if eak.AccountID == accountID { + return &acme.ExternalAccountKey{ + ID: eak.ID, + HmacKey: eak.HmacKey, + ProvisionerID: eak.ProvisionerID, + Reference: eak.Reference, + AccountID: eak.AccountID, + CreatedAt: eak.CreatedAt, + BoundAt: eak.BoundAt, + }, nil + } + logrus.Debug(fmt.Sprintf("%s does not match %s", eak.AccountID, accountID)) + } + + return nil, errors.Errorf("ACME EAB Key for account id %s not found", accountID) } func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *acme.ExternalAccountKey) error { @@ -313,7 +359,14 @@ func (db *DB) addEAKID(ctx context.Context, provisionerID, eakID string) error { } if err = db.save(ctx, provisionerID, _new, _old, "externalAccountKeyIDsByProvisionerID", externalAccountKeyIDsByProvisionerIDTable); err != nil { - return errors.Wrapf(err, "error saving eakIDs index for provisioner %s", provisionerID) + if len(eakIDs) == 0 { + logrus.Warnf("error replacing empty eakID list for provisioner %s", provisionerID) + if err_internal := db.save(ctx, provisionerID, _new, []string{}, "externalAccountKeyIDsByProvisionerID", externalAccountKeyIDsByProvisionerIDTable); err_internal != nil { + return errors.Wrapf(err, "error saving eakIDs index for provisioner %s", provisionerID) + } + } else { + return errors.Wrapf(err, "error saving eakIDs index for provisioner %s", provisionerID) + } } return nil diff --git a/acme/errors.go b/acme/errors.go index 658ec6e0c..7797ccb1f 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -65,6 +65,12 @@ const ( ErrorUserActionRequiredType // ErrorNotImplementedType operation is not implemented ErrorNotImplementedType + // ErrorEabAlreadyUsedType the external account binding has already been used + ErrorEabAlreadyUsedType + // ErrorEabDoesNotExistType the external account binding does not exist + ErrorEabDoesNotExistType + // ErrorEabAccountBindingDoesNotExistType the external account binding does not exist + ErrorEabAccountBindingDoesNotExistType ) // String returns the string representation of the acme problem type, @@ -121,6 +127,12 @@ func (ap ProblemType) String() string { return "userActionRequired" case ErrorNotImplementedType: return "notImplemented" + case ErrorEabAlreadyUsedType: + return "eabAlreadyUsed" + case ErrorEabDoesNotExistType: + return "eabDoesNotExist" + case ErrorEabAccountBindingDoesNotExistType: + return "eabAccountBindingDoesNotExist" default: return fmt.Sprintf("unsupported type ACME error type '%d'", int(ap)) } @@ -141,6 +153,21 @@ var ( status: 500, } errorMap = map[ProblemType]errorMetadata{ + ErrorEabAlreadyUsedType: { + typ: officialACMEPrefix + ErrorExternalAccountRequiredType.String(), + details: "The external account binding has already been used", + status: 400, + }, + ErrorEabDoesNotExistType: { + typ: officialACMEPrefix + ErrorExternalAccountRequiredType.String(), + details: "The used external account binding key id does not exist", + status: 400, + }, + ErrorEabAccountBindingDoesNotExistType: { + typ: officialACMEPrefix + ErrorExternalAccountRequiredType.String(), + details: "The used external account binding seems to be deleted", + status: 400, + }, ErrorAccountDoesNotExistType: { typ: officialACMEPrefix + ErrorAccountDoesNotExistType.String(), details: "Account does not exist", diff --git a/acme/mqtt/client.go b/acme/mqtt/client.go new file mode 100644 index 000000000..655bcd3b3 --- /dev/null +++ b/acme/mqtt/client.go @@ -0,0 +1,100 @@ +package mqtt + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/sirupsen/logrus" + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/acme/validation" +) + +var clock acme.Clock + +func Connect(acmeDB acme.DB, host, user, password, organization string) (validation.MqttClient, error) { + opts := mqtt.NewClientOptions() + opts.SetOrderMatters(false) // Allow out of order messages (use this option unless in order delivery is essential) + opts.ConnectTimeout = time.Second // Minimal delays on connect + opts.WriteTimeout = time.Second // Minimal delays on writes + opts.KeepAlive = 10 // Keepalive every 10 seconds so we quickly detect network outages + opts.PingTimeout = time.Second // local broker so response should be quick + opts.ConnectRetry = true + opts.AutoReconnect = true + opts.ClientID = "acme" + opts.Username = user + opts.Password = password + opts.AddBroker(fmt.Sprintf("ssl://%s:8883", host)) + logrus.Infof("connecting to mqtt broker") + // Log events + opts.OnConnectionLost = func(cl mqtt.Client, err error) { + logrus.Println("mqtt connection lost") + } + opts.OnConnect = func(cl mqtt.Client) { + logrus.Println("mqtt connection established") + go func() { + cl.Subscribe(fmt.Sprintf("%s/data", organization), 1, func(client mqtt.Client, msg mqtt.Message) { + logrus.Printf("Received message on topic: %s\nMessage: %s\n", msg.Topic(), msg.Payload()) + ctx := context.Background() + data := msg.Payload() + var payload validation.ValidationResponse + err := json.Unmarshal(data, &payload) + if err != nil { + logrus.Errorf("error unmarshalling payload: %v", err) + return + } + + ch, err := acmeDB.GetChallenge(ctx, payload.Challenge, payload.Authz) + if err != nil { + logrus.Errorf("error getting challenge: %v", err) + return + } + + acc, err := acmeDB.GetAccount(ctx, ch.AccountID) + if err != nil { + logrus.Errorf("error getting account: %v", err) + return + } + expected, err := acme.KeyAuthorization(ch.Token, acc.Key) + + if payload.Content != expected || err != nil { + logrus.Errorf("invalid key authorization: %v", err) + return + } + u := &url.URL{Scheme: "http", Host: ch.Value, Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)} + logrus.Infof("challenge %s validated using mqtt", u.String()) + + if ch.Status != acme.StatusPending && ch.Status != acme.StatusValid { + return + } + + ch.Status = acme.StatusValid + ch.Error = nil + ch.ValidatedAt = clock.Now().Format(time.RFC3339) + + if err = acmeDB.UpdateChallenge(ctx, ch); err != nil { + logrus.Errorf("error updating challenge: %v", err) + } else { + logrus.Infof("challenge %s updated to valid", u.String()) + } + + }) + }() + } + opts.OnReconnecting = func(mqtt.Client, *mqtt.ClientOptions) { + logrus.Println("mqtt attempting to reconnect") + } + + client := mqtt.NewClient(opts) + + if token := client.Connect(); token.WaitTimeout(30*time.Second) && token.Error() != nil { + logrus.Warn(token.Error()) + return nil, token.Error() + } + + connection := validation.BrokerConnection{Client: client, Organization: organization} + return connection, nil +} diff --git a/acme/order.go b/acme/order.go index 8dfcf97a6..271bc3694 100644 --- a/acme/order.go +++ b/acme/order.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/sirupsen/logrus" "github.com/smallstep/certificates/authority/provisioner" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/x509util" @@ -67,6 +68,8 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error { now := clock.Now() switch o.Status { + case StatusProcessing: + return nil case StatusInvalid: return nil case StatusValid: @@ -150,27 +153,29 @@ func (o *Order) getAuthorizationFingerprint(ctx context.Context, db DB) (string, // Finalize signs a certificate if the necessary conditions for Order completion // have been met. -// -// TODO(mariano): Here or in the challenge validation we should perform some -// external validation using the identifier value and the attestation data. From -// a validation service we can get the list of SANs to set in the final -// certificate. -func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateRequest, auth CertificateAuthority, p Provisioner) error { +func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateRequest, auth CertificateAuthority, p Provisioner) (chan error, error) { if err := o.UpdateStatus(ctx, db); err != nil { - return err + return nil, err } switch o.Status { case StatusInvalid: - return NewError(ErrorOrderNotReadyType, "order %s has been abandoned", o.ID) + return nil, NewError(ErrorOrderNotReadyType, "order %s has been abandoned", o.ID) case StatusValid: - return nil + return nil, nil case StatusPending: - return NewError(ErrorOrderNotReadyType, "order %s is not ready", o.ID) + return nil, NewError(ErrorOrderNotReadyType, "order %s is not ready", o.ID) case StatusReady: break + case StatusProcessing: + return nil, NewErrorISE("order %s is already processing", o.ID) default: - return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID) + return nil, NewErrorISE("unexpected status %s for order %s", o.Status, o.ID) + } + + o.Status = StatusProcessing + if err := db.UpdateOrder(ctx, o); err != nil { + return nil, WrapErrorISE(err, "error updating order %s", o.ID) } // Get key fingerprint if any. And then compare it with the CSR fingerprint. @@ -179,15 +184,15 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques // and the attestation certificate are the same. fingerprint, err := o.getAuthorizationFingerprint(ctx, db) if err != nil { - return err + return nil, err } if fingerprint != "" { fp, err := keyutil.Fingerprint(csr.PublicKey) if err != nil { - return WrapErrorISE(err, "error calculating key fingerprint") + return nil, WrapErrorISE(err, "error calculating key fingerprint") } if subtle.ConstantTimeCompare([]byte(fingerprint), []byte(fp)) == 0 { - return NewError(ErrorUnauthorizedType, "order %s csr does not match the attested key", o.ID) + return nil, NewError(ErrorUnauthorizedType, "order %s csr does not match the attested key", o.ID) } } @@ -212,7 +217,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques // could result in unauthorized access if a relying system relies on the Common // Name in its authorization logic. if csr.Subject.CommonName != "" && csr.Subject.CommonName != permanentIdentifier { - return NewError(ErrorBadCSRType, "CSR Subject Common Name does not match identifiers exactly: "+ + return nil, NewError(ErrorBadCSRType, "CSR Subject Common Name does not match identifiers exactly: "+ "CSR Subject Common Name = %s, Order Permanent Identifier = %s", csr.Subject.CommonName, permanentIdentifier) } break @@ -233,7 +238,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques defaultTemplate = x509util.DefaultLeafTemplate sans, err := o.sans(csr) if err != nil { - return err + return nil, err } data.SetSubjectAlternativeNames(sans...) } @@ -242,7 +247,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) signOps, err := p.AuthorizeSign(ctx, "") if err != nil { - return WrapErrorISE(err, "error retrieving authorization options from ACME provisioner") + return nil, WrapErrorISE(err, "error retrieving authorization options from ACME provisioner") } // Unlike most of the provisioners, ACME's AuthorizeSign method doesn't // define the templates, and the template data used in WebHooks is not @@ -255,38 +260,53 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques templateOptions, err := provisioner.CustomTemplateOptions(p.GetOptions(), data, defaultTemplate) if err != nil { - return WrapErrorISE(err, "error creating template options from ACME provisioner") + return nil, WrapErrorISE(err, "error creating template options from ACME provisioner") } // Build extra signing options. signOps = append(signOps, templateOptions) signOps = append(signOps, extraOptions...) + ch := make(chan error) + go func() { + // Sign a new certificate. + certChain, err := auth.Sign(ctx, csr, provisioner.SignOptions{ + NotBefore: provisioner.NewTimeDuration(o.NotBefore), + NotAfter: provisioner.NewTimeDuration(o.NotAfter), + }, signOps...) + if err != nil { + logrus.WithError(err).Error("error signing certificate") + o.Status = StatusInvalid + ch <- WrapErrorISE(err, "error signing certificate for order %s", o.ID) + if err = db.UpdateOrder(ctx, o); err != nil { + logrus.WithError(err).Error("error updating order") + } + return + } - // Sign a new certificate. - certChain, err := auth.Sign(csr, provisioner.SignOptions{ - NotBefore: provisioner.NewTimeDuration(o.NotBefore), - NotAfter: provisioner.NewTimeDuration(o.NotAfter), - }, signOps...) - if err != nil { - return WrapErrorISE(err, "error signing certificate for order %s", o.ID) - } - - cert := &Certificate{ - AccountID: o.AccountID, - OrderID: o.ID, - Leaf: certChain[0], - Intermediates: certChain[1:], - } - if err := db.CreateCertificate(ctx, cert); err != nil { - return WrapErrorISE(err, "error creating certificate for order %s", o.ID) - } + cert := &Certificate{ + AccountID: o.AccountID, + OrderID: o.ID, + Leaf: certChain[0], + Intermediates: certChain[1:], + } + if err := db.CreateCertificate(ctx, cert); err != nil { + logrus.WithError(err).Error("error creating certificate") + o.Status = StatusInvalid + ch <- WrapErrorISE(err, "error creating certificate for order %s", o.ID) + if err = db.UpdateOrder(ctx, o); err != nil { + logrus.WithError(err).Error("error updating order") + } + return + } - o.CertificateID = cert.ID - o.Status = StatusValid - if err = db.UpdateOrder(ctx, o); err != nil { - return WrapErrorISE(err, "error updating order %s", o.ID) - } - return nil + o.CertificateID = cert.ID + o.Status = StatusValid + if err = db.UpdateOrder(ctx, o); err != nil { + logrus.WithError(err).Error("error updating order") + ch <- WrapErrorISE(err, "error updating order %s", o.ID) + } + }() + return ch, nil } func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativeName, error) { diff --git a/acme/order_test.go b/acme/order_test.go index 2851bb190..69edcb0ef 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -271,16 +271,16 @@ func TestOrder_UpdateStatus(t *testing.T) { } type mockSignAuth struct { - sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + sign func(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) areSANsAllowed func(ctx context.Context, sans []string) error loadProvisionerByName func(string) (provisioner.Interface, error) ret1, ret2 interface{} err error } -func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (m *mockSignAuth) Sign(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { if m.sign != nil { - return m.sign(csr, signOpts, extraOpts...) + return m.sign(ctx, csr, signOpts, extraOpts...) } else if m.err != nil { return nil, m.err } @@ -497,7 +497,16 @@ func TestOrder_Finalize(t *testing.T) { MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { return &Authorization{ID: id, Status: StatusValid}, nil }, - }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.CertificateID, "") + assert.Equals(t, updo.Status, StatusProcessing) + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return nil + }}, err: NewErrorISE("error retrieving authorization options from ACME provisioner: force"), } }, @@ -541,6 +550,16 @@ func TestOrder_Finalize(t *testing.T) { MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { return &Authorization{ID: id, Status: StatusValid}, nil }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.CertificateID, "") + assert.Equals(t, updo.Status, StatusProcessing) + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return nil + }, }, err: NewErrorISE("error creating template options from ACME provisioner: error unmarshaling template data: invalid character 'o' in literal false (expecting 'a')"), } @@ -578,7 +597,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return nil, errors.New("force") }, @@ -587,7 +606,15 @@ func TestOrder_Finalize(t *testing.T) { MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { return &Authorization{ID: id, Status: StatusValid}, nil }, - }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.CertificateID, "") + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return nil + }}, err: NewErrorISE("error signing certificate for order oID: force"), } }, @@ -628,7 +655,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -644,6 +671,14 @@ func TestOrder_Finalize(t *testing.T) { assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz}) return errors.New("force") }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return nil + }, }, err: NewErrorISE("error creating certificate for order oID: force"), } @@ -685,7 +720,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -703,8 +738,8 @@ func TestOrder_Finalize(t *testing.T) { return nil }, MockUpdateOrder: func(ctx context.Context, updo *Order) error { - assert.Equals(t, updo.CertificateID, "certID") - assert.Equals(t, updo.Status, StatusValid) + assert.Equals(t, updo.CertificateID, "") + assert.Equals(t, updo.Status, StatusProcessing) assert.Equals(t, updo.ID, o.ID) assert.Equals(t, updo.AccountID, o.AccountID) assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) @@ -770,7 +805,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(_ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{leaf, inter, root}, nil }, @@ -792,8 +827,8 @@ func TestOrder_Finalize(t *testing.T) { return nil }, MockUpdateOrder: func(ctx context.Context, updo *Order) error { - assert.Equals(t, updo.CertificateID, "certID") - assert.Equals(t, updo.Status, StatusValid) + // assert.Equals(t, updo.CertificateID, "certID") + // assert.Equals(t, updo.Status, StatusValid) assert.Equals(t, updo.ID, o.ID) assert.Equals(t, updo.AccountID, o.AccountID) assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) @@ -863,7 +898,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(_ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{leaf, inter, root}, nil }, @@ -896,8 +931,8 @@ func TestOrder_Finalize(t *testing.T) { return nil }, MockUpdateOrder: func(ctx context.Context, updo *Order) error { - assert.Equals(t, updo.CertificateID, "certID") - assert.Equals(t, updo.Status, StatusValid) + // assert.Equals(t, updo.CertificateID, "certID") + // assert.Equals(t, updo.Status, StatusValid) assert.Equals(t, updo.ID, o.ID) assert.Equals(t, updo.AccountID, o.AccountID) assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) @@ -973,7 +1008,7 @@ func TestOrder_Finalize(t *testing.T) { // using the mocking functions as a wrapper for actual test helpers generated per test case or per // function that's tested. ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(_ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{leaf, inter, root}, nil }, @@ -995,8 +1030,8 @@ func TestOrder_Finalize(t *testing.T) { return nil }, MockUpdateOrder: func(ctx context.Context, updo *Order) error { - assert.Equals(t, updo.CertificateID, "certID") - assert.Equals(t, updo.Status, StatusValid) + // assert.Equals(t, updo.CertificateID, "certID") + // assert.Equals(t, updo.Status, StatusValid) assert.Equals(t, updo.ID, o.ID) assert.Equals(t, updo.AccountID, o.AccountID) assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) @@ -1044,7 +1079,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -1062,8 +1097,6 @@ func TestOrder_Finalize(t *testing.T) { return nil }, MockUpdateOrder: func(ctx context.Context, updo *Order) error { - assert.Equals(t, updo.CertificateID, "certID") - assert.Equals(t, updo.Status, StatusValid) assert.Equals(t, updo.ID, o.ID) assert.Equals(t, updo.AccountID, o.AccountID) assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) @@ -1108,7 +1141,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -1126,8 +1159,6 @@ func TestOrder_Finalize(t *testing.T) { return nil }, MockUpdateOrder: func(ctx context.Context, updo *Order) error { - assert.Equals(t, updo.CertificateID, "certID") - assert.Equals(t, updo.Status, StatusValid) assert.Equals(t, updo.ID, o.ID) assert.Equals(t, updo.AccountID, o.AccountID) assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) @@ -1175,7 +1206,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(ctx context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -1193,8 +1224,6 @@ func TestOrder_Finalize(t *testing.T) { return nil }, MockUpdateOrder: func(ctx context.Context, updo *Order) error { - assert.Equals(t, updo.CertificateID, "certID") - assert.Equals(t, updo.Status, StatusValid) assert.Equals(t, updo.ID, o.ID) assert.Equals(t, updo.AccountID, o.AccountID) assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) @@ -1209,7 +1238,9 @@ func TestOrder_Finalize(t *testing.T) { for name, run := range tests { t.Run(name, func(t *testing.T) { tc := run(t) - if err := tc.o.Finalize(context.Background(), tc.db, tc.csr, tc.ca, tc.prov); err != nil { + ch, err := tc.o.Finalize(context.Background(), tc.db, tc.csr, tc.ca, tc.prov) + + if err != nil { if assert.NotNil(t, tc.err) { var k *Error if errors.As(err, &k) { @@ -1223,7 +1254,23 @@ func TestOrder_Finalize(t *testing.T) { } } } else { - assert.Nil(t, tc.err) + select { + case e := <-ch: + if assert.NotNil(t, tc.err) { + switch k := e.(type) { + case *Error: + assert.Equals(t, k.Type, tc.err.Type) + assert.Equals(t, k.Detail, tc.err.Detail) + assert.Equals(t, k.Status, tc.err.Status) + assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) + assert.Equals(t, k.Detail, tc.err.Detail) + default: + assert.FatalError(t, errors.New("unexpected error type")) + } + } + case <-time.After(1 * time.Second): + assert.Nil(t, tc.err) + } } }) } diff --git a/acme/status.go b/acme/status.go index d9aae82dc..c4fd8e0b4 100644 --- a/acme/status.go +++ b/acme/status.go @@ -16,5 +16,6 @@ var ( StatusReady = Status("ready") //statusExpired = "expired" //statusActive = "active" + StatusProcessing = Status("processing") //statusProcessing = "processing" ) diff --git a/acme/validation/client.go b/acme/validation/client.go new file mode 100644 index 000000000..637d356a7 --- /dev/null +++ b/acme/validation/client.go @@ -0,0 +1,60 @@ +package validation + +import ( + "context" + + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +type ValidationResponse struct { + Authz string `json:"authz"` + Challenge string `json:"challenge"` + Content string `json:"content"` +} + +type ValidationRequest struct { + Authz string `json:"authz"` + Challenge string `json:"challenge"` + Target string `json:"target"` +} + +type validationKey struct{} + +type MqttClient interface { + GetClient() mqtt.Client + GetOrganization() string +} + +type BrokerConnection struct { + Client mqtt.Client + Organization string +} + +func (b BrokerConnection) GetClient() mqtt.Client { + return b.Client +} + +func (b BrokerConnection) GetOrganization() string { + return b.Organization +} + +// NewContext adds the given validation client to the context. +func NewContext(ctx context.Context, a MqttClient) context.Context { + return context.WithValue(ctx, validationKey{}, a) +} + +// FromContext returns the validation client from the given context. +func FromContext(ctx context.Context) (a MqttClient, ok bool) { + a, ok = ctx.Value(validationKey{}).(MqttClient) + return +} + +// MustFromContext returns the validation client from the given context. It will +// panic if no validation client is not in the context. +func MustFromContext(ctx context.Context) MqttClient { + if a, ok := FromContext(ctx); !ok { + panic("validation client is not in the context") + } else { + return a + } +} diff --git a/api/api.go b/api/api.go index 7cf44a11c..58c95181c 100644 --- a/api/api.go +++ b/api/api.go @@ -42,10 +42,10 @@ type Authority interface { AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error) GetTLSOptions() *config.TLSOptions Root(shasum string) (*x509.Certificate, error) - Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) - Renew(peer *x509.Certificate) ([]*x509.Certificate, error) RenewContext(ctx context.Context, peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) - Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) + Sign(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + Renew(ctx context.Context, peer *x509.Certificate) ([]*x509.Certificate, error) + Rekey(ctx context.Context, peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error) LoadProvisionerByName(string) (provisioner.Interface, error) GetProvisioners(cursor string, limit int) (provisioner.List, string, error) @@ -55,6 +55,7 @@ type Authority interface { GetFederation() ([]*x509.Certificate, error) Version() authority.Version GetCertificateRevocationList() ([]byte, error) + Health() error } // mustAuthority will be replaced on unit tests. @@ -360,8 +361,14 @@ func Version(w http.ResponseWriter, r *http.Request) { } // Health is an HTTP handler that returns the status of the server. -func Health(w http.ResponseWriter, _ *http.Request) { - render.JSON(w, HealthResponse{Status: "ok"}) +func Health(w http.ResponseWriter, r *http.Request) { + a := mustAuthority(r.Context()) + err := a.Health() + if err == nil { + render.JSON(w, HealthResponse{Status: "ok"}) + } else { + render.JSONStatus(w, HealthResponse{Status: "error"}, http.StatusServiceUnavailable) + } } // Root is an HTTP handler that using the SHA256 from the URL, returns the root diff --git a/api/api_test.go b/api/api_test.go index c57eef31e..062b24946 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -225,6 +225,10 @@ func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) { return m.ret1.([]byte), m.err } +func (m *mockAuthority) Health() error { + return nil +} + // TODO: remove once Authorize is deprecated. func (m *mockAuthority) Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error) { if m.authorize != nil { @@ -254,14 +258,14 @@ func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) { return m.ret1.(*x509.Certificate), m.err } -func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (m *mockAuthority) Sign(_ context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { if m.sign != nil { return m.sign(cr, opts, signOpts...) } return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } -func (m *mockAuthority) Renew(cert *x509.Certificate) ([]*x509.Certificate, error) { +func (m *mockAuthority) Renew(_ context.Context, cert *x509.Certificate) ([]*x509.Certificate, error) { if m.renew != nil { return m.renew(cert) } @@ -275,7 +279,7 @@ func (m *mockAuthority) RenewContext(ctx context.Context, oldcert *x509.Certific return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } -func (m *mockAuthority) Rekey(oldcert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { +func (m *mockAuthority) Rekey(_ context.Context, oldcert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { if m.rekey != nil { return m.rekey(oldcert, pk) } @@ -858,6 +862,7 @@ func Test_caHandler_Route(t *testing.T) { func Test_Health(t *testing.T) { req := httptest.NewRequest("GET", "http://example.com/health", http.NoBody) w := httptest.NewRecorder() + mockMustAuthority(t, &mockAuthority{}) Health(w, req) res := w.Result() diff --git a/api/rekey.go b/api/rekey.go index cda843a3d..9384a02e6 100644 --- a/api/rekey.go +++ b/api/rekey.go @@ -1,6 +1,7 @@ package api import ( + "context" "net/http" "github.com/smallstep/certificates/api/read" @@ -45,7 +46,7 @@ func Rekey(w http.ResponseWriter, r *http.Request) { } a := mustAuthority(r.Context()) - certChain, err := a.Rekey(r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey) + certChain, err := a.Rekey(context.Background(), r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey) if err != nil { render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey")) return diff --git a/api/renew.go b/api/renew.go index 1b9ed95fa..fae219043 100644 --- a/api/renew.go +++ b/api/renew.go @@ -1,6 +1,7 @@ package api import ( + "context" "crypto/x509" "net/http" "strings" @@ -33,7 +34,7 @@ func Renew(w http.ResponseWriter, r *http.Request) { } a := mustAuthority(ctx) - certChain, err := a.RenewContext(ctx, cert, nil) + certChain, err := a.Renew(context.Background(), cert) if err != nil { render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew")) return diff --git a/api/sign.go b/api/sign.go index c0c83ce21..3fb3bd5c2 100644 --- a/api/sign.go +++ b/api/sign.go @@ -1,6 +1,7 @@ package api import ( + "context" "crypto/tls" "encoding/json" "net/http" @@ -78,7 +79,8 @@ func Sign(w http.ResponseWriter, r *http.Request) { return } - certChain, err := a.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...) + certChain, err := a.Sign(context.Background(), body.CsrPEM.CertificateRequest, opts, signOpts...) + if err != nil { render.Error(w, errs.ForbiddenErr(err, "error signing certificate")) return diff --git a/api/ssh.go b/api/ssh.go index 9d0bbc14b..1c0948586 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -330,7 +330,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) { NotAfter: time.Unix(int64(cert.ValidBefore), 0), }) - certChain, err := a.Sign(cr, provisioner.SignOptions{}, signOpts...) + certChain, err := a.Sign(context.Background(), cr, provisioner.SignOptions{}, signOpts...) if err != nil { render.Error(w, errs.ForbiddenErr(err, "error signing identity certificate")) return diff --git a/api/sshRenew.go b/api/sshRenew.go index cd6d9bde6..b7ad63adc 100644 --- a/api/sshRenew.go +++ b/api/sshRenew.go @@ -109,7 +109,7 @@ func renewIdentityCertificate(r *http.Request, notBefore, notAfter time.Time) ([ cert.NotAfter = notAfter } - certChain, err := mustAuthority(r.Context()).Renew(cert) + certChain, err := mustAuthority(r.Context()).Renew(r.Context(), cert) if err != nil { return nil, err } diff --git a/authority/authority.go b/authority/authority.go index a4a76293e..b7f030af5 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -185,6 +185,11 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return a, nil } +// Health checks if the authority is stil alive. +func (a *Authority) Health() error { + return a.db.Ping() +} + type authorityKey struct{} // NewContext adds the given authority to the context. diff --git a/authority/authority_test.go b/authority/authority_test.go index 45c7cd861..b058ca97b 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -1,6 +1,7 @@ package authority import ( + "context" "crypto" "crypto/rand" "crypto/sha256" @@ -414,7 +415,7 @@ func TestNewEmbedded_Sign(t *testing.T) { csr, err := x509.ParseCertificateRequest(cr) assert.FatalError(t, err) - cert, err := a.Sign(csr, provisioner.SignOptions{}) + cert, err := a.Sign(context.TODO(), csr, provisioner.SignOptions{}) assert.FatalError(t, err) assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames) assert.Equals(t, crt, cert[1]) @@ -431,9 +432,9 @@ func TestNewEmbedded_GetTLSCertificate(t *testing.T) { a, err := NewEmbedded(WithX509RootBundle(caPEM), WithX509Signer(crt, key.(crypto.Signer))) assert.FatalError(t, err) - + name, _ := os.MkdirTemp("", "") // GetTLSCertificate - cert, err := a.GetTLSCertificate() + cert, err := a.GetTLSCertificate(name, false) assert.FatalError(t, err) assert.Equals(t, []string{"localhost"}, cert.Leaf.DNSNames) assert.True(t, cert.Leaf.IPAddresses[0].Equal(net.ParseIP("127.0.0.1"))) diff --git a/authority/authorize_test.go b/authority/authorize_test.go index bec34fd68..316566d67 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -1375,7 +1375,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) { } generateX5cToken := func(a *Authority, key crypto.Signer, claims jose.Claims, opts ...provisioner.SignOption) (string, *x509.Certificate) { - chain, err := a.Sign(csr, provisioner.SignOptions{}, opts...) + chain, err := a.Sign(context.TODO(), csr, provisioner.SignOptions{}, opts...) if err != nil { t.Fatal(err) } diff --git a/authority/config/config.go b/authority/config/config.go index ba581d8a0..cfdcf935a 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -63,6 +63,13 @@ var ( } ) +type MqttConfig struct { + Host string `json:"host"` + Username string `json:"username"` + Password string `json:"password"` + Organization string `json:"organization"` +} + // Config represents the CA configuration and it's mapped to a JSON object. type Config struct { Root multiString `json:"root"` @@ -70,6 +77,7 @@ type Config struct { IntermediateCert string `json:"crt"` IntermediateKey string `json:"key"` Address string `json:"address"` + PublicAddress string `json:"publicAddress"` InsecureAddress string `json:"insecureAddress"` DNSNames []string `json:"dnsNames"` KMS *kms.Options `json:"kms,omitempty"` @@ -84,6 +92,9 @@ type Config struct { CommonName string `json:"commonName,omitempty"` CRL *CRLConfig `json:"crl,omitempty"` SkipValidation bool `json:"-"` + Storage string `json:"storage,omitempty"` + ManagementHost string `json:"managementHost"` + ValidationBroker *MqttConfig `json:"validationBroker,omitempty"` // Keeps record of the filename the Config is read from loadedFromFilepath string diff --git a/authority/provisioners_test.go b/authority/provisioners_test.go index f6af6f548..16aac43c9 100644 --- a/authority/provisioners_test.go +++ b/authority/provisioners_test.go @@ -149,8 +149,8 @@ func TestAuthority_LoadProvisionerByCertificate(t *testing.T) { opts, err := a.Authorize(ctx, token) require.NoError(t, err) opts = append(opts, extraOpts...) - certs, err := a.Sign(csr, provisioner.SignOptions{}, opts...) - require.NoError(t, err) + certs, err := a.Sign(context.TODO(), csr, provisioner.SignOptions{}, opts...) + assert.FatalError(t, err) return certs[0] } getProvisioner := func(a *Authority, name string) provisioner.Interface { diff --git a/authority/tls.go b/authority/tls.go index 6e9679209..dd5dbd74d 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -14,6 +14,7 @@ import ( "math/big" "net" "net/http" + "os" "strings" "time" @@ -92,7 +93,7 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { } // Sign creates a signed certificate from a certificate signing request. -func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (a *Authority) Sign(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { var ( certOptions []x509util.Option certValidators []provisioner.CertificateValidator @@ -265,7 +266,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Sign certificate lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate)) - resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ + + resp, err := a.x509CAService.CreateCertificate(ctx, &casapi.CreateCertificateRequest{ Template: leaf, CSR: csr, Lifetime: lifetime, @@ -307,24 +309,10 @@ func (a *Authority) AreSANsAllowed(_ context.Context, sans []string) error { return a.policyEngine.AreSANsAllowed(sans) } -// Renew creates a new Certificate identical to the old certificate, except with -// a validity window that begins 'now'. -func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error) { - return a.RenewContext(context.Background(), oldCert, nil) -} - -// Rekey is used for rekeying and renewing based on the public key. If the -// public key is 'nil' then it's assumed that the cert should be renewed using -// the existing public key. If the public key is not 'nil' then it's assumed -// that the cert should be rekeyed. -// -// For both Rekey and Renew all other attributes of the new certificate should -// match the old certificate. The exceptions are 'AuthorityKeyId' (which may -// have changed), 'SubjectKeyId' (different in case of rekey), and -// 'NotBefore/NotAfter' (the validity duration of the new certificate should be -// equal to the old one, but starting 'now'). -func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { - return a.RenewContext(context.Background(), oldCert, pk) +// Renew creates a new Certificate identical to the old certificate, except +// with a validity window that begins 'now'. +func (a *Authority) Renew(ctx context.Context, oldCert *x509.Certificate) ([]*x509.Certificate, error) { + return a.Rekey(ctx, oldCert, nil) } // RenewContext creates a new certificate identical to the old one, but it can @@ -337,6 +325,20 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5 // of rekey), and 'NotBefore/NotAfter' (the validity duration of the new // certificate should be equal to the old one, but starting 'now'). func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { + return a.RenewContext(context.Background(), oldCert, pk) +} + +// Rekey is used for rekeying and renewing based on the public key. If the +// public key is 'nil' then it's assumed that the cert should be renewed using +// the existing public key. If the public key is not 'nil' then it's assumed +// that the cert should be rekeyed. +// +// For both Rekey and Renew all other attributes of the new certificate should +// match the old certificate. The exceptions are 'AuthorityKeyId' (which may +// have changed), 'SubjectKeyId' (different in case of rekey), and +// 'NotBefore/NotAfter' (the validity duration of the new certificate should be +// equal to the old one, but starting 'now'). +func (a *Authority) Rekey(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { isRekey := (pk != nil) opts := []errs.Option{ errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), @@ -429,7 +431,7 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, // mode, this can be used to renew a certificate. token, _ := TokenFromContext(ctx) - resp, err := a.x509CAService.RenewCertificate(&casapi.RenewCertificateRequest{ + resp, err := a.x509CAService.RenewCertificate(ctx, &casapi.RenewCertificateRequest{ Template: newCert, Lifetime: lifetime, Backdate: backdate, @@ -629,7 +631,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error // CAS operation, note that SoftCAS (default) is a noop. // The revoke happens when this is stored in the db. - _, err := a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{ + _, err := a.x509CAService.RevokeCertificate(ctx, &casapi.RevokeCertificateRequest{ Certificate: revokedCert, SerialNumber: rci.Serial, Reason: rci.Reason, @@ -814,16 +816,49 @@ func (a *Authority) GenerateCertificateRevocationList() error { } // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server. -func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { +func (a *Authority) GetTLSCertificate(storage string, renew bool) (*tls.Certificate, error) { fatal := func(err error) (*tls.Certificate, error) { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") } + var priv crypto.PrivateKey + data, err := os.ReadFile(fmt.Sprintf("%s/%s", storage, "ca.key")) + switch { + case err != nil && os.IsNotExist(err): + // Generate default key. + priv, err = keyutil.GenerateDefaultKey() + if err != nil { + return fatal(err) + } + pemutil.Serialize(priv, pemutil.ToFile(fmt.Sprintf("%s/%s", storage, "ca.key"), 0600)) + case err != nil: + return fatal(err) + default: + priv, err = pemutil.ParseKey(data) + if err != nil { + return fatal(err) + } + } - // Generate default key. - priv, err := keyutil.GenerateDefaultKey() + keyPEM, err := pemutil.Serialize(priv) if err != nil { return fatal(err) } + data, err = os.ReadFile(fmt.Sprintf("%s/%s", storage, "ca.crt")) + + if !renew && err == nil { + cert, err := pemutil.ParseCertificateBundle(data) + if err != nil { + return fatal(err) + } else if cert[0].NotAfter.After(time.Now().Add(7 * 24 * time.Hour)) { + tlsCrt, err := tls.X509KeyPair(data, pem.EncodeToMemory(keyPEM)) + if err != nil { + return fatal(err) + } + tlsCrt.Leaf = cert[0] + return &tlsCrt, nil + } + } + signer, ok := priv.(crypto.Signer) if !ok { return fatal(errors.New("private key is not a crypto.Signer")) @@ -870,7 +905,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { return fatal(err) } - resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ + resp, err := a.x509CAService.CreateCertificate(context.Background(), &casapi.CreateCertificateRequest{ Template: certTpl, CSR: cr, Lifetime: 24 * time.Hour, @@ -892,10 +927,6 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { Bytes: crt.Raw, })...) } - keyPEM, err := pemutil.Serialize(priv) - if err != nil { - return fatal(err) - } tlsCrt, err := tls.X509KeyPair(pemBlocks, pem.EncodeToMemory(keyPEM)) if err != nil { @@ -903,7 +934,9 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { } // Set leaf certificate tlsCrt.Leaf = resp.Certificate + os.WriteFile(fmt.Sprintf("%s/%s", storage, "ca.crt"), pemBlocks, 0600) return &tlsCrt, nil + } // RFC 5280, 5.2.5 diff --git a/authority/tls_test.go b/authority/tls_test.go index efcb78f83..ad029e9cb 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -846,7 +846,7 @@ ZYtQ9Ot36qc= t.Run(name, func(t *testing.T) { tc := genTestCase(t) - certChain, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...) + certChain, err := tc.auth.Sign(context.TODO(), tc.csr, tc.signOpts, tc.extraOpts...) if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) @@ -1060,9 +1060,9 @@ func TestAuthority_Renew(t *testing.T) { var certChain []*x509.Certificate if tc.auth != nil { - certChain, err = tc.auth.Renew(tc.cert) + certChain, err = tc.auth.Renew(context.Background(), tc.cert) } else { - certChain, err = a.Renew(tc.cert) + certChain, err = a.Renew(context.Background(), tc.cert) } if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { @@ -1265,9 +1265,9 @@ func TestAuthority_Rekey(t *testing.T) { var certChain []*x509.Certificate if tc.auth != nil { - certChain, err = tc.auth.Rekey(tc.cert, tc.pk) + certChain, err = tc.auth.Rekey(context.Background(), tc.cert, tc.pk) } else { - certChain, err = a.Rekey(tc.cert, tc.pk) + certChain, err = a.Rekey(context.Background(), tc.cert, tc.pk) } if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { @@ -1795,12 +1795,12 @@ func TestAuthority_constraints(t *testing.T) { t.Fatal(err) } - _, err = auth.Sign(csr, provisioner.SignOptions{}, templateOption) + _, err = auth.Sign(context.Background(), csr, provisioner.SignOptions{}, templateOption) if (err != nil) != tt.wantErr { t.Errorf("Authority.Sign() error = %v, wantErr %v", err, tt.wantErr) } - _, err = auth.Renew(cert) + _, err = auth.Renew(context.Background(), cert) if (err != nil) != tt.wantErr { t.Errorf("Authority.Renew() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/ca/bootstrap_test.go b/ca/bootstrap_test.go index 62c422d43..3fde61995 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -54,7 +54,7 @@ func startCABootstrapServer() *httptest.Server { if err != nil { panic(err) } - baseContext := buildContext(ca.auth, nil, nil, nil) + baseContext := buildContext(ca.auth, nil, nil, nil, nil, nil) srv.Config.Handler = ca.srv.Handler srv.Config.BaseContext = func(net.Listener) context.Context { return baseContext diff --git a/ca/ca.go b/ca/ca.go index 7baf24192..0be7afe64 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -6,10 +6,12 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "io" "log" "net" "net/http" "net/url" + "os" "reflect" "strings" "sync" @@ -17,10 +19,23 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "go.opentelemetry.io/contrib/propagators/b3" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + + "github.com/sirupsen/logrus" + "github.com/pkg/errors" "github.com/smallstep/certificates/acme" acmeAPI "github.com/smallstep/certificates/acme/api" acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql" + acmeMqtt "github.com/smallstep/certificates/acme/mqtt" + "github.com/smallstep/certificates/acme/validation" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/admin" @@ -35,6 +50,10 @@ import ( "github.com/smallstep/nosql" "go.step.sm/cli-utils/step" "go.step.sm/crypto/x509util" + + pb "github.com/hm-edu/portal-apis" + + "github.com/smallstep/certificates/cas/sectigocas/eab" ) type options struct { @@ -124,10 +143,12 @@ type CA struct { auth *authority.Authority config *config.Config srv *server.Server + public *server.Server insecureSrv *server.Server opts *options renewer *TLSRenewer compactStop chan struct{} + tp *sdktrace.TracerProvider } // New creates and initializes the CA with the given configuration and options. @@ -143,6 +164,31 @@ func New(cfg *config.Config, opts ...Option) (*CA, error) { // Init initializes the CA with the given configuration. func (ca *CA) Init(cfg *config.Config) (*CA, error) { + var exporter sdktrace.SpanExporter + var err error + if os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") == "" { + exporter, err = stdout.New(stdout.WithWriter(io.Discard)) + } else { + opts := []otlptracegrpc.Option{otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT"))} + client := otlptracegrpc.NewClient(opts...) + exporter, err = otlptrace.New(context.Background(), client) + } + if err != nil { + return nil, err + } + ca.tp = sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithBatcher(exporter), + sdktrace.WithResource( + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String("certificates"), + )), + ) + otel.SetTracerProvider(ca.tp) + + otel.SetTextMapPropagator(b3.New()) + // Set password, it's ok to set nil password, the ca will prompt for them if // they are required. opts := []authority.Option{ @@ -171,17 +217,32 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { return nil, err } ca.auth = auth + var tlsConfig *tls.Config - tlsConfig, clientTLSConfig, err := ca.getTLSConfig(auth) - if err != nil { - return nil, err + allInsecure := false + + if os.Getenv("STEP_TLS_INSECURE") == "1" { + allInsecure = true } - webhookTransport.TLSClientConfig = clientTLSConfig + if !allInsecure { + tls, clientTLSConfig, err := ca.getTLSConfig(auth, cfg) + tlsConfig = tls + if err != nil { + return nil, err + } + webhookTransport.TLSClientConfig = clientTLSConfig + } // Using chi as the main router mux := chi.NewRouter() handler := http.Handler(mux) + var publicHandler http.Handler + var publicMux *chi.Mux + if cfg.PublicAddress != "" { + publicMux = chi.NewRouter() + publicHandler = http.Handler(publicMux) + } insecureMux := chi.NewRouter() insecureHandler := http.Handler(insecureMux) @@ -228,8 +289,18 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { mux.Route("/2.0/acme", func(r chi.Router) { acmeAPI.Route(r) }) - } + if cfg.PublicAddress != "" { + publicMux.Route("/acme", func(r chi.Router) { + acmeAPI.Route(r) + }) + // Use 2.0 because, at the moment, our ACME api is only compatible with v2.0 + // of the ACME spec. + publicMux.Route("/2.0/acme", func(r chi.Router) { + acmeAPI.Route(r) + }) + } + } // Admin API Router if cfg.AuthorityConfig.EnableAdmin { adminDB := auth.GetAdminDatabase() @@ -285,6 +356,9 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } handler = m.Middleware(handler) insecureHandler = m.Middleware(insecureHandler) + if cfg.PublicAddress != "" { + publicHandler = m.Middleware(publicHandler) + } } // Add logger if configured @@ -295,15 +369,50 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } handler = logger.Middleware(handler) insecureHandler = logger.Middleware(insecureHandler) + if cfg.PublicAddress != "" { + publicHandler = logger.Middleware(publicHandler) + } } // Create context with all the necessary values. - baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker) + client, err := eab.Connect(cfg.ManagementHost) + if err != nil { + return nil, errors.Wrap(err, "error connecting to EAB") + } + var validationBroker validation.MqttClient + if cfg.ValidationBroker != nil { + if cfg.ValidationBroker.Password == "" { + // pick password from env + cfg.ValidationBroker.Password = os.Getenv("MQTT_PASSWORD") + } - ca.srv = server.New(cfg.Address, handler, tlsConfig) + validationBroker, err = acmeMqtt.Connect(acmeDB, cfg.ValidationBroker.Host, cfg.ValidationBroker.Username, cfg.ValidationBroker.Password, cfg.ValidationBroker.Organization) + if err != nil { + logrus.Warn("error connecting to validation broker. Only local validation will be available!") + } + } + + baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker, client, validationBroker) + + if allInsecure { + ca.srv = server.New(cfg.Address, handler, nil) + } else { + ca.srv = server.New(cfg.Address, handler, tlsConfig) + } ca.srv.BaseContext = func(net.Listener) context.Context { return baseContext } + if cfg.PublicAddress != "" { + if allInsecure { + ca.public = server.New(cfg.PublicAddress, publicHandler, nil) + } else { + ca.public = server.New(cfg.PublicAddress, publicHandler, tlsConfig) + } + + ca.public.BaseContext = func(net.Listener) context.Context { + return baseContext + } + } // only start the insecure server if the insecure address is configured // and, currently, also only when it should serve SCEP endpoints. @@ -339,7 +448,7 @@ func (ca *CA) shouldServeInsecureServer() bool { } // buildContext builds the server base context. -func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB acme.DB, acmeLinker acme.Linker) context.Context { +func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB acme.DB, acmeLinker acme.Linker, eabClient pb.EABServiceClient, validationBroker validation.MqttClient) context.Context { ctx := authority.NewContext(context.Background(), a) if authDB := a.GetDatabase(); authDB != nil { ctx = db.NewContext(ctx, authDB) @@ -353,6 +462,12 @@ func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB if acmeDB != nil { ctx = acme.NewContext(ctx, acmeDB, acme.NewClient(), acmeLinker, nil) } + if eabClient != nil { + ctx = eab.NewContext(ctx, eabClient) + } + if validationBroker != nil { + ctx = validation.NewContext(ctx, validationBroker) + } return ctx } @@ -409,6 +524,13 @@ func (ca *CA) Run() error { defer wg.Done() errs <- ca.srv.ListenAndServe() }() + wg.Add(1) + if ca.public != nil { + go func() { + defer wg.Done() + errs <- ca.public.ListenAndServe() + }() + } // wait till error occurs; ensures the servers keep listening err := <-errs @@ -426,15 +548,25 @@ func (ca *CA) Stop() error { log.Printf("error stopping ca.Authority: %+v\n", err) } var insecureShutdownErr error + var publicErr error if ca.insecureSrv != nil { insecureShutdownErr = ca.insecureSrv.Shutdown() } + if ca.public != nil { + publicErr = ca.public.Shutdown() + } secureErr := ca.srv.Shutdown() - + err := ca.tp.Shutdown(context.Background()) + if err != nil { + return err + } if insecureShutdownErr != nil { return insecureShutdownErr } + if publicErr != nil { + return publicErr + } return secureErr } @@ -484,6 +616,12 @@ func (ca *CA) Reload() error { logContinue("Reload failed because server could not be replaced.") return errors.Wrap(err, "error reloading server") } + if ca.public != nil { + if err = ca.public.Reload(newCA.public); err != nil { + logContinue("Reload failed because server could not be replaced.") + return errors.Wrap(err, "error reloading server") + } + } // 1. Stop previous renewer // 2. Safely shutdown any internal resources (e.g. key manager) @@ -500,9 +638,17 @@ func (ca *CA) Reload() error { // get TLSConfig returns separate TLSConfigs for server and client with the // same self-renewing certificate. -func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, *tls.Config, error) { +func (ca *CA) getTLSConfig(auth *authority.Authority, cfg *config.Config) (*tls.Config, *tls.Config, error) { + + if cfg.Storage != "" { + err := os.Mkdir(cfg.Storage, 0600) + if err != nil && !os.IsExist(err) { + return nil, nil, errors.Wrap(err, "error creating storage directory") + } + } + // Create initial TLS certificate - tlsCrt, err := auth.GetTLSCertificate() + tlsCrt, err := auth.GetTLSCertificate(cfg.Storage, false) if err != nil { return nil, nil, err } @@ -513,7 +659,9 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, *tls.Config, ca.renewer.Stop() } - ca.renewer, err = NewTLSRenewer(tlsCrt, auth.GetTLSCertificate) + ca.renewer, err = NewTLSRenewer(tlsCrt, func() (*tls.Certificate, error) { + return auth.GetTLSCertificate(cfg.Storage, true) + }) if err != nil { return nil, nil, err } diff --git a/ca/tls_test.go b/ca/tls_test.go index dbcc6023d..1ee06ae23 100644 --- a/ca/tls_test.go +++ b/ca/tls_test.go @@ -82,7 +82,7 @@ func startCATestServer() *httptest.Server { panic(err) } // Use a httptest.Server instead - baseContext := buildContext(ca.auth, nil, nil, nil) + baseContext := buildContext(ca.auth, nil, nil, nil, nil, nil) srv := startTestServer(baseContext, ca.srv.TLSConfig, ca.srv.Handler) return srv } diff --git a/cas/apiv1/options_test.go b/cas/apiv1/options_test.go index d48b63df8..891779938 100644 --- a/cas/apiv1/options_test.go +++ b/cas/apiv1/options_test.go @@ -12,15 +12,15 @@ type testCAS struct { name string } -func (t *testCAS) CreateCertificate(*CreateCertificateRequest) (*CreateCertificateResponse, error) { +func (t *testCAS) CreateCertificate(_ context.Context, req *CreateCertificateRequest) (*CreateCertificateResponse, error) { return nil, nil } -func (t *testCAS) RenewCertificate(*RenewCertificateRequest) (*RenewCertificateResponse, error) { +func (t *testCAS) RenewCertificate(_ context.Context, req *RenewCertificateRequest) (*RenewCertificateResponse, error) { return nil, nil } -func (t *testCAS) RevokeCertificate(*RevokeCertificateRequest) (*RevokeCertificateResponse, error) { +func (t *testCAS) RevokeCertificate(_ context.Context, req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) { return nil, nil } diff --git a/cas/apiv1/services.go b/cas/apiv1/services.go index bca24d96f..1eff1cc8d 100644 --- a/cas/apiv1/services.go +++ b/cas/apiv1/services.go @@ -1,6 +1,7 @@ package apiv1 import ( + "context" "crypto/x509" "net/http" "strings" @@ -9,9 +10,9 @@ import ( // CertificateAuthorityService is the interface implemented to support external // certificate authorities. type CertificateAuthorityService interface { - CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) - RenewCertificate(req *RenewCertificateRequest) (*RenewCertificateResponse, error) - RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) + CreateCertificate(context context.Context, req *CreateCertificateRequest) (*CreateCertificateResponse, error) + RenewCertificate(context context.Context, req *RenewCertificateRequest) (*RenewCertificateResponse, error) + RevokeCertificate(context context.Context, req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) } // CertificateAuthorityCRLGenerator is an optional interface implemented by CertificateAuthorityService @@ -53,6 +54,8 @@ const ( StepCAS = "stepcas" // VaultCAS is a CertificateAuthorityService using Hasicorp Vault PKI. VaultCAS = "vaultcas" + // SectigoCAS is a CertificateAuthorityService using sectigocas. + SectigoCAS = "sectigocas" ) // String returns a string from the type. It will always return the lower case diff --git a/cas/cas_test.go b/cas/cas_test.go index 9fc06567a..7aa981e90 100644 --- a/cas/cas_test.go +++ b/cas/cas_test.go @@ -18,15 +18,15 @@ import ( type mockCAS struct{} -func (m *mockCAS) CreateCertificate(*apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { +func (m *mockCAS) CreateCertificate(ctx context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { panic("not implemented") } -func (m *mockCAS) RenewCertificate(*apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { +func (m *mockCAS) RenewCertificate(ctx context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { panic("not implemented") } -func (m *mockCAS) RevokeCertificate(*apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { +func (m *mockCAS) RevokeCertificate(ctx context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { panic("not implemented") } diff --git a/cas/cloudcas/cloudcas.go b/cas/cloudcas/cloudcas.go index c9c8364f2..a8d76610c 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -188,7 +188,7 @@ func (c *CloudCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityReq } // CreateCertificate signs a new certificate using Google Cloud CAS. -func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { +func (c *CloudCAS) CreateCertificate(_ context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { case req.Template == nil: return nil, errors.New("createCertificateRequest `template` cannot be nil") @@ -210,7 +210,7 @@ func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv // RenewCertificate renews the given certificate using Google Cloud CAS. // Google's CAS does not support the renew operation, so this method uses // CreateCertificate. -func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { +func (c *CloudCAS) RenewCertificate(_ context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { switch { case req.Template == nil: return nil, errors.New("renewCertificateRequest `template` cannot be nil") @@ -230,7 +230,7 @@ func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1. } // RevokeCertificate revokes a certificate using Google Cloud CAS. -func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { +func (c *CloudCAS) RevokeCertificate(_ context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { reason, ok := revocationCodeMap[req.ReasonCode] switch { case !ok: diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 95446ee65..8eab42cc0 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -532,7 +532,7 @@ func TestCloudCAS_CreateCertificate(t *testing.T) { client: tt.fields.client, certificateAuthority: tt.fields.certificateAuthority, } - got, err := c.CreateCertificate(tt.args.req) + got, err := c.CreateCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -648,7 +648,7 @@ func TestCloudCAS_RenewCertificate(t *testing.T) { client: tt.fields.client, certificateAuthority: tt.fields.certificateAuthority, } - got, err := c.RenewCertificate(tt.args.req) + got, err := c.RenewCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -727,7 +727,7 @@ func TestCloudCAS_RevokeCertificate(t *testing.T) { client: tt.fields.client, certificateAuthority: tt.fields.certificateAuthority, } - got, err := c.RevokeCertificate(tt.args.req) + got, err := c.RevokeCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/cas/sectigocas/eab/client.go b/cas/sectigocas/eab/client.go new file mode 100644 index 000000000..d93e04603 --- /dev/null +++ b/cas/sectigocas/eab/client.go @@ -0,0 +1,50 @@ +package eab + +import ( + "context" + + pb "github.com/hm-edu/portal-apis" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type eabKey struct{} + +// NewContext adds the given eab client to the context. +func NewContext(ctx context.Context, a pb.EABServiceClient) context.Context { + return context.WithValue(ctx, eabKey{}, a) +} + +// FromContext returns the eab client from the given context. +func FromContext(ctx context.Context) (a pb.EABServiceClient, ok bool) { + a, ok = ctx.Value(eabKey{}).(pb.EABServiceClient) + return +} + +// MustFromContext returns the eab client from the given context. It will +// panic if no eab client is not in the context. +func MustFromContext(ctx context.Context) pb.EABServiceClient { + if a, ok := FromContext(ctx); !ok { + panic("eab client is not in the context") + } else { + return a + } +} + +func Connect(host string) (pb.EABServiceClient, error) { + + conn, err := grpc.DialContext( + context.Background(), + host, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + ) + if err != nil { + return nil, err + } + + apiClient := pb.NewEABServiceClient(conn) + return apiClient, nil + +} diff --git a/cas/sectigocas/sectigocas.go b/cas/sectigocas/sectigocas.go new file mode 100644 index 000000000..e7530e376 --- /dev/null +++ b/cas/sectigocas/sectigocas.go @@ -0,0 +1,187 @@ +package sectigocas + +import ( + "context" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "time" + + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/acme/api" + "github.com/smallstep/certificates/authority/provisioner" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + + pb "github.com/hm-edu/portal-apis" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/smallstep/certificates/cas/apiv1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type Options struct { + PKIBackend string `json:"pkiBackend"` + EABBackend string `json:"eabBackend"` +} + +func init() { + apiv1.Register(apiv1.SectigoCAS, func(ctx context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) { + return New(ctx, opts) + }) +} + +func New(ctx context.Context, opts apiv1.Options) (*SectigoCAS, error) { + var config Options + err := json.Unmarshal(opts.Config, &config) + if err != nil { + return nil, err + } + ctx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + + conn, err := grpc.DialContext( + ctx, + config.PKIBackend, + grpc.WithBlock(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + ) + if err != nil { + return nil, err + } + sslServiceClient := pb.NewSSLServiceClient(conn) + conn, err = grpc.DialContext( + ctx, + config.EABBackend, + grpc.WithBlock(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + ) + if err != nil { + return nil, err + } + eabClient := pb.NewEABServiceClient(conn) + + return &SectigoCAS{sslServiceClient: sslServiceClient, eabClient: eabClient, logger: logrus.StandardLogger()}, nil +} + +type SectigoCAS struct { + sslServiceClient pb.SSLServiceClient + eabClient pb.EABServiceClient + logger *logrus.Logger +} + +func parseCertificates(cert []byte) ([]*x509.Certificate, error) { + var certs []*x509.Certificate + for block, rest := pem.Decode(cert); block != nil; block, rest = pem.Decode(rest) { + switch block.Type { + case "CERTIFICATE": + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + certs = append(certs, cert) + default: + return nil, errors.New("Unknown entry in cert chain") + } + } + return certs, nil +} +func accountFromContext(ctx context.Context) *acme.Account { + val, ok := ctx.Value(api.AccContextKey).(*acme.Account) + if !ok || val == nil { + return nil + } + return val +} + +func (s *SectigoCAS) signCertificate(ctx context.Context, cr *x509.CertificateRequest) (*x509.Certificate, []*x509.Certificate, error) { + sans := make([]string, 0, len(cr.DNSNames)+len(cr.EmailAddresses)+len(cr.IPAddresses)+len(cr.URIs)) + sans = append(sans, cr.DNSNames...) + for _, ip := range cr.IPAddresses { + sans = append(sans, ip.String()) + } + for _, u := range cr.URIs { + sans = append(sans, u.String()) + } + + issuer := "" + prov, ok := acme.ProvisionerFromContext(ctx) + if !ok || prov == nil { + issuer = "Internal" + } else { + acmeProv, ok := prov.(*provisioner.ACME) + if !ok || acmeProv == nil { + return nil, nil, errors.New("No ACME provisioner passed!") + } + if acmeProv.RequireEAB { + acc := accountFromContext(ctx) + if acc == nil { + return nil, nil, errors.New("No account passed!") + } + user, err := s.eabClient.ResolveAccountId(context.Background(), &pb.ResolveAccountIdRequest{AccountId: acc.ID}) + if err != nil { + return nil, nil, errors.WithMessage(err, "Error resolving user account!") + } + issuer = fmt.Sprintf("%v (EAB: %v)", user.User, user.EabKey) + } + + } + + certificates, err := s.sslServiceClient.IssueCertificate(context.Background(), &pb.IssueSslRequest{ + Issuer: issuer, + SubjectAlternativeNames: sans, + Source: "ACME", + Csr: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: cr.Raw})), + }) + if err != nil { + s.logger.WithField("error", err).Error("Failed to sign certificate") + return nil, nil, err + } + certs, err := parseCertificates([]byte(certificates.Certificate)) + if err != nil { + s.logger.WithField("error", err).Error("Failed to parse certificate") + return nil, nil, err + } + return certs[0], certs[1:], nil +} + +func (s *SectigoCAS) CreateCertificate(ctx context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { + cert, chain, err := s.signCertificate(ctx, req.CSR) + if err != nil { + return nil, err + } + return &apiv1.CreateCertificateResponse{ + Certificate: cert, + CertificateChain: chain, + }, nil +} + +func (s *SectigoCAS) RenewCertificate(ctx context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { + cert, chain, err := s.signCertificate(ctx, req.CSR) + if err != nil { + return nil, err + } + return &apiv1.RenewCertificateResponse{ + Certificate: cert, + CertificateChain: chain, + }, nil +} + +func (s *SectigoCAS) RevokeCertificate(ctx context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { + _, err := s.sslServiceClient.RevokeCertificate(context.Background(), &pb.RevokeSslRequest{ + Identifier: &pb.RevokeSslRequest_Serial{Serial: req.SerialNumber}, + Reason: req.Reason, + }) + if err != nil { + s.logger.WithField("error", err).Error("Failed to revoke certificate") + return nil, err + } + + return &apiv1.RevokeCertificateResponse{ + Certificate: req.Certificate, + CertificateChain: nil, + }, nil +} diff --git a/cas/softcas/softcas.go b/cas/softcas/softcas.go index 58be8aab8..0c4f8fc0b 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -54,7 +54,7 @@ func New(_ context.Context, opts apiv1.Options) (*SoftCAS, error) { } // CreateCertificate signs a new certificate using Golang or KMS crypto. -func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { +func (c *SoftCAS) CreateCertificate(_ context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { case req.Template == nil: return nil, errors.New("createCertificateRequest `template` cannot be nil") @@ -90,7 +90,7 @@ func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 } // RenewCertificate signs the given certificate template using Golang or KMS crypto. -func (c *SoftCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { +func (c *SoftCAS) RenewCertificate(_ context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { switch { case req.Template == nil: return nil, errors.New("createCertificateRequest `template` cannot be nil") @@ -122,7 +122,7 @@ func (c *SoftCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.R // RevokeCertificate revokes the given certificate in step-ca. In SoftCAS this // operation is a no-op as the actual revoke will happen when we store the entry // in the db. -func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { +func (c *SoftCAS) RevokeCertificate(_ context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { chain, _, err := c.getCertSigner() if err != nil { return nil, err diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 11bf217aa..ed96dca6c 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -339,7 +339,7 @@ func TestSoftCAS_CreateCertificate(t *testing.T) { Signer: tt.fields.Signer, CertificateSigner: tt.fields.CertificateSigner, } - got, err := c.CreateCertificate(tt.args.req) + got, err := c.CreateCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -383,7 +383,7 @@ func TestSoftCAS_CreateCertificate_pss(t *testing.T) { CertificateChain: []*x509.Certificate{iss}, Signer: signer, } - cert, err := c.CreateCertificate(&apiv1.CreateCertificateRequest{ + cert, err := c.CreateCertificate(context.Background(), &apiv1.CreateCertificateRequest{ Template: &x509.Certificate{ Subject: pkix.Name{CommonName: "test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"}, @@ -469,7 +469,7 @@ func TestSoftCAS_CreateCertificate_ec_rsa(t *testing.T) { CertificateChain: []*x509.Certificate{iss}, Signer: intSigner, } - cert, err := c.CreateCertificate(&apiv1.CreateCertificateRequest{ + cert, err := c.CreateCertificate(context.Background(), &apiv1.CreateCertificateRequest{ Template: &x509.Certificate{ Subject: pkix.Name{CommonName: "test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"}, @@ -567,7 +567,7 @@ func TestSoftCAS_RenewCertificate(t *testing.T) { Signer: tt.fields.Signer, CertificateSigner: tt.fields.CertificateSigner, } - got, err := c.RenewCertificate(tt.args.req) + got, err := c.RenewCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -635,7 +635,7 @@ func TestSoftCAS_RevokeCertificate(t *testing.T) { Signer: tt.fields.Signer, CertificateSigner: tt.fields.CertificateSigner, } - got, err := c.RevokeCertificate(tt.args.req) + got, err := c.RevokeCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/cas/stepcas/stepcas.go b/cas/stepcas/stepcas.go index 9f94c6aed..465df72ab 100644 --- a/cas/stepcas/stepcas.go +++ b/cas/stepcas/stepcas.go @@ -67,7 +67,7 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) { // CreateCertificate uses the step-ca sign request with the configured // provisioner to get a new certificate from the certificate authority. -func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { +func (s *StepCAS) CreateCertificate(ctx context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { case req.CSR == nil: return nil, errors.New("createCertificateRequest `csr` cannot be nil") @@ -100,7 +100,7 @@ func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 // RenewCertificate will always return a non-implemented error as mTLS renewals // are not supported yet. -func (s *StepCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { +func (s *StepCAS) RenewCertificate(ctx context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { if req.Token == "" { return nil, apiv1.ValidationError{Message: "renewCertificateRequest `token` cannot be empty"} } @@ -123,7 +123,7 @@ func (s *StepCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.R } // RevokeCertificate revokes a certificate. -func (s *StepCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { +func (s *StepCAS) RevokeCertificate(ctx context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { if req.SerialNumber == "" && req.Certificate == nil { return nil, errors.New("revokeCertificateRequest `serialNumber` or `certificate` are required") } diff --git a/cas/stepcas/stepcas_test.go b/cas/stepcas/stepcas_test.go index b9dd9abd4..0a59b6fc5 100644 --- a/cas/stepcas/stepcas_test.go +++ b/cas/stepcas/stepcas_test.go @@ -719,7 +719,7 @@ func TestStepCAS_CreateCertificate(t *testing.T) { authorityID: "authority-id", fingerprint: tt.fields.fingerprint, } - got, err := s.CreateCertificate(tt.args.req) + got, err := s.CreateCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("StepCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -784,7 +784,7 @@ func TestStepCAS_RenewCertificate(t *testing.T) { client: tt.fields.client, fingerprint: tt.fields.fingerprint, } - got, err := s.RenewCertificate(tt.args.req) + got, err := s.RenewCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("StepCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -884,7 +884,7 @@ func TestStepCAS_RevokeCertificate(t *testing.T) { client: tt.fields.client, fingerprint: tt.fields.fingerprint, } - got, err := s.RevokeCertificate(tt.args.req) + got, err := s.RevokeCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("StepCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/cas/vaultcas/vaultcas.go b/cas/vaultcas/vaultcas.go index 5908cb7df..5fb448f53 100644 --- a/cas/vaultcas/vaultcas.go +++ b/cas/vaultcas/vaultcas.go @@ -111,7 +111,7 @@ func New(ctx context.Context, opts apiv1.Options) (*VaultCAS, error) { } // CreateCertificate signs a new certificate using Hashicorp Vault. -func (v *VaultCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { +func (v *VaultCAS) CreateCertificate(_ context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { case req.CSR == nil: return nil, errors.New("createCertificate `csr` cannot be nil") @@ -166,12 +166,12 @@ func (v *VaultCAS) GetCertificateAuthority(*apiv1.GetCertificateAuthorityRequest // RenewCertificate will always return a non-implemented error as renewals // are not supported yet. -func (v *VaultCAS) RenewCertificate(*apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { +func (v *VaultCAS) RenewCertificate(_ context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { return nil, apiv1.NotImplementedError{Message: "vaultCAS does not support renewals"} } // RevokeCertificate revokes a certificate by serial number. -func (v *VaultCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { +func (v *VaultCAS) RevokeCertificate(_ context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { if req.SerialNumber == "" && req.Certificate == nil { return nil, errors.New("revokeCertificate `serialNumber` or `certificate` are required") } diff --git a/cas/vaultcas/vaultcas_test.go b/cas/vaultcas/vaultcas_test.go index 0ea0c4b1f..4d1ad40ae 100644 --- a/cas/vaultcas/vaultcas_test.go +++ b/cas/vaultcas/vaultcas_test.go @@ -256,7 +256,7 @@ func TestVaultCAS_CreateCertificate(t *testing.T) { client: tt.fields.client, config: tt.fields.options, } - got, err := c.CreateCertificate(tt.args.req) + got, err := c.CreateCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("VaultCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -379,7 +379,7 @@ func TestVaultCAS_RevokeCertificate(t *testing.T) { client: tt.fields.client, config: tt.fields.options, } - got, err := s.RevokeCertificate(tt.args.req) + got, err := s.RevokeCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("VaultCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr) return @@ -429,7 +429,7 @@ func TestVaultCAS_RenewCertificate(t *testing.T) { client: tt.fields.client, config: tt.fields.options, } - got, err := s.RenewCertificate(tt.args.req) + got, err := s.RenewCertificate(context.TODO(), tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("VaultCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/cmd/step-agent/main.go b/cmd/step-agent/main.go new file mode 100644 index 000000000..85d322a25 --- /dev/null +++ b/cmd/step-agent/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + "os/signal" + "strings" + "syscall" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/sirupsen/logrus" + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/acme/validation" + "github.com/urfave/cli" + "go.step.sm/cli-utils/step" + "go.step.sm/cli-utils/ui" +) + +var agent = cli.Command{ + Name: "agent", + Usage: "start the step-ca agent", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "host", + Usage: "the host of the mqtt broker", + }, + cli.StringFlag{ + Name: "user", + Usage: "the user for the mqtt broker", + }, + cli.StringFlag{ + Name: "password", + Usage: "the password for the mqtt broker", + }, + cli.StringFlag{ + Name: "organization", + Usage: "the organization for the mqtt broker connection", + }, + }, + + Action: func(c *cli.Context) error { + options := mqtt.NewClientOptions() + options.SetOrderMatters(false) + options.ConnectTimeout = time.Second + options.WriteTimeout = time.Second + options.KeepAlive = 10 + options.PingTimeout = time.Second + options.ConnectRetry = true + options.AutoReconnect = true + options.ClientID = fmt.Sprintf("acme-agent-%s-%d", c.String("organization"), time.Now().UnixNano()) + options.Username = c.String("user") + options.Password = c.String("password") + options.AddBroker(fmt.Sprintf("ssl://%s:8883", c.String("host"))) + logrus.Infof("connecting to mqtt broker") + + // Establish connection to MQTT broker + options.OnConnectionLost = func(cl mqtt.Client, err error) { + logrus.Println("mqtt connection lost") + } + options.OnConnect = func(cl mqtt.Client) { + logrus.Println("mqtt connection established") + // Subscribe to topic + token := cl.Subscribe(fmt.Sprintf("%s/jobs", c.String("organization")), 0, func(client mqtt.Client, msg mqtt.Message) { + logrus.Infof("received message on topic %s", msg.Topic()) + logrus.Infof("message: %s", msg.Payload()) + + var data validation.ValidationRequest + + req := msg.Payload() + json.Unmarshal(req, &data) + + logger := logrus.WithField("authz", data.Authz).WithField("target", data.Target).WithField("account", data.Challenge) + + http := acme.NewClient() + resp, err := http.Get(data.Target) + if err != nil { + logger.WithError(err).Warn("validating failed") + return + } + + defer resp.Body.Close() + if resp.StatusCode >= 400 { + logger.Warnf("validation for %s failed with error: %s", data.Target, resp.Status) + return + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + logger.WithError(err).Warn("parsing body failed") + return + } + + keyAuth := strings.TrimSpace(string(body)) + logger.Infof("keyAuth: %s", keyAuth) + + json, err := json.Marshal(&validation.ValidationResponse{ + Authz: data.Authz, + Challenge: data.Challenge, + Content: keyAuth, + }) + if err != nil { + logger.WithError(err).Warn("marshalling failed") + return + } + // Publish to topic + token := cl.Publish(fmt.Sprintf("%s/data", c.String("organization")), 0, false, json) + if token.WaitTimeout(30*time.Second) && token.Error() != nil { + logger.WithError(token.Error()).Warn("publishing failed") + } else { + logger.Infof("published to topic %s", fmt.Sprintf("%s/data", c.String("organization"))) + } + + }) + + if token.WaitTimeout(30*time.Second) && token.Error() != nil { + logrus.WithError(token.Error()).Warn("subscribing failed") + } else { + logrus.Infof("subscribed to topic %s", fmt.Sprintf("%s/jobs", c.String("organization"))) + } + } + + options.OnReconnecting = func(mqtt.Client, *mqtt.ClientOptions) { + logrus.Println("mqtt reconnecting") + } + + client := mqtt.NewClient(options) + if token := client.Connect(); token.WaitTimeout(30*time.Second) && token.Error() != nil { + logrus.Warn(token.Error()) + } + + return nil + }, +} + +// commit and buildTime are filled in during build by the Makefile +var ( + BuildTime = "N/A" + Version = "N/A" +) + +func init() { + step.Set("Smallstep Agent", Version, BuildTime) +} + +func exit(code int) { + ui.Reset() + os.Exit(code) +} + +func main() { + ui.Init() + app := cli.NewApp() + app.Name = "step-agent" + app.Usage = "step-agent" + app.Version = step.Version() + app.Action = func(c *cli.Context) error { + return agent.Run(c) + } + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + done := make(chan bool, 1) + + go func() { + <-sigs + done <- true + }() + + if err := app.Run(os.Args); err != nil { + logrus.Warn(err) + exit(1) + } + + <-done + exit(0) +} diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 289815efd..b68bebba8 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -37,6 +37,7 @@ import ( // Enabled cas interfaces. _ "github.com/smallstep/certificates/cas/cloudcas" + _ "github.com/smallstep/certificates/cas/sectigocas" _ "github.com/smallstep/certificates/cas/softcas" _ "github.com/smallstep/certificates/cas/stepcas" _ "github.com/smallstep/certificates/cas/vaultcas" diff --git a/db/db.go b/db/db.go index 03295f228..fba4de74c 100644 --- a/db/db.go +++ b/db/db.go @@ -60,6 +60,7 @@ type AuthDB interface { IsSSHHost(name string) (bool, error) GetSSHHostPrincipals() ([]string, error) Shutdown() error + Ping() error } type dbKey struct{} @@ -522,6 +523,11 @@ func (m *MockAuthDB) StoreCRL(info *CertificateRevocationListInfo) error { return m.Err } +// Ping mock +func (m *MockAuthDB) Ping() error { + return nil +} + // IsRevoked mock. func (m *MockAuthDB) IsRevoked(sn string) (bool, error) { if m.MIsRevoked != nil { @@ -640,6 +646,11 @@ type MockNoSQLDB struct { MCmpAndSwap func(bucket, key, old, newval []byte) ([]byte, bool, error) } +// Ping mock +func (m *MockNoSQLDB) Ping() error { + return nil +} + // CmpAndSwap mock func (m *MockNoSQLDB) CmpAndSwap(bucket, key, old, newval []byte) ([]byte, bool, error) { if m.MCmpAndSwap != nil { diff --git a/db/simple.go b/db/simple.go index dbef2d615..078802177 100644 --- a/db/simple.go +++ b/db/simple.go @@ -26,6 +26,11 @@ func newSimpleDB(*Config) (*SimpleDB, error) { return db, nil } +// Ping noop +func (s *SimpleDB) Ping() error { + return nil +} + // IsRevoked noop func (s *SimpleDB) IsRevoked(string) (bool, error) { return false, nil diff --git a/docker/Dockerfile.agent b/docker/Dockerfile.agent new file mode 100644 index 000000000..3d9ebe4b0 --- /dev/null +++ b/docker/Dockerfile.agent @@ -0,0 +1,18 @@ +FROM golang:alpine AS builder + +WORKDIR /src +COPY . . + +RUN apk add --no-cache curl git make libcap +RUN PKG=github.com/smallstep/certificates/cmd/step-agent BINNAME=step-agent make V=1 bin/step-agent +RUN setcap CAP_NET_BIND_SERVICE=+eip bin/step-agent + +FROM smallstep/step-cli:latest + +COPY --from=builder /src/bin/step-agent /usr/local/bin/step-agent + +USER step + +VOLUME ["/home/step"] +STOPSIGNAL SIGTERM +CMD exec /usr/local/bin/step-agent diff --git a/go.mod b/go.mod index a1e1c3b5e..7b9bd4084 100644 --- a/go.mod +++ b/go.mod @@ -3,26 +3,33 @@ module github.com/smallstep/certificates go 1.20 require ( + cloud.google.com/go v0.110.8 // indirect cloud.google.com/go/longrunning v0.5.2 cloud.google.com/go/security v1.15.2 github.com/Masterminds/sprig/v3 v3.2.3 + github.com/ThalesIgnite/crypto11 v1.2.5 // indirect + github.com/aws/aws-sdk-go v1.45.26 // indirect github.com/dgraph-io/badger v1.6.2 github.com/dgraph-io/badger/v2 v2.2007.4 + github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/fxamacker/cbor/v2 v2.5.0 github.com/go-chi/chi/v5 v5.0.10 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/go-tpm v0.9.0 - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.4.0 github.com/googleapis/gax-go/v2 v2.12.0 github.com/hashicorp/vault/api v1.10.0 github.com/hashicorp/vault/api/auth/approle v0.5.0 github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 + github.com/hm-edu/portal-apis v0.0.0-20230929065638-ad1f7e7c7ab3 + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/newrelic/go-agent/v3 v3.27.0 github.com/pkg/errors v0.9.1 github.com/rs/xid v1.5.0 github.com/sirupsen/logrus v1.9.3 - github.com/slackhq/nebula v1.6.1 + github.com/slackhq/nebula v1.7.2 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2 github.com/smallstep/nosql v0.6.0 @@ -34,7 +41,7 @@ require ( go.step.sm/crypto v0.36.1 go.step.sm/linkedca v0.20.1 golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 + golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 golang.org/x/net v0.17.0 google.golang.org/api v0.148.0 google.golang.org/grpc v1.59.0 @@ -43,11 +50,23 @@ require ( ) require ( - cloud.google.com/go v0.110.8 // indirect cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.2 // indirect cloud.google.com/go/kms v1.15.3 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 + go.opentelemetry.io/otel v1.19.0 +) + +require ( + go.opentelemetry.io/contrib/propagators/b3 v1.20.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 + go.opentelemetry.io/otel/sdk v1.19.0 +) + +require ( filippo.io/edwards25519 v1.0.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 // indirect @@ -55,19 +74,17 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/ThalesIgnite/crypto11 v1.2.5 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect - github.com/aws/aws-sdk-go v1.45.26 // indirect - github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cenkalti/backoff/v3 v3.2.2 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect @@ -87,31 +104,30 @@ require ( github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huandu/xstrings v1.3.3 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.0 // indirect + github.com/jackc/pgconn v1.14.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx/v4 v4.18.0 // indirect + github.com/jackc/pgx/v4 v4.18.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/klauspost/compress v1.16.3 // indirect + github.com/klauspost/compress v1.17.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.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -124,23 +140,35 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/schollz/jsonstore v1.1.0 // indirect - github.com/shopspring/decimal v1.2.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.49.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +require ( + github.com/eclipse/paho.mqtt.golang v1.4.3 + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + golang.org/x/time v0.3.0 // indirect +) + +replace github.com/smallstep/nosql => github.com/hm-edu/nosql v0.4.1-0.20230305071512-139857866201 diff --git a/go.sum b/go.sum index f923c3f41..5ab356aa5 100644 --- a/go.sum +++ b/go.sum @@ -27,15 +27,16 @@ github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= -github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= -github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= @@ -49,8 +50,11 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/aws/aws-sdk-go v1.45.26 h1:PJ2NJNY5N/yeobLYe1Y+xLdavBi67ZI8gvph6ftwVCg= github.com/aws/aws-sdk-go v1.45.26/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= +github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -68,6 +72,7 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -88,8 +93,8 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= -github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -97,12 +102,16 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= +github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= @@ -120,6 +129,11 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg= github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= @@ -187,12 +201,16 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -205,17 +223,20 @@ github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= +github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= @@ -224,11 +245,16 @@ github.com/hashicorp/vault/api/auth/approle v0.5.0 h1:a1TK6VGwYqSAfkmX4y4dJ4WBxM github.com/hashicorp/vault/api/auth/approle v0.5.0/go.mod h1:CHOQIA1AZACfjTzHggmyfiOZ+xCSKNRFqe48FTCzH0k= github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 h1:CXO0fD7M3iCGovP/UApeHhPcH4paDFKcu7AjEXi94rI= github.com/hashicorp/vault/api/auth/kubernetes v0.5.0/go.mod h1:afrElBIO9Q4sHFVuVWgNevG4uAs1bT2AZFA9aEiI608= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/hm-edu/nosql v0.4.1-0.20230305071512-139857866201 h1:KB8SVIw1MA30wUUXYziiTErSw487ahokcesqzgPlK/o= +github.com/hm-edu/nosql v0.4.1-0.20230305071512-139857866201/go.mod h1:jOXwLtockXORUPPZ2MCUcIkGR6w0cN1QGZniY9DITQA= +github.com/hm-edu/portal-apis v0.0.0-20230929065638-ad1f7e7c7ab3 h1:XrnVZ89D8r7kXCmPsPCa+MtjZJvhhqT1Bkix5iH0sRQ= +github.com/hm-edu/portal-apis v0.0.0-20230929065638-ad1f7e7c7ab3/go.mod h1:o2FYTwt6w4uXfIoPAFRQpxbOOhGjde0PiGZlErVVyZk= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -240,8 +266,9 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -272,8 +299,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.0 h1:Ltaa1ePvc7msFGALnCrqKJVEByu/qYh5jJBYcDtAno4= -github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -284,8 +311,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= -github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -317,8 +344,9 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -354,7 +382,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -370,22 +398,21 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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/slackhq/nebula v1.7.2 h1:Rko1Mlksz/nC0c919xjGpB8uOSrTJ5e6KPgZx+lVfYw= +github.com/slackhq/nebula v1.7.2/go.mod h1:cnaoahkUipDs1vrNoIszyp0QPRIQN9Pm68ppQEW1Fhg= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2 h1:UIAS8DTWkeclraEGH2aiJPyNPu16VbT41w4JoBlyFfU= github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= -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-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= @@ -396,8 +423,8 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -425,8 +452,8 @@ github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE= -github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= 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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -437,6 +464,26 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= +go.opentelemetry.io/contrib/propagators/b3 v1.20.0 h1:Yty9Vs4F3D6/liF1o6FNt0PvN85h/BJJ6DQKJ3nrcM0= +go.opentelemetry.io/contrib/propagators/b3 v1.20.0/go.mod h1:On4VgbkqYL18kbJlWsa18+cMNe6rYpBnPi1ARI/BrsU= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.step.sm/cli-utils v0.8.0 h1:b/Tc1/m3YuQq+u3ghTFP7Dz5zUekZj6GUmd5pCvkEXQ= go.step.sm/cli-utils v0.8.0/go.mod h1:S77aISrC0pKuflqiDfxxJlUbiXcAanyJ4POOnzFSxD4= go.step.sm/crypto v0.36.1 h1:hrHIc0qVcOowJB/r1SgPGu10d59onUw3czYeMLJluBc= @@ -447,6 +494,7 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -471,8 +519,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 h1:LGJsf5LRplCck6jUCH3dBL2dmycNruWNF5xugkSlfXw= -golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -536,9 +584,11 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -618,8 +668,8 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= diff --git a/logging/handler.go b/logging/handler.go index a8b77d603..deb157302 100644 --- a/logging/handler.go +++ b/logging/handler.go @@ -70,6 +70,13 @@ func (l *LoggerHandler) writeEntry(w ResponseLogger, r *http.Request, t time.Tim addr = r.RemoteAddr } + // Try to get X-Forwarded-For header first + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + addr = strings.Split(xff, ", ")[0] + } else if xrip := r.Header.Get("X-Real-Ip"); xrip != "" { + addr = xrip + } + // From https://github.com/gorilla/handlers uri := r.RequestURI // Requests using the CONNECT method over HTTP/2.0 must use diff --git a/logging/logger.go b/logging/logger.go index 1716a7f4c..30bb504e5 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -61,6 +61,7 @@ func New(name string, raw json.RawMessage) (*Logger, error) { } if formatter != nil { logger.Formatter = formatter + logrus.SetFormatter(formatter) } return logger, nil } diff --git a/scep/authority.go b/scep/authority.go index 85faba079..ac0a62351 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -57,7 +57,7 @@ func MustFromContext(ctx context.Context) *Authority { // SignAuthority is the interface for a signing authority type SignAuthority interface { - Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + Sign(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) LoadProvisionerByName(string) (provisioner.Interface, error) } @@ -303,7 +303,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m } signOps = append(signOps, templateOptions) - certChain, err := a.signAuth.Sign(csr, opts, signOps...) + certChain, err := a.signAuth.Sign(ctx, csr, opts, signOps...) if err != nil { return nil, fmt.Errorf("error generating certificate for order: %w", err) } diff --git a/server/server.go b/server/server.go index e12c792c6..829b99337 100644 --- a/server/server.go +++ b/server/server.go @@ -42,7 +42,7 @@ func newHTTPServer(addr string, handler http.Handler, tlsConfig *tls.Config) *ht Addr: addr, Handler: handler, TLSConfig: tlsConfig, - WriteTimeout: 15 * time.Second, + WriteTimeout: 10 * time.Minute, ReadTimeout: 15 * time.Second, ReadHeaderTimeout: 15 * time.Second, IdleTimeout: 15 * time.Second,