Skip to content

Commit 2a3fb2c

Browse files
committed
supported resource types for gateway
1 parent 8ab3fbc commit 2a3fb2c

File tree

6 files changed

+143
-2
lines changed

6 files changed

+143
-2
lines changed

packages/gateway-v2/gateway.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const (
3434
ForwardModeTCP ForwardMode = "TCP"
3535
ForwardModePAM ForwardMode = "PAM"
3636
ForwardModePAMCancellation ForwardMode = "PAM_CANCELLATION"
37+
ForwardModePAMCapabilities ForwardMode = "PAM_CAPABILITIES"
3738
ForwardModePing ForwardMode = "PING"
3839
)
3940

@@ -443,7 +444,7 @@ func (g *Gateway) setupTLSConfig() error {
443444
ClientCAs: clientCAPool,
444445
ClientAuth: tls.RequireAndVerifyClientCert,
445446
MinVersion: tls.VersionTLS12,
446-
NextProtos: []string{"infisical-http-proxy", "infisical-tcp-proxy", "infisical-ping", "infisical-pam-proxy", "infisical-pam-session-cancellation"},
447+
NextProtos: []string{"infisical-http-proxy", "infisical-tcp-proxy", "infisical-ping", "infisical-pam-proxy", "infisical-pam-session-cancellation", "infisical-pam-capabilities"},
447448
}
448449

449450
return nil
@@ -620,6 +621,14 @@ func (g *Gateway) handleIncomingChannel(newChannel ssh.NewChannel) {
620621
log.Error().Err(err).Msg("PAM cancellation proxy handler ended with error")
621622
}
622623
return
624+
} else if forwardConfig.Mode == ForwardModePAMCapabilities {
625+
log.Info().Msg("Starting PAM capabilities handler")
626+
if err := pam.HandlePAMCapabilities(g.ctx, tlsConn); err != nil {
627+
log.Error().Err(err).Msg("PAM capabilities handler ended with error")
628+
} else {
629+
log.Info().Msg("PAM capabilities handler completed")
630+
}
631+
return
623632
} else if forwardConfig.Mode == ForwardModePing {
624633
log.Info().Msg("Starting ping handler")
625634
if err := handlePing(g.ctx, tlsConn, reader); err != nil {
@@ -666,6 +675,10 @@ func (g *Gateway) parseForwardConfigFromALPN(tlsConn *tls.Conn, reader *bufio.Re
666675
config.Mode = ForwardModePAMCancellation
667676
return config, nil
668677

678+
case "infisical-pam-capabilities":
679+
config.Mode = ForwardModePAMCapabilities
680+
return config, nil
681+
669682
case "infisical-ping":
670683
config.Mode = ForwardModePing
671684
return config, nil

packages/pam/local/base-proxy.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ import (
44
"context"
55
"crypto/tls"
66
"crypto/x509"
7+
"encoding/json"
78
"fmt"
9+
"io"
810
"net"
11+
"slices"
912
"strconv"
1013
"strings"
1114
"sync"
1215
"time"
1316

1417
"github.com/Infisical/infisical-merge/packages/api"
18+
"github.com/Infisical/infisical-merge/packages/pam"
1519
"github.com/Infisical/infisical-merge/packages/util"
1620
"github.com/go-resty/resty/v2"
1721
"github.com/rs/zerolog/log"
@@ -29,6 +33,7 @@ type BaseProxyServer struct {
2933
gatewayServerCertChain string
3034
sessionExpiry time.Time
3135
sessionId string
36+
resourceType string
3237
ctx context.Context
3338
cancel context.CancelFunc
3439
activeConnections sync.WaitGroup
@@ -88,6 +93,64 @@ func (b *BaseProxyServer) CreateRelayConnection() (net.Conn, error) {
8893
return conn, nil
8994
}
9095

96+
// FetchGatewayCapabilities fetches the supported resource types from the gateway
97+
func (b *BaseProxyServer) FetchGatewayCapabilities() (*pam.PAMCapabilitiesResponse, error) {
98+
relayConn, err := b.CreateRelayConnection()
99+
if err != nil {
100+
return nil, fmt.Errorf("failed to connect to relay: %w", err)
101+
}
102+
defer relayConn.Close()
103+
104+
gatewayConn, err := b.CreateGatewayConnection(relayConn, ALPNInfisicalPAMCapabilities)
105+
if err != nil {
106+
return nil, fmt.Errorf("failed to connect to gateway: %w", err)
107+
}
108+
defer gatewayConn.Close()
109+
110+
// Read length prefix (4 bytes)
111+
lengthBytes := make([]byte, 4)
112+
if _, err := io.ReadFull(gatewayConn, lengthBytes); err != nil {
113+
return nil, fmt.Errorf("failed to read length prefix: %w", err)
114+
}
115+
116+
length := uint32(lengthBytes[0])<<24 | uint32(lengthBytes[1])<<16 | uint32(lengthBytes[2])<<8 | uint32(lengthBytes[3])
117+
118+
// Read JSON data
119+
data := make([]byte, length)
120+
if _, err := io.ReadFull(gatewayConn, data); err != nil {
121+
return nil, fmt.Errorf("failed to read capabilities response: %w", err)
122+
}
123+
124+
var response pam.PAMCapabilitiesResponse
125+
if err := json.Unmarshal(data, &response); err != nil {
126+
return nil, fmt.Errorf("failed to parse capabilities response: %w", err)
127+
}
128+
129+
log.Debug().Strs("supportedTypes", response.SupportedResourceTypes).Msg("Received gateway capabilities")
130+
return &response, nil
131+
}
132+
133+
// ValidateResourceTypeSupported checks if the resource type is supported by the gateway
134+
func (b *BaseProxyServer) ValidateResourceTypeSupported() error {
135+
capabilities, err := b.FetchGatewayCapabilities()
136+
if err != nil {
137+
return fmt.Errorf("failed to fetch gateway capabilities: %w", err)
138+
}
139+
140+
if slices.Contains(capabilities.SupportedResourceTypes, b.resourceType) {
141+
return nil
142+
}
143+
144+
return fmt.Errorf(`Your Gateway does not support '%s' PAM accounts.
145+
146+
To fix this:
147+
1. Update your Gateway deployment
148+
2. Restart the Gateway service
149+
3. Retry your access command
150+
151+
Gateway upgrade guide: https://infisical.com/docs/documentation/platform/gateways/gateway-deployment`, b.resourceType)
152+
}
153+
91154
// CreateGatewayConnection establishes a mTLS connection to the gateway over the relay
92155
func (b *BaseProxyServer) CreateGatewayConnection(relayConn net.Conn, alpn ALPN) (net.Conn, error) {
93156
// Load gateway certificates

packages/pam/local/database-proxy.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type ALPN string
3232
const (
3333
ALPNInfisicalPAMProxy ALPN = "infisical-pam-proxy"
3434
ALPNInfisicalPAMCancellation ALPN = "infisical-pam-session-cancellation"
35+
ALPNInfisicalPAMCapabilities ALPN = "infisical-pam-capabilities"
3536
)
3637

3738
func askForApprovalRequestTrigger() (bool, error) {
@@ -130,12 +131,18 @@ func StartDatabaseLocalProxy(accessToken string, accountPath string, projectID s
130131
gatewayServerCertChain: pamResponse.GatewayServerCertificateChain,
131132
sessionExpiry: time.Now().Add(duration),
132133
sessionId: pamResponse.SessionId,
134+
resourceType: pamResponse.ResourceType,
133135
ctx: ctx,
134136
cancel: cancel,
135137
shutdownCh: make(chan struct{}),
136138
},
137139
}
138140

141+
if err := proxy.ValidateResourceTypeSupported(); err != nil {
142+
util.HandleError(err, "Gateway version outdated")
143+
return
144+
}
145+
139146
err = proxy.Start(port)
140147
if err != nil {
141148
util.HandleError(err, "Failed to start proxy server")

packages/pam/local/kubernetes-proxy.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,18 @@ func StartKubernetesLocalProxy(accessToken string, accountPath string, projectId
7777
gatewayServerCertChain: pamResponse.GatewayServerCertificateChain,
7878
sessionExpiry: time.Now().Add(duration),
7979
sessionId: pamResponse.SessionId,
80+
resourceType: pamResponse.ResourceType,
8081
ctx: ctx,
8182
cancel: cancel,
8283
shutdownCh: make(chan struct{}),
8384
},
8485
}
8586

87+
if err := proxy.ValidateResourceTypeSupported(); err != nil {
88+
util.HandleError(err, "Gateway version outdated")
89+
return
90+
}
91+
8692
err = proxy.Start(port)
8793
if err != nil {
8894
util.HandleError(err, "Failed to start proxy server")

packages/pam/local/ssh-proxy.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,18 @@ func StartSSHLocalProxy(accessToken string, accountPath string, projectID string
7070
gatewayServerCertChain: pamResponse.GatewayServerCertificateChain,
7171
sessionExpiry: time.Now().Add(duration),
7272
sessionId: pamResponse.SessionId,
73+
resourceType: pamResponse.ResourceType,
7374
ctx: ctx,
7475
cancel: cancel,
7576
shutdownCh: make(chan struct{}),
7677
},
7778
}
7879

80+
if err := proxy.ValidateResourceTypeSupported(); err != nil {
81+
util.HandleError(err, "Gateway version outdated")
82+
return
83+
}
84+
7985
// Start the local TCP proxy on a random port
8086
err = proxy.Start(0) // 0 = random port
8187
if err != nil {

packages/pam/pam-proxy.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import (
44
"context"
55
"crypto/tls"
66
"crypto/x509"
7+
"encoding/json"
78
"fmt"
89
"net/url"
910
"time"
1011

1112
"github.com/Infisical/infisical-merge/packages/pam/handlers"
1213
"github.com/Infisical/infisical-merge/packages/pam/handlers/kubernetes"
1314
"github.com/Infisical/infisical-merge/packages/pam/handlers/mysql"
14-
"github.com/Infisical/infisical-merge/packages/pam/handlers/ssh"
1515
"github.com/Infisical/infisical-merge/packages/pam/session"
1616
"github.com/go-resty/resty/v2"
1717
"github.com/rs/zerolog/log"
@@ -25,6 +25,52 @@ type GatewayPAMConfig struct {
2525
SessionUploader *session.SessionUploader
2626
}
2727

28+
type PAMCapabilitiesResponse struct {
29+
SupportedResourceTypes []string `json:"supportedResourceTypes"`
30+
}
31+
32+
func GetSupportedResourceTypes() []string {
33+
return []string{
34+
session.ResourceTypePostgres,
35+
session.ResourceTypeMysql,
36+
session.ResourceTypeSSH,
37+
session.ResourceTypeKubernetes,
38+
}
39+
}
40+
41+
// HandlePAMCapabilities handles the capabilities request from the client
42+
func HandlePAMCapabilities(ctx context.Context, conn *tls.Conn) error {
43+
response := PAMCapabilitiesResponse{
44+
SupportedResourceTypes: GetSupportedResourceTypes(),
45+
}
46+
47+
data, err := json.Marshal(response)
48+
if err != nil {
49+
log.Error().Err(err).Msg("Failed to marshal capabilities response")
50+
return fmt.Errorf("failed to marshal capabilities response: %w", err)
51+
}
52+
53+
// Write length prefix (4 bytes) followed by JSON data
54+
length := uint32(len(data))
55+
lengthBytes := []byte{
56+
byte(length >> 24),
57+
byte(length >> 16),
58+
byte(length >> 8),
59+
byte(length),
60+
}
61+
62+
if _, err := conn.Write(lengthBytes); err != nil {
63+
return fmt.Errorf("failed to write length prefix: %w", err)
64+
}
65+
66+
if _, err := conn.Write(data); err != nil {
67+
return fmt.Errorf("failed to write capabilities response: %w", err)
68+
}
69+
70+
log.Debug().Strs("supportedTypes", response.SupportedResourceTypes).Msg("Sent PAM capabilities to client")
71+
return nil
72+
}
73+
2874
func HandlePAMCancellation(ctx context.Context, conn *tls.Conn, pamConfig *GatewayPAMConfig, httpClient *resty.Client) error {
2975
log.Info().Str("sessionId", pamConfig.SessionId).Msg("Received session termination message")
3076

0 commit comments

Comments
 (0)