Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7444282
Add redis res proxy
fangpenlin Dec 23, 2025
e1c4eae
Remove gen code
fangpenlin Dec 23, 2025
30c2114
Handle cmd
fangpenlin Dec 23, 2025
b04c054
Add redis proxy
fangpenlin Dec 24, 2025
e364ee9
Handle auth
fangpenlin Dec 24, 2025
492cb78
Forward cmd
fangpenlin Dec 24, 2025
be773a5
Add todo
fangpenlin Dec 24, 2025
1494346
Use a different lib
fangpenlin Dec 24, 2025
2736369
Use our own conn
fangpenlin Dec 24, 2025
26ec61b
Read resp
fangpenlin Dec 24, 2025
1a2b310
Refine the code
fangpenlin Dec 24, 2025
b3c3cc9
Add todo
fangpenlin Dec 24, 2025
20732c2
Log entries
fangpenlin Dec 24, 2025
9cb6533
Fix log uploading for redis
fangpenlin Dec 24, 2025
30dc694
Clean code a bit
fangpenlin Dec 24, 2025
ea03f33
Clean up
fangpenlin Dec 24, 2025
1dbba3a
Handle TLS
fangpenlin Dec 24, 2025
e8c7efe
Try to read the msg in a goroutine
fangpenlin Dec 25, 2025
d79eaad
Implement resp3 push msg
fangpenlin Dec 25, 2025
f6716c3
Fix pubsub hanging issue
fangpenlin Dec 25, 2025
1e24f3c
Fix resp2 msg as well
fangpenlin Dec 25, 2025
3095037
Log push msg as well
fangpenlin Dec 25, 2025
5992dbb
Implement monitor mode
fangpenlin Dec 25, 2025
c1823f4
Use diff type of audit log for monitor
fangpenlin Dec 25, 2025
cafa9ac
Use our own fork for resp3
fangpenlin Dec 25, 2025
1b9fcb8
Fix review feedbacks
fangpenlin Dec 26, 2025
625f176
Address review feedbacks
fangpenlin Jan 6, 2026
c9ce0aa
Add project id for k8s as well
fangpenlin Jan 6, 2026
b4aa62d
Remove unused database for redis
fangpenlin Jan 6, 2026
8807c28
Use the right ver of resp3
fangpenlin Jan 6, 2026
a329071
Just let ide yell at me
fangpenlin Jan 6, 2026
a252b19
Make auth cmd optional for redis
fangpenlin Jan 6, 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
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ require (
github.com/quic-go/quic-go v0.54.1
github.com/rs/cors v1.11.0
github.com/rs/zerolog v1.26.1
github.com/smallnest/resp3 v0.0.0-20251228151914-4f2fa7427e69
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.10.0
Expand Down Expand Up @@ -73,14 +74,15 @@ require (
github.com/aws/smithy-go v1.20.2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
Expand Down Expand Up @@ -159,6 +159,8 @@ github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9Tzqv
github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -513,6 +515,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smallnest/resp3 v0.0.0-20251228151914-4f2fa7427e69 h1:AkDv2coi+ZsMlEp/6V21FWxdswSIEzqflgJ6snIQG+U=
github.com/smallnest/resp3 v0.0.0-20251228151914-4f2fa7427e69/go.mod h1:cmfXTZVXEA7xFOYcGnpKp2VeFf6FUHmxdKQHVNE6BXY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
Expand Down
78 changes: 77 additions & 1 deletion packages/cmd/pam.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ var pamKubernetesAccessAccountCmd = &cobra.Command{
Use: "access-account <account-path>",
Short: "Access Kubernetes PAM account",
Long: "Access Kubernetes via a PAM-managed Kubernetes account. This command automatically launches a proxy connected to your Kubernetes cluster through the Infisical Gateway.",
Example: "infisical pam kubernetes access-account prod/ssh/my-k8s-account --duration 2h",
Example: "infisical pam kubernetes access-account prod/ssh/my-k8s-account --duration 2h --project-id <project_uuid>",
DisableFlagsInUseLine: true,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -222,6 +222,76 @@ var pamKubernetesAccessAccountCmd = &cobra.Command{
},
}

var pamRedisCmd = &cobra.Command{
Use: "redis",
Short: "Redis-related PAM commands",
Long: "Redis-related PAM commands for Infisical",
DisableFlagsInUseLine: true,
Args: cobra.NoArgs,
}

var pamRedisAccessAccountCmd = &cobra.Command{
Use: "access-account <account-path>",
Short: "Access Redis PAM account",
Long: "Access Redis via a PAM-managed Redis account. This starts a local Redis proxy server that you can use to connect to Redis directly.",
Example: "infisical pam redis access-account prod/redis/my-redis-account --duration 4h --port 6379 --project-id <project_uuid>",
DisableFlagsInUseLine: true,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
util.RequireLogin()

accountPath := args[0]

projectID, err := cmd.Flags().GetString("project-id")
if err != nil {
util.HandleError(err, "Unable to parse project-id flag")
}

if projectID == "" {
workspaceFile, err := util.GetWorkSpaceFromFile()
if err != nil {
util.PrintErrorMessageAndExit("Please either run infisical init to connect to a project or pass in project id with --project-id flag")
}
projectID = workspaceFile.WorkspaceId
}

durationStr, err := cmd.Flags().GetString("duration")
if err != nil {
util.HandleError(err, "Unable to parse duration flag")
}

// Parse duration
_, err = time.ParseDuration(durationStr)
if err != nil {
util.HandleError(err, "Invalid duration format. Use formats like '1h', '30m', '2h30m'")
}

port, err := cmd.Flags().GetInt("port")
if err != nil {
util.HandleError(err, "Unable to parse port flag")
}

log.Debug().Msg("PAM Redis Access: Trying to fetch secrets using logged in details")

loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
isConnected := util.ValidateInfisicalAPIConnection()

if isConnected {
log.Debug().Msg("PAM Redis Access: Connected to Infisical instance, checking logged in creds")
}

if err != nil {
util.HandleError(err, "Unable to get logged in user details")
}

if isConnected && loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession()
}

pam.StartRedisLocalProxy(loggedInUserDetails.UserCredentials.JTWToken, accountPath, projectID, durationStr, port)
},
}

func init() {
pamDbCmd.AddCommand(pamDbAccessAccountCmd)
pamDbAccessAccountCmd.Flags().String("duration", "1h", "Duration for database access session (e.g., '1h', '30m', '2h30m')")
Expand All @@ -237,8 +307,14 @@ func init() {
pamKubernetesAccessAccountCmd.Flags().Int("port", 0, "Port for the local kubernetes proxy server (0 for auto-assign)")
pamKubernetesAccessAccountCmd.Flags().String("project-id", "", "Project ID of the account to access")

pamRedisCmd.AddCommand(pamRedisAccessAccountCmd)
pamRedisAccessAccountCmd.Flags().String("duration", "1h", "Duration for Redis access session (e.g., '1h', '30m', '2h30m')")
pamRedisAccessAccountCmd.Flags().Int("port", 0, "Port for the local Redis proxy server (0 for auto-assign)")
pamRedisAccessAccountCmd.Flags().String("project-id", "", "Project ID of the account to access")

pamCmd.AddCommand(pamDbCmd)
pamCmd.AddCommand(pamSshCmd)
pamCmd.AddCommand(pamKubernetesCmd)
pamCmd.AddCommand(pamRedisCmd)
rootCmd.AddCommand(pamCmd)
}
6 changes: 3 additions & 3 deletions packages/pam/handlers/mysql/relay_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ func (r *RelayHandler) checkConnLostError(err error) {
}
}

func (r *RelayHandler) writeLogEntry(entry session.SessionLogEntry) (*mysql.Result, error) {
func (r *RelayHandler) writeLogEntry(entry session.SessionLogEntry) error {
err := r.sessionLogger.LogEntry(entry)
if err != nil {
log.Error().Err(err).Msg("failed to write log entry to file")
return nil, err
return err
}
return nil, nil
return nil
}

func formatResult(result *mysql.Result) string {
Expand Down
48 changes: 48 additions & 0 deletions packages/pam/handlers/redis/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package redis

import (
"net"

"github.com/smallnest/resp3"
)

type RedisConn struct {
conn net.Conn
reader *resp3.Reader
writer *resp3.Writer
}

func NewRedisConn(conn net.Conn) *RedisConn {
return &RedisConn{
conn: conn,
reader: resp3.NewReader(conn),
writer: resp3.NewWriter(conn),
}
}

func (c *RedisConn) Close() error {
defer func() { _ = c.conn.Close() }()
if err := c.writer.Flush(); err != nil {
return err
}
return nil
}

func (c *RedisConn) Reader() *resp3.Reader {
return c.reader
}

func (c *RedisConn) Writer() *resp3.Writer {
return c.writer
}

func (c *RedisConn) WriteValue(value *resp3.Value, flush bool) error {
_, err := c.writer.WriteString(value.ToRESP3String())
if err != nil {
return err
}
if !flush {
return nil
}
return c.writer.Flush()
}
99 changes: 99 additions & 0 deletions packages/pam/handlers/redis/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package redis

import (
"context"
"crypto/tls"
"fmt"
"net"

"github.com/Infisical/infisical-merge/packages/pam/session"
"github.com/rs/zerolog/log"
"github.com/smallnest/resp3"
)

// RedisProxyConfig holds configuration for the Redis proxy
type RedisProxyConfig struct {
TargetAddr string
InjectUsername string
InjectPassword string
EnableTLS bool
TLSConfig *tls.Config
SessionID string
SessionLogger session.SessionLogger
}

// RedisProxy handles proxying Redis connections
type RedisProxy struct {
config RedisProxyConfig
relayHandler *RelayHandler
}

// NewRedisProxy creates a new Redis proxy instance
func NewRedisProxy(config RedisProxyConfig) *RedisProxy {
return &RedisProxy{config: config}
}

// HandleConnection handles a single client connection
func (p *RedisProxy) HandleConnection(ctx context.Context, clientConn net.Conn) error {
defer clientConn.Close()

sessionID := p.config.SessionID

// Ensure session logger cleanup
defer func() {
if err := p.config.SessionLogger.Close(); err != nil {
log.Error().Err(err).Str("sessionID", sessionID).Msg("Failed to close session logger")
}
}()

log.Info().
Str("sessionID", sessionID).
Msg("New Redis connection for PAM session")

var selfToServerConn net.Conn
if !p.config.EnableTLS {
c, err := net.Dial("tcp", p.config.TargetAddr)
if err != nil {
return err
}
selfToServerConn = c
} else {
c, err := tls.Dial("tcp", p.config.TargetAddr, p.config.TLSConfig)
if err != nil {
return err
}
selfToServerConn = c
}

selfToClientRedisConn := NewRedisConn(selfToServerConn)
defer func(selfToClientRedisConn *RedisConn) { _ = selfToClientRedisConn.Close() }(selfToClientRedisConn)

// Only authenticate if credentials are provided
if p.config.InjectUsername != "" && p.config.InjectPassword != "" {
if err := selfToClientRedisConn.Writer().WriteCommand("AUTH", p.config.InjectUsername, p.config.InjectPassword); err != nil {
return err
}
if err := selfToClientRedisConn.Writer().Flush(); err != nil {
return err
}

respValue, _, err := selfToClientRedisConn.Reader().ReadValue()
if err != nil {
return err
}
if respValue.Str != "OK" {
errorMsg := "unknown"
if respValue.Type == resp3.TypeSimpleError || respValue.Type == resp3.TypeBlobError {
errorMsg = respValue.Err
}
log.Error().Str("errorMsg", errorMsg).Msg("Failed to authenticate with the target redis server")
return fmt.Errorf("failed to authenticate with the target redis server")
}
}

clientToSelfConn := NewRedisConn(clientConn)
defer clientToSelfConn.Close()

p.relayHandler = NewRelayHandler(clientToSelfConn, selfToClientRedisConn, p.config.SessionLogger)
return p.relayHandler.Handle(ctx)
}
Loading
Loading