From a3de984ee3c145550ab4f2effac5065975107262 Mon Sep 17 00:00:00 2001 From: beltram Date: Fri, 19 Jan 2024 09:55:15 +0100 Subject: [PATCH 1/8] fix: use 2 separate identifiers for Wire --- acme/api/order.go | 36 +++++++++++++++++++++++++++------- acme/order.go | 49 ++++++++++++++++++++++++++++------------------- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index bb7786b0..6f4a51e3 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -49,7 +49,12 @@ func (n *NewOrderRequest) Validate() error { if id.Value == "" { return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty") } - case acme.WireID: + case acme.WireUser: + _, err := wire.ParseID([]byte(id.Value)) + if err != nil { + return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID") + } + case acme.WireDevice: wireID, err := wire.ParseID([]byte(id.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID") @@ -273,10 +278,27 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error { } var target string - if az.Identifier.Type == acme.WireID { + if az.Identifier.Type == acme.WireUser { + wireOptions, err := prov.GetOptions().GetWireOptions() + if err != nil { + return acme.WrapErrorISE(err, "failed getting Wire options") + } + var targetProvider interface{ EvaluateTarget(string) (string, error) } + switch typ { + case acme.WIREOIDC01: + targetProvider = wireOptions.GetOIDCOptions() + default: + return acme.NewError(acme.ErrorMalformedType, "unsupported type %q", typ) + } + + target, err = targetProvider.EvaluateTarget("") + if err != nil { + return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'") + } + } else if az.Identifier.Type == acme.WireDevice { wireID, err := wire.ParseID([]byte(az.Identifier.Value)) if err != nil { - return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireID") + return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireUser") } clientID, err := wire.ParseClientID(wireID.ClientID) if err != nil { @@ -288,8 +310,6 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error { } var targetProvider interface{ EvaluateTarget(string) (string, error) } switch typ { - case acme.WIREOIDC01: - targetProvider = wireOptions.GetOIDCOptions() case acme.WIREDPOP01: targetProvider = wireOptions.GetDPOPOptions() default: @@ -440,8 +460,10 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType { } case acme.PermanentIdentifier: chTypes = []acme.ChallengeType{acme.DEVICEATTEST01} - case acme.WireID: - chTypes = []acme.ChallengeType{acme.WIREOIDC01, acme.WIREDPOP01} + case acme.WireUser: + chTypes = []acme.ChallengeType{acme.WIREOIDC01} + case acme.WireDevice: + chTypes = []acme.ChallengeType{acme.WIREDPOP01} default: chTypes = []acme.ChallengeType{} } diff --git a/acme/order.go b/acme/order.go index 5c0b3a4a..60093bea 100644 --- a/acme/order.go +++ b/acme/order.go @@ -31,8 +31,10 @@ const ( // PermanentIdentifier is the ACME permanent-identifier identifier type // defined in https://datatracker.ietf.org/doc/html/draft-bweeks-acme-device-attest-00 PermanentIdentifier IdentifierType = "permanent-identifier" - // WireID is the Wire user identifier type - WireID IdentifierType = "wireapp-id" + // WireUser is the Wire user identifier type + WireUser IdentifierType = "wireapp-user" + // WireDevice is the Wire device identifier type + WireDevice IdentifierType = "wireapp-device" ) // Identifier encodes the type that an order pertains to. @@ -322,22 +324,22 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques } // containsWireIdentifiers checks if [Order] contains ACME -// identifiers for the WireID type. +// identifiers for the WireUser type. func (o *Order) containsWireIdentifiers() bool { for _, i := range o.Identifiers { - if i.Type == WireID { + if i.Type == WireUser || i.Type == WireDevice { return true } } return false } -// createWireSubject creates the subject for an [Order] with WireID identifiers. +// createWireSubject creates the subject for an [Order] with WireUser identifiers. func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util.Subject, err error) { - wireIDs, otherIDs := 0, 0 + wireUserIDs, wireDeviceIDs, otherIDs := 0, 0, 0 for _, identifier := range o.Identifiers { switch identifier.Type { - case WireID: + case WireUser: wireID, err := wire.ParseID([]byte(identifier.Value)) if err != nil { return subject, NewErrorISE("unmarshal wireID: %s", err) @@ -357,7 +359,7 @@ func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util } } if !foundDisplayName { - return subject, NewErrorISE("CSR must contain the display name in 2.16.840.1.113730.3.1.241 OID") + return subject, NewErrorISE("CSR must contain the display name in '2.16.840.1.113730.3.1.241' OID") } if len(csr.Subject.Organization) == 0 || !strings.EqualFold(csr.Subject.Organization[0], wireID.Domain) { @@ -365,14 +367,16 @@ func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util } subject.CommonName = wireID.Name subject.Organization = []string{wireID.Domain} - wireIDs++ + wireUserIDs++ + case WireDevice: + wireDeviceIDs++ default: otherIDs++ } } - if wireIDs > 0 && otherIDs > 0 || wireIDs > 1 { - return subject, NewErrorISE("at most one WireID can be signed along with no other ID, found %d WireIDs and %d other IDs", wireIDs, otherIDs) + if otherIDs > 0 || wireUserIDs != 1 && wireDeviceIDs != 1 { + return subject, NewErrorISE("there must only be 1 WireUser & 1 WireDevice identifiers") } return @@ -385,10 +389,10 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ } // order the DNS names and IP addresses, so that they can be compared against the canonicalized CSR - orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers)+2*numberOfIdentifierType(WireID, o.Identifiers)) + orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers)) orderIPs := make([]net.IP, numberOfIdentifierType(IP, o.Identifiers)) orderPIDs := make([]string, numberOfIdentifierType(PermanentIdentifier, o.Identifiers)) - tmpOrderURIs := make([]*url.URL, 2*numberOfIdentifierType(WireID, o.Identifiers)) + tmpOrderURIs := make([]*url.URL, numberOfIdentifierType(WireUser, o.Identifiers)+numberOfIdentifierType(WireDevice, o.Identifiers)) indexDNS, indexIP, indexPID, indexURI := 0, 0, 0, 0 for _, n := range o.Identifiers { switch n.Type { @@ -401,7 +405,18 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ case PermanentIdentifier: orderPIDs[indexPID] = n.Value indexPID++ - case WireID: + case WireUser: + wireID, err := wire.ParseID([]byte(n.Value)) + if err != nil { + return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value) + } + handle, err := url.Parse(wireID.Handle) + if err != nil { + return sans, NewErrorISE("handle must be a URI: %s", wireID.Handle) + } + tmpOrderURIs[indexURI] = handle + indexURI++ + case WireDevice: wireID, err := wire.ParseID([]byte(n.Value)) if err != nil { return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value) @@ -412,12 +427,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ } tmpOrderURIs[indexURI] = clientID indexURI++ - handle, err := url.Parse(wireID.Handle) - if err != nil { - return sans, NewErrorISE("handle must be a URI: %s", wireID.Handle) - } - tmpOrderURIs[indexURI] = handle - indexURI++ default: return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type) } From 18d3b7f61eff106bc7958c043934a0f716153893 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:44:51 +0000 Subject: [PATCH 2/8] Bump go.step.sm/crypto from 0.41.0 to 0.42.0 Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.41.0 to 0.42.0. - [Release notes](https://github.com/smallstep/crypto/releases) - [Commits](https://github.com/smallstep/crypto/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: go.step.sm/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++++++++++--- go.sum | 41 +++++++++++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 1af92f36..b6f33412 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/urfave/cli v1.22.14 go.step.sm/cli-utils v0.8.0 - go.step.sm/crypto v0.41.0 + go.step.sm/crypto v0.42.0 go.step.sm/linkedca v0.20.1 golang.org/x/crypto v0.18.0 golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 @@ -59,7 +59,20 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect - github.com/aws/aws-sdk-go v1.49.17 // indirect + github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.27.9 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -107,7 +120,6 @@ 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.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.16.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect diff --git a/go.sum b/go.sum index a750777f..9f7e41f4 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,34 @@ github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.49.17 h1:Cc+7LgPjKeJkF2SdNo1IkpQ5Dfl9HCZEVw9OP3CPuEI= -github.com/aws/aws-sdk-go v1.49.17/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= +github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.9 h1:W9PbZAZAEcelhhjb7KuwUtf+Lbc+i7ByYJRuWLlnxyQ= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.9/go.mod h1:2tFmR7fQnOdQlM2ZCEPpFnBIQD1U8wmXmduBgZbOag0= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= 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= @@ -287,10 +313,6 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= @@ -450,13 +472,13 @@ go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.step.sm/cli-utils v0.8.0 h1:b/Tc1/m3YuQq+u3ghTFP7Dz5zUekZj6GUmd5pCvkEXQ= go.step.sm/cli-utils v0.8.0/go.mod h1:S77aISrC0pKuflqiDfxxJlUbiXcAanyJ4POOnzFSxD4= -go.step.sm/crypto v0.41.0 h1:cv1zSLsAUKurAmoG559fmtMTCaK0CrbLgSI1pmI/ITc= -go.step.sm/crypto v0.41.0/go.mod h1:BBkqzupJdsSZ8LrPNyfqN81DMtahOenTk66tVgPmDvI= +go.step.sm/crypto v0.42.0 h1:1yPpg+v2c+fqKTLb5mTl45xdJ4gh1MXF0/X3dar71aU= +go.step.sm/crypto v0.42.0/go.mod h1:PHgVNnxqQnhOKT6yx/0faP82VCeC3g/nJRlBMIQ8G64= go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -636,7 +658,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 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.2.8/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= From bcaf8a5624f1aff282db7d7ea789441795752582 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:45:36 +0000 Subject: [PATCH 3/8] Bump google.golang.org/api from 0.156.0 to 0.157.0 Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.156.0 to 0.157.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.156.0...v0.157.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 | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1af92f36..f0dee021 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( golang.org/x/crypto v0.18.0 golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 golang.org/x/net v0.20.0 - google.golang.org/api v0.156.0 + google.golang.org/api v0.157.0 google.golang.org/grpc v1.60.1 google.golang.org/protobuf v1.32.0 ) @@ -143,8 +143,8 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a750777f..bc8aafa0 100644 --- a/go.sum +++ b/go.sum @@ -594,8 +594,8 @@ 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.156.0 h1:yloYcGbBtVYjLKQe4enCunxvwn3s2w/XPrrhVf6MsvQ= -google.golang.org/api v0.156.0/go.mod h1:bUSmn4KFO0Q+69zo9CNIDp4Psi6BqM0np0CbzKRSiSY= +google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20= +google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= 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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -603,12 +603,12 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ 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-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= -google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o= -google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= 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= From b8eb559ee9a5fc7ebcd3f5ce9a38e979844d6dc0 Mon Sep 17 00:00:00 2001 From: beltram <7527120+beltram@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:39:39 +0100 Subject: [PATCH 4/8] Update acme/order.go Co-authored-by: Herman Slatman --- acme/order.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/order.go b/acme/order.go index 60093bea..291cb57d 100644 --- a/acme/order.go +++ b/acme/order.go @@ -324,7 +324,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques } // containsWireIdentifiers checks if [Order] contains ACME -// identifiers for the WireUser type. +// identifiers for the WireUser or WireDevice types. func (o *Order) containsWireIdentifiers() bool { for _, i := range o.Identifiers { if i.Type == WireUser || i.Type == WireDevice { From 9eed61a9c50fb9c95c22cd30caf3e4b9be88e327 Mon Sep 17 00:00:00 2001 From: beltram Date: Tue, 23 Jan 2024 11:42:09 +0100 Subject: [PATCH 5/8] use switch statement --- acme/api/order.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index 6f4a51e3..b7a1114d 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -278,7 +278,8 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error { } var target string - if az.Identifier.Type == acme.WireUser { + switch az.Identifier.Type { + case acme.WireUser: wireOptions, err := prov.GetOptions().GetWireOptions() if err != nil { return acme.WrapErrorISE(err, "failed getting Wire options") @@ -295,7 +296,7 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error { if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'") } - } else if az.Identifier.Type == acme.WireDevice { + case acme.WireDevice: wireID, err := wire.ParseID([]byte(az.Identifier.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireUser") From 93ba1654ea7660e4ad46cff49816b6a96abe9178 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 24 Jan 2024 13:45:20 +0100 Subject: [PATCH 6/8] Fix tests to work with Wire `UserID` and `DeviceID` --- acme/api/order.go | 6 +- acme/api/order_test.go | 157 +++++++++++++++++++++++++----- acme/api/wire_integration_test.go | 6 +- acme/challenge.go | 8 +- acme/challenge_wire_test.go | 8 +- acme/order.go | 6 +- acme/wire/id.go | 17 +++- acme/wire/id_test.go | 34 ++++++- authority/provisioner/acme.go | 13 ++- 9 files changed, 200 insertions(+), 55 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index b7a1114d..beda4e5c 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -50,12 +50,12 @@ func (n *NewOrderRequest) Validate() error { return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty") } case acme.WireUser: - _, err := wire.ParseID([]byte(id.Value)) + _, err := wire.ParseUserID([]byte(id.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID") } case acme.WireDevice: - wireID, err := wire.ParseID([]byte(id.Value)) + wireID, err := wire.ParseDeviceID([]byte(id.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID") } @@ -297,7 +297,7 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error { return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'") } case acme.WireDevice: - wireID, err := wire.ParseID([]byte(az.Identifier.Value)) + wireID, err := wire.ParseDeviceID([]byte(az.Identifier.Value)) if err != nil { return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireUser") } diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 4948ea94..da95bc23 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -101,7 +101,7 @@ func TestNewOrderRequest_Validate(t *testing.T) { return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: "{}"}, + {Type: "wireapp-device", Value: "{}"}, }, }, err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "": invalid Wire client ID URI "": error parsing : scheme is missing`), @@ -111,7 +111,7 @@ func TestNewOrderRequest_Validate(t *testing.T) { return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + {Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, }, err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "nowireapp://example.com": invalid Wire client ID scheme "nowireapp"; expected "wireapp"`), @@ -121,7 +121,7 @@ func TestNewOrderRequest_Validate(t *testing.T) { return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://user-device@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + {Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://user-device@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, }, err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "wireapp://user-device@example.com": invalid Wire client ID username "user-device"`), @@ -205,13 +205,28 @@ func TestNewOrderRequest_Validate(t *testing.T) { naf: naf, } }, - "ok/wireapp-idd": func(t *testing.T) test { + "ok/wireapp-user": func(t *testing.T) test { nbf := time.Now().UTC().Add(time.Minute) naf := time.Now().UTC().Add(5 * time.Minute) return test{ nor: &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + {Type: "wireapp-user", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, + }, + NotAfter: naf, + NotBefore: nbf, + }, + nbf: nbf, + naf: naf, + } + }, + "ok/wireapp-device": func(t *testing.T) test { + nbf := time.Now().UTC().Add(time.Minute) + naf := time.Now().UTC().Add(5 * time.Minute) + return test{ + nor: &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, NotAfter: naf, NotBefore: nbf, @@ -1719,7 +1734,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, - "ok/default-naf-nbf-wireapp": func(t *testing.T) test { + "ok/default-naf-nbf-wireapp-user": func(t *testing.T) test { acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{ Wire: &wire.Options{ OIDC: &wire.OIDCOptions{ @@ -1749,7 +1764,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= acc := &acme.Account{ID: "accID"} nor := &NewOrderRequest{ Identifiers: []acme.Identifier{ - {Type: "wireapp-id", Value: `{"client-id": "wireapp://user!client@domain"}`}, + {Type: "wireapp-user", Value: `{"name": "Alice Smith", "handle": "wireapp://%40alice.smith.qa@example.com"}`}, }, } b, err := json.Marshal(nor) @@ -1758,9 +1773,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= ctx = context.WithValue(ctx, accContextKey, acc) ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) var ( - ch1, ch2 **acme.Challenge - az1ID *string - count = 0 + ch1 **acme.Challenge + az1ID *string ) return test{ ctx: ctx, @@ -1769,20 +1783,113 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= ca: &mockCA{}, db: &acme.MockDB{ MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { - switch count { - case 0: - ch.ID = "wireapp-oidc" - assert.Equals(t, ch.Type, acme.WIREOIDC01) - ch1 = &ch - case 1: - ch.ID = "wireapp-dpop" - assert.Equals(t, ch.Type, acme.WIREDPOP01) - ch2 = &ch - default: - assert.FatalError(t, errors.New("test logic error")) - return errors.New("force") - } - count++ + ch.ID = "wireapp-oidc" + assert.Equals(t, ch.Type, acme.WIREOIDC01) + ch1 = &ch + assert.Equals(t, ch.AccountID, "accID") + assert.NotEquals(t, ch.Token, "") + assert.Equals(t, ch.Status, acme.StatusPending) + assert.Equals(t, ch.Value, `{"name": "Alice Smith", "handle": "wireapp://%40alice.smith.qa@example.com"}`) + return nil + }, + MockCreateAuthorization: func(ctx context.Context, az *acme.Authorization) error { + az.ID = "az1ID" + az1ID = &az.ID + assert.Equals(t, az.AccountID, "accID") + assert.NotEquals(t, az.Token, "") + assert.Equals(t, az.Status, acme.StatusPending) + assert.Equals(t, az.Identifier, nor.Identifiers[0]) + assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1}) + assert.Equals(t, az.Wildcard, false) + return nil + }, + MockCreateOrder: func(ctx context.Context, o *acme.Order) error { + o.ID = "ordID" + assert.Equals(t, o.AccountID, "accID") + assert.Equals(t, o.ProvisionerID, prov.GetID()) + assert.Equals(t, o.Status, acme.StatusPending) + assert.Equals(t, o.Identifiers, nor.Identifiers) + assert.Equals(t, o.AuthorizationIDs, []string{*az1ID}) + return nil + }, + MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) { + assert.Equals(t, prov.GetID(), provisionerID) + assert.Equals(t, "accID", accountID) + return nil, nil + }, + }, + vr: func(t *testing.T, o *acme.Order) { + now := clock.Now() + testBufferDur := 5 * time.Second + orderExpiry := now.Add(defaultOrderExpiry) + expNbf := now.Add(-defaultOrderBackdate) + expNaf := now.Add(prov.DefaultTLSCertDuration()) + + assert.Equals(t, o.ID, "ordID") + assert.Equals(t, o.Status, acme.StatusPending) + assert.Equals(t, o.Identifiers, nor.Identifiers) + assert.Equals(t, o.AuthorizationURLs, []string{fmt.Sprintf("%s/acme/%s/authz/az1ID", baseURL.String(), escProvName)}) + assert.True(t, o.NotBefore.Add(-testBufferDur).Before(expNbf)) + assert.True(t, o.NotBefore.Add(testBufferDur).After(expNbf)) + assert.True(t, o.NotAfter.Add(-testBufferDur).Before(expNaf)) + assert.True(t, o.NotAfter.Add(testBufferDur).After(expNaf)) + assert.True(t, o.ExpiresAt.Add(-testBufferDur).Before(orderExpiry)) + assert.True(t, o.ExpiresAt.Add(testBufferDur).After(orderExpiry)) + }, + } + }, + "ok/default-naf-nbf-wireapp-device": func(t *testing.T) test { + acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{ + Wire: &wire.Options{ + OIDC: &wire.OIDCOptions{ + Provider: &wire.Provider{ + IssuerURL: "https://issuer.example.com", + AuthURL: "", + TokenURL: "", + JWKSURL: "", + UserInfoURL: "", + Algorithms: []string{"ES256"}, + }, + Config: &wire.Config{ + ClientID: "integration test", + SignatureAlgorithms: []string{"ES256"}, + SkipClientIDCheck: true, + SkipExpiryCheck: true, + SkipIssuerCheck: true, + InsecureSkipSignatureCheck: true, + Now: time.Now, + }, + }, + DPOP: &wire.DPOPOptions{ + SigningKey: []byte(fakeWireSigningKey), + }, + }, + }) + acc := &acme.Account{ID: "accID"} + nor := &NewOrderRequest{ + Identifiers: []acme.Identifier{ + {Type: "wireapp-device", Value: `{"client-id": "wireapp://user!client@domain"}`}, + }, + } + b, err := json.Marshal(nor) + assert.FatalError(t, err) + ctx := acme.NewProvisionerContext(context.Background(), acmeWireProv) + ctx = context.WithValue(ctx, accContextKey, acc) + ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) + var ( + ch1 **acme.Challenge + az1ID *string + ) + return test{ + ctx: ctx, + statusCode: 201, + nor: nor, + ca: &mockCA{}, + db: &acme.MockDB{ + MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { + ch.ID = "wireapp-dpop" + assert.Equals(t, ch.Type, acme.WIREDPOP01) + ch1 = &ch assert.Equals(t, ch.AccountID, "accID") assert.NotEquals(t, ch.Token, "") assert.Equals(t, ch.Status, acme.StatusPending) @@ -1796,7 +1903,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.NotEquals(t, az.Token, "") assert.Equals(t, az.Status, acme.StatusPending) assert.Equals(t, az.Identifier, nor.Identifiers[0]) - assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1, *ch2}) + assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1}) assert.Equals(t, az.Wildcard, false) return nil }, diff --git a/acme/api/wire_integration_test.go b/acme/api/wire_integration_test.go index 3bb62a9a..c59af069 100644 --- a/acme/api/wire_integration_test.go +++ b/acme/api/wire_integration_test.go @@ -234,7 +234,11 @@ func TestWireIntegration(t *testing.T) { nor := &NewOrderRequest{ Identifiers: []acme.Identifier{ { - Type: "wireapp-id", + Type: "wireapp-user", + Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`, + }, + { + Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`, }, }, diff --git a/acme/challenge.go b/acme/challenge.go index 3a53ed3a..0a80f3ed 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -373,7 +373,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO return WrapError(ErrorMalformedType, err, "error unmarshalling Wire OIDC challenge payload") } - wireID, err := wire.ParseID([]byte(ch.Value)) + wireID, err := wire.ParseUserID([]byte(ch.Value)) if err != nil { return WrapErrorISE(err, "error unmarshalling challenge data") } @@ -451,7 +451,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO return nil } -func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.ID) (map[string]any, error) { +func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.UserID) (map[string]any, error) { var m map[string]any if err := token.Claims(&m); err != nil { return nil, fmt.Errorf("failed extracting OIDC ID token claims: %w", err) @@ -500,7 +500,7 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j return WrapError(ErrorMalformedType, err, "error unmarshalling Wire DPoP challenge payload") } - wireID, err := wire.ParseID([]byte(ch.Value)) + wireID, err := wire.ParseDeviceID([]byte(ch.Value)) if err != nil { return WrapErrorISE(err, "error unmarshalling challenge data") } @@ -598,7 +598,7 @@ type wireVerifyParams struct { dpopKeyID string issuer string audience string - wireID wire.ID + wireID wire.DeviceID chToken string t time.Time } diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index 5a471a0f..b7881fa9 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -100,7 +100,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Type: "urn:ietf:params:acme:error:serverInternal", Detail: "The server experienced an internal error", Status: 500, - Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`), + Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.DeviceID`), }, } }, @@ -1096,7 +1096,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Type: "urn:ietf:params:acme:error:serverInternal", Detail: "The server experienced an internal error", Status: 500, - Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`), + Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.UserID`), }, } }, @@ -2030,7 +2030,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= require.True(t, ok) issuer := "http://wire.com:19983/clients/7a41cf5b79683410/access-token" - wireID := wire.ID{ + wireID := wire.DeviceID{ ClientID: "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com", Handle: "wireapp://%40alice_wire@wire.com", } @@ -2127,7 +2127,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= idToken, err := verifier.Verify(ctx, idTokenString) require.NoError(t, err) - wireID := wire.ID{ + wireID := wire.UserID{ Name: "Alice Smith", Handle: "wireapp://%40alice_wire@wire.com", } diff --git a/acme/order.go b/acme/order.go index 291cb57d..1e53eafe 100644 --- a/acme/order.go +++ b/acme/order.go @@ -340,7 +340,7 @@ func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util for _, identifier := range o.Identifiers { switch identifier.Type { case WireUser: - wireID, err := wire.ParseID([]byte(identifier.Value)) + wireID, err := wire.ParseUserID([]byte(identifier.Value)) if err != nil { return subject, NewErrorISE("unmarshal wireID: %s", err) } @@ -406,7 +406,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ orderPIDs[indexPID] = n.Value indexPID++ case WireUser: - wireID, err := wire.ParseID([]byte(n.Value)) + wireID, err := wire.ParseUserID([]byte(n.Value)) if err != nil { return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value) } @@ -417,7 +417,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ tmpOrderURIs[indexURI] = handle indexURI++ case WireDevice: - wireID, err := wire.ParseID([]byte(n.Value)) + wireID, err := wire.ParseDeviceID([]byte(n.Value)) if err != nil { return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value) } diff --git a/acme/wire/id.go b/acme/wire/id.go index 5e5bd03b..3662cc70 100644 --- a/acme/wire/id.go +++ b/acme/wire/id.go @@ -8,15 +8,26 @@ import ( "go.step.sm/crypto/kms/uri" ) -type ID struct { +type UserID struct { + Name string `json:"name,omitempty"` + Domain string `json:"domain,omitempty"` + Handle string `json:"handle,omitempty"` +} + +type DeviceID struct { Name string `json:"name,omitempty"` Domain string `json:"domain,omitempty"` ClientID string `json:"client-id,omitempty"` Handle string `json:"handle,omitempty"` } -func ParseID(data []byte) (wireID ID, err error) { - err = json.Unmarshal(data, &wireID) +func ParseUserID(data []byte) (id UserID, err error) { + err = json.Unmarshal(data, &id) + return +} + +func ParseDeviceID(data []byte) (id DeviceID, err error) { + err = json.Unmarshal(data, &id) return } diff --git a/acme/wire/id_test.go b/acme/wire/id_test.go index 36c81d23..4c008e46 100644 --- a/acme/wire/id_test.go +++ b/acme/wire/id_test.go @@ -7,19 +7,43 @@ import ( "github.com/stretchr/testify/assert" ) -func TestParseID(t *testing.T) { - ok := `{"name": "Alice Smith", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}` +func TestParseUserID(t *testing.T) { + ok := `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}` tests := []struct { name string data []byte - wantWireID ID + wantWireID UserID expectedErr error }{ - {name: "ok", data: []byte(ok), wantWireID: ID{Name: "Alice Smith", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}}, + {name: "ok", data: []byte(ok), wantWireID: UserID{Name: "Alice Smith", Domain: "wire.com", Handle: "wireapp://%40alice_wire@wire.com"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotWireID, err := ParseID(tt.data) + gotWireID, err := ParseUserID(tt.data) + if tt.expectedErr != nil { + assert.EqualError(t, err, tt.expectedErr.Error()) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wantWireID, gotWireID) + }) + } +} + +func TestParseDeviceID(t *testing.T) { + ok := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}` + tests := []struct { + name string + data []byte + wantWireID DeviceID + expectedErr error + }{ + {name: "ok", data: []byte(ok), wantWireID: DeviceID{Name: "device", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotWireID, err := ParseDeviceID(tt.data) if tt.expectedErr != nil { assert.EqualError(t, err, tt.expectedErr.Error()) return diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index 52b24530..19880338 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -11,8 +11,6 @@ import ( "github.com/pkg/errors" "go.step.sm/linkedca" - - "github.com/smallstep/certificates/acme/wire" ) // ACMEChallenge represents the supported acme challenges. @@ -252,11 +250,12 @@ func (p *ACME) AuthorizeOrderIdentifier(_ context.Context, identifier ACMEIdenti case DNS: err = x509Policy.IsDNSAllowed(identifier.Value) case WireID: - var wireID wire.ID - if wireID, err = wire.ParseID([]byte(identifier.Value)); err != nil { - return fmt.Errorf("failed parsing Wire SANs: %w", err) - } - err = x509Policy.AreSANsAllowed([]string{wireID.ClientID, wireID.Handle}) + // TODO: parse the value as user or device ID + // var wireID wire.ID + // if wireID, err = wire.ParseID([]byte(identifier.Value)); err != nil { + // return fmt.Errorf("failed parsing Wire SANs: %w", err) + // } + // err = x509Policy.AreSANsAllowed([]string{wireID.ClientID, wireID.Handle}) default: err = fmt.Errorf("invalid ACME identifier type '%s' provided", identifier.Type) } From a38132aa58c7d4c41fee955fa73d23a3e51a5f84 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 24 Jan 2024 20:28:45 +0100 Subject: [PATCH 7/8] Fix policy check for Wire user and device identifiers --- acme/order.go | 2 +- authority/provisioner/acme.go | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/acme/order.go b/acme/order.go index 1e53eafe..974bac5f 100644 --- a/acme/order.go +++ b/acme/order.go @@ -376,7 +376,7 @@ func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util } if otherIDs > 0 || wireUserIDs != 1 && wireDeviceIDs != 1 { - return subject, NewErrorISE("there must only be 1 WireUser & 1 WireDevice identifiers") + return subject, NewErrorISE("order must have exactly one WireUser and WireDevice identifier") } return diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index 19880338..f338a78a 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -10,6 +10,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/certificates/acme/wire" "go.step.sm/linkedca" ) @@ -222,8 +223,10 @@ const ( IP ACMEIdentifierType = "ip" // DNS is the ACME dns identifier type DNS ACMEIdentifierType = "dns" - // WireID is the Wire user identifier type - WireID ACMEIdentifierType = "wireapp-id" + // WireUser is the Wire user identifier type + WireUser ACMEIdentifierType = "wireapp-user" + // WireDevice is the Wire device identifier type + WireDevice ACMEIdentifierType = "wireapp-device" ) // ACMEIdentifier encodes ACME Order Identifiers @@ -249,13 +252,18 @@ func (p *ACME) AuthorizeOrderIdentifier(_ context.Context, identifier ACMEIdenti err = x509Policy.IsIPAllowed(net.ParseIP(identifier.Value)) case DNS: err = x509Policy.IsDNSAllowed(identifier.Value) - case WireID: - // TODO: parse the value as user or device ID - // var wireID wire.ID - // if wireID, err = wire.ParseID([]byte(identifier.Value)); err != nil { - // return fmt.Errorf("failed parsing Wire SANs: %w", err) - // } - // err = x509Policy.AreSANsAllowed([]string{wireID.ClientID, wireID.Handle}) + case WireUser: + var wireID wire.UserID + if wireID, err = wire.ParseUserID([]byte(identifier.Value)); err != nil { + return fmt.Errorf("failed parsing Wire SANs: %w", err) + } + err = x509Policy.AreSANsAllowed([]string{wireID.Handle}) + case WireDevice: + var wireID wire.DeviceID + if wireID, err = wire.ParseDeviceID([]byte(identifier.Value)); err != nil { + return fmt.Errorf("failed parsing Wire SANs: %w", err) + } + err = x509Policy.AreSANsAllowed([]string{wireID.ClientID}) default: err = fmt.Errorf("invalid ACME identifier type '%s' provided", identifier.Type) } From dd1ff9c15b0f8ca5920dac6a0ff080b7f0cfb8be Mon Sep 17 00:00:00 2001 From: Panagiotis Siatras Date: Fri, 26 Jan 2024 09:47:27 +0200 Subject: [PATCH 8/8] Implementation of the Prometheus endpoint (#1669) Implementation of the http://{metricsAddress}/metrics Prometheus endpoint. --- authority/authority.go | 13 +++ authority/authorize.go | 12 +-- authority/authorize_test.go | 2 +- authority/config/config.go | 7 ++ authority/meter.go | 87 ++++++++++++++++ authority/options.go | 13 +++ authority/ssh.go | 150 +++++++++++++++------------ authority/tls.go | 173 ++++++++++++++++++------------- ca/ca.go | 31 ++++++ commands/app.go | 3 +- go.mod | 6 ++ go.sum | 15 ++- internal/metrix/meter.go | 196 ++++++++++++++++++++++++++++++++++++ 13 files changed, 566 insertions(+), 142 deletions(-) create mode 100644 authority/meter.go create mode 100644 internal/metrix/meter.go diff --git a/authority/authority.go b/authority/authority.go index 95e00a45..c112bc25 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -104,6 +104,9 @@ type Authority struct { // If true, do not output initialization logs quietInit bool + + // Called whenever applicable, in order to instrument the authority. + meter Meter } // Info contains information about the authority. @@ -126,6 +129,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) { config: cfg, certificates: new(sync.Map), validateSCEP: true, + meter: noopMeter{}, } // Apply options. @@ -134,6 +138,9 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) { return nil, err } } + if a.keyManager != nil { + a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} + } if !a.skipInit { // Initialize authority from options or configuration. @@ -151,6 +158,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) { a := &Authority{ config: &config.Config{}, certificates: new(sync.Map), + meter: noopMeter{}, } // Apply options. @@ -159,6 +167,9 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return nil, err } } + if a.keyManager != nil { + a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} + } // Validate required options switch { @@ -337,6 +348,8 @@ func (a *Authority) init() error { if err != nil { return err } + + a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} } // Initialize linkedca client if necessary. On a linked RA, the issuer diff --git a/authority/authorize.go b/authority/authorize.go index f14574a8..02147687 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -286,16 +286,16 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error { // extra extension cannot be found, authorize the renewal by default. // // TODO(mariano): should we authorize by default? -func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) error { +func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) (provisioner.Interface, error) { serial := cert.SerialNumber.String() var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)} isRevoked, err := a.IsRevoked(serial) if err != nil { - return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) } if isRevoked { - return errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...) + return nil, errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...) } p, err := a.LoadProvisionerByCertificate(cert) if err != nil { @@ -305,13 +305,13 @@ func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) // returns the noop provisioner if this happens, and it allows // certificate renewals. if p, ok = a.provisioners.LoadByCertificate(cert); !ok { - return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...) + return nil, errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...) } } if err := p.AuthorizeRenew(ctx, cert); err != nil { - return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) } - return nil + return p, nil } // authorizeSSHCertificate returns an error if the given certificate is revoked. diff --git a/authority/authorize_test.go b/authority/authorize_test.go index bec34fd6..3d748f69 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -876,7 +876,7 @@ func TestAuthority_authorizeRenew(t *testing.T) { t.Run(name, func(t *testing.T) { tc := genTestCase(t) - err := tc.auth.authorizeRenew(context.Background(), tc.cert) + _, err := tc.auth.authorizeRenew(context.Background(), tc.cert) if err != nil { if assert.NotNil(t, tc.err) { var sc render.StatusCodedError diff --git a/authority/config/config.go b/authority/config/config.go index ba581d8a..ea7ce35d 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -83,6 +83,7 @@ type Config struct { Templates *templates.Templates `json:"templates,omitempty"` CommonName string `json:"commonName,omitempty"` CRL *CRLConfig `json:"crl,omitempty"` + MetricsAddress string `json:"metricsAddress,omitempty"` SkipValidation bool `json:"-"` // Keeps record of the filename the Config is read from @@ -327,6 +328,12 @@ func (c *Config) Validate() error { return errors.Errorf("invalid address %s", c.Address) } + if addr := c.MetricsAddress; addr != "" { + if _, _, err := net.SplitHostPort(addr); err != nil { + return errors.Errorf("invalid metrics address %q", c.Address) + } + } + if c.TLS == nil { c.TLS = &DefaultTLSOptions } else { diff --git a/authority/meter.go b/authority/meter.go new file mode 100644 index 00000000..cccda22a --- /dev/null +++ b/authority/meter.go @@ -0,0 +1,87 @@ +package authority + +import ( + "crypto" + "io" + + "go.step.sm/crypto/kms" + kmsapi "go.step.sm/crypto/kms/apiv1" + + "github.com/smallstep/certificates/authority/provisioner" +) + +// Meter wraps the set of defined callbacks for metrics gatherers. +type Meter interface { + // X509Signed is called whenever an X509 certificate is signed. + X509Signed(provisioner.Interface, error) + + // X509Renewed is called whenever an X509 certificate is renewed. + X509Renewed(provisioner.Interface, error) + + // X509Rekeyed is called whenever an X509 certificate is rekeyed. + X509Rekeyed(provisioner.Interface, error) + + // X509WebhookAuthorized is called whenever an X509 authoring webhook is called. + X509WebhookAuthorized(provisioner.Interface, error) + + // X509WebhookEnriched is called whenever an X509 enriching webhook is called. + X509WebhookEnriched(provisioner.Interface, error) + + // SSHSigned is called whenever an SSH certificate is signed. + SSHSigned(provisioner.Interface, error) + + // SSHRenewed is called whenever an SSH certificate is renewed. + SSHRenewed(provisioner.Interface, error) + + // SSHRekeyed is called whenever an SSH certificate is rekeyed. + SSHRekeyed(provisioner.Interface, error) + + // SSHWebhookAuthorized is called whenever an SSH authoring webhook is called. + SSHWebhookAuthorized(provisioner.Interface, error) + + // SSHWebhookEnriched is called whenever an SSH enriching webhook is called. + SSHWebhookEnriched(provisioner.Interface, error) + + // KMSSigned is called per KMS signer signature. + KMSSigned(error) +} + +// noopMeter implements a noop [Meter]. +type noopMeter struct{} + +func (noopMeter) SSHRekeyed(provisioner.Interface, error) {} +func (noopMeter) SSHRenewed(provisioner.Interface, error) {} +func (noopMeter) SSHSigned(provisioner.Interface, error) {} +func (noopMeter) SSHWebhookAuthorized(provisioner.Interface, error) {} +func (noopMeter) SSHWebhookEnriched(provisioner.Interface, error) {} +func (noopMeter) X509Rekeyed(provisioner.Interface, error) {} +func (noopMeter) X509Renewed(provisioner.Interface, error) {} +func (noopMeter) X509Signed(provisioner.Interface, error) {} +func (noopMeter) X509WebhookAuthorized(provisioner.Interface, error) {} +func (noopMeter) X509WebhookEnriched(provisioner.Interface, error) {} +func (noopMeter) KMSSigned(error) {} + +type instrumentedKeyManager struct { + kms.KeyManager + meter Meter +} + +func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) { + if s, err = i.KeyManager.CreateSigner(req); err == nil { + s = &instrumentedKMSSigner{s, i.meter} + } + + return +} + +type instrumentedKMSSigner struct { + crypto.Signer + meter Meter +} + +func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { + signature, err = i.Signer.Sign(rand, digest, opts) + i.meter.KMSSigned(err) + + return +} diff --git a/authority/options.go b/authority/options.go index 4fc5a20f..82c62bc4 100644 --- a/authority/options.go +++ b/authority/options.go @@ -381,3 +381,16 @@ func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) { } return certs, nil } + +// WithMeter is an option that sets the authority's [Meter] to the provided one. +func WithMeter(m Meter) Option { + if m == nil { + m = noopMeter{} + } + + return func(a *Authority) (_ error) { + a.meter = m + + return + } +} diff --git a/authority/ssh.go b/authority/ssh.go index f9371d60..756e376e 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -146,7 +146,13 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (* } // SignSSH creates a signed SSH certificate with the given public key and options. -func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { +func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { + cert, prov, err := a.signSSH(ctx, key, opts, signOpts...) + a.meter.SSHSigned(prov, err) + return cert, err +} + +func (a *Authority) signSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) { var ( certOptions []sshutil.Option mods []provisioner.SSHCertModifier @@ -155,7 +161,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // Validate given options. if err := opts.Validate(); err != nil { - return nil, err + return nil, nil, err } // Set backdate with the configured value @@ -184,7 +190,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // validate the given SSHOptions case provisioner.SSHCertOptionsValidator: if err := o.Valid(opts); err != nil { - return nil, errs.BadRequestErr(err, "error validating ssh certificate options") + return nil, prov, errs.BadRequestErr(err, "error validating ssh certificate options") } // call webhooks @@ -192,7 +198,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision webhookCtl = o default: - return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o) + return nil, prov, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o) } } @@ -205,8 +211,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision } // Call enriching webhooks - if err := callEnrichingWebhooksSSH(webhookCtl, cr); err != nil { - return nil, errs.ApplyOptions( + if err := a.callEnrichingWebhooksSSH(prov, webhookCtl, cr); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, err.Error()), errs.WithKeyVal("signOptions", signOpts), ) @@ -216,20 +222,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision certificate, err := sshutil.NewCertificate(cr, certOptions...) if err != nil { var te *sshutil.TemplateError - if errors.As(err, &te) { - return nil, errs.ApplyOptions( + switch { + case errors.As(err, &te): + return nil, prov, errs.ApplyOptions( errs.BadRequestErr(err, err.Error()), errs.WithKeyVal("signOptions", signOpts), ) - } - // explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors - if strings.HasPrefix(err.Error(), "error unmarshaling certificate") { - return nil, errs.InternalServerErr(templatingError(err), + case strings.HasPrefix(err.Error(), "error unmarshaling certificate"): + // explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors + return nil, prov, errs.InternalServerErr(templatingError(err), errs.WithKeyVal("signOptions", signOpts), errs.WithMessage("error applying certificate template"), ) + default: + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH") } - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH") } // Get actual *ssh.Certificate and continue with provisioner modifiers. @@ -238,13 +245,13 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // Use SignSSHOptions to modify the certificate validity. It will be later // checked or set if not defined. if err := opts.ModifyValidity(certTpl); err != nil { - return nil, errs.BadRequestErr(err, err.Error()) + return nil, prov, errs.BadRequestErr(err, err.Error()) } // Use provisioner modifiers. for _, m := range mods { if err := m.Modify(certTpl, opts); err != nil { - return nil, errs.ForbiddenErr(err, "error creating ssh certificate") + return nil, prov, errs.ForbiddenErr(err, "error creating ssh certificate") } } @@ -253,32 +260,32 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision switch certTpl.CertType { case ssh.UserCert: if a.sshCAUserCertSignKey == nil { - return nil, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled") + return nil, prov, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled") } signer = a.sshCAUserCertSignKey case ssh.HostCert: if a.sshCAHostCertSignKey == nil { - return nil, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled") + return nil, prov, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled") } signer = a.sshCAHostCertSignKey default: - return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType) + return nil, prov, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType) } // Check if authority is allowed to sign the certificate if err := a.isAllowedToSignSSHCertificate(certTpl); err != nil { var ee *errs.Error if errors.As(err, &ee) { - return nil, ee + return nil, prov, ee } - return nil, errs.InternalServerErr(err, + return nil, prov, errs.InternalServerErr(err, errs.WithMessage("authority.SignSSH: error creating ssh certificate"), ) } // Send certificate to webhooks for authorization - if err := callAuthorizingWebhooksSSH(webhookCtl, certificate, certTpl); err != nil { - return nil, errs.ApplyOptions( + if err := a.callAuthorizingWebhooksSSH(prov, webhookCtl, certificate, certTpl); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"), ) } @@ -286,21 +293,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // Sign certificate. cert, err := sshutil.CreateCertificate(certTpl, signer) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate") + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate") } // User provisioners validators. for _, v := range validators { if err := v.Valid(cert, opts); err != nil { - return nil, errs.ForbiddenErr(err, "error validating ssh certificate") + return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate") } } - if err = a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db") + if err := a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db") } - return cert, nil + return cert, prov, nil } // isAllowedToSignSSHCertificate checks if the Authority is allowed to sign the SSH certificate. @@ -310,12 +317,18 @@ func (a *Authority) isAllowedToSignSSHCertificate(cert *ssh.Certificate) error { // RenewSSH creates a signed SSH certificate using the old SSH certificate as a template. func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) { + cert, prov, err := a.renewSSH(ctx, oldCert) + a.meter.SSHRenewed(prov, err) + return cert, err +} + +func (a *Authority) renewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, provisioner.Interface, error) { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { - return nil, errs.BadRequest("cannot renew a certificate without validity period") + return nil, nil, errs.BadRequest("cannot renew a certificate without validity period") } if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil { - return nil, err + return nil, nil, err } // Attempt to extract the provisioner from the token. @@ -348,36 +361,41 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss switch certTpl.CertType { case ssh.UserCert: if a.sshCAUserCertSignKey == nil { - return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled") + return nil, prov, errs.NotImplemented("renewSSH: user certificate signing is not enabled") } signer = a.sshCAUserCertSignKey case ssh.HostCert: if a.sshCAHostCertSignKey == nil { - return nil, errs.NotImplemented("renewSSH: host certificate signing is not enabled") + return nil, prov, errs.NotImplemented("renewSSH: host certificate signing is not enabled") } signer = a.sshCAHostCertSignKey default: - return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType) + return nil, prov, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType) } // Sign certificate. cert, err := sshutil.CreateCertificate(certTpl, signer) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") } - if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") + if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") } - return cert, nil + return cert, prov, nil } // RekeySSH creates a signed SSH certificate using the old SSH certificate as a template. func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { - var validators []provisioner.SSHCertValidator + cert, prov, err := a.rekeySSH(ctx, oldCert, pub, signOpts...) + a.meter.SSHRekeyed(prov, err) + return cert, err +} +func (a *Authority) rekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) { var prov provisioner.Interface + var validators []provisioner.SSHCertValidator for _, op := range signOpts { switch o := op.(type) { // Capture current provisioner @@ -387,16 +405,16 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub case provisioner.SSHCertValidator: validators = append(validators, o) default: - return nil, errs.InternalServer("rekeySSH; invalid extra option type %T", o) + return nil, prov, errs.InternalServer("rekeySSH; invalid extra option type %T", o) } } if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { - return nil, errs.BadRequest("cannot rekey a certificate without validity period") + return nil, prov, errs.BadRequest("cannot rekey a certificate without validity period") } if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil { - return nil, err + return nil, prov, err } backdate := a.config.AuthorityConfig.Backdate.Duration @@ -423,37 +441,37 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub switch cert.CertType { case ssh.UserCert: if a.sshCAUserCertSignKey == nil { - return nil, errs.NotImplemented("rekeySSH; user certificate signing is not enabled") + return nil, prov, errs.NotImplemented("rekeySSH; user certificate signing is not enabled") } signer = a.sshCAUserCertSignKey case ssh.HostCert: if a.sshCAHostCertSignKey == nil { - return nil, errs.NotImplemented("rekeySSH; host certificate signing is not enabled") + return nil, prov, errs.NotImplemented("rekeySSH; host certificate signing is not enabled") } signer = a.sshCAHostCertSignKey default: - return nil, errs.BadRequest("unexpected certificate type '%d'", cert.CertType) + return nil, prov, errs.BadRequest("unexpected certificate type '%d'", cert.CertType) } var err error // Sign certificate. cert, err = sshutil.CreateCertificate(cert, signer) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") } // Apply validators from provisioner. for _, v := range validators { if err := v.Valid(cert, provisioner.SignSSHOptions{Backdate: backdate}); err != nil { - return nil, errs.ForbiddenErr(err, "error validating ssh certificate") + return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate") } } - if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db") + if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db") } - return cert, nil + return cert, prov, nil } func (a *Authority) storeSSHCertificate(prov provisioner.Interface, cert *ssh.Certificate) error { @@ -653,28 +671,36 @@ func (a *Authority) getAddUserCommand(principal string) string { return strings.ReplaceAll(cmd, "", principal) } -func callEnrichingWebhooksSSH(webhookCtl webhookController, cr sshutil.CertificateRequest) error { +func (a *Authority) callEnrichingWebhooksSSH(prov provisioner.Interface, webhookCtl webhookController, cr sshutil.CertificateRequest) (err error) { if webhookCtl == nil { - return nil + return } - whEnrichReq, err := webhook.NewRequestBody( + + var whEnrichReq *webhook.RequestBody + if whEnrichReq, err = webhook.NewRequestBody( webhook.WithSSHCertificateRequest(cr), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Enrich(whEnrichReq) + + a.meter.SSHWebhookEnriched(prov, err) } - return webhookCtl.Enrich(whEnrichReq) + + return } -func callAuthorizingWebhooksSSH(webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) error { +func (a *Authority) callAuthorizingWebhooksSSH(prov provisioner.Interface, webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) (err error) { if webhookCtl == nil { - return nil + return } - whAuthBody, err := webhook.NewRequestBody( + + var whAuthBody *webhook.RequestBody + if whAuthBody, err = webhook.NewRequestBody( webhook.WithSSHCertificate(cert, certTpl), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Authorize(whAuthBody) + + a.meter.SSHWebhookAuthorized(prov, err) } - return webhookCtl.Authorize(whAuthBody) + + return } diff --git a/authority/tls.go b/authority/tls.go index 7da8ec40..0dd6eb54 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -93,6 +93,12 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { // Sign creates a signed certificate from a certificate signing request. func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + chain, prov, err := a.signX509(csr, signOpts, extraOpts...) + a.meter.X509Signed(prov, err) + return chain, err +} + +func (a *Authority) signX509(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, provisioner.Interface, error) { var ( certOptions []x509util.Option certValidators []provisioner.CertificateValidator @@ -100,9 +106,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign certEnforcers []provisioner.CertificateEnforcer ) - opts := []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} + opts := []any{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} if err := csr.CheckSignature(); err != nil { - return nil, errs.ApplyOptions( + return nil, nil, errs.ApplyOptions( errs.BadRequestErr(err, "invalid certificate request"), opts..., ) @@ -111,10 +117,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Set backdate with the configured value signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration - var prov provisioner.Interface - var pInfo *casapi.ProvisionerInfo - var attData *provisioner.AttestationData - var webhookCtl webhookController + var ( + prov provisioner.Interface + pInfo *casapi.ProvisionerInfo + attData *provisioner.AttestationData + webhookCtl webhookController + ) for _, op := range extraOpts { switch k := op.(type) { // Capture current provisioner @@ -132,7 +140,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Validate the given certificate request. case provisioner.CertificateRequestValidator: if err := k.Valid(csr); err != nil { - return nil, errs.ApplyOptions( + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error validating certificate request"), opts..., ) @@ -159,45 +167,46 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign webhookCtl = k default: - return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) + return nil, prov, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]any{k}, opts...)...) } } - if err := callEnrichingWebhooksX509(webhookCtl, attData, csr); err != nil { - return nil, errs.ApplyOptions( + if err := a.callEnrichingWebhooksX509(prov, webhookCtl, attData, csr); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, err.Error()), errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), ) } - cert, err := x509util.NewCertificate(csr, certOptions...) + crt, err := x509util.NewCertificate(csr, certOptions...) if err != nil { var te *x509util.TemplateError - if errors.As(err, &te) { - return nil, errs.ApplyOptions( + switch { + case errors.As(err, &te): + return nil, prov, errs.ApplyOptions( errs.BadRequestErr(err, err.Error()), errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), ) - } - // explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors - if strings.HasPrefix(err.Error(), "error unmarshaling certificate") { - return nil, errs.InternalServerErr(templatingError(err), + case strings.HasPrefix(err.Error(), "error unmarshaling certificate"): + // explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors + return nil, prov, errs.InternalServerErr(templatingError(err), errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), errs.WithMessage("error applying certificate template"), ) + default: + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } // Certificate modifiers before validation - leaf := cert.GetCertificate() + leaf := crt.GetCertificate() // Set default subject if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil { - return nil, errs.ApplyOptions( + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -205,7 +214,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign for _, m := range certModifiers { if err := m.Modify(leaf, signOpts); err != nil { - return nil, errs.ApplyOptions( + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -215,7 +224,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Certificate validation. for _, v := range certValidators { if err := v.Valid(leaf, signOpts); err != nil { - return nil, errs.ApplyOptions( + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error validating certificate"), opts..., ) @@ -224,8 +233,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Certificate modifiers after validation for _, m := range certEnforcers { - if err := m.Enforce(leaf); err != nil { - return nil, errs.ApplyOptions( + if err = m.Enforce(leaf); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -234,8 +243,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Process injected modifiers after validation for _, m := range a.x509Enforcers { - if err := m.Enforce(leaf); err != nil { - return nil, errs.ApplyOptions( + if err = m.Enforce(leaf); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -243,12 +252,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign } // Check if authority is allowed to sign the certificate - if err := a.isAllowedToSignX509Certificate(leaf); err != nil { + if err = a.isAllowedToSignX509Certificate(leaf); err != nil { var ee *errs.Error if errors.As(err, &ee) { - return nil, errs.ApplyOptions(ee, opts...) + return nil, prov, errs.ApplyOptions(ee, opts...) } - return nil, errs.InternalServerErr(err, + return nil, prov, errs.InternalServerErr(err, errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), errs.WithMessage("error creating certificate"), @@ -256,8 +265,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign } // Send certificate to webhooks for authorization - if err := callAuthorizingWebhooksX509(webhookCtl, cert, leaf, attData); err != nil { - return nil, errs.ApplyOptions( + if err := a.callAuthorizingWebhooksX509(prov, webhookCtl, crt, leaf, attData); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -265,6 +274,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Sign certificate lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate)) + resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ Template: leaf, CSR: csr, @@ -273,23 +283,22 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign Provisioner: pInfo, }) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) } - fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) + chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) - // Wrap provisioner with extra information. - prov = wrapProvisioner(prov, attData) + // Wrap provisioner with extra information, if not nil + if prov != nil { + prov = wrapProvisioner(prov, attData) + } // Store certificate in the db. - if err = a.storeCertificate(prov, fullchain); err != nil { - if !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, - "authority.Sign; error storing certificate in db", opts...) - } + if err := a.storeCertificate(prov, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error storing certificate in db", opts...) } - return fullchain, nil + return chain, prov, nil } // isAllowedToSignX509Certificate checks if the Authority is allowed @@ -337,14 +346,25 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5 // of rekey), and 'NotBefore/NotAfter' (the validity duration of the new // certificate should be equal to the old one, but starting 'now'). func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { + chain, prov, err := a.renewContext(ctx, oldCert, pk) + if pk == nil { + a.meter.X509Renewed(prov, err) + } else { + a.meter.X509Rekeyed(prov, err) + } + return chain, err +} + +func (a *Authority) renewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, provisioner.Interface, error) { isRekey := (pk != nil) opts := []errs.Option{ errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), } // Check step provisioner extensions - if err := a.authorizeRenew(ctx, oldCert); err != nil { - return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) + prov, err := a.authorizeRenew(ctx, oldCert) + if err != nil { + return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } // Durations @@ -414,15 +434,17 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, // // TODO(hslatman,maraino): consider adding policies too and consider if // RenewSSH should check policies. - if err := a.constraintsEngine.ValidateCertificate(newCert); err != nil { + if err = a.constraintsEngine.ValidateCertificate(newCert); err != nil { var ee *errs.Error - if errors.As(err, &ee) { - return nil, errs.StatusCodeError(ee.StatusCode(), err, opts...) + switch { + case errors.As(err, &ee): + return nil, prov, errs.StatusCodeError(ee.StatusCode(), err, opts...) + default: + return nil, prov, errs.InternalServerErr(err, + errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), + errs.WithMessage("error renewing certificate"), + ) } - return nil, errs.InternalServerErr(err, - errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), - errs.WithMessage("error renewing certificate"), - ) } // The token can optionally be in the context. If the CA is running in RA @@ -436,17 +458,16 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, Token: token, }) if err != nil { - return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) + return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } - fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) - if err = a.storeRenewedCertificate(oldCert, fullchain); err != nil { - if !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) - } + chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) + + if err = a.storeRenewedCertificate(oldCert, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } - return fullchain, nil + return chain, prov, nil } // storeCertificate allows to use an extension of the db.AuthDB interface that @@ -952,42 +973,52 @@ func templatingError(err error) error { return errors.Wrap(cause, "error applying certificate template") } -func callEnrichingWebhooksX509(webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) error { +func (a *Authority) callEnrichingWebhooksX509(prov provisioner.Interface, webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) (err error) { if webhookCtl == nil { - return nil + return } + var attested *webhook.AttestationData if attData != nil { attested = &webhook.AttestationData{ PermanentIdentifier: attData.PermanentIdentifier, } } - whEnrichReq, err := webhook.NewRequestBody( + + var whEnrichReq *webhook.RequestBody + if whEnrichReq, err = webhook.NewRequestBody( webhook.WithX509CertificateRequest(csr), webhook.WithAttestationData(attested), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Enrich(whEnrichReq) + + a.meter.X509WebhookEnriched(prov, err) } - return webhookCtl.Enrich(whEnrichReq) + + return } -func callAuthorizingWebhooksX509(webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) error { +func (a *Authority) callAuthorizingWebhooksX509(prov provisioner.Interface, webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) (err error) { if webhookCtl == nil { - return nil + return } + var attested *webhook.AttestationData if attData != nil { attested = &webhook.AttestationData{ PermanentIdentifier: attData.PermanentIdentifier, } } - whAuthBody, err := webhook.NewRequestBody( + + var whAuthBody *webhook.RequestBody + if whAuthBody, err = webhook.NewRequestBody( webhook.WithX509Certificate(cert, leaf), webhook.WithAttestationData(attested), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Authorize(whAuthBody) + + a.meter.X509WebhookAuthorized(prov, err) } - return webhookCtl.Authorize(whAuthBody) + + return } diff --git a/ca/ca.go b/ca/ca.go index 7baf2419..0059a5d0 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -27,6 +27,7 @@ import ( adminAPI "github.com/smallstep/certificates/authority/admin/api" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/db" + "github.com/smallstep/certificates/internal/metrix" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/monitoring" "github.com/smallstep/certificates/scep" @@ -125,6 +126,7 @@ type CA struct { config *config.Config srv *server.Server insecureSrv *server.Server + metricsSrv *server.Server opts *options renewer *TLSRenewer compactStop chan struct{} @@ -163,6 +165,13 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { opts = append(opts, authority.WithQuietInit()) } + var meter *metrix.Meter + if ca.config.MetricsAddress != "" { + meter = metrix.New() + + opts = append(opts, authority.WithMeter(meter)) + } + webhookTransport := http.DefaultTransport.(*http.Transport).Clone() opts = append(opts, authority.WithWebhookClient(&http.Client{Transport: webhookTransport})) @@ -318,6 +327,13 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } } + if meter != nil { + ca.metricsSrv = server.New(ca.config.MetricsAddress, meter, nil) + ca.metricsSrv.BaseContext = func(net.Listener) context.Context { + return baseContext + } + } + return ca, nil } @@ -404,6 +420,14 @@ func (ca *CA) Run() error { }() } + if ca.metricsSrv != nil { + wg.Add(1) + go func() { + defer wg.Done() + errs <- ca.metricsSrv.ListenAndServe() + }() + } + wg.Add(1) go func() { defer wg.Done() @@ -480,6 +504,13 @@ func (ca *CA) Reload() error { } } + if ca.metricsSrv != nil { + if err = ca.metricsSrv.Reload(newCA.metricsSrv); err != nil { + logContinue("Reload failed because metrics server could not be replaced.") + return errors.Wrap(err, "error reloading metrics server") + } + } + if err = ca.srv.Reload(newCA.srv); err != nil { logContinue("Reload failed because server could not be replaced.") return errors.Wrap(err, "error reloading server") diff --git a/commands/app.go b/commands/app.go index e5c6ea1e..c96b50ae 100644 --- a/commands/app.go +++ b/commands/app.go @@ -251,7 +251,8 @@ To get a linked authority token: ca.WithSSHUserPassword(sshUserPassword), ca.WithIssuerPassword(issuerPassword), ca.WithLinkedCAToken(token), - ca.WithQuiet(quiet)) + ca.WithQuiet(quiet), + ) if err != nil { fatal(err) } diff --git a/go.mod b/go.mod index 31911b98..6d23bdac 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 github.com/newrelic/go-agent/v3 v3.29.0 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.15.1 github.com/rs/xid v1.5.0 github.com/sirupsen/logrus v1.9.3 github.com/slackhq/nebula v1.6.1 @@ -73,6 +74,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect github.com/aws/smithy-go v1.19.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -125,6 +127,7 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // 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 @@ -134,6 +137,9 @@ require ( github.com/peterbourgon/diskv/v3 v3.0.1 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect 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 diff --git a/go.sum b/go.sum index e74913d9..7ba1523d 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGz github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +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= @@ -350,6 +352,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -383,7 +387,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -540,6 +552,7 @@ golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -653,8 +666,8 @@ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7 google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/metrix/meter.go b/internal/metrix/meter.go new file mode 100644 index 00000000..a867b197 --- /dev/null +++ b/internal/metrix/meter.go @@ -0,0 +1,196 @@ +// Package metrix implements stats-related functionality. +package metrix + +import ( + "net/http" + "strconv" + "time" + + "github.com/smallstep/certificates/authority/provisioner" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// New initializes and returns a new [Meter]. +func New() (m *Meter) { + initializedAt := time.Now() + + m = &Meter{ + uptime: prometheus.NewGaugeFunc( + prometheus.GaugeOpts(opts( + "", + "uptime_seconds", + "Number of seconds since service start", + )), + func() float64 { + return float64(time.Since(initializedAt) / time.Second) + }, + ), + ssh: newProvisionerInstruments("ssh"), + x509: newProvisionerInstruments("x509"), + kms: &kms{ + signed: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "signed", "Number of KMS-backed signatures"))), + errors: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "errors", "Number of KMS-related errors"))), + }, + } + + reg := prometheus.NewRegistry() + + reg.MustRegister( + m.uptime, + m.ssh.rekeyed, + m.ssh.renewed, + m.ssh.signed, + m.x509.rekeyed, + m.x509.renewed, + m.x509.signed, + m.kms.signed, + m.kms.errors, + ) + + h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{ + Registry: reg, + Timeout: 5 * time.Second, + MaxRequestsInFlight: 10, + }) + + mux := http.NewServeMux() + mux.Handle("/metrics", h) + m.Handler = mux + + return +} + +// Meter wraps the functionality of a Prometheus-compatible HTTP handler. +type Meter struct { + http.Handler + + uptime prometheus.GaugeFunc + ssh *provisionerInstruments + x509 *provisionerInstruments + kms *kms +} + +// SSHRekeyed implements [authority.Meter] for [Meter]. +func (m *Meter) SSHRekeyed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.rekeyed, p, err) +} + +// SSHRenewed implements [authority.Meter] for [Meter]. +func (m *Meter) SSHRenewed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.renewed, p, err) +} + +// SSHSigned implements [authority.Meter] for [Meter]. +func (m *Meter) SSHSigned(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.signed, p, err) +} + +// SSHAuthorized implements [authority.Meter] for [Meter]. +func (m *Meter) SSHWebhookAuthorized(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.webhookAuthorized, p, err) +} + +// SSHEnriched implements [authority.Meter] for [Meter]. +func (m *Meter) SSHWebhookEnriched(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.webhookEnriched, p, err) +} + +// X509Rekeyed implements [authority.Meter] for [Meter]. +func (m *Meter) X509Rekeyed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.rekeyed, p, err) +} + +// X509Renewed implements [authority.Meter] for [Meter]. +func (m *Meter) X509Renewed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.renewed, p, err) +} + +// X509Signed implements [authority.Meter] for [Meter]. +func (m *Meter) X509Signed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.signed, p, err) +} + +// X509Authorized implements [authority.Meter] for [Meter]. +func (m *Meter) X509WebhookAuthorized(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.webhookAuthorized, p, err) +} + +// X509Enriched implements [authority.Meter] for [Meter]. +func (m *Meter) X509WebhookEnriched(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.webhookEnriched, p, err) +} + +func incrProvisionerCounter(cv *prometheus.CounterVec, p provisioner.Interface, err error) { + var name string + if p != nil { + name = p.GetName() + } + + cv.WithLabelValues(name, strconv.FormatBool(err == nil)).Inc() +} + +// KMSSigned implements [authority.Meter] for [Meter]. +func (m *Meter) KMSSigned(err error) { + if err == nil { + m.kms.signed.Inc() + } else { + m.kms.errors.Inc() + } +} + +// provisionerInstruments wraps the counters exported by provisioners. +type provisionerInstruments struct { + rekeyed *prometheus.CounterVec + renewed *prometheus.CounterVec + signed *prometheus.CounterVec + + webhookAuthorized *prometheus.CounterVec + webhookEnriched *prometheus.CounterVec +} + +func newProvisionerInstruments(subsystem string) *provisionerInstruments { + return &provisionerInstruments{ + rekeyed: newCounterVec(subsystem, "rekeyed_total", "Number of certificates rekeyed", + "provisioner", + "success", + ), + renewed: newCounterVec(subsystem, "renewed_total", "Number of certificates renewed", + "provisioner", + "success", + ), + signed: newCounterVec(subsystem, "signed_total", "Number of certificates signed", + "provisioner", + "success", + ), + webhookAuthorized: newCounterVec(subsystem, "webhook_authorized_total", "Number of authorizing webhooks called", + "provisioner", + "success", + ), + webhookEnriched: newCounterVec(subsystem, "webhook_enriched_total", "Number of enriching webhooks called", + "provisioner", + "success", + ), + } +} + +type kms struct { + signed prometheus.Counter + errors prometheus.Counter +} + +func newCounterVec(subsystem, name, help string, labels ...string) *prometheus.CounterVec { + opts := opts(subsystem, name, help) + + return prometheus.NewCounterVec(prometheus.CounterOpts(opts), labels) +} + +func opts(subsystem, name, help string) prometheus.Opts { + return prometheus.Opts{ + Namespace: "step_ca", + Subsystem: subsystem, + Name: name, + Help: help, + } +}