Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .github/workflows/release_build_infisical_cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ jobs:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
sudo apt update
sudo apt-get install -y libssl1.0-dev
- name: Install glibc cross-compilers for PKCS#11 (HSM) builds
run: |
set -euo pipefail
# PKCS#11 driver loading uses dlopen; the artifact must be dynamically
# linked against glibc. We use the system gcc for amd64 (native) and
# gcc-aarch64-linux-gnu for arm64.
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Install cross-compile toolchains for RDP tier
run: |
set -euo pipefail
Expand Down
57 changes: 56 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,41 @@ builds:
goarm:
- "7"

# PKCS#11-enabled HSM build. Loads the vendor's PKCS#11 driver via dlopen at
# runtime, so it MUST be dynamically linked (no -extldflags "-static") and
# must use a glibc toolchain. Ships as a separate `infisical-pkcs11` artifact.
- id: linux-amd64-pkcs11
binary: infisical-pkcs11
ldflags:
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
flags:
- -trimpath
- -tags=pkcs11
env:
- CGO_ENABLED=1
- CC=x86_64-linux-gnu-gcc
goos:
- linux
goarch:
- amd64

- id: linux-arm64-pkcs11
binary: infisical-pkcs11
ldflags:
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
flags:
- -trimpath
- -tags=pkcs11
env:
- CGO_ENABLED=1
- CC=aarch64-linux-gnu-gcc
goos:
- linux
goarch:
- arm64

# BSDs and windows/arm64 stay on CGO=0 stub; see build-rdp-bridge.yml.
- id: all-other-builds
env:
Expand Down Expand Up @@ -151,7 +186,18 @@ builds:
goarch: arm

archives:
- format_overrides:
- id: default
builds_info:
group: default
builds:
- linux-amd64-rdp
- linux-arm64-rdp
- linux-386-rdp
- linux-armv6-rdp
- linux-armv7-rdp
- windows-amd64-rdp
- all-other-builds
format_overrides:
- goos: windows
format: zip
files:
Expand All @@ -160,6 +206,15 @@ archives:
- manpages/*
- completions/*

- id: pkcs11
builds:
- linux-amd64-pkcs11
- linux-arm64-pkcs11
name_template: "{{ .ProjectName }}-pkcs11_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
files:
- README*
- LICENSE*

release:
mode: append

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/jackc/pgx/v5 v5.9.2
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/mattn/go-isatty v0.0.20
github.com/miekg/pkcs11 v1.1.1
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
github.com/muesli/mango-cobra v1.2.0
github.com/muesli/reflow v0.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
Expand Down
3 changes: 2 additions & 1 deletion packages/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -812,10 +812,11 @@ func CallGatewayHeartBeatV1(httpClient *resty.Client) error {
return nil
}

func CallGatewayHeartBeatV2(httpClient *resty.Client) error {
func CallGatewayHeartBeatV2(httpClient *resty.Client, request GatewayHeartbeatRequest) error {
response, err := httpClient.
R().
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v2/gateways/heartbeat", config.INFISICAL_URL))

if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions packages/api/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,10 @@ type RelayHeartbeatRequest struct {
Name string `json:"name"`
}

type GatewayHeartbeatRequest struct {
Capabilities map[string]any `json:"capabilities,omitempty"`
}

type RelayLoginRequest struct {
Method string `json:"method"`
Token string `json:"token,omitempty"`
Expand Down
25 changes: 21 additions & 4 deletions packages/cmd/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"sync/atomic"
"syscall"
Expand Down Expand Up @@ -401,11 +402,26 @@ var gatewayStartCmd = &cobra.Command{
}
}

pkcs11ModulePath, _ := cmd.Flags().GetString("pkcs11-module")
if pkcs11ModulePath != "" {
if !filepath.IsAbs(pkcs11ModulePath) {
util.HandleError(fmt.Errorf("--pkcs11-module must be an absolute path (got %q)", pkcs11ModulePath), "unable to load PKCS#11 driver")
}
info, statErr := os.Stat(pkcs11ModulePath)
if statErr != nil {
util.HandleError(fmt.Errorf("PKCS#11 driver not found at %q: %w", pkcs11ModulePath, statErr), "unable to load PKCS#11 driver")
}
if info.IsDir() {
util.HandleError(fmt.Errorf("--pkcs11-module path is a directory, expected a driver file: %q", pkcs11ModulePath), "unable to load PKCS#11 driver")
}
}

gatewayInstance, err := gatewayv2.NewGateway(&gatewayv2.GatewayConfig{
Name: gatewayName,
RelayName: relayName,
ReconnectDelay: 10 * time.Second,
UseV3Connect: runningWithStoredToken,
Name: gatewayName,
RelayName: relayName,
ReconnectDelay: 10 * time.Second,
UseV3Connect: runningWithStoredToken,
Pkcs11ModulePath: pkcs11ModulePath,
})

if err != nil {
Expand Down Expand Up @@ -759,6 +775,7 @@ func init() {
gatewayStartCmd.Flags().String("service-account-key-file-path", "", "service account key file path for GCP IAM auth")
gatewayStartCmd.Flags().String("jwt", "", "JWT for jwt-based auth methods [oidc-auth, jwt-auth]")
gatewayStartCmd.Flags().String("pam-session-recording-path", "", "directory path for PAM session recordings (defaults to /var/lib/infisical/session_recordings)")
gatewayStartCmd.Flags().String("pkcs11-module", "", "absolute path to a PKCS#11 driver (e.g. /opt/fortanix/pkcs11/fortanix_pkcs11.so). When set, the gateway loads the driver, advertises pkcs11 capability on heartbeat, and serves HSM operations.")

// Legacy install command flags (v1)
gatewayInstallCmd.Flags().String("token", "", "Connect with Infisical using machine identity access token")
Expand Down
72 changes: 64 additions & 8 deletions packages/gateway-v2/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
ForwardModePAMCapabilities ForwardMode = "PAM_CAPABILITIES"
ForwardModePing ForwardMode = "PING"
ForwardModeHealth ForwardMode = "HEALTH"
ForwardModePkcs11 ForwardMode = "PKCS11"
)

type ActorType string
Expand Down Expand Up @@ -82,12 +83,13 @@ type ActorDetails struct {
}

type GatewayConfig struct {
Name string
RelayName string
IdentityToken string
SSHPort int
ReconnectDelay time.Duration
UseV3Connect bool // Use V3 /connect endpoint instead of V2 /gateways for cert refresh
Name string
RelayName string
IdentityToken string
SSHPort int
ReconnectDelay time.Duration
UseV3Connect bool // Use V3 /connect endpoint instead of V2 /gateways for cert refresh
Pkcs11ModulePath string
}

type pamSessionEntry struct {
Expand Down Expand Up @@ -132,6 +134,7 @@ type Gateway struct {
// MongoDB proxy registry: one topology per session, shared across connections
mongoProxies map[string]*mongoProxyEntry
mongoProxiesMu sync.Mutex
pkcs11Module Pkcs11Module
}

// mongoProxyEntry holds a session-level MongoDB proxy with a ready signal.
Expand Down Expand Up @@ -160,6 +163,17 @@ func NewGateway(config *GatewayConfig) (*Gateway, error) {

pamCredentialsManager := session.NewCredentialsManager(httpClient)

var pkcs11Module Pkcs11Module
if config.Pkcs11ModulePath != "" {
mod, err := LoadPkcs11Module(config.Pkcs11ModulePath)
if err != nil {
cancel()
return nil, fmt.Errorf("failed to load PKCS#11 module: %w", err)
}
pkcs11Module = mod
log.Info().Str("path", config.Pkcs11ModulePath).Msg("PKCS#11 module loaded; Gateway will serve HSM requests")
}

return &Gateway{
httpClient: httpClient,
config: config,
Expand All @@ -169,6 +183,7 @@ func NewGateway(config *GatewayConfig) (*Gateway, error) {
pamSessionUploader: session.NewSessionUploader(httpClient, pamCredentialsManager),
pamSessions: make(map[string][]*pamSessionEntry),
mongoProxies: make(map[string]*mongoProxyEntry),
pkcs11Module: pkcs11Module,
}, nil
}

Expand Down Expand Up @@ -366,7 +381,12 @@ func (g *Gateway) reapIdleSessions() {

func (g *Gateway) registerHeartBeat(ctx context.Context, errCh chan error) {
sendHeartbeat := func() error {
if err := api.CallGatewayHeartBeatV2(g.httpClient); err != nil {
capabilities := map[string]any{}
if g.pkcs11Module != nil {
capabilities[CapabilityPkcs11] = true
}
req := api.GatewayHeartbeatRequest{Capabilities: capabilities}
Comment thread
carlosmonastyrski marked this conversation as resolved.
if err := api.CallGatewayHeartBeatV2(g.httpClient, req); err != nil {
log.Warn().Msgf("Heartbeat failed: %v", err)
select {
case errCh <- err:
Expand Down Expand Up @@ -502,6 +522,13 @@ func (g *Gateway) Stop() {
if g.pamCredentialsManager != nil {
g.pamCredentialsManager.Shutdown()
}

if g.pkcs11Module != nil {
if err := g.pkcs11Module.Finalize(); err != nil {
log.Warn().Err(err).Msg("PKCS#11 module Finalize returned an error")
}
g.pkcs11Module = nil
}
}

func (g *Gateway) startHeartbeatOnce(ctx context.Context, errCh chan error) {
Expand Down Expand Up @@ -707,7 +734,7 @@ func (g *Gateway) setupTLSConfig() error {
ClientCAs: clientCAPool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"infisical-http-proxy", "infisical-tcp-proxy", "infisical-health", "infisical-ping", "infisical-pam-proxy", "infisical-pam-rdp-browser", "infisical-pam-session-cancellation", "infisical-pam-capabilities"},
NextProtos: nextProtosForGateway(g.pkcs11Module != nil),
}

return nil
Expand Down Expand Up @@ -921,6 +948,14 @@ func (g *Gateway) handleIncomingChannel(newChannel ssh.NewChannel) {
log.Info().Msg("Health handler completed")
}
return
} else if forwardConfig.Mode == ForwardModePkcs11 {
log.Info().Msg("Starting PKCS#11 handler")
if err := servePkcs11OverTLS(g.ctx, tlsConn, reader, g.pkcs11Module); err != nil {
log.Error().Err(err).Msg("PKCS#11 handler ended with error")
} else {
log.Info().Msg("PKCS#11 handler completed")
}
return
}
}

Expand Down Expand Up @@ -975,6 +1010,10 @@ func (g *Gateway) parseForwardConfigFromALPN(tlsConn *tls.Conn, reader *bufio.Re
config.Mode = ForwardModeHealth
return config, nil

case "infisical-pkcs11":
config.Mode = ForwardModePkcs11
return config, nil

default:
return nil, fmt.Errorf("unsupported ALPN protocol: %s", negotiatedProtocol)
}
Expand Down Expand Up @@ -1137,3 +1176,20 @@ func (g *Gateway) renewCertificates() error {

return nil
}

func nextProtosForGateway(pkcs11Loaded bool) []string {
base := []string{
"infisical-http-proxy",
"infisical-tcp-proxy",
"infisical-health",
"infisical-ping",
"infisical-pam-proxy",
"infisical-pam-rdp-browser",
"infisical-pam-session-cancellation",
"infisical-pam-capabilities",
}
if pkcs11Loaded {
base = append(base, "infisical-pkcs11")
}
return base
}
53 changes: 53 additions & 0 deletions packages/gateway-v2/pkcs11.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package gatewayv2

type Pkcs11Module interface {
Test(slotLabel string, pin []byte) (SlotInfo, error)

GenerateKeyPair(slotLabel string, pin []byte, keyLabel string, keyAlgorithm string) ([]byte, error)

GetPublicKey(slotLabel string, pin []byte, keyLabel string) ([]byte, error)

Sign(slotLabel string, pin []byte, keyLabel string, mechanism string, data []byte, isDigest bool) ([]byte, error)

Finalize() error
}

type SlotInfo struct {
Manufacturer string `json:"manufacturer"`
Model string `json:"model"`
Firmware string `json:"firmware"`
}

type Pkcs11ErrorCode string

const (
Pkcs11ErrPinIncorrect Pkcs11ErrorCode = "pin_incorrect"
Pkcs11ErrPinLocked Pkcs11ErrorCode = "pin_locked"
Pkcs11ErrSlotNotFound Pkcs11ErrorCode = "slot_not_found"
Pkcs11ErrKeyNotFound Pkcs11ErrorCode = "key_not_found"
Pkcs11ErrMechanismInvalid Pkcs11ErrorCode = "mechanism_invalid"
Pkcs11ErrDriverUnavailable Pkcs11ErrorCode = "driver_unavailable"
Pkcs11ErrLoginFailed Pkcs11ErrorCode = "login_failed"
Pkcs11ErrNotSupported Pkcs11ErrorCode = "pkcs11_not_supported"
Pkcs11ErrBadRequest Pkcs11ErrorCode = "bad_request"
Pkcs11ErrInternal Pkcs11ErrorCode = "internal"
)

type Pkcs11Error struct {
Code Pkcs11ErrorCode
Message string
}

func (e *Pkcs11Error) Error() string {
return string(e.Code) + ": " + e.Message
}

// Supported keyAlgorithm values.
const (
KeyAlgorithmRSA2048 = "RSA_2048"
KeyAlgorithmRSA4096 = "RSA_4096"
KeyAlgorithmECCP256 = "ECC_P256"
KeyAlgorithmECCP384 = "ECC_P384"
)

const CapabilityPkcs11 = "pkcs11"
10 changes: 10 additions & 0 deletions packages/gateway-v2/pkcs11_disabled.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build !pkcs11

package gatewayv2

func LoadPkcs11Module(_ string) (Pkcs11Module, error) {
return nil, &Pkcs11Error{
Code: Pkcs11ErrNotSupported,
Message: "This Gateway build was compiled without PKCS#11 support. Use the infisical-pkcs11 release artifact, or build from source with `go build -tags pkcs11` (cgo + dynamic linking required).",
}
}
Loading
Loading