Skip to content

Commit

Permalink
[gateway] Allow performing plain execution on connection over interna…
Browse files Browse the repository at this point in the history
…l routes using clientexec (#568)

Revert emitting stacktraces when logging errors
Fix bug when guardrule returns a match over api execution
  • Loading branch information
sandromello authored Nov 26, 2024
1 parent b9386c4 commit 29bf7d1
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 6 deletions.
2 changes: 1 addition & 1 deletion common/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func NewDefaultLogger(additionalWriterLogger io.Writer) *zap.Logger {
}),
zap.ErrorOutput(stderrSink),
zap.AddCaller(),
zap.AddStacktrace(zapcore.DPanicLevel),
zap.AddStacktrace(zapcore.ErrorLevel),
)
zap.ReplaceGlobals(logger)
return logger
Expand Down
5 changes: 3 additions & 2 deletions common/proto/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ const (

ClientLoginCallbackAddress string = "127.0.0.1:3587"

ClientVerbConnect = "connect"
ClientVerbExec = "exec"
ClientVerbConnect = "connect"
ClientVerbExec = "exec"
ClientVerbPlainExec = "plain-exec"

SessionPhaseClientConnect = "client-connect"
SessionPhaseClientConnected = "client-connected"
Expand Down
2 changes: 1 addition & 1 deletion gateway/api/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func Post(c *gin.Context) {
{0, "e", base64.StdEncoding.EncodeToString([]byte(err.Error()))},
},
}
if err = pgsession.New().Upsert(ctx, newSession); err != nil {
if err := pgsession.New().Upsert(ctx, newSession); err != nil {
log.Errorf("unable to update session, err=%v", err)
}
c.JSON(http.StatusOK, clientexec.Response{
Expand Down
28 changes: 27 additions & 1 deletion gateway/clientexec/clientexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package clientexec

import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
"path/filepath"
Expand All @@ -26,6 +29,9 @@ var (
walLogPath = filepath.Join(plugintypes.AuditPath, "clientexec")
walFolderTmpl = `%s/%s-%s-wal`
maxResponseBytes = sessionwal.DefaultMaxRead

// PlainExecSecretKey is a key to execute plain executions in the gateway securely by this package
PlainExecSecretKey string = generateSecureRandomKeyOrDie()
)

func init() { _ = os.MkdirAll(walLogPath, 0755) }
Expand All @@ -52,6 +58,7 @@ type Options struct {
ConnectionName string
BearerToken string
Origin string
Verb string
UserAgent string
}

Expand Down Expand Up @@ -97,10 +104,15 @@ func New(opts *Options) (*clientExec, error) {
if opts.SessionID == "" {
opts.SessionID = uuid.NewString()
}

if opts.Origin == "" {
opts.Origin = pb.ConnectionOriginClientAPI
}

if opts.Verb == "" {
opts.Verb = pb.ClientVerbExec
}

folderName := fmt.Sprintf(walFolderTmpl, walLogPath, opts.OrgID, opts.SessionID)
wlog, err := wal.Open(folderName, wal.DefaultOptions)
if err != nil {
Expand All @@ -122,8 +134,9 @@ func New(opts *Options) (*clientExec, error) {
},
grpc.WithOption(grpc.OptionConnectionName, opts.ConnectionName),
grpc.WithOption("origin", opts.Origin),
grpc.WithOption("verb", pb.ClientVerbExec),
grpc.WithOption("verb", opts.Verb),
grpc.WithOption("session-id", opts.SessionID),
grpc.WithOption("plain-exec-key", PlainExecSecretKey),
)
if err != nil {
_ = wlog.Close()
Expand Down Expand Up @@ -290,3 +303,16 @@ func (c *clientExec) readAll() ([]byte, bool, error) {

return stdoutData, isTruncated, nil
}

func generateSecureRandomKeyOrDie() string {
secretRandomBytes := make([]byte, 32)
if _, err := rand.Read(secretRandomBytes); err != nil {
log.Fatalf("failed generating entropy, err=%v", err)
}
secretKey := base64.RawURLEncoding.EncodeToString(secretRandomBytes)
h := sha256.New()
if _, err := h.Write([]byte(secretKey)); err != nil {
log.Fatalf("failed hashing secret key, err=%v", err)
}
return fmt.Sprintf("%x", h.Sum(nil))
}
1 change: 1 addition & 0 deletions gateway/transport/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (s *Server) listenAgentMessages(pctx *plugintypes.Context, stream *streamcl
OrgID: pctx.OrgID,
SID: pctx.SID,
ConnectionName: proxyStream.PluginContext().ConnectionName,
Verb: proxyStream.PluginContext().ClientVerb,
}

if err := transportext.OnReceive(extContext, pkt); err != nil {
Expand Down
1 change: 1 addition & 0 deletions gateway/transport/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func (s *Server) processClientPacket(stream *streamclient.ProxyStream, pkt *pb.P
OrgID: pctx.OrgID,
SID: pctx.SID,
ConnectionName: pctx.ConnectionName,
Verb: pctx.ClientVerb,
}

if err := transportext.OnReceive(extContext, pkt); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions gateway/transport/extensions/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ type Context struct {
SID string
OrgID string
ConnectionName string
Verb string
}

func OnReceive(ctx Context, pkt *proto.Packet) error {
if ctx.Verb == proto.ClientVerbPlainExec {
return nil
}

switch pkt.Type {
case pbagent.SessionOpen:
conn, err := models.GetConnectionGuardRailRules(ctx.OrgID, ctx.ConnectionName)
Expand Down
14 changes: 13 additions & 1 deletion gateway/transport/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package authinterceptor

import (
"context"
"errors"
"os"
"strings"

Expand All @@ -15,6 +16,7 @@ import (
apiconnections "github.com/hoophq/hoop/gateway/api/connections"
localauthapi "github.com/hoophq/hoop/gateway/api/localauth"
"github.com/hoophq/hoop/gateway/appconfig"
"github.com/hoophq/hoop/gateway/clientexec"
"github.com/hoophq/hoop/gateway/pgrest"
pgagents "github.com/hoophq/hoop/gateway/pgrest/agents"
pgorgs "github.com/hoophq/hoop/gateway/pgrest/orgs"
Expand Down Expand Up @@ -87,13 +89,23 @@ func (i *interceptor) StreamServerInterceptor(srv any, ss grpc.ServerStream, inf
if !ok {
return status.Error(codes.InvalidArgument, "missing context metadata")
}
clientOrigin := md.Get("origin")
clientOrigin, clientVerb := md.Get("origin"), md.Get("verb")
if len(clientOrigin) == 0 {
md.Delete("authorization")
log.Debugf("client missing origin, client-metadata=%v", md)
return status.Error(codes.InvalidArgument, "missing client origin")
}

if len(clientVerb) > 0 && clientVerb[0] == pb.ClientVerbPlainExec {
plainExecKey := md.Get("plain-exec-key")
if len(plainExecKey) == 0 || plainExecKey[0] != clientexec.PlainExecSecretKey {
errMsg := "failed validating plain execution, plain-exec-key attribute is missing or does not match"
log.Error(errMsg)
sentry.CaptureException(errors.New(errMsg))
return status.Errorf(codes.Unauthenticated, "invalid authentication")
}
}

bearerToken, err := parseBearerToken(md)
if err != nil {
return err
Expand Down
3 changes: 3 additions & 0 deletions gateway/transport/streamclient/pluginruntime.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type runtimePlugin struct {

func loadRuntimePlugins(ctx plugintypes.Context) ([]runtimePlugin, error) {
pluginsConfig := make([]runtimePlugin, 0)
if ctx.ClientVerb == pb.ClientVerbPlainExec {
return pluginsConfig, nil
}
var nonRegisteredPlugins []string
for _, p := range plugintypes.RegisteredPlugins {
p1, err := pgplugins.New().FetchOne(ctx, p.Name())
Expand Down

0 comments on commit 29bf7d1

Please sign in to comment.