├── .github ├── Dockerfile ├── dependabot.yml └── workflows │ ├── build-dockers.yml │ ├── build-standalone.yml │ ├── golangci-lint.yml │ ├── release.yml │ └── try-bump.yml ├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── README_legacy.md ├── README_standalone.md ├── backup ├── build ├── .env.sample ├── Dockerfile-boulder ├── Dockerfile-control ├── Dockerfile-gui ├── Dockerfile-standalone ├── build.sh ├── docker-compose.yml ├── tag_and_upload.sh ├── tmp.patch └── tmp2.patch ├── checkcrl ├── checkrenew ├── commander ├── control.sh ├── control_do.sh ├── cron_d ├── debian ├── changelog ├── compat ├── control ├── copyright ├── rules └── source │ └── format ├── dev └── versions ├── gui ├── acme.go ├── apply ├── apply-boulder ├── apply-nginx ├── certificate.go ├── chains.go ├── dashboard.go ├── data │ ├── issuer │ │ └── openssl.cnf │ └── openssl.cnf ├── go.mod ├── go.sum ├── hsm.go ├── hsm_standalone.go ├── main.go ├── restart_control ├── setup.sh ├── static │ ├── 502.html │ ├── certs │ │ └── index.html │ ├── cps │ │ └── index.html │ ├── css │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ ├── dataTables.bootstrap.css │ │ ├── dataTables.responsive.css │ │ ├── font-awesome.min.css │ │ ├── labca.css │ │ ├── metisMenu.min.css │ │ └── sb-admin-2.min.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ ├── fav-admin.png │ │ ├── fav-public.png │ │ ├── spinner.gif │ │ └── warning.png │ ├── index.html │ ├── js │ │ ├── bootstrap-dialog.min.js │ │ ├── bootstrap.min.js │ │ ├── dataTables.bootstrap.min.js │ │ ├── dataTables.responsive.js │ │ ├── jquery.dataTables.min.js │ │ ├── jquery.form.min.js │ │ ├── jquery.min.js │ │ ├── jquery.stickytabs.js │ │ ├── labca.js │ │ ├── metisMenu.min.js │ │ ├── pwdux.js │ │ ├── sb-admin-2.min.js │ │ ├── zxcvbn.js │ │ └── zxcvbn.js.map │ ├── rate-limits.html │ └── terms │ │ └── v1.html ├── templates │ ├── base.tmpl │ ├── cert-ceremonies │ │ ├── issuer-cert.yaml │ │ ├── issuer-key.yaml │ │ ├── root-crl.yaml │ │ └── root.yaml │ ├── partials │ │ ├── css.tmpl │ │ ├── js.tmpl │ │ ├── nav.tmpl │ │ └── progress.tmpl │ └── views │ │ ├── about.tmpl │ │ ├── cert.tmpl │ │ ├── dashboard.tmpl │ │ ├── error.tmpl │ │ ├── final.tmpl │ │ ├── index.tmpl │ │ ├── list.tmpl │ │ ├── login.tmpl │ │ ├── logs.tmpl │ │ ├── manage.tmpl │ │ ├── polling.tmpl │ │ ├── register.tmpl │ │ ├── revoke-partial.tmpl │ │ ├── setup.tmpl │ │ ├── show.tmpl │ │ ├── standalone.tmpl │ │ └── wrapup.tmpl └── upgrades.go ├── init_d ├── install ├── logrotate_d ├── mail-tester.go ├── mailer ├── nginx.conf ├── patch-cfg.sh ├── patch.sh ├── patches ├── bad-key-revoker_main.patch ├── boulder-ra_main.patch ├── boulder-va_main.patch ├── ca_ca.patch ├── ca_ca_keytype_hack.patch ├── ca_crl.patch ├── ceremony_crl.patch ├── ceremony_ecdsa.patch ├── ceremony_key.patch ├── ceremony_main.patch ├── ceremony_rsa.patch ├── cert-checker_main.patch ├── cmd_config.patch ├── config_akamai-purger.patch ├── config_bad-key-revoker.patch ├── config_crl-storer.patch ├── config_crl-updater.patch ├── config_duration.patch ├── config_expiration-mailer.patch ├── config_notify-mailer.patch ├── config_ocsp-responder.patch ├── config_publisher.patch ├── config_ra.patch ├── config_rocsp_config.patch ├── config_wfe2.patch ├── contact-auditor_main.patch ├── core_interfaces.patch ├── crl-storer_main.patch ├── db_migrations.patch ├── db_migrations2.patch ├── db_migrations3.patch ├── db_migrations4.patch ├── db_migrations5.patch ├── docker-compose.patch ├── entrypoint.patch ├── expiration-mailer_main.patch ├── issuance_crl.patch ├── issuance_issuer.patch ├── linter_linter.patch ├── log_prod_prefix.patch ├── log_test_prefix.patch ├── log_validator_validator.patch ├── mail_mailer.patch ├── makefile.patch ├── notify-mailer_main.patch ├── ocsp-responder_main.patch ├── policy_pa.patch ├── ra_ra.patch ├── ratelimits_names.patch ├── redis_config.patch ├── remoteva_main.patch ├── sfe_templates_layout.patch ├── start.patch ├── storer_storer.patch ├── test_certs_generate.patch ├── test_config_ca.patch ├── test_health-checker_main.patch ├── test_ocsp_helper_helper.patch ├── test_startservers.patch ├── updater_continuous.patch ├── updater_updater.patch ├── va_http.patch ├── va_va.patch ├── wfe2_main.patch └── wfe2_wfe.patch ├── proxy.inc ├── renew ├── restore ├── update └── utils.sh /.github/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # Dummy Dockerfile for dependabot, if it finds an update we need to update docker-compose.yml 3 | # 4 | FROM nginx:latest 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "docker" 5 | directory: ".github/" 6 | open-pull-requests-limit: 1 7 | schedule: 8 | interval: "daily" 9 | # interval: "weekly" 10 | # day: "wednesday" 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | open-pull-requests-limit: 1 14 | schedule: 15 | interval: "daily" 16 | # interval: monthly 17 | - package-ecosystem: "gomod" 18 | directory: "/" 19 | open-pull-requests-limit: 1 20 | schedule: 21 | interval: "daily" 22 | # interval: "weekly" 23 | # day: "wednesday" 24 | -------------------------------------------------------------------------------- /.github/workflows/build-dockers.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker Images 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | tags: 8 | - "v*" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | prepare: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | ref: ${{ github.head_ref }} 20 | 21 | - name: Build binaries 22 | run: | 23 | build/build.sh 24 | 25 | - name: Import GPG key 26 | uses: crazy-max/ghaction-import-gpg@v6 27 | with: 28 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 29 | passphrase: ${{ secrets.PASSPHRASE }} 30 | git_user_signingkey: true 31 | git_commit_gpgsign: true 32 | 33 | - name: Commit any updated files 34 | uses: stefanzweifel/git-auto-commit-action@v5 35 | with: 36 | commit_message: "[skip ci] Commit changes from build-dockers action" 37 | commit_options: "-S" 38 | commit_user_email: ${{ vars.COMMIT_USER }} 39 | 40 | - name: Cache build output 41 | uses: actions/cache/save@v4 42 | with: 43 | path: build 44 | key: build-dockers-${{ github.sha }} 45 | 46 | build-publish: 47 | runs-on: ubuntu-latest 48 | needs: prepare 49 | permissions: 50 | packages: write 51 | strategy: 52 | matrix: 53 | include: 54 | - image: ghcr.io/hakwerk/labca-gui 55 | dockerfile: build/Dockerfile-gui 56 | label: org.opencontainers.image.title=labca-gui 57 | - image: ghcr.io/hakwerk/labca-boulder 58 | dockerfile: build/Dockerfile-boulder 59 | label: org.opencontainers.image.title=labca-boulder 60 | - image: ghcr.io/hakwerk/labca-control 61 | dockerfile: build/Dockerfile-control 62 | label: org.opencontainers.image.title=labca-control 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | with: 67 | ref: ${{ github.head_ref }} 68 | 69 | - name: Restore build output 70 | uses: actions/cache/restore@v4 71 | with: 72 | path: build 73 | key: build-dockers-${{ github.sha }} 74 | 75 | - name: Set up docker buildx 76 | uses: docker/setup-buildx-action@v3 77 | 78 | - name: Extract metadata (tags, labels) for Docker 79 | id: meta 80 | uses: docker/metadata-action@v5 81 | with: 82 | images: ${{ matrix.image }} 83 | labels: ${{ matrix.label }} 84 | tags: | 85 | type=schedule,pattern={{date 'YYYYMMDD'}} 86 | type=match,pattern=v(.*),group=1 87 | type=edge,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} 88 | type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }} 89 | type=ref,event=pr 90 | 91 | - name: Login to GHCR 92 | if: github.event_name != 'pull_request' 93 | uses: docker/login-action@v3 94 | with: 95 | registry: ghcr.io 96 | username: ${{ github.repository_owner }} 97 | password: ${{ secrets.GITHUB_TOKEN }} 98 | 99 | - name: Build and push 100 | uses: docker/build-push-action@v6 101 | with: 102 | context: build 103 | file: ${{ matrix.dockerfile }} 104 | labels: ${{ steps.meta.outputs.labels }} 105 | platforms: linux/amd64 106 | push: ${{ github.event_name != 'pull_request' }} 107 | tags: ${{ steps.meta.outputs.tags }} 108 | 109 | -------------------------------------------------------------------------------- /.github/workflows/build-standalone.yml: -------------------------------------------------------------------------------- 1 | name: Build Standalone Docker Images 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | tags: 8 | - "v*" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build-standalone: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | packages: write 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | GO_VERSION: 20 | - 1.24.1 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | persist-credentials: false 27 | fetch-depth: 0 28 | 29 | - name: Set up QEMU 30 | uses: docker/setup-qemu-action@v3 31 | 32 | - name: Set up Docker Buildx 33 | uses: docker/setup-buildx-action@v3 34 | 35 | - name: Set up Go 36 | uses: actions/setup-go@v5 37 | with: 38 | go-version: ${{ matrix.GO_VERSION }} 39 | 40 | - name: APT Install 41 | run: | 42 | sudo dpkg --add-architecture arm64 43 | sudo apt-get -y install build-essential debhelper fakeroot crossbuild-essential-arm64 44 | 45 | - name: Build Debian packages 46 | run: | 47 | make debian 48 | make debian-arm64 49 | 50 | - name: Extract metadata (tags, labels) for Docker 51 | id: meta 52 | uses: docker/metadata-action@v5 53 | with: 54 | images: ghcr.io/hakwerk/labca-standalone 55 | annotations: | 56 | org.opencontainers.image.description=Standalone version of LabCA GUI for use with other ACME servers 57 | org.opencontainers.image.documentation=https://github.com/hakwerk/labca/blob/master/README_standalone.md 58 | org.opencontainers.image.title=labca-standalone 59 | labels: | 60 | org.opencontainers.image.description=Standalone version of LabCA GUI for use with other ACME servers 61 | org.opencontainers.image.documentation=https://github.com/hakwerk/labca/blob/master/README_standalone.md 62 | org.opencontainers.image.title=labca-standalone 63 | tags: | 64 | type=schedule,pattern={{date 'YYYYMMDD'}} 65 | type=match,pattern=v(.*),group=1 66 | type=edge,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} 67 | type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }} 68 | type=ref,event=pr 69 | 70 | - name: Login to GHCR 71 | uses: docker/login-action@v3 72 | with: 73 | registry: ghcr.io 74 | username: ${{ github.repository_owner }} 75 | password: ${{ secrets.GITHUB_TOKEN }} 76 | 77 | - name: Build and push docker images 78 | uses: docker/build-push-action@v6 79 | with: 80 | annotations: ${{ steps.meta.outputs.annotations }} 81 | context: release 82 | file: build/Dockerfile-standalone 83 | labels: ${{ steps.meta.outputs.labels }} 84 | platforms: linux/amd64,linux/arm64/v8 85 | push: true 86 | tags: ${{ steps.meta.outputs.tags }} 87 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: 9 | 10 | permissions: 11 | contents: read 12 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 13 | # pull-requests: read 14 | 15 | jobs: 16 | golangci: 17 | name: lint 18 | runs-on: ubuntu-latest 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | GO_VERSION: 23 | - 1.24.1 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: actions/setup-go@v5 29 | with: 30 | go-version: ${{ matrix.GO_VERSION }} 31 | 32 | - name: golangci-lint 33 | uses: golangci/golangci-lint-action@v8 34 | with: 35 | version: v2.1 36 | working-directory: ./gui 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | create-release: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | GO_VERSION: 16 | - 1.24.1 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | persist-credentials: false 23 | fetch-depth: 0 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: ${{ matrix.GO_VERSION }} 29 | 30 | - name: APT Install 31 | id: aptInstall 32 | run: | 33 | sudo dpkg --add-architecture arm64 34 | sudo apt-get -y install build-essential debhelper fakeroot crossbuild-essential-arm64 35 | 36 | - name: Build Debian packages 37 | id: make_debian 38 | run: | 39 | make debian 40 | make debian-arm64 41 | 42 | - name: Create changelog text 43 | id: changelog 44 | uses: loopwerk/tag-changelog@v1 45 | with: 46 | token: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | - name: Create release 49 | uses: ncipollo/release-action@v1 50 | with: 51 | artifacts: "release/*" 52 | body: ${{ steps.changelog.outputs.changes }} 53 | draft: true 54 | token: ${{ secrets.GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/try-bump.yml: -------------------------------------------------------------------------------- 1 | name: Try Boulder Bump 2 | 3 | on: 4 | schedule: 5 | - cron: '30 5 * * 5' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | try-bump: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | 14 | steps: 15 | - name: Checkout Self 16 | uses: actions/checkout@v4 17 | 18 | - uses: oprypin/find-latest-tag@v1 19 | with: 20 | repository: letsencrypt/boulder 21 | id: boulder 22 | 23 | - run: echo "Boulder is at version ${{ steps.boulder.outputs.tag }}" 24 | 25 | - uses: actions/checkout@v4 26 | with: 27 | repository: letsencrypt/boulder 28 | ref: ${{ steps.boulder.outputs.tag }} 29 | path: boulder 30 | 31 | - name: Apply our code patches 32 | run: | 33 | cd boulder 34 | ../patch.sh 35 | 36 | - name: Apply our config patches 37 | run: | 38 | cd boulder 39 | cp -r test labca 40 | ../patch-cfg.sh 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # GitHub Pages 2 | _site 3 | .sass-cache 4 | .jekyll-metadata 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, build with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # IDE 20 | *.code-workspace 21 | 22 | # Project specific 23 | gui/data/ 24 | gui/bin/ 25 | bin/labca-gui 26 | debian/.debhelper/ 27 | debian/files 28 | debian/labca-gui.substvars 29 | debian/labca-gui/ 30 | build/tmp/ 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PKG?=github.com/hakwerk/labca/gui 2 | BINNAME?=labca-gui 3 | 4 | # Set V to 1 for verbose output from the Makefile 5 | Q=$(if $V,,@) 6 | PREFIX?= 7 | TAG=$(shell git rev-list --tags --max-count=1) 8 | VERSION=$(shell git describe --always --tags $(TAG)) 9 | DEB_VERSION=$(shell echo $(VERSION) | sed 's/^v//' | sed 's/-/./g') 10 | FULLVERSION=$(shell git describe --always --tags) 11 | RELEASE=./release 12 | 13 | all: build 14 | 15 | .PHONY: all 16 | 17 | ifdef V 18 | $(info VERSION is $(VERSION)) 19 | $(info DEB_VERSION is $(DEB_VERSION)) 20 | $(info FULLVERSION is $(FULLVERSION)) 21 | endif 22 | 23 | ######################################### 24 | # Build 25 | ######################################### 26 | 27 | LDFLAGS := -ldflags='-w -X "main.standaloneVersion=$(FULLVERSION)" -extldflags "-static"' -tags standalone 28 | 29 | download: 30 | $Q cd gui; \ 31 | go mod download; \ 32 | cd .. 33 | 34 | build: $(PREFIX)bin/$(BINNAME) 35 | @echo "Build Complete!" 36 | 37 | $(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go) 38 | $Q mkdir -p $(@D) 39 | $Q cd gui; \ 40 | $(GOOS_OVERRIDE) $(GOFLAGS) go build -o ../$(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG); \ 41 | cd .. 42 | 43 | .PHONY: download build 44 | 45 | ######################################### 46 | # Install 47 | ######################################### 48 | 49 | INSTALL_PREFIX?=/usr/ 50 | 51 | install: $(PREFIX)bin/$(BINNAME) 52 | $Q install -D $(PREFIX)bin/$(BINNAME) $(DESTDIR)$(INSTALL_PREFIX)bin/$(BINNAME) 53 | 54 | uninstall: 55 | $Q rm -f $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BINNAME) 56 | 57 | .PHONY: install uninstall 58 | 59 | ######################################### 60 | # Debian 61 | ######################################### 62 | 63 | changelog: 64 | $Q echo "labca-gui ($(DEB_VERSION)) unstable; urgency=medium" > debian/changelog 65 | $Q echo >> debian/changelog 66 | $Q echo " * See https://github.com/hakwerk/labca/releases" >> debian/changelog 67 | $Q echo >> debian/changelog 68 | $Q echo " -- hakwerk $(shell date -uR)" >> debian/changelog 69 | 70 | debian: changelog 71 | $Q mkdir -p $(RELEASE); \ 72 | OUTPUT=../labca-gui*.deb; \ 73 | rm -f $$OUTPUT; \ 74 | dpkg-buildpackage -b -rfakeroot -us -uc && cp $$OUTPUT $(RELEASE)/ 75 | 76 | debian-arm64: changelog 77 | $Q mkdir -p $(RELEASE); \ 78 | OUTPUT=../labca-gui*.deb; \ 79 | rm -f $$OUTPUT; \ 80 | GOOS_OVERRIDE="GOARCH=arm64" \ 81 | dpkg-buildpackage -b -rfakeroot -us -uc --host-arch arm64 && cp $$OUTPUT $(RELEASE)/ 82 | 83 | distclean: clean 84 | 85 | .PHONY: changelog debian debian-arm64 distclean 86 | 87 | ######################################### 88 | # Clean 89 | ######################################### 90 | 91 | clean: 92 | ifneq ($(BINNAME),"") 93 | $Q rm -f $(PREFIX)bin/$(BINNAME) 94 | endif 95 | 96 | .PHONY: clean 97 | 98 | ######################################### 99 | # Dev 100 | ######################################### 101 | 102 | run: 103 | $Q cd gui && go run -ldflags='-X "main.standaloneVersion=$(shell git describe --always --tags HEAD)"' github.com/hakwerk/labca/gui --config stepca_config.json; cd .. 104 | 105 | .PHONY: run 106 | -------------------------------------------------------------------------------- /README_legacy.md: -------------------------------------------------------------------------------- 1 | # LabCA Legacy Mode 2 | 3 | The `install` script method to run LabCA on a dedicated (virtual) machine is no longer updated, but it is still available on the `master` branch. 4 | During installation this method takes a lot of time and CPU cycles as a lot of compiling is done on startup of the containers. 5 | 6 | ## Install 7 | 8 | LabCA is best run on its own server / virtual machine to prevent any issues caused by conflicting applications. On a freshly installed Linux machine (tested with Debian and Ubuntu) run this command as root user (or as a regular user that already is in the sudo group): 9 | 10 | ```sh 11 | curl -sSL https://raw.githubusercontent.com/hakwerk/labca/master/install | bash 12 | ``` 13 | 14 | Alternatively, clone this git repository, checkout the master branch and run the install script locally. 15 | Or a combination: run the above curl command, but abort (ctrl-c) the script after the `[✓] Clone https://github.com/hakwerk/labca/ to /home/labca/labca` line (it will be waiting for the FQDN input) so that this repository is cloned in its final location, and then inspect, tweak and/or run the script `/home/labca/labca/install`. 16 | 17 | The first-time install will take a while, depending on the power of your server and your internet speed. On my machine it takes about 12 minutes. It will install the latest versions of some packages, download the relevant programs and configure everything. If all goes well it should look like this: 18 | 19 | 20 | 21 | Now you can point your browser to the LabCA GUI and setup your instance. 22 | 23 | ## Migration 24 | 25 | If you have an existing VM installation that you would like to convert to the docker-only setup, first export the data from your existing instance: in the left menu in the Admin web gui click "Manage" then on the "Backup" tab click "Backup Now"; wait for the page to reload and then click on the newest file name and download it. 26 | 27 | Now install the docker-only setup as described in the main [README](README.md). On the very first "Create admin account" GUI setup page, click the link "restore from a backup file". 28 | -------------------------------------------------------------------------------- /README_standalone.md: -------------------------------------------------------------------------------- 1 | # LabCA Standalone Version ![status-experimental](https://img.shields.io/badge/status-experimental-orange.svg) 2 | 3 | As the ACME protocol is a standard (RFC8555) and not limited to boulder, there also are other implementations, e.g. step-ca from Smallstep™ that you can run and manage yourself. 4 | 5 | Getting started with step-ca is much easier than starting with boulder. But Smallstep is not providing a self-managed web GUI to easily see what certificates have been issued by step-ca and what their expiry statuses are. In fact they are using a very specific database storage that does not allow you to query the data directly from a normal database client either. 6 | 7 | As the structure of the ACME data is pretty standard anyway, this standalone version of the LabCA GUI was created to work with step-ca (and potentially other ACME implementations in the future). It only works with their MySQL backend, as the BadgerDB backend has several limitations. 8 | 9 | The standalone GUI is distributed as a single binary so that it can be easily installed and started. There is also a docker image available. 10 | 11 | 12 | ## Usage 13 | 14 | ### Install package 15 | 16 | Download the latest .deb file for your platform architecture from the latest [release](https://github.com/hakwerk/labca/releases) on GitHub. 17 | 18 | Install the .deb file: 19 | ``` 20 | dpkg -i labca-gui__.deb 21 | ``` 22 | 23 | The first time you can use the -init flag to create the config file. The location of the config file (default data/config.json), the IP address to listen on (default 0.0.0.0) and the port number (default 3000) can be specified, e.g.: 24 | ``` 25 | labca-gui -config stepca.json -address 127.0.0.1 -port 8080 -init 26 | ``` 27 | 28 | For consecutive starts you only need to specify the config file if it is not data/config.json 29 | ``` 30 | labca-gui -config stepca.json 31 | ``` 32 | 33 | The first time you connect to the application, you can create an admin account and specify the MySQL connection details for your step-ca database. 34 | 35 | ### Docker 36 | 37 | When running a docker container you can map local filesystem files or directories as volumes to have the config data outside of the image, e.g.: 38 | ``` 39 | docker run -it --rm -v /home/username/acme/stepca_config.json:/usr/data/config.json ghcr.io/hakwerk/labca-standalone 40 | ``` 41 | or 42 | ``` 43 | docker run -it --rm -v /home/username/acme:/opt/acme ghcr.io/hakwerk/labca-standalone labca-gui -config /opt/acme/stepca.json 44 | ``` 45 | 46 | 47 | ## systemd service 48 | 49 | If you want to have the standalone version running all the time, even after a system reboot, you can create a service with the following steps (with thanks to [budulinek](https://github.com/budulinek)): 50 | ``` 51 | $ sudo mkdir -p /etc/labca 52 | $ sudo labca-gui -config /etc/labca/labca.json -port 3000 -init 53 | $ sudo useradd --system --home /etc/labca --shell /bin/false labca 54 | $ sudo chown -R labca:labca /etc/labca 55 | $ sudo nano /etc/systemd/system/labca.service 56 | ``` 57 | Put the following into that service file: 58 | ``` 59 | [Unit] 60 | Description=LabCA service 61 | After=network-online.target 62 | Wants=network-online.target 63 | StartLimitIntervalSec=30 64 | StartLimitBurst=3 65 | 66 | [Service] 67 | Type=simple 68 | User=labca 69 | Group=labca 70 | WorkingDirectory=/etc/labca 71 | ExecStart=/usr/bin/labca-gui -config /etc/labca/labca.json 72 | ExecReload=/bin/kill --signal HUP $MAINPID 73 | Restart=on-failure 74 | RestartSec=5 75 | TimeoutStopSec=30 76 | StartLimitInterval=30 77 | StartLimitBurst=3 78 | 79 | [Install] 80 | WantedBy=multi-user.target 81 | ``` 82 | And finally 83 | ``` 84 | $ sudo systemctl daemon-reload 85 | $ systemctl enable --now labca 86 | ``` 87 | -------------------------------------------------------------------------------- /backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | NOW=$(date +%y%m%d-%H%M%S) 6 | CRON="" 7 | if [ "$1" == "cron" ]; then 8 | CRON="_${1}" 9 | TODAY=`date '+%Y_%m_%d'` 10 | echo "Running cron-$(basename $0) for ${TODAY}..." 11 | fi 12 | instance=$(grep fqdn /opt/labca/data/config.json 2>/dev/null | cut -d ":" -f 2- | tr -d " \"," | cut -d"." -f1) 13 | BASE=${NOW}_${instance}${CRON} 14 | TMPDIR=/tmp/$BASE 15 | mkdir -p $TMPDIR/nginx_ssl 16 | mkdir -p /opt/backup 17 | 18 | cd /opt/boulder 19 | docker compose exec bmysql mysqldump boulder_sa_integration >$TMPDIR/boulder_sa_integration.sql 20 | 21 | cp -rp /etc/nginx/ssl $TMPDIR/nginx_ssl/ 22 | 23 | cp -rp /opt/labca/data $TMPDIR/ 24 | #cp -p /opt/labca/data/config.json $TMPDIR/ 25 | 26 | cp -rp /opt/boulder/labca/certs/webpki $TMPDIR/ 27 | 28 | cp -rp /var/lib/softhsm/tokens $TMPDIR/ 29 | 30 | 31 | cd /tmp 32 | tar czf /opt/backup/$BASE.tgz $BASE 33 | rm -rf $TMPDIR 34 | 35 | # housekeeping 36 | find /opt/backup -name "*_cron_*.tgz" -mtime +31 -exec rm -rf {} \; 37 | 38 | if [ "$1" != "cron" ]; then 39 | echo /opt/backup/$BASE.tgz 40 | fi 41 | -------------------------------------------------------------------------------- /build/.env.sample: -------------------------------------------------------------------------------- 1 | # You can use a .env file to set variables used in the docker-compose.yml file, e.g.: 2 | LABCA_FQDN=ca.test.internal 3 | -------------------------------------------------------------------------------- /build/Dockerfile-boulder: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM letsencrypt/boulder-tools:go1.24.1_2025-04-30 AS boulder-tools 3 | 4 | FROM ubuntu:noble 5 | 6 | RUN apt-get update && \ 7 | apt-get install -y --no-install-recommends \ 8 | ca-certificates \ 9 | mariadb-client-core \ 10 | net-tools \ 11 | python3-pip \ 12 | rsyslog \ 13 | softhsm2 \ 14 | && rm -rf /var/lib/apt/lists/* \ 15 | && pip3 install --break-system-packages requests 16 | 17 | COPY --from=boulder-tools /usr/local/bin/sql-migrate /usr/local/bin/sql-migrate 18 | COPY --from=boulder-tools /usr/local/bin/minica /usr/local/bin/minica 19 | COPY tmp/bin /opt/boulder/bin 20 | COPY tmp/src/start.py /opt/boulder 21 | RUN sed -i -e "s|./test|./labca|" /opt/boulder/start.py 22 | COPY tmp/src/sa/db /opt/boulder/sa/db 23 | COPY tmp/src/sa/db-users /opt/boulder/sa/db-users 24 | COPY tmp/src/test/boulder-tools/boulder.rsyslog.conf /etc/rsyslog.d/ 25 | RUN sed -i '/imklog/s/^/#/' /etc/rsyslog.conf 26 | RUN sed -i '/$ActionFileDefaultTemplate/s/^/#/' /etc/rsyslog.conf 27 | RUN sed -i '/$RepeatedMsgReduction on/s/^/#/' /etc/rsyslog.conf 28 | -------------------------------------------------------------------------------- /build/Dockerfile-control: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM letsencrypt/boulder-tools:go1.24.1_2025-04-30 AS boulder-tools 3 | 4 | FROM ubuntu:noble AS builder 5 | 6 | RUN export DEBIAN_FRONTEND=noninteractive \ 7 | && apt-get update \ 8 | && apt-get install -y --no-install-recommends \ 9 | ca-certificates \ 10 | curl \ 11 | gnupg \ 12 | && install -m 0755 -d /etc/apt/keyrings \ 13 | && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ 14 | && chmod a+r /etc/apt/keyrings/docker.gpg \ 15 | && echo \ 16 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ 17 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 18 | tee /etc/apt/sources.list.d/docker.list > /dev/null \ 19 | && apt-get update \ 20 | && apt-get install -y --no-install-recommends \ 21 | docker-ce \ 22 | docker-ce-cli \ 23 | containerd.io \ 24 | docker-compose-plugin \ 25 | && dcver=$(docker compose version | grep v2.19 | wc -l) \ 26 | && if [ "$dcver" != "0" ]; then \ 27 | dc18=$(apt list docker-compose-plugin -a 2>/dev/null | grep 2.18 | cut -d ' ' -f 2); \ 28 | apt install -y --allow-downgrades docker-compose-plugin=${dc18}; \ 29 | fi \ 30 | && rm -rf /var/lib/apt/lists/* 31 | 32 | FROM ubuntu:noble 33 | 34 | RUN export DEBIAN_FRONTEND=noninteractive \ 35 | && apt update \ 36 | && apt install -y --no-install-recommends --reinstall software-properties-common \ 37 | && add-apt-repository -y ppa:deadsnakes/ppa \ 38 | && apt-get update \ 39 | && apt-get install -y --no-install-recommends \ 40 | ca-certificates \ 41 | cron \ 42 | curl \ 43 | python3.10-venv \ 44 | softhsm2 \ 45 | tzdata \ 46 | ucspi-tcp \ 47 | && python3.10 -m venv /opt/certbot \ 48 | && /opt/certbot/bin/pip install --upgrade pip \ 49 | && /opt/certbot/bin/pip install certbot \ 50 | && ln -sf /opt/certbot/bin/certbot /usr/bin/certbot \ 51 | && rm -rf /var/lib/apt/lists/* 52 | 53 | COPY --from=boulder-tools /usr/local/bin/minica /usr/local/bin/minica 54 | 55 | COPY --from=builder /usr/bin/docker /usr/bin/docker 56 | COPY --from=builder /usr/libexec/docker/cli-plugins/docker-compose /usr/libexec/docker/cli-plugins/docker-compose 57 | 58 | COPY tmp/backup /opt/labca/ 59 | COPY tmp/checkcrl /opt/labca/ 60 | COPY tmp/checkrenew /opt/labca/ 61 | COPY tmp/commander /opt/labca/ 62 | COPY tmp/control.sh /opt/labca/ 63 | COPY tmp/cron_d /opt/labca/ 64 | COPY tmp/mailer /opt/labca/ 65 | COPY tmp/renew /opt/labca/ 66 | COPY tmp/restore /opt/labca/ 67 | COPY tmp/utils.sh /opt/labca/ 68 | COPY tmp/src/labca /opt/staging/boulder_labca 69 | COPY tmp/admin/apply-boulder /opt/labca/ 70 | COPY tmp/admin/apply /opt/labca/ 71 | COPY tmp/labca-gui /opt/labca/bin/ 72 | 73 | COPY tmp/admin/static /opt/staging/static 74 | COPY tmp/admin/data /opt/staging/data 75 | COPY tmp/nginx.conf /opt/staging/ 76 | COPY tmp/proxy.inc /opt/staging/ 77 | COPY tmp/admin/apply-nginx /opt/labca/ 78 | 79 | COPY tmp/bin/boulder /opt/boulder/bin/ 80 | 81 | RUN mkdir /opt/logs 82 | -------------------------------------------------------------------------------- /build/Dockerfile-gui: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM ubuntu:noble AS builder 3 | 4 | RUN export DEBIAN_FRONTEND=noninteractive \ 5 | && apt-get update \ 6 | && apt-get install -y --no-install-recommends \ 7 | ca-certificates \ 8 | curl \ 9 | gnupg \ 10 | && install -m 0755 -d /etc/apt/keyrings \ 11 | && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ 12 | && chmod a+r /etc/apt/keyrings/docker.gpg \ 13 | && echo \ 14 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ 15 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 16 | tee /etc/apt/sources.list.d/docker.list > /dev/null \ 17 | && apt-get update \ 18 | && apt-get install -y --no-install-recommends \ 19 | docker-ce \ 20 | docker-ce-cli \ 21 | containerd.io \ 22 | docker-compose-plugin \ 23 | && dcver=$(docker compose version | grep v2.19 | wc -l) \ 24 | && if [ "$dcver" != "0" ]; then \ 25 | dc18=$(apt list docker-compose-plugin -a 2>/dev/null | grep 2.18 | cut -d ' ' -f 2); \ 26 | apt install -y --allow-downgrades docker-compose-plugin=${dc18}; \ 27 | fi \ 28 | && rm -rf /var/lib/apt/lists/* 29 | 30 | FROM ubuntu:noble 31 | 32 | RUN apt-get update && \ 33 | apt-get install -y --no-install-recommends \ 34 | ca-certificates \ 35 | softhsm2 \ 36 | tzdata \ 37 | unzip \ 38 | zip \ 39 | && rm -rf /var/lib/apt/lists/* 40 | 41 | COPY --from=builder /usr/bin/docker /usr/bin/docker 42 | COPY --from=builder /usr/libexec/docker/cli-plugins/docker-compose /usr/libexec/docker/cli-plugins/docker-compose 43 | 44 | COPY tmp/labca-gui /opt/labca/bin/ 45 | COPY tmp/admin/setup.sh /opt/labca/ 46 | COPY tmp/admin/apply /opt/labca/ 47 | COPY tmp/admin/apply-boulder /opt/labca/ 48 | COPY tmp/admin/apply-nginx /opt/labca/ 49 | COPY tmp/admin/restart_control /opt/labca/ 50 | COPY tmp/admin/templates /opt/labca/templates/ 51 | 52 | COPY tmp/bin/ceremony /opt/boulder/bin/ 53 | COPY tmp/bin/nameid /opt/boulder/bin/ 54 | -------------------------------------------------------------------------------- /build/Dockerfile-standalone: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM ubuntu:noble 3 | 4 | ARG TARGETARCH 5 | 6 | COPY labca-gui*.deb /tmp/ 7 | 8 | RUN dpkg -i /tmp/labca-gui_*_$TARGETARCH.deb 9 | 10 | CMD ["labca-gui", "-config", "/usr/data/config.json"] 11 | -------------------------------------------------------------------------------- /build/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -euo pipefail 4 | 5 | cd $(dirname $0) 6 | 7 | TMP_DIR=$(pwd)/tmp 8 | rm -rf $TMP_DIR && mkdir -p $TMP_DIR/{admin,bin,logs,src} 9 | 10 | boulderDir=$TMP_DIR/src 11 | boulderTag="release-2025-05-27" 12 | boulderUrl="https://github.com/letsencrypt/boulder/" 13 | cloneDir=$(pwd)/.. 14 | 15 | GIT_VERSION=$(git describe --always --tags 2>/dev/null) 16 | BUILD_HOST=labca-$GIT_VERSION 17 | BUILD_IMAGE=$(eval echo $(grep boulder-tools ../patches/docker-compose.patch | head -3 | tail -1 | sed -e "s/\+\s*image://" | sed -e "s/&boulder_tools_image//")) 18 | 19 | git clone --branch $boulderTag --depth 1 $boulderUrl $boulderDir 2>/dev/null 20 | cd $boulderDir 21 | if [ $boulderTag != "main" ]; then 22 | git checkout $boulderTag -b $boulderTag 2>/dev/null 23 | fi 24 | 25 | if [ "$BUILD_IMAGE" == "" ]; then 26 | BUILD_IMAGE=$(eval echo $(grep boulder-tools $TMP_DIR/src/docker-compose.yml | grep "image:" | head -1 | sed -e "s/image://" | sed -e "s/&boulder_tools_image//")) 27 | fi 28 | 29 | BOULDER_TOOLS_TAG=$(grep go1. $TMP_DIR/src/.github/workflows/boulder-ci.yml | head -1 | sed -e "s/\s*- //") 30 | BUILD_IMAGE=${BUILD_IMAGE/latest/$BOULDER_TOOLS_TAG} 31 | 32 | echo 33 | $cloneDir/patch.sh 34 | cp -r test labca 35 | $cloneDir/patch-cfg.sh " " "$boulderDir/labca" 36 | sed -i "s/BUILD_ID = .*/BUILD_ID = \$(shell git describe --always HEAD 2>\/dev\/null) +\$(COMMIT_ID)/" $boulderDir/Makefile 37 | sed -i "s/BUILD_HOST = .*/BUILD_HOST ?= labca-develop/" $boulderDir/Makefile 38 | sed -i "s/-ldflags \"-X/-ldflags \"-s -w -X/" $boulderDir/Makefile 39 | cp -p docker-compose.yml $cloneDir/build/ 40 | 41 | echo 42 | BASEDIR=/go/src/github.com/letsencrypt/boulder 43 | docker run -v $boulderDir:$BASEDIR:cached -v $TMP_DIR/bin:$BASEDIR/bin -w $BASEDIR -e BUILD_HOST=$BUILD_HOST $BUILD_IMAGE sh -c "git config --global --add safe.directory $BASEDIR && make build" 44 | 45 | cp $cloneDir/nginx.conf $TMP_DIR/ 46 | cp $cloneDir/proxy.inc $TMP_DIR/ 47 | cp -rp $cloneDir/gui/* $TMP_DIR/admin/ 48 | head -13 $cloneDir/gui/setup.sh > $TMP_DIR/admin/setup.sh 49 | sed -i '/^$/d' $TMP_DIR/admin/setup.sh 50 | 51 | echo 52 | BASEDIR=/go/src/labca 53 | docker run -v $TMP_DIR/admin:$BASEDIR:cached -v $TMP_DIR:$BASEDIR/bin -w $BASEDIR -e GIT_VERSION=$GIT_VERSION $BUILD_IMAGE ./setup.sh 54 | 55 | cp -rp $cloneDir/gui/setup.sh $TMP_DIR/admin/ 56 | cp -rp $cloneDir/backup $TMP_DIR/ 57 | cp -rp $cloneDir/checkcrl $TMP_DIR/ 58 | cp -rp $cloneDir/checkrenew $TMP_DIR/ 59 | cp -rp $cloneDir/commander $TMP_DIR/ 60 | cp -rp $cloneDir/control_do.sh $TMP_DIR/control.sh 61 | cp -rp $cloneDir/cron_d $TMP_DIR/ 62 | cp -rp $cloneDir/mailer $TMP_DIR/ 63 | cp -rp $cloneDir/renew $TMP_DIR/ 64 | cp -rp $cloneDir/restore $TMP_DIR/ 65 | cp -rp $cloneDir/utils.sh $TMP_DIR/ 66 | 67 | echo 68 | -------------------------------------------------------------------------------- /build/tag_and_upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -euo pipefail 4 | 5 | cd $(dirname $0) 6 | 7 | REPO_BASE="hakwerk/labca" 8 | 9 | BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) 10 | if [ "$BRANCH" == "master" ] || [ "$BRANCH" == "main" ]; then 11 | TAG=$(git describe --always --tags 2>/dev/null) 12 | [[ $TAG == v* ]] && TAG="${TAG:1}" || /bin/true 13 | else 14 | TAG=$BRANCH 15 | fi 16 | 17 | LABCA_GUI_TAG="${REPO_BASE}-gui:$TAG" 18 | LABCA_GUI_LATEST="${REPO_BASE}-gui:latest" 19 | LABCA_BOULDER_TAG="${REPO_BASE}-boulder:$TAG" 20 | LABCA_BOULDER_LATEST="${REPO_BASE}-boulder:latest" 21 | LABCA_CONTROL_TAG="${REPO_BASE}-control:$TAG" 22 | LABCA_CONTROL_LATEST="${REPO_BASE}-control:latest" 23 | 24 | die() { 25 | echo $1 26 | exit 1 27 | } 28 | 29 | [ -f "tmp/labca-gui" ] || die "LabCA binary does not exist!" 30 | docker build -f Dockerfile-gui -t $LABCA_GUI_TAG . 31 | 32 | if [ "$BRANCH" == "master" ] || [ "$BRANCH" == "main" ]; then 33 | ID="$(docker images | grep "${REPO_BASE}-gui" | grep -v latest | head -n 1 | awk '{print $3}')" 34 | docker tag "$ID" $LABCA_GUI_LATEST 35 | fi 36 | 37 | cnt=$(ls -1 tmp/bin | wc -l) 38 | [ $cnt -gt 16 ] || die "Only found $cnt boulder binaries!" # ?? still correct?? 39 | docker build -f Dockerfile-boulder -t $LABCA_BOULDER_TAG . 40 | 41 | if [ "$BRANCH" == "master" ] || [ "$BRANCH" == "main" ]; then 42 | ID="$(docker images | grep "${REPO_BASE}-boulder" | grep -v latest | head -n 1 | awk '{print $3}')" 43 | docker tag "$ID" $LABCA_BOULDER_LATEST 44 | fi 45 | 46 | docker build -f Dockerfile-control -t $LABCA_CONTROL_TAG . 47 | 48 | if [ "$BRANCH" == "master" ] || [ "$BRANCH" == "main" ]; then 49 | ID="$(docker images | grep "${REPO_BASE}-control" | grep -v latest | head -n 1 | awk '{print $3}')" 50 | docker tag "$ID" $LABCA_CONTROL_LATEST 51 | fi 52 | 53 | echo 54 | if [ "$BRANCH" != "master" ] && [ "$BRANCH" != "main" ]; then 55 | echo "Not pushing to Dockerhub..." 56 | exit 57 | fi 58 | 59 | echo "Image ready, please login to allow Dockerhub push" 60 | echo TODO docker login 61 | 62 | echo 63 | echo "Pushing ${LABCA_GUI_TAG} to Dockerhub" 64 | echo TODO docker push ${LABCA_GUI_TAG} 65 | echo "Pushing ${LABCA_BOULDER_TAG} to Dockerhub" 66 | echo TODO docker push ${LABCA_BOULDER_TAG} 67 | echo "Pushing ${LABCA_CONTROL_TAG} to Dockerhub" 68 | echo TODO docker push ${LABCA_CONTROL_TAG} 69 | 70 | if [ "$BRANCH" == "master" ] || [ "$BRANCH" == "main" ]; then 71 | echo "Pushing ${LABCA_GUI_LATEST} to Dockerhub" 72 | echo TODO docker push ${LABCA_GUI_LATEST} 73 | echo "Pushing ${LABCA_BOULDER_LATEST} to Dockerhub" 74 | echo TODO docker push ${LABCA_BOULDER_LATEST} 75 | echo "Pushing ${LABCA_CONTROL_LATEST} to Dockerhub" 76 | echo TODO docker push ${LABCA_CONTROL_LATEST} 77 | fi 78 | -------------------------------------------------------------------------------- /build/tmp2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/startservers.py b/test/startservers.py 2 | index c42a1bb51..9eeac8906 100644 3 | --- a/test/startservers.py 4 | +++ b/test/startservers.py 5 | @@ -194,6 +194,9 @@ processes = [] 6 | challSrvProcess = None 7 | 8 | def install(race_detection): 9 | + return True 10 | + 11 | +def installOriginal(race_detection): 12 | # Pass empty BUILD_TIME and BUILD_ID flags to avoid constantly invalidating the 13 | # build cache with new BUILD_TIMEs, or invalidating it on merges with a new 14 | # BUILD_ID. 15 | -------------------------------------------------------------------------------- /checkcrl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd /var/www/html 6 | 7 | ROOT_CRL_FILE=/opt/boulder/labca/certs/webpki/root-01-crl.pem 8 | ROOT_CRL_NAME=$(basename $ROOT_CRL_FILE) 9 | [ -e "crl/$ROOT_CRL_NAME" ] || (cp $ROOT_CRL_FILE crl/ && touch crl/) 10 | [ $ROOT_CRL_FILE -ot "crl/$ROOT_CRL_NAME" ] || (cp $ROOT_CRL_FILE crl/ && touch crl/) 11 | 12 | if [ crl/ -nt certs/index.html ]; then 13 | echo "Updating certs/index.html with latest CRL info..." 14 | 15 | PKI_ROOT_CRL_LINK="" 16 | PKI_ROOT_CRL_VALIDITY="" 17 | if [ -e $ROOT_CRL_FILE ]; then 18 | PKI_ROOT_CRL_LINK="$ROOT_CRL_NAME" 19 | PKI_ROOT_CRL_VALIDITY="$(openssl crl -noout -in $ROOT_CRL_FILE -lastupdate | sed -e "s/.*=/Last Update: /")
$(openssl crl -noout -in $ROOT_CRL_FILE -nextupdate | sed -e "s/.*=/Next Update: /")" 20 | fi 21 | sed -i -e "s|<\!-- BEGIN PKI_ROOT_CRL_LINK -->.*<\!-- END PKI_ROOT_CRL_LINK -->|<\!-- BEGIN PKI_ROOT_CRL_LINK -->$PKI_ROOT_CRL_LINK<\!-- END PKI_ROOT_CRL_LINK -->|g" certs/index.html 22 | sed -i -e "s|<\!-- BEGIN PKI_ROOT_CRL_VALIDITY -->.*<\!-- END PKI_ROOT_CRL_VALIDITY -->|<\!-- BEGIN PKI_ROOT_CRL_VALIDITY -->$PKI_ROOT_CRL_VALIDITY<\!-- END PKI_ROOT_CRL_VALIDITY -->|g" certs/index.html 23 | 24 | PKI_INT_CERT_BASE="/opt/boulder/labca/certs/webpki/issuer-01-cert" 25 | INT_BASE_NAME=$(basename $PKI_INT_CERT_BASE) 26 | INT_CRL_NAME=${INT_BASE_NAME/-cert/-crl}.pem 27 | PKI_ISSUER_NAME_ID=$(grep issuer_name_id /opt/labca/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/,//g' | sed -e 's/\"//g') 28 | PKI_INT_CRL_LINK="" 29 | PKI_INT_CRL_VALIDITY="" 30 | if [ -e "crl/$PKI_ISSUER_NAME_ID.crl" ]; then 31 | [ -L "crl/$INT_CRL_NAME" ] || ln -sf $PKI_ISSUER_NAME_ID.crl crl/$INT_CRL_NAME 32 | PKI_INT_CRL_LINK="$INT_CRL_NAME" 33 | PKI_INT_CRL_VALIDITY="$(openssl crl -noout -inform der -in crl/$PKI_ISSUER_NAME_ID.crl -lastupdate | sed -e "s/.*=/Last Update: /")
$(openssl crl -noout -inform der -in crl/$PKI_ISSUER_NAME_ID.crl -nextupdate | sed -e "s/.*=/Next Update: /")" 34 | fi 35 | sed -i -e "s|<\!-- BEGIN PKI_INT_CRL_LINK -->.*<\!-- END PKI_INT_CRL_LINK -->|<\!-- BEGIN PKI_INT_CRL_LINK -->$PKI_INT_CRL_LINK<\!-- END PKI_INT_CRL_LINK -->|g" certs/index.html 36 | sed -i -e "s|<\!-- BEGIN PKI_INT_CRL_VALIDITY -->.*<\!-- END PKI_INT_CRL_VALIDITY -->|<\!-- BEGIN PKI_INT_CRL_VALIDITY -->$PKI_INT_CRL_VALIDITY<\!-- END PKI_INT_CRL_VALIDITY -->|g" certs/index.html 37 | fi 38 | -------------------------------------------------------------------------------- /checkrenew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | RENEW=30 6 | TODAY=`date '+%Y_%m_%d'` 7 | 8 | echo "Running cron-$(basename $0) for ${TODAY}..." 9 | 10 | if ! expires=`openssl x509 -checkend $[ 86400 * $RENEW ] -noout -in /etc/nginx/ssl/labca_cert.pem`; then 11 | echo " renewing!" 12 | /opt/labca/renew 13 | fi 14 | 15 | /opt/labca/bin/labca-gui -config /opt/labca/data/config.json -renewcrl $RENEW 16 | -------------------------------------------------------------------------------- /control.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | get_fqdn() { 6 | local file_fqdn="" 7 | if [ -e /opt/labca/data/config.json ]; then 8 | file_fqdn=$(grep fqdn /opt/labca/data/config.json 2>/dev/null | cut -d ":" -f 2- | tr -d " \",") 9 | fi 10 | if [ "$file_fqdn" == "" ]; then 11 | if [ "$LABCA_FQDN" == "notset" ]; then 12 | echo "ERROR: environment variable LABCA_FQDN is not set!" 13 | exit 1 14 | else 15 | echo -e "{\n \"config\": {\n \"complete\": false\n },\n \"labca\": {\n \"fqdn\": \"$LABCA_FQDN\"\n },\n \"version\": \"\"\n}" > /opt/labca/data/config.json 16 | fi 17 | elif [ "$LABCA_FQDN" != "notset" ] && [ "$LABCA_FQDN" != "$file_fqdn" ]; then 18 | echo "WARNING: environment variable LABCA_FQDN ('$LABCA_FQDN') does not match config file. Using '$file_fqdn'..." 19 | export LABCA_FQDN=$file_fqdn 20 | fi 21 | } 22 | 23 | # TODO: install docker should be done in pre-baked image 24 | install_docker() { 25 | export DEBIAN_FRONTEND=noninteractive 26 | apt update 27 | apt install -y apt-transport-https ca-certificates curl software-properties-common 28 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - 29 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" 30 | apt-cache policy docker-ce 31 | apt update 32 | apt install -y docker-ce 33 | } 34 | 35 | selfsigned_cert() { 36 | pushd /etc/nginx/ssl >/dev/null 37 | openssl req -x509 -nodes -sha256 -newkey rsa:2048 -keyout labca_key.pem -out labca_cert.pem -days 7 \ 38 | -subj "/O=LabCA/CN=$LABCA_FQDN" -reqexts SAN -extensions SAN \ 39 | -config <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nbasicConstraints=CA:FALSE\nnsCertType=server\nsubjectAltName=DNS:$LABCA_FQDN")) 40 | popd >/dev/null 41 | } 42 | 43 | renew_near_expiry() { 44 | pushd /etc/nginx/ssl >/dev/null 45 | if ! expires=$(openssl x509 -checkend 86400 -noout -in /etc/nginx/ssl/labca_cert.pem); then 46 | hash=$(openssl x509 -hash -noout -in /etc/nginx/ssl/labca_cert.pem) 47 | issuer_hash=$(openssl x509 -issuer_hash -noout -in /etc/nginx/ssl/labca_cert.pem) 48 | if [ "$hash" == "$issuer_hash" ]; then 49 | selfsigned_cert 50 | else 51 | echo "acme-request" | /opt/labca/commander 52 | fi 53 | fi 54 | popd >/dev/null 55 | } 56 | 57 | # TODO: install cron should be done in pre-baked image 58 | start_cron() { 59 | apt update 60 | apt install -y cron 61 | [ -e /opt/boulder/labca/setup_complete ] && [ ! -e /etc/cron.d/labca ] && ln -sf /opt/labca/cron_d /etc/cron.d/labca || true 62 | chmod g-w /opt/labca/cron_d 63 | [ -e /opt/logs/cron.log ] || touch /opt/logs/cron.log 64 | tail -f -n0 /opt/logs/cron.log & 65 | service cron start 66 | } 67 | 68 | # TODO: install ucspi-tcp should be done in pre-baked image 69 | serve_commander() { 70 | apt update 71 | apt install -y ucspi-tcp 72 | cd /opt/boulder/labca 73 | /opt/labca/gui/apply-boulder 74 | cd - 75 | echo "Start serving commander script..." 76 | tcpserver 0.0.0.0 3030 /opt/labca/commander 77 | } 78 | 79 | main() { 80 | mkdir -p /opt/logs 81 | 82 | get_fqdn 83 | 84 | docker ps &>/dev/null || install_docker 85 | 86 | # Use python 3.10 to prevent warnings from certbot 87 | add-apt-repository -y ppa:deadsnakes/ppa 88 | apt update 89 | apt install -y python3.10-venv 90 | python3.10 -m venv /opt/certbot 91 | /opt/certbot/bin/pip install --upgrade pip 92 | /opt/certbot/bin/pip install certbot 93 | ln -sf /opt/certbot/bin/certbot /usr/bin/certbot 94 | 95 | [ -e /etc/nginx/ssl/labca_cert.pem ] || selfsigned_cert 96 | renew_near_expiry 97 | 98 | start_cron 99 | 100 | serve_commander 101 | } 102 | 103 | main "$@" 104 | -------------------------------------------------------------------------------- /cron_d: -------------------------------------------------------------------------------- 1 | # /etc/cron.d/labca: crontab entries for the LabCA application 2 | SHELL=/bin/bash 3 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 4 | 5 | 1 7 * * * root /opt/labca/mailer &>>/opt/logs/cron.log 6 | 5 7 * * * root /opt/labca/checkrenew &>>/opt/logs/cron.log 7 | 7 7 * * Sun root /opt/boulder/labca/certs/generate.sh &>>/opt/logs/cron.log 8 | 11 7 * * Mon root /opt/labca/backup cron &>>/opt/logs/cron.log 9 | */5 * * * * root /opt/labca/checkcrl &>>/opt/logs/cron.log 10 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | labca-gui (0.9) unstable; urgency=medium 2 | 3 | * See https://github.com/hakwerk/labca/releases 4 | 5 | -- hakwerk Fri, 02 Sep 2022 16:19:08 +0000 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: labca-gui 2 | Section: utils 3 | Priority: optional 4 | Maintainer: hakwerk 5 | Build-Depends: debhelper (>= 10) 6 | Standards-Version: 4.1.2 7 | Homepage: https://github.com/hakwerk/labca 8 | Vcs-Browser: https://github.com/hakwerk/labca.git 9 | Vcs-Git: https://github.com/hakwerk/labca.git 10 | 11 | Package: labca-gui 12 | Architecture: any 13 | Depends: ${misc:Depends} 14 | Description: Standalone ACME GUI 15 | Standalone web GUI for ACME servers. 16 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: labca-gui 3 | Source: https://github.com/hakwerk/labca 4 | 5 | Files: 6 | * 7 | Copyright: 2022-2025 hakwerk 8 | License: MPL-2.0 and Commons-Clause-1.0 9 | 10 | License: MPL-2.0 11 | Copyright (c) 2022-2025 hakwerk 12 | . 13 | Licensed under the Mozilla Public License 2.0 (the "License"); 14 | you may not use this file except in compliance with the License. 15 | You may obtain a copy of the License at 16 | . 17 | https://www.mozilla.org/en-US/MPL/2.0/ 18 | . 19 | Unless required by applicable law or agreed to in writing, software 20 | distributed under the License is distributed on an "AS IS" BASIS, 21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | See the License for the specific language governing permissions and 23 | limitations under the License. 24 | 25 | License: Commons-Clause-1.0 26 | "Commons Clause" License Condition v1.0 27 | . 28 | The Software is provided to you by the Licensor under the License, as 29 | defined below, subject to the following condition. 30 | . 31 | Without limiting other conditions in the License, the grant of rights 32 | under the License will not include, and the License does not grant to 33 | you, the right to Sell the Software. 34 | . 35 | For purposes of the foregoing, "Sell" means practicing any or all of the 36 | rights granted to you under the License to provide to third parties, for 37 | a fee or other consideration (including without limitation fees for 38 | hosting or consulting/ support services related to the Software), a 39 | product or service whose value derives, entirely or substantially, from 40 | the functionality of the Software. Any license notice or attribution 41 | required by the License must also include this Commons Cause License 42 | Condition notice. 43 | . 44 | Software: LabCA 45 | . 46 | License: Mozilla Public License 2.0 47 | . 48 | Licensor: https://github.com/hakwerk 49 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | override_dh_install-arch: 4 | dh_install --arch 5 | 6 | build: 7 | dh build 8 | 9 | override_dh_auto_build: 10 | dh_auto_build -- build 11 | 12 | %: 13 | dh $@ 14 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /gui/apply: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | baseDir=$(cd $(dirname $0) && pwd) 6 | dataDir="/opt/boulder/labca/certs/webpki" 7 | 8 | export PKI_ROOT_CERT_BASE="$dataDir/root-01-cert" 9 | export PKI_INT_CERT_BASE="$dataDir/issuer-01-cert" 10 | 11 | cd /opt/boulder/labca 12 | $baseDir/apply-boulder 13 | 14 | cd /var/www/html 15 | 16 | PKI_ROOT_CRL_FILE=${PKI_ROOT_CERT_BASE/-cert/-crl}.pem 17 | if [ -e "$PKI_ROOT_CRL_FILE" ]; then 18 | cp $PKI_ROOT_CRL_FILE crl/ 19 | else 20 | echo "WARNING: no Root CRL file present - please upload one from the manage page" 21 | fi 22 | cp $PKI_ROOT_CERT_BASE.pem certs/ 23 | cp $PKI_INT_CERT_BASE.pem certs/ 24 | 25 | $baseDir/apply-nginx 26 | -------------------------------------------------------------------------------- /gui/hsm_standalone.go: -------------------------------------------------------------------------------- 1 | //go:build standalone 2 | 3 | package main 4 | 5 | import "crypto" 6 | 7 | const CERT_FILES_PATH = "/opt/boulder/labca/certs/webpki/" 8 | 9 | type HSMConfig struct { 10 | Module string 11 | UserPIN string 12 | SOPIN string 13 | SlotID string 14 | Label string 15 | } 16 | 17 | func (cfg *HSMConfig) Initialize(ca_type string, seqnr string) { 18 | } 19 | 20 | func (cfg *HSMConfig) CreateSlot() error { 21 | return nil 22 | } 23 | 24 | func (cfg *HSMConfig) GetPrivateKey() ([]byte, error) { 25 | return nil, nil 26 | } 27 | 28 | func (cfg *HSMConfig) ClearAll() error { 29 | return nil 30 | } 31 | 32 | func (cfg *HSMConfig) ImportKeyCert(keyFile, certFile string) (crypto.PublicKey, error) { 33 | return nil, nil 34 | } 35 | -------------------------------------------------------------------------------- /gui/restart_control: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd /opt/boulder 6 | docker compose restart control 7 | -------------------------------------------------------------------------------- /gui/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | [ -d bin ] || mkdir bin 6 | 7 | [ -e bin/labca-gui ] || set -ev 8 | if [ ! -e bin/labca-gui ]; then 9 | go mod download 10 | 11 | go build -buildvcs=false -o bin/labca-gui -ldflags="-X 'main.standaloneVersion=$GIT_VERSION'" 12 | fi 13 | 14 | export DEBIAN_FRONTEND=noninteractive 15 | apt-get update 16 | apt-get install -y iproute2 zip unzip 17 | apt-get install -y apt-transport-https ca-certificates curl software-properties-common gnupg 18 | install -m 0755 -d /etc/apt/keyrings 19 | [ ! -e /etc/apt/keyrings/docker.gpg ] || mv /etc/apt/keyrings/docker.gpg /etc/apt/keyrings/docker.gpg_PREV 20 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 21 | chmod a+r /etc/apt/keyrings/docker.gpg 22 | echo \ 23 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ 24 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 25 | tee /etc/apt/sources.list.d/docker.list > /dev/null 26 | apt-get update 27 | apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin 28 | 29 | dcver=$(docker compose version | grep v2.19 | wc -l) 30 | if [ "$dcver" != "0" ]; then 31 | dc18=$(apt list docker-compose-plugin -a 2>/dev/null | grep 2.18 | cut -d ' ' -f 2) 32 | apt install -y --allow-downgrades docker-compose-plugin=${dc18} 33 | fi 34 | 35 | bin/labca-gui 36 | -------------------------------------------------------------------------------- /gui/static/css/dataTables.responsive.css: -------------------------------------------------------------------------------- 1 | table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child, 2 | table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child { 3 | position: relative; 4 | padding-left: 30px; 5 | cursor: pointer; 6 | } 7 | table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child:before, 8 | table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child:before { 9 | top: 8px; 10 | left: 4px; 11 | height: 16px; 12 | width: 16px; 13 | display: block; 14 | position: absolute; 15 | color: white; 16 | border: 2px solid white; 17 | border-radius: 16px; 18 | text-align: center; 19 | line-height: 14px; 20 | box-shadow: 0 0 3px #444; 21 | box-sizing: content-box; 22 | content: '+'; 23 | background-color: #31b131; 24 | } 25 | table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child.dataTables_empty:before, 26 | table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child.dataTables_empty:before { 27 | display: none; 28 | } 29 | table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td:first-child:before, 30 | table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th:first-child:before { 31 | content: '-'; 32 | background-color: #d33333; 33 | } 34 | table.dataTable.dtr-inline.collapsed > tbody > tr.child td:before { 35 | display: none; 36 | } 37 | table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child, 38 | table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child { 39 | padding-left: 27px; 40 | } 41 | table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child:before, 42 | table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child:before { 43 | top: 5px; 44 | left: 4px; 45 | height: 14px; 46 | width: 14px; 47 | border-radius: 14px; 48 | line-height: 12px; 49 | } 50 | table.dataTable.dtr-column > tbody > tr > td.control, 51 | table.dataTable.dtr-column > tbody > tr > th.control { 52 | position: relative; 53 | cursor: pointer; 54 | } 55 | table.dataTable.dtr-column > tbody > tr > td.control:before, 56 | table.dataTable.dtr-column > tbody > tr > th.control:before { 57 | top: 50%; 58 | left: 50%; 59 | height: 16px; 60 | width: 16px; 61 | margin-top: -10px; 62 | margin-left: -10px; 63 | display: block; 64 | position: absolute; 65 | color: white; 66 | border: 2px solid white; 67 | border-radius: 16px; 68 | text-align: center; 69 | line-height: 14px; 70 | box-shadow: 0 0 3px #444; 71 | box-sizing: content-box; 72 | content: '+'; 73 | background-color: #31b131; 74 | } 75 | table.dataTable.dtr-column > tbody > tr.parent td.control:before, 76 | table.dataTable.dtr-column > tbody > tr.parent th.control:before { 77 | content: '-'; 78 | background-color: #d33333; 79 | } 80 | table.dataTable > tbody > tr.child { 81 | padding: 0.5em 1em; 82 | } 83 | table.dataTable > tbody > tr.child:hover { 84 | background: transparent !important; 85 | } 86 | table.dataTable > tbody > tr.child ul { 87 | display: inline-block; 88 | list-style-type: none; 89 | margin: 0; 90 | padding: 0; 91 | } 92 | table.dataTable > tbody > tr.child ul li { 93 | border-bottom: 1px solid #efefef; 94 | padding: 0.5em 0; 95 | } 96 | table.dataTable > tbody > tr.child ul li:first-child { 97 | padding-top: 0; 98 | } 99 | table.dataTable > tbody > tr.child ul li:last-child { 100 | border-bottom: none; 101 | } 102 | table.dataTable > tbody > tr.child span.dtr-title { 103 | display: inline-block; 104 | min-width: 75px; 105 | font-weight: bold; 106 | } 107 | -------------------------------------------------------------------------------- /gui/static/css/labca.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: #d9534f; 3 | } 4 | 5 | .warning { 6 | color: #f0ad4e; 7 | } 8 | 9 | .success { 10 | color: #5cb85c; 11 | } 12 | 13 | .strength-none { 14 | background-color: #bbb; 15 | } 16 | 17 | .strength-bad { 18 | background-color: #d9534f; 19 | } 20 | 21 | .strength-med { 22 | background-color: #f0ad4e; 23 | } 24 | 25 | .strength-good { 26 | background-color: #5cb85c; 27 | } 28 | 29 | .footer { 30 | padding-top: 20px; 31 | } 32 | 33 | .hidden { 34 | display: none; 35 | } 36 | 37 | .dataTables_filter, .dataTables_paginate { 38 | float: right; 39 | } 40 | 41 | .pagination { 42 | margin: 0; 43 | } 44 | 45 | .fixed-corner { 46 | position: fixed; 47 | bottom: 0; 48 | right: 2px; 49 | padding: 0 7px; 50 | font-style: italic; 51 | font-size: small; 52 | } 53 | 54 | #fileData { 55 | margin-top: 1em; 56 | } 57 | 58 | .admin-login { 59 | color: #555; 60 | } 61 | 62 | a.public { 63 | color: forestgreen; 64 | } 65 | 66 | i.ext-link { 67 | font-size: smaller; 68 | } 69 | 70 | p.caption { 71 | padding-top: 0.8em; 72 | } 73 | 74 | .mb0 { 75 | margin-bottom: 0px; 76 | } 77 | 78 | .mb5 { 79 | margin-bottom: 5px; 80 | } 81 | 82 | .mb15 { 83 | margin-bottom: 15px; 84 | } 85 | 86 | .ml20 { 87 | margin-left: 20px; 88 | } 89 | 90 | .mt5 { 91 | margin-top: 5px; 92 | } 93 | 94 | .mt10 { 95 | margin-top: 10px; 96 | } 97 | 98 | .mt20 { 99 | margin-top: 20px; 100 | } 101 | 102 | .p48>tbody>tr>td { 103 | padding: 4px 8px; 104 | } 105 | 106 | td.pad-low-bottom { 107 | padding-bottom: 4px !important; 108 | } 109 | 110 | td.pad-low { 111 | padding-top: 4px !important; 112 | padding-bottom: 4px !important; 113 | } 114 | 115 | td.pad-low-top { 116 | padding-top: 4px !important; 117 | } 118 | 119 | .btn-reg { 120 | width: 5em; 121 | } 122 | 123 | .btn-wide { 124 | width: 10em; 125 | } 126 | 127 | .btn-new-issuer { 128 | width: 7em; 129 | } 130 | 131 | .btn-6em { 132 | width: 6em; 133 | } 134 | 135 | .btn-right { 136 | float: right; 137 | } 138 | 139 | .vmiddle { 140 | vertical-align: middle !important; 141 | } 142 | 143 | .center { 144 | text-align: center !important; 145 | } 146 | 147 | .bd-modal-lg .modal-dialog { 148 | display: table; 149 | position: relative; 150 | margin: 0 auto; 151 | top: calc(40% - 24px); 152 | } 153 | 154 | .bd-modal-lg .modal-dialog .modal-content { 155 | text-align: center; 156 | border: none; 157 | } 158 | 159 | .modal-content { 160 | padding: 6px; 161 | width: 674px; 162 | } 163 | 164 | .modal-content > textarea { 165 | width: 660px; 166 | height: 550px; 167 | } 168 | 169 | .non-fluid { 170 | width: auto !important; 171 | } 172 | 173 | .vizpwd { 174 | float: left; 175 | margin-left: 180px; 176 | margin-top: -23px; 177 | position: relative; 178 | z-index: 2; 179 | } 180 | 181 | a.update { 182 | color: darkgreen; 183 | } 184 | 185 | a.update:hover { 186 | text-decoration: none; 187 | } 188 | 189 | .bootstrap-dialog .modal-header { 190 | border-top-left-radius: 4px; 191 | border-top-right-radius: 4px; 192 | } 193 | 194 | .bootstrap-dialog.type-primary .modal-header { 195 | background-color: #f5f5f5; 196 | } 197 | 198 | .rel-notes-title { 199 | font-weight: bold; 200 | } 201 | 202 | pre.json { 203 | background-color: transparent; 204 | border: none; 205 | margin: 0px; 206 | padding: 0px; 207 | } 208 | 209 | .hide-overflow { 210 | overflow: hidden; 211 | } 212 | 213 | #modal-spinner { 214 | z-index: 10000; 215 | } 216 | 217 | button.close { 218 | margin-top: -40px; 219 | } 220 | 221 | .form-small-inline { 222 | display: inline-block; 223 | width: 5em !important; 224 | } -------------------------------------------------------------------------------- /gui/static/css/metisMenu.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | * metismenu - v1.1.3 3 | * Easy menu jQuery plugin for Twitter Bootstrap 3 4 | * https://github.com/onokumus/metisMenu 5 | * 6 | * Made by Osman Nuri Okumus 7 | * Under MIT License 8 | */ 9 | 10 | .arrow{float:right;line-height:1.42857}.glyphicon.arrow:before{content:"\e079"}.active>a>.glyphicon.arrow:before{content:"\e114"}.fa.arrow:before{content:"\f104"}.active>a>.fa.arrow:before{content:"\f107"}.plus-times{float:right}.fa.plus-times:before{content:"\f067"}.active>a>.fa.plus-times{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.plus-minus{float:right}.fa.plus-minus:before{content:"\f067"}.active>a>.fa.plus-minus:before{content:"\f068"} -------------------------------------------------------------------------------- /gui/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /gui/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /gui/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /gui/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /gui/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /gui/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /gui/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /gui/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /gui/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /gui/static/img/fav-admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/img/fav-admin.png -------------------------------------------------------------------------------- /gui/static/img/fav-public.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/img/fav-public.png -------------------------------------------------------------------------------- /gui/static/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/img/spinner.gif -------------------------------------------------------------------------------- /gui/static/img/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakwerk/labca/ec77c14f6233d17efbde9b91c63f45bb4fdd3ecf/gui/static/img/warning.png -------------------------------------------------------------------------------- /gui/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | LabCA 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 53 | 54 |
55 |
56 |
57 |

LabCA

58 | 59 |

60 | LabCA is a private CA (Certificate Authority) for use inside an organization, 61 | i.e. for creating HTTPS/SSL certificates for machines that cannot be reached via the open internet. It is based on Let's Encrypt™ 62 | code for ACMEv2 (Automated Certificate Management Environment) so all modern LE clients should work.
LabCA should NOT 63 | be used on the open internet, please use the official 64 | Let's Encrypt™ 65 | instance for that. 66 |

67 |

68 | To trust the certificates provided by LabCA, all your client devices should install 69 | the root certificate (.pem format) in their 70 | Trusted Root Certification Authorities store.
71 |

72 |
73 |

More information

74 |

Additional information about this LabCA instance can be found on these pages:
75 | Terms - the Usage Terms
76 | CPS - the Certification Practice Statement 77 |

78 |
79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /gui/static/js/dataTables.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | DataTables Bootstrap 3 integration 3 | ©2011-2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(a,b,c){a instanceof String&&(a=String(a));for(var e=a.length,d=0;d<'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});a.extend(d.ext.classes, 12 | {sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm",sProcessing:"dataTables_processing panel panel-default"});d.ext.renderer.pageButton.bootstrap=function(f,l,A,B,m,t){var u=new d.Api(f),C=f.oClasses,n=f.oLanguage.oPaginate,D=f.oLanguage.oAria.paginate||{},h,k,v=0,y=function(q,w){var x,E=function(p){p.preventDefault();a(p.currentTarget).hasClass("disabled")||u.page()==p.data.action||u.page(p.data.action).draw("page")}; 13 | var r=0;for(x=w.length;r",{"class":C.sPageButton+" "+k,id:0===A&&"string"===typeof g?f.sTableId+"_"+g:null}).append(a("", 14 | {href:"#","aria-controls":f.sTableId,"aria-label":D[g],"data-dt-idx":v,tabindex:f.iTabIndex}).html(h)).appendTo(q);f.oApi._fnBindAction(F,{action:g},E);v++}}}};try{var z=a(l).find(c.activeElement).data("dt-idx")}catch(q){}y(a(l).empty().html('
    ').children("ul"),B);z!==e&&a(l).find("[data-dt-idx="+z+"]").trigger("focus")};return d}); 15 | -------------------------------------------------------------------------------- /gui/static/js/jquery.stickytabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery Plugin: Sticky Tabs 3 | * 4 | * @author Aidan Lister 5 | * @version 1.2.0 6 | */ 7 | (function ( $ ) { 8 | $.fn.stickyTabs = function( options ) { 9 | var context = this 10 | 11 | var settings = $.extend({ 12 | getHashCallback: function(hash, btn) { return hash }, 13 | selectorAttribute: "href", 14 | backToTop: false, 15 | initialTab: $('li.active > a', context) 16 | }, options ); 17 | 18 | // Show the tab corresponding with the hash in the URL, or the first tab. 19 | var showTabFromHash = function() { 20 | var hash = settings.selectorAttribute == "href" ? window.location.hash : window.location.hash.substring(1); 21 | if (hash != '') { 22 | var selector = hash ? 'a[' + settings.selectorAttribute +'="' + hash + '"]' : settings.initialTab; 23 | $(selector, context).tab('show'); 24 | setTimeout(backToTop, 1); 25 | } 26 | } 27 | 28 | // We use pushState if it's available so the page won't jump, otherwise a shim. 29 | var changeHash = function(hash) { 30 | if (history && history.pushState) { 31 | history.pushState(null, null, window.location.pathname + window.location.search + '#' + hash); 32 | } else { 33 | scrollV = document.body.scrollTop; 34 | scrollH = document.body.scrollLeft; 35 | window.location.hash = hash; 36 | document.body.scrollTop = scrollV; 37 | document.body.scrollLeft = scrollH; 38 | } 39 | } 40 | 41 | var backToTop = function() { 42 | if (settings.backToTop === true) { 43 | window.scrollTo(0, 0); 44 | } 45 | } 46 | 47 | // Set the correct tab when the page loads 48 | showTabFromHash(); 49 | 50 | // Set the correct tab when a user uses their back/forward button 51 | $(window).on('hashchange', showTabFromHash); 52 | 53 | // Change the URL when tabs are clicked 54 | $('a', context).on('click', function(e) { 55 | var hash = this.href.split('#')[1]; 56 | if (typeof hash != 'undefined' && hash != '') { 57 | var adjustedhash = settings.getHashCallback(hash, this); 58 | changeHash(adjustedhash); 59 | setTimeout(backToTop, 1); 60 | } 61 | }); 62 | 63 | return this; 64 | }; 65 | }( jQuery )); 66 | -------------------------------------------------------------------------------- /gui/static/js/metisMenu.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * metismenu - v1.1.3 3 | * Easy menu jQuery plugin for Twitter Bootstrap 3 4 | * https://github.com/onokumus/metisMenu 5 | * 6 | * Made by Osman Nuri Okumus 7 | * Under MIT License 8 | */ 9 | !function(a,b,c){function d(b,c){this.element=a(b),this.settings=a.extend({},f,c),this._defaults=f,this._name=e,this.init()}var e="metisMenu",f={toggle:!0,doubleTapToGo:!1};d.prototype={init:function(){var b=this.element,d=this.settings.toggle,f=this;this.isIE()<=9?(b.find("li.active").has("ul").children("ul").collapse("show"),b.find("li").not(".active").has("ul").children("ul").collapse("hide")):(b.find("li.active").has("ul").children("ul").addClass("collapse in"),b.find("li").not(".active").has("ul").children("ul").addClass("collapse")),f.settings.doubleTapToGo&&b.find("li.active").has("ul").children("a").addClass("doubleTapToGo"),b.find("li").has("ul").children("a").on("click."+e,function(b){return b.preventDefault(),f.settings.doubleTapToGo&&f.doubleTapToGo(a(this))&&"#"!==a(this).attr("href")&&""!==a(this).attr("href")?(b.stopPropagation(),void(c.location=a(this).attr("href"))):(a(this).parent("li").toggleClass("active").children("ul").collapse("toggle"),void(d&&a(this).parent("li").siblings().removeClass("active").children("ul.in").collapse("hide")))})},isIE:function(){for(var a,b=3,d=c.createElement("div"),e=d.getElementsByTagName("i");d.innerHTML="",e[0];)return b>4?b:a},doubleTapToGo:function(a){var b=this.element;return a.hasClass("doubleTapToGo")?(a.removeClass("doubleTapToGo"),!0):a.parent().children("ul").length?(b.find(".doubleTapToGo").removeClass("doubleTapToGo"),a.addClass("doubleTapToGo"),!1):void 0},remove:function(){this.element.off("."+e),this.element.removeData(e)}},a.fn[e]=function(b){return this.each(function(){var c=a(this);c.data(e)&&c.data(e).remove(),c.data(e,new d(this,b))}),this}}(jQuery,window,document); -------------------------------------------------------------------------------- /gui/static/js/pwdux.js: -------------------------------------------------------------------------------- 1 | 2 | function pwduxInit(barSel, pwdSel) { 3 | $(barSel).css("width", $(pwdSel).outerWidth()); 4 | 5 | $(".vizpwd").each(function(idx) { 6 | if ( $(this).prev().outerWidth() > 0 ) { 7 | $(this).css("margin-left", $(this).prev().outerWidth() - $(this).outerWidth() - 4); 8 | } 9 | }); 10 | } 11 | 12 | function pwduxReset(barSel) { 13 | $(barSel + ' div').removeClass().addClass('progress-bar'); 14 | $(barSel + ' div').addClass('strength-none').css('width', "0%"); 15 | } 16 | 17 | function pwduxHandlers(barSel, pwdSel, blckSels) { 18 | $(pwdSel).keyup(function() { 19 | $(barSel).css('width', $(pwdSel).outerWidth()); 20 | $(barSel + ' div').removeClass().addClass('progress-bar'); 21 | 22 | if ($(pwdSel).val().length == 0) { 23 | $(barSel + ' div').addClass('strength-none').css('width', "0%"); 24 | } else { 25 | blacklist = ['labca', 'acme']; 26 | if (blckSels) { 27 | blckSels.forEach(function(blckSel) { 28 | v = $(blckSel).val(); 29 | if (v.indexOf('@') > 0) { 30 | d = v.split('@')[1]; 31 | v = v.split('@')[0]; 32 | for (i=0; i= 3) { 45 | cls = 'strength-good'; 46 | } else if (strength >= 2) { 47 | cls = 'strength-med'; 48 | } 49 | $(barSel + ' div').addClass(cls).css('width', (100*strength/4)+'%'); 50 | } 51 | }); 52 | 53 | $('input[type=password]').focus(function() { 54 | if ($(this).next().hasClass('vizpwd') && $(this).val() == "") { 55 | $(this).next().data('active', 1); 56 | } 57 | }); 58 | 59 | $('input[type=password]').keyup(function() { 60 | if ($(this).next().hasClass('vizpwd') && $(this).val() == "") { 61 | $(this).next().data('active', 1); 62 | } 63 | }); 64 | 65 | $('input[type=password]').blur(function() { 66 | if ($(this).next().hasClass('vizpwd')) { 67 | $(this).next().data('active', 0); 68 | } 69 | }); 70 | 71 | $('.vizpwd').mousedown(function() { 72 | if ($(this).data('active')) { 73 | $(this).prev().attr('type', 'text'); 74 | viz = $(this); 75 | setTimeout(function() { 76 | viz.data('active', 1); 77 | }, 100); 78 | } 79 | }); 80 | 81 | $('.vizpwd').mouseup(function() { 82 | $(this).prev().attr('type', 'password'); 83 | $(this).prev().focus(); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /gui/static/js/sb-admin-2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin 2 v3.3.7+1 (http://startbootstrap.com/template-overviews/sb-admin-2) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | $(function(){$("#side-menu").metisMenu()}),$(function(){$(window).bind("load resize",function(){var i=50,n=this.window.innerWidth>0?this.window.innerWidth:this.screen.width;n<768?($("div.navbar-collapse").addClass("collapse"),i=100):$("div.navbar-collapse").removeClass("collapse");var e=(this.window.innerHeight>0?this.window.innerHeight:this.screen.height)-1;e-=i,e<1&&(e=1),e>i&&$("#page-wrapper").css("min-height",e+"px")});for(var i=window.location,n=$("ul.nav a").filter(function(){return this.href==i}).addClass("active").parent();;){if(!n.is("li"))break;n=n.parent().addClass("in").parent()}}); -------------------------------------------------------------------------------- /gui/static/rate-limits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Rate Limits | LabCA 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 | 55 | 56 |
    57 |
    58 |
    59 |

    Rate Limits

    60 | 61 |

    62 | It is unlikely that you hit the rate limit mechanism for your selected domain, as it is set to allow 10,000 certificates in LabCA. 63 |

    64 |

    65 | If this LabCA instance is set up to (also) allow official domains (not recommended), then for the other domains the main limit is 66 | Certificates per Registered Domain: 5 per 24 hours. As per the 67 | Let's Encrypt™ rate limits page , 68 | a registered domain is, generally speaking, the part of the domain you purchased from your domain name registrar. For instance, 69 | in the name www.example.com, the registered domain is example.com. 70 | In new.blog.example.co.uk, the registered domain is example.co.uk. 71 |

    72 |

    73 | The other limit is the Duplicate Certificate limit of 2 per 90 days. This applies to renewals when the old dertificate 74 | is still valid. 75 |

    76 |

    77 | Revoking certificates does not reset rate limits, because the resources used to issue those certificates have already been 78 | consumed. 79 |

    80 |
    81 |
    82 |
    83 |
    84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /gui/templates/base.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ block "title" . }}{{ if .Title }}{{ .Title }} | {{ end }}{{ .WebTitle }}{{ end }} 11 | {{ block "css" . }}{{ template "partials/css.tmpl" . }}{{ end }} 12 | 13 | {{ block "head" . }}{{ .Head }}{{ end }} 14 | 15 | 16 | 17 |
    18 | {{ block "nav" . }}{{ template "partials/nav.tmpl" . }}{{ end }} 19 | 20 |
    21 |
    22 |
    23 |
    24 | {{ block "body" . }}{{ .Body }}{{ end }} 25 |
    26 |
    27 |
    28 | 29 |
    30 |
    31 | 34 | 37 |
    38 |
    39 | 40 |
    41 |
    42 | 43 | {{ block "js" . }}{{ template "partials/js.tmpl" . }}{{ end }} 44 | {{ block "tail" . }}{{ .Tail }}{{ end }} 45 | 46 | 47 | -------------------------------------------------------------------------------- /gui/templates/cert-ceremonies/issuer-cert.yaml: -------------------------------------------------------------------------------- 1 | ceremony-type: intermediate 2 | pkcs11: 3 | module: {{ .Module }} 4 | pin: {{ .UserPIN }} 5 | signing-key-slot: {{ .RootSlotID }} 6 | signing-key-label: {{ .RootLabel }} 7 | inputs: 8 | public-key-path: {{ .Path }}issuer-{{ .SeqNr }}-pubkey.pem 9 | issuer-certificate-path: {{ .Path }}root-{{ .RootSeqNr }}-cert.pem 10 | outputs: 11 | certificate-path: {{ .Path }}issuer-{{ .SeqNr }}-cert.pem 12 | certificate-profile: 13 | signature-algorithm: {{ .SignAlgorithm }} 14 | common-name: {{ .CommonName }} 15 | organization: {{ .OrgName }} 16 | country: {{ .Country }} 17 | not-before: {{ .NotBefore }} 18 | not-after: {{ .NotAfter }} 19 | crl-url: {{ .CrlUrl }} 20 | issuer-url: {{ .IssuerUrl }} 21 | policies: 22 | - oid: 2.23.140.1.2.1 23 | key-usages: 24 | - Digital Signature 25 | - Cert Sign 26 | - CRL Sign 27 | -------------------------------------------------------------------------------- /gui/templates/cert-ceremonies/issuer-key.yaml: -------------------------------------------------------------------------------- 1 | ceremony-type: key 2 | pkcs11: 3 | module: {{ .Module }} 4 | pin: {{ .UserPIN }} 5 | store-key-in-slot: {{ .SlotID }} 6 | store-key-with-label: {{ .Label }} 7 | key: 8 | type: {{ .KeyType }} 9 | {{ if eq .KeyType "rsa" }} 10 | rsa-mod-length: {{ .KeyParam }} 11 | {{ else }} 12 | ecdsa-curve: {{ .KeyParam }} 13 | {{ end }} 14 | {{ if eq .Extractable "true" }} 15 | extractable: true 16 | {{ end }} 17 | outputs: 18 | public-key-path: {{ .Path }}issuer-{{ .SeqNr }}-pubkey.pem 19 | pkcs11-config-path: {{ .Path }}issuer-{{ .SeqNr }}.pkcs11.json 20 | -------------------------------------------------------------------------------- /gui/templates/cert-ceremonies/root-crl.yaml: -------------------------------------------------------------------------------- 1 | ceremony-type: crl 2 | pkcs11: 3 | module: {{ .Module }} 4 | pin: {{ .UserPIN }} 5 | signing-key-slot: {{ .RootSlotID }} 6 | signing-key-label: {{ .RootLabel }} 7 | inputs: 8 | issuer-certificate-path: {{ .Path }}root-{{ .RootSeqNr }}-cert.pem 9 | outputs: 10 | crl-path: {{ .Path }}root-{{ .RootSeqNr }}-crl.pem 11 | crl-profile: 12 | this-update: {{ .ThisUpdate }} 13 | next-update: {{ .NextUpdate }} 14 | number: {{ .CrlNumber }} 15 | -------------------------------------------------------------------------------- /gui/templates/cert-ceremonies/root.yaml: -------------------------------------------------------------------------------- 1 | ceremony-type: root 2 | pkcs11: 3 | module: {{ .Module }} 4 | pin: {{ .UserPIN }} 5 | store-key-in-slot: {{ .SlotID }} 6 | store-key-with-label: {{ .Label }} 7 | key: 8 | type: {{ .KeyType }} 9 | {{ if eq .KeyType "rsa" }} 10 | rsa-mod-length: {{ .KeyParam }} 11 | {{ else }} 12 | ecdsa-curve: {{ .KeyParam }} 13 | {{ end }} 14 | {{ if eq .Extractable "true" }} 15 | extractable: true 16 | {{ end }} 17 | outputs: 18 | public-key-path: {{ .Path }}root-{{ .SeqNr }}-pubkey.pem 19 | certificate-path: {{ .Path }}root-{{ .SeqNr }}-cert.pem 20 | certificate-profile: 21 | signature-algorithm: {{ .SignAlgorithm }} 22 | common-name: {{ .CommonName }} 23 | organization: {{ .OrgName }} 24 | country: {{ .Country }} 25 | not-before: {{ .NotBefore }} 26 | not-after: {{ .NotAfter }} 27 | key-usages: 28 | - Cert Sign 29 | - CRL Sign 30 | skip-lints: 31 | - n_ca_digital_signature_not_set 32 | {{ if eq .Renewal "true" }} 33 | renewal: true 34 | {{ end }} 35 | -------------------------------------------------------------------------------- /gui/templates/partials/css.tmpl: -------------------------------------------------------------------------------- 1 | {{ if .Css }} 2 | {{ range .Css }} 3 | {{ end }} 4 | {{ else }} 5 | 6 | 7 | 8 | 9 | 10 | {{ end }} 11 | -------------------------------------------------------------------------------- /gui/templates/partials/js.tmpl: -------------------------------------------------------------------------------- 1 | {{ if .Js }} 2 | {{ range .Js }} 3 | {{ end }} 4 | {{ else }} 5 | 6 | 7 | 8 | 9 | 10 | {{ end }} 11 | -------------------------------------------------------------------------------- /gui/templates/partials/nav.tmpl: -------------------------------------------------------------------------------- 1 | {{ if .Menu }} 2 | 51 | 52 | {{ end }} 53 | -------------------------------------------------------------------------------- /gui/templates/partials/progress.tmpl: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |
    5 |
    6 |
    7 |
    8 | {{ if .HelpText }} 9 |
    10 | {{ .HelpText }} 11 |
    12 | {{ end }} 13 | -------------------------------------------------------------------------------- /gui/templates/views/about.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |

    About LabCA

    3 | 4 |

    For the public internet, Let's Encrypt™ is providing free HTTPS certificates in an easy and automated way, without human interaction. There are many clients available to interact with their so called ACME (Automated Certificate Management Environment). They also have a staging environment that allows you to get things right before issuing trusted certificates and reduce the chance of you running up against rate limits.

    5 | 6 |
    7 |

    We want to create a more secure and privacy-respecting Web

    8 | Let's Encrypt™ 9 |
    10 | 11 |

    One technical requirement however is to have a publicly reachable location where your client application and their server can exchange information. For intranet / company internal applications or for testing clients within your organization this may not always be feasible.

    12 | 13 |

    Luckily they have made the core of their application, called "Boulder", available as open source. It is possible to install Boulder on your own server and use it internally to hand out certificates. As long as all client machines / laptops in your organization trust your root CA certificate, all certificates it signed are trusted automatically and users see a green lock icon in their browsers.

    14 | 15 |

    Also if you are developing your own client application or integrating one into your own application, a local test ACME can be very handy. There is a lot of information on the internet about setting up your own PKI (Public Key Infrastructure) but those are usually not automated.

    16 | 17 |

    Getting Boulder up and running has quite a learning curve though and that is where LabCA comes in. It is a self-contained installation with a nice web GUI built on top of Boulder so you can quickly start using it. All regular management tasks can be done from the web interface. It is best installed in its own Virtual Machine and uses Debian Linux as a base.

    18 | 19 |

    NOTE: although LabCA tries to be as robust as possible, use it at your own risk. If you depend on it, make sure that you know what you are doing!

    20 | 21 | {{ if .Standalone }} 22 |

     

    23 |

    Standalone GUI Version

    24 |

    As the ACME protocol is a standard (RFC8555) and not limited to boulder, there also are other implementations, e.g. step-ca from Smallstep™ that you can run and manage yourself.

    25 | 26 |

    Getting started with step-ca is much easier than starting with boulder. But Smallstep is not providing a self-managed web GUI to easily see what certificates have been issued by step-ca and what their expiry statuses are. In fact they are using a very specific database storage that does not allow you to query the data directly from a normal database client either.

    27 | 28 |

    As the structure of the ACME data is pretty standard anyway, this standalone version of the LabCA GUI was created to work with step-ca (and potentially other ACME implementations in the future). It only works with their MySQL backend, as the BadgerDB backend has several limitations.

    29 | 30 |

    The standalone GUI is distributed as a single binary so that it can be easily installed and started.

    31 | {{ end }} 32 | {{ end }} 33 | -------------------------------------------------------------------------------- /gui/templates/views/error.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |

    OOPS

    3 |

    {{ .Message }}

    4 | {{ if .FileErrors }} 5 |
    6 |

    Diagnostics

    7 |

    These log files might help you determine what the problem is:

    8 | {{ range $item := .FileErrors }} 9 |

    {{ $item.FileName }}

    10 |
    {{ $item.Content }}
    11 | {{ end }} 12 | {{ end }} 13 | {{ end }} 14 | -------------------------------------------------------------------------------- /gui/templates/views/final.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |
    3 |
    4 |

    LabCA

    5 |

    Congratulations! You are now done setting up LabCA!

    6 |

    You probably need to restart this browser to pickup the new certificate, it is not sufficient to just refresh the page.

    7 |

    Then go to the admin/ page or to the public homepage.

    8 | {{ template "partials/progress.tmpl" . }} 9 | {{end}} 10 | -------------------------------------------------------------------------------- /gui/templates/views/index.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |

    TODO

    3 | {{ .Message }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /gui/templates/views/list.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 | {{with .List}} 3 |

    {{ .Title }}

    4 | 5 | 6 | 7 | 8 | {{ range .Header }} 9 | 10 | {{ end }} 11 | 12 | 13 | 14 | {{ range .Rows }} 15 | 16 | {{ range . }} 17 | 18 | {{ end }} 19 | 20 | {{ end }} 21 | 22 |
    {{ . }}
    {{ . }}
    23 | {{end}} 24 | {{end}} 25 | 26 | {{ define "head" }} 27 | 28 | {{ end }} 29 | 30 | {{ define "tail" }} 31 | 32 | 33 | 34 | {{ end }} 35 | -------------------------------------------------------------------------------- /gui/templates/views/login.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |

    Login

    3 | 4 | {{with .User}} 5 |
    6 |
    7 | 8 | 9 | {{ with .Errors.Name }} 10 | {{ . }} 11 | {{ end }} 12 |
    13 |
    14 | 15 | 16 | {{ with .Errors.Password }} 17 | {{ . }} 18 | {{ end }} 19 |
    20 | 21 |
    22 | {{end}} 23 | {{end}} 24 | -------------------------------------------------------------------------------- /gui/templates/views/logs.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |

    {{ .Name }}

    3 | {{ .Message }} 4 |
    {{.Data}}
    5 | {{ if .WsUrl }} 6 |
    7 | Autoscroll: 8 |
    9 | {{ end }} 10 | {{ end }} 11 | 12 | {{ define "tail" }} 13 | 39 | {{ end }} 40 | -------------------------------------------------------------------------------- /gui/templates/views/polling.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |
    3 |
    4 |

    Restart

    5 |

    6 | Please install the root certificate in the Trusted Root Certification Authorities store of your client machine now.
    7 | root-01-cert.pem 8 |

    9 |

    Then, restart LabCA

    10 | 12 | {{ template "partials/progress.tmpl" . }} 13 | {{end}} 14 | -------------------------------------------------------------------------------- /gui/templates/views/register.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |
    3 |
    4 |

    Create admin account

    5 | 6 | 7 | {{with .User}} 8 |
    9 |
    10 | 11 | 12 | {{ with .Errors.Name }} 13 | {{ . }} 14 | {{ end }} 15 |
    16 |
    17 | 18 | 19 | {{ with .Errors.Email }} 20 | {{ . }} 21 | {{ end }} 22 |
    23 |
    24 | 25 | 26 | 27 |
    28 |
    29 |
    30 | {{ with .Errors.Password }} 31 | {{ . }} 32 | {{ end }} 33 |
    34 |
    35 | 36 | 37 | 38 | {{ with .Errors.Confirm }} 39 | {{ . }} 40 | {{ end }} 41 |
    42 |
    43 | 44 |
    45 |
    46 | 58 | 60 | {{end}} 61 | {{ template "partials/progress.tmpl" . }} 62 | {{end}} 63 | 64 | {{ define "tail" }} 65 | 66 | 67 | 97 | {{ end }} 98 | -------------------------------------------------------------------------------- /gui/templates/views/revoke-partial.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | -------------------------------------------------------------------------------- /gui/templates/views/setup.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |
    3 |
    4 |

    Basic configuration

    5 | 6 | {{with .SetupConfig}} 7 |
    8 |
    9 | 10 | 11 | {{ with .Errors.Fqdn }} 12 | {{ . }} 13 | {{ end }} 14 |
    15 |
    16 | 17 | 18 | {{ with .Errors.DNS }} 19 | {{ . }} 20 | {{ end }} 21 |
    22 | 23 |
    24 |
    25 | {{ with .Errors.DomainMode }} 26 | {{ . }}
    27 | {{ end }} 28 | Lockdown to only these domains (one per line):
    29 |
    30 | {{ with .Errors.LockdownDomains }} 31 | {{ . }}
    32 | {{ end }} 33 | 34 | Next to all official domains, also allow these domains (whitelist; one per line):
    35 |
    36 | {{ with .Errors.WhitelistDomains }} 37 | {{ . }}
    38 | {{ end }} 39 | 40 | Standard - any official domains
    41 |
    42 |
    43 |
    44 | 45 |  Still allow all public domain names in contact email addresses when creating new ACME accounts 46 |
    47 |
    48 | Are you sure? This facilitates man-in-the-middle attacks! 49 |
    50 |
    51 | 52 |
    53 |
    54 | {{end}} 55 | {{ template "partials/progress.tmpl" . }} 56 | {{end}} 57 | -------------------------------------------------------------------------------- /gui/templates/views/show.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 | {{ with .Details }} 3 |

    {{ .Title }}

    4 | 5 | 6 | 7 | {{ range .Rows }} 8 | 9 | 10 | 11 | 12 | {{ end }} 13 | 14 |
    {{ .Name }}{{ .Value }}
    15 | {{ if .Extra }}{{ range $extra := .Extra }} 16 | {{ $extra }} 17 | {{ end }}{{ end }} 18 |
    19 | 20 | {{ range .Relateds }} 21 |

    {{ .Title }}

    22 | 23 | 24 | 25 | {{ range .Header }} 26 | 27 | {{ end }} 28 | 29 | 30 | 31 | {{ range .Rows }} 32 | 33 | {{ range . }} 34 | 35 | {{ end }} 36 | 37 | {{ end }} 38 | 39 |
    {{ . }}
    {{ . }}
    40 | {{ end }} 41 | {{end}} 42 | {{end}} 43 | 44 | {{ define "head" }} 45 | 46 | {{ end }} 47 | 48 | {{ define "tail" }} 49 | 50 | 51 | 52 | {{ end }} 53 | -------------------------------------------------------------------------------- /gui/templates/views/standalone.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |
    3 |
    4 |

    Configuration

    5 | 6 | {{with .SetupConfig}} 7 |
    8 |
    9 |
    10 | {{ with .Errors.Backend }} 11 | {{ . }}
    12 | {{ end }} 13 | step-ca
    14 | 15 | 16 | {{ with .Errors.MySQLServer }} 17 | {{ . }}
    18 | {{ end }} 19 | 20 | 21 | {{ with .Errors.MySQLPort }} 22 | {{ . }}
    23 | {{ end }} 24 | 25 | 26 | {{ with .Errors.MySQLDBName }} 27 | {{ . }}
    28 | {{ end }} 29 | 30 | 31 | {{ with .Errors.MySQLUser }} 32 | {{ . }}
    33 | {{ end }} 34 | 35 | 36 | {{ with .Errors.MySQLPasswd }} 37 | {{ . }}
    38 | {{ end }} 39 |
    40 | 41 |
    42 | 43 | 44 | {{ with .Errors.CertPath }} 45 | {{ . }}
    46 | {{ end }} 47 | 48 | 49 | {{ with .Errors.KeyPath }} 50 | {{ . }}
    51 | {{ end }} 52 |
    53 | ...

    54 |
    55 | 56 |
    57 | 58 |
    59 |
    60 | {{end}} 61 | {{ template "partials/progress.tmpl" . }} 62 | {{end}} 63 | -------------------------------------------------------------------------------- /gui/templates/views/wrapup.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "body" }} 2 |
    3 |
    4 |

    Restart

    5 |

    Almost there!
    Now we will request a certificate for this website and restart one more time...
    6 |

    7 | {{ template "partials/progress.tmpl" . }} 8 | {{end}} 9 | -------------------------------------------------------------------------------- /init_d: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: 4 | # Required-Start: $remote_fs $syslog $network 5 | # Required-Stop: $remote_fs $syslog $network 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Start LabCA commander daemon at boot time 9 | # Description: Enable service provided by LabCA commander daemon. 10 | ### END INIT INFO 11 | 12 | cmd="tcpserver $(docker inspect --format "{{ .NetworkSettings.Networks.boulder_bluenet.Gateway }}" boulder_labca_1) 3030 /home/labca/labca/commander" 13 | user="" 14 | 15 | name=`basename $0` 16 | pid_file="/var/run/$name.pid" 17 | stdout_log="/var/log/$name.log" 18 | stderr_log="/var/log/$name.err" 19 | 20 | get_pid() { 21 | cat "$pid_file" 22 | } 23 | 24 | is_running() { 25 | [ -f "$pid_file" ] && ps -p `get_pid` > /dev/null 2>&1 26 | } 27 | 28 | case "$1" in 29 | start) 30 | if is_running; then 31 | echo "Already started" 32 | else 33 | echo "Starting $name" 34 | if [ -z "$user" ]; then 35 | sudo $cmd >> "$stdout_log" 2>> "$stderr_log" & 36 | else 37 | sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" & 38 | fi 39 | echo $! > "$pid_file" 40 | if ! is_running; then 41 | echo "Unable to start, see $stdout_log and $stderr_log" 42 | exit 1 43 | fi 44 | fi 45 | ;; 46 | stop) 47 | if is_running; then 48 | echo -n "Stopping $name.." 49 | kill `get_pid` 50 | for i in 1 2 3 4 5 6 7 8 9 10 51 | # for i in `seq 10` 52 | do 53 | if ! is_running; then 54 | break 55 | fi 56 | 57 | echo -n "." 58 | sleep 1 59 | done 60 | echo 61 | 62 | if is_running; then 63 | echo "Not stopped; may still be shutting down or shutdown may have failed" 64 | exit 1 65 | else 66 | echo "Stopped" 67 | if [ -f "$pid_file" ]; then 68 | rm "$pid_file" 69 | fi 70 | fi 71 | else 72 | echo "Not running" 73 | fi 74 | ;; 75 | restart) 76 | $0 stop 77 | if is_running; then 78 | echo "Unable to stop, will not attempt to start" 79 | exit 1 80 | fi 81 | $0 start 82 | ;; 83 | status) 84 | if is_running; then 85 | echo "Running" 86 | else 87 | echo "Stopped" 88 | exit 1 89 | fi 90 | ;; 91 | *) 92 | echo "Usage: $0 {start|stop|restart|status}" 93 | exit 1 94 | ;; 95 | esac 96 | 97 | exit 0 98 | 99 | -------------------------------------------------------------------------------- /logrotate_d: -------------------------------------------------------------------------------- 1 | /etc/nginx/ssl/*.log 2 | /opt/logs/cron-*.log 3 | { 4 | rotate 4 5 | monthly 6 | compress 7 | missingok 8 | notifempty 9 | } 10 | -------------------------------------------------------------------------------- /mailer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | TODAY=`date '+%Y_%m_%d'` 6 | echo "Running cron-$(basename $0) for ${TODAY}..." 7 | 8 | cd /opt/boulder 9 | docker compose exec boulder bin/boulder expiration-mailer --config labca/config/expiration-mailer.json 2>&1 10 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | # Include any custom http context settings from custom-base.inc if that file exists 2 | include conf.d/custom-base[.]inc; 3 | 4 | server { 5 | listen [::]:80 default_server ipv6only=off; 6 | server_name _; 7 | server_tokens off; 8 | 9 | root /var/www/html; 10 | index index.html index.htm index.nginx-debian.html; 11 | 12 | # Include any custom plain-HTTP server context settings from custom.inc if that file exists 13 | include conf.d/custom[.]inc; 14 | 15 | location /admin/ { 16 | return 301 https://$host$request_uri; 17 | } 18 | 19 | location /acme/ { 20 | return 301 https://$host$request_uri; 21 | } 22 | 23 | location /aia/issuer { 24 | include conf.d/proxy.inc; 25 | proxy_pass http://boulder:4001; 26 | } 27 | 28 | location /directory { 29 | return 301 https://$host$request_uri; 30 | } 31 | 32 | location /ocsp { 33 | include conf.d/proxy.inc; 34 | proxy_pass http://boulder:4002/; 35 | } 36 | 37 | location /sfe { 38 | return 301 https://$host$request_uri; 39 | } 40 | 41 | location /rate-limits { 42 | try_files $uri $uri.html $uri/ =404; 43 | } 44 | 45 | location /terms/ { 46 | try_files $uri $uri.html $uri/ =404; 47 | } 48 | } 49 | 50 | server { 51 | listen [::]:443 default_server ssl ipv6only=off; 52 | server_name _; 53 | server_tokens off; 54 | 55 | ssl_certificate /etc/nginx/ssl/labca_cert.pem; 56 | ssl_certificate_key /etc/nginx/ssl/labca_key.pem; 57 | 58 | root /var/www/html; 59 | index index.html index.htm index.nginx-debian.html; 60 | 61 | # Include any custom HTTPS server context settings from custom-ssl.inc if that file exists 62 | include conf.d/custom-ssl[.]inc; 63 | 64 | location ~ ^/admin/static/(.+) { 65 | alias /var/www/html/$1; 66 | } 67 | 68 | location ~ ^/admin/.+/static/(.+) { 69 | alias /var/www/html/$1; 70 | } 71 | 72 | location /admin/ { 73 | client_max_body_size 20M; 74 | include conf.d/proxy.inc; 75 | proxy_read_timeout 120; 76 | proxy_set_header X-Request-Base "/admin"; 77 | proxy_pass http://gui:3000/; 78 | error_page 502 504 /502.html; 79 | } 80 | 81 | location /admin/ws { 82 | include conf.d/proxy.inc; 83 | proxy_set_header X-Request-Base "/admin"; 84 | proxy_set_header Upgrade $http_upgrade; 85 | proxy_set_header Connection "Upgrade"; 86 | proxy_pass http://gui:3000/ws; 87 | } 88 | 89 | location /acme/ { 90 | include conf.d/proxy.inc; 91 | proxy_pass http://boulder:4001; 92 | } 93 | 94 | location /directory { 95 | include conf.d/proxy.inc; 96 | proxy_pass http://boulder:4001; 97 | } 98 | 99 | location /build { 100 | include conf.d/proxy.inc; 101 | proxy_pass http://boulder:4001; 102 | } 103 | 104 | location /aia/issuer { 105 | include conf.d/proxy.inc; 106 | proxy_pass http://boulder:4001; 107 | } 108 | 109 | location /ocsp { 110 | include conf.d/proxy.inc; 111 | proxy_pass http://boulder:4002/; 112 | } 113 | 114 | location /sfe { 115 | include conf.d/proxy.inc; 116 | proxy_pass http://boulder:4003/; 117 | } 118 | 119 | location /rate-limits { 120 | try_files $uri $uri.html $uri/ =404; 121 | } 122 | 123 | location /terms/ { 124 | try_files $uri $uri.html $uri/ =404; 125 | } 126 | 127 | # BEGIN temporary redirect 128 | location = / { 129 | return 302 /admin/; 130 | } 131 | # END temporary redirect 132 | } 133 | -------------------------------------------------------------------------------- /patch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cloneDir=$(dirname $0) 6 | 7 | # For legacy mode, when called from the install script... 8 | SUDO="$1" 9 | 10 | 11 | $SUDO patch -p1 < $cloneDir/patches/docker-compose.patch 12 | if [ "$SUDO" == "" ]; then 13 | # TODO: should incorporate this into docker-compose.patch 14 | $SUDO patch -p1 < $cloneDir/build/tmp.patch 15 | fi 16 | 17 | $SUDO patch -p1 < $cloneDir/patches/bad-key-revoker_main.patch 18 | $SUDO patch -p1 < $cloneDir/patches/boulder-ra_main.patch 19 | $SUDO patch -p1 < $cloneDir/patches/boulder-va_main.patch 20 | $SUDO patch -p1 < $cloneDir/patches/ca_ca.patch 21 | $SUDO patch -p1 < $cloneDir/patches/ca_ca_keytype_hack.patch 22 | $SUDO patch -p1 < $cloneDir/patches/ca_crl.patch 23 | $SUDO patch -p1 < $cloneDir/patches/ceremony_crl.patch 24 | $SUDO patch -p1 < $cloneDir/patches/ceremony_ecdsa.patch 25 | $SUDO patch -p1 < $cloneDir/patches/ceremony_key.patch 26 | $SUDO patch -p1 < $cloneDir/patches/ceremony_main.patch 27 | $SUDO patch -p1 < $cloneDir/patches/ceremony_rsa.patch 28 | $SUDO patch -p1 < $cloneDir/patches/cert-checker_main.patch 29 | $SUDO patch -p1 < $cloneDir/patches/cmd_config.patch 30 | $SUDO patch -p1 < $cloneDir/patches/config_duration.patch 31 | $SUDO patch -p1 < $cloneDir/patches/config_rocsp_config.patch 32 | $SUDO patch -p1 < $cloneDir/patches/contact-auditor_main.patch 33 | $SUDO patch -p1 < $cloneDir/patches/core_interfaces.patch 34 | $SUDO patch -p1 < $cloneDir/patches/crl-storer_main.patch 35 | $SUDO patch -p1 < $cloneDir/patches/db_migrations.patch 36 | $SUDO patch -p1 < $cloneDir/patches/db_migrations2.patch 37 | $SUDO patch -p1 < $cloneDir/patches/db_migrations3.patch 38 | $SUDO patch -p1 < $cloneDir/patches/db_migrations4.patch 39 | $SUDO patch -p1 < $cloneDir/patches/db_migrations5.patch 40 | $SUDO patch -p1 < $cloneDir/patches/expiration-mailer_main.patch 41 | $SUDO patch -p1 < $cloneDir/patches/issuance_crl.patch 42 | $SUDO patch -p1 < $cloneDir/patches/issuance_issuer.patch 43 | $SUDO patch -p1 < $cloneDir/patches/linter_linter.patch 44 | $SUDO patch -p1 < $cloneDir/patches/log_prod_prefix.patch 45 | $SUDO patch -p1 < $cloneDir/patches/log_test_prefix.patch 46 | $SUDO patch -p1 < $cloneDir/patches/log_validator_validator.patch 47 | $SUDO patch -p1 < $cloneDir/patches/mail_mailer.patch 48 | $SUDO patch -p1 < $cloneDir/patches/makefile.patch 49 | $SUDO patch -p1 < $cloneDir/patches/notify-mailer_main.patch 50 | $SUDO patch -p1 < $cloneDir/patches/ocsp-responder_main.patch 51 | $SUDO patch -p1 < $cloneDir/patches/policy_pa.patch 52 | $SUDO patch -p1 < $cloneDir/patches/ra_ra.patch 53 | $SUDO patch -p1 < $cloneDir/patches/ratelimits_names.patch 54 | $SUDO patch -p1 < $cloneDir/patches/redis_config.patch 55 | $SUDO patch -p1 < $cloneDir/patches/remoteva_main.patch 56 | $SUDO patch -p1 < $cloneDir/patches/start.patch 57 | $SUDO patch -p1 < $cloneDir/patches/test_startservers.patch 58 | if [ "$SUDO" == "" ]; then 59 | # TODO: should include this into startservers.patch 60 | $SUDO patch -p1 < $cloneDir/build/tmp2.patch 61 | fi 62 | $SUDO patch -p1 < $cloneDir/patches/sfe_templates_layout.patch 63 | $SUDO patch -p1 < $cloneDir/patches/storer_storer.patch 64 | $SUDO patch -p1 < $cloneDir/patches/test_health-checker_main.patch 65 | $SUDO patch -p1 < $cloneDir/patches/test_ocsp_helper_helper.patch 66 | $SUDO patch -p1 < $cloneDir/patches/updater_updater.patch 67 | $SUDO patch -p1 < $cloneDir/patches/updater_continuous.patch 68 | $SUDO patch -p1 < $cloneDir/patches/va_http.patch 69 | $SUDO patch -p1 < $cloneDir/patches/va_va.patch 70 | $SUDO patch -p1 < $cloneDir/patches/wfe2_main.patch 71 | $SUDO patch -p1 < $cloneDir/patches/wfe2_wfe.patch 72 | 73 | sed -i -e "s|./test|./labca|" start.py 74 | 75 | sed -i -e "s/proxysql:6033/mysql:3306/" sa/db/dbconfig.yml 76 | 77 | sed -i -e "s/\(.*overrides.*\)/-- \1/" sa/db-users/boulder_sa.sql 78 | 79 | mkdir -p "cmd/mail-tester" 80 | cp $cloneDir/mail-tester.go cmd/mail-tester/main.go 81 | perl -i -p0e "s/(\n\t\"github.com\/letsencrypt\/boulder\/cmd\")/\t_ \"github.com\/letsencrypt\/boulder\/cmd\/mail-tester\"\n\1/igs" cmd/boulder/main.go 82 | 83 | perl -i -p0e "s/If you continue to encounter.*for troubleshooting and advice.//igs" sfe/pages/index.html 84 | perl -i -p0e "s/Note:<\/b> If you encounter.*troubleshooting and advice.//igs" sfe/pages/unpause-form.html 85 | perl -i -p0e "s/If you continue to encounter.*for troubleshooting and advice.//igs" sfe/pages/unpause-invalid-request.html 86 | perl -i -p0e "s/ If you face continued.*for troubleshooting and advice.//igs" sfe/pages/unpause-status.html 87 | -------------------------------------------------------------------------------- /patches/bad-key-revoker_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/bad-key-revoker/main.go b/cmd/bad-key-revoker/main.go 2 | index c333b88c3..8e9cc21bd 100644 3 | --- a/cmd/bad-key-revoker/main.go 4 | +++ b/cmd/bad-key-revoker/main.go 5 | @@ -18,6 +18,7 @@ import ( 6 | "google.golang.org/grpc" 7 | "google.golang.org/protobuf/types/known/emptypb" 8 | 9 | + "github.com/letsencrypt/boulder/bdns" 10 | "github.com/letsencrypt/boulder/cmd" 11 | "github.com/letsencrypt/boulder/config" 12 | "github.com/letsencrypt/boulder/core" 13 | @@ -398,6 +399,11 @@ type Config struct { 14 | TLS cmd.TLSConfig 15 | RAService *cmd.GRPCClientConfig 16 | 17 | + DNSTries int 18 | + DNSStaticResolvers []string 19 | + DNSTimeout string 20 | + DNSAllowLoopbackAddresses bool 21 | + 22 | // MaximumRevocations specifies the maximum number of certificates associated with 23 | // a key hash that bad-key-revoker will attempt to revoke. If the number of certificates 24 | // is higher than MaximumRevocations bad-key-revoker will error out and refuse to 25 | @@ -417,6 +423,8 @@ type Config struct { 26 | // or no work to do. 27 | BackoffIntervalMax config.Duration `validate:"-"` 28 | 29 | + UserAgent string 30 | + 31 | Mailer struct { 32 | cmd.SMTPConfig 33 | // Path to a file containing a list of trusted root certificates for use 34 | @@ -469,8 +477,36 @@ func main() { 35 | cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA") 36 | rac := rapb.NewRegistrationAuthorityClient(conn) 37 | 38 | + dnsTimeout, err := time.ParseDuration(config.BadKeyRevoker.DNSTimeout) 39 | + cmd.FailOnError(err, "Couldn't parse DNS timeout") 40 | + dnsTries := config.BadKeyRevoker.DNSTries 41 | + if dnsTries < 1 { 42 | + dnsTries = 1 43 | + } 44 | + var resolver bdns.Client 45 | + servers, err := bdns.NewStaticProvider(config.BadKeyRevoker.DNSStaticResolvers) 46 | + cmd.FailOnError(err, "Couldn't start static DNS server resolver") 47 | + if !config.BadKeyRevoker.DNSAllowLoopbackAddresses { 48 | + r := bdns.New( 49 | + dnsTimeout, 50 | + servers, 51 | + scope, 52 | + clk, 53 | + dnsTries, 54 | + config.BadKeyRevoker.UserAgent, 55 | + logger, 56 | + tlsConfig) 57 | + resolver = r 58 | + } else { 59 | + r := bdns.NewTest(dnsTimeout, servers, scope, clk, dnsTries, config.BadKeyRevoker.UserAgent, logger, tlsConfig) 60 | + resolver = r 61 | + } 62 | + 63 | var smtpRoots *x509.CertPool 64 | - if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile != "" { 65 | + smtpSkipVerify := false 66 | + if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile == "InsecureSkipVerify" { 67 | + smtpSkipVerify = true 68 | + } else if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile != "" { 69 | pem, err := os.ReadFile(config.BadKeyRevoker.Mailer.SMTPTrustedRootFile) 70 | cmd.FailOnError(err, "Loading trusted roots file") 71 | smtpRoots = x509.NewCertPool() 72 | @@ -490,6 +526,8 @@ func main() { 73 | config.BadKeyRevoker.Mailer.Username, 74 | smtpPassword, 75 | smtpRoots, 76 | + smtpSkipVerify, 77 | + resolver, 78 | *fromAddress, 79 | logger, 80 | scope, 81 | -------------------------------------------------------------------------------- /patches/boulder-ra_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/boulder-ra/main.go b/cmd/boulder-ra/main.go 2 | index 9aa809e42..0facecca5 100644 3 | --- a/cmd/boulder-ra/main.go 4 | +++ b/cmd/boulder-ra/main.go 5 | @@ -270,6 +270,8 @@ func main() { 6 | limiterRedis, err = bredis.NewRingFromConfig(*c.RA.Limiter.Redis, scope, logger) 7 | cmd.FailOnError(err, "Failed to create Redis ring") 8 | 9 | + // Set Policy Authority for ratelimits 10 | + ratelimits.PA = pa 11 | source := ratelimits.NewRedisSource(limiterRedis.Ring, clk, scope) 12 | limiter, err = ratelimits.NewLimiter(clk, source, scope) 13 | cmd.FailOnError(err, "Failed to create rate limiter") 14 | -------------------------------------------------------------------------------- /patches/boulder-va_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/boulder-va/main.go b/cmd/boulder-va/main.go 2 | index 981c4f9b5..9d5db072d 100644 3 | --- a/cmd/boulder-va/main.go 4 | +++ b/cmd/boulder-va/main.go 5 | @@ -52,6 +52,7 @@ type Config struct { 6 | // Deprecated and ignored 7 | MaxRemoteValidationFailures int `validate:"omitempty,min=0,required_with=RemoteVAs"` 8 | Features features.Config 9 | + LabCADomains []string 10 | } 11 | 12 | Syslog cmd.SyslogConfig 13 | @@ -152,7 +153,8 @@ func main() { 14 | c.VA.AccountURIPrefixes, 15 | va.PrimaryPerspective, 16 | "", 17 | - bdns.IsReservedIP) 18 | + bdns.IsReservedIP, 19 | + c.VA.LabCADomains) 20 | cmd.FailOnError(err, "Unable to create VA server") 21 | 22 | start, err := bgrpc.NewServer(c.VA.GRPC, logger).Add( 23 | -------------------------------------------------------------------------------- /patches/ca_ca.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ca/ca.go b/ca/ca.go 2 | index f8caf76fb..400d2b613 100644 3 | --- a/ca/ca.go 4 | +++ b/ca/ca.go 5 | @@ -171,10 +171,10 @@ func makeIssuerMaps(issuers []*issuance.Issuer) (issuerMaps, error) { 6 | } 7 | } 8 | if i, ok := issuersByAlg[x509.ECDSA]; !ok || len(i) == 0 { 9 | - return issuerMaps{}, errors.New("no ECDSA issuers configured") 10 | + fmt.Println("WARNING: no ECDSA issuers configured") 11 | } 12 | if i, ok := issuersByAlg[x509.RSA]; !ok || len(i) == 0 { 13 | - return issuerMaps{}, errors.New("no RSA issuers configured") 14 | + fmt.Println("WARNING: no RSA issuers configured") 15 | } 16 | return issuerMaps{issuersByAlg, issuersByNameID}, nil 17 | } 18 | -------------------------------------------------------------------------------- /patches/ca_ca_keytype_hack.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ca/ca.go b/ca/ca.go 2 | index 400d2b613..09e651a96 100644 3 | --- a/ca/ca.go 4 | +++ b/ca/ca.go 5 | @@ -171,10 +171,14 @@ func makeIssuerMaps(issuers []*issuance.Issuer) (issuerMaps, error) { 6 | } 7 | } 8 | if i, ok := issuersByAlg[x509.ECDSA]; !ok || len(i) == 0 { 9 | - fmt.Println("WARNING: no ECDSA issuers configured") 10 | + // TODO: LabCA hack! 11 | + issuersByAlg[x509.ECDSA] = issuersByAlg[x509.RSA] 12 | + // fmt.Println("WARNING: no ECDSA issuers configured") 13 | } 14 | if i, ok := issuersByAlg[x509.RSA]; !ok || len(i) == 0 { 15 | - fmt.Println("WARNING: no RSA issuers configured") 16 | + // TODO: LabCA hack! 17 | + issuersByAlg[x509.RSA] = issuersByAlg[x509.ECDSA] 18 | + // fmt.Println("WARNING: no RSA issuers configured") 19 | } 20 | return issuerMaps{issuersByAlg, issuersByNameID}, nil 21 | } 22 | -------------------------------------------------------------------------------- /patches/ca_crl.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ca/crl.go b/ca/crl.go 2 | index 5937046fe..15c144984 100644 3 | --- a/ca/crl.go 4 | +++ b/ca/crl.go 5 | @@ -132,8 +132,10 @@ func (ci *crlImpl) GenerateCRL(stream grpc.BidiStreamingServer[capb.GenerateCRLR 6 | builder = strings.Builder{} 7 | } 8 | } 9 | - fmt.Fprint(&builder, "]") 10 | - ci.log.AuditInfo(builder.String()) 11 | + if builder.Len() > 0 { 12 | + fmt.Fprint(&builder, "]") 13 | + ci.log.AuditInfo(builder.String()) 14 | + } 15 | } 16 | 17 | req.Entries = rcs 18 | -------------------------------------------------------------------------------- /patches/ceremony_crl.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/ceremony/crl.go b/cmd/ceremony/crl.go 2 | index 98790d906..4de35ae5c 100644 3 | --- a/cmd/ceremony/crl.go 4 | +++ b/cmd/ceremony/crl.go 5 | @@ -42,7 +42,7 @@ func generateCRL(signer crypto.Signer, issuer *x509.Certificate, thisUpdate, nex 6 | } 7 | template.ExtraExtensions = append(template.ExtraExtensions, *idp) 8 | 9 | - err = linter.CheckCRL(template, issuer, signer, []string{}) 10 | + err = linter.CheckCRL(template, issuer, signer, []string{"e_crl_next_update_invalid"}) 11 | if err != nil { 12 | return nil, fmt.Errorf("crl failed pre-issuance lint: %w", err) 13 | } 14 | -------------------------------------------------------------------------------- /patches/ceremony_ecdsa.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/ceremony/ecdsa.go b/cmd/ceremony/ecdsa.go 2 | index 65f5c6f99..24102ad8e 100644 3 | --- a/cmd/ceremony/ecdsa.go 4 | +++ b/cmd/ceremony/ecdsa.go 5 | @@ -29,7 +29,7 @@ var curveToOIDDER = map[string][]byte{ 6 | // ecArgs constructs the private and public key template attributes sent to the 7 | // device and specifies which mechanism should be used. curve determines which 8 | // type of key should be generated. 9 | -func ecArgs(label string, curve elliptic.Curve, keyID []byte) generateArgs { 10 | +func ecArgs(label string, curve elliptic.Curve, keyID []byte, extractable bool) generateArgs { 11 | encodedCurve := curveToOIDDER[curve.Params().Name] 12 | log.Printf("\tEncoded curve parameters for %s: %X\n", curve.Params().Name, encodedCurve) 13 | return generateArgs{ 14 | @@ -50,7 +50,7 @@ func ecArgs(label string, curve elliptic.Curve, keyID []byte) generateArgs { 15 | // Prevent attributes being retrieved 16 | pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true), 17 | // Prevent the key being extracted from the device 18 | - pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false), 19 | + pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, extractable), 20 | // Allow the key to sign data 21 | pkcs11.NewAttribute(pkcs11.CKA_SIGN, true), 22 | }, 23 | @@ -81,7 +81,7 @@ func ecPub( 24 | // specified by curveStr and with the provided label. It returns the public 25 | // part of the generated key pair as a ecdsa.PublicKey and the random key ID 26 | // that the HSM uses to identify the key pair. 27 | -func ecGenerate(session *pkcs11helpers.Session, label, curveStr string) (*ecdsa.PublicKey, []byte, error) { 28 | +func ecGenerate(session *pkcs11helpers.Session, label, curveStr string, extractable bool) (*ecdsa.PublicKey, []byte, error) { 29 | curve, present := stringToCurve[curveStr] 30 | if !present { 31 | return nil, nil, fmt.Errorf("curve %q not supported", curveStr) 32 | @@ -92,7 +92,7 @@ func ecGenerate(session *pkcs11helpers.Session, label, curveStr string) (*ecdsa. 33 | return nil, nil, err 34 | } 35 | log.Printf("Generating ECDSA key with curve %s and ID %x\n", curveStr, keyID) 36 | - args := ecArgs(label, curve, keyID) 37 | + args := ecArgs(label, curve, keyID, extractable) 38 | pub, _, err := session.GenerateKeyPair(args.mechanism, args.publicAttrs, args.privateAttrs) 39 | if err != nil { 40 | return nil, nil, err 41 | -------------------------------------------------------------------------------- /patches/ceremony_key.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/ceremony/key.go b/cmd/ceremony/key.go 2 | index e0ed20594..4c4656b4d 100644 3 | --- a/cmd/ceremony/key.go 4 | +++ b/cmd/ceremony/key.go 5 | @@ -56,12 +56,12 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string 6 | var keyID []byte 7 | switch config.Type { 8 | case "rsa": 9 | - pubKey, keyID, err = rsaGenerate(session, label, config.RSAModLength) 10 | + pubKey, keyID, err = rsaGenerate(session, label, config.RSAModLength, config.Extractable) 11 | if err != nil { 12 | return nil, fmt.Errorf("failed to generate RSA key pair: %s", err) 13 | } 14 | case "ecdsa": 15 | - pubKey, keyID, err = ecGenerate(session, label, config.ECDSACurve) 16 | + pubKey, keyID, err = ecGenerate(session, label, config.ECDSACurve, config.Extractable) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to generate ECDSA key pair: %s", err) 19 | } 20 | -------------------------------------------------------------------------------- /patches/ceremony_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/ceremony/main.go b/cmd/ceremony/main.go 2 | index 12cc9249c..8ac5af0a3 100644 3 | --- a/cmd/ceremony/main.go 4 | +++ b/cmd/ceremony/main.go 5 | @@ -98,6 +98,7 @@ type keyGenConfig struct { 6 | Type string `yaml:"type"` 7 | RSAModLength int `yaml:"rsa-mod-length"` 8 | ECDSACurve string `yaml:"ecdsa-curve"` 9 | + Extractable bool `yaml:"extractable"` 10 | } 11 | 12 | var allowedCurves = map[string]bool{ 13 | @@ -174,6 +175,7 @@ type rootConfig struct { 14 | } `yaml:"outputs"` 15 | CertProfile certProfile `yaml:"certificate-profile"` 16 | SkipLints []string `yaml:"skip-lints"` 17 | + Renewal bool `yaml:"renewal"` 18 | } 19 | 20 | func (rc rootConfig) validate() error { 21 | @@ -189,9 +191,11 @@ func (rc rootConfig) validate() error { 22 | } 23 | 24 | // Output fields 25 | - err = checkOutputFile(rc.Outputs.PublicKeyPath, "public-key-path") 26 | - if err != nil { 27 | - return err 28 | + if !rc.Renewal { 29 | + err = checkOutputFile(rc.Outputs.PublicKeyPath, "public-key-path") 30 | + if err != nil { 31 | + return err 32 | + } 33 | } 34 | err = checkOutputFile(rc.Outputs.CertificatePath, "certificate-path") 35 | if err != nil { 36 | @@ -629,23 +633,42 @@ func rootCeremony(configBytes []byte) error { 37 | return fmt.Errorf("failed to setup session and PKCS#11 context for slot %d: %s", config.PKCS11.StoreSlot, err) 38 | } 39 | log.Printf("Opened PKCS#11 session for slot %d\n", config.PKCS11.StoreSlot) 40 | - keyInfo, err := generateKey(session, config.PKCS11.StoreLabel, config.Outputs.PublicKeyPath, config.Key) 41 | - if err != nil { 42 | - return err 43 | + var rKeyInfo *keyInfo 44 | + if config.Renewal { 45 | + // Reuse existing root key for a renewal 46 | + pub, _, err := loadPubKey(config.Outputs.PublicKeyPath) 47 | + if err != nil { 48 | + return err 49 | + } 50 | + 51 | + der, err := x509.MarshalPKIXPublicKey(pub) 52 | + if err != nil { 53 | + return fmt.Errorf("Failed to marshal public key: %s", err) 54 | + } 55 | + 56 | + rKeyInfo = &keyInfo{ 57 | + key: pub, 58 | + der: der, 59 | + } 60 | + } else { 61 | + rKeyInfo, err = generateKey(session, config.PKCS11.StoreLabel, config.Outputs.PublicKeyPath, config.Key) 62 | + if err != nil { 63 | + return err 64 | + } 65 | } 66 | - signer, err := session.NewSigner(config.PKCS11.StoreLabel, keyInfo.key) 67 | + signer, err := session.NewSigner(config.PKCS11.StoreLabel, rKeyInfo.key) 68 | if err != nil { 69 | return fmt.Errorf("failed to retrieve signer: %s", err) 70 | } 71 | - template, err := makeTemplate(newRandReader(session), &config.CertProfile, keyInfo.der, nil, rootCert) 72 | + template, err := makeTemplate(newRandReader(session), &config.CertProfile, rKeyInfo.der, nil, rootCert) 73 | if err != nil { 74 | return fmt.Errorf("failed to create certificate profile: %s", err) 75 | } 76 | - lintCert, err := issueLintCertAndPerformLinting(template, template, keyInfo.key, signer, config.SkipLints) 77 | + lintCert, err := issueLintCertAndPerformLinting(template, template, rKeyInfo.key, signer, config.SkipLints) 78 | if err != nil { 79 | return err 80 | } 81 | - finalCert, err := signAndWriteCert(template, template, lintCert, keyInfo.key, signer, config.Outputs.CertificatePath) 82 | + finalCert, err := signAndWriteCert(template, template, lintCert, rKeyInfo.key, signer, config.Outputs.CertificatePath) 83 | if err != nil { 84 | return err 85 | } 86 | -------------------------------------------------------------------------------- /patches/ceremony_rsa.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/ceremony/rsa.go b/cmd/ceremony/rsa.go 2 | index 7d0eb4b30..465857f3a 100644 3 | --- a/cmd/ceremony/rsa.go 4 | +++ b/cmd/ceremony/rsa.go 5 | @@ -19,7 +19,7 @@ const ( 6 | // device and specifies which mechanism should be used. modulusLen specifies the 7 | // length of the modulus to be generated on the device in bits and exponent 8 | // specifies the public exponent that should be used. 9 | -func rsaArgs(label string, modulusLen int, keyID []byte) generateArgs { 10 | +func rsaArgs(label string, modulusLen int, keyID []byte, extractable bool) generateArgs { 11 | // Encode as unpadded big endian encoded byte slice 12 | expSlice := big.NewInt(rsaExp).Bytes() 13 | log.Printf("\tEncoded public exponent (%d) as: %0X\n", rsaExp, expSlice) 14 | @@ -45,7 +45,7 @@ func rsaArgs(label string, modulusLen int, keyID []byte) generateArgs { 15 | // Prevent attributes being retrieved 16 | pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true), 17 | // Prevent the key being extracted from the device 18 | - pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false), 19 | + pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, extractable), 20 | // Allow the key to create signatures 21 | pkcs11.NewAttribute(pkcs11.CKA_SIGN, true), 22 | }, 23 | @@ -76,14 +76,14 @@ func rsaPub(session *pkcs11helpers.Session, object pkcs11.ObjectHandle, modulusL 24 | // specified by modulusLen and with the exponent 65537. 25 | // It returns the public part of the generated key pair as a rsa.PublicKey 26 | // and the random key ID that the HSM uses to identify the key pair. 27 | -func rsaGenerate(session *pkcs11helpers.Session, label string, modulusLen int) (*rsa.PublicKey, []byte, error) { 28 | +func rsaGenerate(session *pkcs11helpers.Session, label string, modulusLen int, extractable bool) (*rsa.PublicKey, []byte, error) { 29 | keyID := make([]byte, 4) 30 | _, err := newRandReader(session).Read(keyID) 31 | if err != nil { 32 | return nil, nil, err 33 | } 34 | log.Printf("Generating RSA key with %d bit modulus and public exponent %d and ID %x\n", modulusLen, rsaExp, keyID) 35 | - args := rsaArgs(label, modulusLen, keyID) 36 | + args := rsaArgs(label, modulusLen, keyID, extractable) 37 | pub, _, err := session.GenerateKeyPair(args.mechanism, args.publicAttrs, args.privateAttrs) 38 | if err != nil { 39 | return nil, nil, err 40 | -------------------------------------------------------------------------------- /patches/cert-checker_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/cert-checker/main.go b/cmd/cert-checker/main.go 2 | index a323e70b8..df64d3e94 100644 3 | --- a/cmd/cert-checker/main.go 4 | +++ b/cmd/cert-checker/main.go 5 | @@ -109,6 +109,7 @@ type certChecker struct { 6 | acceptableValidityDurations map[time.Duration]bool 7 | lints lint.Registry 8 | logger blog.Logger 9 | + skipForbiddenDomains bool 10 | } 11 | 12 | func newChecker(saDbMap certDB, 13 | @@ -119,6 +120,7 @@ func newChecker(saDbMap certDB, 14 | avd map[time.Duration]bool, 15 | lints lint.Registry, 16 | logger blog.Logger, 17 | + sfd bool, 18 | ) certChecker { 19 | precertGetter := func(ctx context.Context, serial string) ([]byte, error) { 20 | precertPb, err := sa.SelectPrecertificate(ctx, saDbMap, serial) 21 | @@ -140,6 +142,7 @@ func newChecker(saDbMap certDB, 22 | acceptableValidityDurations: avd, 23 | lints: lints, 24 | logger: logger, 25 | + skipForbiddenDomains: sfd, 26 | } 27 | } 28 | 29 | @@ -437,14 +440,16 @@ func (c *certChecker) checkCert(ctx context.Context, cert *corepb.Certificate) ( 30 | problems = append(problems, fmt.Sprintf("Policy Authority isn't willing to issue for '%s': %s", name, err)) 31 | continue 32 | } 33 | - // For defense-in-depth, even if the PA was willing to issue for a name 34 | - // we double check it against a list of forbidden domains. This way even 35 | - // if the hostnamePolicyFile malfunctions we will flag the forbidden 36 | - // domain matches 37 | - if forbidden, pattern := isForbiddenDomain(name); forbidden { 38 | - problems = append(problems, fmt.Sprintf( 39 | - "Policy Authority was willing to issue but domain '%s' matches "+ 40 | - "forbiddenDomains entry %q", name, pattern)) 41 | + if !c.skipForbiddenDomains { 42 | + // For defense-in-depth, even if the PA was willing to issue for a name 43 | + // we double check it against a list of forbidden domains. This way even 44 | + // if the hostnamePolicyFile malfunctions we will flag the forbidden 45 | + // domain matches 46 | + if forbidden, pattern := isForbiddenDomain(name); forbidden { 47 | + problems = append(problems, fmt.Sprintf( 48 | + "Policy Authority was willing to issue but domain '%s' matches "+ 49 | + "forbiddenDomains entry %q", name, pattern)) 50 | + } 51 | } 52 | } 53 | for _, name := range parsedCert.IPAddresses { 54 | @@ -533,9 +538,10 @@ type Config struct { 55 | 56 | Workers int `validate:"required,min=1"` 57 | // Deprecated: this is ignored, and cert checker always checks both expired and unexpired. 58 | - UnexpiredOnly bool 59 | - BadResultsOnly bool 60 | - CheckPeriod config.Duration 61 | + UnexpiredOnly bool 62 | + BadResultsOnly bool 63 | + SkipForbiddenDomains bool 64 | + CheckPeriod config.Duration 65 | 66 | // AcceptableValidityDurations is a list of durations which are 67 | // acceptable for certificates we issue. 68 | @@ -593,6 +599,8 @@ func main() { 69 | acceptableValidityDurations[ninetyDays] = true 70 | } 71 | 72 | + skipForbiddenDomains := config.CertChecker.SkipForbiddenDomains 73 | + 74 | // Validate PA config and set defaults if needed. 75 | cmd.FailOnError(config.PA.CheckChallenges(), "Invalid PA configuration") 76 | cmd.FailOnError(config.PA.CheckIdentifiers(), "Invalid PA configuration") 77 | @@ -637,6 +645,7 @@ func main() { 78 | acceptableValidityDurations, 79 | lints, 80 | logger, 81 | + skipForbiddenDomains, 82 | ) 83 | fmt.Fprintf(os.Stderr, "# Getting certificates issued in the last %s\n", config.CertChecker.CheckPeriod) 84 | 85 | -------------------------------------------------------------------------------- /patches/cmd_config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/config.go b/cmd/config.go 2 | index f8b6b847f..38ea91f33 100644 3 | --- a/cmd/config.go 4 | +++ b/cmd/config.go 5 | @@ -469,7 +469,7 @@ type GRPCServerConfig struct { 6 | // this controls how long it takes before a client learns about changes to its 7 | // backends. 8 | // https://pkg.go.dev/google.golang.org/grpc/keepalive#ServerParameters 9 | - MaxConnectionAge config.Duration `validate:"required"` 10 | + MaxConnectionAge config.Duration 11 | } 12 | 13 | // GRPCServiceConfig contains the information needed to configure a gRPC service. 14 | -------------------------------------------------------------------------------- /patches/config_akamai-purger.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/akamai-purger.json b/test/config/akamai-purger.json 2 | index 62c5b4cc9..2c39d70cb 100644 3 | --- a/test/config/akamai-purger.json 4 | +++ b/test/config/akamai-purger.json 5 | @@ -9,9 +9,13 @@ 6 | "accessToken": "idk-how-this-is-different-from-client-token-but-okay", 7 | "v3Network": "staging", 8 | "tls": { 9 | - "caCertfile": "test/certs/ipki/minica.pem", 10 | - "certFile": "test/certs/ipki/akamai-purger.boulder/cert.pem", 11 | - "keyFile": "test/certs/ipki/akamai-purger.boulder/key.pem" 12 | + "caCertfile": "labca/certs/ipki/minica.pem", 13 | + "certFile": "labca/certs/ipki/akamai-purger.boulder/cert.pem", 14 | + "keyFile": "labca/certs/ipki/akamai-purger.boulder/key.pem" 15 | + }, 16 | + "throughput": { 17 | + "queueEntriesPerBatch": 5, 18 | + "purgeBatchInterval": "5m" 19 | }, 20 | "grpc": { 21 | "address": ":9099", 22 | -------------------------------------------------------------------------------- /patches/config_bad-key-revoker.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/bad-key-revoker.json b/test/config/bad-key-revoker.json 2 | index f4696dc2..b9c19ce3 100644 3 | --- a/test/config/bad-key-revoker.json 4 | +++ b/test/config/bad-key-revoker.json 5 | @@ -5,6 +5,13 @@ 6 | "maxOpenConns": 10 7 | }, 8 | "debugAddr": ":8020", 9 | + "dnsTries": 3, 10 | + "dnsStaticResolvers": [ 11 | + "127.0.0.1:8053", 12 | + "127.0.0.1:8054" 13 | + ], 14 | + "dnsTimeout": "3s", 15 | + "dnsAllowLoopbackAddresses": true, 16 | "tls": { 17 | "caCertFile": "test/certs/ipki/minica.pem", 18 | "certFile": "test/certs/ipki/bad-key-revoker.boulder/cert.pem", 19 | @@ -32,7 +39,7 @@ 20 | }, 21 | "maximumRevocations": 15, 22 | "findCertificatesBatchSize": 10, 23 | - "interval": "50ms", 24 | + "interval": "5m", 25 | "backoffIntervalMax": "2s" 26 | }, 27 | "syslog": { 28 | -------------------------------------------------------------------------------- /patches/config_crl-storer.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/crl-storer.json b/test/config/crl-storer.json 2 | index 3ab267b0f..3c6f5c6a2 100644 3 | --- a/test/config/crl-storer.json 4 | +++ b/test/config/crl-storer.json 5 | @@ -23,13 +23,9 @@ 6 | } 7 | }, 8 | "issuerCerts": [ 9 | - "test/certs/webpki/int-rsa-a.cert.pem", 10 | - "test/certs/webpki/int-rsa-b.cert.pem", 11 | - "test/certs/webpki/int-rsa-c.cert.pem", 12 | - "test/certs/webpki/int-ecdsa-a.cert.pem", 13 | - "test/certs/webpki/int-ecdsa-b.cert.pem", 14 | - "test/certs/webpki/int-ecdsa-c.cert.pem" 15 | + "test/certs/webpki/int-rsa-a.cert.pem" 16 | ], 17 | + "localStorePath": "/var/www/html/crl", 18 | "s3Endpoint": "http://localhost:4501", 19 | "s3Bucket": "lets-encrypt-crls", 20 | "awsConfigFile": "test/config/crl-storer.ini", 21 | -------------------------------------------------------------------------------- /patches/config_crl-updater.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/crl-updater.json b/test/config/crl-updater.json 2 | index adb2b01e5..6066b7e5e 100644 3 | --- a/test/config/crl-updater.json 4 | +++ b/test/config/crl-updater.json 5 | @@ -36,24 +36,19 @@ 6 | "hostOverride": "crl-storer.boulder" 7 | }, 8 | "issuerCerts": [ 9 | - "test/certs/webpki/int-rsa-a.cert.pem", 10 | - "test/certs/webpki/int-rsa-b.cert.pem", 11 | - "test/certs/webpki/int-rsa-c.cert.pem", 12 | - "test/certs/webpki/int-ecdsa-a.cert.pem", 13 | - "test/certs/webpki/int-ecdsa-b.cert.pem", 14 | - "test/certs/webpki/int-ecdsa-c.cert.pem" 15 | + "test/certs/webpki/int-rsa-a.cert.pem" 16 | ], 17 | - "numShards": 10, 18 | - "shardWidth": "240h", 19 | - "lookbackPeriod": "24h", 20 | - "updatePeriod": "10m", 21 | - "updateTimeout": "1m", 22 | + "numShards": 1, 23 | + "shardWidth": "24h", 24 | + "lookbackPeriod": "96h", 25 | + "updatePeriod": "24h", 26 | + "updateTimeout": "2m", 27 | "expiresMargin": "5m", 28 | "cacheControl": "stale-if-error=60", 29 | "temporallyShardedSerialPrefixes": [ 30 | "7f" 31 | ], 32 | - "maxParallelism": 10, 33 | + "maxParallelism": 1, 34 | "maxAttempts": 2, 35 | "features": {} 36 | }, 37 | -------------------------------------------------------------------------------- /patches/config_duration.patch: -------------------------------------------------------------------------------- 1 | diff --git a/config/duration.go b/config/duration.go 2 | index 90cb2277d..44b56bc18 100644 3 | --- a/config/duration.go 4 | +++ b/config/duration.go 5 | @@ -10,7 +10,7 @@ import ( 6 | // Duration is custom type embedding a time.Duration which allows defining 7 | // methods such as serialization to YAML or JSON. 8 | type Duration struct { 9 | - time.Duration `validate:"required"` 10 | + time.Duration 11 | } 12 | 13 | // DurationCustomTypeFunc enables registration of our custom config.Duration 14 | -------------------------------------------------------------------------------- /patches/config_expiration-mailer.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/expiration-mailer.json b/test/config/expiration-mailer.json 2 | index 3b813060..6c709172 100644 3 | --- a/test/config/expiration-mailer.json 4 | +++ b/test/config/expiration-mailer.json 5 | @@ -16,6 +16,13 @@ 6 | ], 7 | "emailTemplate": "test/config/expiration-mailer.gotmpl", 8 | "debugAddr": ":8008", 9 | + "dnsTries": 3, 10 | + "dnsStaticResolvers": [ 11 | + "127.0.0.1:8053", 12 | + "127.0.0.1:8054" 13 | + ], 14 | + "dnsTimeout": "3s", 15 | + "dnsAllowLoopbackAddresses": true, 16 | "tls": { 17 | "caCertFile": "test/certs/ipki/minica.pem", 18 | "certFile": "test/certs/ipki/expiration-mailer.boulder/cert.pem", 19 | -------------------------------------------------------------------------------- /patches/config_notify-mailer.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/notify-mailer.json b/test/config/notify-mailer.json 2 | index f6813a696..115d5b150 100644 3 | --- a/test/config/notify-mailer.json 4 | +++ b/test/config/notify-mailer.json 5 | @@ -2,13 +2,22 @@ 6 | "notifyMailer": { 7 | "server": "localhost", 8 | "port": "9380", 9 | + "hostnamePolicyFile": "test/hostname-policy.yaml", 10 | "username": "cert-manager@example.com", 11 | "passwordFile": "test/secrets/smtp_password", 12 | + "SMTPTrustedRootFile": "test/certs/ipki/minica.pem", 13 | "db": { 14 | "dbConnectFile": "test/secrets/mailer_dburl", 15 | "maxOpenConns": 10 16 | } 17 | }, 18 | + "pa": { 19 | + "challenges": { 20 | + "http-01": true, 21 | + "dns-01": true, 22 | + "tls-alpn-01": true 23 | + } 24 | + }, 25 | "syslog": { 26 | "stdoutLevel": 7, 27 | "syslogLevel": 7 28 | -------------------------------------------------------------------------------- /patches/config_ocsp-responder.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/ocsp-responder.json b/test/config/ocsp-responder.json 2 | index c67aa41f7..92fe8a28f 100644 3 | --- a/test/config/ocsp-responder.json 4 | +++ b/test/config/ocsp-responder.json 5 | @@ -4,22 +4,6 @@ 6 | "dbConnectFile": "test/secrets/ocsp_responder_dburl", 7 | "maxOpenConns": 10 8 | }, 9 | - "redis": { 10 | - "username": "ocsp-responder", 11 | - "passwordFile": "test/secrets/ocsp_responder_redis_password", 12 | - "shardAddrs": { 13 | - "shard1": "10.33.33.2:4218", 14 | - "shard2": "10.33.33.3:4218" 15 | - }, 16 | - "timeout": "5s", 17 | - "poolSize": 100, 18 | - "routeRandomly": true, 19 | - "tls": { 20 | - "caCertFile": "test/certs/ipki/minica.pem", 21 | - "certFile": "test/certs/ipki/ocsp-responder.boulder/cert.pem", 22 | - "keyFile": "test/certs/ipki/ocsp-responder.boulder/key.pem" 23 | - } 24 | - }, 25 | "tls": { 26 | "caCertFile": "test/certs/ipki/minica.pem", 27 | "certFile": "test/certs/ipki/ocsp-responder.boulder/cert.pem", 28 | @@ -49,12 +33,7 @@ 29 | "path": "/", 30 | "listenAddress": "0.0.0.0:4002", 31 | "issuerCerts": [ 32 | - "test/certs/webpki/int-rsa-a.cert.pem", 33 | - "test/certs/webpki/int-rsa-b.cert.pem", 34 | - "test/certs/webpki/int-rsa-c.cert.pem", 35 | - "test/certs/webpki/int-ecdsa-a.cert.pem", 36 | - "test/certs/webpki/int-ecdsa-b.cert.pem", 37 | - "test/certs/webpki/int-ecdsa-c.cert.pem" 38 | + "test/certs/webpki/int-rsa-a.cert.pem" 39 | ], 40 | "liveSigningPeriod": "60h", 41 | "timeout": "4.9s", 42 | -------------------------------------------------------------------------------- /patches/config_publisher.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/publisher.json b/test/config/publisher.json 2 | index 1909a6f60..795de12e6 100644 3 | --- a/test/config/publisher.json 4 | +++ b/test/config/publisher.json 5 | @@ -4,20 +4,8 @@ 6 | "blockProfileRate": 1000000000, 7 | "chains": [ 8 | [ 9 | - "test/certs/webpki/int-rsa-a.cert.pem", 10 | - "test/certs/webpki/root-rsa.cert.pem" 11 | - ], 12 | - [ 13 | - "test/certs/webpki/int-rsa-b.cert.pem", 14 | - "test/certs/webpki/root-rsa.cert.pem" 15 | - ], 16 | - [ 17 | - "test/certs/webpki/int-ecdsa-a.cert.pem", 18 | - "test/certs/webpki/root-ecdsa.cert.pem" 19 | - ], 20 | - [ 21 | - "test/certs/webpki/int-ecdsa-b.cert.pem", 22 | - "test/certs/webpki/root-ecdsa.cert.pem" 23 | + "labca/certs/webpki/issuer-01-cert.pem", 24 | + "labca/certs/webpki/root-01-cert.pem" 25 | ] 26 | ], 27 | "grpc": { 28 | @@ -36,9 +24,9 @@ 29 | } 30 | }, 31 | "tls": { 32 | - "caCertFile": "test/certs/ipki/minica.pem", 33 | - "certFile": "test/certs/ipki/publisher.boulder/cert.pem", 34 | - "keyFile": "test/certs/ipki/publisher.boulder/key.pem" 35 | + "caCertFile": "labca/certs/ipki/minica.pem", 36 | + "certFile": "labca/certs/ipki/publisher.boulder/cert.pem", 37 | + "keyFile": "labca/certs/ipki/publisher.boulder/key.pem" 38 | }, 39 | "features": {} 40 | }, 41 | -------------------------------------------------------------------------------- /patches/config_ra.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/ra.json b/test/config/ra.json 2 | index c16978e12..15e8252c0 100644 3 | --- a/test/config/ra.json 4 | +++ b/test/config/ra.json 5 | @@ -3,7 +3,8 @@ 6 | "limiter": { 7 | "redis": { 8 | "username": "boulder-wfe", 9 | - "passwordFile": "test/secrets/wfe_ratelimits_redis_password", 10 | + "passwordFile": "labca/secrets/wfe_ratelimits_redis_password", 11 | + "db": 1, 12 | "lookups": [ 13 | { 14 | "Service": "redisratelimits", 15 | @@ -16,25 +17,20 @@ 16 | "poolSize": 100, 17 | "routeRandomly": true, 18 | "tls": { 19 | - "caCertFile": "test/certs/ipki/minica.pem", 20 | - "certFile": "test/certs/ipki/wfe.boulder/cert.pem", 21 | - "keyFile": "test/certs/ipki/wfe.boulder/key.pem" 22 | + "caCertFile": "labca/certs/ipki/minica.pem", 23 | + "certFile": "labca/certs/ipki/wfe.boulder/cert.pem", 24 | + "keyFile": "labca/certs/ipki/wfe.boulder/key.pem" 25 | } 26 | }, 27 | - "Defaults": "test/config/wfe2-ratelimit-defaults.yml", 28 | - "Overrides": "test/config/wfe2-ratelimit-overrides.yml" 29 | + "Defaults": "labca/config/wfe2-ratelimit-defaults.yml", 30 | + "Overrides": "labca/config/wfe2-ratelimit-overrides.yml" 31 | }, 32 | "maxContactsPerRegistration": 3, 33 | "debugAddr": ":8002", 34 | - "hostnamePolicyFile": "test/hostname-policy.yaml", 35 | + "hostnamePolicyFile": "labca/hostname-policy.yaml", 36 | "goodkey": {}, 37 | "issuerCerts": [ 38 | - "test/certs/webpki/int-rsa-a.cert.pem", 39 | - "test/certs/webpki/int-rsa-b.cert.pem", 40 | - "test/certs/webpki/int-rsa-c.cert.pem", 41 | - "test/certs/webpki/int-ecdsa-a.cert.pem", 42 | - "test/certs/webpki/int-ecdsa-b.cert.pem", 43 | - "test/certs/webpki/int-ecdsa-c.cert.pem" 44 | + "labca/certs/webpki/issuer-01-cert.pem" 45 | ], 46 | "validationProfiles": { 47 | "legacy": { 48 | @@ -58,9 +54,9 @@ 49 | }, 50 | "defaultProfileName": "legacy", 51 | "tls": { 52 | - "caCertFile": "test/certs/ipki/minica.pem", 53 | - "certFile": "test/certs/ipki/ra.boulder/cert.pem", 54 | - "keyFile": "test/certs/ipki/ra.boulder/key.pem" 55 | + "caCertFile": "labca/certs/ipki/minica.pem", 56 | + "certFile": "labca/certs/ipki/ra.boulder/cert.pem", 57 | + "keyFile": "labca/certs/ipki/ra.boulder/key.pem" 58 | }, 59 | "vaService": { 60 | "dnsAuthority": "consul.service.consul", 61 | @@ -154,7 +150,7 @@ 62 | }, 63 | "ctLogs": { 64 | "stagger": "500ms", 65 | - "logListFile": "test/ct-test-srv/log_list.json", 66 | + "logListFile": "labca/ct-test-srv/log_list.json", 67 | "sctLogs": [ 68 | "A1 Current", 69 | "A1 Future", 70 | -------------------------------------------------------------------------------- /patches/config_rocsp_config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/rocsp/config/rocsp_config.go b/rocsp/config/rocsp_config.go 2 | index c5416a499..d23091b53 100644 3 | --- a/rocsp/config/rocsp_config.go 4 | +++ b/rocsp/config/rocsp_config.go 5 | @@ -31,6 +31,8 @@ type RedisConfig struct { 6 | TLS cmd.TLSConfig 7 | // Username is a Redis username. 8 | Username string `validate:"required"` 9 | + // DB is the database number in Redis 10 | + DB int `validate:"min=0"` 11 | // ShardAddrs is a map of shard names to IP address:port pairs. The go-redis 12 | // `Ring` client will shard reads and writes across the provided Redis 13 | // Servers based on a consistent hashing algorithm. 14 | @@ -114,6 +116,7 @@ func MakeClient(c *RedisConfig, clk clock.Clock, stats prometheus.Registerer) (* 15 | 16 | rdb := redis.NewRing(&redis.RingOptions{ 17 | Addrs: c.ShardAddrs, 18 | + DB: c.DB, 19 | Username: c.Username, 20 | Password: password, 21 | TLSConfig: tlsConfig, 22 | -------------------------------------------------------------------------------- /patches/config_wfe2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/config/wfe2.json b/test/config/wfe2.json 2 | index 51c7aa8ef..1ed5d37af 100644 3 | --- a/test/config/wfe2.json 4 | +++ b/test/config/wfe2.json 5 | @@ -3,8 +3,8 @@ 6 | "timeout": "30s", 7 | "listenAddress": "0.0.0.0:4001", 8 | "TLSListenAddress": "0.0.0.0:4431", 9 | - "serverCertificatePath": "test/certs/ipki/boulder/cert.pem", 10 | - "serverKeyPath": "test/certs/ipki/boulder/key.pem", 11 | + "serverCertificatePath": "labca/certs/ipki/boulder/cert.pem", 12 | + "serverKeyPath": "labca/certs/ipki/boulder/key.pem", 13 | "allowOrigins": [ 14 | "*" 15 | ], 16 | @@ -12,13 +12,14 @@ 17 | "subscriberAgreementURL": "https://boulder.service.consul:4431/terms/v7", 18 | "debugAddr": ":8013", 19 | "directoryCAAIdentity": "happy-hacker-ca.invalid", 20 | - "directoryWebsite": "https://github.com/letsencrypt/boulder", 21 | + "directoryWebsite": "https://github.com/hakwerk/labca", 22 | + "hostnamePolicyFile": "labca/hostname-policy.yaml", 23 | "legacyKeyIDPrefix": "http://boulder.service.consul:4000/reg/", 24 | "goodkey": {}, 25 | "tls": { 26 | - "caCertFile": "test/certs/ipki/minica.pem", 27 | - "certFile": "test/certs/ipki/wfe.boulder/cert.pem", 28 | - "keyFile": "test/certs/ipki/wfe.boulder/key.pem" 29 | + "caCertFile": "labca/certs/ipki/minica.pem", 30 | + "certFile": "labca/certs/ipki/wfe.boulder/cert.pem", 31 | + "keyFile": "labca/certs/ipki/wfe.boulder/key.pem" 32 | }, 33 | "raService": { 34 | "dnsAuthority": "consul.service.consul", 35 | @@ -72,39 +73,20 @@ 36 | "hostOverride": "nonce.boulder" 37 | }, 38 | "nonceHMACKey": { 39 | - "keyFile": "test/secrets/nonce_prefix_key" 40 | + "keyFile": "labca/secrets/nonce_prefix_key" 41 | }, 42 | "chains": [ 43 | [ 44 | - "test/certs/webpki/int-rsa-a.cert.pem", 45 | - "test/certs/webpki/root-rsa.cert.pem" 46 | - ], 47 | - [ 48 | - "test/certs/webpki/int-rsa-b.cert.pem", 49 | - "test/certs/webpki/root-rsa.cert.pem" 50 | - ], 51 | - [ 52 | - "test/certs/webpki/int-ecdsa-a.cert.pem", 53 | - "test/certs/webpki/root-ecdsa.cert.pem" 54 | - ], 55 | - [ 56 | - "test/certs/webpki/int-ecdsa-b.cert.pem", 57 | - "test/certs/webpki/root-ecdsa.cert.pem" 58 | - ], 59 | - [ 60 | - "test/certs/webpki/int-ecdsa-a-cross.cert.pem", 61 | - "test/certs/webpki/root-rsa.cert.pem" 62 | - ], 63 | - [ 64 | - "test/certs/webpki/int-ecdsa-b-cross.cert.pem", 65 | - "test/certs/webpki/root-rsa.cert.pem" 66 | + "labca/certs/webpki/issuer-01-cert.pem", 67 | + "labca/certs/webpki/root-01-cert.pem" 68 | ] 69 | ], 70 | "staleTimeout": "5m", 71 | "limiter": { 72 | "redis": { 73 | "username": "boulder-wfe", 74 | - "passwordFile": "test/secrets/wfe_ratelimits_redis_password", 75 | + "passwordFile": "labca/secrets/wfe_ratelimits_redis_password", 76 | + "db": 1, 77 | "lookups": [ 78 | { 79 | "Service": "redisratelimits", 80 | @@ -117,13 +99,13 @@ 81 | "poolSize": 100, 82 | "routeRandomly": true, 83 | "tls": { 84 | - "caCertFile": "test/certs/ipki/minica.pem", 85 | - "certFile": "test/certs/ipki/wfe.boulder/cert.pem", 86 | - "keyFile": "test/certs/ipki/wfe.boulder/key.pem" 87 | + "caCertFile": "labca/certs/ipki/minica.pem", 88 | + "certFile": "labca/certs/ipki/wfe.boulder/cert.pem", 89 | + "keyFile": "labca/certs/ipki/wfe.boulder/key.pem" 90 | } 91 | }, 92 | - "Defaults": "test/config/wfe2-ratelimit-defaults.yml", 93 | - "Overrides": "test/config/wfe2-ratelimit-overrides.yml" 94 | + "Defaults": "labca/config/wfe2-ratelimit-defaults.yml", 95 | + "Overrides": "labca/config/wfe2-ratelimit-overrides.yml" 96 | }, 97 | "features": { 98 | "ServeRenewalInfo": true, 99 | @@ -136,7 +118,7 @@ 100 | }, 101 | "unpause": { 102 | "hmacKey": { 103 | - "keyFile": "test/secrets/sfe_unpause_key" 104 | + "keyFile": "labca/secrets/sfe_unpause_key" 105 | }, 106 | "jwtLifetime": "336h", 107 | "url": "https://boulder.service.consul:4003" 108 | -------------------------------------------------------------------------------- /patches/contact-auditor_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/contact-auditor/main.go b/cmd/contact-auditor/main.go 2 | index fdec0c660..cc62d91c0 100644 3 | --- a/cmd/contact-auditor/main.go 4 | +++ b/cmd/contact-auditor/main.go 5 | @@ -12,7 +12,9 @@ import ( 6 | "time" 7 | 8 | "github.com/letsencrypt/boulder/cmd" 9 | + "github.com/letsencrypt/boulder/core" 10 | "github.com/letsencrypt/boulder/db" 11 | + "github.com/letsencrypt/boulder/identifier" 12 | blog "github.com/letsencrypt/boulder/log" 13 | "github.com/letsencrypt/boulder/policy" 14 | "github.com/letsencrypt/boulder/sa" 15 | @@ -50,9 +52,16 @@ func validateContacts(id int64, createdAt string, contacts []string) error { 16 | fmt.Fprintf(&probsBuff, "%d\t%s\tvalidation\t%q\t%q\t%q\n", id, createdAt, contact, prob, contacts) 17 | } 18 | 19 | + var pa *policy.AuthorityImpl 20 | + logger := cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7}) 21 | + pa, _ = policy.New( 22 | + map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true}, 23 | + map[core.AcmeChallenge]bool{}, 24 | + logger) 25 | + 26 | for _, contact := range contacts { 27 | if strings.HasPrefix(contact, "mailto:") { 28 | - err := policy.ValidEmail(strings.TrimPrefix(contact, "mailto:")) 29 | + err := pa.ValidEmail(strings.TrimPrefix(contact, "mailto:")) 30 | if err != nil { 31 | writeProb(contact, err.Error()) 32 | } 33 | -------------------------------------------------------------------------------- /patches/core_interfaces.patch: -------------------------------------------------------------------------------- 1 | diff --git a/core/interfaces.go b/core/interfaces.go 2 | index 35ebf3896..61d2510e3 100644 3 | --- a/core/interfaces.go 4 | +++ b/core/interfaces.go 5 | @@ -11,4 +11,5 @@ type PolicyAuthority interface { 6 | ChallengeTypesFor(identifier.ACMEIdentifier) ([]AcmeChallenge, error) 7 | ChallengeTypeEnabled(AcmeChallenge) bool 8 | CheckAuthzChallenges(*Authorization) error 9 | + ValidEmail(address string) error 10 | } 11 | -------------------------------------------------------------------------------- /patches/crl-storer_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/crl-storer/main.go b/cmd/crl-storer/main.go 2 | index 4dddfaa9f..8dcf40bbc 100644 3 | --- a/cmd/crl-storer/main.go 4 | +++ b/cmd/crl-storer/main.go 5 | @@ -46,6 +46,9 @@ type Config struct { 6 | // https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html. 7 | AWSCredsFile string 8 | 9 | + // If this is set, store the files locally instead of using (fake) S3 10 | + LocalStorePath string 11 | + 12 | Features features.Config 13 | } 14 | 15 | @@ -129,7 +132,7 @@ func main() { 16 | } 17 | s3client := s3.NewFromConfig(awsConfig, s3opts...) 18 | 19 | - csi, err := storer.New(issuers, s3client, c.CRLStorer.S3Bucket, scope, logger, clk) 20 | + csi, err := storer.New(issuers, s3client, c.CRLStorer.S3Bucket, c.CRLStorer.LocalStorePath, scope, logger, clk) 21 | cmd.FailOnError(err, "Failed to create CRLStorer impl") 22 | 23 | start, err := bgrpc.NewServer(c.CRLStorer.GRPC, logger).Add( 24 | -------------------------------------------------------------------------------- /patches/db_migrations2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sa/db/boulder_sa/20230519000000_CrlShards.sql b/sa/db/boulder_sa/20230519000000_CrlShards.sql 2 | index 6c0d0f9eb..8f4152299 100644 3 | --- a/sa/db/boulder_sa/20230519000000_CrlShards.sql 4 | +++ b/sa/db/boulder_sa/20230519000000_CrlShards.sql 5 | @@ -1,7 +1,7 @@ 6 | -- +migrate Up 7 | -- SQL in section 'Up' is executed when this migration is applied 8 | 9 | -CREATE TABLE `crlShards` ( 10 | +CREATE TABLE IF NOT EXISTS `crlShards` ( 11 | `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, 12 | `issuerID` bigint(20) NOT NULL, 13 | `idx` int UNSIGNED NOT NULL, 14 | -------------------------------------------------------------------------------- /patches/db_migrations3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sa/db/boulder_sa/20230919000000_RevokedCertificates.sql b/sa/db/boulder_sa/20230919000000_RevokedCertificates.sql 2 | index fe86aa71b..f47feb9fb 100644 3 | --- a/sa/db/boulder_sa/20230919000000_RevokedCertificates.sql 4 | +++ b/sa/db/boulder_sa/20230919000000_RevokedCertificates.sql 5 | @@ -1,7 +1,7 @@ 6 | -- +migrate Up 7 | -- SQL in section 'Up' is executed when this migration is applied 8 | 9 | -CREATE TABLE `revokedCertificates` ( 10 | +CREATE TABLE IF NOT EXISTS `revokedCertificates` ( 11 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 12 | `issuerID` bigint(20) NOT NULL, 13 | `serial` varchar(255) NOT NULL, 14 | -------------------------------------------------------------------------------- /patches/db_migrations4.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sa/db/boulder_sa/20240119000000_ReplacementOrders.sql b/sa/db/boulder_sa/20240119000000_ReplacementOrders.sql 2 | index c2bc65f9c..d331ca4fd 100644 3 | --- a/sa/db/boulder_sa/20240119000000_ReplacementOrders.sql 4 | +++ b/sa/db/boulder_sa/20240119000000_ReplacementOrders.sql 5 | @@ -1,7 +1,7 @@ 6 | -- +migrate Up 7 | -- SQL in section 'Up' is executed when this migration is applied 8 | 9 | -CREATE TABLE `replacementOrders` ( 10 | +CREATE TABLE IF NOT EXISTS `replacementOrders` ( 11 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 12 | `serial` varchar(255) NOT NULL, 13 | `orderID` bigint(20) NOT NULL, 14 | -------------------------------------------------------------------------------- /patches/db_migrations5.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sa/db/boulder_sa/20240514000000_Paused.sql b/sa/db/boulder_sa/20240514000000_Paused.sql 2 | index 9f5890cad..1707916ba 100644 3 | --- a/sa/db/boulder_sa/20240514000000_Paused.sql 4 | +++ b/sa/db/boulder_sa/20240514000000_Paused.sql 5 | @@ -5,7 +5,7 @@ 6 | -- partition it. This table expected to be < 800K rows initially and grow at a 7 | -- rate of ~18% per year. 8 | 9 | -CREATE TABLE `paused` ( 10 | +CREATE TABLE IF NOT EXISTS `paused` ( 11 | `registrationID` bigint(20) UNSIGNED NOT NULL, 12 | `identifierType` tinyint(4) NOT NULL, 13 | `identifierValue` varchar(255) NOT NULL, 14 | -------------------------------------------------------------------------------- /patches/entrypoint.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/entrypoint.sh b/test/entrypoint.sh 2 | index 343979a0c..3733ba952 100755 3 | --- a/test/entrypoint.sh 4 | +++ b/test/entrypoint.sh 5 | @@ -13,15 +13,15 @@ service rsyslog start 6 | # make sure we can reach the mysqldb. 7 | ./test/wait-for-it.sh boulder-mysql 3306 8 | 9 | -# make sure we can reach the proxysql. 10 | -./test/wait-for-it.sh bproxysql 6032 11 | - 12 | # make sure we can reach pkilint 13 | ./test/wait-for-it.sh bpkimetal 8080 14 | 15 | # create the database 16 | MYSQL_CONTAINER=1 $DIR/create_db.sh 17 | 18 | +# Generate the internal keys and certs 19 | +./test/certs/generate.sh 20 | + 21 | if [[ $# -eq 0 ]]; then 22 | exec python3 ./start.py 23 | fi 24 | -------------------------------------------------------------------------------- /patches/expiration-mailer_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/expiration-mailer/main.go b/cmd/expiration-mailer/main.go 2 | index 8c80c8408..4102e879b 100644 3 | --- a/cmd/expiration-mailer/main.go 4 | +++ b/cmd/expiration-mailer/main.go 5 | @@ -23,6 +23,7 @@ import ( 6 | 7 | "github.com/prometheus/client_golang/prometheus" 8 | 9 | + "github.com/letsencrypt/boulder/bdns" 10 | "github.com/letsencrypt/boulder/cmd" 11 | "github.com/letsencrypt/boulder/config" 12 | "github.com/letsencrypt/boulder/core" 13 | @@ -40,7 +41,7 @@ import ( 14 | ) 15 | 16 | const ( 17 | - defaultExpirationSubject = "Let's Encrypt certificate expiration notice for domain {{.ExpirationSubject}}" 18 | + defaultExpirationSubject = "LabCA certificate expiration notice for domain {{.ExpirationSubject}}" 19 | ) 20 | 21 | var ( 22 | @@ -162,8 +163,12 @@ func (m *mailer) sendNags(conn bmail.Conn, contacts []string, certs []*x509.Cert 23 | if parsed.Scheme != "mailto" { 24 | continue 25 | } 26 | + pa, err := policy.New(nil, nil, nil) 27 | + if err != nil { 28 | + return fmt.Errorf("cannot create policy authority implementation") 29 | + } 30 | address := parsed.Opaque 31 | - err = policy.ValidEmail(address) 32 | + err = pa.ValidEmail(address) 33 | if err != nil { 34 | m.log.Debugf("skipping invalid email: %s", err) 35 | continue 36 | @@ -697,10 +702,17 @@ type Config struct { 37 | TLS cmd.TLSConfig 38 | SAService *cmd.GRPCClientConfig 39 | 40 | + DNSTries int 41 | + DNSStaticResolvers []string 42 | + DNSTimeout string 43 | + DNSAllowLoopbackAddresses bool 44 | + 45 | // Path to a file containing a list of trusted root certificates for use 46 | // during the SMTP connection (as opposed to the gRPC connections). 47 | SMTPTrustedRootFile string 48 | 49 | + UserAgent string 50 | + 51 | Features features.Config 52 | } 53 | 54 | @@ -850,8 +862,36 @@ func main() { 55 | cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") 56 | sac := sapb.NewStorageAuthorityClient(conn) 57 | 58 | + dnsTimeout, err := time.ParseDuration(c.Mailer.DNSTimeout) 59 | + cmd.FailOnError(err, "Couldn't parse DNS timeout") 60 | + dnsTries := c.Mailer.DNSTries 61 | + if dnsTries < 1 { 62 | + dnsTries = 1 63 | + } 64 | + var resolver bdns.Client 65 | + servers, err := bdns.NewStaticProvider(c.Mailer.DNSStaticResolvers) 66 | + cmd.FailOnError(err, "Couldn't start static DNS server resolver") 67 | + if !c.Mailer.DNSAllowLoopbackAddresses { 68 | + r := bdns.New( 69 | + dnsTimeout, 70 | + servers, 71 | + scope, 72 | + clk, 73 | + dnsTries, 74 | + c.Mailer.UserAgent, 75 | + logger, 76 | + tlsConfig) 77 | + resolver = r 78 | + } else { 79 | + r := bdns.NewTest(dnsTimeout, servers, scope, clk, dnsTries, c.Mailer.UserAgent, logger, tlsConfig) 80 | + resolver = r 81 | + } 82 | + 83 | var smtpRoots *x509.CertPool 84 | - if c.Mailer.SMTPTrustedRootFile != "" { 85 | + smtpSkipVerify := false 86 | + if c.Mailer.SMTPTrustedRootFile == "InsecureSkipVerify" { 87 | + smtpSkipVerify = true 88 | + } else if c.Mailer.SMTPTrustedRootFile != "" { 89 | pem, err := os.ReadFile(c.Mailer.SMTPTrustedRootFile) 90 | cmd.FailOnError(err, "Loading trusted roots file") 91 | smtpRoots = x509.NewCertPool() 92 | @@ -885,6 +925,8 @@ func main() { 93 | c.Mailer.Username, 94 | smtpPassword, 95 | smtpRoots, 96 | + smtpSkipVerify, 97 | + resolver, 98 | *fromAddress, 99 | logger, 100 | scope, 101 | -------------------------------------------------------------------------------- /patches/issuance_crl.patch: -------------------------------------------------------------------------------- 1 | diff --git a/issuance/crl.go b/issuance/crl.go 2 | index f33af1883..b78acf9be 100644 3 | --- a/issuance/crl.go 4 | +++ b/issuance/crl.go 5 | @@ -5,6 +5,7 @@ import ( 6 | "crypto/x509" 7 | "fmt" 8 | "math/big" 9 | + "strings" 10 | "time" 11 | 12 | "github.com/zmap/zlint/v3/lint" 13 | @@ -75,7 +76,11 @@ type CRLRequest struct { 14 | 15 | // crlURL combines the CRL URL base with a shard, and adds a suffix. 16 | func (i *Issuer) crlURL(shard int) string { 17 | - return fmt.Sprintf("%s%d.crl", i.crlURLBase, shard) 18 | + if strings.HasSuffix(i.crlURLBase, "/") { 19 | + return fmt.Sprintf("%s%d.crl", i.crlURLBase, shard) 20 | + } 21 | + 22 | + return i.crlURLBase 23 | } 24 | 25 | func (i *Issuer) IssueCRL(prof *CRLProfile, req *CRLRequest) ([]byte, error) { 26 | -------------------------------------------------------------------------------- /patches/issuance_issuer.patch: -------------------------------------------------------------------------------- 1 | diff --git a/issuance/issuer.go b/issuance/issuer.go 2 | index 95d2f03a7..c3129fe97 100644 3 | --- a/issuance/issuer.go 4 | +++ b/issuance/issuer.go 5 | @@ -161,7 +161,7 @@ type IssuerConfig struct { 6 | Active bool 7 | 8 | IssuerURL string `validate:"required,url"` 9 | - CRLURLBase string `validate:"required,url,startswith=http://,endswith=/"` 10 | + CRLURLBase string `validate:"required,url,startswith=http://"` 11 | 12 | // TODO(#8177): Remove this. 13 | OCSPURL string `validate:"omitempty,url"` 14 | @@ -248,9 +248,6 @@ func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk 15 | if !strings.HasPrefix(config.CRLURLBase, "http://") { 16 | return nil, fmt.Errorf("crlURLBase must use HTTP scheme, got %q", config.CRLURLBase) 17 | } 18 | - if !strings.HasSuffix(config.CRLURLBase, "/") { 19 | - return nil, fmt.Errorf("crlURLBase must end with exactly one forward slash, got %q", config.CRLURLBase) 20 | - } 21 | 22 | // We require that all of our issuers be capable of both issuing certs and 23 | // providing revocation information. 24 | -------------------------------------------------------------------------------- /patches/linter_linter.patch: -------------------------------------------------------------------------------- 1 | diff --git a/linter/linter.go b/linter/linter.go 2 | index 522dd5ee5..a58708f7b 100644 3 | --- a/linter/linter.go 4 | +++ b/linter/linter.go 5 | @@ -200,10 +200,21 @@ func makeIssuer(realIssuer *x509.Certificate, lintSigner crypto.Signer) (*x509.C 6 | SubjectKeyId: realIssuer.SubjectKeyId, 7 | URIs: realIssuer.URIs, 8 | UnknownExtKeyUsage: realIssuer.UnknownExtKeyUsage, 9 | + // Deal with UTF8String elements in the subject that get lost in the strings in the pkix.Name struct 10 | + RawSubject: realIssuer.RawSubject, 11 | } 12 | lintIssuerBytes, err := x509.CreateCertificate(rand.Reader, lintIssuerTBS, lintIssuerTBS, lintSigner.Public(), lintSigner) 13 | if err != nil { 14 | - return nil, fmt.Errorf("failed to create lint issuer: %w", err) 15 | + // Deal with mixed RSA/ECDSA root and issuer certificates 16 | + if strings.Contains(fmt.Sprint(err), "requested SignatureAlgorithm does not match private key type") { 17 | + lintIssuerTBS.SignatureAlgorithm = 0 18 | + lintIssuerBytes, err = x509.CreateCertificate(rand.Reader, lintIssuerTBS, lintIssuerTBS, lintSigner.Public(), lintSigner) 19 | + if err != nil { 20 | + return nil, fmt.Errorf("failed to create lint issuer (without SignatureAlgorithm): %w", err) 21 | + } 22 | + } else { 23 | + return nil, fmt.Errorf("failed to create lint issuer: %w", err) 24 | + } 25 | } 26 | lintIssuer, err := x509.ParseCertificate(lintIssuerBytes) 27 | if err != nil { 28 | -------------------------------------------------------------------------------- /patches/log_prod_prefix.patch: -------------------------------------------------------------------------------- 1 | diff --git a/log/prod_prefix.go b/log/prod_prefix.go 2 | index b4cf55daf..91f1aee8b 100644 3 | --- a/log/prod_prefix.go 4 | +++ b/log/prod_prefix.go 5 | @@ -25,6 +25,9 @@ func getPrefix() (string, string) { 6 | } 7 | 8 | prefix := fmt.Sprintf("%s %s %s[%d]: ", shortHostname, datacenter, core.Command(), os.Getpid()) 9 | + if datacenter == "unknown" { 10 | + prefix = fmt.Sprintf("%s[%d]: ", core.Command(), os.Getpid()) 11 | + } 12 | clkFormat := "2006-01-02T15:04:05.000000+00:00Z" 13 | 14 | return prefix, clkFormat 15 | -------------------------------------------------------------------------------- /patches/log_test_prefix.patch: -------------------------------------------------------------------------------- 1 | diff --git a/log/test_prefix.go b/log/test_prefix.go 2 | index d1fb89491..8974ac30e 100644 3 | --- a/log/test_prefix.go 4 | +++ b/log/test_prefix.go 5 | @@ -2,8 +2,18 @@ 6 | 7 | package log 8 | 9 | +import ( 10 | + "fmt" 11 | + "os" 12 | + 13 | + "github.com/letsencrypt/boulder/core" 14 | +) 15 | + 16 | // getPrefix returns the prefix and clkFormat that should be used by the 17 | // stdout logger. 18 | func getPrefix() (string, string) { 19 | - return "", "15:04:05.000000" 20 | + prefix := fmt.Sprintf("%s[%d]: ", core.Command(), os.Getpid()) 21 | + clkFormat := "2006-01-02T15:04:05.000000+00:00Z" 22 | + 23 | + return prefix, clkFormat 24 | } 25 | -------------------------------------------------------------------------------- /patches/log_validator_validator.patch: -------------------------------------------------------------------------------- 1 | diff --git a/log/validator/validator.go b/log/validator/validator.go 2 | index a73330cb3..a5a752063 100644 3 | --- a/log/validator/validator.go 4 | +++ b/log/validator/validator.go 5 | @@ -203,8 +203,8 @@ func lineValid(text string) error { 6 | if strings.Contains(text, errorPrefix) { 7 | return nil 8 | } 9 | - // Check the extracted checksum against the computed checksum 10 | - if computedChecksum := log.LogLineChecksum(line); checksum != computedChecksum { 11 | + // Check the extracted checksum against the computed checksum, but ignore "message repeated X times" lines 12 | + if computedChecksum := log.LogLineChecksum(line); checksum != computedChecksum && checksum != "message" { 13 | return fmt.Errorf("%s invalid checksum (expected %q, got %q)", errorPrefix, computedChecksum, checksum) 14 | } 15 | return nil 16 | -------------------------------------------------------------------------------- /patches/mail_mailer.patch: -------------------------------------------------------------------------------- 1 | diff --git a/mail/mailer.go b/mail/mailer.go 2 | index 31ebd40b1..760b0b66e 100644 3 | --- a/mail/mailer.go 4 | +++ b/mail/mailer.go 5 | @@ -2,6 +2,7 @@ package mail 6 | 7 | import ( 8 | "bytes" 9 | + "context" 10 | "crypto/rand" 11 | "crypto/tls" 12 | "crypto/x509" 13 | @@ -23,7 +24,9 @@ import ( 14 | "github.com/jmhodges/clock" 15 | "github.com/prometheus/client_golang/prometheus" 16 | 17 | + "github.com/letsencrypt/boulder/bdns" 18 | "github.com/letsencrypt/boulder/core" 19 | + berrors "github.com/letsencrypt/boulder/errors" 20 | blog "github.com/letsencrypt/boulder/log" 21 | ) 22 | 23 | @@ -139,6 +142,8 @@ func New( 24 | username, 25 | password string, 26 | rootCAs *x509.CertPool, 27 | + insecureSkipVerify bool, 28 | + resolver bdns.Client, 29 | from mail.Address, 30 | logger blog.Logger, 31 | stats prometheus.Registerer, 32 | @@ -154,11 +159,13 @@ func New( 33 | return &mailerImpl{ 34 | config: config{ 35 | dialer: &dialerImpl{ 36 | - username: username, 37 | - password: password, 38 | - server: server, 39 | - port: port, 40 | - rootCAs: rootCAs, 41 | + username: username, 42 | + password: password, 43 | + server: server, 44 | + port: port, 45 | + rootCAs: rootCAs, 46 | + insecureSkipVerify: insecureSkipVerify, 47 | + dnsClient: resolver, 48 | }, 49 | log: logger, 50 | from: from, 51 | @@ -202,7 +209,7 @@ func (c config) generateMessage(to []string, subject, body string) ([]byte, erro 52 | fmt.Sprintf("To: %s", strings.Join(addrs, ", ")), 53 | fmt.Sprintf("From: %s", c.from.String()), 54 | fmt.Sprintf("Subject: %s", subject), 55 | - fmt.Sprintf("Date: %s", now.Format(time.RFC822)), 56 | + fmt.Sprintf("Date: %s", now.Format(time.RFC1123Z)), 57 | fmt.Sprintf("Message-Id: <%s.%s.%s>", now.Format("20060102T150405"), mid.String(), c.from.Address), 58 | "MIME-Version: 1.0", 59 | "Content-Type: text/plain; charset=UTF-8", 60 | @@ -259,23 +266,41 @@ func (m *mailerImpl) Connect() (Conn, error) { 61 | type dialerImpl struct { 62 | username, password, server, port string 63 | rootCAs *x509.CertPool 64 | + insecureSkipVerify bool 65 | + dnsClient bdns.Client 66 | } 67 | 68 | func (di *dialerImpl) Dial() (smtpClient, error) { 69 | - hostport := net.JoinHostPort(di.server, di.port) 70 | - var conn net.Conn 71 | - var err error 72 | - conn, err = tls.Dial("tcp", hostport, &tls.Config{ 73 | - RootCAs: di.rootCAs, 74 | - }) 75 | + deadline := time.Now().Add(30 * time.Second) 76 | + ctx, cancel := context.WithDeadline(context.Background(), deadline) 77 | + defer cancel() 78 | + 79 | + addrs, _, err := di.dnsClient.LookupHost(ctx, di.server) 80 | if err != nil { 81 | - return nil, err 82 | + problem := berrors.DNSError("%v") 83 | + return nil, problem 84 | + } 85 | + 86 | + if len(addrs) == 0 { 87 | + return nil, berrors.DNSError("No valid IP addresses found for %s", di.server) 88 | } 89 | - client, err := smtp.NewClient(conn, di.server) 90 | + 91 | + tlsconf := &tls.Config{ 92 | + ServerName: di.server, 93 | + } 94 | + if di.insecureSkipVerify { 95 | + tlsconf.InsecureSkipVerify = true 96 | + } else { 97 | + tlsconf.RootCAs = di.rootCAs 98 | + } 99 | + 100 | + hostport := net.JoinHostPort(addrs[0].String(), di.port) 101 | + client, err := smtp.Dial(hostport) 102 | if err != nil { 103 | return nil, err 104 | } 105 | - auth := smtp.PlainAuth("", di.username, di.password, di.server) 106 | + client.StartTLS(tlsconf) 107 | + auth := smtp.PlainAuth("", di.username, di.password, addrs[0].String()) 108 | if err = client.Auth(auth); err != nil { 109 | return nil, err 110 | } 111 | -------------------------------------------------------------------------------- /patches/makefile.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index 9522b89a7..b5aa9d84a 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -36,7 +36,7 @@ $(CMD_BINS): build_cmds 6 | 7 | build_cmds: | $(OBJDIR) 8 | echo $(OBJECTS) 9 | - GOBIN=$(OBJDIR) GO111MODULE=on go install -mod=vendor $(GO_BUILD_FLAGS) ./... 10 | + GOBIN=$(OBJDIR) GO111MODULE=on go install -mod=vendor -buildvcs=false $(GO_BUILD_FLAGS) ./... 11 | 12 | # Building a .deb requires `fpm` from https://github.com/jordansissel/fpm 13 | # which you can install with `gem install fpm`. 14 | -------------------------------------------------------------------------------- /patches/notify-mailer_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/notify-mailer/main.go b/cmd/notify-mailer/main.go 2 | index 6c01efd64..6da77c7eb 100644 3 | --- a/cmd/notify-mailer/main.go 4 | +++ b/cmd/notify-mailer/main.go 5 | @@ -2,6 +2,7 @@ package notmain 6 | 7 | import ( 8 | "context" 9 | + "crypto/x509" 10 | "encoding/csv" 11 | "encoding/json" 12 | "errors" 13 | @@ -37,6 +38,7 @@ type mailer struct { 14 | recipients []recipient 15 | targetRange interval 16 | sleepInterval time.Duration 17 | + pa *policy.AuthorityImpl 18 | parallelSends uint 19 | } 20 | 21 | @@ -201,7 +203,7 @@ func (m *mailer) run(ctx context.Context) error { 22 | continue 23 | } 24 | 25 | - err := policy.ValidEmail(w.address) 26 | + err := m.pa.ValidEmail(w.address) 27 | if err != nil { 28 | m.log.Infof("Skipping %q due to policy violation: %s", w.address, err) 29 | continue 30 | @@ -502,7 +504,12 @@ type Config struct { 31 | NotifyMailer struct { 32 | DB cmd.DBConfig 33 | cmd.SMTPConfig 34 | + // Path to a file containing a list of trusted root certificates for use 35 | + // during the SMTP connection (as opposed to the gRPC connections). 36 | + SMTPTrustedRootFile string 37 | + cmd.HostnamePolicyConfig 38 | } 39 | + PA cmd.PAConfig 40 | Syslog cmd.SyslogConfig 41 | } 42 | 43 | @@ -570,6 +577,15 @@ func main() { 44 | log.Infof("While reading the recipient list file %s", probs) 45 | } 46 | 47 | + // Validate PA config and set defaults if needed 48 | + cmd.FailOnError(cfg.PA.CheckChallenges(), "Invalid PA configuration") 49 | + 50 | + logger := cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7}) 51 | + pa, err := policy.New(cfg.PA.Identifiers, cfg.PA.Challenges, logger) 52 | + cmd.FailOnError(err, "Failed to create PA") 53 | + err = pa.LoadHostnamePolicyFile(cfg.NotifyMailer.HostnamePolicyFile) 54 | + cmd.FailOnError(err, "Failed to load HostnamePolicyFile") 55 | + 56 | var mailClient bmail.Mailer 57 | if *dryRun { 58 | log.Infof("Starting %s in dry-run mode", cmd.VersionString()) 59 | @@ -579,11 +595,26 @@ func main() { 60 | smtpPassword, err := cfg.NotifyMailer.PasswordConfig.Pass() 61 | cmd.FailOnError(err, "Couldn't load SMTP password from file") 62 | 63 | + var smtpRoots *x509.CertPool 64 | + smtpSkipVerify := false 65 | + if cfg.NotifyMailer.SMTPTrustedRootFile == "InsecureSkipVerify" { 66 | + smtpSkipVerify = true 67 | + } else if cfg.NotifyMailer.SMTPTrustedRootFile != "" { 68 | + pem, err := os.ReadFile(cfg.NotifyMailer.SMTPTrustedRootFile) 69 | + cmd.FailOnError(err, "Loading trusted roots file") 70 | + smtpRoots = x509.NewCertPool() 71 | + if !smtpRoots.AppendCertsFromPEM(pem) { 72 | + cmd.FailOnError(nil, "Failed to parse root certs PEM") 73 | + } 74 | + } 75 | + 76 | mailClient = bmail.New( 77 | cfg.NotifyMailer.Server, 78 | cfg.NotifyMailer.Port, 79 | cfg.NotifyMailer.Username, 80 | smtpPassword, 81 | + smtpRoots, 82 | + smtpSkipVerify, 83 | nil, 84 | *address, 85 | log, 86 | @@ -605,6 +636,7 @@ func main() { 87 | end: *end, 88 | }, 89 | sleepInterval: *sleep, 90 | + pa: pa, 91 | parallelSends: *parallelSends, 92 | } 93 | 94 | -------------------------------------------------------------------------------- /patches/ocsp-responder_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/ocsp-responder/main.go b/cmd/ocsp-responder/main.go 2 | index ec03eb05f..1cfe3e20e 100644 3 | --- a/cmd/ocsp-responder/main.go 4 | +++ b/cmd/ocsp-responder/main.go 5 | @@ -91,7 +91,7 @@ type Config struct { 6 | 7 | // Configuration for using Redis as a cache. This configuration should 8 | // allow for both read and write access. 9 | - Redis *rocsp_config.RedisConfig `validate:"required_without=Source"` 10 | + Redis *rocsp_config.RedisConfig 11 | 12 | // TLS client certificate, private key, and trusted root bundle. 13 | TLS cmd.TLSConfig `validate:"required_without=Source,structonly"` 14 | @@ -165,7 +165,7 @@ as generated by Boulder's ceremony command. 15 | } 16 | source, err = responder.NewMemorySourceFromFile(filename, logger) 17 | cmd.FailOnError(err, fmt.Sprintf("Couldn't read file: %s", url.Path)) 18 | - } else { 19 | + } else if c.OCSPResponder.Redis != nil { 20 | // Set up the redis source and the combined multiplex source. 21 | rocspRWClient, err := rocsp_config.MakeClient(c.OCSPResponder.Redis, clk, scope) 22 | cmd.FailOnError(err, "Could not make redis client") 23 | @@ -209,6 +209,19 @@ as generated by Boulder's ceremony command. 24 | 25 | source, err = redis_responder.NewCheckedRedisSource(rocspSource, dbMap, sac, scope, logger) 26 | cmd.FailOnError(err, "Could not create checkedRedis source") 27 | + } else { 28 | + tlsConfig, err := c.OCSPResponder.TLS.Load(scope) 29 | + cmd.FailOnError(err, "TLS config") 30 | + 31 | + raConn, err := bgrpc.ClientSetup(c.OCSPResponder.RAService, tlsConfig, scope, clk) 32 | + cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA") 33 | + rac := rapb.NewRegistrationAuthorityClient(raConn) 34 | + 35 | + maxInflight := c.OCSPResponder.MaxInflightSignings 36 | + if maxInflight == 0 { 37 | + maxInflight = 1000 38 | + } 39 | + source = live.New(rac, int64(maxInflight), c.OCSPResponder.MaxSigningWaiters) 40 | } 41 | 42 | // Load the certificate from the file path. 43 | -------------------------------------------------------------------------------- /patches/ra_ra.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ra/ra.go b/ra/ra.go 2 | index e8acf0781..3122449be 100644 3 | --- a/ra/ra.go 4 | +++ b/ra/ra.go 5 | @@ -44,7 +44,6 @@ import ( 6 | "github.com/letsencrypt/boulder/issuance" 7 | blog "github.com/letsencrypt/boulder/log" 8 | "github.com/letsencrypt/boulder/metrics" 9 | - "github.com/letsencrypt/boulder/policy" 10 | "github.com/letsencrypt/boulder/probs" 11 | pubpb "github.com/letsencrypt/boulder/publisher/proto" 12 | rapb "github.com/letsencrypt/boulder/ra/proto" 13 | @@ -608,7 +607,7 @@ func (ra *RegistrationAuthorityImpl) validateContacts(contacts []string) error { 14 | if !core.IsASCII(contact) { 15 | return berrors.InvalidEmailError("contact email contains non-ASCII characters") 16 | } 17 | - err = policy.ValidEmail(parsed.Opaque) 18 | + err = ra.PA.ValidEmail(parsed.Opaque) 19 | if err != nil { 20 | return err 21 | } 22 | @@ -1981,6 +1980,9 @@ func crlShard(cert *x509.Certificate) (int64, error) { 23 | return 0, fmt.Errorf("malformed CRLDistributionPoint %q", url) 24 | } 25 | shardStr := url[lastIndex+1:] 26 | + if strings.HasSuffix(shardStr, "-crl.pem") { 27 | + return 1, nil 28 | + } 29 | shardIdx, err := strconv.Atoi(shardStr) 30 | if err != nil { 31 | return 0, fmt.Errorf("parsing CRLDistributionPoint: %s", err) 32 | -------------------------------------------------------------------------------- /patches/ratelimits_names.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ratelimits/names.go b/ratelimits/names.go 2 | index bfda772b5..971892f22 100644 3 | --- a/ratelimits/names.go 4 | +++ b/ratelimits/names.go 5 | @@ -102,6 +102,9 @@ var nameToString = map[Name]string{ 6 | FailedAuthorizationsForPausingPerDomainPerAccount: "FailedAuthorizationsForPausingPerDomainPerAccount", 7 | } 8 | 9 | +// Policy Authority singleton 10 | +var PA *policy.AuthorityImpl 11 | + 12 | // isValid returns true if the Name is a valid rate limit name. 13 | func (n Name) isValid() bool { 14 | return n > Unknown && n < Name(len(nameToString)) 15 | @@ -163,7 +166,15 @@ func validateRegId(id string) error { 16 | // validateDomain validates that the provided string is formatted 'domain', 17 | // where domain is a domain name. 18 | func validateDomain(id string) error { 19 | - err := policy.ValidDomain(id) 20 | + pa := PA 21 | + var err error 22 | + if pa == nil { 23 | + pa, err = policy.New(nil, nil, nil) 24 | + if err != nil { 25 | + return fmt.Errorf("cannot create policy authority implementation") 26 | + } 27 | + } 28 | + err = pa.ValidDomain(id) 29 | if err != nil { 30 | return fmt.Errorf("invalid domain, %q must be formatted 'domain': %w", id, err) 31 | } 32 | @@ -184,7 +195,14 @@ func validateRegIdDomain(id string) error { 33 | return fmt.Errorf( 34 | "invalid regId, %q must be formatted 'regId:domain'", id) 35 | } 36 | - err = policy.ValidDomain(regIdDomain[1]) 37 | + pa := PA 38 | + if pa == nil { 39 | + pa, err = policy.New(nil, nil, nil) 40 | + if err != nil { 41 | + return fmt.Errorf("cannot create policy authority implementation") 42 | + } 43 | + } 44 | + err = pa.ValidDomain(regIdDomain[1]) 45 | if err != nil { 46 | return fmt.Errorf( 47 | "invalid domain, %q must be formatted 'regId:domain': %w", id, err) 48 | @@ -202,7 +220,15 @@ func validateFQDNSet(id string) error { 49 | return fmt.Errorf( 50 | "invalid fqdnSet, %q must be formatted 'fqdnSet'", id) 51 | } 52 | - return policy.WellFormedIdentifiers(identifier.NewDNSSlice(domains)) 53 | + pa := PA 54 | + var err error 55 | + if pa == nil { 56 | + pa, err = policy.New(nil, nil, nil) 57 | + if err != nil { 58 | + return fmt.Errorf("cannot create policy authority implementation") 59 | + } 60 | + } 61 | + return pa.WellFormedIdentifiers(identifier.NewDNSSlice(domains)) 62 | } 63 | 64 | func validateIdForName(name Name, id string) error { 65 | -------------------------------------------------------------------------------- /patches/redis_config.patch: -------------------------------------------------------------------------------- 1 | diff --git a/redis/config.go b/redis/config.go 2 | index c858a4beb..2ec26aab1 100644 3 | --- a/redis/config.go 4 | +++ b/redis/config.go 5 | @@ -24,6 +24,9 @@ type Config struct { 6 | // authenticate to each Redis instance. 7 | cmd.PasswordConfig 8 | 9 | + // DB is the database number in Redis 10 | + DB int `validate:"min=0"` 11 | + 12 | // ShardAddrs is a map of shard names to IP address:port pairs. The go-redis 13 | // `Ring` client will shard reads and writes across the provided Redis 14 | // Servers based on a consistent hashing algorithm. 15 | @@ -134,6 +137,7 @@ func NewRingFromConfig(c Config, stats prometheus.Registerer, log blog.Logger) ( 16 | 17 | inner := redis.NewRing(&redis.RingOptions{ 18 | Addrs: c.ShardAddrs, 19 | + DB: c.DB, 20 | Username: c.Username, 21 | Password: password, 22 | TLSConfig: tlsConfig, 23 | -------------------------------------------------------------------------------- /patches/remoteva_main.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmd/remoteva/main.go b/cmd/remoteva/main.go 2 | index f99ded497..9a1033a87 100644 3 | --- a/cmd/remoteva/main.go 4 | +++ b/cmd/remoteva/main.go 5 | @@ -56,7 +56,8 @@ type Config struct { 6 | // For more information, see: https://pkg.go.dev/crypto/tls#ClientAuthType 7 | SkipGRPCClientCertVerification bool 8 | 9 | - Features features.Config 10 | + Features features.Config 11 | + LabCADomains []string 12 | } 13 | 14 | Syslog cmd.SyslogConfig 15 | @@ -141,7 +142,8 @@ func main() { 16 | c.RVA.AccountURIPrefixes, 17 | c.RVA.Perspective, 18 | c.RVA.RIR, 19 | - bdns.IsReservedIP) 20 | + bdns.IsReservedIP, 21 | + c.RVA.LabCADomains) 22 | cmd.FailOnError(err, "Unable to create Remote-VA server") 23 | 24 | start, err := bgrpc.NewServer(c.RVA.GRPC, logger).Add( 25 | -------------------------------------------------------------------------------- /patches/sfe_templates_layout.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sfe/templates/layout.html b/sfe/templates/layout.html 2 | index 15d5e88d9..2511e9e13 100644 3 | --- a/sfe/templates/layout.html 4 | +++ b/sfe/templates/layout.html 5 | @@ -4,8 +4,8 @@ 6 | 7 | 8 | 9 | - Let's Encrypt - Portal 10 | - 11 | + Self-Service Portal | LabCA 12 | + 13 |