Compare commits
31 Commits
Author | SHA1 | Date |
---|---|---|
|
a9ef40ef40 | |
|
455dc10734 | |
|
81e2be7c6b | |
|
dc4561673d | |
|
0be33f2139 | |
|
bb4fed2902 | |
|
00b5d41de5 | |
|
aa326b746d | |
|
68d6913607 | |
|
0beea6bbc8 | |
|
3bf80d78fb | |
|
c7de9b5377 | |
|
9720677383 | |
|
3c1b79fef5 | |
|
e3774ace37 | |
|
81c5e7da3f | |
|
3228b9f043 | |
|
c9a1c338ef | |
|
71632fca2a | |
|
44475437d9 | |
|
050d6bfaa4 | |
|
74ede875c7 | |
|
33f069ceca | |
|
458a2f893f | |
|
b7910d3af2 | |
|
ec51e0c7aa | |
|
6b2e90b018 | |
|
94e7cf40dc | |
|
5379b3607f | |
|
2725e23db1 | |
|
e28a1f1a49 |
|
@ -0,0 +1,15 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
groups:
|
||||
aws-sdk-go:
|
||||
patterns:
|
||||
- "github.com/aws/aws-sdk-go-v2"
|
||||
- "github.com/aws/aws-sdk-go-v2/*"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
|
@ -13,14 +13,14 @@ permissions:
|
|||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
go-version: 1.24.2
|
||||
- uses: actions/checkout@v4
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: latest
|
||||
|
|
|
@ -13,12 +13,12 @@ permissions:
|
|||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
go-version: 1.24.2
|
||||
- run: ./build-release.sh
|
||||
# Upload to S3:
|
||||
- uses: aws-actions/configure-aws-credentials@v4
|
||||
|
|
|
@ -11,15 +11,15 @@ on:
|
|||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
go-version: 1.24.2
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
|
|
@ -10,10 +10,10 @@ on:
|
|||
|
||||
jobs:
|
||||
try-release:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
go-version: 1.24.2
|
||||
- run: ./build-release.sh
|
||||
|
|
|
@ -1,19 +1,37 @@
|
|||
version: "2"
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- gofmt
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- wastedassign
|
||||
linters-settings:
|
||||
errcheck:
|
||||
ignore: fmt:[FS]?[Pp]rint*,io:Write,os:Remove,net/http:Write,net:Write,encoding/binary:Write
|
||||
settings:
|
||||
gosec:
|
||||
excludes:
|
||||
- G404
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
|
57
README.md
57
README.md
|
@ -2,7 +2,57 @@
|
|||
|
||||
[](https://github.com/letsencrypt/crl-monitor/actions/workflows/test.yml?query=branch%3Amain)
|
||||
|
||||
CRL-Monitor monitors CRLs
|
||||
CRL-Monitor monitors CRLs.
|
||||
|
||||
It issues certificates, revokes them, and then looks for them to appear in CRLs.
|
||||
Each component runs as an AWS Lambda.
|
||||
|
||||
The `churner` runs periodically. On each run it issues a certificate, fetches and lints
|
||||
the certificate's CRL URL, revokes the certificate, and stores its serial number and metadata
|
||||
for the `checker` to later verify that it shows up as revoked. It also checks previously
|
||||
seen serials. If they haven't shown up in a CRL after a reasonable amount of time, `checker`
|
||||
produces an error.
|
||||
|
||||
The `checker` runs in response to the upload of each new CRL shard in S3. It diffs the newly
|
||||
uploaded CRL shard against its previous version and verifies:
|
||||
|
||||
- New CRL has a later date and higher CRL number than the previous version.
|
||||
- New CRL passes lints.
|
||||
- For any serials removed between the old shard and the new one:
|
||||
- The certificate is expired (based on fetching it by serial from Let's Encrypt).
|
||||
- For any serials added (if the certificate was issued by the churner):
|
||||
- The certificate's CRLDistributionPoint matches the CRL shard's IssuingDistributionPoint.
|
||||
|
||||
The `checker` also removes from database any certificates it sees, to indicate that their
|
||||
revocation has been published, so the `churner` won't alert about them
|
||||
It then marks as completed (deletes) any `churner`-issued certificates that show up on
|
||||
the new CRL.
|
||||
|
||||
## Build and Deployment
|
||||
|
||||
This repository has two binaries named `checker` and two binaries named `churner`. The
|
||||
binaries under `cmd` are for local use and testing. The binaries under `lambda` are for
|
||||
deployment to AWS Lambda. The key difference is that the `lambda/` binaries register a
|
||||
lambda handler ([`lambda.StartWithOptions()`]), which AWS then calls. That
|
||||
[handler can return errors], and we have separate Cloudwatch monitoring that alerts when
|
||||
any errors are detected.
|
||||
|
||||
The lambda binaries are built by a release workflow on GitHub Actions triggered by uploading
|
||||
a release tag (starting with `v`). Those binaries are uploaded to S3 under a versioned path.
|
||||
They are then deployed to Lambda using Terraform (in another repository).
|
||||
|
||||
[`lambda.StartWithOptions()`]: https://pkg.go.dev/github.com/aws/aws-lambda-go/lambda#StartWithOptions
|
||||
[handler can return errors]: https://docs.aws.amazon.com/lambda/latest/dg/foundation-progmodel.html
|
||||
|
||||
## Testing
|
||||
|
||||
Most of the tests are unittests and can be run with:
|
||||
|
||||
go test ./...
|
||||
|
||||
There is also an integration test for DynamoDB code. To run this, install Java and run:
|
||||
|
||||
./db/run_integration_test.sh
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
|
@ -17,6 +67,7 @@ sequenceDiagram
|
|||
loop timer
|
||||
activate churn
|
||||
churn->>ca: Issue certificate
|
||||
churn->>ca: Fetch CRL
|
||||
churn->>ca: Revoke certificate
|
||||
churn->>ddb: Store certificate metadata
|
||||
ddb->>churn: Get previous revoked serials
|
||||
|
@ -31,10 +82,10 @@ sequenceDiagram
|
|||
checker->>s3: Read current CRL
|
||||
checker->>s3: Read previous CRL
|
||||
Note over checker: Alert if CRL<br />fails linting
|
||||
loop all removed serials
|
||||
loop random selection of serials
|
||||
checker->>ca: Get Certificate
|
||||
end
|
||||
Note over checker: Alert if CRL had any<br />serials leave early
|
||||
Note over checker: Alert if CRL had<br />serials leave early
|
||||
checker->>ddb: Get revoked serials
|
||||
checker->>ddb: Delete seen serials
|
||||
deactivate checker
|
||||
|
|
|
@ -4,16 +4,17 @@ import (
|
|||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/crl/checker"
|
||||
"github.com/letsencrypt/boulder/crl/idp"
|
||||
|
||||
"github.com/letsencrypt/crl-monitor/checker/earlyremoval"
|
||||
"github.com/letsencrypt/crl-monitor/checker/expiry"
|
||||
|
@ -77,7 +78,6 @@ func NewFromEnv(ctx context.Context) (*Checker, error) {
|
|||
}
|
||||
|
||||
baf := expiry.BoulderAPIFetcher{
|
||||
Client: http.DefaultClient,
|
||||
BaseURL: boulderBaseURL,
|
||||
}
|
||||
|
||||
|
@ -113,18 +113,57 @@ type Checker struct {
|
|||
issuers map[string]*x509.Certificate
|
||||
}
|
||||
|
||||
// crlSummary is a subset of fields from *x509.RevocationList
|
||||
// useful for logging, plus the number of entries and some metadata.
|
||||
type crlSummary struct {
|
||||
Number *big.Int
|
||||
NumEntries int
|
||||
ThisUpdate time.Time
|
||||
NextUpdate time.Time
|
||||
URL string
|
||||
StorageKey storage.Key
|
||||
}
|
||||
|
||||
func summary(crl *x509.RevocationList, key storage.Key) crlSummary {
|
||||
// If getIDP fails, we will just log ""
|
||||
idp, _ := getIDP(crl)
|
||||
return crlSummary{
|
||||
ThisUpdate: crl.ThisUpdate,
|
||||
NextUpdate: crl.NextUpdate,
|
||||
Number: crl.Number,
|
||||
NumEntries: len(crl.RevokedCertificateEntries),
|
||||
URL: idp,
|
||||
StorageKey: key,
|
||||
}
|
||||
}
|
||||
|
||||
type crlsSummary struct {
|
||||
Old, New crlSummary
|
||||
}
|
||||
|
||||
func logSummary(old *x509.RevocationList, oldStorageKey storage.Key, new *x509.RevocationList, newStorageKey storage.Key) crlsSummary {
|
||||
return crlsSummary{
|
||||
Old: summary(old, oldStorageKey),
|
||||
New: summary(new, newStorageKey),
|
||||
}
|
||||
}
|
||||
|
||||
// Check fetches a CRL and its previous version. It runs lints on the CRL, checks for early removal, and removes any
|
||||
// certificates we're waiting for out of the database.
|
||||
func (c *Checker) Check(ctx context.Context, bucket, object string, startingVersion *string) error {
|
||||
// Read the current CRL shard
|
||||
crlDER, version, err := c.storage.Fetch(ctx, bucket, object, startingVersion)
|
||||
crlDER, version, err := c.storage.Fetch(ctx, storage.Key{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
Version: startingVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crl, err := x509.ParseRevocationList(crlDER)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing current crl: %v", err)
|
||||
return fmt.Errorf("parsing current crl: %v", err)
|
||||
}
|
||||
log.Printf("loaded CRL number %d (len %d) from %s version %s", crl.Number, len(crl.RevokedCertificateEntries), object, version)
|
||||
|
||||
|
@ -139,26 +178,41 @@ func (c *Checker) Check(ctx context.Context, bucket, object string, startingVers
|
|||
}
|
||||
log.Printf("crl %d successfully linted", crl.Number)
|
||||
|
||||
// And the previous:
|
||||
prevVersion, err := c.storage.Previous(ctx, bucket, object, version)
|
||||
_, err = getIDP(crl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prevDER, _, err := c.storage.Fetch(ctx, bucket, object, &prevVersion)
|
||||
curKey := storage.Key{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
Version: &version,
|
||||
}
|
||||
// And the previous:
|
||||
prevVersion, err := c.storage.Previous(ctx, curKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prevKey := curKey
|
||||
prevKey.Version = &prevVersion
|
||||
|
||||
prevDER, _, err := c.storage.Fetch(ctx, prevKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prev, err := x509.ParseRevocationList(prevDER)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing previous crl: %v", err)
|
||||
return fmt.Errorf("parsing previous crl: %v", err)
|
||||
}
|
||||
log.Printf("loaded previous CRL number %d (len %d) from version %s", prev.Number, len(prev.RevokedCertificateEntries), prevVersion)
|
||||
|
||||
context := logSummary(prev, prevKey, crl, curKey)
|
||||
|
||||
earlyRemoved, err := earlyremoval.Check(ctx, c.fetcher, c.maxFetch, prev, crl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check for early removal: %v", err)
|
||||
return fmt.Errorf("checking for early removal: %v. context: %+v", err, context)
|
||||
}
|
||||
|
||||
if len(earlyRemoved) != 0 {
|
||||
|
@ -168,7 +222,7 @@ func (c *Checker) Check(ctx context.Context, bucket, object string, startingVers
|
|||
}
|
||||
|
||||
// Certificates removed early! This is very bad.
|
||||
return fmt.Errorf("early removal of %d certificates detected! First %d: %v", len(earlyRemoved), len(sample), sample)
|
||||
return fmt.Errorf("early removal of %d certificates detected! First %d: %v. context: %+v", len(earlyRemoved), len(sample), sample, context)
|
||||
}
|
||||
|
||||
return c.lookForSeenCerts(ctx, crl)
|
||||
|
@ -179,20 +233,31 @@ func (c *Checker) Check(ctx context.Context, bucket, object string, startingVers
|
|||
func (c *Checker) lookForSeenCerts(ctx context.Context, crl *x509.RevocationList) error {
|
||||
unseenCerts, err := c.db.GetAllCerts(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read from db: %v", err)
|
||||
return fmt.Errorf("getting all certs from DB: %v", err)
|
||||
}
|
||||
var seenSerials [][]byte
|
||||
var errs []error
|
||||
for _, seen := range crl.RevokedCertificateEntries {
|
||||
if metadata, ok := unseenCerts[db.NewCertKey(seen.SerialNumber).SerialString()]; ok {
|
||||
idp, err := getIDP(crl)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if metadata.CRLDistributionPoint != "" && metadata.CRLDistributionPoint != idp {
|
||||
errs = append(errs, fmt.Errorf("cert %x on CRL %q has non-matching CRLDistributionPoint %q",
|
||||
seen.SerialNumber, idp, metadata.CRLDistributionPoint))
|
||||
continue
|
||||
}
|
||||
seenSerials = append(seenSerials, metadata.SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
err = c.db.DeleteSerials(ctx, seenSerials)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete from db: %v", err)
|
||||
errs = append(errs, fmt.Errorf("deleting %d serials from DB: %v", len(seenSerials), err))
|
||||
}
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// issuerForObject takes an s3 object path, extracts the issuer prefix, and returns the right x509.Certificate
|
||||
|
@ -209,3 +274,14 @@ func (c *Checker) issuerForObject(object string) (*x509.Certificate, error) {
|
|||
|
||||
return issuer, nil
|
||||
}
|
||||
|
||||
func getIDP(crl *x509.RevocationList) (string, error) {
|
||||
idps, err := idp.GetIDPURIs(crl.Extensions)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("extracting IssuingDistributionPoint URIs: %v", err)
|
||||
}
|
||||
if len(idps) == 1 {
|
||||
return idps[0], nil
|
||||
}
|
||||
return "", fmt.Errorf("CRL had incorrect number of IssuingDistributionPoint URIs: %s", idps)
|
||||
}
|
||||
|
|
|
@ -31,13 +31,17 @@ func TestCheck(t *testing.T) {
|
|||
issuerName := nameID(issuer)
|
||||
shouldBeGood := fmt.Sprintf("%s/should-be-good.crl", issuerName)
|
||||
earlyRemoval := fmt.Sprintf("%s/early-removal.crl", issuerName)
|
||||
shouldBeGoodIDP := fmt.Sprintf("http://idp/%s", shouldBeGood)
|
||||
earlyRemovalIDP := fmt.Sprintf("http://idp/%s", earlyRemoval)
|
||||
certificatesHaveCRLDP := fmt.Sprintf("%s/certificates-have-crldp.crl", issuerName)
|
||||
shouldBeGoodURL := fmt.Sprintf("http://idp/%s", shouldBeGood)
|
||||
earlyRemovalURL := fmt.Sprintf("http://idp/%s", earlyRemoval)
|
||||
certificatesHaveCRLDPURL := fmt.Sprintf("http://idp/%s", certificatesHaveCRLDP)
|
||||
|
||||
crl1der := testdata.MakeCRL(t, &testdata.CRL1, shouldBeGoodIDP, issuer, key)
|
||||
crl2der := testdata.MakeCRL(t, &testdata.CRL2, shouldBeGoodIDP, issuer, key)
|
||||
crl3der := testdata.MakeCRL(t, &testdata.CRL3, earlyRemovalIDP, issuer, key)
|
||||
crl4der := testdata.MakeCRL(t, &testdata.CRL4, earlyRemovalIDP, issuer, key)
|
||||
crl1der := testdata.MakeCRL(t, &testdata.CRL1, shouldBeGoodURL, issuer, key)
|
||||
crl2der := testdata.MakeCRL(t, &testdata.CRL2, shouldBeGoodURL, issuer, key)
|
||||
crl3der := testdata.MakeCRL(t, &testdata.CRL3, earlyRemovalURL, issuer, key)
|
||||
crl4der := testdata.MakeCRL(t, &testdata.CRL4, earlyRemovalURL, issuer, key)
|
||||
crl6der := testdata.MakeCRL(t, &testdata.CRL6, certificatesHaveCRLDPURL, issuer, key)
|
||||
crl7der := testdata.MakeCRL(t, &testdata.CRL7, certificatesHaveCRLDPURL, issuer, key)
|
||||
|
||||
data := map[string][]storagemock.MockObject{
|
||||
shouldBeGood: {
|
||||
|
@ -60,6 +64,16 @@ func TestCheck(t *testing.T) {
|
|||
Data: crl3der,
|
||||
},
|
||||
},
|
||||
certificatesHaveCRLDP: {
|
||||
{
|
||||
VersionID: "the-current-version",
|
||||
Data: crl7der, // CRL6 has serial 4213, which has a CRLDP
|
||||
},
|
||||
{
|
||||
VersionID: "the-previous-version",
|
||||
Data: crl6der,
|
||||
},
|
||||
},
|
||||
}
|
||||
bucket := "crl-test"
|
||||
|
||||
|
@ -74,11 +88,12 @@ func TestCheck(t *testing.T) {
|
|||
|
||||
ctx := context.Background()
|
||||
|
||||
// Watch the first revoked cert's serial
|
||||
// Insert some serials in the "unseen-certificates" table to be checked.
|
||||
serial := testdata.CRL1.RevokedCertificateEntries[0].SerialNumber
|
||||
require.NoError(t, checker.db.AddCert(ctx, &x509.Certificate{SerialNumber: serial}, testdata.Now))
|
||||
shouldNotBeSeen := big.NewInt(12345)
|
||||
require.NoError(t, checker.db.AddCert(ctx, &x509.Certificate{SerialNumber: shouldNotBeSeen}, testdata.Now))
|
||||
mismatchCRLDistributionPoint := big.NewInt(4213)
|
||||
|
||||
require.NoError(t, checker.Check(ctx, bucket, shouldBeGood, nil))
|
||||
|
||||
|
@ -92,6 +107,15 @@ func TestCheck(t *testing.T) {
|
|||
|
||||
// The "early-removal" object should error on a certificate removed early
|
||||
require.ErrorContains(t, checker.Check(ctx, bucket, earlyRemoval, nil), "early removal of 1 certificates detected!")
|
||||
|
||||
require.NoError(t, checker.db.AddCert(ctx, &x509.Certificate{
|
||||
SerialNumber: mismatchCRLDistributionPoint,
|
||||
CRLDistributionPoints: []string{
|
||||
"http://example.com",
|
||||
},
|
||||
}, testdata.Now))
|
||||
// The "certificates-have-crldp" object should error because the certificate CRL is a mismatch
|
||||
require.ErrorContains(t, checker.Check(ctx, bucket, certificatesHaveCRLDP, nil), "has non-matching CRLDistributionPoint")
|
||||
}
|
||||
|
||||
func Test_nameID(t *testing.T) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"crypto/x509"
|
||||
"log"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/crl/checker"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto/x509"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -68,9 +68,8 @@ func TestSample(t *testing.T) {
|
|||
require.Empty(t, sample([]int{}, 999))
|
||||
|
||||
var data []int
|
||||
// Generate a random array for tests. Insecure RNG is fine.
|
||||
// #nosec G404
|
||||
length := 100 + rand.Intn(300)
|
||||
// Generate a random array for tests.
|
||||
length := 100 + rand.IntN(300)
|
||||
for i := 0; i < length; i++ {
|
||||
data = append(data, i)
|
||||
}
|
||||
|
|
|
@ -5,80 +5,41 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/crl-monitor/retryhttp"
|
||||
)
|
||||
|
||||
type BoulderAPIFetcher struct {
|
||||
Client *http.Client
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
func (baf *BoulderAPIFetcher) getBody(ctx context.Context, url string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "CRL-Monitor/0.1")
|
||||
resp, err := baf.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http status %d (%s)", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// getWithRetries is a simple wrapper around client.Do that will retry on a fixed backoff schedule
|
||||
func (baf *BoulderAPIFetcher) getWithRetries(ctx context.Context, url string) ([]byte, error) {
|
||||
// A fixed sequence of retries. We start with 0 seconds, retrying
|
||||
// immediately, and increase a few seconds between each retry. The final
|
||||
// value is zero so that we don't sleep before returning the final error.
|
||||
var err error
|
||||
for _, backoff := range []int{0, 1, 1, 2, 3, 0} {
|
||||
var body []byte
|
||||
body, err = baf.getBody(ctx, url)
|
||||
if err == nil {
|
||||
return body, nil
|
||||
}
|
||||
time.Sleep(time.Duration(backoff) * time.Second)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FetchNotAfter downloads a certificate, parses it, and returns the NotAfter on
|
||||
// it. It uses a non-acme path to download a certificate unauthenticated by
|
||||
// serial. So it is specific to Boulder's API, not a generic ACME API client.
|
||||
func (baf *BoulderAPIFetcher) FetchNotAfter(ctx context.Context, serial *big.Int) (time.Time, error) {
|
||||
// The baseURL is followed by a hex-encoded serial
|
||||
url := fmt.Sprintf("%s/%036x", baf.BaseURL, serial)
|
||||
url := fmt.Sprintf("%s/%s", baf.BaseURL, formatSerial(serial))
|
||||
|
||||
body, err := baf.getWithRetries(ctx, url)
|
||||
body, err := retryhttp.Get(ctx, url)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("error fetching NotAfter for serial %d: %w", serial, err)
|
||||
return time.Time{}, fmt.Errorf("fetching NotAfter for serial %s: %w", formatSerial(serial), err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(body)
|
||||
if block == nil {
|
||||
return time.Time{}, fmt.Errorf("error parsing PEM for serial %d: %s", serial, string(body))
|
||||
return time.Time{}, fmt.Errorf("parsing PEM for serial %s: %s", formatSerial(serial), string(body))
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("error parsing certificate for serial %d: %w", serial, err)
|
||||
return time.Time{}, fmt.Errorf("parsing certificate for serial %s: %s", formatSerial(serial), err)
|
||||
}
|
||||
|
||||
return cert.NotAfter, nil
|
||||
}
|
||||
|
||||
func formatSerial(serial *big.Int) string {
|
||||
return fmt.Sprintf("%036x", serial)
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ func TestBoulderAPIFetcher(t *testing.T) {
|
|||
res.Write([]byte(testCert))
|
||||
}))
|
||||
|
||||
fetcher := BoulderAPIFetcher{BaseURL: testServer.URL + somePrefix, Client: http.DefaultClient}
|
||||
fetcher := BoulderAPIFetcher{BaseURL: testServer.URL + somePrefix}
|
||||
|
||||
serial := new(big.Int)
|
||||
serial.SetString(serialhex, 16)
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -36,7 +35,7 @@ func TestBoulderAPI(t *testing.T) {
|
|||
} {
|
||||
t.Run(tc.subdomain, func(t *testing.T) {
|
||||
baseURL := fmt.Sprintf("https://%s.api.letsencrypt.org/get/cert", tc.subdomain)
|
||||
baf := BoulderAPIFetcher{Client: http.DefaultClient, BaseURL: baseURL}
|
||||
baf := BoulderAPIFetcher{BaseURL: baseURL}
|
||||
|
||||
serial := new(big.Int)
|
||||
serial.SetString(tc.serial, 16)
|
||||
|
|
|
@ -70,6 +70,28 @@ var CRL5 = x509.RevocationList{
|
|||
RevokedCertificateEntries: nil,
|
||||
}
|
||||
|
||||
// CRL6 contains serial 4213, which will have a CRLDistributionPoint
|
||||
// that doesn't match the CRL.
|
||||
var CRL6 = x509.RevocationList{
|
||||
ThisUpdate: Now.Add(4 * time.Hour),
|
||||
NextUpdate: Now.Add(24 * time.Hour),
|
||||
Number: big.NewInt(1),
|
||||
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||
{SerialNumber: big.NewInt(4213), RevocationTime: Now},
|
||||
},
|
||||
}
|
||||
|
||||
// CRL7 also contains serial 4213, which will have a CRLDistributionPoint
|
||||
// that doesn't match the CRL.
|
||||
var CRL7 = x509.RevocationList{
|
||||
ThisUpdate: Now.Add(5 * time.Hour),
|
||||
NextUpdate: Now.Add(25 * time.Hour),
|
||||
Number: big.NewInt(2),
|
||||
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||
{SerialNumber: big.NewInt(4213), RevocationTime: Now},
|
||||
},
|
||||
}
|
||||
|
||||
func MakeIssuer(t *testing.T) (*x509.Certificate, crypto.Signer) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -6,20 +6,25 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
mathrand "math/rand/v2"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/libdns/route53"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
"github.com/mholt/acmez/v3"
|
||||
"github.com/mholt/acmez/v3/acme"
|
||||
|
||||
"github.com/letsencrypt/boulder/crl/checker"
|
||||
"github.com/letsencrypt/crl-monitor/cmd"
|
||||
"github.com/letsencrypt/crl-monitor/db"
|
||||
"github.com/letsencrypt/crl-monitor/retryhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -45,21 +50,20 @@ type Churner struct {
|
|||
// `baseDomain` should be a domain name that the `dnsProvider` can create/delete
|
||||
// records for. The certs will be issued from the CA at `acmeDirectory`.
|
||||
// The resulting serials are stored into `db`
|
||||
func New(baseDomain string, acmeDirectory string, dnsProvider certmagic.ACMEDNSProvider, db *db.Database, cutoff time.Time) (*Churner, error) {
|
||||
zapLogger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func New(baseDomain string, acmeDirectory string, dnsProvider certmagic.DNSProvider, db *db.Database, cutoff time.Time) (*Churner, error) {
|
||||
slogger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
||||
|
||||
acmeClient := acmez.Client{
|
||||
Client: &acme.Client{
|
||||
Directory: acmeDirectory,
|
||||
Logger: zapLogger,
|
||||
Logger: slogger,
|
||||
},
|
||||
ChallengeSolvers: map[string]acmez.Solver{
|
||||
acme.ChallengeTypeDNS01: &certmagic.DNS01Solver{
|
||||
DNSProvider: dnsProvider,
|
||||
PropagationDelay: 60 * time.Second, // Route53 docs say 60 seconds in normal conditions
|
||||
DNSManager: certmagic.DNSManager{
|
||||
DNSProvider: dnsProvider,
|
||||
PropagationDelay: 60 * time.Second, // Route53 docs say 60 seconds in normal conditions,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -117,10 +121,17 @@ func (c *Churner) RegisterAccount(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (c *Churner) retryObtain(ctx context.Context, certPrivateKey crypto.Signer, sans []string) ([]acme.Certificate, error) {
|
||||
var err error
|
||||
csr, err := acmez.NewCSR(certPrivateKey, sans)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params, err := acmez.OrderParametersFromCSR(c.acmeAccount, csr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var certificates []acme.Certificate
|
||||
for retry := 0; retry < 5; retry++ {
|
||||
certificates, err = c.acmeClient.ObtainCertificate(ctx, c.acmeAccount, certPrivateKey, sans)
|
||||
certificates, err = c.acmeClient.ObtainCertificate(ctx, params)
|
||||
if err != nil {
|
||||
log.Printf("error obtaining certificate on retry %d: %v", retry, err)
|
||||
time.Sleep(time.Second)
|
||||
|
@ -133,8 +144,7 @@ func (c *Churner) retryObtain(ctx context.Context, certPrivateKey crypto.Signer,
|
|||
|
||||
// Churn issues a certificate, revokes it, and stores the result in DynamoDB
|
||||
func (c *Churner) Churn(ctx context.Context) error {
|
||||
// Generate either an ecdsa or rsa private key
|
||||
certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
certPrivateKey, err := randomKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -144,14 +154,47 @@ func (c *Churner) Churn(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// certificates contains all the possible cert chains. We only care about
|
||||
// the cert, so we just take the first one and parse it.
|
||||
// certificates contains all the possible cert chains. We don't
|
||||
// care about alternate chains, but we do care about getting
|
||||
// the parent of the certificate we just got, so we can validate its CRL.
|
||||
firstChain := certificates[0].ChainPEM
|
||||
block, _ := pem.Decode(firstChain)
|
||||
block, remaining := pem.Decode(firstChain)
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, _ = pem.Decode(remaining)
|
||||
issuer, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the certificate has any CRLDistributionPoints, check that they can be fetched,
|
||||
// parsed, verified, and linted. We don't try to check for revocation at this stage
|
||||
// because it may be several hours before a new CRL is uploaded that reflects the
|
||||
// revocation we're about to do. Contrariwise, we check for non-revocation, since
|
||||
// we're fetching the CRL before revoking.
|
||||
for _, url := range cert.CRLDistributionPoints {
|
||||
body, err := retryhttp.Get(ctx, url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching CRL %q from CRLDistributionPoint of certificate %036x: %s",
|
||||
url, cert.SerialNumber, err)
|
||||
}
|
||||
crl, err := x509.ParseRevocationList(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching CRL %q from CRLDistributionPoint of certificate %036x: %s",
|
||||
url, cert.SerialNumber, err)
|
||||
}
|
||||
err = checker.Validate(crl, issuer, 24*time.Hour)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range crl.RevokedCertificateEntries {
|
||||
if entry.SerialNumber.Cmp(cert.SerialNumber) == 0 {
|
||||
return fmt.Errorf("certificate %x was found on CRL %s before it was revoked", cert.SerialNumber, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = c.acmeClient.RevokeCertificate(ctx, c.acmeAccount, cert, c.acmeAccount.PrivateKey, acme.ReasonCessationOfOperation)
|
||||
if err != nil {
|
||||
|
@ -161,16 +204,19 @@ func (c *Churner) Churn(ctx context.Context) error {
|
|||
return c.db.AddCert(ctx, cert, time.Now())
|
||||
}
|
||||
|
||||
// randomKey generates either an ecdsa or rsa private key
|
||||
func randomKey() (crypto.Signer, error) {
|
||||
if mathrand.IntN(2) == 0 {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
} else {
|
||||
return rsa.GenerateKey(rand.Reader, 2048)
|
||||
}
|
||||
}
|
||||
|
||||
// randDomains picks the domains to include on the certificate.
|
||||
// We put a single domain which includes the current time and a random value.
|
||||
func randDomains(baseDomain string) []string {
|
||||
randomSuffix := make([]byte, 2)
|
||||
_, err := rand.Read(randomSuffix)
|
||||
if err != nil {
|
||||
// Something has to go terribly wrong for this
|
||||
panic(fmt.Sprintf("random read failed: %v", err))
|
||||
}
|
||||
domain := fmt.Sprintf("r%dz%x.%s", time.Now().Unix(), randomSuffix, baseDomain)
|
||||
domain := fmt.Sprintf("r%dz%x.%s", time.Now().Unix(), mathrand.Uint32(), baseDomain)
|
||||
return []string{domain}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestRandDomains(t *testing.T) {
|
|||
base := "revoked.invalid"
|
||||
domains := randDomains(base)
|
||||
require.Len(t, domains, 1)
|
||||
require.Regexp(t, regexp.MustCompile(`r[0-9]{10}z[0-9a-f]{4}\.`+regexp.QuoteMeta(base)), domains[0])
|
||||
require.Regexp(t, regexp.MustCompile(`r[0-9]{10}z[0-9a-f]+\.`+regexp.QuoteMeta(base)), domains[0])
|
||||
|
||||
second := randDomains(base)
|
||||
require.NotEqual(t, domains, second, "Domains should be different each invocation")
|
||||
|
|
32
db/db.go
32
db/db.go
|
@ -47,7 +47,8 @@ func New(ctx context.Context, table, dynamoEndpoint string) (*Database, error) {
|
|||
// That is the CertKey plus the revocation time today.
|
||||
type CertMetadata struct {
|
||||
CertKey
|
||||
RevocationTime time.Time `dynamodbav:"RT,unixtime"`
|
||||
RevocationTime time.Time `dynamodbav:"RT,unixtime"`
|
||||
CRLDistributionPoint string `dynamodbav:"DP,string,omitempty"`
|
||||
}
|
||||
|
||||
// CertKey is the DynamoDB primary key, which is the serial number.
|
||||
|
@ -67,9 +68,19 @@ func (ck CertKey) SerialString() string {
|
|||
|
||||
// AddCert inserts the metadata for monitoring
|
||||
func (db *Database) AddCert(ctx context.Context, certificate *x509.Certificate, revocationTime time.Time) error {
|
||||
var crlDistributionPoint string
|
||||
// TODO: Once all issued certificates have a CRLDistributionPoint, error out when
|
||||
// the extension is absent.
|
||||
if len(certificate.CRLDistributionPoints) > 0 {
|
||||
crlDistributionPoint = certificate.CRLDistributionPoints[0]
|
||||
}
|
||||
if len(certificate.CRLDistributionPoints) > 1 {
|
||||
return fmt.Errorf("too many CRLDistributionPoints in certificate: %d", len(certificate.CRLDistributionPoints))
|
||||
}
|
||||
item, err := attributevalue.MarshalMap(CertMetadata{
|
||||
CertKey: NewCertKey(certificate.SerialNumber),
|
||||
RevocationTime: revocationTime,
|
||||
CertKey: NewCertKey(certificate.SerialNumber),
|
||||
RevocationTime: revocationTime,
|
||||
CRLDistributionPoint: crlDistributionPoint,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -109,7 +120,7 @@ func (db *Database) GetAllCerts(ctx context.Context) (map[string]CertMetadata, e
|
|||
|
||||
certs := make(map[string]CertMetadata, len(certList))
|
||||
for _, cert := range certList {
|
||||
certs[cert.CertKey.SerialString()] = cert
|
||||
certs[cert.SerialString()] = cert
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
@ -141,16 +152,3 @@ func (db *Database) DeleteSerials(ctx context.Context, serialNumbers [][]byte) e
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StaticResolver is used in test and dev to use the local dynamodb
|
||||
func StaticResolver(url string) func(service, region string, opts ...interface{}) (aws.Endpoint, error) {
|
||||
return func(service, region string, opts ...interface{}) (aws.Endpoint, error) {
|
||||
if service != dynamodb.ServiceID {
|
||||
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
|
||||
}
|
||||
return aws.Endpoint{
|
||||
PartitionID: "aws",
|
||||
URL: url,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -69,3 +70,57 @@ func smoketest(t *testing.T, handle *db.Database) {
|
|||
}
|
||||
require.Equal(t, expected, remaining)
|
||||
}
|
||||
|
||||
func TestAddCertCRLDP(t *testing.T) {
|
||||
handle := mock.NewMockedDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
revocationTime := time.Now().Add(100 * time.Hour)
|
||||
|
||||
int111 := big.NewInt(111)
|
||||
int4s := big.NewInt(444444)
|
||||
int60s := big.NewInt(606060)
|
||||
|
||||
err := handle.AddCert(ctx, &x509.Certificate{
|
||||
SerialNumber: int111,
|
||||
}, revocationTime)
|
||||
if err != nil {
|
||||
t.Errorf("inserting plain cert: %s", err)
|
||||
}
|
||||
|
||||
err = handle.AddCert(ctx, &x509.Certificate{
|
||||
SerialNumber: int4s,
|
||||
CRLDistributionPoints: []string{
|
||||
"http://example.com/crl",
|
||||
"http://example.net/crl",
|
||||
},
|
||||
}, revocationTime)
|
||||
if err == nil {
|
||||
t.Errorf("inserting cert with two CRLDistributionPoints: got success, want error")
|
||||
}
|
||||
|
||||
err = handle.AddCert(ctx, &x509.Certificate{
|
||||
SerialNumber: int60s,
|
||||
CRLDistributionPoints: []string{
|
||||
"http://example.com/crl",
|
||||
},
|
||||
}, revocationTime)
|
||||
if err != nil {
|
||||
t.Errorf("inserting cert with one CRLDistributionPoint: %s", err)
|
||||
}
|
||||
|
||||
results, err := handle.GetAllCerts(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("getting all certs: %s", err)
|
||||
}
|
||||
|
||||
serialString := fmt.Sprintf("%036x", int60s)
|
||||
metadata, ok := results[serialString]
|
||||
if !ok {
|
||||
t.Errorf("getting all certs: expected entry for %s, got %+v", serialString, metadata)
|
||||
}
|
||||
|
||||
if metadata.CRLDistributionPoint != "http://example.com/crl" {
|
||||
t.Errorf("CRL for %s = %q, want %q", serialString, metadata.CRLDistributionPoint, "http://example.com/crl")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,10 @@ import (
|
|||
// run most tests outside the db package.
|
||||
func TestIntegrationDynamoDB(t *testing.T) {
|
||||
cfg := aws.NewConfig()
|
||||
cfg.EndpointResolverWithOptions = aws.EndpointResolverWithOptionsFunc(db.StaticResolver("http://localhost:8000"))
|
||||
cfg.Credentials = aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
|
||||
return aws.Credentials{AccessKeyID: "Bogus", SecretAccessKey: "Bogus"}, nil
|
||||
})
|
||||
handle, err := db.New("unseen-certificates", cfg)
|
||||
handle, err := db.New(context.Background(), "unseen-certificates", "http://localhost:8000")
|
||||
require.NoError(t, err)
|
||||
|
||||
smoketest(t, handle)
|
||||
|
|
85
go.mod
85
go.mod
|
@ -1,63 +1,66 @@
|
|||
module github.com/letsencrypt/crl-monitor
|
||||
|
||||
go 1.22.0
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.22.2
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/aws/aws-lambda-go v1.46.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.10
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/aws/aws-lambda-go v1.49.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.4
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.16
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.2
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.3
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2
|
||||
github.com/caddyserver/certmagic v0.21.7
|
||||
github.com/letsencrypt/boulder v0.0.0-20240424004736-7ee5b469a6a9
|
||||
github.com/libdns/route53 v1.3.3
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/zap v1.27.0
|
||||
github.com/libdns/route53 v1.5.1
|
||||
github.com/mholt/acmez/v3 v3.1.2
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.20.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.69 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/weppos/publicsuffix-go v0.30.3-0.20240411085455-21202160c2ed // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c // indirect
|
||||
github.com/zmap/zlint/v3 v3.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
|
174
go.sum
174
go.sum
|
@ -1,71 +1,60 @@
|
|||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
|
||||
github.com/aws/aws-lambda-go v1.46.0 h1:UWVnvh2h2gecOlFhHQfIPQcD8pL/f7pVCutmFl+oXU8=
|
||||
github.com/aws/aws-lambda-go v1.46.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.8/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.21/go.mod h1:+jPQiVPz1diRnjj6VGqWcLK6EzNmQ42l7J3OqGTLsSY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.10 h1:PS+65jThT0T/snC5WjyfHHyUgG+eBoupSDV+f838cro=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.10/go.mod h1:BePM7Vo4OBpHreKRUMuDXX+/+JWP38FLkzl5m27/Jjs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.20/go.mod h1:xtZnXErtbZ8YGXC3+8WfajpMBn5Ga/3ojZdxHq6iI8o=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.10 h1:qDZ3EA2lv1KangvQB6y258OssCHD0xvaGiEDkG4X/10=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.10/go.mod h1:6t3sucOaYDwDssHQa0ojH1RpmVmF5/jArkye1b2FKMI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13 h1:loQ4VSt3hTm9n8ST9jveArwmhqAc5aiRJXlxLPxCNTw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13/go.mod h1:RjdeQvzJuUf9jWj+ta+7l3VnVpDZ+RmtP/p+QdwRIpI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2/go.mod h1:cDh1p6XkSGSwSRIArWRc6+UqAQ7x4alQ0QfpVR6f+co=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.32/go.mod h1:RudqOgadTWdcS3t/erPQo24pcVEoYyqj/kKW5Vya21I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.26/go.mod h1:vq86l7956VgFr0/FWQ2BWnK07QC3WYsepKzy33qqY5U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33/go.mod h1:zG2FcwjQarWaqXSCGpgcr3RSjZ6dHGguZSppUL0XR7Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1 h1:dZXY07Dm59TxAjJcUfNMJHLDI/gLMxTRZefn2jFAVsw=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1/go.mod h1:lVLqEtX+ezgtfalyJs7Peb0uv9dEpAQP5yuq2O26R44=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.20.4 h1:hSwDD19/e01z3pfyx+hDeX5T/0Sn+ZEnnTO5pVWKWx8=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.20.4/go.mod h1:61CuGwE7jYn0g2gl7K3qoT4vCY59ZQEixkPu8PN5IrE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 h1:ZMeFZ5yk+Ek+jNr1+uwCd2tG89t6oTS5yVWpa6yy2es=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7/go.mod h1:mxV05U+4JiHqIpGqqYXOHLPKUC6bDXC44bsUhNjOEwY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6 h1:6tayEze2Y+hiL3kdnEUxSPsP+pJsUfwLSFspFl1ru9Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6/go.mod h1:qVNb/9IOVsLCZh0x2lnagrBwQ9fxajUpXS7OZfIsKn0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26/go.mod h1:Bd4C/4PkVGubtNe5iMXu5BNnaBi/9t/UsFspPt4ram8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 h1:f9RyWNtS8oH7cZlbn+/JNPpjUk5+5fLd5lM9M0i49Ys=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5/go.mod h1:h5CoMZV2VF297/VLhRhO1WF+XYWOzXo+4HsObA4HjBQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.27.7/go.mod h1:Jhu94omkrksnqX6Xs4Qo10eA1Fx+2NYKjZMU4GvZLp0=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4 h1:ZZKiHm4cN8IDDZ2kh8DTk+YnYBjVsiFdwf5FwVs//IQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4/go.mod h1:RTfjFUctf+Zyq8e4rgLXmz43+0kIoIXbENvrFtilumI=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7jz8TsskTTccbgc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.8/go.mod h1:GNIveDnP+aE3jujyUSH5aZ/rktsTM5EvtKnCqBZawdw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 h1:WzFol5Cd+yDxPAdnzTA5LmpHYSWinhmSj4rQChV0ee8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.4/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8/go.mod h1:44qFP1g7pfd+U+sQHLPalAPKnyfTZjJsYR4xIwsJy5o=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.9/go.mod h1:yyW88BEPXA2fGFyI2KCcZC3dNpiT0CZAHaF+i656/tQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/aws/aws-lambda-go v1.49.0 h1:z4VhTqkFZPM3xpEtTqWqRqsRH4TZBMJqTkRiBPYLqIQ=
|
||||
github.com/aws/aws-lambda-go v1.49.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.4 h1:GySzjhVvx0ERP6eyfAbAuAXLtAda5TEy19E5q5W8I9E=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.4/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.16 h1:XkruGnXX1nEZ+Nyo9v84TzsX+nj86icbFAeust6uo8A=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.16/go.mod h1:uCW7PNjGwZ5cOGZ5jr8vCWrYkGIhPoTNV23Q/tpHKzg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.69 h1:8B8ZQboRc3uaIKjshve/XlvJ570R7BKNy3gftSbS178=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.69/go.mod h1:gPME6I8grR1jCqBFEGthULiolzf/Sexq/Wy42ibKK9c=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.2 h1:Nl1i1+ZtpafH5DHr4LYpAgPwvWjDc3bfPlcZpLw3ffQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.2/go.mod h1:P9puVqIaBsnqbUcfDOIk0dsKaa7jckuRxwBbg6NzF9Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 h1:oQWSGexYasNpYp4epLGZxxjsDo8BMBh6iNWkTXQvkwk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31/go.mod h1:nc332eGUU+djP3vrMI6blS0woaCfHTe3KiSQUVTMRq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 h1:o1v1VFfPcDVlK3ll1L5xHsaQAFdNtZ5GXnNR7SwueC4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35/go.mod h1:rZUQNYMNG+8uZxz9FOerQJ+FceCiodXvixpeRtdESrU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 h1:R5b82ubO2NntENm3SAm0ADME+H630HomNJdgv+yZ3xw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35/go.mod h1:FuA+nmgMRfkzVKYDNEqQadvEMxtxl9+RLT9ribCwEMs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.35 h1:th/m+Q18CkajTw1iqx2cKkLCij/uz8NMwJFPK91p2ug=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.35/go.mod h1:dkJuf0a1Bc8HAA0Zm2MoTGm/WDC18Td9vSbrQ1+VqE8=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.3 h1:2FCJAT5wyPs5JjAFoLgaEB0MIiWvXiJ0T6PZiKDkJoo=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.3/go.mod h1:rUOhTo9+gtTYTMnGD+xiiks/2Z8vssPP+uSMNhJBbmI=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.5 h1:JSQ8/BuqZHaeE/kVgimmjHZ27wTKjYHujo6Oo6M1Iv4=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.5/go.mod h1:4iQhABsZl371BGh/fJq/qJcHzxoNX3kHTmhOXQWYhjU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.3 h1:VHPZakq2L7w+RLzV54LmQavbvheFaR2u1NomJRSEfcU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.3/go.mod h1:DX1e/lkbsAt0MkY3NgLYuH4jQvRfw8MYxTe9feR7aXM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.16 h1:TLsOzHW9zlJoMgjcKQI/7bolyv/DL0796y4NigWgaw8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.16/go.mod h1:mNoiR5qsO9TxXZ6psjjQ3M+Zz7hURFTumXHF+UKjyAU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16 h1:/ldKrPPXTC421bTNWrUIpq3CxwHwRI/kpc+jPUTJocM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16/go.mod h1:5vkf/Ws0/wgIMJDQbjI4p2op86hNW6Hie5QtebrDgT8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.16 h1:2HuI7vWKhFWsBhIr2Zq8KfFZT6xqaId2XXnXZjkbEuc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.16/go.mod h1:BrwWnsfbFtFeRjdx0iM1ymvlqDX1Oz68JsQaibX/wG8=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 h1:MmLCRqP4U4Cw9gJ4bNrCG0mWqEtBlmAVleyelcHARMU=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3/go.mod h1:AMPjK2YnRh0YgOID3PqhJA1BRNfXDfGOnSsKHtAe8yA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2 h1:T6Wu+8E2LeTUqzqQ/Bh1EoFNj1u4jUyveMgmTlu9fDU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2/go.mod h1:chSY8zfqmS0OnhZoO/hpPx/BHfAIL80m77HwhRLYScY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 h1:EU58LP8ozQDVroOEyAfcq0cGc5R/FTZjVoYJ6tvby3w=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4/go.mod h1:CrtOgCcysxMvrCoHnvNAD7PHWclmoFG78Q2xLK0KKcs=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2 h1:XB4z0hbQtpmBnb1FQYvKaCM7UsS6Y/u8jVBwIUGeCTk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2/go.mod h1:hwRpqkRxnQ58J9blRDrB4IanlXCpcKmsC83EhG77upg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 h1:nyLjs8sYJShFYj6aiyjCBI3EcLn1udWrQTjEF+SOXB0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21/go.mod h1:EhdxtZ+g84MSGrSrHzZiUm9PYiZkrADNja15wtRJSJo=
|
||||
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
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/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
||||
github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
|
@ -73,8 +62,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
|
@ -85,8 +74,9 @@ github.com/google/certificate-transparency-go v1.1.6/go.mod h1:0OJjOsOk+wj6aYQgP
|
|||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
|
@ -95,9 +85,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
|||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
|
||||
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
|
@ -111,17 +100,16 @@ github.com/letsencrypt/boulder v0.0.0-20240424004736-7ee5b469a6a9 h1:1WzTMHK3XWz
|
|||
github.com/letsencrypt/boulder v0.0.0-20240424004736-7ee5b469a6a9/go.mod h1:CtAtcgvLkR5K75e/MrIvIlMnCiC4/+Wu2U8ipjUDaSQ=
|
||||
github.com/letsencrypt/pkcs11key/v4 v4.0.0 h1:qLc/OznH7xMr5ARJgkZCCWk+EomQkiNTOoOF5LAgagc=
|
||||
github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/route53 v1.3.3 h1:16sTxbbRGm0zODz0p0aVHHIyTqtHzEn3j0s4dGzQvNI=
|
||||
github.com/libdns/route53 v1.3.3/go.mod h1:n1Xy55lpfdxMIx4CVWAM16GQac+/OZcnm1xBjMyhZAo=
|
||||
github.com/libdns/route53 v1.5.1 h1:dkdcc2CKY/EHBBzAKqE0Cko7MKR8uVJ3GvpzwKu/UKM=
|
||||
github.com/libdns/route53 v1.5.1/go.mod h1:joT4hKmaTNKHEwb7GmZ65eoDz1whTu7KKYPS8ZqIh6Q=
|
||||
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/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
|
@ -149,8 +137,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ=
|
||||
github.com/weppos/publicsuffix-go v0.30.3-0.20240411085455-21202160c2ed h1:2SVbw+/Q/si5J66tuM12841RSkH/3Q4TQpiUkUctBgY=
|
||||
|
@ -158,8 +146,8 @@ github.com/weppos/publicsuffix-go v0.30.3-0.20240411085455-21202160c2ed/go.mod h
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
|
@ -179,6 +167,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
@ -189,12 +179,13 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
|
|||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -207,14 +198,15 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
|||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -233,8 +225,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -254,14 +247,15 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package retryhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getBody(ctx context.Context, url string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "CRL-Monitor/0.1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http status %d (%s)", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// Get is a simple wrapper around http.Client.Do that will retry on a fixed backoff schedule
|
||||
func Get(ctx context.Context, url string) ([]byte, error) {
|
||||
// A fixed exponential backoff schedule. The final value is zero so that we don't sleep before
|
||||
// returning the final error.
|
||||
var err error
|
||||
for _, backoff := range []int{1000, 1250, 1562, 1953, 2441, 3051, 3814, 4768, 5960, 7450, 9313, 11641, 0} {
|
||||
var body []byte
|
||||
body, err = getBody(ctx, url)
|
||||
if err == nil {
|
||||
return body, nil
|
||||
}
|
||||
time.Sleep(time.Duration(backoff) * time.Millisecond)
|
||||
}
|
||||
return nil, err
|
||||
}
|
|
@ -20,6 +20,12 @@ type Storage struct {
|
|||
S3Client s3client
|
||||
}
|
||||
|
||||
// The parameters used to fetch a unique item from storage.
|
||||
type Key struct {
|
||||
Bucket, Object string
|
||||
Version *string
|
||||
}
|
||||
|
||||
func New(ctx context.Context) *Storage {
|
||||
cfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
|
@ -34,29 +40,32 @@ func New(ctx context.Context) *Storage {
|
|||
// The bucket and object names are required.
|
||||
// If version is nil, the current version is returned.
|
||||
// Returns the retrieved DER CRL bytes and what VersionID it was.
|
||||
func (s *Storage) Fetch(ctx context.Context, bucket, object string, version *string) ([]byte, string, error) {
|
||||
func (s *Storage) Fetch(ctx context.Context, key Key) ([]byte, string, error) {
|
||||
resp, err := s.S3Client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &object,
|
||||
VersionId: version,
|
||||
Bucket: &key.Bucket,
|
||||
Key: &key.Object,
|
||||
VersionId: key.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error retrieving CRL %s %s version %v: %w", bucket, object, version, err)
|
||||
return nil, "", fmt.Errorf("retrieving CRL %s %s version %v: %w", key.Bucket, key.Object, key.Version, err)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error reading CRL %s %s version %v: %w", bucket, object, version, err)
|
||||
return nil, "", fmt.Errorf("reading CRL %s %s version %v: %w", key.Bucket, key.Object, key.Version, err)
|
||||
}
|
||||
|
||||
return body, *resp.VersionId, err
|
||||
}
|
||||
|
||||
// Previous returns the previous version of a CRL shard, which can then be fetched.
|
||||
func (s *Storage) Previous(ctx context.Context, bucket, object, version string) (string, error) {
|
||||
func (s *Storage) Previous(ctx context.Context, key Key) (string, error) {
|
||||
if key.Version == nil {
|
||||
return "", fmt.Errorf("Previous called with no Version")
|
||||
}
|
||||
resp, err := s.S3Client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
||||
Bucket: &bucket,
|
||||
Prefix: &object,
|
||||
Bucket: &key.Bucket,
|
||||
Prefix: &key.Object,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -70,14 +79,14 @@ func (s *Storage) Previous(ctx context.Context, bucket, object, version string)
|
|||
break
|
||||
}
|
||||
|
||||
if v.VersionId != nil && *v.VersionId == version {
|
||||
if v.VersionId != nil && *v.VersionId == *key.Version {
|
||||
// This is the version of interest; select the next one
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!found || prevVersion == nil) && resp.IsTruncated != nil && *resp.IsTruncated {
|
||||
return "", fmt.Errorf("too many versions and pagination not implemented! %s %s %s", bucket, object, version)
|
||||
return "", fmt.Errorf("too many versions and pagination not implemented! %+v", key)
|
||||
}
|
||||
|
||||
if !found {
|
||||
|
|
|
@ -7,11 +7,12 @@ import (
|
|||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/letsencrypt/crl-monitor/storage"
|
||||
"github.com/letsencrypt/crl-monitor/storage/mock"
|
||||
)
|
||||
|
||||
func TestStorage(t *testing.T) {
|
||||
storage := mock.New(t, "somebucket", map[string][]mock.MockObject{
|
||||
mockStorage := mock.New(t, "somebucket", map[string][]mock.MockObject{
|
||||
"123/0.crl": {
|
||||
{VersionID: "111", Data: []byte{0xaa, 0xbb}},
|
||||
{VersionID: "222", Data: []byte{0xcc, 0xdd}},
|
||||
|
@ -56,7 +57,11 @@ func TestStorage(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
crl, version, err := storage.Fetch(context.Background(), "somebucket", tt.object, tt.version)
|
||||
crl, version, err := mockStorage.Fetch(context.Background(), storage.Key{
|
||||
Bucket: "somebucket",
|
||||
Object: tt.object,
|
||||
Version: tt.version,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedVer, version)
|
||||
require.Equal(t, tt.expectedCRL, crl)
|
||||
|
@ -87,7 +92,11 @@ func TestStorage(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
version, err := storage.Previous(context.Background(), "somebucket", tt.object, tt.version)
|
||||
version, err := mockStorage.Previous(context.Background(), storage.Key{
|
||||
Bucket: "somebucket",
|
||||
Object: tt.object,
|
||||
Version: &tt.version,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedVer, version)
|
||||
})
|
||||
|
@ -113,7 +122,11 @@ func TestStorage(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
version, err := storage.Previous(context.Background(), "somebucket", tt.object, tt.version)
|
||||
version, err := mockStorage.Previous(context.Background(), storage.Key{
|
||||
Bucket: "somebucket",
|
||||
Object: tt.object,
|
||||
Version: &tt.version,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "", version)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue