From 7d4fe4012f46c941579ed90333d3f648860a96f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:45:39 +0000 Subject: [PATCH 1/2] Bump github.com/AppsFlyer/go-sundheit in the third-party group Bumps the third-party group with 1 update: [github.com/AppsFlyer/go-sundheit](https://github.com/AppsFlyer/go-sundheit). Updates `github.com/AppsFlyer/go-sundheit` from 0.5.1 to 0.6.0 - [Release notes](https://github.com/AppsFlyer/go-sundheit/releases) - [Commits](https://github.com/AppsFlyer/go-sundheit/compare/v0.5.1...v0.6.0) --- updated-dependencies: - dependency-name: github.com/AppsFlyer/go-sundheit dependency-type: direct:production update-type: version-update:semver-minor dependency-group: third-party ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5d2e81c89..2d830d160 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/openziti/ziti go 1.22 require ( - github.com/AppsFlyer/go-sundheit v0.5.1 + github.com/AppsFlyer/go-sundheit v0.6.0 github.com/Jeffail/gabs v1.4.0 github.com/Jeffail/gabs/v2 v2.7.0 github.com/MakeNowJust/heredoc v1.0.0 diff --git a/go.sum b/go.sum index 4eaec6157..c3d8b3c65 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74= -github.com/AppsFlyer/go-sundheit v0.5.1 h1:VLmM1/tbn3fAHHFdp7Ew75sc2+Sfy31ADA8TaMbN7No= -github.com/AppsFlyer/go-sundheit v0.5.1/go.mod h1:LDdBHD6tQBtmHsdW+i1GwdTt6Wqc0qazf5ZEJVTbTME= +github.com/AppsFlyer/go-sundheit v0.6.0 h1:d2hBvCjBSb2lUsEWGfPigr4MCOt04sxB+Rppl0yUMSk= +github.com/AppsFlyer/go-sundheit v0.6.0/go.mod h1:LDdBHD6tQBtmHsdW+i1GwdTt6Wqc0qazf5ZEJVTbTME= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= From 353cf93905701a6f0a375c43833d765f2fde0880 Mon Sep 17 00:00:00 2001 From: Andrew Martinez Date: Tue, 25 Jun 2024 09:01:27 -0400 Subject: [PATCH 2/2] fixes #2154 500 error on mfa enrollment in HA, fixes #2159 HA certs - fixes 500 internal error on MFA enrllment in HA deployments - fixes HA API Session Certs not working in HA - adds spiffehlp module to common - adds tests api session certs w/ spiffe id - adds tests for SPIFFE IDs in API Session Certs - adds tests for ext jwt + cert auth --- common/spiffehlp/spiffe.go | 90 ++++++++++++ .../routes/current_api_session_router.go | 21 ++- .../routes/current_identity_router.go | 10 +- .../model/api_session_certificate_manager.go | 40 ++++-- controller/model/authenticator_mod_ext_jwt.go | 6 + controller/response/context.go | 1 + go.mod | 8 +- go.sum | 16 +-- router/xgress_edge/connections.go | 87 +++++++++--- tests/api_session_certificates_test.go | 89 +++++++++++- tests/sdk_auth_test.go | 129 +++++++++++++++++- zititest/go.mod | 10 +- zititest/go.sum | 20 +-- 13 files changed, 461 insertions(+), 66 deletions(-) create mode 100644 common/spiffehlp/spiffe.go diff --git a/common/spiffehlp/spiffe.go b/common/spiffehlp/spiffe.go new file mode 100644 index 000000000..de5cf30dc --- /dev/null +++ b/common/spiffehlp/spiffe.go @@ -0,0 +1,90 @@ +/* + Copyright NetFoundry Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package spiffehlp + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "github.com/pkg/errors" + "net/url" +) + +// GetSpiffeIdFromCertChain cycles through a slice of certificates that goes from leaf up CAs. Each certificate +// must contain 0 or 1 spiffe:// URI SAN. The first encountered SPIFFE id looking up the chain back to the root CA is returned. +// If no SPIFFE id is encountered, nil is returned. Errors are returned for parsing and processing errors only. +func GetSpiffeIdFromCertChain(certs []*x509.Certificate) (*url.URL, error) { + var spiffeId *url.URL + for _, cert := range certs { + var err error + spiffeId, err = GetSpiffeIdFromCert(cert) + + if err != nil { + return nil, fmt.Errorf("failed to determine SPIFFE ID from x509 certificate chain: %w", err) + } + + if spiffeId != nil { + return spiffeId, nil + } + } + + return nil, errors.New("failed to determine SPIFFE ID, no spiffe:// URI SANs found in x509 certificate chain") +} + +// GetSpiffeIdFromTlsCertChain will search a tls certificate chain for a trust domain encoded as a spiffe:// URI SAN. +// Each certificate must contain 0 or 1 spiffe:// URI SAN. The first SPIFFE id looking up the chain is returned. If +// no SPIFFE id is encountered, nil is returned. Errors are returned for parsing and processing errors only. +func GetSpiffeIdFromTlsCertChain(tlsCerts []*tls.Certificate) (*url.URL, error) { + for _, tlsCert := range tlsCerts { + for i, rawCert := range tlsCert.Certificate { + cert, err := x509.ParseCertificate(rawCert) + + if err != nil { + return nil, fmt.Errorf("failed to parse TLS cert at index [%d]: %w", i, err) + } + + spiffeId, err := GetSpiffeIdFromCert(cert) + + if err != nil { + return nil, fmt.Errorf("failed to determine SPIFFE ID from TLS cert at index [%d]: %w", i, err) + } + + if spiffeId != nil { + return spiffeId, nil + } + } + } + + return nil, nil +} + +// GetSpiffeIdFromCert will search a x509 certificate for a trust domain encoded as a spiffe:// URI SAN. +// Each certificate must contain 0 or 1 spiffe:// URI SAN. The first SPIFFE id looking up the chain is returned. If +// no SPIFFE id is encountered, nil is returned. Errors are returned for parsing and processing errors only. +func GetSpiffeIdFromCert(cert *x509.Certificate) (*url.URL, error) { + var spiffeId *url.URL + for _, uriSan := range cert.URIs { + if uriSan.Scheme == "spiffe" { + if spiffeId != nil { + return nil, fmt.Errorf("multiple URI SAN spiffe:// ids encountered, must only have one, encountered at least two: [%s] and [%s]", spiffeId.String(), uriSan.String()) + } + spiffeId = uriSan + } + } + + return spiffeId, nil +} diff --git a/controller/internal/routes/current_api_session_router.go b/controller/internal/routes/current_api_session_router.go index 7b49675d0..f4c123378 100644 --- a/controller/internal/routes/current_api_session_router.go +++ b/controller/internal/routes/current_api_session_router.go @@ -24,6 +24,7 @@ import ( "github.com/openziti/edge-api/rest_model" "github.com/openziti/ziti/controller/env" "github.com/openziti/ziti/controller/internal/permissions" + "github.com/openziti/ziti/controller/model" "github.com/openziti/ziti/controller/response" "net/http" "time" @@ -125,7 +126,15 @@ func (router *CurrentSessionRouter) ListCertificates(ae *env.AppEnv, rc *respons func (router *CurrentSessionRouter) CreateCertificate(ae *env.AppEnv, rc *response.RequestContext, params clientCurrentApiSession.CreateCurrentAPISessionCertificateParams) { responder := &ApiSessionCertificateCreateResponder{ae: ae, Responder: rc} CreateWithResponder(rc, responder, CurrentApiSessionCertificateLinkFactory, func() (string, error) { - return ae.GetManagers().ApiSessionCertificate.CreateFromCSR(rc.ApiSession.Id, 12*time.Hour, []byte(*params.SessionCertificate.Csr), rc.NewChangeContext()) + newApiSessionCert, err := ae.GetManagers().ApiSessionCertificate.CreateFromCSR(rc.Identity, rc.ApiSession, rc.IsJwtToken, 12*time.Hour, []byte(*params.SessionCertificate.Csr), rc.NewChangeContext()) + + if err != nil { + return "", err + } + + responder.ApiSessionCertificate = newApiSessionCert + + return newApiSessionCert.Id, nil }) } @@ -191,18 +200,18 @@ func (router *CurrentSessionRouter) ListServiceUpdates(ae *env.AppEnv, rc *respo type ApiSessionCertificateCreateResponder struct { response.Responder - ae *env.AppEnv + ApiSessionCertificate *model.ApiSessionCertificate + ae *env.AppEnv } func (nsr *ApiSessionCertificateCreateResponder) RespondWithCreatedId(id string, _ rest_model.Link) { - sessionCert, _ := nsr.ae.GetManagers().ApiSessionCertificate.Read(id) - certString := sessionCert.PEM + certString := nsr.ApiSessionCertificate.PEM newSessionEnvelope := &rest_model.CreateCurrentAPISessionCertificateEnvelope{ Data: &rest_model.CurrentAPISessionCertificateCreateResponse{ CreateLocation: rest_model.CreateLocation{ - Links: CurrentApiSessionCertificateLinkFactory.Links(sessionCert), - ID: sessionCert.Id, + Links: CurrentApiSessionCertificateLinkFactory.Links(nsr.ApiSessionCertificate), + ID: nsr.ApiSessionCertificate.Id, }, Certificate: &certString, Cas: string(nsr.ae.GetConfig().Edge.CaPems()), diff --git a/controller/internal/routes/current_identity_router.go b/controller/internal/routes/current_identity_router.go index 338ca4681..39d2b3763 100644 --- a/controller/internal/routes/current_identity_router.go +++ b/controller/internal/routes/current_identity_router.go @@ -144,11 +144,13 @@ func (r *CurrentIdentityRouter) verifyMfa(ae *env.AppEnv, rc *response.RequestCo return } - err = ae.Managers.ApiSession.SetMfaPassed(rc.ApiSession, changeCtx) + if !rc.IsJwtToken { + err = ae.Managers.ApiSession.SetMfaPassed(rc.ApiSession, changeCtx) - if err != nil { - rc.RespondWithError(err) - return + if err != nil { + rc.RespondWithError(err) + return + } } rc.RespondWithEmptyOk() diff --git a/controller/model/api_session_certificate_manager.go b/controller/model/api_session_certificate_manager.go index 2875ccfbe..4a0c8ff1b 100644 --- a/controller/model/api_session_certificate_manager.go +++ b/controller/model/api_session_certificate_manager.go @@ -18,12 +18,15 @@ package model import ( "crypto/x509" + "fmt" "github.com/openziti/ziti/common/cert" + "github.com/openziti/ziti/common/eid" "github.com/openziti/ziti/controller/apierror" "github.com/openziti/ziti/controller/change" "github.com/openziti/ziti/controller/db" "github.com/openziti/ziti/controller/models" "go.etcd.io/bbolt" + "net/url" "time" ) @@ -48,7 +51,7 @@ func (self *ApiSessionCertificateManager) Create(entity *ApiSessionCertificate, return self.createEntity(entity, ctx.NewMutateContext()) } -func (self *ApiSessionCertificateManager) CreateFromCSR(apiSessionId string, lifespan time.Duration, csrPem []byte, ctx *change.Context) (string, error) { +func (self *ApiSessionCertificateManager) CreateFromCSR(identity *Identity, apiSession *ApiSession, isJwt bool, lifespan time.Duration, csrPem []byte, ctx *change.Context) (*ApiSessionCertificate, error) { notBefore := time.Now() notAfter := time.Now().Add(lifespan) @@ -58,38 +61,59 @@ func (self *ApiSessionCertificateManager) CreateFromCSR(apiSessionId string, lif apiErr := apierror.NewCouldNotProcessCsr() apiErr.Cause = err apiErr.AppendCause = true - return "", apiErr + return nil, apiErr + } + + newId := eid.New() + + trustDomain := self.env.GetConfig().SpiffeIdTrustDomain.Hostname() + spiffeId := &url.URL{ + Scheme: "spiffe", + Host: trustDomain, + Path: fmt.Sprintf("identity/%s/apiSession/%s/apiSessionCertificate/%s", identity.Id, apiSession.Id, newId), } certRaw, err := self.env.GetApiClientCsrSigner().SignCsr(csr, &cert.SigningOpts{ NotAfter: ¬After, NotBefore: ¬Before, + URIs: []*url.URL{ + spiffeId, + }, }) if err != nil { apiErr := apierror.NewCouldNotProcessCsr() apiErr.Cause = err apiErr.AppendCause = true - return "", apiErr + return nil, apiErr } fp := self.env.GetFingerprintGenerator().FromRaw(certRaw) certPem, _ := cert.RawToPem(certRaw) - cert, _ := x509.ParseCertificate(certRaw) + newCert, _ := x509.ParseCertificate(certRaw) entity := &ApiSessionCertificate{ - BaseEntity: models.BaseEntity{}, - ApiSessionId: apiSessionId, - Subject: cert.Subject.String(), + BaseEntity: models.BaseEntity{ + Id: newId, + }, + ApiSessionId: apiSession.Id, + Subject: newCert.Subject.String(), Fingerprint: fp, ValidAfter: ¬Before, ValidBefore: ¬After, PEM: string(certPem), } - return self.Create(entity, ctx) + if isJwt { + // can't create if using bearer tokens, the API Session will not exist + return entity, nil + } + + entity.Id, err = self.Create(entity, ctx) + + return entity, err } func (self *ApiSessionCertificateManager) IsUpdated(_ string) bool { diff --git a/controller/model/authenticator_mod_ext_jwt.go b/controller/model/authenticator_mod_ext_jwt.go index 92111975b..33af5399c 100644 --- a/controller/model/authenticator_mod_ext_jwt.go +++ b/controller/model/authenticator_mod_ext_jwt.go @@ -352,6 +352,11 @@ func (a *AuthModuleExtJwt) process(context AuthContext, isPrimary bool) (AuthRes var verifyResults []*candidateResult + if len(candidates) == 0 { + logger.Error("encountered 0 candidate JWTs, verification cannot occur") + return nil, apierror.NewInvalidAuth() + } + for i, candidate := range candidates { verifyResult := a.verifyCandidate(context, isPrimary, candidate) @@ -378,6 +383,7 @@ func (a *AuthModuleExtJwt) process(context AuthContext, isPrimary bool) (AuthRes if isPrimary { authType = "primary" } + logger.Errorf("encountered %d candidate JWTs and all failed to validate for %s authentication, see the following log messages", len(verifyResults), authType) for i, result := range verifyResults { result.LogResult(logger, i) diff --git a/controller/response/context.go b/controller/response/context.go index ec9da5a7a..0caa4dd4a 100644 --- a/controller/response/context.go +++ b/controller/response/context.go @@ -23,6 +23,7 @@ import ( "github.com/openziti/ziti/common" "github.com/openziti/ziti/controller/change" "github.com/openziti/ziti/controller/model" + "net/http" "time" ) diff --git a/go.mod b/go.mod index 2d830d160..5327abb2a 100644 --- a/go.mod +++ b/go.mod @@ -136,7 +136,7 @@ require ( github.com/kr/pty v1.1.8 // indirect github.com/kyokomi/emoji/v2 v2.2.12 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -182,11 +182,11 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/image v0.13.0 // indirect - golang.org/x/mod v0.18.0 // indirect + golang.org/x/mod v0.19.0 // indirect golang.org/x/term v0.22.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.23.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect nhooyr.io/websocket v1.8.11 // indirect diff --git a/go.sum b/go.sum index c3d8b3c65..f21259fbe 100644 --- a/go.sum +++ b/go.sum @@ -458,8 +458,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucsky/cuid v1.2.1 h1:MtJrL2OFhvYufUIn48d35QGXyeTC8tn0upumW9WwTHg= github.com/lucsky/cuid v1.2.1/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE= -github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0KA2oYIngXVFhQcXxhLqCVK7cBcldkk= -github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -872,8 +872,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -906,8 +906,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1163,8 +1163,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= diff --git a/router/xgress_edge/connections.go b/router/xgress_edge/connections.go index 5a670b5a8..4e2fc17ea 100644 --- a/router/xgress_edge/connections.go +++ b/router/xgress_edge/connections.go @@ -17,6 +17,7 @@ package xgress_edge import ( + "crypto/x509" "errors" "fmt" "github.com/michaelquigley/pfxlog" @@ -24,7 +25,9 @@ import ( "github.com/openziti/metrics" "github.com/openziti/sdk-golang/ziti/edge" "github.com/openziti/ziti/common/cert" + "github.com/openziti/ziti/common/spiffehlp" "github.com/openziti/ziti/router/state" + "strings" ) type sessionConnectionHandler struct { @@ -61,7 +64,7 @@ func (handler *sessionConnectionHandler) BindChannel(binding channel.Binding, ed } fpg := cert.NewFingerprintGenerator() - fingerprints := fpg.FromCerts(certificates) + fingerprint := fpg.FromCert(certificates[0]) token := string(byteToken) @@ -81,38 +84,52 @@ func (handler *sessionConnectionHandler) BindChannel(binding channel.Binding, ed handler.invalidApiSessionTokenDuringSync.Mark(1) } - return fmt.Errorf("no api session found for token [%s], fingerprints: [%v], subjects [%v]", token, fingerprints, subjects) + return fmt.Errorf("no api session found for token [%s], fingerprint: [%v], subjects [%v]", token, fingerprint, subjects) } edgeConn.apiSession = apiSession - if apiSession.Claims != nil { - token = apiSession.Claims.ApiSessionId + isValid := handler.validateBySpiffeId(apiSession, certificates[0]) + + if !isValid { + isValid = handler.validateByFingerprint(apiSession, fingerprint) } - for _, fingerprint := range apiSession.CertFingerprints { - if fingerprints.Contains(fingerprint) { - removeListener := handler.stateManager.AddApiSessionRemovedListener(token, func(token string) { - if !ch.IsClosed() { - if err := ch.Close(); err != nil { - pfxlog.Logger().WithError(err).Error("could not close channel during api session removal") - } + if isValid { + if apiSession.Claims != nil { + token = apiSession.Claims.ApiSessionId + } + + removeListener := handler.stateManager.AddApiSessionRemovedListener(token, func(token string) { + if !ch.IsClosed() { + if err := ch.Close(); err != nil { + pfxlog.Logger().WithError(err).Error("could not close channel during api session removal") } + } - handler.stateManager.RemoveActiveChannel(ch) - }) + handler.stateManager.RemoveActiveChannel(ch) + }) - handler.stateManager.AddActiveChannel(ch, apiSession) - handler.stateManager.AddConnectedApiSessionWithChannel(token, removeListener, ch) + handler.stateManager.AddActiveChannel(ch, apiSession) + handler.stateManager.AddConnectedApiSessionWithChannel(token, removeListener, ch) - return nil - } + return nil } _ = ch.Close() return errors.New("invalid client certificate for api session") } +func (handler *sessionConnectionHandler) validateByFingerprint(apiSession *state.ApiSession, clientFingerprint string) bool { + for _, fingerprint := range apiSession.CertFingerprints { + if clientFingerprint == fingerprint { + return true + } + } + + return false +} + func (handler *sessionConnectionHandler) HandleClose(ch channel.Channel) { token := "" if byteToken, ok := ch.Underlay().Headers()[edge.SessionTokenHeader]; ok { @@ -125,3 +142,39 @@ func (handler *sessionConnectionHandler) HandleClose(ch channel.Channel) { Error("session connection handler encountered a HandleClose that did not have a SessionTokenHeader") } } + +func (handler *sessionConnectionHandler) validateBySpiffeId(apiSession *state.ApiSession, clientCert *x509.Certificate) bool { + spiffeId, err := spiffehlp.GetSpiffeIdFromCert(clientCert) + + if err != nil { + return false + } + + if spiffeId == nil { + return false + } + + parts := strings.Split(spiffeId.Path, "/") + + if len(parts) != 6 { + return false + } + + if parts[0] != "identity" { + return false + } + + if parts[2] != "apiSession" { + return false + } + + if parts[4] != "apiSessionCertificate" { + return false + } + + if apiSession.Id == parts[3] { + return true + } + + return false +} diff --git a/tests/api_session_certificates_test.go b/tests/api_session_certificates_test.go index bedc309cb..72cd38721 100644 --- a/tests/api_session_certificates_test.go +++ b/tests/api_session_certificates_test.go @@ -25,11 +25,18 @@ import ( "crypto/rand" "crypto/x509" "encoding/pem" + "fmt" "github.com/Jeffail/gabs" "github.com/michaelquigley/pfxlog" + "github.com/openziti/edge-api/rest_client_api_client/current_api_session" + "github.com/openziti/edge-api/rest_model" "github.com/openziti/identity/certtools" + edge_apis "github.com/openziti/sdk-golang/edge-apis" + "github.com/openziti/ziti/common/spiffehlp" "net/http" + "net/url" "os" + "strings" "testing" ) @@ -38,7 +45,7 @@ func Test_Api_Session_Certs(t *testing.T) { defer ctx.Teardown() ctx.StartServer() - t.Run("as the default admin, session certs", func(t *testing.T) { + t.Run("as the default admin, session certs, using legacy authentication", func(t *testing.T) { var createdResponseBody *gabs.Container //used across multiple subtests, set in first var createdId string //used across multiple subtests, set in first @@ -174,6 +181,86 @@ func Test_Api_Session_Certs(t *testing.T) { standardJsonResponseTests(resp, http.StatusOK, t) }) }) + + t.Run("as admin using oidc authentication", func(t *testing.T) { + ctx.testContextChanged(t) + + adminCreds := edge_apis.NewUpdbCredentials(ctx.AdminAuthenticator.Username, ctx.AdminAuthenticator.Password) + + clientApiUrl, err := url.Parse("https://" + ctx.ApiHost + EdgeClientApiPath) + ctx.Req.NoError(err) + + adminClientClient := edge_apis.NewClientApiClient([]*url.URL{clientApiUrl}, ctx.ControllerConfig.Id.CA(), func(strings chan string) { + strings <- "123" + }) + + adminClientClient.SetUseOidc(true) + adminClientApiSession, err := adminClientClient.Authenticate(adminCreds, nil) + ctx.Req.NoError(err) + ctx.Req.NotNil(adminClientApiSession) + + managementApiUrl, err := url.Parse("https://" + ctx.ApiHost + EdgeManagementApiPath) + ctx.Req.NoError(err) + + adminManagementClient := edge_apis.NewManagementApiClient([]*url.URL{managementApiUrl}, ctx.ControllerConfig.Id.CA(), func(strings chan string) { + strings <- "123" + }) + + adminManagementClient.SetUseOidc(true) + adminManagementApiSession, err := adminManagementClient.Authenticate(adminCreds, nil) + ctx.Req.NoError(err) + ctx.Req.NotNil(adminManagementApiSession) + + t.Run("can create a new session certificate", func(t *testing.T) { + ctx.testContextChanged(t) + + csrBytes, privateKey, err := generateCsr() + + ctx.Req.NoError(err) + ctx.Req.NotNil(privateKey) + ctx.Req.NotEmpty(csrBytes) + + csrPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) + ctx.Req.NotEmpty(csrPem) + + csr := string(csrPem) + + params := ¤t_api_session.CreateCurrentAPISessionCertificateParams{ + SessionCertificate: &rest_model.CurrentAPISessionCertificateCreate{ + Csr: &csr, + }, + } + + resp, err := adminClientClient.API.CurrentAPISession.CreateCurrentAPISessionCertificate(params, nil) + + ctx.Req.NoError(err) + ctx.Req.NotNil(resp) + ctx.Req.NotNil(resp.Payload) + ctx.Req.NotNil(resp.Payload.Data) + ctx.Req.NotNil(resp.Payload.Data.Certificate) + + t.Run("the certificate contains the proper SPIFFE id", func(t *testing.T) { + ctx.testContextChanged(t) + + certs, err := parsePEMBundle([]byte(*resp.Payload.Data.Certificate)) + + ctx.Req.NoError(err) + ctx.Req.Len(certs, 1) + + spiffeId, err := spiffehlp.GetSpiffeIdFromCert(certs[0]) + + ctx.Req.NoError(err) + ctx.Req.NotEmpty(spiffeId) + + apiSession := *adminClientClient.ApiSession.Load() + apiSessionId := apiSession.GetId() + identityId := apiSession.GetIdentityId() + expectedId := fmt.Sprintf("%s/identity/%s/apiSession/%s/apiSessionCertificate/", ctx.ControllerConfig.SpiffeIdTrustDomain, identityId, apiSessionId) + ctx.Req.True(strings.HasPrefix(spiffeId.String(), expectedId), "expected the spiffe id to have the prefix %s, but got %s", expectedId, spiffeId) + + }) + }) + }) } func generateCsr() ([]byte, crypto.PrivateKey, error) { diff --git a/tests/sdk_auth_test.go b/tests/sdk_auth_test.go index 67f8022e4..80d95de42 100644 --- a/tests/sdk_auth_test.go +++ b/tests/sdk_auth_test.go @@ -4,9 +4,10 @@ import ( "crypto/x509" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "github.com/openziti/edge-api/rest_client_api_client/current_api_session" "github.com/openziti/edge-api/rest_management_api_client/auth_policy" "github.com/openziti/edge-api/rest_management_api_client/external_jwt_signer" - identity2 "github.com/openziti/edge-api/rest_management_api_client/identity" + management_identity "github.com/openziti/edge-api/rest_management_api_client/identity" "github.com/openziti/edge-api/rest_model" "github.com/openziti/edge-api/rest_util" nfpem "github.com/openziti/foundation/v2/pem" @@ -78,7 +79,7 @@ func TestSdkAuth(t *testing.T) { ctx.Req.NotNil(apiSession.GetToken()) }) - t.Run("management cert, ca pool on client can authenticate", func(t *testing.T) { + t.Run("management cert, ca pool on client can authenticate", func(t *testing.T) { ctx.testContextChanged(t) creds := edge_apis.NewCertCredentials([]*x509.Certificate{testIdCerts.cert}, testIdCerts.key) @@ -236,7 +237,7 @@ func TestSdkAuth(t *testing.T) { Type: &identityType, } - identityCreateParams := identity2.NewCreateIdentityParams() + identityCreateParams := management_identity.NewCreateIdentityParams() identityCreateParams.Identity = idCreate idCreateResp, err := adminClient.API.Identity.CreateIdentity(identityCreateParams, nil) @@ -306,4 +307,126 @@ func TestSdkAuth(t *testing.T) { }) }) + t.Run("client cert + secondary JWT can authenticate", func(t *testing.T) { + ctx.testContextChanged(t) + + certJwtId := ctx.AdminManagementSession.RequireNewIdentityWithOtt(false) + certJwtCerts := ctx.completeOttEnrollment(certJwtId.Id) + + //valid signer with issuer and audience + validJwtSignerCert, validJwtSignerPrivateKey := newSelfSignedCert("valid signer") + validJwtSignerCertPem := nfpem.EncodeToString(validJwtSignerCert) + + validJwtSigner := &rest_model.ExternalJWTSignerCreate{ + CertPem: &validJwtSignerCertPem, + Enabled: B(true), + Name: S("Test Cert+JWT Signer - Enabled"), + Kid: S(uuid.NewString()), + Issuer: S("the-very-best-iss2"), + Audience: S("the-very-best-aud2"), + UseExternalID: B(false), + } + + //valid signed jwt + jwtToken := jwt.New(jwt.SigningMethodES256) + jwtToken.Claims = jwt.RegisteredClaims{ + Audience: []string{*validJwtSigner.Audience}, + ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(2 * time.Hour)}, + ID: time.Now().String(), + IssuedAt: &jwt.NumericDate{Time: time.Now()}, + Issuer: *validJwtSigner.Issuer, + NotBefore: &jwt.NumericDate{Time: time.Now()}, + Subject: certJwtId.Id, + } + + jwtToken.Header["kid"] = *validJwtSigner.Kid + + jwtStrSigned, err := jwtToken.SignedString(validJwtSignerPrivateKey) + ctx.Req.NoError(err) + ctx.Req.NotEmpty(jwtStrSigned) + + // ext jwt + createExtJwtParams := external_jwt_signer.NewCreateExternalJWTSignerParams() + createExtJwtParams.ExternalJWTSigner = validJwtSigner + + createExtJwtSignerResp, err := adminClient.API.ExternalJWTSigner.CreateExternalJWTSigner(createExtJwtParams, nil) + ctx.Req.NoError(err) + ctx.Req.NotNil(createExtJwtSignerResp) + ctx.Req.NotEmpty(createExtJwtSignerResp.Payload.Data.ID) + + //auth policy + authPolicyCreate := &rest_model.AuthPolicyCreate{ + Name: S(uuid.NewString()), + Primary: &rest_model.AuthPolicyPrimary{ + Cert: &rest_model.AuthPolicyPrimaryCert{Allowed: B(true), AllowExpiredCerts: B(false)}, + ExtJWT: &rest_model.AuthPolicyPrimaryExtJWT{Allowed: B(false), AllowedSigners: make([]string, 0)}, + Updb: &rest_model.AuthPolicyPrimaryUpdb{ + Allowed: B(false), + LockoutDurationMinutes: I(0), + MaxAttempts: I(0), + MinPasswordLength: I(5), + RequireMixedCase: B(true), + RequireNumberChar: B(true), + RequireSpecialChar: B(true), + }, + }, + Secondary: &rest_model.AuthPolicySecondary{ + RequireExtJWTSigner: ToPtr(createExtJwtSignerResp.Payload.Data.ID), + RequireTotp: B(false), + }, + Tags: &rest_model.Tags{SubTags: rest_model.SubTags{}}, + } + + authPolicyCreateParams := auth_policy.NewCreateAuthPolicyParams() + authPolicyCreateParams.AuthPolicy = authPolicyCreate + + createAuthPolicyResp, err := adminClient.API.AuthPolicy.CreateAuthPolicy(authPolicyCreateParams, nil) + err = rest_util.WrapErr(err) + ctx.Req.NoError(err) + ctx.Req.NotNil(createAuthPolicyResp) + ctx.Req.NotEmpty(createAuthPolicyResp.Payload.Data.ID) + + //assign identity auth policy + identityPatchParams := &management_identity.PatchIdentityParams{ + ID: certJwtId.Id, + Identity: &rest_model.IdentityPatch{ + AuthPolicyID: S(createAuthPolicyResp.Payload.Data.ID), + }, + } + + identityPatchResp, err := adminClient.API.Identity.PatchIdentity(identityPatchParams, nil) + ctx.Req.NoError(err) + ctx.Req.NotNil(identityPatchResp) + + t.Run("client cert + secondary JWT, ca pool on client", func(t *testing.T) { + ctx.testContextChanged(t) + creds := edge_apis.NewCertCredentials([]*x509.Certificate{certJwtCerts.cert}, certJwtCerts.key) + + creds.AddJWT(jwtStrSigned) + + client := edge_apis.NewClientApiClient([]*url.URL{clientApiUrl}, ctx.ControllerConfig.Id.CA(), func(strings chan string) { + strings <- "123" + }) + apiSession, err := client.Authenticate(creds, nil) + + ctx.Req.NoError(err) + ctx.Req.NotNil(client) + ctx.Req.NotNil(apiSession) + ctx.Req.NotNil(apiSession.GetToken()) + + t.Run("can issue requests", func(t *testing.T) { + ctx.testContextChanged(t) + + getCurrentApiSessionparams := ¤t_api_session.GetCurrentAPISessionParams{} + + resp, err := client.API.CurrentAPISession.GetCurrentAPISession(getCurrentApiSessionparams, nil) + + ctx.Req.NoError(err) + ctx.Req.NotNil(resp) + ctx.Req.NotNil(resp.Payload) + ctx.Req.NotNil(resp.Payload.Data) + ctx.Req.NotEmpty(resp.Payload.Data.ID) + }) + }) + }) } diff --git a/zititest/go.mod b/zititest/go.mod index 5f4d04f87..33a066faa 100644 --- a/zititest/go.mod +++ b/zititest/go.mod @@ -33,7 +33,7 @@ require ( ) require ( - github.com/AppsFlyer/go-sundheit v0.5.1 // indirect + github.com/AppsFlyer/go-sundheit v0.6.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/MichaelMure/go-term-markdown v0.1.4 // indirect github.com/MichaelMure/go-term-text v0.3.1 // indirect @@ -113,7 +113,7 @@ require ( github.com/kyokomi/emoji/v2 v2.2.12 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucsky/cuid v1.2.1 // indirect - github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -186,15 +186,15 @@ require ( go.uber.org/multierr v1.9.0 // indirect go4.org v0.0.0-20180809161055-417644f6feb5 // indirect golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/image v0.18.0 // indirect - golang.org/x/mod v0.18.0 // indirect + golang.org/x/mod v0.19.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.23.0 // indirect gopkg.in/AlecAivazis/survey.v1 v1.8.8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/resty.v1 v1.12.0 // indirect diff --git a/zititest/go.sum b/zititest/go.sum index a357ac84c..477f6337e 100644 --- a/zititest/go.sum +++ b/zititest/go.sum @@ -45,8 +45,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74= -github.com/AppsFlyer/go-sundheit v0.5.1 h1:VLmM1/tbn3fAHHFdp7Ew75sc2+Sfy31ADA8TaMbN7No= -github.com/AppsFlyer/go-sundheit v0.5.1/go.mod h1:LDdBHD6tQBtmHsdW+i1GwdTt6Wqc0qazf5ZEJVTbTME= +github.com/AppsFlyer/go-sundheit v0.6.0 h1:d2hBvCjBSb2lUsEWGfPigr4MCOt04sxB+Rppl0yUMSk= +github.com/AppsFlyer/go-sundheit v0.6.0/go.mod h1:LDdBHD6tQBtmHsdW+i1GwdTt6Wqc0qazf5ZEJVTbTME= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -472,8 +472,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucsky/cuid v1.2.1 h1:MtJrL2OFhvYufUIn48d35QGXyeTC8tn0upumW9WwTHg= github.com/lucsky/cuid v1.2.1/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE= -github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0KA2oYIngXVFhQcXxhLqCVK7cBcldkk= -github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -899,8 +899,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -933,8 +933,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1190,8 +1190,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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=