Skip to content
Merged
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: |
Comment thread
carlosmonastyrski marked this conversation as resolved.
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
58 changes: 57 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,42 @@ builds:
goarm:
- "7"

# PKCS#11-enabled HSM companion. Loads the vendor's PKCS#11 driver via dlopen
# at runtime, so it MUST be dynamically linked (no -extldflags "-static") and
# built with a glibc toolchain. Shipped as a separate artifact and fetched by
# the launcher in packages/gateway-v2/pkcs11_launcher.go.
- 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 +187,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 +207,15 @@ archives:
- manpages/*
- completions/*

- id: pkcs11
builds:
- linux-amd64-pkcs11
- linux-arm64-pkcs11
name_template: "infisical-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
42 changes: 35 additions & 7 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 @@ -209,6 +210,23 @@ var gatewayStartCmd = &cobra.Command{
Example: "infisical gateway start my-gateway --token=<token>",
DisableFlagsInUseLine: true,
Args: cobra.MaximumNArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
pkcs11ModulePath, _ := util.GetCmdFlagOrEnv(cmd, "pkcs11-module", []string{gatewayv2.INFISICAL_PKCS11_MODULE_ENV_NAME})
if pkcs11ModulePath == "" {
return nil
}
if !filepath.IsAbs(pkcs11ModulePath) {
return fmt.Errorf("--pkcs11-module must be an absolute path (got %q)", pkcs11ModulePath)
}
info, err := os.Stat(pkcs11ModulePath)
if err != nil {
return fmt.Errorf("PKCS#11 driver not found at %q: %w", pkcs11ModulePath, err)
}
if info.IsDir() {
return fmt.Errorf("--pkcs11-module path is a directory, expected a driver file: %q", pkcs11ModulePath)
}
return gatewayv2.MaybeExecPkcs11Launcher(pkcs11ModulePath, os.Args)
},
Run: func(cmd *cobra.Command, args []string) {
enrollMethod, _ := cmd.Flags().GetString("enroll-method")
// Fall back to env var for systemd-managed runs where flags aren't set.
Expand Down Expand Up @@ -401,11 +419,14 @@ var gatewayStartCmd = &cobra.Command{
}
}

pkcs11ModulePath, _ := util.GetCmdFlagOrEnv(cmd, "pkcs11-module", []string{gatewayv2.INFISICAL_PKCS11_MODULE_ENV_NAME})

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 @@ -591,6 +612,11 @@ var gatewaySystemdInstallCmd = &cobra.Command{

enrollMethod, _ := cmd.Flags().GetString("enroll-method")

pkcs11ModulePath, _ := cmd.Flags().GetString("pkcs11-module")
if pkcs11ModulePath != "" && !filepath.IsAbs(pkcs11ModulePath) {
util.HandleError(fmt.Errorf("--pkcs11-module must be an absolute path (got %q)", pkcs11ModulePath))
}

var installedServiceName string

if enrollMethod == gatewayv2.EnrollMethodToken {
Expand All @@ -616,7 +642,7 @@ var gatewaySystemdInstallCmd = &cobra.Command{
}

// Install systemd service using the long-lived access token
svcName, installErr := gatewayv2.InstallEnrolledGatewaySystemdService(enrollResp.AccessToken, domain, gatewayName, relayName, serviceLogFile)
svcName, installErr := gatewayv2.InstallEnrolledGatewaySystemdService(enrollResp.AccessToken, domain, gatewayName, relayName, serviceLogFile, pkcs11ModulePath)
if installErr != nil {
util.HandleError(installErr, "Unable to install systemd service")
}
Expand All @@ -632,7 +658,7 @@ var gatewaySystemdInstallCmd = &cobra.Command{

relayName, _ := util.GetRelayName(cmd, false, "")

svcName, installErr := gatewayv2.InstallAwsAuthGatewaySystemdService(gatewayID, domain, gatewayName, relayName, serviceLogFile)
svcName, installErr := gatewayv2.InstallAwsAuthGatewaySystemdService(gatewayID, domain, gatewayName, relayName, serviceLogFile, pkcs11ModulePath)
if installErr != nil {
util.HandleError(installErr, "Unable to install systemd service")
}
Expand All @@ -653,7 +679,7 @@ var gatewaySystemdInstallCmd = &cobra.Command{
util.HandleError(relayErr, "unable to get relay name")
}

svcName, installErr := gatewayv2.InstallGatewaySystemdService(token.Token, domain, gatewayName, relayName, serviceLogFile)
svcName, installErr := gatewayv2.InstallGatewaySystemdService(token.Token, domain, gatewayName, relayName, serviceLogFile, pkcs11ModulePath)
if installErr != nil {
util.HandleError(installErr, "Unable to install systemd service")
}
Expand Down Expand Up @@ -759,6 +785,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 and serves HSM operations through it.")

// Legacy install command flags (v1)
gatewayInstallCmd.Flags().String("token", "", "Connect with Infisical using machine identity access token")
Expand All @@ -774,6 +801,7 @@ func init() {
gatewaySystemdInstallCmd.Flags().String("relay", "", "The name of the relay (deprecated, use --target-relay-name)") // Deprecated, use --target-relay-name instead
gatewaySystemdInstallCmd.Flags().String("target-relay-name", "", "The name of the relay")
gatewaySystemdInstallCmd.Flags().String("log-file", "", "The file to write the service logs to. Example: /var/log/infisical/gateway.log. If not provided, logs will not be written to a file.")
gatewaySystemdInstallCmd.Flags().String("pkcs11-module", "", "absolute path to a PKCS#11 driver (e.g. /opt/fortanix/pkcs11/fortanix_pkcs11.so). When set, the systemd service starts the gateway with the PKCS#11 driver loaded for HSM operations.")

// Gateway relay command flags
gatewayRelayCmd.Flags().String("config", "", "Relay config yaml file path")
Expand Down
1 change: 1 addition & 0 deletions packages/gateway-v2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
KUBERNETES_SERVICE_ACCOUNT_CA_CERT_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"
INFISICAL_PAM_SESSION_RECORDING_PATH_ENV_NAME = "INFISICAL_PAM_SESSION_RECORDING_PATH"
INFISICAL_PKCS11_MODULE_ENV_NAME = "INFISICAL_PKCS11_MODULE"

RELAY_NAME_ENV_NAME = "INFISICAL_RELAY_NAME"
RELAY_HOST_ENV_NAME = "INFISICAL_RELAY_HOST"
Expand Down
67 changes: 59 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,12 @@ func NewGateway(config *GatewayConfig) (*Gateway, error) {

pamCredentialsManager := session.NewCredentialsManager(httpClient)

pkcs11Module, err := setupPkcs11ModuleForConfig(config.Pkcs11ModulePath)
if err != nil {
cancel()
return nil, fmt.Errorf("failed to load PKCS#11 module: %w", err)
}

return &Gateway{
httpClient: httpClient,
config: config,
Expand All @@ -169,6 +178,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 +376,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 +517,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 +729,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 +943,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 +1005,10 @@ func (g *Gateway) parseForwardConfigFromALPN(tlsConn *tls.Conn, reader *bufio.Re
config.Mode = ForwardModeHealth
return config, nil

case "infisical-pkcs11":
Comment thread
carlosmonastyrski marked this conversation as resolved.
config.Mode = ForwardModePkcs11
return config, nil

default:
return nil, fmt.Errorf("unsupported ALPN protocol: %s", negotiatedProtocol)
}
Expand Down Expand Up @@ -1137,3 +1171,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
}
Loading
Loading