Now it's time to install the Fulcio WebPKI.
Fulcio requires a means to manage certificates. We have two options here, we can use a SoftHSM or Google Certificate Authority service.
📝 As of time of writing, plans are in place to support AWS Cloud HSM and Azure Dedicated HSM.
SSH into the Fulcio Compute instance
$ gcloud compute ssh sigstore-fulcio
We need a few dependencies installed
Update your system
$ sudo apt-get update -y
If you want to save up some time, remove man-db first
$ sudo apt-get remove -y --purge man-db
Grab the following packages
$ sudo apt-get install git gcc haproxy softhsm certbot opensc -y
📝 If you plan to use GCP Certificate Service, you can drop SoftHSM and opensc
Download and run the golang installer (system package is not yet 1.16)
$ curl -O https://storage.googleapis.com/golang/getgo/installer_linux
$ chmod +x installer_linux
$ ./installer_linux
e.g.
Welcome to the Go installer!
Downloading Go version go1.17.1 to /home/luke/.go
This may take a bit of time...
Downloaded!
Setting up GOPATH
GOPATH has been set up!
One more thing! Run `source /home/$USER/.bash_profile` to persist the
new environment variables to your current session, or open a
new shell prompt.
As suggested run
$ source /home/$USER/.bash_profile
$ go version
go version go1.17.1 linux/amd64
$ go install github.com/sigstore/fulcio@v0.1.1
$ sudo mv ~/go/bin/fulcio /usr/local/bin/
Let's create a HAProxy config, set DOMAIN
to your registered domain and your
private IP
address
DOMAIN="fulcio.yourdomain.com"
IP="10.240.0.11"
Let's now run certbot to obtain our TLS certs.
$ sudo certbot certonly --standalone --preferred-challenges http \
--http-01-address ${IP} --http-01-port 80 -d ${DOMAIN} \
--non-interactive --agree-tos --email youremail@domain.com
Move the PEM chain into place
$ sudo cat "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" \
"/etc/letsencrypt/live/${DOMAIN}/privkey.pem" \
| sudo tee "/etc/ssl/private/${DOMAIN}.pem" > /dev/null
Now we need to change certbot configuration for automatic renewal
Prepare post renewal script
$ cat /etc/letsencrypt/renewal-hooks/post/haproxy-ssl-renew.sh
#!/bin/bash
DOMAIN="fulcio.example.com"
cat "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" \
"/etc/letsencrypt/live/${DOMAIN}/privkey.pem" \
> "/etc/ssl/private/${DOMAIN}.pem"
systemctl reload haproxy.service
Make sure the script has executable flag set
$ sudo chmod +x /etc/letsencrypt/renewal-hooks/post/haproxy-ssl-renew.sh
Replace port and address in the certbot's renewal configuration file for the domain (pass ACME request through the haproxy to certbot)
$ ls -l /etc/letsencrypt/renewal/fulcio.example.com.conf
http01_port = 9080
http01_address = 127.0.0.1
Append new line
post_hook = /etc/letsencrypt/renewal-hooks/post/haproxy-ssl-renew.sh
Prepare haproxy configuration
$ cat > haproxy.cfg <<EOF
defaults
timeout connect 10s
timeout client 30s
timeout server 30s
log global
mode http
option httplog
maxconn 3000
log 127.0.0.1 local0
frontend haproxy
#public IP address
bind ${IP}:80
bind ${IP}:443 ssl crt /etc/ssl/private/${DOMAIN}.pem
# HTTPS redirect
redirect scheme https code 301 if !{ ssl_fc }
acl letsencrypt-acl path_beg /.well-known/acme-challenge/
use_backend letsencrypt-backend if letsencrypt-acl
default_backend sigstore_fulcio
backend sigstore_fulcio
server sigstore_fulcio_internal 0.0.0.0:5000
backend letsencrypt-backend
server certbot_internal 127.0.0.1:9080
EOF
Inspect the resulting haproxy.cfg
and make sure everything looks correct.
If so, move it into place
$ sudo mv haproxy.cfg /etc/haproxy/
Check syntax
$ sudo /usr/sbin/haproxy -c -V -f /etc/haproxy/haproxy.cfg
Let's now start HAProxy
$ sudo systemctl enable haproxy.service
Synchronizing state of haproxy.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable haproxy
$ sudo systemctl restart haproxy.service
$ sudo systemctl status haproxy.service
● haproxy.service - HAProxy Load Balancer
Loaded: loaded (/lib/systemd/system/haproxy.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2021-07-18 10:12:28 UTC; 58min ago
Docs: man:haproxy(1)
file:/usr/share/doc/haproxy/configuration.txt.gz
Main PID: 439 (haproxy)
Tasks: 2 (limit: 2322)
Memory: 4.1M
CGroup: /system.slice/haproxy.service
├─439 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid
└─444 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid
Jul 18 10:12:27 sigstore-fulcio systemd[1]: Starting HAProxy Load Balancer...
Jul 18 10:12:28 sigstore-fulcio systemd[1]: Started HAProxy Load Balancer.
Test automatic renewal
$ sudo certbot renew --dry-run
By default SoftHSM stores tokens in
/var/lib/softhsm/tokens/
directory, which is defined in/etc/softhsm/softhsm2.conf
configuration file, below we will define a custom configuration for fulcio.
$ sudo mkdir -p /etc/fulcio-config/config
$ sudo mkdir -p /etc/fulcio-config/tokens
$ cat <<'EOF' | sudo tee /etc/fulcio-config/config/softhsm2.cfg > /dev/null
directories.tokendir = /etc/fulcio-config/tokens
objectstore.backend = file
log.level = INFO
slots.removable = false
EOF
$ export SOFTHSM2_CONF="/etc/fulcio-config/config/softhsm2.cfg"
$ echo 'export SOFTHSM2_CONF="/etc/fulcio-config/config/softhsm2.cfg"' >> ~/.bash_profile
$ sudo -E softhsm2-util --init-token --slot 0 --label fulcio
$ sudo -E softhsm2-util --show-slots
$ ls -la /etc/fulcio-config/tokens
NOTE: A -E
parameter preserves the environment variable we need, and sudo
is needed to be able to write token into the system path.
For example:
$ sudo -E softhsm2-util --init-token --slot 0 --label fulcio
=== SO PIN (4-255 characters) ===
Please enter SO PIN: ****
Please reenter SO PIN: ****
=== User PIN (4-255 characters) ===
Please enter user PIN: ****
Please reenter user PIN: ******
ERROR: The entered PINs are not equal.
=== User PIN (4-255 characters) ===
Please enter user PIN: ****
Please reenter user PIN: ****
The token has been initialized and is reassigned to slot 1773686385
Now remembering you pin, lets create a SoftHSM config for Fulcio
$ cat <<'EOF' | sudo tee /etc/fulcio-config/config/crypto11.conf > /dev/null
{
"Path" : "/usr/lib/softhsm/libsofthsm2.so",
"TokenLabel": "fulcio",
"Pin" : "2324"
}
EOF
Now let's create a private key within the HSM
$ sudo -E pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --login-type user --keypairgen --id 1 --label FulcioCA --key-type EC:secp384r1
For example:
$ sudo -E pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --login-type user --keypairgen --id 1 --label FulcioCA --key-type EC:secp384r1
Using slot 0 with a present token (0x69b84e71)
Logging in to "fulcio".
Please enter User PIN:
Key pair generated:
Private Key Object; EC
label: FulcioCA
ID: 01
Usage: decrypt, sign, unwrap, derive
Access: sensitive, always sensitive, never extractable, local
Public Key Object; EC EC_POINT 384 bits
EC_POINT: 046104b04911577ad1a655ba469b32ae63832d6c0d19482058af1822c2b42f54934da3613cd87171594a9b00ff1f0b298c75fa9383470ec46f0b4a35e73b54c34cf2ecc664ada2d0a818a5ac2390d952cb3b8d66ebea974a1bb2465f323cbebc50927d
EC_PARAMS: 06052b81040022
label: FulcioCA
ID: 01
Usage: encrypt, verify, wrap, derive
Access: local
Now its time to create a Root CA using our newly minted private key:
$ cd /etc/fulcio-config/
$ sudo -E fulcio createca --org={ORG} --country={UK} --locality={TOWN} --province={PROVINCE} --postal-code={POST_CODE} --street-address={STREET} --hsm-caroot-id 1 --out fulcio-root.pem
An example
$ cd /etc/fulcio-config/
$ sudo -E fulcio createca --org=acme --country=USA --locality=Anytown --province=AnyPlace --postal-code=ABCDEF --street-address=123 Main St --hsm-caroot-id 1 --out fulcio-root.pem
2021-10-01T18:09:16.284Z INFO app/createca.go:48 binding to PKCS11 HSM
2021-10-01T18:09:16.289Z INFO app/createca.go:68 finding slot for private key: FulcioCA
2021-10-01T18:09:16.304Z INFO app/createca.go:108 Root CA:
-----BEGIN CERTIFICATE-----
MIICJDCCAaqgAwIBAgIIVUu5cbwBx8EwCgYIKoZIzj0EAwMwVjELMAkGA1UEBhMC
TFYxCzAJBgNVBAgTAkxWMQswCQYDVQQHEwJMVjENMAsGA1UECRMESG9tZTEPMA0G
A1UEERMGTFYxMDI2MQ0wCwYDVQQKEwRhY21lMB4XDTIxMTAwMTE4MDkxNloXDTMx
MTAwMTE4MDkxNlowVjELMAkGA1UEBhMCTFYxCzAJBgNVBAgTAkxWMQswCQYDVQQH
EwJMVjENMAsGA1UECRMESG9tZTEPMA0GA1UEERMGTFYxMDI2MQ0wCwYDVQQKEwRh
Y21lMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEk4wYXHkLhdDlUlASZc65GI+5VDv3
OqmFdOI7/TwnPfrqFBNCxTPp0qNh7//s55tRac5pkXV4Af+xWUETlRd6RqBKcjjX
PHMZ0f+J/pZui4pPmw3ItvVCqfmNvCtASksSo0UwQzAOBgNVHQ8BAf8EBAMCAQYw
EgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUOXQnhKM/yhGTICrrgO78QyVN
nUMwCgYIKoZIzj0EAwMDaAAwZQIwEd1VjWI+P3eXMwUOGXbWJMYzrpcLakwj0JPW
Bx6oFXBadm4jZoKQX1FfNXMWgu0mAjEA4nz6OBtF8YJGRS9bTnWfe4V/lwukRczk
OPl9CeCgaJqQRXlMSw8uf3nO0rYXTGCF
-----END CERTIFICATE-----
2021-10-01T18:09:16.324Z INFO app/createca.go:122 root CA created with PKCS11 ID: 1
2021-10-01T18:09:16.324Z INFO app/createca.go:138 root CA saved to file: fulcio-root.pem
Check Root CA key usage
$ openssl x509 -in fulcio-root.pem -noout -ext extendedKeyUsage,keyUsage
X509v3 Key Usage: critical
Certificate Sign, CRL Sign
Transfer the root certificate over to the certificate transparency log
$ gcloud compute scp fulcio-root.pem <google_account_name>@sigstore-ctl:~/
Navigate to the Certificate Authority Service API and enable the service
On the Google Cloud Console page, go to Security > Certificate Authority Service > Create CA
-
Set the CA type (DevOps)
-
Set the cert subject details
-
Set the key and algorithm to Ecliptic Curve P384
-
Leave Configure Artifacts as it is
-
Label (don't need one)
-
Create the CA
-
Note down the Root CA and Resource name
Set the DNS for the OAuth2 / Dex Server
OAUTH2_DOMAIN="oauth2.example.com"
$ cat > config.json <<EOF
{
"OIDCIssuers": {
"https://accounts.google.com": {
"IssuerURL": "https://accounts.google.com",
"ClientID": "sigstore",
"Type": "email"
},
"https://${OAUTH2_DOMAIN}/auth": {
"IssuerURL": "https://${OAUTH2_DOMAIN}/auth",
"ClientID": "sigstore",
"Type": "email"
}
}
}
EOF
Inspect config.json
and if everything looks in order, copy it into place
$ sudo mv config.json /etc/fulcio-config/
We now have two methods of starting Fulcio depending on your Certificate Authority system choice.
In both cases you may create a bare minimal systemd service
$ cat /etc/systemd/system/fulcio.service
[Unit]
Description=fulcio
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Environment=SOFTHSM2_CONF=/etc/fulcio-config/config/softhsm2.cfg
ExecStart=/usr/local/bin/fulcio serve --config-path=/etc/fulcio-config/config.json ...
WorkingDirectory=/etc/fulcio-config
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable fulcio.service
$ sudo systemctl start fulcio.service
$ sudo systemctl status fulcio.service
$ fulcio serve --config-path=/etc/fulcio-config/config.json --ca=fulcioca --hsm-caroot-id=1 --ct-log-url=http://sigstore-ctl:6105/sigstore --host=0.0.0.0 --port=5000
📝 Don't worry that the Certificate Transparency Log is not up yet. We will set this up next.
$ fulcio serve --config-path=/etc/fulcio-config/config.json --ca googleca --gcp_private_ca_parent=${resource_name} --ct-log-url=http://sigstore-ctl:6105/sigstore --host=0.0.0.0 --port=5000
📝 Your resource name is a long POSIX type path string, e.g.
projects/sigstore-the-hard-way-proj/locations/europe-west1/caPools/sigstore-the-hard-way/certificateAuthorities/xxxx
For example
$ fulcio serve --config-path=/etc/fulcio-config/config.json --ca googleca --gcp_private_ca_parent=projects/sigstore-the-hard-way-proj/locations/europe-west1/caPools/sigstore-the-hard-way/certificateAuthorities/xxxx --ctl-log-url=http://sigstore-ctl:6105/sigstore
Next: Certificate Transparency