From 2c65a9cbc756ad3fd97ab4360a517d871e2c4f6e Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sat, 22 Oct 2022 07:24:44 +0200 Subject: [PATCH 01/24] fix: increase http timeout --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From a73cc38bf8b35289a73bb216e068a520e174fa4f Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sun, 2 Jul 2023 10:39:29 +0200 Subject: [PATCH 02/24] feat: add health endpoint --- api/api.go | 9 ++++++++- api/api_test.go | 5 +++++ authority/authority.go | 5 +++++ db/db.go | 11 +++++++++++ db/simple.go | 5 +++++ go.mod | 6 ++++-- go.sum | 12 ++++++------ 7 files changed, 44 insertions(+), 9 deletions(-) diff --git a/api/api.go b/api/api.go index 0b139a710..f11a866f2 100644 --- a/api/api.go +++ b/api/api.go @@ -55,6 +55,7 @@ type Authority interface { GetFederation() ([]*x509.Certificate, error) Version() authority.Version GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) + Health() error } // mustAuthority will be replaced on unit tests. @@ -361,7 +362,13 @@ 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, r *http.Request) { - render.JSON(w, r, HealthResponse{Status: "ok"}) + a := mustAuthority(r.Context()) + err := a.Health() + if err == nil { + render.JSON(w, r, HealthResponse{Status: "ok"}) + } else { + render.JSONStatus(w, r, 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 8090c6d43..787cac811 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -222,6 +222,10 @@ func (m *mockAuthority) GetCertificateRevocationList() (*authority.CertificateRe return m.ret1.(*authority.CertificateRevocationListInfo), 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 { @@ -816,6 +820,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/authority/authority.go b/authority/authority.go index d3b932883..562c80162 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -197,6 +197,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/db/db.go b/db/db.go index 503a7c14b..346292e97 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{} @@ -525,6 +526,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 { @@ -643,6 +649,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/go.mod b/go.mod index 0f5618f55..7e5be18c2 100644 --- a/go.mod +++ b/go.mod @@ -93,7 +93,7 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-piv/piv-go v1.11.0 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -149,7 +149,7 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/x448/float16 v0.8.4 // indirect - go.etcd.io/bbolt v1.3.9 // indirect + go.etcd.io/bbolt v1.3.10 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect @@ -166,3 +166,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/smallstep/nosql => github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f diff --git a/go.sum b/go.sum index d5b9ad10a..416aee37a 100644 --- a/go.sum +++ b/go.sum @@ -159,8 +159,8 @@ 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= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= @@ -262,6 +262,8 @@ github.com/hashicorp/vault/api/auth/approle v0.7.0 h1:R5IRVuFA5JSdG3UdGVcGysi0St github.com/hashicorp/vault/api/auth/approle v0.7.0/go.mod h1:B+WaC6VR+aSXiUxykpaPUoFiiZAhic53tDLbGjWZmRA= github.com/hashicorp/vault/api/auth/kubernetes v0.7.0 h1:pHCbeeyD6E5KmMMCc9vwwZZ5OVlM6yFayxFHWodiOUU= github.com/hashicorp/vault/api/auth/kubernetes v0.7.0/go.mod h1:Eey0x0X2g+b2LYWgBrQFyf5W0fp+Y1HGrEckP8Q0wns= +github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f h1:dfG7e+uqlDG+rAgb4C6hvAgYX70n5rP/c4b0lli2Re8= +github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f/go.mod h1:T7wwgLGyNJvTUaM883SFOrE30r8hw4V03LBR5vrKBpY= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -428,8 +430,6 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= -github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= -github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= 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= @@ -474,8 +474,8 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= +go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= 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.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= From 873a9cda0ccf652dccf766b1c64c52a7af80846e Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sat, 22 Oct 2022 07:59:22 +0200 Subject: [PATCH 03/24] fix/extend eab functionality --- acme/api/account.go | 2 +- acme/api/eab.go | 2 +- acme/db/nosql/eab.go | 61 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/acme/api/account.go b/acme/api/account.go index 3114dcb35..e917613ea 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -149,7 +149,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) { render.Error(w, r, 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, r, acme.WrapErrorISE(err, "error updating external account binding key")) return } diff --git a/acme/api/eab.go b/acme/api/eab.go index 26854595b..81af66060 100644 --- a/acme/api/eab.go +++ b/acme/api/eab.go @@ -51,7 +51,7 @@ 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) { 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 From 0c6ff02bb5042299989e8308a34c30c260ca72b4 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sat, 22 Oct 2022 08:30:13 +0200 Subject: [PATCH 04/24] Adaptions for MUAS - Add Connection to EAB Management Service - Split Endpoints - Passthrough Context for Actions - Add Sectigo/PKI Client - Add Jaeger Telemetry - Fix minor issues handling EAB --- .devcontainer/.env | 4 + .devcontainer/Dockerfile | 19 ++ .devcontainer/devcontainer.json | 36 ++++ .devcontainer/docker-compose.yml | 53 ++++++ .github/pull.yml | 7 + .github/workflows/code-scan-cron.yml | 7 - .github/workflows/dependabot-auto-merge.yml | 11 -- .github/workflows/docker.yml | 50 ++++++ .vscode/launch.json | 15 ++ acme/api/account_test.go | 30 ++-- acme/api/handler.go | 28 ++- acme/api/handler_test.go | 50 ++++-- acme/api/middleware.go | 8 +- acme/api/order.go | 22 ++- acme/api/order_test.go | 115 +++++++----- acme/api/revoke_test.go | 20 +-- acme/order.go | 104 ++++++----- acme/order_test.go | 83 +++++++-- acme/status.go | 1 + api/api.go | 4 +- api/api_test.go | 4 +- api/rekey.go | 3 +- api/renew.go | 3 +- api/sshRenew.go | 2 +- authority/authority_test.go | 4 +- authority/config/config.go | 3 + authority/tls.go | 66 +++++-- authority/tls_test.go | 10 +- ca/bootstrap_test.go | 2 +- ca/ca.go | 115 +++++++++++- ca/tls_test.go | 2 +- cas/apiv1/options_test.go | 6 +- cas/apiv1/services.go | 9 +- cas/apiv1/services_test.go | 7 +- cas/cas_test.go | 6 +- cas/cloudcas/cloudcas.go | 6 +- cas/cloudcas/cloudcas_test.go | 6 +- cas/sectigocas/eab/client.go | 50 ++++++ cas/sectigocas/sectigocas.go | 187 ++++++++++++++++++++ cas/softcas/softcas.go | 6 +- cas/softcas/softcas_test.go | 10 +- cas/stepcas/stepcas.go | 6 +- cas/stepcas/stepcas_test.go | 6 +- cas/vaultcas/vaultcas.go | 6 +- cas/vaultcas/vaultcas_test.go | 6 +- cmd/step-ca/main.go | 1 + go.mod | 18 +- go.sum | 12 +- logging/logger.go | 1 + test/integration/scep/common_test.go | 6 +- 50 files changed, 977 insertions(+), 259 deletions(-) create mode 100644 .devcontainer/.env create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml create mode 100644 .github/pull.yml delete mode 100644 .github/workflows/code-scan-cron.yml delete mode 100644 .github/workflows/dependabot-auto-merge.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .vscode/launch.json create mode 100644 cas/sectigocas/eab/client.go create mode 100644 cas/sectigocas/sectigocas.go 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 c0b39e0c5..000000000 --- a/.github/workflows/dependabot-auto-merge.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Dependabot auto-merge -on: pull_request - -permissions: - contents: write - pull-requests: write - -jobs: - dependabot-auto-merge: - uses: smallstep/workflows/.github/workflows/dependabot-auto-merge.yml@main - secrets: inherit diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000..4aee10c1f --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,50 @@ +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@v2 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v3 + 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@v2 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + file: docker/Dockerfile \ No newline at end of file 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_test.go b/acme/api/account_test.go index b0fa6e2c3..e8344705a 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -328,14 +328,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{}, @@ -346,7 +346,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{ @@ -361,7 +361,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) { @@ -696,7 +696,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, @@ -877,7 +877,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, @@ -886,7 +886,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, @@ -895,7 +895,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{}, @@ -905,7 +905,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{}, @@ -920,7 +920,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{}, @@ -935,7 +935,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{ @@ -957,7 +957,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{ @@ -976,7 +976,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{}, @@ -991,7 +991,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{ @@ -1007,7 +1007,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/handler.go b/acme/api/handler.go index 0722bd9b3..487915a1b 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 628da7edb..3f1078c4e 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -258,7 +258,7 @@ func extractJWK(next nextHTTP) nextHTTP { render.Error(w, r, 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)) } @@ -361,7 +361,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 @@ -568,7 +568,7 @@ type ContextKey string const ( // accContextKey account key - accContextKey = ContextKey("acc") + AccContextKey = ContextKey("acc") // jwsContextKey jws key jwsContextKey = ContextKey("jws") // jwkContextKey jwk key @@ -580,7 +580,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 77e32fdb5..5f19b04dd 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -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, r, acme.NewError(acme.ErrorServerInternalType, "Internal server error")) + return + } + render.Error(w, r, acme.NewError(acme.ErrorRejectedIdentifierType, "Missing registration for domain(s) %v", missing)) + return + } + for i, identifier := range o.Identifiers { az := &acme.Authorization{ AccountID: acc.ID, @@ -321,6 +330,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, r, acme.NewErrorISE("Request is processing")) + return + } + w.Header().Set("Retry-After", "10") + } render.JSON(w, r, o) } @@ -373,14 +391,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, r, 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, r, o) } diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 36de975a3..55de4b79b 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/smallstep/certificates/cas/sectigocas/eab" "io" "net/http" "net/http/httptest" @@ -328,7 +329,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 +339,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 +350,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 +361,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 +375,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 +391,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 +407,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 +431,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 +800,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 +815,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 +825,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 +836,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 +846,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 +858,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 +870,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 +885,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 +904,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,7 +932,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, @@ -958,7 +960,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 +996,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 +1039,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 +1082,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 +1120,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 +1154,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 +1219,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 +1260,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 +1380,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 +1477,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 +1573,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 +1670,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 +1770,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 +1860,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 +1963,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 +1973,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 +1984,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 +1994,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 +2006,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 +2018,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 +2033,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 +2046,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 +2061,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 +2078,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 +2095,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 +2120,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 85b9a0326..4aa2571ad 100644 --- a/acme/api/revoke_test.go +++ b/acme/api/revoke_test.go @@ -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/order.go b/acme/order.go index 13c16b27c..da95adebe 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.SignWithContext(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.SignWithContext(ctx, 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 07372af07..6d08d856b 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -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')"), } @@ -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"), } }, @@ -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"), } @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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/api/api.go b/api/api.go index f11a866f2..c7e3101c3 100644 --- a/api/api.go +++ b/api/api.go @@ -43,9 +43,9 @@ type Authority interface { GetTLSOptions() *config.TLSOptions Root(shasum string) (*x509.Certificate, error) SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) - Renew(peer *x509.Certificate) ([]*x509.Certificate, error) + Renew(ctx context.Context, 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) + 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) diff --git a/api/api_test.go b/api/api_test.go index 787cac811..6bf90ca5d 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -262,7 +262,7 @@ func (m *mockAuthority) SignWithContext(ctx context.Context, cr *x509.Certificat 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) } @@ -276,7 +276,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) } diff --git a/api/rekey.go b/api/rekey.go index 772de2173..4d665685f 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, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey")) return diff --git a/api/renew.go b/api/renew.go index 7cd3707db..5eee463ed 100644 --- a/api/renew.go +++ b/api/renew.go @@ -1,6 +1,7 @@ package api import ( + "context" "crypto/x509" "net/http" "strings" @@ -34,7 +35,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, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew")) return diff --git a/api/sshRenew.go b/api/sshRenew.go index dea7cea76..89426ed82 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_test.go b/authority/authority_test.go index a39dca8ef..39cf815aa 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -432,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/config/config.go b/authority/config/config.go index ea7ce35da..4b23f4764 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -70,6 +70,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"` @@ -85,6 +86,8 @@ type Config struct { CRL *CRLConfig `json:"crl,omitempty"` MetricsAddress string `json:"metricsAddress,omitempty"` SkipValidation bool `json:"-"` + Storage string `json:"storage,omitempty"` + ManagementHost string `json:"managementHost"` // Keeps record of the filename the Config is read from loadedFromFilepath string diff --git a/authority/tls.go b/authority/tls.go index 679c28ac5..f1d0bb773 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -14,6 +14,7 @@ import ( "math/big" "net" "net/http" + "os" "strings" "time" @@ -299,7 +300,7 @@ func (a *Authority) signX509(ctx context.Context, csr *x509.CertificateRequest, // 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, @@ -340,10 +341,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) +// 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.RenewContext(ctx, oldCert, nil) } // Rekey is used for rekeying and renewing based on the public key. If the @@ -356,8 +357,8 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error // 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) +func (a *Authority) Rekey(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { + return a.RenewContext(ctx, oldCert, pk) } // RenewContext creates a new certificate identical to the old one, but it can @@ -475,7 +476,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, @@ -674,7 +675,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, @@ -872,16 +873,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")) @@ -937,7 +971,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { lifetime = 24 * time.Hour } - resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ + resp, err := a.x509CAService.CreateCertificate(context.Background(), &casapi.CreateCertificateRequest{ Template: certTpl, CSR: cr, Lifetime: lifetime, @@ -959,10 +993,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 { @@ -970,7 +1000,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 4965fe46e..9226da47e 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -1071,9 +1071,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)) { @@ -1275,9 +1275,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)) { @@ -1808,7 +1808,7 @@ func TestAuthority_constraints(t *testing.T) { t.Errorf("Authority.SignWithContext() 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 da37eee58..056a44968 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) 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 0b426ded2..39bb6365e 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "net/url" + "os" "reflect" "strings" "sync" @@ -17,6 +18,13 @@ 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/jaeger" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "github.com/pkg/errors" "github.com/smallstep/certificates/acme" acmeAPI "github.com/smallstep/certificates/acme/api" @@ -38,6 +46,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 { @@ -144,11 +156,13 @@ type CA struct { auth *authority.Authority config *config.Config srv *server.Server + public *server.Server insecureSrv *server.Server metricsSrv *server.Server opts *options renewer *TLSRenewer compactStop chan struct{} + tp *sdktrace.TracerProvider } // New creates and initializes the CA with the given configuration and options. @@ -164,6 +178,23 @@ 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) { + exporter, err := jaeger.New(jaeger.WithCollectorEndpoint()) + 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{ @@ -213,7 +244,7 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } else { // default to using the step-ca x509 Signer Interface log.Print("Building new tls configuration using step-ca x509 Signer Interface") - tlsConfig, clientTLSConfig, err = ca.getTLSConfig(auth) + tlsConfig, clientTLSConfig, err = ca.getTLSConfig(auth, cfg) if err != nil { return nil, err } @@ -224,6 +255,12 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { // 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) @@ -270,8 +307,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() @@ -327,6 +374,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 @@ -339,6 +389,9 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { legacyTraceHeader = logger.GetTraceHeader() handler = logger.Middleware(handler) insecureHandler = logger.Middleware(insecureHandler) + if cfg.PublicAddress != "" { + publicHandler = logger.Middleware(publicHandler) + } } // always use request ID middleware; traceHeader is provided for backwards compatibility (for now) @@ -346,12 +399,22 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { insecureHandler = requestid.New(legacyTraceHeader).Middleware(insecureHandler) // 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") + } + baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker, client) ca.srv = server.New(cfg.Address, handler, tlsConfig) ca.srv.BaseContext = func(net.Listener) context.Context { return baseContext } + if cfg.PublicAddress != "" { + 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. @@ -394,7 +457,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) context.Context { ctx := authority.NewContext(context.Background(), a) if authDB := a.GetDatabase(); authDB != nil { ctx = db.NewContext(ctx, authDB) @@ -408,6 +471,9 @@ 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) + } return ctx } @@ -472,6 +538,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 @@ -506,15 +579,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 } @@ -571,6 +654,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) @@ -590,9 +679,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 } @@ -603,7 +700,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 d1ce11ea7..890331ef0 100644 --- a/ca/tls_test.go +++ b/ca/tls_test.go @@ -78,7 +78,7 @@ func startCATestServer(t *testing.T) *httptest.Server { ca, err := New(config) require.NoError(t, err) // Use a httptest.Server instead - baseContext := buildContext(ca.auth, nil, nil, nil) + baseContext := buildContext(ca.auth, 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 37c5adbaa..2b9fdfe6d 100644 --- a/cas/apiv1/services.go +++ b/cas/apiv1/services.go @@ -1,6 +1,7 @@ package apiv1 import ( + "context" "crypto" "crypto/x509" "net/http" @@ -10,9 +11,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 @@ -63,6 +64,8 @@ const ( VaultCAS = "vaultcas" // ExternalCAS is a CertificateAuthorityService using an external injected CA implementation ExternalCAS = "externalcas" + // 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/apiv1/services_test.go b/cas/apiv1/services_test.go index 2080f8431..619016523 100644 --- a/cas/apiv1/services_test.go +++ b/cas/apiv1/services_test.go @@ -1,18 +1,19 @@ package apiv1 import ( + "context" "testing" ) type simpleCAS struct{} -func (*simpleCAS) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { +func (*simpleCAS) CreateCertificate(ctx context.Context, req *CreateCertificateRequest) (*CreateCertificateResponse, error) { return nil, NotImplementedError{} } -func (*simpleCAS) RenewCertificate(req *RenewCertificateRequest) (*RenewCertificateResponse, error) { +func (*simpleCAS) RenewCertificate(ctx context.Context, req *RenewCertificateRequest) (*RenewCertificateResponse, error) { return nil, NotImplementedError{} } -func (*simpleCAS) RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) { +func (*simpleCAS) RevokeCertificate(ctx context.Context, req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) { return nil, NotImplementedError{} } 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 398f7fed5..d946def95 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -193,7 +193,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") @@ -215,7 +215,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") @@ -235,7 +235,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 6db5d75d4..7913ee815 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -550,7 +550,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 @@ -666,7 +666,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 @@ -745,7 +745,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 1e590effc..cc571ae5f 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -66,7 +66,7 @@ func (c *SoftCAS) GetSigner() (crypto.Signer, 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") @@ -102,7 +102,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") @@ -134,7 +134,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 1c20d2776..dfe22c849 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -398,7 +398,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 @@ -442,7 +442,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"}, @@ -528,7 +528,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"}, @@ -626,7 +626,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 @@ -694,7 +694,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 cab8f2031..30febb2c9 100644 --- a/cas/stepcas/stepcas.go +++ b/cas/stepcas/stepcas.go @@ -72,7 +72,7 @@ func (s *StepCAS) Type() apiv1.Type { // 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") @@ -107,7 +107,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"} } @@ -130,7 +130,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 d2846fb01..f191958fc 100644 --- a/cas/stepcas/stepcas_test.go +++ b/cas/stepcas/stepcas_test.go @@ -768,7 +768,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 @@ -833,7 +833,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 @@ -933,7 +933,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 16adccc4e..63ceabd5a 100644 --- a/cas/vaultcas/vaultcas.go +++ b/cas/vaultcas/vaultcas.go @@ -116,7 +116,7 @@ func (v *VaultCAS) Type() apiv1.Type { } // 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") @@ -172,12 +172,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 2f44bc059..4b1c1711b 100644 --- a/cas/vaultcas/vaultcas_test.go +++ b/cas/vaultcas/vaultcas_test.go @@ -273,7 +273,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 @@ -396,7 +396,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 @@ -446,7 +446,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-ca/main.go b/cmd/step-ca/main.go index c7ece08ff..5206b6b02 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -38,6 +38,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/go.mod b/go.mod index 7e5be18c2..6769733fe 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,10 @@ require ( cloud.google.com/go/longrunning v0.5.7 cloud.google.com/go/security v1.17.0 github.com/Masterminds/sprig/v3 v3.2.3 + github.com/ThalesIgnite/crypto11 v1.2.5 // 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.0 // indirect github.com/fxamacker/cbor/v2 v2.6.0 github.com/go-chi/chi/v5 v5.0.12 github.com/go-jose/go-jose/v3 v3.0.3 @@ -51,6 +53,14 @@ require ( cloud.google.com/go/iam v1.1.8 // indirect cloud.google.com/go/kms v1.17.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 + go.opentelemetry.io/contrib/propagators/b3 v1.15.0 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/jaeger v1.16.0 + go.opentelemetry.io/otel/sdk v1.24.0 +) + +require ( github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect @@ -60,7 +70,6 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // 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/aws/aws-sdk-go-v2 v1.27.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.17 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.17 // indirect @@ -82,7 +91,6 @@ require ( github.com/chzyer/readline v1.5.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // 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/felixge/httpsnoop v1.0.4 // indirect @@ -115,7 +123,7 @@ require ( github.com/hashicorp/go-sockaddr v1.0.2 // 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/imdario/mergo v0.3.15 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -151,9 +159,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.etcd.io/bbolt v1.3.10 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect @@ -168,3 +174,5 @@ require ( ) replace github.com/smallstep/nosql => github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f + +require github.com/hm-edu/portal-apis v0.0.0-20230929065638-ad1f7e7c7ab3 diff --git a/go.sum b/go.sum index 416aee37a..79f870cf9 100644 --- a/go.sum +++ b/go.sum @@ -264,11 +264,13 @@ github.com/hashicorp/vault/api/auth/kubernetes v0.7.0 h1:pHCbeeyD6E5KmMMCc9vwwZZ github.com/hashicorp/vault/api/auth/kubernetes v0.7.0/go.mod h1:Eey0x0X2g+b2LYWgBrQFyf5W0fp+Y1HGrEckP8Q0wns= github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f h1:dfG7e+uqlDG+rAgb4C6hvAgYX70n5rP/c4b0lli2Re8= github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f/go.mod h1:T7wwgLGyNJvTUaM883SFOrE30r8hw4V03LBR5vrKBpY= +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 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/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.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/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= @@ -451,6 +453,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -482,8 +485,12 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q= +go.opentelemetry.io/contrib/propagators/b3 v1.15.0/go.mod h1:VjU0g2v6HSQ+NwfifambSLAeBgevjIcqmceaKWEzl0c= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/jaeger v1.16.0 h1:YhxxmXZ011C0aDZKoNw+juVWAmEfv/0W2XBOv9aHTaA= +go.opentelemetry.io/otel/exporters/jaeger v1.16.0/go.mod h1:grYbBo/5afWlPpdPZYhyn78Bk04hnvxn2+hvxQhKIQM= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= @@ -680,7 +687,6 @@ 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 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/test/integration/scep/common_test.go b/test/integration/scep/common_test.go index 86f64c3d2..c9f27be9e 100644 --- a/test/integration/scep/common_test.go +++ b/test/integration/scep/common_test.go @@ -243,7 +243,7 @@ type testCAS struct { ca *minica.CA } -func (c *testCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { +func (c *testCAS) CreateCertificate(ctx context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { cert, err := c.ca.SignCSR(req.CSR) if err != nil { return nil, fmt.Errorf("failed signing CSR: %w", err) @@ -254,11 +254,11 @@ func (c *testCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 CertificateChain: []*x509.Certificate{cert, c.ca.Intermediate}, }, nil } -func (c *testCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { +func (c *testCAS) RenewCertificate(ctx context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { return nil, errors.New("not implemented") } -func (c *testCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { +func (c *testCAS) RevokeCertificate(ctx context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { return nil, errors.New("not implemented") } From a856e51b75c52e600ce5838574acc7d4a5889703 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Mon, 10 Jul 2023 07:52:20 +0200 Subject: [PATCH 05/24] feat: add validation agent functionality --- .github/workflows/docker.yml | 43 ++++++++- .gitignore | 1 + acme/challenge.go | 37 +++++++- acme/mqtt/client.go | 100 ++++++++++++++++++++ acme/validation/client.go | 60 ++++++++++++ authority/config/config.go | 8 ++ ca/bootstrap_test.go | 2 +- ca/ca.go | 39 +++++++- ca/tls_test.go | 2 +- cmd/step-agent/main.go | 173 +++++++++++++++++++++++++++++++++++ docker/Dockerfile.agent | 18 ++++ go.mod | 15 ++- go.sum | 21 ++++- 13 files changed, 504 insertions(+), 15 deletions(-) create mode 100644 acme/mqtt/client.go create mode 100644 acme/validation/client.go create mode 100644 cmd/step-agent/main.go create mode 100644 docker/Dockerfile.agent diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4aee10c1f..f3882f5d7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -47,4 +47,45 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - file: docker/Dockerfile \ No newline at end of file + 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@v2 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v3 + 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@v2 + 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/.gitignore b/.gitignore index 42e960498..543a0521f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ output vendor .idea .envrc +step-ca diff --git a/acme/challenge.go b/acme/challenge.go index 9f3ca388d..b8ee2c910 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,38 @@ 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 { + return + } + if token := mqtt.GetClient().Publish(fmt.Sprintf("%s/jobs", mqtt.GetOrganization()), 1, false, data); token.Wait() && token.Error() != nil { + logrus.Warn(token.Error()) + } + }() 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/mqtt/client.go b/acme/mqtt/client.go new file mode 100644 index 000000000..f062c7b19 --- /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(mqtt.Client) { + logrus.Println("mqtt connection established") + } + 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() + } + + go func() { + client.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()) + } + + }) + }() + connection := validation.BrokerConnection{Client: client, Organization: organization} + return connection, nil +} 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/authority/config/config.go b/authority/config/config.go index 4b23f4764..5018497ea 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"` @@ -88,6 +95,7 @@ type Config struct { 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/ca/bootstrap_test.go b/ca/bootstrap_test.go index 056a44968..ed84a629d 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, 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 39bb6365e..ae1d668ea 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "io" "log" "net" "net/http" @@ -20,15 +21,21 @@ import ( "github.com/go-chi/chi/v5/middleware" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/jaeger" + "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" @@ -178,7 +185,15 @@ 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) { - exporter, err := jaeger.New(jaeger.WithCollectorEndpoint()) + 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 } @@ -403,7 +418,20 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { if err != nil { return nil, errors.Wrap(err, "error connecting to EAB") } - baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker, client) + var validationBroker validation.MqttClient + if cfg.ValidationBroker != nil { + if cfg.ValidationBroker.Password == "" { + // pick password from env + cfg.ValidationBroker.Password = os.Getenv("MQTT_PASSWORD") + } + + 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) ca.srv = server.New(cfg.Address, handler, tlsConfig) ca.srv.BaseContext = func(net.Listener) context.Context { @@ -457,7 +485,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, eabClient pb.EABServiceClient) 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) @@ -474,6 +502,9 @@ func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB if eabClient != nil { ctx = eab.NewContext(ctx, eabClient) } + if validationBroker != nil { + ctx = validation.NewContext(ctx, validationBroker) + } return ctx } diff --git a/ca/tls_test.go b/ca/tls_test.go index 890331ef0..276c5856d 100644 --- a/ca/tls_test.go +++ b/ca/tls_test.go @@ -78,7 +78,7 @@ func startCATestServer(t *testing.T) *httptest.Server { ca, err := New(config) require.NoError(t, err) // Use a httptest.Server instead - baseContext := buildContext(ca.auth, nil, 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/cmd/step-agent/main.go b/cmd/step-agent/main.go new file mode 100644 index 000000000..147c93f44 --- /dev/null +++ b/cmd/step-agent/main.go @@ -0,0 +1,173 @@ +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(mqtt.Client) { + logrus.Println("mqtt connection established") + } + 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()) + } + + // Subscribe to topic + client.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) + + logrus.Infof("authz: %s", data.Authz) + logrus.Infof("target: %s", data.Target) + logrus.Infof("account: %s", data.Challenge) + 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 := client.Publish(fmt.Sprintf("%s/data", c.String("organization")), 0, false, json) + if token.WaitTimeout(30*time.Second) && token.Error() != nil { + logrus.WithError(token.Error()).Warn("publishing failed") + } + + }) + + 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/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 6769733fe..8a5f6caa4 100644 --- a/go.mod +++ b/go.mod @@ -56,10 +56,15 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 go.opentelemetry.io/contrib/propagators/b3 v1.15.0 go.opentelemetry.io/otel v1.24.0 - go.opentelemetry.io/otel/exporters/jaeger v1.16.0 go.opentelemetry.io/otel/sdk v1.24.0 ) +require ( + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0 +) + require ( github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect @@ -86,6 +91,7 @@ require ( github.com/aws/smithy-go v1.20.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // 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 @@ -113,6 +119,7 @@ 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.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.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 @@ -162,6 +169,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect @@ -176,3 +184,8 @@ require ( replace github.com/smallstep/nosql => github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f require github.com/hm-edu/portal-apis v0.0.0-20230929065638-ad1f7e7c7ab3 + +require ( + github.com/eclipse/paho.mqtt.golang v1.4.3 + github.com/gorilla/websocket v1.5.0 // indirect +) diff --git a/go.sum b/go.sum index 79f870cf9..bf656b3da 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r 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/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= @@ -124,6 +126,8 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn 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= @@ -233,6 +237,10 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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= @@ -453,7 +461,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -489,14 +496,20 @@ go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5 go.opentelemetry.io/contrib/propagators/b3 v1.15.0/go.mod h1:VjU0g2v6HSQ+NwfifambSLAeBgevjIcqmceaKWEzl0c= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/jaeger v1.16.0 h1:YhxxmXZ011C0aDZKoNw+juVWAmEfv/0W2XBOv9aHTaA= -go.opentelemetry.io/otel/exporters/jaeger v1.16.0/go.mod h1:grYbBo/5afWlPpdPZYhyn78Bk04hnvxn2+hvxQhKIQM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0 h1:hSWWvDjXHVLq9DkmB+77fl8v7+t+yYiS+eNkiplDK54= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0/go.mod h1:zG7KQql1WjZCaUJd+L/ReSYx4bjbYJxg5ws9ws+mYes= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= go.step.sm/crypto v0.47.1 h1:XvqgWLA1OTJXkmkmD6QSDZrmGKP4flv3PEoau60htcU= @@ -507,6 +520,8 @@ 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/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 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= From a53c6a6cdb96f7947e893a6b7c6f65fb9bf46efe Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Fri, 22 Sep 2023 10:42:54 +0200 Subject: [PATCH 06/24] feat: permit insecure endpoints for reverse proxying --- ca/ca.go | 50 +++++++++++++++++++++++++++++----------------- logging/handler.go | 7 +++++++ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index ae1d668ea..66b3ffbf1 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -248,25 +248,30 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { return nil, err } ca.auth = auth - var tlsConfig *tls.Config - var clientTLSConfig *tls.Config - if ca.opts.tlsConfig != nil { - // try using the tls Configuration supplied by the caller - log.Print("Using tls configuration supplied by the application") - tlsConfig = ca.opts.tlsConfig - clientTLSConfig = ca.opts.tlsConfig - } else { - // default to using the step-ca x509 Signer Interface - log.Print("Building new tls configuration using step-ca x509 Signer Interface") - tlsConfig, clientTLSConfig, err = ca.getTLSConfig(auth, cfg) - if err != nil { - return nil, err - } - } + allInsecure := false - webhookTransport.TLSClientConfig = clientTLSConfig + if os.Getenv("STEP_TLS_INSECURE") == "1" { + allInsecure = true + } + if !allInsecure { + var clientTLSConfig *tls.Config + if ca.opts.tlsConfig != nil { + // try using the tls Configuration supplied by the caller + log.Print("Using tls configuration supplied by the application") + tlsConfig = ca.opts.tlsConfig + clientTLSConfig = ca.opts.tlsConfig + } else { + // default to using the step-ca x509 Signer Interface + log.Print("Building new tls configuration using step-ca x509 Signer Interface") + tlsConfig, clientTLSConfig, err = ca.getTLSConfig(auth, cfg) + if err != nil { + return nil, err + } + } + webhookTransport.TLSClientConfig = clientTLSConfig + } // Using chi as the main router mux := chi.NewRouter() handler := http.Handler(mux) @@ -433,12 +438,21 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker, client, validationBroker) - ca.srv = server.New(cfg.Address, handler, tlsConfig) + 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 != "" { - ca.public = server.New(cfg.PublicAddress, publicHandler, tlsConfig) + 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 } diff --git a/logging/handler.go b/logging/handler.go index 701d631ae..5b77d933a 100644 --- a/logging/handler.go +++ b/logging/handler.go @@ -72,6 +72,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 From ad2757e07f420f99a4c12bd7d15706e54ef9ea37 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Fri, 29 Sep 2023 08:06:15 +0200 Subject: [PATCH 07/24] chore: upgrade packages --- go.mod | 41 +++++++++++++++++---------------- go.sum | 71 ++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 8a5f6caa4..d35fcb6b1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/ThalesIgnite/crypto11 v1.2.5 // 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.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/fxamacker/cbor/v2 v2.6.0 github.com/go-chi/chi/v5 v5.0.12 github.com/go-jose/go-jose/v3 v3.0.3 @@ -21,12 +21,15 @@ require ( github.com/hashicorp/vault/api v1.14.0 github.com/hashicorp/vault/api/auth/approle v0.7.0 github.com/hashicorp/vault/api/auth/kubernetes v0.7.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.20 // indirect github.com/newrelic/go-agent/v3 v3.33.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.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.20240109183208-413678f90935 github.com/smallstep/nosql v0.6.1 @@ -54,15 +57,15 @@ require ( cloud.google.com/go/kms v1.17.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 - go.opentelemetry.io/contrib/propagators/b3 v1.15.0 + go.opentelemetry.io/contrib/propagators/b3 v1.24.0 go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/sdk v1.24.0 ) require ( - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 ) require ( @@ -74,7 +77,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/aws/aws-sdk-go-v2 v1.27.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.17 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.17 // indirect @@ -90,7 +93,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.28.11 // indirect github.com/aws/smithy-go v1.20.2 // indirect github.com/beorn7/perks v1.0.1 // 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 @@ -119,18 +122,18 @@ 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.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.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.7.6 // 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.15 // 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.3 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -139,11 +142,9 @@ require ( 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.3 // 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.20 // 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 @@ -159,9 +160,9 @@ 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/x448/float16 v0.8.4 // indirect go.etcd.io/bbolt v1.3.10 // indirect @@ -169,7 +170,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect @@ -183,8 +184,6 @@ require ( replace github.com/smallstep/nosql => github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f -require github.com/hm-edu/portal-apis v0.0.0-20230929065638-ad1f7e7c7ab3 - require ( github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/gorilla/websocket v1.5.0 // indirect diff --git a/go.sum b/go.sum index bf656b3da..12154216d 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,9 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi 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= @@ -77,8 +78,8 @@ github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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= @@ -118,8 +119,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= @@ -137,6 +138,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= @@ -239,8 +242,8 @@ github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= 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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= 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= @@ -255,13 +258,14 @@ github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDy github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K6pJLU= @@ -274,11 +278,12 @@ github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f h1:dfG7e+uqlDG+rAgb github.com/hm-edu/nosql v0.4.1-0.20240506195746-2ea82a22279f/go.mod h1:T7wwgLGyNJvTUaM883SFOrE30r8hw4V03LBR5vrKBpY= 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 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= 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.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +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= @@ -329,8 +334,8 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 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= @@ -426,16 +431,17 @@ 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.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= @@ -450,8 +456,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= @@ -492,24 +498,24 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q= -go.opentelemetry.io/contrib/propagators/b3 v1.15.0/go.mod h1:VjU0g2v6HSQ+NwfifambSLAeBgevjIcqmceaKWEzl0c= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0 h1:hSWWvDjXHVLq9DkmB+77fl8v7+t+yYiS+eNkiplDK54= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0/go.mod h1:zG7KQql1WjZCaUJd+L/ReSYx4bjbYJxg5ws9ws+mYes= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -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.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= go.step.sm/crypto v0.47.1 h1:XvqgWLA1OTJXkmkmD6QSDZrmGKP4flv3PEoau60htcU= @@ -520,8 +526,8 @@ 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/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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= @@ -610,6 +616,7 @@ 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= From 130820014e820fee0e5ffce73b310a8462f8bff5 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Fri, 20 Oct 2023 13:20:49 +0200 Subject: [PATCH 08/24] feat: better errors for eab usage --- acme/api/eab.go | 10 +++++----- acme/api/eab_test.go | 8 ++++---- acme/errors.go | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/acme/api/eab.go b/acme/api/eab.go index 81af66060..8026ae829 100644 --- a/acme/api/eab.go +++ b/acme/api/eab.go @@ -57,21 +57,21 @@ func validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) 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/errors.go b/acme/errors.go index 586cfb9b6..fe8b77731 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -65,6 +65,10 @@ 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 ) // String returns the string representation of the acme problem type, @@ -121,6 +125,10 @@ func (ap ProblemType) String() string { return "userActionRequired" case ErrorNotImplementedType: return "notImplemented" + case ErrorEabAlreadyUsedType: + return "eabAlreadyUsed" + case ErrorEabDoesNotExistType: + return "eabDoesNotExist" default: return fmt.Sprintf("unsupported type ACME error type '%d'", int(ap)) } @@ -141,6 +149,16 @@ 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, + }, ErrorAccountDoesNotExistType: { typ: officialACMEPrefix + ErrorAccountDoesNotExistType.String(), details: "Account does not exist", From 292c370304f5fa500bbdc800fb7514b429cc93a4 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Fri, 20 Oct 2023 15:37:39 +0200 Subject: [PATCH 09/24] fix: optimize error logging --- acme/api/order.go | 2 +- acme/api/order_test.go | 7 ++++--- acme/errors.go | 9 +++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index 5f19b04dd..9426726cb 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, r, acme.WrapErrorISE(err, "error retrieving external account binding key")) + render.Error(w, r, acme.NewError(acme.ErrorEabAccountBindingDoesNotExistType, "error retrieving external account binding key")) return } } diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 55de4b79b..f78600bd3 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -7,7 +7,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/smallstep/certificates/cas/sectigocas/eab" "io" "net/http" "net/http/httptest" @@ -16,6 +15,8 @@ import ( "testing" "time" + "github.com/smallstep/certificates/cas/sectigocas/eab" + "github.com/go-chi/chi/v5" "github.com/pkg/errors" @@ -936,7 +937,7 @@ func TestHandler_NewOrder(t *testing.T) { 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) { @@ -945,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 { diff --git a/acme/errors.go b/acme/errors.go index fe8b77731..0cbf68161 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -69,6 +69,8 @@ const ( 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, @@ -129,6 +131,8 @@ func (ap ProblemType) String() string { return "eabAlreadyUsed" case ErrorEabDoesNotExistType: return "eabDoesNotExist" + case ErrorEabAccountBindingDoesNotExistType: + return "eabAccountBindingDoesNotExist" default: return fmt.Sprintf("unsupported type ACME error type '%d'", int(ap)) } @@ -159,6 +163,11 @@ var ( 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", From 6b9d049fa24f3773d5e36de51aab23cc73bcfcf4 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sun, 22 Oct 2023 17:52:43 +0200 Subject: [PATCH 10/24] chore: update github actions --- .github/workflows/docker.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f3882f5d7..227169878 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,20 +18,20 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + 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@v3 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -41,7 +41,7 @@ jobs: 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@v2 + uses: docker/build-push-action@v5 with: context: . push: ${{ github.event_name != 'pull_request' }} @@ -59,20 +59,20 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + 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@v3 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-agent tags: | @@ -82,7 +82,7 @@ jobs: 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@v2 + uses: docker/build-push-action@v5 with: context: . push: ${{ github.event_name != 'pull_request' }} From 2e259c18c256f2ee0530f11f6b9732b00f1b0d12 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Thu, 26 Oct 2023 08:13:49 +0200 Subject: [PATCH 11/24] feat: more mqtt logging --- acme/challenge.go | 2 ++ cmd/step-agent/main.go | 16 +++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index b8ee2c910..3371e0954 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -131,11 +131,13 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb } 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, errHttp := vc.Get(u.String()) diff --git a/cmd/step-agent/main.go b/cmd/step-agent/main.go index 147c93f44..21a83ee16 100644 --- a/cmd/step-agent/main.go +++ b/cmd/step-agent/main.go @@ -66,13 +66,14 @@ var agent = cli.Command{ 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()) } // Subscribe to topic - client.Subscribe(fmt.Sprintf("%s/jobs", c.String("organization")), 0, func(client mqtt.Client, msg mqtt.Message) { + token := client.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()) @@ -81,9 +82,6 @@ var agent = cli.Command{ req := msg.Payload() json.Unmarshal(req, &data) - logrus.Infof("authz: %s", data.Authz) - logrus.Infof("target: %s", data.Target) - logrus.Infof("account: %s", data.Challenge) logger := logrus.WithField("authz", data.Authz).WithField("target", data.Target).WithField("account", data.Challenge) http := acme.NewClient() @@ -120,11 +118,19 @@ var agent = cli.Command{ // Publish to topic token := client.Publish(fmt.Sprintf("%s/data", c.String("organization")), 0, false, json) if token.WaitTimeout(30*time.Second) && token.Error() != nil { - logrus.WithError(token.Error()).Warn("publishing failed") + 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"))) + } + return nil }, } From f801fa7ab587ebca29c09ba01de8c6e9ed082410 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Thu, 26 Oct 2023 08:42:20 +0200 Subject: [PATCH 12/24] chore: better handling of reconnects --- acme/mqtt/client.go | 98 ++++++++++++++++----------------- cmd/step-agent/main.go | 120 ++++++++++++++++++++--------------------- 2 files changed, 109 insertions(+), 109 deletions(-) diff --git a/acme/mqtt/client.go b/acme/mqtt/client.go index f062c7b19..655bcd3b3 100644 --- a/acme/mqtt/client.go +++ b/acme/mqtt/client.go @@ -33,8 +33,56 @@ func Connect(acmeDB acme.DB, host, user, password, organization string) (validat opts.OnConnectionLost = func(cl mqtt.Client, err error) { logrus.Println("mqtt connection lost") } - opts.OnConnect = func(mqtt.Client) { + 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") @@ -47,54 +95,6 @@ func Connect(acmeDB acme.DB, host, user, password, organization string) (validat return nil, token.Error() } - go func() { - client.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()) - } - - }) - }() connection := validation.BrokerConnection{Client: client, Organization: organization} return connection, nil } diff --git a/cmd/step-agent/main.go b/cmd/step-agent/main.go index 21a83ee16..85d322a25 100644 --- a/cmd/step-agent/main.go +++ b/cmd/step-agent/main.go @@ -60,9 +60,68 @@ var agent = cli.Command{ options.OnConnectionLost = func(cl mqtt.Client, err error) { logrus.Println("mqtt connection lost") } - options.OnConnect = func(mqtt.Client) { + 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") } @@ -72,65 +131,6 @@ var agent = cli.Command{ logrus.Warn(token.Error()) } - // Subscribe to topic - token := client.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 := client.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"))) - } - return nil }, } From 0100ce3c1e82ef27728d23101d0176ab448b715e Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sat, 25 Nov 2023 06:07:44 +0100 Subject: [PATCH 13/24] fix: try to handle concurrent saving better --- acme/challenge.go | 14 +++++++++++--- acme/mqtt/client.go | 16 ++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 3371e0954..39104d5a5 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -181,9 +181,17 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb ch.Status = StatusValid ch.Error = nil ch.ValidatedAt = clock.Now().Format(time.RFC3339) - - if err = db.UpdateChallenge(ctx, ch); err != nil { - return WrapErrorISE(err, "error updating challenge") + for { + if err = db.UpdateChallenge(ctx, ch); err != nil { + if strings.Contains(err.Error(), "changed since last read") { + // If the challenge has changed since we read it, then we + // don't want to overwrite the error. + logrus.Warn("challenge changed since last read -> retry saving") + continue + } + return WrapErrorISE(err, "error updating challenge") + } + break } return nil } diff --git a/acme/mqtt/client.go b/acme/mqtt/client.go index 655bcd3b3..086493ba7 100644 --- a/acme/mqtt/client.go +++ b/acme/mqtt/client.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/url" + "strings" "time" mqtt "github.com/eclipse/paho.mqtt.golang" @@ -74,11 +75,18 @@ func Connect(acmeDB acme.DB, host, user, password, organization string) (validat 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 { + for { + if err = acmeDB.UpdateChallenge(ctx, ch); err != nil { + if strings.Contains(err.Error(), "changed since last read") { + // If the challenge has changed since we read it, then we + // don't want to overwrite the error. + logrus.Warn("challenge changed since last read -> retry saving") + continue + } + logrus.Errorf("error updating challenge: %v", err) + } logrus.Infof("challenge %s updated to valid", u.String()) + break } }) From de7dde1266e69475f7ba396505b26439558c4792 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sun, 24 Mar 2024 08:48:26 +0100 Subject: [PATCH 14/24] tests: start fixing broken tests --- acme/api/handler_test.go | 8 +++++--- acme/challenge_test.go | 43 ++++++++++++++++++++++++++++++++++++++++ ca/ca.go | 11 ++++++---- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index 7fb2d2c3b..44a78d698 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -683,9 +683,11 @@ func TestHandler_GetChallenge(t *testing.T) { assert.Equals(t, chID, "chID") assert.Equals(t, azID, "authzID") return &acme.Challenge{ - Status: acme.StatusPending, - Type: acme.HTTP01, - AccountID: "accID", + ID: chID, + AuthorizationID: azID, + Status: acme.StatusPending, + Type: acme.HTTP01, + AccountID: "accID", }, nil }, MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error { diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 5cede1c5c..97dfbb54a 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -450,6 +450,9 @@ func TestChallenge_Validate(t *testing.T) { }, }, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -487,6 +490,9 @@ func TestChallenge_Validate(t *testing.T) { }, }, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -529,6 +535,9 @@ func TestChallenge_Validate(t *testing.T) { }, }, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -920,6 +929,9 @@ func TestHTTP01Validate(t *testing.T) { }, }, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -955,6 +967,9 @@ func TestHTTP01Validate(t *testing.T) { }, }, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -992,6 +1007,9 @@ func TestHTTP01Validate(t *testing.T) { }, }, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -1030,6 +1048,9 @@ func TestHTTP01Validate(t *testing.T) { }, }, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -1065,6 +1086,11 @@ func TestHTTP01Validate(t *testing.T) { }, nil }, }, + db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, + }, err: NewErrorISE("error reading response body for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token), } }, @@ -1089,6 +1115,11 @@ func TestHTTP01Validate(t *testing.T) { }, }, jwk: jwk, + db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, + }, err: NewErrorISE("error generating JWK thumbprint: go-jose/go-jose: unknown key type 'string'"), } }, @@ -1116,6 +1147,9 @@ func TestHTTP01Validate(t *testing.T) { }, jwk: jwk, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -1159,6 +1193,9 @@ func TestHTTP01Validate(t *testing.T) { }, jwk: jwk, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -1203,6 +1240,9 @@ func TestHTTP01Validate(t *testing.T) { }, jwk: jwk, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) @@ -1246,6 +1286,9 @@ func TestHTTP01Validate(t *testing.T) { }, jwk: jwk, db: &MockDB{ + MockGetChallenge: func(ctx context.Context, id, authzID string) (*Challenge, error) { + return ch, nil + }, MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { assert.Equal(t, "chID", updch.ID) assert.Equal(t, "token", updch.Token) diff --git a/ca/ca.go b/ca/ca.go index 66b3ffbf1..5c17e65c8 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -419,9 +419,12 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { insecureHandler = requestid.New(legacyTraceHeader).Middleware(insecureHandler) // Create context with all the necessary values. - client, err := eab.Connect(cfg.ManagementHost) - if err != nil { - return nil, errors.Wrap(err, "error connecting to EAB") + var eabClient pb.EABServiceClient + if cfg.ManagementHost != "" { + eabClient, 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 { @@ -436,7 +439,7 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } } - baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker, client, validationBroker) + baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker, eabClient, validationBroker) if allInsecure { ca.srv = server.New(cfg.Address, handler, nil) From 09e18235a9a169a6300782c65e6675b3f1c9904c Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sun, 24 Mar 2024 09:08:19 +0100 Subject: [PATCH 15/24] tests: fix broken tests --- ca/ca.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index 5c17e65c8..cd5d85adf 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -586,8 +586,8 @@ func (ca *CA) Run() error { defer wg.Done() errs <- ca.srv.ListenAndServe() }() - wg.Add(1) if ca.public != nil { + wg.Add(1) go func() { defer wg.Done() errs <- ca.public.ListenAndServe() diff --git a/go.mod b/go.mod index d35fcb6b1..ea7e6c8f4 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/rs/xid v1.5.0 github.com/sirupsen/logrus v1.9.3 - github.com/slackhq/nebula v1.7.2 + github.com/slackhq/nebula v1.6.1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 github.com/smallstep/nosql v0.6.1 diff --git a/go.sum b/go.sum index 12154216d..441cf05ed 100644 --- a/go.sum +++ b/go.sum @@ -440,8 +440,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/slackhq/nebula v1.7.2 h1:Rko1Mlksz/nC0c919xjGpB8uOSrTJ5e6KPgZx+lVfYw= -github.com/slackhq/nebula v1.7.2/go.mod h1:cnaoahkUipDs1vrNoIszyp0QPRIQN9Pm68ppQEW1Fhg= +github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= +github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= 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.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= From 5007a8605aa7c394ec034469c2dc8044f8c9e342 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Sun, 24 Mar 2024 12:11:22 +0100 Subject: [PATCH 16/24] make eab optional --- acme/api/order.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index 9426726cb..3369613d0 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -180,13 +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, r, acme.NewError(acme.ErrorServerInternalType, "Internal server error")) + if acmeProv.RequireEAB { + if missing, err := checkPermission(ctx, o.Identifiers, eak); len(missing) != 0 || err != nil { + if err != nil { + render.Error(w, r, acme.NewError(acme.ErrorServerInternalType, "Internal server error")) + return + } + render.Error(w, r, acme.NewError(acme.ErrorRejectedIdentifierType, "Missing registration for domain(s) %v", missing)) return } - render.Error(w, r, acme.NewError(acme.ErrorRejectedIdentifierType, "Missing registration for domain(s) %v", missing)) - return } for i, identifier := range o.Identifiers { From ab90f0d5c9412a7534318ac0faaf15d187cd7ec5 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Tue, 26 Mar 2024 07:52:17 +0100 Subject: [PATCH 17/24] stick to old repo name --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 227169878..6a8f77657 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,7 +13,7 @@ jobs: # Use docker.io for Docker Hub if empty REGISTRY: ghcr.io # github.repository as / - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: hm-edu/certificates permissions: contents: read packages: write @@ -54,7 +54,7 @@ jobs: # Use docker.io for Docker Hub if empty REGISTRY: ghcr.io # github.repository as / - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: hm-edu/certificates-agent permissions: contents: read packages: write From bb0370fc2fc83935e3289af9c2364a5565ba022e Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Tue, 26 Mar 2024 07:53:10 +0100 Subject: [PATCH 18/24] fix agent image name --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6a8f77657..faa7eea90 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -74,7 +74,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-agent + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=schedule type=ref,event=branch From ca0ad39002b69e911552281e76fec3a1fe689e3d Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Fri, 5 Apr 2024 07:32:54 +0200 Subject: [PATCH 19/24] upgrade packages --- cas/sectigocas/eab/client.go | 5 ++--- cas/sectigocas/sectigocas.go | 15 ++++----------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/cas/sectigocas/eab/client.go b/cas/sectigocas/eab/client.go index d93e04603..ce5b9f14c 100644 --- a/cas/sectigocas/eab/client.go +++ b/cas/sectigocas/eab/client.go @@ -34,11 +34,10 @@ func MustFromContext(ctx context.Context) pb.EABServiceClient { func Connect(host string) (pb.EABServiceClient, error) { - conn, err := grpc.DialContext( - context.Background(), + conn, err := grpc.NewClient( host, grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), ) if err != nil { return nil, err diff --git a/cas/sectigocas/sectigocas.go b/cas/sectigocas/sectigocas.go index e7530e376..d77c8f3cf 100644 --- a/cas/sectigocas/sectigocas.go +++ b/cas/sectigocas/sectigocas.go @@ -6,7 +6,6 @@ import ( "encoding/json" "encoding/pem" "fmt" - "time" "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme/api" @@ -38,26 +37,20 @@ func New(ctx context.Context, opts apiv1.Options) (*SectigoCAS, error) { if err != nil { return nil, err } - ctx, cancel := context.WithTimeout(ctx, 3*time.Second) - defer cancel() - conn, err := grpc.DialContext( - ctx, + conn, err := grpc.NewClient( config.PKIBackend, - grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), ) if err != nil { return nil, err } sslServiceClient := pb.NewSSLServiceClient(conn) - conn, err = grpc.DialContext( - ctx, + conn, err = grpc.NewClient( config.EABBackend, - grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), ) if err != nil { return nil, err From 963bbb72bcb5334494fd2a086ff04ebaae2ce643 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Mon, 13 May 2024 17:43:38 +0200 Subject: [PATCH 20/24] fix: make client id unique --- acme/mqtt/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/mqtt/client.go b/acme/mqtt/client.go index 086493ba7..4e7515346 100644 --- a/acme/mqtt/client.go +++ b/acme/mqtt/client.go @@ -25,7 +25,7 @@ func Connect(acmeDB acme.DB, host, user, password, organization string) (validat opts.PingTimeout = time.Second // local broker so response should be quick opts.ConnectRetry = true opts.AutoReconnect = true - opts.ClientID = "acme" + opts.ClientID = fmt.Sprintf("acme-%s", organization) opts.Username = user opts.Password = password opts.AddBroker(fmt.Sprintf("ssl://%s:8883", host)) From 5c04e0c2539a5d725c562ebab303f470bd5bce75 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Tue, 28 May 2024 19:00:44 +0200 Subject: [PATCH 21/24] tests: fix tests --- authority/tls_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/authority/tls_test.go b/authority/tls_test.go index 9226da47e..18199ef3f 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -1961,13 +1961,13 @@ func TestAuthority_CRL(t *testing.T) { type notImplementedCAS struct{} -func (notImplementedCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { +func (notImplementedCAS) CreateCertificate(context context.Context, req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { return nil, apiv1.NotImplementedError{} } -func (notImplementedCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { +func (notImplementedCAS) RenewCertificate(context context.Context, req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { return nil, apiv1.NotImplementedError{} } -func (notImplementedCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { +func (notImplementedCAS) RevokeCertificate(context context.Context, req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { return nil, apiv1.NotImplementedError{} } From b0d3535ddf0f2331be77a07674b1d0d05fa04267 Mon Sep 17 00:00:00 2001 From: Florian Ritterhoff Date: Wed, 29 May 2024 06:15:55 +0200 Subject: [PATCH 22/24] chore: upgrade packages --- go.mod | 24 ++++++++++++------------ go.sum | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index ea7e6c8f4..32e47abd9 100644 --- a/go.mod +++ b/go.mod @@ -56,16 +56,16 @@ require ( cloud.google.com/go/iam v1.1.8 // indirect cloud.google.com/go/kms v1.17.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 - go.opentelemetry.io/contrib/propagators/b3 v1.24.0 - go.opentelemetry.io/otel v1.24.0 - go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 + go.opentelemetry.io/contrib/propagators/b3 v1.27.0 + go.opentelemetry.io/otel v1.27.0 + go.opentelemetry.io/otel/sdk v1.27.0 ) require ( - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 ) require ( @@ -94,7 +94,7 @@ require ( github.com/aws/smithy-go v1.20.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -122,7 +122,7 @@ 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.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.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 @@ -168,9 +168,9 @@ require ( go.etcd.io/bbolt v1.3.10 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect diff --git a/go.sum b/go.sum index 441cf05ed..d1f422670 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/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= @@ -242,8 +242,8 @@ github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= 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.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= 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= @@ -494,28 +494,28 @@ go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= 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.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY= -go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/contrib/propagators/b3 v1.27.0 h1:IjgxbomVrV9za6bRi8fWCNXENs0co37SZedQilP2hm0= +go.opentelemetry.io/contrib/propagators/b3 v1.27.0/go.mod h1:Dv9obQz25lCisDvvs4dy28UPh974CxkahRDUPsY7y9E= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 h1:/0YaXu3755A/cFbtXp+21lkXgI0QE5avTWA2HjU9/WE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0/go.mod h1:m7SFxp0/7IxmJPLIY3JhOcU9CoFzDaCPL6xxQIxhA+o= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= go.step.sm/crypto v0.47.1 h1:XvqgWLA1OTJXkmkmD6QSDZrmGKP4flv3PEoau60htcU= From 60c2d145fe61db39a2af8c9bc20048c635c5aeec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jun 2024 05:44:44 +0000 Subject: [PATCH 23/24] chore(deps): bump docker/build-push-action from 5 to 6 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index faa7eea90..a17345318 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -41,7 +41,7 @@ jobs: 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 + uses: docker/build-push-action@v6 with: context: . push: ${{ github.event_name != 'pull_request' }} @@ -82,7 +82,7 @@ jobs: 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 + uses: docker/build-push-action@v6 with: context: . push: ${{ github.event_name != 'pull_request' }} From 281b67793b1b5d72afa9328e9e58fbed8f2601d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:18:32 +0000 Subject: [PATCH 24/24] chore(deps): bump google.golang.org/api from 0.184.0 to 0.186.0 Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.184.0 to 0.186.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.184.0...v0.186.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 32e47abd9..40fb641c1 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-tpm v0.9.0 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.12.4 + github.com/googleapis/gax-go/v2 v2.12.5 github.com/hashicorp/vault/api v1.14.0 github.com/hashicorp/vault/api/auth/approle v0.7.0 github.com/hashicorp/vault/api/auth/kubernetes v0.7.0 @@ -43,14 +43,14 @@ require ( golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 golang.org/x/net v0.26.0 - google.golang.org/api v0.184.0 + google.golang.org/api v0.186.0 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.2 ) require ( - cloud.google.com/go v0.114.0 // indirect - cloud.google.com/go/auth v0.5.1 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.6.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect @@ -176,9 +176,9 @@ require ( golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d1f422670..b7ad5992f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= -cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= -cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= -cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.6.0 h1:5x+d6b5zdezZ7gmLWD1m/xNjnaQ2YDhmIz/HH3doy1g= +cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= @@ -238,8 +238,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= 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.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= @@ -669,19 +669,19 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.184.0 h1:dmEdk6ZkJNXy1JcDhn/ou0ZUq7n9zropG2/tR4z+RDg= -google.golang.org/api v0.184.0/go.mod h1:CeDTtUEiYENAf8PPG5VZW2yNp2VM3VWbCeTioAZBTBA= +google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug= +google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 h1:HCZ6DlkKtCDAtD8ForECsY3tKuaR+p4R3grlK80uCCc= -google.golang.org/genproto v0.0.0-20240604185151-ef581f913117/go.mod h1:lesfX/+9iA+3OdqeCpoDddJaNxVB1AB6tD7EfqMmprc= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 h1:CUiCqkPw1nNrNQzCCG4WA65m0nAmQiwXHpub3dNyruU= +google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4/go.mod h1:EvuUDCulqGgV80RvP1BHuom+smhX4qtlhnNatHuroGQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=