Test the PKCS#11 build against SoftHSM in CI and warn once where the build has no driver

This commit is contained in:
Viktor Liu
2026-10-02 12:44:17 +02:00
parent 07cf08230c
commit 0ed3eb6139
7 changed files with 100 additions and 5 deletions
+36
View File
@@ -207,6 +207,42 @@ jobs:
# regression test, and need no frontend bundle.
run: CGO_ENABLED=1 go test -timeout 5m ./client/ui/authsession/... ./client/ui/i18n/... ./client/ui/preferences/... ./client/ui/services/...
test_client_pkcs11:
name: "Client PKCS#11 / Unit"
# The deb and rpm packages ship the client built with the pkcs11 tag, which loads
# PKCS#11 modules through purego. The other jobs build without the tag, so this one
# compiles that driver and signs with a real SoftHSM token, the build release ships.
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: "go.mod"
cache: false
- name: Install SoftHSM
run: sudo apt update && sudo apt install -y -q softhsm2
- name: Initialise token
run: |
mkdir -p "$RUNNER_TEMP/softhsm/tokens"
printf 'directories.tokendir = %s\nobjectstore.backend = file\n' "$RUNNER_TEMP/softhsm/tokens" > "$RUNNER_TEMP/softhsm/softhsm2.conf"
echo "SOFTHSM2_CONF=$RUNNER_TEMP/softhsm/softhsm2.conf" >> "$GITHUB_ENV"
SOFTHSM2_CONF="$RUNNER_TEMP/softhsm/softhsm2.conf" softhsm2-util --init-token --free --label netbird --pin 1234 --so-pin 1234
- name: Vet
run: CGO_ENABLED=0 go vet -tags pkcs11 ./client/internal/pkcs11/... ./client/internal/certproof/...
- name: Test
env:
NB_TEST_PKCS11_URI: "pkcs11:token=netbird?module-path=/usr/lib/softhsm/libsofthsm2.so&pin-value=1234"
run: CGO_ENABLED=0 go test -tags pkcs11 -timeout 5m ./client/internal/pkcs11/... ./client/internal/certproof/...
test_client_on_docker:
name: "Client (Docker) / Unit"
needs: [build-cache]
+4 -2
View File
@@ -197,8 +197,10 @@ The module is loaded at runtime without cgo, through `purego`, which means the b
dynamically linked against libc. The store is therefore compiled in only with `-tags pkcs11`
on linux/amd64 and linux/arm64: the deb and rpm packages are built that way, since they
target glibc distributions, while the release tarballs and the Alpine-based container
images keep the fully static build. Without the tag, setting `NB_TPM_PIN` logs that
the build lacks the support.
images keep the fully static build. The arm and 386 packages carry the tag too but have
no driver, so they behave like the static build. A build without support logs one warning
when a token is configured and keeps reading the PEM directory. The "Client PKCS#11 /
Unit" CI job builds with the tag and signs with a SoftHSM token.
To exercise the path without hardware, initialise a SoftHSM token and run the end-to-end
test, which imports a key and certificate itself:
@@ -339,3 +339,26 @@ func TestNewPKCS11Store_PIN(t *testing.T) {
assert.ErrorIs(t, err, errPINNeedsToken, "%s: a PIN must not go to whichever token is listed first", name)
}
}
// TestPKCS11Store_WrongPINIsTriedOnce logs in to the real token with a wrong PIN: the
// token refuses it, and the next collection refuses to send the same PIN again rather
// than spending another attempt of the token's lockout counter.
func TestPKCS11Store_WrongPINIsTriedOnce(t *testing.T) {
_, uri := pkcs11TestStore(t, "")
wrongPIN := "wrong-pin-" + t.Name()
store, err := NewPKCS11Store(PKCS11Config{URI: uri, PIN: wrongPIN}, "")
require.NoError(t, err)
_, err = store.Candidates(context.Background())
require.Error(t, err)
assert.True(t, pkcs11.PINRejected(err), "the token itself rejects the PIN: %v", err)
_, err = store.Candidates(context.Background())
assert.ErrorIs(t, err, errPINRejectedBefore, "the rejected PIN is not sent to the token again")
good, err := NewPKCS11Store(PKCS11Config{URI: uri}, "")
require.NoError(t, err)
_, err = good.Candidates(context.Background())
assert.NoError(t, err, "the correct PIN for the same token is unaffected")
}
+15 -1
View File
@@ -2,7 +2,15 @@
package certproof
import log "github.com/sirupsen/logrus"
import (
"sync"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/pkcs11"
)
var unsupportedTokenOnce sync.Once
// DefaultStore is the PEM directory named by NB_CERT_STORE_DIR, or /etc/netbird/certs.
func DefaultStore() Store {
@@ -16,6 +24,12 @@ func storeWithToken(cfg Config) Store {
if cfg.PKCS11.URI == "" && cfg.PKCS11.PIN == "" {
return files
}
if !pkcs11.Supported() {
unsupportedTokenOnce.Do(func() {
log.Warnf("ignoring the configured PKCS#11 token: %v", pkcs11.ErrUnsupported)
})
return files
}
token, err := NewPKCS11Store(cfg.PKCS11, cfg.dir())
if err != nil {
log.Warnf("ignoring PKCS#11 URI: %v", err)
+12 -2
View File
@@ -7,6 +7,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/client/internal/pkcs11"
)
func TestStoreWithToken(t *testing.T) {
@@ -23,10 +25,18 @@ func TestStoreWithToken(t *testing.T) {
assert.IsType(t, &FileStore{}, storeWithToken(Config{PKCS11: PKCS11Config{URI: "not-a-pkcs11-uri"}}), "an invalid URI must not hide the PEM directory")
assert.IsType(t, &FileStore{}, storeWithToken(Config{PKCS11: PKCS11Config{PIN: "1234"}}), "a PIN naming no token is refused and leaves the PEM directory")
for name, cfg := range map[string]PKCS11Config{
configured := map[string]PKCS11Config{
"env pin with token uri": {URI: "pkcs11:token=netbird", PIN: "1234"},
"uri alone": {URI: "pkcs11:token=netbird?pin-value=1234"},
} {
}
if !pkcs11.Supported() {
for name, cfg := range configured {
assert.IsType(t, &FileStore{}, storeWithToken(Config{Dir: dir, PKCS11: cfg}),
"%s: a build without PKCS#11 support reads the PEM directory alone", name)
}
return
}
for name, cfg := range configured {
store, ok := storeWithToken(Config{Dir: dir, PKCS11: cfg}).(Stores)
require.True(t, ok, "%s joins the token to the PEM directory", name)
require.Len(t, store, 2, name)
+5
View File
@@ -121,6 +121,11 @@ type module struct {
cSign func(session ulong, data *byte, dataLen ulong, signature *byte, signatureLen *ulong) ulong
}
// Supported reports whether this build can load PKCS#11 modules.
func Supported() bool {
return true
}
func load(path string) (driver, error) {
lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL)
if err != nil {
+5
View File
@@ -2,6 +2,11 @@
package pkcs11
// Supported reports whether this build can load PKCS#11 modules.
func Supported() bool {
return false
}
func load(string) (driver, error) {
return nil, ErrUnsupported
}