Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
|
a9ef40ef40 | |
|
455dc10734 | |
|
81e2be7c6b | |
|
dc4561673d | |
|
0be33f2139 | |
|
bb4fed2902 | |
|
00b5d41de5 | |
|
aa326b746d | |
|
68d6913607 | |
|
0beea6bbc8 | |
|
3bf80d78fb | |
|
c7de9b5377 | |
|
9720677383 | |
|
3c1b79fef5 | |
|
e3774ace37 | |
|
81c5e7da3f | |
|
3228b9f043 |
|
@ -17,10 +17,10 @@ jobs:
|
|||
steps:
|
||||
- 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@v6
|
||||
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
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
- uses: actions/checkout@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
|
||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
go-version: 1.24.2
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
|
|
@ -15,5 +15,5 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
go-version: 1.24.2
|
||||
- run: ./build-release.sh
|
||||
|
|
|
@ -1,20 +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:
|
||||
gosec:
|
||||
excludes:
|
||||
- G404
|
||||
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$
|
||||
|
|
53
README.md
53
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
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -79,7 +78,6 @@ func NewFromEnv(ctx context.Context) (*Checker, error) {
|
|||
}
|
||||
|
||||
baf := expiry.BoulderAPIFetcher{
|
||||
Client: http.DefaultClient,
|
||||
BaseURL: boulderBaseURL,
|
||||
}
|
||||
|
||||
|
@ -115,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)
|
||||
|
||||
|
@ -146,26 +183,36 @@ func (c *Checker) Check(ctx context.Context, bucket, object string, startingVers
|
|||
return err
|
||||
}
|
||||
|
||||
curKey := storage.Key{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
Version: &version,
|
||||
}
|
||||
// And the previous:
|
||||
prevVersion, err := c.storage.Previous(ctx, bucket, object, version)
|
||||
prevVersion, err := c.storage.Previous(ctx, curKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prevDER, _, err := c.storage.Fetch(ctx, bucket, object, &prevVersion)
|
||||
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 {
|
||||
|
@ -175,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)
|
||||
|
@ -186,7 +233,7 @@ 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
|
||||
|
@ -208,7 +255,7 @@ func (c *Checker) lookForSeenCerts(ctx context.Context, crl *x509.RevocationList
|
|||
|
||||
err = c.db.DeleteSerials(ctx, seenSerials)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to delete from db: %v", err))
|
||||
errs = append(errs, fmt.Errorf("deleting %d serials from DB: %v", len(seenSerials), err))
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -21,8 +21,10 @@ import (
|
|||
"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 (
|
||||
|
@ -152,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 {
|
||||
|
|
2
db/db.go
2
db/db.go
|
@ -120,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
|
||||
}
|
||||
|
|
62
go.mod
62
go.mod
|
@ -1,45 +1,45 @@
|
|||
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.47.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.6
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.18.4
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.40.1
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.77.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.5.1
|
||||
github.com/mholt/acmez/v3 v3.0.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.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.59 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 // 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.24.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 // 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.9 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
|
@ -54,12 +54,12 @@ require (
|
|||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.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
|
||||
|
|
116
go.sum
116
go.sum
|
@ -1,51 +1,51 @@
|
|||
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.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI=
|
||||
github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.9 h1:VZPDrbzdsU1ZxhyWrvROqLY0nxFWgMCAzhn/nYz3X48=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.9/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.6 h1:fqgqEKK5HaZVWLQoLiC9Q+xDlSp+1LYidp6ybGE2OGg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.6/go.mod h1:Ft+WLODzDQmCTHDvqAH1JfC2xxbZ0MxpZAcJqmE1LTQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.59 h1:9btwmrt//Q6JcSdgJOLI98sdr5p7tssS9yAsGe8aKP4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.59/go.mod h1:NM8fM6ovI3zak23UISdWidyZuI1ghNe2xjzUZAyT+08=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.18.4 h1:phn1rkXqpC2IMSrYF9lC99BnvctRo4ArDG5S8XcoJMA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.18.4/go.mod h1:8Nk8uFZ5rACaV8aiP31yQZPh9kasjSFMDj/GOrFT91E=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 h1:KwsodFKVQTlI5EyhRSugALzsV6mG/SGrdjlMXSZSdso=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28/go.mod h1:EY3APf9MzygVhKuPXAc5H+MkGb8k/DOSQjWS0LgkKqI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 h1:OIHj/nAhVzIXGzbAE+4XmZ8FPvro3THr6NlqErJc3wY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32/go.mod h1:LiBEsDo34OJXqdDlRGsilhlIiXR7DL+6Cx2f4p1EgzI=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.40.1 h1:JUvURAe0mNRzYd+1uTHEiojeyWtNPIQ5EXnDKfgKGUU=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.40.1/go.mod h1:FcMiR2AALpkrpik6JzbYu+iEfktzrs3XOq5Shk9nvik=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.20 h1:uUTR6EInXq1uf/Bz/0V9bc4jT3sKQ3UuFOjxeUVjeCM=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.20/go.mod h1:jpQRvf4Atm1US92/h+6U3NLeoygPdFid9OYw8awLEa8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.0 h1:kT2WeWcFySdYpPgyqJMSUE7781Qucjtn6wBvrgm9P+M=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.0/go.mod h1:WYH1ABybY7JK9TITPnk6ZlP7gQB8psI4c9qDmMsnLSA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.13 h1:eWoHfLIzYeUtJEuoUmD5PwTE+fLaIPN9NZ7UXd9CW0s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.13/go.mod h1:x5t8Ve0J7JK9VHKSPSRAdBrWAgr/5hH3UeCFMLoyUGQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 h1:OBsrtam3rk8NfBEq7OLOMm5HtQ9Yyw32X4UQMya/wjw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13/go.mod h1:3U4gFA5pmoCOja7aq4nSaIAGbaOHv2Yl2ug018cmC+Q=
|
||||
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.77.0 h1:RCOi1rDmLqOICym/6UeS2cqKED4T4m966w2rl1HfL+g=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.77.0/go.mod h1:VC4EKSHqT3nzOcU955VWHMGsQ+w67wfAUBSjC8NOo8U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 h1:/eE3DogBjYlvlbhd2ssWyeuovWunHLxfgw3s/OJa4GQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15/go.mod h1:2PCJYpi7EKeA5SkStAmZlF6fi0uUABuhtF8ILHjGc3Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 h1:M/zwXiL2iXUrHputuXgmO94TVNmcenPHxgLXLutodKE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14/go.mod h1:RVwIw3y/IqxC2YEXSIkAzRDdEU1iRabDPaYjpGCbCGQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 h1:TzeR06UCMUq+KA3bDkujxK1GVGy+G8qQN/QVYzGLkQE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14/go.mod h1:dspXf/oYWGWo6DEvj98wpaTeqt5+DMidZD0A9BYTizc=
|
||||
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=
|
||||
|
@ -62,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=
|
||||
|
@ -106,8 +106,8 @@ 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/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8=
|
||||
github.com/mholt/acmez/v3 v3.0.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
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=
|
||||
|
@ -180,8 +180,8 @@ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIi
|
|||
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/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
|
@ -199,14 +199,14 @@ 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/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.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=
|
||||
|
@ -226,8 +226,8 @@ 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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.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=
|
||||
|
@ -248,8 +248,8 @@ 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/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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=
|
||||
|
|
|
@ -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