From 5c3380bf0f3dea4895356ee0d08a6d57fc119601 Mon Sep 17 00:00:00 2001 From: Arjan H Date: Wed, 17 Aug 2022 20:36:39 +0200 Subject: [PATCH] Generate and store crl files regularly; set crl URL in certs Tweak the new crl-storer to save the crl files locally instead of in S3, with some housekeeping to keep only the last five versions. --- gui/apply-boulder | 4 ++ install | 4 +- patch.sh | 2 + patches/config_crl-storer.patch | 6 +- patches/config_crl-updater.patch | 17 +++-- patches/crl-storer_main.patch | 23 +++++++ patches/docker-compose.patch | 3 +- patches/storer_storer.patch | 115 +++++++++++++++++++++++++++++++ 8 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 patches/crl-storer_main.patch create mode 100644 patches/storer_storer.patch diff --git a/gui/apply-boulder b/gui/apply-boulder index 7e83743..57514c6 100755 --- a/gui/apply-boulder +++ b/gui/apply-boulder @@ -64,9 +64,13 @@ if [ "$PKI_DOMAIN_MODE" == "lockdown" ] || [ "$PKI_DOMAIN_MODE" == "whitelist" ] cat rate-limit-policies.yml | tr '\n' '\r' | sed -e "s|\(certificatesPerFQDNSet:.*must-staple.le.wtf: 10000\).*\(certificatesPerFQDNSetFast:.*\)|\1\n${REPLACEMENT}rateLimitsURL: http://$PKI_FQDN/rate-limits\n\2|" | tr '\r' '\n' > rate-limit-policies.yml.bak && mv rate-limit-policies.yml.bak rate-limit-policies.yml fi +perl -i -p0e "s/(\"maxNames\": [^\n]*).*?(\s+)(\"lifespanOCSP\":)/\1\2\"lifespanCRL\": \"2h\",\2\3/igs" config/ca-a.json +perl -i -p0e "s/(\"maxNames\": [^\n]*).*?(\s+)(\"lifespanOCSP\":)/\1\2\"lifespanCRL\": \"2h\",\2\3/igs" config/ca-b.json sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/aia/issuer/$PKI_ISSUER_NAME_ID\"|" config/ca-a.json sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/aia/issuer/$PKI_ISSUER_NAME_ID\"|" config/ca-b.json +sed -i -e "s|\"crlURL\": \".*\"|\"crlURL\": \"http://$PKI_FQDN/crl/$PKI_ISSUER_NAME_ID.crl\"|" config/ca-a.json +sed -i -e "s|\"crlURL\": \".*\"|\"crlURL\": \"http://$PKI_FQDN/crl/$PKI_ISSUER_NAME_ID.crl\"|" config/ca-b.json if [ "$PKI_EXTENDED_TIMEOUT" == "1" ]; then sed -i -e "s/\"timeout\": \"15s\"/\"timeout\": \"30s\"/" config/ca-a.json diff --git a/install b/install index a6c8f43..c9f4453 100755 --- a/install +++ b/install @@ -663,8 +663,6 @@ config_boulder() { sed -i -e "s|http://example.com/cps|http://$LABCA_FQDN/cps/|g" config/ca-b.json sed -i -e "s|1.2.3.4|1.3.6.1.4.1.44947.1.1.1|g" config/ca-a.json sed -i -e "s|1.2.3.4|1.3.6.1.4.1.44947.1.1.1|g" config/ca-b.json - perl -i -p0e "s/(\s+\"crlURL\":[^\n]*)//igs" config/ca-a.json - perl -i -p0e "s/(\s+\"crlURL\":[^\n]*)//igs" config/ca-b.json sed -i -e "s/ocspURL.Path = encodedReq/ocspURL.Path += encodedReq/" ocsp/helper/helper.go sed -i -e "s/\"dnsTimeout\": \".*\"/\"dnsTimeout\": \"3s\"/" config/ra.json sed -i -e "s/\"dnsTimeout\": \".*\"/\"dnsTimeout\": \"3s\"/" config/va.json @@ -695,7 +693,7 @@ config_boulder() { export PKI_DOMAIN_MODE=$(grep domain_mode $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') export PKI_LOCKDOWN_DOMAINS=$(grep lockdown $adminDir/data/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') export PKI_WHITELIST_DOMAINS=$(grep whitelist $adminDir/data/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') - export PKI_ISSUER_NAME_ID=$(grep issuer_name_id $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_ISSUER_NAME_ID=$(grep issuer_name_id $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/,//g' | sed -e 's/\"//g') if [ -z "$PKI_ISSUER_NAME_ID" ] && [ -e "$adminDir/data/issuer/ca-int.pem" ]; then local img=$(grep "&boulder_image" $boulderDir/docker-compose.yml | sed -e "s/.*boulder_image \(.*\)/\1/") eval img=$img diff --git a/patch.sh b/patch.sh index 93fd625..b7035d7 100755 --- a/patch.sh +++ b/patch.sh @@ -31,6 +31,8 @@ $SUDO patch -p1 < $cloneDir/patches/startservers.patch $SUDO patch -p1 < $cloneDir/patches/errors_errors.patch $SUDO patch -p1 < $cloneDir/patches/ratelimit_rate-limits.patch $SUDO patch -p1 < $cloneDir/patches/linter_linter.patch +$SUDO patch -p1 < $cloneDir/patches/crl-storer_main.patch +$SUDO patch -p1 < $cloneDir/patches/storer_storer.patch sed -i -e "s/berrors.RateLimitError(/berrors.RateLimitError(ra.rlPolicies.RateLimitsURL(), /g" ra/ra.go diff --git a/patches/config_crl-storer.patch b/patches/config_crl-storer.patch index 7aef7ac..fa83218 100644 --- a/patches/config_crl-storer.patch +++ b/patches/config_crl-storer.patch @@ -1,8 +1,8 @@ diff --git a/test/config/crl-storer.json b/test/config/crl-storer.json -index 61f14d795..4896803c4 100644 +index 61f14d79..a620896f 100644 --- a/test/config/crl-storer.json +++ b/test/config/crl-storer.json -@@ -15,9 +15,7 @@ +@@ -15,10 +15,9 @@ ] }, "issuerCerts": [ @@ -11,5 +11,7 @@ index 61f14d795..4896803c4 100644 - "/hierarchy/intermediate-cert-ecdsa-a.pem" + "/hierarchy/intermediate-cert-rsa-a.pem" ], ++ "localStorePath": "/wwwstatic/crl", "s3Endpoint": "http://localhost:7890", "s3Region": "us-west-1", + "s3Bucket": "lets-encrypt-crls", diff --git a/patches/config_crl-updater.patch b/patches/config_crl-updater.patch index 706c333..e8d3bc0 100644 --- a/patches/config_crl-updater.patch +++ b/patches/config_crl-updater.patch @@ -1,10 +1,15 @@ diff --git a/test/config/crl-updater.json b/test/config/crl-updater.json -index 02a57b25..a5752dec 100644 +index 875fc7ab..9c0ea6ed 100644 --- a/test/config/crl-updater.json +++ b/test/config/crl-updater.json -@@ -15,15 +15,13 @@ +@@ -14,16 +14,18 @@ + "serverAddress": "ca.boulder:9106", "timeout": "15s" }, ++ "crlStorerService": { ++ "serverAddress": "crl-storer.boulder:9109", ++ "timeout": "15s" ++ }, "issuerCerts": [ - "/hierarchy/intermediate-cert-rsa-a.pem", - "/hierarchy/intermediate-cert-rsa-b.pem", @@ -14,10 +19,12 @@ index 02a57b25..a5752dec 100644 - "numShards": 10, + "numShards": 1, "certificateLifetime": "2160h", - "updatePeriod": "6h", - "updateOffset": "9120s", +- "updatePeriod": "6h", +- "updateOffset": "9120s", - "maxParallelism": 10 ++ "updatePeriod": "1h", ++ "updateOffset": "120s", + "maxParallelism": 1 }, - + "syslog": { diff --git a/patches/crl-storer_main.patch b/patches/crl-storer_main.patch new file mode 100644 index 0000000..2e02252 --- /dev/null +++ b/patches/crl-storer_main.patch @@ -0,0 +1,23 @@ +diff --git a/cmd/crl-storer/main.go b/cmd/crl-storer/main.go +index 74d525c4..6a91ddab 100644 +--- a/cmd/crl-storer/main.go ++++ b/cmd/crl-storer/main.go +@@ -47,6 +47,9 @@ type Config struct { + // https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html. + S3CredsFile string + ++ // If this is set, store the files locally instead of using (fake) S3 ++ LocalStorePath string ++ + Features map[string]bool + } + +@@ -129,7 +132,7 @@ func main() { + } + s3client := s3.NewFromConfig(awsConfig, s3opts...) + +- csi, err := storer.New(issuers, s3client, c.CRLStorer.S3Bucket, scope, logger, clk) ++ csi, err := storer.New(issuers, s3client, c.CRLStorer.S3Bucket, c.CRLStorer.LocalStorePath, scope, logger, clk) + cmd.FailOnError(err, "Failed to create CRLStorer impl") + + serverMetrics := bgrpc.NewServerMetrics(scope) diff --git a/patches/docker-compose.patch b/patches/docker-compose.patch index 9fc3ed8..35c5c59 100644 --- a/patches/docker-compose.patch +++ b/patches/docker-compose.patch @@ -11,11 +11,12 @@ index 0cd8c8dd..36619190 100644 GOFLAGS: -mod=vendor # Go 1.18 turns off SHA-1 validation on CSRs (and certs, but that doesn't # affect us). It also turns off TLS 1.0 and TLS 1.1. Temporarily go back -@@ -17,6 +17,7 @@ services: +@@ -17,6 +17,8 @@ services: GODEBUG: x509sha1=1,tls10default=1 volumes: - .:/boulder:cached + - /home/labca/boulder_labca:/boulder/labca ++ - /home/labca/nginx_data/static:/wwwstatic - ./.gocache:/root/.cache/go-build:cached - ./.hierarchy:/hierarchy/:cached - ./.softhsm-tokens/:/var/lib/softhsm/tokens/:cached diff --git a/patches/storer_storer.patch b/patches/storer_storer.patch new file mode 100644 index 0000000..ab77e4e --- /dev/null +++ b/patches/storer_storer.patch @@ -0,0 +1,115 @@ +diff --git a/crl/storer/storer.go b/crl/storer/storer.go +index b2514eb4..77955b0c 100644 +--- a/crl/storer/storer.go ++++ b/crl/storer/storer.go +@@ -9,6 +9,9 @@ import ( + "fmt" + "io" + "math/big" ++ "os" ++ "path/filepath" ++ "sort" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" +@@ -32,6 +35,7 @@ type crlStorer struct { + cspb.UnimplementedCRLStorerServer + s3Client s3Putter + s3Bucket string ++ localStorePath string + issuers map[issuance.IssuerNameID]*issuance.Certificate + uploadCount *prometheus.CounterVec + sizeHistogram *prometheus.HistogramVec +@@ -44,6 +48,7 @@ func New( + issuers []*issuance.Certificate, + s3Client s3Putter, + s3Bucket string, ++ localStorePath string, + stats prometheus.Registerer, + log blog.Logger, + clk clock.Clock, +@@ -77,6 +82,7 @@ func New( + issuers: issuersByNameID, + s3Client: s3Client, + s3Bucket: s3Bucket, ++ localStorePath: localStorePath, + uploadCount: uploadCount, + sizeHistogram: sizeHistogram, + latencyHistogram: latencyHistogram, +@@ -153,15 +159,19 @@ func (cs *crlStorer) UploadCRL(stream cspb.CRLStorer_UploadCRLServer) error { + checksum := sha256.Sum256(crlBytes) + checksumb64 := base64.StdEncoding.EncodeToString(checksum[:]) + crlContentType := "application/pkix-crl" +- _, err = cs.s3Client.PutObject(stream.Context(), &s3.PutObjectInput{ +- Bucket: &cs.s3Bucket, +- Key: &filename, +- Body: bytes.NewReader(crlBytes), +- ChecksumAlgorithm: types.ChecksumAlgorithmSha256, +- ChecksumSHA256: &checksumb64, +- ContentType: &crlContentType, +- Metadata: map[string]string{"crlNumber": crlNumber.String()}, +- }) ++ if cs.localStorePath == "" { ++ _, err = cs.s3Client.PutObject(stream.Context(), &s3.PutObjectInput{ ++ Bucket: &cs.s3Bucket, ++ Key: &filename, ++ Body: bytes.NewReader(crlBytes), ++ ChecksumAlgorithm: types.ChecksumAlgorithmSha256, ++ ChecksumSHA256: &checksumb64, ++ ContentType: &crlContentType, ++ Metadata: map[string]string{"crlNumber": crlNumber.String()}, ++ }) ++ } else { ++ err = storeLocalFile(cs.localStorePath, issuer.NameID(), crlNumber, shardIdx, bytes.NewReader(crlBytes)) ++ } + if err != nil { + cs.uploadCount.WithLabelValues(issuer.Subject.CommonName, "failed") + cs.log.AuditErrf( +@@ -186,3 +196,46 @@ func (cs *crlStorer) UploadCRL(stream cspb.CRLStorer_UploadCRLServer) error { + + return stream.SendAndClose(&emptypb.Empty{}) + } ++ ++func storeLocalFile(path string, nameID issuance.IssuerNameID, crlNumber *big.Int, shardIdx int64, crlBytes io.Reader) error { ++ // Write the file ++ fn := fmt.Sprintf("%s%c%d-%d-%d.crl", path, os.PathSeparator, nameID, crlNumber, shardIdx) ++ out, err := os.Create(fn) ++ defer out.Close() ++ if err != nil { ++ return fmt.Errorf("creating local crl file: %w", err) ++ } ++ ++ _, err = io.Copy(out, crlBytes) ++ if err != nil { ++ return fmt.Errorf("storing crl locally: %w", err) ++ } ++ ++ // Create/update the symlink ++ ln := fmt.Sprintf("%s%c%d.crl", path, os.PathSeparator, nameID) ++ lntmp := ln + ".new" ++ fn = fmt.Sprintf("%d-%d-%d.crl", nameID, crlNumber, shardIdx) ++ ++ if err = os.Remove(lntmp); err != nil && !os.IsNotExist(err) { ++ return fmt.Errorf("removing symlink: %w", err) ++ } ++ if err = os.Symlink(fn, lntmp); err != nil { ++ return fmt.Errorf("creating symlink: %w", err) ++ } ++ if err = os.Rename(lntmp, ln); err != nil { ++ return fmt.Errorf("renaming symlink: %w", err) ++ } ++ ++ // Remove old files (keep last five) ++ pt := fmt.Sprintf("%s%c%d-*-%d.crl", path, os.PathSeparator, nameID, shardIdx) ++ matches, err := filepath.Glob(pt) ++ sort.Strings(matches) ++ for i := 0; i < len(matches)-5; i++ { ++ err := os.Remove(matches[i]) ++ if err != nil { ++ return fmt.Errorf("deleting file %s: %w", matches[i], err) ++ } ++ } ++ ++ return nil ++} +\ No newline at end of file