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:
|
jobs:
|
||||||
golangci:
|
golangci:
|
||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.2
|
go-version: 1.24.2
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# 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
|
version: latest
|
||||||
|
|
|
@ -13,12 +13,12 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.2
|
go-version: 1.24.2
|
||||||
- run: ./build-release.sh
|
- run: ./build-release.sh
|
||||||
# Upload to S3:
|
# Upload to S3:
|
||||||
- uses: aws-actions/configure-aws-credentials@v4
|
- uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
|
|
@ -11,15 +11,15 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.2
|
go-version: 1.24.2
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./...
|
run: go build -v ./...
|
||||||
|
|
|
@ -10,10 +10,10 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
try-release:
|
try-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.2
|
go-version: 1.24.2
|
||||||
- run: ./build-release.sh
|
- run: ./build-release.sh
|
||||||
|
|
|
@ -1,19 +1,37 @@
|
||||||
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- gofmt
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosimple
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- misspell
|
- misspell
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
- unused
|
||||||
- wastedassign
|
- wastedassign
|
||||||
linters-settings:
|
settings:
|
||||||
errcheck:
|
gosec:
|
||||||
ignore: fmt:[FS]?[Pp]rint*,io:Write,os:Remove,net/http:Write,net:Write,encoding/binary:Write
|
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)
|
[](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
|
## Architecture Diagram
|
||||||
|
|
||||||
|
@ -17,6 +67,7 @@ sequenceDiagram
|
||||||
loop timer
|
loop timer
|
||||||
activate churn
|
activate churn
|
||||||
churn->>ca: Issue certificate
|
churn->>ca: Issue certificate
|
||||||
|
churn->>ca: Fetch CRL
|
||||||
churn->>ca: Revoke certificate
|
churn->>ca: Revoke certificate
|
||||||
churn->>ddb: Store certificate metadata
|
churn->>ddb: Store certificate metadata
|
||||||
ddb->>churn: Get previous revoked serials
|
ddb->>churn: Get previous revoked serials
|
||||||
|
@ -31,10 +82,10 @@ sequenceDiagram
|
||||||
checker->>s3: Read current CRL
|
checker->>s3: Read current CRL
|
||||||
checker->>s3: Read previous CRL
|
checker->>s3: Read previous CRL
|
||||||
Note over checker: Alert if CRL<br />fails linting
|
Note over checker: Alert if CRL<br />fails linting
|
||||||
loop all removed serials
|
loop random selection of serials
|
||||||
checker->>ca: Get Certificate
|
checker->>ca: Get Certificate
|
||||||
end
|
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: Get revoked serials
|
||||||
checker->>ddb: Delete seen serials
|
checker->>ddb: Delete seen serials
|
||||||
deactivate checker
|
deactivate checker
|
||||||
|
|
|
@ -4,16 +4,17 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/core"
|
"github.com/letsencrypt/boulder/core"
|
||||||
"github.com/letsencrypt/boulder/crl/checker"
|
"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/earlyremoval"
|
||||||
"github.com/letsencrypt/crl-monitor/checker/expiry"
|
"github.com/letsencrypt/crl-monitor/checker/expiry"
|
||||||
|
@ -77,7 +78,6 @@ func NewFromEnv(ctx context.Context) (*Checker, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
baf := expiry.BoulderAPIFetcher{
|
baf := expiry.BoulderAPIFetcher{
|
||||||
Client: http.DefaultClient,
|
|
||||||
BaseURL: boulderBaseURL,
|
BaseURL: boulderBaseURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,18 +113,57 @@ type Checker struct {
|
||||||
issuers map[string]*x509.Certificate
|
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
|
// 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.
|
// certificates we're waiting for out of the database.
|
||||||
func (c *Checker) Check(ctx context.Context, bucket, object string, startingVersion *string) error {
|
func (c *Checker) Check(ctx context.Context, bucket, object string, startingVersion *string) error {
|
||||||
// Read the current CRL shard
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
crl, err := x509.ParseRevocationList(crlDER)
|
crl, err := x509.ParseRevocationList(crlDER)
|
||||||
if err != nil {
|
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)
|
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)
|
log.Printf("crl %d successfully linted", crl.Number)
|
||||||
|
|
||||||
// And the previous:
|
_, err = getIDP(crl)
|
||||||
prevVersion, err := c.storage.Previous(ctx, bucket, object, version)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
prev, err := x509.ParseRevocationList(prevDER)
|
prev, err := x509.ParseRevocationList(prevDER)
|
||||||
if err != nil {
|
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)
|
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)
|
earlyRemoved, err := earlyremoval.Check(ctx, c.fetcher, c.maxFetch, prev, crl)
|
||||||
if err != nil {
|
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 {
|
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.
|
// 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)
|
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 {
|
func (c *Checker) lookForSeenCerts(ctx context.Context, crl *x509.RevocationList) error {
|
||||||
unseenCerts, err := c.db.GetAllCerts(ctx)
|
unseenCerts, err := c.db.GetAllCerts(ctx)
|
||||||
if err != nil {
|
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 seenSerials [][]byte
|
||||||
|
var errs []error
|
||||||
for _, seen := range crl.RevokedCertificateEntries {
|
for _, seen := range crl.RevokedCertificateEntries {
|
||||||
if metadata, ok := unseenCerts[db.NewCertKey(seen.SerialNumber).SerialString()]; ok {
|
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)
|
seenSerials = append(seenSerials, metadata.SerialNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.db.DeleteSerials(ctx, seenSerials)
|
err = c.db.DeleteSerials(ctx, seenSerials)
|
||||||
if err != nil {
|
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
|
// 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
|
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)
|
issuerName := nameID(issuer)
|
||||||
shouldBeGood := fmt.Sprintf("%s/should-be-good.crl", issuerName)
|
shouldBeGood := fmt.Sprintf("%s/should-be-good.crl", issuerName)
|
||||||
earlyRemoval := fmt.Sprintf("%s/early-removal.crl", issuerName)
|
earlyRemoval := fmt.Sprintf("%s/early-removal.crl", issuerName)
|
||||||
shouldBeGoodIDP := fmt.Sprintf("http://idp/%s", shouldBeGood)
|
certificatesHaveCRLDP := fmt.Sprintf("%s/certificates-have-crldp.crl", issuerName)
|
||||||
earlyRemovalIDP := fmt.Sprintf("http://idp/%s", earlyRemoval)
|
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)
|
crl1der := testdata.MakeCRL(t, &testdata.CRL1, shouldBeGoodURL, issuer, key)
|
||||||
crl2der := testdata.MakeCRL(t, &testdata.CRL2, shouldBeGoodIDP, issuer, key)
|
crl2der := testdata.MakeCRL(t, &testdata.CRL2, shouldBeGoodURL, issuer, key)
|
||||||
crl3der := testdata.MakeCRL(t, &testdata.CRL3, earlyRemovalIDP, issuer, key)
|
crl3der := testdata.MakeCRL(t, &testdata.CRL3, earlyRemovalURL, issuer, key)
|
||||||
crl4der := testdata.MakeCRL(t, &testdata.CRL4, earlyRemovalIDP, 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{
|
data := map[string][]storagemock.MockObject{
|
||||||
shouldBeGood: {
|
shouldBeGood: {
|
||||||
|
@ -60,6 +64,16 @@ func TestCheck(t *testing.T) {
|
||||||
Data: crl3der,
|
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"
|
bucket := "crl-test"
|
||||||
|
|
||||||
|
@ -74,11 +88,12 @@ func TestCheck(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.Background()
|
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
|
serial := testdata.CRL1.RevokedCertificateEntries[0].SerialNumber
|
||||||
require.NoError(t, checker.db.AddCert(ctx, &x509.Certificate{SerialNumber: serial}, testdata.Now))
|
require.NoError(t, checker.db.AddCert(ctx, &x509.Certificate{SerialNumber: serial}, testdata.Now))
|
||||||
shouldNotBeSeen := big.NewInt(12345)
|
shouldNotBeSeen := big.NewInt(12345)
|
||||||
require.NoError(t, checker.db.AddCert(ctx, &x509.Certificate{SerialNumber: shouldNotBeSeen}, testdata.Now))
|
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))
|
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
|
// 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.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) {
|
func Test_nameID(t *testing.T) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/crl/checker"
|
"github.com/letsencrypt/boulder/crl/checker"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -68,9 +68,8 @@ func TestSample(t *testing.T) {
|
||||||
require.Empty(t, sample([]int{}, 999))
|
require.Empty(t, sample([]int{}, 999))
|
||||||
|
|
||||||
var data []int
|
var data []int
|
||||||
// Generate a random array for tests. Insecure RNG is fine.
|
// Generate a random array for tests.
|
||||||
// #nosec G404
|
length := 100 + rand.IntN(300)
|
||||||
length := 100 + rand.Intn(300)
|
|
||||||
for i := 0; i < length; i++ {
|
for i := 0; i < length; i++ {
|
||||||
data = append(data, i)
|
data = append(data, i)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,80 +5,41 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/crl-monitor/retryhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BoulderAPIFetcher struct {
|
type BoulderAPIFetcher struct {
|
||||||
Client *http.Client
|
|
||||||
BaseURL string
|
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
|
// FetchNotAfter downloads a certificate, parses it, and returns the NotAfter on
|
||||||
// it. It uses a non-acme path to download a certificate unauthenticated by
|
// 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.
|
// 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) {
|
func (baf *BoulderAPIFetcher) FetchNotAfter(ctx context.Context, serial *big.Int) (time.Time, error) {
|
||||||
// The baseURL is followed by a hex-encoded serial
|
// 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 {
|
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)
|
block, _ := pem.Decode(body)
|
||||||
if block == nil {
|
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)
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
if err != nil {
|
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
|
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))
|
res.Write([]byte(testCert))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
fetcher := BoulderAPIFetcher{BaseURL: testServer.URL + somePrefix, Client: http.DefaultClient}
|
fetcher := BoulderAPIFetcher{BaseURL: testServer.URL + somePrefix}
|
||||||
|
|
||||||
serial := new(big.Int)
|
serial := new(big.Int)
|
||||||
serial.SetString(serialhex, 16)
|
serial.SetString(serialhex, 16)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ func TestBoulderAPI(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
t.Run(tc.subdomain, func(t *testing.T) {
|
t.Run(tc.subdomain, func(t *testing.T) {
|
||||||
baseURL := fmt.Sprintf("https://%s.api.letsencrypt.org/get/cert", tc.subdomain)
|
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 := new(big.Int)
|
||||||
serial.SetString(tc.serial, 16)
|
serial.SetString(tc.serial, 16)
|
||||||
|
|
|
@ -70,6 +70,28 @@ var CRL5 = x509.RevocationList{
|
||||||
RevokedCertificateEntries: nil,
|
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) {
|
func MakeIssuer(t *testing.T) (*x509.Certificate, crypto.Signer) {
|
||||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -6,20 +6,25 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
mathrand "math/rand/v2"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/route53"
|
"github.com/libdns/route53"
|
||||||
"github.com/mholt/acmez"
|
"github.com/mholt/acmez/v3"
|
||||||
"github.com/mholt/acmez/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
"go.uber.org/zap"
|
|
||||||
|
|
||||||
|
"github.com/letsencrypt/boulder/crl/checker"
|
||||||
"github.com/letsencrypt/crl-monitor/cmd"
|
"github.com/letsencrypt/crl-monitor/cmd"
|
||||||
"github.com/letsencrypt/crl-monitor/db"
|
"github.com/letsencrypt/crl-monitor/db"
|
||||||
|
"github.com/letsencrypt/crl-monitor/retryhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -45,21 +50,20 @@ type Churner struct {
|
||||||
// `baseDomain` should be a domain name that the `dnsProvider` can create/delete
|
// `baseDomain` should be a domain name that the `dnsProvider` can create/delete
|
||||||
// records for. The certs will be issued from the CA at `acmeDirectory`.
|
// records for. The certs will be issued from the CA at `acmeDirectory`.
|
||||||
// The resulting serials are stored into `db`
|
// The resulting serials are stored into `db`
|
||||||
func New(baseDomain string, acmeDirectory string, dnsProvider certmagic.ACMEDNSProvider, db *db.Database, cutoff time.Time) (*Churner, error) {
|
func New(baseDomain string, acmeDirectory string, dnsProvider certmagic.DNSProvider, db *db.Database, cutoff time.Time) (*Churner, error) {
|
||||||
zapLogger, err := zap.NewProduction()
|
slogger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
acmeClient := acmez.Client{
|
acmeClient := acmez.Client{
|
||||||
Client: &acme.Client{
|
Client: &acme.Client{
|
||||||
Directory: acmeDirectory,
|
Directory: acmeDirectory,
|
||||||
Logger: zapLogger,
|
Logger: slogger,
|
||||||
},
|
},
|
||||||
ChallengeSolvers: map[string]acmez.Solver{
|
ChallengeSolvers: map[string]acmez.Solver{
|
||||||
acme.ChallengeTypeDNS01: &certmagic.DNS01Solver{
|
acme.ChallengeTypeDNS01: &certmagic.DNS01Solver{
|
||||||
DNSProvider: dnsProvider,
|
DNSManager: certmagic.DNSManager{
|
||||||
PropagationDelay: 60 * time.Second, // Route53 docs say 60 seconds in normal conditions
|
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) {
|
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
|
var certificates []acme.Certificate
|
||||||
for retry := 0; retry < 5; retry++ {
|
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 {
|
if err != nil {
|
||||||
log.Printf("error obtaining certificate on retry %d: %v", retry, err)
|
log.Printf("error obtaining certificate on retry %d: %v", retry, err)
|
||||||
time.Sleep(time.Second)
|
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
|
// Churn issues a certificate, revokes it, and stores the result in DynamoDB
|
||||||
func (c *Churner) Churn(ctx context.Context) error {
|
func (c *Churner) Churn(ctx context.Context) error {
|
||||||
// Generate either an ecdsa or rsa private key
|
certPrivateKey, err := randomKey()
|
||||||
certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -144,14 +154,47 @@ func (c *Churner) Churn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// certificates contains all the possible cert chains. We only care about
|
// certificates contains all the possible cert chains. We don't
|
||||||
// the cert, so we just take the first one and parse it.
|
// 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
|
firstChain := certificates[0].ChainPEM
|
||||||
block, _ := pem.Decode(firstChain)
|
block, remaining := pem.Decode(firstChain)
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
err = c.acmeClient.RevokeCertificate(ctx, c.acmeAccount, cert, c.acmeAccount.PrivateKey, acme.ReasonCessationOfOperation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,16 +204,19 @@ func (c *Churner) Churn(ctx context.Context) error {
|
||||||
return c.db.AddCert(ctx, cert, time.Now())
|
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.
|
// randDomains picks the domains to include on the certificate.
|
||||||
// We put a single domain which includes the current time and a random value.
|
// We put a single domain which includes the current time and a random value.
|
||||||
func randDomains(baseDomain string) []string {
|
func randDomains(baseDomain string) []string {
|
||||||
randomSuffix := make([]byte, 2)
|
domain := fmt.Sprintf("r%dz%x.%s", time.Now().Unix(), mathrand.Uint32(), baseDomain)
|
||||||
_, 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)
|
|
||||||
return []string{domain}
|
return []string{domain}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestRandDomains(t *testing.T) {
|
||||||
base := "revoked.invalid"
|
base := "revoked.invalid"
|
||||||
domains := randDomains(base)
|
domains := randDomains(base)
|
||||||
require.Len(t, domains, 1)
|
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)
|
second := randDomains(base)
|
||||||
require.NotEqual(t, domains, second, "Domains should be different each invocation")
|
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.
|
// That is the CertKey plus the revocation time today.
|
||||||
type CertMetadata struct {
|
type CertMetadata struct {
|
||||||
CertKey
|
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.
|
// 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
|
// AddCert inserts the metadata for monitoring
|
||||||
func (db *Database) AddCert(ctx context.Context, certificate *x509.Certificate, revocationTime time.Time) error {
|
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{
|
item, err := attributevalue.MarshalMap(CertMetadata{
|
||||||
CertKey: NewCertKey(certificate.SerialNumber),
|
CertKey: NewCertKey(certificate.SerialNumber),
|
||||||
RevocationTime: revocationTime,
|
RevocationTime: revocationTime,
|
||||||
|
CRLDistributionPoint: crlDistributionPoint,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -109,7 +120,7 @@ func (db *Database) GetAllCerts(ctx context.Context) (map[string]CertMetadata, e
|
||||||
|
|
||||||
certs := make(map[string]CertMetadata, len(certList))
|
certs := make(map[string]CertMetadata, len(certList))
|
||||||
for _, cert := range certList {
|
for _, cert := range certList {
|
||||||
certs[cert.CertKey.SerialString()] = cert
|
certs[cert.SerialString()] = cert
|
||||||
}
|
}
|
||||||
return certs, nil
|
return certs, nil
|
||||||
}
|
}
|
||||||
|
@ -141,16 +152,3 @@ func (db *Database) DeleteSerials(ctx context.Context, serialNumbers [][]byte) e
|
||||||
}
|
}
|
||||||
return nil
|
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"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -69,3 +70,57 @@ func smoketest(t *testing.T, handle *db.Database) {
|
||||||
}
|
}
|
||||||
require.Equal(t, expected, remaining)
|
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.
|
// run most tests outside the db package.
|
||||||
func TestIntegrationDynamoDB(t *testing.T) {
|
func TestIntegrationDynamoDB(t *testing.T) {
|
||||||
cfg := aws.NewConfig()
|
cfg := aws.NewConfig()
|
||||||
cfg.EndpointResolverWithOptions = aws.EndpointResolverWithOptionsFunc(db.StaticResolver("http://localhost:8000"))
|
|
||||||
cfg.Credentials = aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
|
cfg.Credentials = aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
|
||||||
return aws.Credentials{AccessKeyID: "Bogus", SecretAccessKey: "Bogus"}, nil
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
smoketest(t, handle)
|
smoketest(t, handle)
|
||||||
|
|
85
go.mod
85
go.mod
|
@ -1,63 +1,66 @@
|
||||||
module github.com/letsencrypt/crl-monitor
|
module github.com/letsencrypt/crl-monitor
|
||||||
|
|
||||||
go 1.22.0
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.22.2
|
toolchain go1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-lambda-go v1.46.0
|
github.com/aws/aws-lambda-go v1.49.0
|
||||||
github.com/aws/aws-sdk-go-v2 v1.26.1
|
github.com/aws/aws-sdk-go-v2 v1.36.4
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.10
|
github.com/aws/aws-sdk-go-v2/config v1.29.16
|
||||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13
|
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.2
|
||||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1
|
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.3
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2
|
||||||
github.com/caddyserver/certmagic v0.20.0
|
github.com/caddyserver/certmagic v0.21.7
|
||||||
github.com/letsencrypt/boulder v0.0.0-20240424004736-7ee5b469a6a9
|
github.com/letsencrypt/boulder v0.0.0-20240424004736-7ee5b469a6a9
|
||||||
github.com/libdns/route53 v1.3.3
|
github.com/libdns/route53 v1.5.1
|
||||||
github.com/mholt/acmez v1.2.0
|
github.com/mholt/acmez/v3 v3.1.2
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
go.uber.org/zap v1.27.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.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.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.1 // 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.5 // 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.5 // 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.0 // 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.5 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.35 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.20.4 // 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.11.2 // 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.3.7 // 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.9.6 // 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.11.7 // 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.17.5 // 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.40.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.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.28.6 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 // indirect
|
||||||
github.com/aws/smithy-go v1.20.2 // 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/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/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/kr/pretty v0.3.0 // indirect
|
||||||
github.com/libdns/libdns v0.2.2 // 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/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/weppos/publicsuffix-go v0.30.3-0.20240411085455-21202160c2ed // 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/zcrypto v0.0.0-20231219022726-a1f61fb1661c // indirect
|
||||||
github.com/zmap/zlint/v3 v3.6.0 // indirect
|
github.com/zmap/zlint/v3 v3.6.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.22.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
golang.org/x/net v0.24.0 // indirect
|
golang.org/x/crypto v0.37.0 // indirect
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
golang.org/x/tools v0.17.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
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // 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=
|
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/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.49.0 h1:z4VhTqkFZPM3xpEtTqWqRqsRH4TZBMJqTkRiBPYLqIQ=
|
||||||
github.com/aws/aws-lambda-go v1.46.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
|
github.com/aws/aws-lambda-go v1.49.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.36.4 h1:GySzjhVvx0ERP6eyfAbAuAXLtAda5TEy19E5q5W8I9E=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
github.com/aws/aws-sdk-go-v2 v1.36.4/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||||
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.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||||
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.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
|
||||||
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.29.16 h1:XkruGnXX1nEZ+Nyo9v84TzsX+nj86icbFAeust6uo8A=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.18.21/go.mod h1:+jPQiVPz1diRnjj6VGqWcLK6EzNmQ42l7J3OqGTLsSY=
|
github.com/aws/aws-sdk-go-v2/config v1.29.16/go.mod h1:uCW7PNjGwZ5cOGZ5jr8vCWrYkGIhPoTNV23Q/tpHKzg=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.10 h1:PS+65jThT0T/snC5WjyfHHyUgG+eBoupSDV+f838cro=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.69 h1:8B8ZQboRc3uaIKjshve/XlvJ570R7BKNy3gftSbS178=
|
||||||
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.17.69/go.mod h1:gPME6I8grR1jCqBFEGthULiolzf/Sexq/Wy42ibKK9c=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.20/go.mod h1:xtZnXErtbZ8YGXC3+8WfajpMBn5Ga/3ojZdxHq6iI8o=
|
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.2 h1:Nl1i1+ZtpafH5DHr4LYpAgPwvWjDc3bfPlcZpLw3ffQ=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.10 h1:qDZ3EA2lv1KangvQB6y258OssCHD0xvaGiEDkG4X/10=
|
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.2/go.mod h1:P9puVqIaBsnqbUcfDOIk0dsKaa7jckuRxwBbg6NzF9Y=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.10/go.mod h1:6t3sucOaYDwDssHQa0ojH1RpmVmF5/jArkye1b2FKMI=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 h1:oQWSGexYasNpYp4epLGZxxjsDo8BMBh6iNWkTXQvkwk=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13 h1:loQ4VSt3hTm9n8ST9jveArwmhqAc5aiRJXlxLPxCNTw=
|
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/feature/dynamodb/attributevalue v1.13.13/go.mod h1:RjdeQvzJuUf9jWj+ta+7l3VnVpDZ+RmtP/p+QdwRIpI=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 h1:o1v1VFfPcDVlK3ll1L5xHsaQAFdNtZ5GXnNR7SwueC4=
|
||||||
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/internal/configsources v1.3.35/go.mod h1:rZUQNYMNG+8uZxz9FOerQJ+FceCiodXvixpeRtdESrU=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 h1:R5b82ubO2NntENm3SAm0ADME+H630HomNJdgv+yZ3xw=
|
||||||
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/endpoints/v2 v2.6.35/go.mod h1:FuA+nmgMRfkzVKYDNEqQadvEMxtxl9+RLT9ribCwEMs=
|
||||||
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/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
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/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
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/endpoints/v2 v2.4.26/go.mod h1:vq86l7956VgFr0/FWQ2BWnK07QC3WYsepKzy33qqY5U=
|
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/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.3 h1:2FCJAT5wyPs5JjAFoLgaEB0MIiWvXiJ0T6PZiKDkJoo=
|
||||||
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/service/dynamodb v1.43.3/go.mod h1:rUOhTo9+gtTYTMnGD+xiiks/2Z8vssPP+uSMNhJBbmI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33/go.mod h1:zG2FcwjQarWaqXSCGpgcr3RSjZ6dHGguZSppUL0XR7Q=
|
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.5 h1:JSQ8/BuqZHaeE/kVgimmjHZ27wTKjYHujo6Oo6M1Iv4=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
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/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
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/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU=
|
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/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.3 h1:VHPZakq2L7w+RLzV54LmQavbvheFaR2u1NomJRSEfcU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1 h1:dZXY07Dm59TxAjJcUfNMJHLDI/gLMxTRZefn2jFAVsw=
|
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/dynamodb v1.31.1/go.mod h1:lVLqEtX+ezgtfalyJs7Peb0uv9dEpAQP5yuq2O26R44=
|
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/dynamodbstreams v1.20.4 h1:hSwDD19/e01z3pfyx+hDeX5T/0Sn+ZEnnTO5pVWKWx8=
|
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/dynamodbstreams v1.20.4/go.mod h1:61CuGwE7jYn0g2gl7K3qoT4vCY59ZQEixkPu8PN5IrE=
|
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/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
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/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.16 h1:2HuI7vWKhFWsBhIr2Zq8KfFZT6xqaId2XXnXZjkbEuc=
|
||||||
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/s3shared v1.18.16/go.mod h1:BrwWnsfbFtFeRjdx0iM1ymvlqDX1Oz68JsQaibX/wG8=
|
||||||
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/route53 v1.42.3 h1:MmLCRqP4U4Cw9gJ4bNrCG0mWqEtBlmAVleyelcHARMU=
|
||||||
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/route53 v1.42.3/go.mod h1:AMPjK2YnRh0YgOID3PqhJA1BRNfXDfGOnSsKHtAe8yA=
|
||||||
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/s3 v1.80.2 h1:T6Wu+8E2LeTUqzqQ/Bh1EoFNj1u4jUyveMgmTlu9fDU=
|
||||||
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/s3 v1.80.2/go.mod h1:chSY8zfqmS0OnhZoO/hpPx/BHfAIL80m77HwhRLYScY=
|
||||||
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/sso v1.25.4 h1:EU58LP8ozQDVroOEyAfcq0cGc5R/FTZjVoYJ6tvby3w=
|
||||||
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/sso v1.25.4/go.mod h1:CrtOgCcysxMvrCoHnvNAD7PHWclmoFG78Q2xLK0KKcs=
|
||||||
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/ssooidc v1.30.2 h1:XB4z0hbQtpmBnb1FQYvKaCM7UsS6Y/u8jVBwIUGeCTk=
|
||||||
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/ssooidc v1.30.2/go.mod h1:hwRpqkRxnQ58J9blRDrB4IanlXCpcKmsC83EhG77upg=
|
||||||
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/sts v1.33.21 h1:nyLjs8sYJShFYj6aiyjCBI3EcLn1udWrQTjEF+SOXB0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4 h1:ZZKiHm4cN8IDDZ2kh8DTk+YnYBjVsiFdwf5FwVs//IQ=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21/go.mod h1:EhdxtZ+g84MSGrSrHzZiUm9PYiZkrADNja15wtRJSJo=
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4/go.mod h1:RTfjFUctf+Zyq8e4rgLXmz43+0kIoIXbENvrFtilumI=
|
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7jz8TsskTTccbgc=
|
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
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/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/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.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
||||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
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 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
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/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
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.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.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.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.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-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
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/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 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
|
||||||
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
|
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.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
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/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 h1:qLc/OznH7xMr5ARJgkZCCWk+EomQkiNTOoOF5LAgagc=
|
||||||
github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
|
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 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
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.5.1 h1:dkdcc2CKY/EHBBzAKqE0Cko7MKR8uVJ3GvpzwKu/UKM=
|
||||||
github.com/libdns/route53 v1.3.3/go.mod h1:n1Xy55lpfdxMIx4CVWAM16GQac+/OZcnm1xBjMyhZAo=
|
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 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
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/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
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 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
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=
|
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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.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.2-0.20230730094716-a20f9abcc222/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ=
|
||||||
github.com/weppos/publicsuffix-go v0.30.3-0.20240411085455-21202160c2ed h1:2SVbw+/Q/si5J66tuM12841RSkH/3Q4TQpiUkUctBgY=
|
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/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 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
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.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
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 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
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=
|
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/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 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
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-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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
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.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.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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
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-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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
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.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.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/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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.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.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.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.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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
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.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.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.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.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-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.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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
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
|
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 {
|
func New(ctx context.Context) *Storage {
|
||||||
cfg, err := config.LoadDefaultConfig(ctx)
|
cfg, err := config.LoadDefaultConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -34,29 +40,32 @@ func New(ctx context.Context) *Storage {
|
||||||
// The bucket and object names are required.
|
// The bucket and object names are required.
|
||||||
// If version is nil, the current version is returned.
|
// If version is nil, the current version is returned.
|
||||||
// Returns the retrieved DER CRL bytes and what VersionID it was.
|
// 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{
|
resp, err := s.S3Client.GetObject(ctx, &s3.GetObjectInput{
|
||||||
Bucket: &bucket,
|
Bucket: &key.Bucket,
|
||||||
Key: &object,
|
Key: &key.Object,
|
||||||
VersionId: version,
|
VersionId: key.Version,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
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
|
return body, *resp.VersionId, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Previous returns the previous version of a CRL shard, which can then be fetched.
|
// 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{
|
resp, err := s.S3Client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
||||||
Bucket: &bucket,
|
Bucket: &key.Bucket,
|
||||||
Prefix: &object,
|
Prefix: &key.Object,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -70,14 +79,14 @@ func (s *Storage) Previous(ctx context.Context, bucket, object, version string)
|
||||||
break
|
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
|
// This is the version of interest; select the next one
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found || prevVersion == nil) && resp.IsTruncated != nil && *resp.IsTruncated {
|
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 {
|
if !found {
|
||||||
|
|
|
@ -7,11 +7,12 @@ import (
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/crl-monitor/storage"
|
||||||
"github.com/letsencrypt/crl-monitor/storage/mock"
|
"github.com/letsencrypt/crl-monitor/storage/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStorage(t *testing.T) {
|
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": {
|
"123/0.crl": {
|
||||||
{VersionID: "111", Data: []byte{0xaa, 0xbb}},
|
{VersionID: "111", Data: []byte{0xaa, 0xbb}},
|
||||||
{VersionID: "222", Data: []byte{0xcc, 0xdd}},
|
{VersionID: "222", Data: []byte{0xcc, 0xdd}},
|
||||||
|
@ -56,7 +57,11 @@ func TestStorage(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(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.NoError(t, err)
|
||||||
require.Equal(t, tt.expectedVer, version)
|
require.Equal(t, tt.expectedVer, version)
|
||||||
require.Equal(t, tt.expectedCRL, crl)
|
require.Equal(t, tt.expectedCRL, crl)
|
||||||
|
@ -87,7 +92,11 @@ func TestStorage(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(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.NoError(t, err)
|
||||||
require.Equal(t, tt.expectedVer, version)
|
require.Equal(t, tt.expectedVer, version)
|
||||||
})
|
})
|
||||||
|
@ -113,7 +122,11 @@ func TestStorage(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(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.Error(t, err)
|
||||||
require.Equal(t, "", version)
|
require.Equal(t, "", version)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue