Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aa8a751
Add RFC 6125-compliant SAN support for client certificate identity ex…
AntiD2ta Jan 13, 2026
6a2fa20
Support on-demand certificate reload on SIGHUP
AntiD2ta Jan 13, 2026
0ad6d17
Update CHANGELOG
AntiD2ta Jan 13, 2026
8597df1
Update version
AntiD2ta Jan 16, 2026
8aad0fd
Integrate go-certmanager v0.1.1
AntiD2ta Apr 14, 2026
24bd980
Merge branch 'master' into use-go-certmanager
AntiD2ta Apr 15, 2026
a29806d
Fix CI lint and DeepSource complexity issues
AntiD2ta Apr 16, 2026
750ccf1
docs(permissions): correct SAN identity extraction to DNS + CN only
AntiD2ta May 28, 2026
26aa6b2
changelog: clarify SIGHUP reloads only the server certificate
AntiD2ta May 28, 2026
1ca5151
test(grpcapi): wait for server readiness via TCP poll instead of fixe…
AntiD2ta May 28, 2026
8a5c3f2
release: bump version to 1.3.0-rc.1
AntiD2ta May 28, 2026
31a8bf1
lint: address attgo findings on files touched in this branch
AntiD2ta May 28, 2026
e705826
test(checker): drop unnecessary closure around context.Background
AntiD2ta May 28, 2026
d3f7295
test(daemon): skip DeepSource cyclomatic-complexity check on New
AntiD2ta May 28, 2026
7c00611
refactor(checker): extract Check helpers to reduce cyclomatic complexity
AntiD2ta May 28, 2026
d8ada6c
test(grpcapi): refactor createTestServer to address DeepSource findings
AntiD2ta May 28, 2026
c73bb8b
cert: align empty ca-cert behaviour and bound client cert loads
AntiD2ta May 28, 2026
02fea4d
identity: warn during SAN-DNS migration and harden interceptor auth c…
AntiD2ta May 28, 2026
f6f73f2
test(grpcapi): tighten port handling and isolate mock fixtures
AntiD2ta May 28, 2026
4a9bc76
test(grpcapi): exercise the server interceptor and the denial path
AntiD2ta May 28, 2026
bcd2d92
docs(configuration): document SIGHUP refresh limits and ca-cert fallback
AntiD2ta May 28, 2026
81a14e6
checker: shorten the SAN-DNS CN-fallback warning message
AntiD2ta Jun 4, 2026
9e59341
docs(configuration): note empty ca-cert falls back to the system trus…
AntiD2ta Jun 4, 2026
0efedfc
test(grpcapi): make context.Context the first parameter of createServers
AntiD2ta Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Development
- fix account manager completed metric OnGenerate error
- import latest go-eth2-wallet-store-s3 to enable force-path-style on S3
- import latest go-eth2-wallet-store-s3 to enable force-path-style on S3
- integrate go-certmanager for TLS certificate management
- use DNS SAN for client certificate identity extraction, with CN fallback
- support on-demand reload of the server certificate on SIGHUP (the client certificate used for outgoing DKG connections is not reloaded)
- use separate client certificate manager for outgoing DKG connections

# Version 1.2.1
- run slashing protection database garbage collection periodically
Expand Down
6 changes: 3 additions & 3 deletions core/stores.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2020 - 2024 Attestant Limited.
// Copyright © 2020 - 2026 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down Expand Up @@ -105,7 +105,7 @@ func initFilesystemStore(ctx context.Context,
log.Trace().Str("name", storeDefinition.Name).Str("location", storeDefinition.Location).Msg("Adding filesystem store")

opts := make([]filesystem.Option, 0)
if len(storeDefinition.Passphrase) > 0 {
if storeDefinition.Passphrase != "" {
passphrase, err := majordomo.Fetch(ctx, storeDefinition.Passphrase)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain passphrase")
Expand All @@ -130,7 +130,7 @@ func initS3Store(ctx context.Context,
log.Trace().Str("name", storeDefinition.Name).Str("location", storeDefinition.Location).Msg("Adding S3 store")

opts := make([]s3.Option, 0)
if len(storeDefinition.Passphrase) > 0 {
if storeDefinition.Passphrase != "" {
passphrase, err := majordomo.Fetch(ctx, storeDefinition.Passphrase)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain passphrase")
Expand Down
31 changes: 29 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,35 @@ certificates:
server-cert: file:///home/me/dirk/security/certificates/myserver.example.com.crt
# server-key is the majordomo URL to the server's key.
server-key: file:///home/me/dirk/security/certificates/myserver.example.com.key
# ca-cert is the certificate of the CA that issued the client certificates. If not present Dirk will use
# the standard CA certificates supplied with the server.
# ca-cert is the certificate of the CA that issued the client certificates. If not present, Dirk
# falls back to the host's system trust store, which means any client certificate that chains to a
# publicly-trusted CA will pass TLS verification. Client authentication then rests solely on
# permissions.yaml (which matches on the certificate identity); set ca-cert to your own issuing CA
# to restrict which certificates can establish a connection at all.
# Note: Client certificates should include the client identity in Subject Alternative Names (SAN).
# Dirk supports DNS names in SAN fields.
# Legacy certificates using only Common Name (CN) are still supported for backward compatibility.
ca-cert: file:///home/me/dirk/security/certificates/ca.crt
# load-timeout defines the maximum time allowed for a certificate reload operation to complete.
# If the operation exceeds this duration, it will be cancelled. If not specified or set to 0,
# Certificates are loaded at Dirk start up with no timeout.
# They can also be reloaded on demand by sending a SIGHUP signal to Dirk's process.
# Note that only one reload operation can run at a time; concurrent reload attempts will return
# an error while a reload is in progress.
load-timeout: '10m'

# IMPORTANT: SIGHUP reloads only the server certificate and key (server-cert / server-key).
# The following TLS material is snapshotted at Dirk start and CANNOT be refreshed without a
# full process restart:
#
# - ca-cert (used as the trusted client CA pool for incoming connections)
# - the DKG client certificate and key (used for outbound peer connections; today these
# reuse server-cert / server-key, but the client certificate manager loads them once at
# start and holds them for the life of the process)
#
# Operational consequence: rotating ca-cert (adding a new client CA, revoking a compromised
# one) or rotating the peer client certificate requires a full Dirk restart. SIGHUP alone
# will quietly leave the cluster on the previous trust anchors. Plan rotations accordingly.
# storage-path is the path where information created by the slashing protection system is stored. If not
# supplied it will default to using the 'storage' directory in the user's home directory.
storage-path: /home/me/dirk/protection
Expand Down Expand Up @@ -121,6 +147,7 @@ Modules levels are used for each module, overriding the global log level. The a

- **accountmanager** operations on accounts such as locking and unlocking existing accounts, and generating new accounts
- **api** operations from the external API
- **certmanager** manages TLS certificates for server and client connections
- **checker** checks client access to operations
- **fetcher** fetches wallets and accounts from Ethereum 2 stores
- **lister** lists accounts that match a given path specification
Expand Down
19 changes: 16 additions & 3 deletions docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ Dirk has a permissions system that allows fine-grained control of access to Dirk
Dirk permissions have three components: the client, the account, and the operation.

## Clients
Client names are embedded in the certificate that is used to connect to Dirk. These certificates must be issued by either the local certificate authority known to Dirk, or one of the trusted root certificate authorities.
Client identities are extracted from the certificate that is used to connect to Dirk. These certificates must be issued by either the local certificate authority known to Dirk, or one of the trusted root certificate authorities.

Client names should be fully qualified (_i.e._ server.example.com rather than just server) to avoid potential confusion with multiple clients of the same name in different domains.
### Client Identity Extraction
Dirk extracts the client identity from certificates following RFC 6125 compliance by prioritizing Subject Alternative Name (SAN) fields over the deprecated Common Name (CN). The identity extraction follows this priority order:

1. **DNS names from SAN** - The first valid DNS name in the SAN extension is used (e.g., `validator-01.example.com`)
2. **Common Name (CN)** - Fallback for backward compatibility with legacy certificates that do not include a DNS SAN

This approach ensures compatibility with:
- **Modern certificates** that follow RFC 6125 and carry the identity in a DNS SAN entry
- **Legacy certificates** that rely on the CN field for identity

**Note:** Other SAN types (IP addresses, email addresses, URIs such as SPIFFE IDs) are not used for identity extraction. Certificates must expose a DNS name in the SAN extension, or fall back to CN, to be usable for permission matching.

### Client Identity Best Practices
For new certificates, client identities should be issued as a fully qualified DNS SAN (_e.g._ `server.example.com` rather than just `server`) to avoid potential confusion with multiple clients of the same name in different domains. Unqualified names are accepted (including via the CN fallback) for backward compatibility with existing deployments, but new permission entries should match the fully qualified DNS SAN of the issuing client.

## Accounts
Accounts are standard `ethdo` account specifiers of the form `wallet/account`. It is possible for either or both of `wallet` and `account` to be regular expressions. Some examples of account specifiers are:
Expand Down Expand Up @@ -86,4 +99,4 @@ Explicit denial is configured by prepending the ~ symbol to the operation, for e
[~Voluntary exit, All]
```

is read by Dirk as "do not allow voluntary exits, allow all other operations". Explicit denials are useful when you want your permissions to be of the form "allow all operations _except_..."
is read by Dirk as "do not allow voluntary exits, allow all other operations". Explicit denials are useful when you want your permissions to be of the form "allow all operations _except_..."
46 changes: 23 additions & 23 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module github.com/attestantio/dirk

go 1.25

toolchain go1.25.3
go 1.25.5

require (
github.com/attestantio/go-certmanager v0.1.1
github.com/attestantio/go-eth2-client v0.21.11
github.com/aws/aws-sdk-go v1.55.5
github.com/dgraph-io/badger/v2 v2.2007.4
Expand All @@ -17,10 +16,10 @@ require (
github.com/opentracing/opentracing-go v1.2.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.20.4
github.com/rs/zerolog v1.33.0
github.com/rs/zerolog v1.35.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.11.1
github.com/wealdtech/eth2-signer-api v1.7.2
github.com/wealdtech/go-bytesutil v1.2.1
github.com/wealdtech/go-eth2-types/v2 v2.8.2
Expand All @@ -34,21 +33,21 @@ require (
github.com/wealdtech/go-eth2-wallet-store-s3 v1.12.2
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.2
github.com/wealdtech/go-eth2-wallet-types/v2 v2.12.0
github.com/wealdtech/go-majordomo v1.1.1
github.com/wealdtech/go-majordomo v1.1.2
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0
go.opentelemetry.io/otel v1.30.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0
go.opentelemetry.io/otel/sdk v1.30.0
go.opentelemetry.io/otel/trace v1.30.0
google.golang.org/grpc v1.66.2
google.golang.org/protobuf v1.34.2
go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
)

require (
cloud.google.com/go/auth v0.9.4 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.1 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.2.1 // indirect
cloud.google.com/go/secretmanager v1.14.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
Expand All @@ -63,7 +62,7 @@ require (
github.com/fatih/color v1.17.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-yaml v1.9.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
Expand All @@ -78,7 +77,7 @@ require (
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
Expand All @@ -100,23 +99,24 @@ require (
github.com/wealdtech/go-eth2-util v1.8.2 // indirect
github.com/wealdtech/go-indexer v1.1.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
go.opentelemetry.io/otel/metric v1.30.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/api v0.197.0 // indirect
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading
Loading