diff --git a/build/.env.sample b/build/.env.sample new file mode 100644 index 0000000..d5ac98e --- /dev/null +++ b/build/.env.sample @@ -0,0 +1,2 @@ +# You can use a .env file to set variables used in the docker-compose.yml file, e.g.: +LABCA_FQDN=ca.test.internal diff --git a/build/Dockerfile-boulder b/build/Dockerfile-boulder index 3f7d612..2a4caa7 100644 --- a/build/Dockerfile-boulder +++ b/build/Dockerfile-boulder @@ -1,4 +1,4 @@ -FROM letsencrypt/boulder-tools:go1.22.3_2024-05-13 AS boulder-tools +FROM letsencrypt/boulder-tools:go1.22.3_2024-05-22 AS boulder-tools FROM ubuntu:focal @@ -6,6 +6,7 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ mariadb-client-core-10.3 \ + net-tools \ python3-pip \ rsyslog \ softhsm2 \ diff --git a/build/build.sh b/build/build.sh index e2f2f28..ae5ca19 100755 --- a/build/build.sh +++ b/build/build.sh @@ -8,7 +8,7 @@ TMP_DIR=$(pwd)/tmp rm -rf $TMP_DIR && mkdir -p $TMP_DIR/{admin,bin,logs,src} boulderDir=$TMP_DIR/src -boulderTag="release-2024-05-20" +boulderTag="release-2024-06-10" boulderUrl="https://github.com/letsencrypt/boulder/" cloneDir=$(pwd)/.. diff --git a/build/docker-compose.yml b/build/docker-compose.yml index 33503bd..4aaac62 100644 --- a/build/docker-compose.yml +++ b/build/docker-compose.yml @@ -51,6 +51,7 @@ services: depends_on: - bmysql - bconsul + - bpkilint - control entrypoint: labca/entrypoint.sh working_dir: &boulder_working_dir /opt/boulder @@ -178,6 +179,13 @@ services: command: ./control.sh restart: always + bpkilint: + image: ghcr.io/digicert/pkilint:v0.10.1 + networks: + bouldernet: + ipv4_address: 10.77.77.9 + command: "gunicorn -w 1 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:80 pkilint.rest:app" + volumes: dbdata: nginx_conf: diff --git a/build/tmp.patch b/build/tmp.patch index fd0c8e6..69ab8a1 100644 --- a/build/tmp.patch +++ b/build/tmp.patch @@ -1,5 +1,5 @@ diff --git a/docker-compose.yml b/docker-compose.yml -index 08d29d67c..33503bdfd 100644 +index 1bffccd61..f7860d6f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: @@ -27,15 +27,15 @@ index 08d29d67c..33503bdfd 100644 networks: bouldernet: ipv4_address: 10.77.77.77 -@@ -52,6 +51,7 @@ services: - depends_on: +@@ -53,6 +52,7 @@ services: - bmysql - bconsul + - bpkilint + - control entrypoint: labca/entrypoint.sh working_dir: &boulder_working_dir /opt/boulder logging: -@@ -62,12 +62,11 @@ services: +@@ -63,12 +63,11 @@ services: restart: always bsetup: @@ -52,7 +52,7 @@ index 08d29d67c..33503bdfd 100644 entrypoint: labca/certs/generate.sh working_dir: *boulder_working_dir profiles: -@@ -101,34 +100,39 @@ services: +@@ -102,34 +101,39 @@ services: bconsul: image: hashicorp/consul:1.15.4 @@ -102,7 +102,7 @@ index 08d29d67c..33503bdfd 100644 logging: driver: "json-file" options: -@@ -145,27 +149,27 @@ services: +@@ -146,27 +150,27 @@ services: - 80:80 - 443:443 volumes: @@ -145,7 +145,7 @@ index 08d29d67c..33503bdfd 100644 expose: - 3030 environment: -@@ -176,6 +180,15 @@ services: +@@ -184,6 +188,15 @@ services: volumes: dbdata: diff --git a/commander b/commander index 2e6789a..30011fc 100755 --- a/commander +++ b/commander @@ -39,10 +39,11 @@ read txt case $txt in "docker-restart") cd /opt/boulder - COMPOSE_HTTP_TIMEOUT=120 docker compose restart boulder bmysql bconsul gui nginx &>>$LOGFILE + COMPOSE_HTTP_TIMEOUT=120 docker compose restart boulder bmysql bconsul bpkilint gui nginx &>>$LOGFILE sleep 45 wait_up $PS_MYSQL &>>$LOGFILE wait_up $PS_CONSUL 2 &>>$LOGFILE + wait_up $PS_PKILINT 2 &>>$LOGFILE wait_up $PS_LABCA &>>$LOGFILE wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$LOGFILE ;; @@ -146,7 +147,8 @@ case $txt in labca=$(docker inspect $(docker ps --format "{{.Names}}" | grep -- labca-gui) | grep -i started | grep -v depends_on | sed -e "s/[^:]*:\(.*\)/\1/" | sed -e "s/.*\"\(.*\)\".*/\1/") mysql=$(docker inspect $(docker ps --format "{{.Names}}" | grep -- -bmysql-) | grep -i started | grep -v depends_on | sed -e "s/[^:]*:\(.*\)/\1/" | sed -e "s/.*\"\(.*\)\".*/\1/") consul=$(docker inspect $(docker ps --format "{{.Names}}" | grep -- -bconsul-) | grep -i started | grep -v depends_on | sed -e "s/[^:]*:\(.*\)/\1/" | sed -e "s/.*\"\(.*\)\".*/\1/") - echo "$nginx|$svc|$boulder|$labca|$mysql|$consul" + pkilint=$(docker inspect $(docker ps --format "{{.Names}}" | grep -- -bpkilint-) | grep -i started | grep -v depends_on | sed -e "s/[^:]*:\(.*\)/\1/" | sed -e "s/.*\"\(.*\)\".*/\1/") + echo "$nginx|$svc|$boulder|$labca|$mysql|$consul|$pkilint" exit 0 ;; "log-uptime") @@ -171,26 +173,29 @@ case $txt in ;; "boulder-start") cd /opt/boulder - COMPOSE_HTTP_TIMEOUT=120 docker compose up -d bmysql bconsul + COMPOSE_HTTP_TIMEOUT=120 docker compose up -d bmysql bconsul bpkilint wait_up $PS_MYSQL &>>$LOGFILE wait_up $PS_CONSUL 2 &>>$LOGFILE + wait_up $PS_PKILINT 2 &>>$LOGFILE COMPOSE_HTTP_TIMEOUT=120 docker compose up -d boulder wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$LOGFILE ;; "boulder-stop") cd /opt/boulder docker compose stop boulder - docker compose stop bmysql bconsul + docker compose stop bmysql bconsul bpkilint wait_down $PS_MYSQL &>>$LOGFILE wait_down $PS_CONSUL &>>$LOGFILE + wait_down $PS_PKILINT &>>$LOGFILE wait_down $PS_BOULDER &>>$LOGFILE ;; "boulder-restart") cd /opt/boulder - COMPOSE_HTTP_TIMEOUT=120 docker compose restart boulder bmysql bconsul &>>$LOGFILE + COMPOSE_HTTP_TIMEOUT=120 docker compose restart boulder bmysql bconsul bpkilint &>>$LOGFILE sleep 30 wait_up $PS_MYSQL &>>$LOGFILE wait_up $PS_CONSUL 2 &>>$LOGFILE + wait_up $PS_PKILINT 2 &>>$LOGFILE wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$LOGFILE ;; "labca-restart") @@ -211,6 +216,12 @@ case $txt in COMPOSE_HTTP_TIMEOUT=120 docker compose restart bconsul set -e ;; +"pkilint-restart") + cd /opt/boulder + set +e + COMPOSE_HTTP_TIMEOUT=120 docker compose restart bpkilint + set -e + ;; "log-backups") ls -1tr /opt/backup || /bin/true exit 0 diff --git a/gui/apply-boulder b/gui/apply-boulder index c338e23..dfa9535 100755 --- a/gui/apply-boulder +++ b/gui/apply-boulder @@ -74,6 +74,8 @@ if ([ "$PKI_DOMAIN_MODE" == "lockdown" ] && [ "$PKI_LOCKDOWN_DOMAINS" != "" ]) | perl -i -p0e "s/(\"badResultsOnly\":[^\n]*).*?(\s+)(\"checkPeriod\":)/\1\2\"skipForbiddenDomains\": true,\2\3/igs" config/cert-checker.json perl -i -p0e "s/(\"ignoredLints\": \[).*?(\s+)(\"w_subject_common_name_included\")/\1\2\"e_dnsname_not_valid_tld\",\2\3/igs" config/cert-checker.json perl -i -p0e "s/(\"ignoredLints\": \[).*?(\s+)(\"w_subject_common_name_included\")/\1\2\"e_dnsname_not_valid_tld\",\2\3/igs" config/ca.json + + perl -i -p0e "s/(\"SubscriberKeyUsageValidator:cabf.serverauth.subscriber_rsa_digitalsignature_and_keyencipherment_present\",).*(\])/\1\n \"GeneralNameDnsNameInternalDomainNameValidator:cabf.internal_domain_name\",\n \"GeneralNameUriInternalDomainNameValidator:cabf.internal_domain_name\",\n\2/igs" config/zlint.toml fi [ -e ../test/hostname-policy.yaml ] && cp ../test/hostname-policy.yaml ./ || true @@ -161,7 +163,7 @@ rm -f config/orphan-finder.json rm -f config/ca-a.json rm -f config/ca-b.json -sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/aia/issuer/$PKI_ISSUER_NAME_ID\"|" config/ca.json +sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/certs/ca-int.pem\"|" config/ca.json sed -i -e "s|\"ocspURL\": \".*\"|\"ocspURL\": \"http://$PKI_FQDN/ocsp/\"|" config/ca.json sed -i -e "s|\"crlURLBase\": \".*\"|\"crlURLBase\": \"http://$PKI_FQDN/crl/\"|" config/ca.json @@ -175,6 +177,10 @@ if [ "$PKI_EXTENDED_TIMEOUT" == "1" ]; then sed -i -e "s/\"timeout\": \"20s\"/\"timeout\": \"40s\"/" config/ra.json sed -i -e "s/\"timeout\": \"15s\"/\"timeout\": \"30s\"/" config/crl-storer.json sed -i -e "s/\"timeout\": \"15s\"/\"timeout\": \"30s\"/" config/crl-updater.json + + sed -i -e "s/pkilint_timeout = .*/pkilint_timeout = 30000000000 # 30 seconds/" config/zlint.toml +else + sed -i -e "s/pkilint_timeout = .*/pkilint_timeout = 10000000000 # 10 seconds/" config/zlint.toml fi sed -i -e "s/\"timeout\": \"1s\"/\"timeout\": \"5s\"/" config/health-checker.json diff --git a/install b/install index 7095854..46c823a 100755 --- a/install +++ b/install @@ -30,7 +30,7 @@ dockerComposeVersion="v2.5.0" labcaUrl="https://github.com/hakwerk/labca/" boulderUrl="https://github.com/letsencrypt/boulder/" -boulderTag="release-2024-05-20" +boulderTag="release-2024-06-10" # Feature flags flag_skip_redis=true @@ -773,6 +773,7 @@ startup() { wait_down $PS_NGINX &>>$installLog || true wait_down $PS_MYSQL &>>$installLog || true wait_down $PS_CONSUL &>>$installLog || true + wait_down $PS_PKILINT &>>$installLog || true wait_down $PS_LABCA &>>$installLog || true wait_down $PS_CONTROL &>>$installLog || true wait_down $PS_BOULDER &>>$installLog || true @@ -807,6 +808,7 @@ startup() { wait_up $PS_NGINX &>>$installLog || true wait_up $PS_MYSQL &>>$installLog || true wait_up $PS_CONSUL 2 &>>$installLog || true + wait_up $PS_PKILINT &>>$installLog || true wait_up $PS_LABCA &>>$installLog || true wait_up $PS_CONTROL &>>$installLog || true docker exec -i labca-bmysql-1 mysql_upgrade &>>$installLog diff --git a/patch.sh b/patch.sh index 2a7c402..b28624d 100755 --- a/patch.sh +++ b/patch.sh @@ -58,6 +58,7 @@ $SUDO patch -p1 < $cloneDir/patches/updater_continuous.patch $SUDO patch -p1 < $cloneDir/patches/va_http.patch $SUDO patch -p1 < $cloneDir/patches/va_va.patch $SUDO patch -p1 < $cloneDir/patches/wfe2_main.patch +$SUDO patch -p1 < $cloneDir/patches/wfe2_wfe.patch sed -i -e "s|./test|./labca|" start.py diff --git a/patches/ca_ca.patch b/patches/ca_ca.patch index baf2219..869fb76 100644 --- a/patches/ca_ca.patch +++ b/patches/ca_ca.patch @@ -1,8 +1,8 @@ diff --git a/ca/ca.go b/ca/ca.go -index 252fa87d9..6630b731c 100644 +index 239a5a4c3..775ffa8a4 100644 --- a/ca/ca.go +++ b/ca/ca.go -@@ -118,10 +118,10 @@ func makeIssuerMaps(issuers []*issuance.Issuer) (issuerMaps, error) { +@@ -160,10 +160,10 @@ func makeIssuerMaps(issuers []*issuance.Issuer) (issuerMaps, error) { } } if i, ok := issuersByAlg[x509.ECDSA]; !ok || len(i) == 0 { diff --git a/patches/ca_crl.patch b/patches/ca_crl.patch index 5a2cf27..50f7b2f 100644 --- a/patches/ca_crl.patch +++ b/patches/ca_crl.patch @@ -1,8 +1,8 @@ diff --git a/ca/crl.go b/ca/crl.go -index 35bf4c07d..36316235e 100644 +index 5937046fe..15c144984 100644 --- a/ca/crl.go +++ b/ca/crl.go -@@ -122,8 +122,10 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error +@@ -132,8 +132,10 @@ func (ci *crlImpl) GenerateCRL(stream grpc.BidiStreamingServer[capb.GenerateCRLR builder = strings.Builder{} } } diff --git a/patches/config_ca.patch b/patches/config_ca.patch deleted file mode 100644 index 14ad5e3..0000000 --- a/patches/config_ca.patch +++ /dev/null @@ -1,42 +0,0 @@ -diff --git a/test/config/ca.json b/test/config/ca.json -index cbb84f385..ec28cd37d 100644 ---- a/test/config/ca.json -+++ b/test/config/ca.json -@@ -58,18 +58,6 @@ - "maxValidityBackdate": "1h5m" - }, - "issuers": [ -- { -- "useForRSALeaves": false, -- "useForECDSALeaves": true, -- "issuerURL": "http://ca.example.org:4502/int-ecdsa-a", -- "ocspURL": "http://ca.example.org:4002/", -- "crlURLBase": "http://ca.example.org:4501/ecdsa-a/", -- "location": { -- "configFile": "test/certs/webpki/int-ecdsa-a.pkcs11.json", -- "certFile": "test/certs/webpki/int-ecdsa-a.cert.pem", -- "numSessions": 2 -- } -- }, - { - "useForRSALeaves": true, - "useForECDSALeaves": true, -@@ -81,18 +69,6 @@ - "certFile": "test/certs/webpki/int-rsa-a.cert.pem", - "numSessions": 2 - } -- }, -- { -- "useForRSALeaves": false, -- "useForECDSALeaves": false, -- "issuerURL": "http://ca.example.org:4502/int-rsa-b", -- "ocspURL": "http://ca.example.org:4003/", -- "crlURLBase": "http://ca.example.org:4501/rsa-b/", -- "location": { -- "configFile": "test/certs/webpki/int-rsa-b.pkcs11.json", -- "certFile": "test/certs/webpki/int-rsa-b.cert.pem", -- "numSessions": 2 -- } - } - ], - "ignoredLints": [ diff --git a/patches/config_wfe2.patch b/patches/config_wfe2.patch index 9776650..5bf7a35 100644 --- a/patches/config_wfe2.patch +++ b/patches/config_wfe2.patch @@ -1,8 +1,16 @@ diff --git a/test/config/wfe2.json b/test/config/wfe2.json -index c0093044..e8ba4263 100644 +index 05d46fe95..c0e4a2a27 100644 --- a/test/config/wfe2.json +++ b/test/config/wfe2.json -@@ -79,26 +79,6 @@ +@@ -12,6 +12,7 @@ + "debugAddr": ":8013", + "directoryCAAIdentity": "happy-hacker-ca.invalid", + "directoryWebsite": "https://github.com/letsencrypt/boulder", ++ "hostnamePolicyFile": "test/hostname-policy.yaml", + "legacyKeyIDPrefix": "http://boulder.service.consul:4000/reg/", + "goodkey": { + "blockedKeyFile": "test/example-blocked-keys.yaml" +@@ -79,26 +80,6 @@ [ "test/certs/webpki/int-rsa-a.cert.pem", "test/certs/webpki/root-rsa.cert.pem" diff --git a/patches/docker-compose.patch b/patches/docker-compose.patch index 5198d5b..3643209 100644 --- a/patches/docker-compose.patch +++ b/patches/docker-compose.patch @@ -1,5 +1,5 @@ diff --git a/docker-compose.yml b/docker-compose.yml -index 79ed8c4e4..08d29d67c 100644 +index 79ed8c4e4..3562b8fb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,4 @@ @@ -26,14 +26,14 @@ index 79ed8c4e4..08d29d67c 100644 networks: bouldernet: ipv4_address: 10.77.77.77 -@@ -48,20 +51,24 @@ services: +@@ -48,20 +51,25 @@ services: - 4003:4003 # OCSP depends_on: - bmysql - - bproxysql - bconsul - - bjaeger -- - bpkilint + - bpkilint - entrypoint: test/entrypoint.sh - working_dir: &boulder_working_dir /boulder + entrypoint: labca/entrypoint.sh @@ -59,7 +59,7 @@ index 79ed8c4e4..08d29d67c 100644 working_dir: *boulder_working_dir profiles: # Adding a profile to this container means that it won't be started by a -@@ -71,6 +78,8 @@ services: +@@ -71,6 +79,8 @@ services: bmysql: image: mariadb:10.5 @@ -68,7 +68,7 @@ index 79ed8c4e4..08d29d67c 100644 networks: bouldernet: aliases: -@@ -84,46 +93,89 @@ services: +@@ -84,46 +94,96 @@ services: # small. command: mysqld --bind-address=0.0.0.0 --slow-query-log --log-output=TABLE --log-queries-not-using-indexes=ON logging: @@ -136,16 +136,11 @@ index 79ed8c4e4..08d29d67c 100644 + max-size: "500k" + max-file: "5" + restart: always - -- bpkilint: -- image: ghcr.io/digicert/pkilint:v0.10.1 ++ + nginx: + image: nginx:1.26.0 + restart: always - networks: -- bouldernet: -- ipv4_address: 10.77.77.9 -- command: "gunicorn -w 8 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:80 pkilint.rest:app" ++ networks: + - bouldernet + ports: + - 80:80 @@ -179,6 +174,14 @@ index 79ed8c4e4..08d29d67c 100644 + working_dir: /opt/labca + command: ./control.sh + restart: always + + bpkilint: + image: ghcr.io/digicert/pkilint:v0.10.1 + networks: + bouldernet: + ipv4_address: 10.77.77.9 +- command: "gunicorn -w 8 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:80 pkilint.rest:app" ++ command: "gunicorn -w 1 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:80 pkilint.rest:app" + +volumes: + dbdata: diff --git a/patches/expiration-mailer_main.patch b/patches/expiration-mailer_main.patch index 12d6282..ba0f065 100644 --- a/patches/expiration-mailer_main.patch +++ b/patches/expiration-mailer_main.patch @@ -1,5 +1,5 @@ diff --git a/cmd/expiration-mailer/main.go b/cmd/expiration-mailer/main.go -index e1014ebab..4cf2fdbfc 100644 +index 46fa939a6..03a9e6ae3 100644 --- a/cmd/expiration-mailer/main.go +++ b/cmd/expiration-mailer/main.go @@ -23,6 +23,7 @@ import ( @@ -33,7 +33,7 @@ index e1014ebab..4cf2fdbfc 100644 if err != nil { m.log.Debugf("skipping invalid email %q: %s", address, err) continue -@@ -698,6 +703,11 @@ type Config struct { +@@ -701,6 +706,11 @@ type Config struct { TLS cmd.TLSConfig SAService *cmd.GRPCClientConfig @@ -45,7 +45,7 @@ index e1014ebab..4cf2fdbfc 100644 // Path to a file containing a list of trusted root certificates for use // during the SMTP connection (as opposed to the gRPC connections). SMTPTrustedRootFile string -@@ -851,6 +861,30 @@ func main() { +@@ -854,6 +864,30 @@ func main() { cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") sac := sapb.NewStorageAuthorityClient(conn) @@ -76,7 +76,7 @@ index e1014ebab..4cf2fdbfc 100644 var smtpRoots *x509.CertPool if c.Mailer.SMTPTrustedRootFile != "" { pem, err := os.ReadFile(c.Mailer.SMTPTrustedRootFile) -@@ -886,6 +920,7 @@ func main() { +@@ -889,6 +923,7 @@ func main() { c.Mailer.Username, smtpPassword, smtpRoots, diff --git a/patches/linter_linter.patch b/patches/linter_linter.patch index accf542..4ea459b 100644 --- a/patches/linter_linter.patch +++ b/patches/linter_linter.patch @@ -1,8 +1,8 @@ diff --git a/linter/linter.go b/linter/linter.go -index 07ac8b029..bd0abd93e 100644 +index e9bf33b85..e88cc6b7f 100644 --- a/linter/linter.go +++ b/linter/linter.go -@@ -199,10 +199,21 @@ func makeIssuer(realIssuer *x509.Certificate, lintSigner crypto.Signer) (*x509.C +@@ -200,10 +200,21 @@ func makeIssuer(realIssuer *x509.Certificate, lintSigner crypto.Signer) (*x509.C SubjectKeyId: realIssuer.SubjectKeyId, URIs: realIssuer.URIs, UnknownExtKeyUsage: realIssuer.UnknownExtKeyUsage, diff --git a/patches/policy_pa.patch b/patches/policy_pa.patch index 4288a17..476484a 100644 --- a/patches/policy_pa.patch +++ b/patches/policy_pa.patch @@ -1,5 +1,5 @@ diff --git a/policy/pa.go b/policy/pa.go -index d872d5cbe..faa052d6c 100644 +index ce7857a7d..ef29cb60e 100644 --- a/policy/pa.go +++ b/policy/pa.go @@ -32,6 +32,9 @@ type AuthorityImpl struct { @@ -49,12 +49,12 @@ index d872d5cbe..faa052d6c 100644 // - exactly equal to an IANA registered TLD // // It does NOT ensure that the domain is absent from any PA blocked lists. --func ValidNonWildcardDomain(domain string) error { +-func validNonWildcardDomain(domain string) error { +func (pa *AuthorityImpl) ValidNonWildcardDomain(domain string, isContact bool) error { if domain == "" { return errEmptyName } -@@ -235,7 +253,9 @@ func ValidNonWildcardDomain(domain string) error { +@@ -235,7 +253,9 @@ func validNonWildcardDomain(domain string) error { return errTooManyLabels } if len(labels) < 2 { @@ -65,7 +65,7 @@ index d872d5cbe..faa052d6c 100644 } for _, label := range labels { // Check that this is a valid LDH Label: "A string consisting of ASCII -@@ -279,6 +299,14 @@ func ValidNonWildcardDomain(domain string) error { +@@ -279,6 +299,14 @@ func validNonWildcardDomain(domain string) error { } } @@ -80,14 +80,14 @@ index d872d5cbe..faa052d6c 100644 // Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD. icannTLD, err := iana.ExtractSuffix(domain) if err != nil { -@@ -294,9 +322,9 @@ func ValidNonWildcardDomain(domain string) error { +@@ -294,9 +322,9 @@ func validNonWildcardDomain(domain string) error { // ValidDomain checks that a domain is valid and that it doesn't contain any // invalid wildcard characters. It does NOT ensure that the domain is absent // from any PA blocked lists. -func ValidDomain(domain string) error { +func (pa *AuthorityImpl) ValidDomain(domain string) error { if strings.Count(domain, "*") <= 0 { -- return ValidNonWildcardDomain(domain) +- return validNonWildcardDomain(domain) + return pa.ValidNonWildcardDomain(domain, false) } @@ -105,7 +105,7 @@ index d872d5cbe..faa052d6c 100644 if baseDomain == icannTLD { return errICANNTLDWildcard } -- return ValidNonWildcardDomain(baseDomain) +- return validNonWildcardDomain(baseDomain) + return pa.ValidNonWildcardDomain(baseDomain, false) } @@ -123,38 +123,45 @@ index d872d5cbe..faa052d6c 100644 } splitEmail := strings.SplitN(email.Address, "@", -1) domain := strings.ToLower(splitEmail[len(splitEmail)-1]) -- err = ValidNonWildcardDomain(domain) +- err = validNonWildcardDomain(domain) + err = pa.ValidNonWildcardDomain(domain, true) if err != nil { return berrors.InvalidEmailError( "contact email %q has invalid domain : %s", -@@ -416,7 +444,7 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error { - for _, domain := range domains { - if strings.Count(domain, "*") > 0 { - // Domain contains a wildcard, check that it is valid. -- err := ValidDomain(domain) -+ err := pa.ValidDomain(domain) - if err != nil { - appendSubError(domain, err) - continue -@@ -433,12 +461,15 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error { - } - } else { - // Validate that the domain is well-formed. -- err := ValidNonWildcardDomain(domain) -+ err := pa.ValidNonWildcardDomain(domain, false) - if err != nil { - appendSubError(domain, err) - continue +@@ -395,7 +423,7 @@ func subError(name string, err error) berrors.SubBoulderError { + // + // Precondition: all input domain names must be in lowercase. + func (pa *AuthorityImpl) WillingToIssue(domains []string) error { +- err := WellFormedDomainNames(domains) ++ err := pa.WellFormedDomainNames(domains) + if err != nil { + return err + } +@@ -414,6 +442,10 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error { } } + + if ok, _ := pa.checkWhitelist(domain, false); ok { + return nil + } - // Require no match against hostname block lists ++ + // For both wildcard and non-wildcard domains, check whether any parent domain + // name is on the regular blocklist. err := pa.checkHostLists(domain) +@@ -447,10 +479,10 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error { + // + // If multiple domains are invalid, the error will contain suberrors specific to + // each domain. +-func WellFormedDomainNames(domains []string) error { ++func (pa *AuthorityImpl) WellFormedDomainNames(domains []string) error { + var subErrors []berrors.SubBoulderError + for _, domain := range domains { +- err := ValidDomain(domain) ++ err := pa.ValidDomain(domain) if err != nil { -@@ -471,6 +502,34 @@ func (pa *AuthorityImpl) WillingToIssue(domains []string) error { + subErrors = append(subErrors, subError(domain, err)) + } +@@ -484,6 +516,34 @@ func combineSubErrors(subErrors []berrors.SubBoulderError) error { return nil } @@ -189,7 +196,7 @@ index d872d5cbe..faa052d6c 100644 // checkWildcardHostList checks the wildcardExactBlocklist for a given domain. // If the domain is not present on the list nil is returned, otherwise // errPolicyForbidden is returned. -@@ -500,6 +559,9 @@ func (pa *AuthorityImpl) checkHostLists(domain string) error { +@@ -513,6 +573,9 @@ func (pa *AuthorityImpl) checkHostLists(domain string) error { labels := strings.Split(domain, ".") for i := range labels { joined := strings.Join(labels[i:], ".") diff --git a/patches/ra_ra.patch b/patches/ra_ra.patch index 8d69b1b..49b84de 100644 --- a/patches/ra_ra.patch +++ b/patches/ra_ra.patch @@ -1,5 +1,5 @@ diff --git a/ra/ra.go b/ra/ra.go -index c5cdc0c98..8d34d3325 100644 +index 300610496..906573e63 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -44,7 +44,6 @@ import ( @@ -10,7 +10,7 @@ index c5cdc0c98..8d34d3325 100644 "github.com/letsencrypt/boulder/probs" pubpb "github.com/letsencrypt/boulder/publisher/proto" rapb "github.com/letsencrypt/boulder/ra/proto" -@@ -576,7 +575,7 @@ func (ra *RegistrationAuthorityImpl) validateContacts(contacts []string) error { +@@ -578,7 +577,7 @@ func (ra *RegistrationAuthorityImpl) validateContacts(contacts []string) error { contact, ) } diff --git a/patches/ratelimits_names.patch b/patches/ratelimits_names.patch index e917267..1a3b619 100644 --- a/patches/ratelimits_names.patch +++ b/patches/ratelimits_names.patch @@ -1,5 +1,5 @@ diff --git a/ratelimits/names.go b/ratelimits/names.go -index 0037363b0..c2ddc6076 100644 +index fdfd8e81e..636720bf2 100644 --- a/ratelimits/names.go +++ b/ratelimits/names.go @@ -150,7 +150,11 @@ func validateRegId(id string) error { @@ -28,17 +28,16 @@ index 0037363b0..c2ddc6076 100644 if err != nil { return fmt.Errorf( "invalid domain, %q must be formatted 'regId:domain': %w", id, err) -@@ -187,8 +195,12 @@ func validateFQDNSet(id string) error { +@@ -187,7 +195,11 @@ func validateFQDNSet(id string) error { return fmt.Errorf( "invalid fqdnSet, %q must be formatted 'fqdnSet'", id) } +- return policy.WellFormedDomainNames(domains) + pa, err := policy.New(nil, nil) + if err != nil { + return fmt.Errorf("cannot create policy authority implementation") + } - for _, domain := range domains { -- err := policy.ValidDomain(domain) -+ err = pa.ValidDomain(domain) - if err != nil { - return fmt.Errorf( - "invalid domain, %q must be formatted 'fqdnSet': %w", id, err) ++ return pa.WellFormedDomainNames(domains) + } + + func validateIdForName(name Name, id string) error { diff --git a/patches/storer_storer.patch b/patches/storer_storer.patch index ec6e763..3f92c0f 100644 --- a/patches/storer_storer.patch +++ b/patches/storer_storer.patch @@ -1,5 +1,5 @@ diff --git a/crl/storer/storer.go b/crl/storer/storer.go -index 10b1753c7..2cbc2eb17 100644 +index 9b41f560f..70bd63a3c 100644 --- a/crl/storer/storer.go +++ b/crl/storer/storer.go @@ -9,8 +9,12 @@ import ( @@ -15,15 +15,15 @@ index 10b1753c7..2cbc2eb17 100644 "time" "github.com/aws/aws-sdk-go-v2/service/s3" -@@ -38,6 +42,7 @@ type crlStorer struct { - cspb.UnimplementedCRLStorerServer +@@ -39,6 +43,7 @@ type crlStorer struct { + cspb.UnsafeCRLStorerServer s3Client simpleS3 s3Bucket string + localStorePath string issuers map[issuance.NameID]*issuance.Certificate uploadCount *prometheus.CounterVec sizeHistogram *prometheus.HistogramVec -@@ -50,6 +55,7 @@ func New( +@@ -53,6 +58,7 @@ func New( issuers []*issuance.Certificate, s3Client simpleS3, s3Bucket string, @@ -31,7 +31,7 @@ index 10b1753c7..2cbc2eb17 100644 stats prometheus.Registerer, log blog.Logger, clk clock.Clock, -@@ -83,6 +89,7 @@ func New( +@@ -86,6 +92,7 @@ func New( issuers: issuersByNameID, s3Client: s3Client, s3Bucket: s3Bucket, @@ -39,7 +39,7 @@ index 10b1753c7..2cbc2eb17 100644 uploadCount: uploadCount, sizeHistogram: sizeHistogram, latencyHistogram: latencyHistogram, -@@ -218,15 +225,19 @@ func (cs *crlStorer) UploadCRL(stream cspb.CRLStorer_UploadCRLServer) error { +@@ -221,15 +228,19 @@ func (cs *crlStorer) UploadCRL(stream grpc.ClientStreamingServer[cspb.UploadCRLR checksum := sha256.Sum256(crlBytes) checksumb64 := base64.StdEncoding.EncodeToString(checksum[:]) crlContentType := "application/pkix-crl" @@ -68,7 +68,7 @@ index 10b1753c7..2cbc2eb17 100644 latency := cs.clk.Now().Sub(start) cs.latencyHistogram.WithLabelValues(issuer.Subject.CommonName).Observe(latency.Seconds()) -@@ -245,3 +256,46 @@ func (cs *crlStorer) UploadCRL(stream cspb.CRLStorer_UploadCRLServer) error { +@@ -248,3 +259,46 @@ func (cs *crlStorer) UploadCRL(stream grpc.ClientStreamingServer[cspb.UploadCRLR return stream.SendAndClose(&emptypb.Empty{}) } diff --git a/patches/test_config_ca.patch b/patches/test_config_ca.patch index 14ad5e3..24ed612 100644 --- a/patches/test_config_ca.patch +++ b/patches/test_config_ca.patch @@ -1,14 +1,13 @@ diff --git a/test/config/ca.json b/test/config/ca.json -index cbb84f385..ec28cd37d 100644 +index cc4728363..2eb95ad81 100644 --- a/test/config/ca.json +++ b/test/config/ca.json -@@ -58,18 +58,6 @@ +@@ -58,39 +58,6 @@ "maxValidityBackdate": "1h5m" }, "issuers": [ - { -- "useForRSALeaves": false, -- "useForECDSALeaves": true, +- "active": true, - "issuerURL": "http://ca.example.org:4502/int-ecdsa-a", - "ocspURL": "http://ca.example.org:4002/", - "crlURLBase": "http://ca.example.org:4501/ecdsa-a/", @@ -17,26 +16,58 @@ index cbb84f385..ec28cd37d 100644 - "certFile": "test/certs/webpki/int-ecdsa-a.cert.pem", - "numSessions": 2 - } +- }, +- { +- "active": true, +- "issuerURL": "http://ca.example.org:4502/int-ecdsa-b", +- "ocspURL": "http://ca.example.org:4002/", +- "crlURLBase": "http://ca.example.org:4501/ecdsa-b/", +- "location": { +- "configFile": "test/certs/webpki/int-ecdsa-b.pkcs11.json", +- "certFile": "test/certs/webpki/int-ecdsa-b.cert.pem", +- "numSessions": 2 +- } +- }, +- { +- "active": false, +- "issuerURL": "http://ca.example.org:4502/int-ecdsa-c", +- "ocspURL": "http://ca.example.org:4002/", +- "crlURLBase": "http://ca.example.org:4501/ecdsa-c/", +- "location": { +- "configFile": "test/certs/webpki/int-ecdsa-c.pkcs11.json", +- "certFile": "test/certs/webpki/int-ecdsa-c.cert.pem", +- "numSessions": 2 +- } - }, { - "useForRSALeaves": true, - "useForECDSALeaves": true, -@@ -81,18 +69,6 @@ + "active": true, + "issuerURL": "http://ca.example.org:4502/int-rsa-a", +@@ -101,28 +68,6 @@ "certFile": "test/certs/webpki/int-rsa-a.cert.pem", "numSessions": 2 } - }, - { -- "useForRSALeaves": false, -- "useForECDSALeaves": false, +- "active": true, - "issuerURL": "http://ca.example.org:4502/int-rsa-b", -- "ocspURL": "http://ca.example.org:4003/", +- "ocspURL": "http://ca.example.org:4002/", - "crlURLBase": "http://ca.example.org:4501/rsa-b/", - "location": { - "configFile": "test/certs/webpki/int-rsa-b.pkcs11.json", - "certFile": "test/certs/webpki/int-rsa-b.cert.pem", - "numSessions": 2 +- } +- }, +- { +- "active": false, +- "issuerURL": "http://ca.example.org:4502/int-rsa-c", +- "ocspURL": "http://ca.example.org:4002/", +- "crlURLBase": "http://ca.example.org:4501/rsa-c/", +- "location": { +- "configFile": "test/certs/webpki/int-rsa-c.pkcs11.json", +- "certFile": "test/certs/webpki/int-rsa-c.cert.pem", +- "numSessions": 2 - } } ], - "ignoredLints": [ + "lintConfig": "test/config/zlint.toml", diff --git a/patches/va_va.patch b/patches/va_va.patch index d9fc804..3c0bfc2 100644 --- a/patches/va_va.patch +++ b/patches/va_va.patch @@ -1,8 +1,8 @@ diff --git a/va/va.go b/va/va.go -index dd743b593..b74a313f0 100644 +index d43346bbc..e0784adcb 100644 --- a/va/va.go +++ b/va/va.go -@@ -265,6 +265,7 @@ type ValidationAuthorityImpl struct { +@@ -256,6 +256,7 @@ type ValidationAuthorityImpl struct { maxRemoteFailures int accountURIPrefixes []string singleDialTimeout time.Duration @@ -10,7 +10,7 @@ index dd743b593..b74a313f0 100644 metrics *vaMetrics } -@@ -280,6 +281,7 @@ func NewValidationAuthorityImpl( +@@ -274,6 +275,7 @@ func NewValidationAuthorityImpl( clk clock.Clock, logger blog.Logger, accountURIPrefixes []string, @@ -18,7 +18,7 @@ index dd743b593..b74a313f0 100644 ) (*ValidationAuthorityImpl, error) { if len(accountURIPrefixes) == 0 { -@@ -306,6 +308,7 @@ func NewValidationAuthorityImpl( +@@ -300,6 +302,7 @@ func NewValidationAuthorityImpl( // used for the DialContext operations that take place during an // HTTP-01 challenge validation. singleDialTimeout: 10 * time.Second, diff --git a/patches/wfe2_main.patch b/patches/wfe2_main.patch index fade35c..08540de 100644 --- a/patches/wfe2_main.patch +++ b/patches/wfe2_main.patch @@ -1,8 +1,8 @@ diff --git a/cmd/boulder-wfe2/main.go b/cmd/boulder-wfe2/main.go -index 13e362c8..c16b0c56 100644 +index 83ff247f8..8f0449b9f 100644 --- a/cmd/boulder-wfe2/main.go +++ b/cmd/boulder-wfe2/main.go -@@ -106,7 +106,7 @@ type Config struct { +@@ -96,7 +96,7 @@ type Config struct { // DirectoryCAAIdentity is used for the /directory response's "meta" // element's "caaIdentities" field. It should match the VA's "issuerDomain" // configuration value (this value is the one used to enforce CAA) @@ -11,3 +11,20 @@ index 13e362c8..c16b0c56 100644 // DirectoryWebsite is used for the /directory response's "meta" element's // "website" field. DirectoryWebsite string `validate:"required,url"` +@@ -164,6 +164,8 @@ type Config struct { + // list will be rejected. This field is optional; if unset, no profile + // names are accepted. + CertificateProfileNames []string `validate:"omitempty,dive,alphanum,min=1,max=32"` ++ ++ cmd.HostnamePolicyConfig + } + + Syslog cmd.SyslogConfig +@@ -382,6 +384,7 @@ func main() { + txnBuilder, + maxNames, + c.WFE.CertificateProfileNames, ++ c.WFE.HostnamePolicyFile, + ) + cmd.FailOnError(err, "Unable to create WFE") + diff --git a/patches/wfe2_wfe.patch b/patches/wfe2_wfe.patch new file mode 100644 index 0000000..df895f0 --- /dev/null +++ b/patches/wfe2_wfe.patch @@ -0,0 +1,63 @@ +diff --git a/wfe2/wfe.go b/wfe2/wfe.go +index 756cef2f2..0e95a1dc2 100644 +--- a/wfe2/wfe.go ++++ b/wfe2/wfe.go +@@ -23,6 +23,7 @@ import ( + "go.opentelemetry.io/otel/trace" + "google.golang.org/protobuf/types/known/emptypb" + ++ "github.com/letsencrypt/boulder/cmd" + "github.com/letsencrypt/boulder/core" + corepb "github.com/letsencrypt/boulder/core/proto" + berrors "github.com/letsencrypt/boulder/errors" +@@ -169,6 +170,8 @@ type WebFrontEndImpl struct { + // passed to the newOrder endpoint. If a profile name is not in this list, + // the request will be rejected as malformed. + certificateProfileNames []string ++ ++ hostnamePolicyFile string + } + + // NewWebFrontEndImpl constructs a web service for Boulder +@@ -193,6 +196,7 @@ func NewWebFrontEndImpl( + txnBuilder *ratelimits.TransactionBuilder, + maxNames int, + certificateProfileNames []string, ++ hostnamePolicyFile string, + ) (WebFrontEndImpl, error) { + if len(issuerCertificates) == 0 { + return WebFrontEndImpl{}, errors.New("must provide at least one issuer certificate") +@@ -231,6 +235,7 @@ func NewWebFrontEndImpl( + txnBuilder: txnBuilder, + maxNames: maxNames, + certificateProfileNames: certificateProfileNames, ++ hostnamePolicyFile: hostnamePolicyFile, + } + + return wfe, nil +@@ -2337,7 +2342,24 @@ func (wfe *WebFrontEndImpl) NewOrder( + names[i] = ident.Value + } + +- err = policy.WellFormedDomainNames(names) ++ logger := cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7}) ++ pa, err := policy.New(map[core.AcmeChallenge]bool{}, logger) ++ if err != nil { ++ wfe.sendError(response, logEvent, probs.Malformed("cannot create policy authority implementation"), nil) ++ return ++ } ++ ++ if wfe.hostnamePolicyFile == "" { ++ wfe.sendError(response, logEvent, probs.Malformed("HostnamePolicyFile must be provided in config"), nil) ++ return ++ } ++ err = pa.LoadHostnamePolicyFile(wfe.hostnamePolicyFile) ++ if err != nil { ++ wfe.sendError(response, logEvent, probs.Malformed("couldn't load hostname policy file"), nil) ++ return ++ } ++ ++ err = pa.WellFormedDomainNames(names) + if err != nil { + wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Invalid identifiers requested"), nil) + return diff --git a/utils.sh b/utils.sh index 90f0739..f0e9a3d 100644 --- a/utils.sh +++ b/utils.sh @@ -9,6 +9,7 @@ export PS_MYSQL="mysqld" export PS_CONTROL="tcpserver" export PS_NGINX="nginx:" export PS_CONSUL="consul" +export PS_PKILINT="pkilint" LOOPCOUNT=120 @@ -32,6 +33,9 @@ count() { $PS_CONSUL) prefix="docker exec $(docker ps --format "{{.Names}}" | grep -- -bconsul-) " ;; + $PS_PKILINT) + prefix="docker exec $(docker ps --format "{{.Names}}" | grep -- -bpkilint-) " + ;; *) ;; esac @@ -40,6 +44,9 @@ count() { if [ "$pattern" == "$PS_CONSUL" ]; then res=$(${prefix}ps -eo pid,args 2>/dev/null | grep "$pattern" | grep -v grep | wc -l) fi + if [ "$pattern" == "$PS_PKILINT" ]; then + res=$(${prefix}ls -d /proc/[1-9]* 2>/dev/null | wc -l) + fi echo $res }