diff --git a/cmd/ssh-portal/serve.go b/cmd/ssh-portal/serve.go index 19586b9..8f4fbee 100644 --- a/cmd/ssh-portal/serve.go +++ b/cmd/ssh-portal/serve.go @@ -9,7 +9,7 @@ import ( "syscall" "time" - "github.com/nats-io/nats.go" + "github.com/uselagoon/ssh-portal/internal/bus" "github.com/uselagoon/ssh-portal/internal/k8s" "github.com/uselagoon/ssh-portal/internal/metrics" "github.com/uselagoon/ssh-portal/internal/sshserver" @@ -36,24 +36,12 @@ type ServeCmd struct { // Run the serve command to handle SSH connection requests. func (cmd *ServeCmd) Run(log *slog.Logger) error { // get main process context, which cancels on SIGTERM - ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM) - defer stop() - // get nats server connection - nc, err := nats.Connect(cmd.NATSServer, - nats.Name("ssh-portal"), - // exit on connection close - nats.ClosedHandler(func(_ *nats.Conn) { - log.Error("nats connection closed") - stop() - }), - nats.DisconnectErrHandler(func(_ *nats.Conn, err error) { - log.Warn("nats disconnected", slog.Any("error", err)) - }), - nats.ReconnectHandler(func(nc *nats.Conn) { - log.Info("nats reconnected", slog.String("url", nc.ConnectedUrl())) - })) + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM) + defer cancel() + // get nats client + nc, err := bus.NewNATSClient(cmd.NATSServer, log, cancel) if err != nil { - return fmt.Errorf("couldn't connect to NATS server: %v", err) + return fmt.Errorf("couldn't get nats client: %v", err) } defer nc.Close() // start listening on TCP port diff --git a/go.mod b/go.mod index f52104f..6e52910 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/alecthomas/kong v1.5.0 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc - github.com/gliderlabs/ssh v0.3.7 + github.com/gliderlabs/ssh v0.3.8 github.com/go-sql-driver/mysql v1.8.1 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.1-0.20240806143717-0e97ed3b5379 @@ -20,10 +20,10 @@ require ( github.com/zitadel/oidc/v3 v3.33.1 go.opentelemetry.io/otel v1.32.0 go.uber.org/mock v0.5.0 - golang.org/x/crypto v0.29.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/oauth2 v0.24.0 - golang.org/x/sync v0.9.0 + golang.org/x/sync v0.10.0 golang.org/x/time v0.8.0 k8s.io/api v0.31.3 k8s.io/apimachinery v0.31.3 @@ -74,9 +74,9 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.26.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 3b2b534..da6adb3 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= @@ -182,6 +184,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -199,18 +203,26 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/bus/ssh.go b/internal/bus/ssh.go index 72b1c29..e6c1809 100644 --- a/internal/bus/ssh.go +++ b/internal/bus/ssh.go @@ -1,20 +1,30 @@ // Package bus contains the definitions of the messages passed across NATS. package bus -import "log/slog" +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "time" + + "github.com/nats-io/nats.go" +) const ( // SubjectSSHAccessQuery defines the NATS subject for SSH access queries. SubjectSSHAccessQuery = "lagoon.sshportal.api" + // NATS request timeout. + natsTimeout = 8 * time.Second ) // SSHAccessQuery defines the structure of an SSH access query. type SSHAccessQuery struct { + SessionID string SSHFingerprint string NamespaceName string ProjectID int EnvironmentID int - SessionID string } // LogValue implements the slog.LogValuer interface. @@ -27,3 +37,83 @@ func (q SSHAccessQuery) LogValue() slog.Value { slog.String("sessionID", q.SessionID), ) } + +// NATSClient is a NATS client. +type NATSClient struct { + conn *nats.Conn +} + +// NewNATSClient constructs a new NATS client which connects to the given +// srvAddr. It logs to the given log, and calls the given context.CancelFunc +// when the NATS connection closes. +// +// The idea is that when the connection closes on the other end, this function +// must be called again to construct a new client. +func NewNATSClient( + srvAddr string, + log *slog.Logger, + cancel context.CancelFunc, +) (*NATSClient, error) { + // get nats server connection + conn, err := nats.Connect( + srvAddr, + nats.Name("ssh-portal"), + // cancel upstream context on connection close + nats.ClosedHandler(func(_ *nats.Conn) { + log.Error("nats connection closed") + cancel() + }), + nats.DisconnectErrHandler(func(_ *nats.Conn, err error) { + log.Warn("nats disconnected", slog.Any("error", err)) + }), + nats.ReconnectHandler(func(nc *nats.Conn) { + log.Info("nats reconnected", slog.String("url", nc.ConnectedUrl())) + })) + if err != nil { + return nil, fmt.Errorf("couldn't connect to NATS server: %v", err) + } + return &NATSClient{ + conn: conn, + }, nil +} + +// Close calls Close() on the underlying NATS connection. +func (c *NATSClient) Close() { + c.conn.Close() +} + +// KeyCanAccessEnvironment returns true if the given key can access the given +// environment, or false otherwise. +func (c *NATSClient) KeyCanAccessEnvironment( + sessionID, + sshFingerprint, + namespaceName string, + projectID, + environmentID int, +) (bool, error) { + // construct ssh access query + queryData, err := json.Marshal(SSHAccessQuery{ + SessionID: sessionID, + SSHFingerprint: sshFingerprint, + NamespaceName: namespaceName, + ProjectID: projectID, + EnvironmentID: environmentID, + }) + if err != nil { + return false, fmt.Errorf("couldn't marshal NATS request: %v", err) + } + // send query + msg, err := c.conn.Request( + SubjectSSHAccessQuery, + queryData, + natsTimeout) + if err != nil { + return false, fmt.Errorf("couldn't make NATS request: %v", err) + } + // handle response + var ok bool + if err := json.Unmarshal(msg.Data, &ok); err != nil { + return false, fmt.Errorf("couldn't unmarshal response: %v", err) + } + return ok, nil +} diff --git a/internal/k8s/namespacedetails.go b/internal/k8s/namespacedetails.go index 1f8c823..a143b9a 100644 --- a/internal/k8s/namespacedetails.go +++ b/internal/k8s/namespacedetails.go @@ -28,19 +28,23 @@ func intFromLabel(labels map[string]string, label string) (int, error) { // the labels on a Lagoon environment namespace for a Lagoon namespace. If one // of the expected labels is missing or cannot be parsed, it will return an // error. -func (c *Client) NamespaceDetails(ctx context.Context, name string) ( - int, int, string, string, error) { +func (c *Client) NamespaceDetails( + ctx context.Context, + name string, +) (int, int, string, string, error) { var eid, pid int var ename, pname string var ok bool ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - ns, err := c.clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) + ns, err := + c.clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) if err != nil { return 0, 0, "", "", fmt.Errorf("couldn't get namespace: %v", err) } if eid, err = intFromLabel(ns.Labels, environmentIDLabel); err != nil { - return 0, 0, "", "", fmt.Errorf("couldn't get environment ID from label: %v", err) + return 0, 0, "", "", + fmt.Errorf("couldn't get environment ID from label: %v", err) } if pid, err = intFromLabel(ns.Labels, projectIDLabel); err != nil { return 0, 0, "", "", fmt.Errorf("couldn't get project ID from label: %v", err) @@ -50,7 +54,8 @@ func (c *Client) NamespaceDetails(ctx context.Context, name string) ( environmentNameLabel) } if pname, ok = ns.Labels[projectNameLabel]; !ok { - return 0, 0, "", "", fmt.Errorf("missing project name label %v", projectNameLabel) + return 0, 0, "", "", + fmt.Errorf("missing project name label %v", projectNameLabel) } return eid, pid, ename, pname, nil } diff --git a/internal/sshserver/authhandler.go b/internal/sshserver/authhandler.go index 0694cfb..f6d6e62 100644 --- a/internal/sshserver/authhandler.go +++ b/internal/sshserver/authhandler.go @@ -1,60 +1,50 @@ package sshserver import ( - "encoding/json" "log/slog" - "time" + "strconv" "github.com/gliderlabs/ssh" - "github.com/nats-io/nats.go" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/uselagoon/ssh-portal/internal/bus" - "github.com/uselagoon/ssh-portal/internal/k8s" gossh "golang.org/x/crypto/ssh" ) -type ctxKey int - const ( - environmentIDKey ctxKey = iota - environmentNameKey - projectIDKey - projectNameKey - sshFingerprint -) - -var ( - natsTimeout = 8 * time.Second + environmentIDKey = "uselagoon/environmentID" + environmentNameKey = "uselagoon/environmentName" + projectIDKey = "uselagoon/projectID" + projectNameKey = "uselagoon/projectName" ) -var ( - authAttemptsTotal = promauto.NewCounter(prometheus.CounterOpts{ - Name: "sshportal_authentication_attempts_total", - Help: "The total number of ssh-portal authentication attempts", - }) - authSuccessTotal = promauto.NewCounter(prometheus.CounterOpts{ - Name: "sshportal_authentication_success_total", - Help: "The total number of successful ssh-portal authentications", - }) -) +// permissionsMarshal takes details of the Lagoon environment and stores them +// in the Extensions field of the ssh connection permissions. +// +// The Extensions field is the only way to safely pass information between +// handlers. See https://pkg.go.dev/vuln/GO-2024-3321 +func permissionsMarshal(ctx ssh.Context, eid, pid int, ename, pname string) { + ctx.Permissions().Extensions = map[string]string{ + environmentIDKey: strconv.Itoa(eid), + environmentNameKey: ename, + projectIDKey: strconv.Itoa(pid), + projectNameKey: pname, + } +} -// pubKeyAuth returns a ssh.PublicKeyHandler which queries the remote +// pubKeyHandler returns a ssh.PublicKeyHandler which queries the remote // ssh-portal-api for Lagoon SSH authorization. -func pubKeyAuth( +// +// Note that this function will be called for ALL public keys presented by the +// client, even if the client does not go on to prove ownership of the key by +// signing with it. See https://pkg.go.dev/vuln/GO-2024-3321 +func pubKeyHandler( log *slog.Logger, - nc *nats.Conn, - c *k8s.Client, + nc NATSService, + c K8SAPIService, ) ssh.PublicKeyHandler { return func(ctx ssh.Context, key ssh.PublicKey) bool { - authAttemptsTotal.Inc() - log := log.With(slog.String("sessionID", ctx.SessionID())) - // parse SSH public key - pubKey, err := gossh.ParsePublicKey(key.Marshal()) - if err != nil { - log.Warn("couldn't parse SSH public key", slog.Any("error", err)) - return false - } + log := log.With( + slog.String("sessionID", ctx.SessionID()), + slog.String("namespace", ctx.User()), + ) // get Lagoon labels from namespace if available eid, pid, ename, pname, err := c.NamespaceDetails(ctx, ctx.User()) if err != nil { @@ -62,46 +52,27 @@ func pubKeyAuth( slog.String("namespace", ctx.User()), slog.Any("error", err)) return false } - // construct ssh access query - fingerprint := gossh.FingerprintSHA256(pubKey) - queryData, err := json.Marshal(bus.SSHAccessQuery{ - SSHFingerprint: fingerprint, - NamespaceName: ctx.User(), - ProjectID: pid, - EnvironmentID: eid, - SessionID: ctx.SessionID(), - }) + fingerprint := gossh.FingerprintSHA256(key) + ok, err := nc.KeyCanAccessEnvironment( + ctx.SessionID(), + fingerprint, + ctx.User(), + pid, + eid, + ) if err != nil { - log.Warn("couldn't marshal NATS request", slog.Any("error", err)) - return false - } - // send query - msg, err := nc.Request(bus.SubjectSSHAccessQuery, queryData, natsTimeout) - if err != nil { - log.Warn("couldn't make NATS request", slog.Any("error", err)) + log.Warn("couldn't query permission via NATS", slog.Any("error", err)) return false } // handle response - var ok bool - if err := json.Unmarshal(msg.Data, &ok); err != nil { - log.Warn("couldn't unmarshal response", slog.Any("response", msg.Data)) - return false - } if !ok { log.Debug("SSH access not authorized", - slog.String("fingerprint", fingerprint), - slog.String("namespace", ctx.User())) + slog.String("fingerprint", fingerprint)) return false } - authSuccessTotal.Inc() - ctx.SetValue(environmentIDKey, eid) - ctx.SetValue(environmentNameKey, ename) - ctx.SetValue(projectIDKey, pid) - ctx.SetValue(projectNameKey, pname) - ctx.SetValue(sshFingerprint, fingerprint) log.Debug("SSH access authorized", - slog.String("fingerprint", fingerprint), - slog.String("namespace", ctx.User())) + slog.String("fingerprint", fingerprint)) + permissionsMarshal(ctx, eid, pid, ename, pname) return true } } diff --git a/internal/sshserver/authhandler_test.go b/internal/sshserver/authhandler_test.go new file mode 100644 index 0000000..b77815c --- /dev/null +++ b/internal/sshserver/authhandler_test.go @@ -0,0 +1,77 @@ +package sshserver_test + +import ( + "crypto/ed25519" + "log/slog" + "os" + "testing" + + "github.com/alecthomas/assert/v2" + "github.com/gliderlabs/ssh" + "github.com/uselagoon/ssh-portal/internal/sshserver" + gomock "go.uber.org/mock/gomock" + gossh "golang.org/x/crypto/ssh" +) + +func TestPubKeyHandler(t *testing.T) { + log := slog.New(slog.NewJSONHandler(os.Stderr, nil)) + var testCases = map[string]struct { + keyCanAccessEnv bool + }{ + "access granted": { + keyCanAccessEnv: true, + }, + "access denied": { + keyCanAccessEnv: false, + }, + } + for name, tc := range testCases { + t.Run(name, func(tt *testing.T) { + ctrl := gomock.NewController(tt) + k8sService := NewMockK8SAPIService(ctrl) + natsService := NewMockNATSService(ctrl) + sshContext := NewMockContext(ctrl) + // configure callback + callback := sshserver.PubKeyHandler( + log, + natsService, + k8sService, + ) + // configure mocks + namespaceName := "my-project-master" + sessionID := "abc123" + projectID := 1 + environmentID := 2 + sshContext.EXPECT().User().Return(namespaceName).AnyTimes() + sshContext.EXPECT().SessionID().Return(sessionID).AnyTimes() + k8sService.EXPECT().NamespaceDetails(sshContext, namespaceName). + Return(environmentID, projectID, "master", "my-project", nil) + // set up public key mock + publicKey, _, err := ed25519.GenerateKey(nil) + if err != nil { + tt.Fatal(err) + } + sshPublicKey, err := gossh.NewPublicKey(publicKey) + if err != nil { + tt.Fatal(err) + } + fingerprint := gossh.FingerprintSHA256(sshPublicKey) + natsService.EXPECT().KeyCanAccessEnvironment( + sessionID, + fingerprint, + namespaceName, + projectID, + environmentID, + ).Return(tc.keyCanAccessEnv, nil) + // set up permissions mock + sshPermissions := ssh.Permissions{Permissions: &gossh.Permissions{}} + // permissions are not touched if access is denied + if tc.keyCanAccessEnv { + sshContext.EXPECT().Permissions().Return(&sshPermissions) + } + // execute callback + assert.Equal( + tt, tc.keyCanAccessEnv, callback(sshContext, sshPublicKey), name) + }) + } +} diff --git a/internal/sshserver/helper_test.go b/internal/sshserver/helper_test.go index 17ee91f..c762ea0 100644 --- a/internal/sshserver/helper_test.go +++ b/internal/sshserver/helper_test.go @@ -1,14 +1,13 @@ package sshserver -// ParseConnectionParams exposes the private parseConnectionParams for testing -// only. -var ParseConnectionParams = parseConnectionParams - -// ParseLogsArg exposes the private parseLogsArg for testing only. -var ParseLogsArg = parseLogsArg - -// SessionHandler exposes the private sessionHandler for testing only. -var SessionHandler = sessionHandler +// These variables are exposed for testing only. +var ( + ParseConnectionParams = parseConnectionParams + ParseLogsArg = parseLogsArg + PermissionsMarshal = permissionsMarshal + SessionHandler = sessionHandler + PubKeyHandler = pubKeyHandler +) // Exposes the private ctxKey constants for testing only. const ( @@ -16,5 +15,4 @@ const ( EnvironmentNameKey = environmentNameKey ProjectIDKey = projectIDKey ProjectNameKey = projectNameKey - SSHFingerprint = sshFingerprint ) diff --git a/internal/sshserver/serve.go b/internal/sshserver/serve.go index 137badd..2623e33 100644 --- a/internal/sshserver/serve.go +++ b/internal/sshserver/serve.go @@ -10,7 +10,6 @@ import ( "time" "github.com/gliderlabs/ssh" - "github.com/nats-io/nats.go" "github.com/uselagoon/ssh-portal/internal/k8s" gossh "golang.org/x/crypto/ssh" ) @@ -19,6 +18,11 @@ import ( // (e.g. via signal) const shutdownTimeout = 8 * time.Second +// NATSService represents a NATS RPC service. +type NATSService interface { + KeyCanAccessEnvironment(string, string, string, int, int) (bool, error) +} + // disableSHA1Kex returns a ServerConfig which relies on default for everything // except key exchange algorithms. There it removes the SHA1 based algorithms. // @@ -40,7 +44,7 @@ func disableSHA1Kex(_ ssh.Context) *gossh.ServerConfig { func Serve( ctx context.Context, log *slog.Logger, - nc *nats.Conn, + nats NATSService, l net.Listener, c *k8s.Client, hostKeys [][]byte, @@ -52,7 +56,7 @@ func Serve( SubsystemHandlers: map[string]ssh.SubsystemHandler{ "sftp": ssh.SubsystemHandler(sessionHandler(log, c, true, logAccessEnabled)), }, - PublicKeyHandler: pubKeyAuth(log, nc, c), + PublicKeyHandler: pubKeyHandler(log, nats, c), ServerConfigCallback: disableSHA1Kex, Banner: banner, } diff --git a/internal/sshserver/sessionhandler.go b/internal/sshserver/sessionhandler.go index 75ec698..434a8eb 100644 --- a/internal/sshserver/sessionhandler.go +++ b/internal/sshserver/sessionhandler.go @@ -5,12 +5,14 @@ import ( "fmt" "io" "log/slog" + "strconv" "time" "github.com/gliderlabs/ssh" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/uselagoon/ssh-portal/internal/k8s" + gossh "golang.org/x/crypto/ssh" "k8s.io/utils/exec" ) @@ -20,6 +22,7 @@ type K8SAPIService interface { io.Writer, bool, <-chan ssh.Window) error FindDeployment(context.Context, string, string) (string, error) Logs(context.Context, string, string, string, bool, int64, io.ReadWriter) error + NamespaceDetails(context.Context, string) (int, int, string, string, error) } var ( @@ -37,37 +40,44 @@ var ( }) ) -// authCtxValues extracts the context values set by the authhandler. -func authCtxValues(ctx ssh.Context) (int, string, int, string, string, error) { - var ok bool +// permissionsUnmarshal extracts details of the Lagoon environment identified +// in the pubKeyHandler which were stored in the Extensions field of the ssh +// connection. See permissionsMarshal. +func permissionsUnmarshal(ctx ssh.Context) (int, int, string, string, error) { var eid, pid int - var ename, pname, fingerprint string - eid, ok = ctx.Value(environmentIDKey).(int) + var ename, pname string + var err error + eidString, ok := ctx.Permissions().Extensions[environmentIDKey] if !ok { - return eid, ename, pid, pname, fingerprint, - fmt.Errorf("couldn't extract environment ID from session context") + return eid, pid, ename, pname, + fmt.Errorf("missing environmentID in permissions") } - ename, ok = ctx.Value(environmentNameKey).(string) - if !ok { - return eid, ename, pid, pname, fingerprint, - fmt.Errorf("couldn't extract environment name from session context") + eid, err = strconv.Atoi(eidString) + if err != nil { + return eid, pid, ename, pname, + fmt.Errorf("couldn't parse environmentID in permissions") } - pid, ok = ctx.Value(projectIDKey).(int) + pidString, ok := ctx.Permissions().Extensions[projectIDKey] if !ok { - return eid, ename, pid, pname, fingerprint, - fmt.Errorf("couldn't extract project ID from session context") + return eid, pid, ename, pname, + fmt.Errorf("missing projectID in permissions") + } + pid, err = strconv.Atoi(pidString) + if err != nil { + return eid, pid, ename, pname, + fmt.Errorf("couldn't parse projectID in permissions") } - pname, ok = ctx.Value(projectNameKey).(string) + ename, ok = ctx.Permissions().Extensions[environmentNameKey] if !ok { - return eid, ename, pid, pname, fingerprint, - fmt.Errorf("couldn't extract project name from session context") + return eid, pid, ename, pname, + fmt.Errorf("missing environmentName in permissions") } - fingerprint, ok = ctx.Value(sshFingerprint).(string) + pname, ok = ctx.Permissions().Extensions[projectNameKey] if !ok { - return eid, ename, pid, pname, fingerprint, - fmt.Errorf("couldn't extract SSH key fingerprint from session context") + return eid, pid, ename, pname, + fmt.Errorf("missing projectName in permissions") } - return eid, ename, pid, pname, fingerprint, nil + return eid, pid, ename, pname, nil } // getSSHIntent analyses the SFTP flag and the raw command strings to determine @@ -162,9 +172,9 @@ func sessionHandler( return } // extract info passed through the context by the authhandler - eid, ename, pid, pname, fingerprint, err := authCtxValues(ctx) + eid, pid, ename, pname, err := permissionsUnmarshal(ctx) if err != nil { - log.Error("couldn't extract auth values from context", + log.Error("couldn't unmarshal values from permissions", slog.Any("error", err)) _, err = fmt.Fprintf(s.Stderr(), "error executing command. SID: %s\r\n", ctx.SessionID()) @@ -211,7 +221,7 @@ func sessionHandler( log.Info("sending logs to SSH client", slog.Int("environmentID", eid), slog.Int("projectID", pid), - slog.String("SSHFingerprint", fingerprint), + slog.String("SSHFingerprint", gossh.FingerprintSHA256(s.PublicKey())), slog.String("container", container), slog.String("deployment", deployment), slog.String("environmentName", ename), @@ -231,7 +241,7 @@ func sessionHandler( slog.Bool("pty", pty), slog.Int("environmentID", eid), slog.Int("projectID", pid), - slog.String("SSHFingerprint", fingerprint), + slog.String("SSHFingerprint", gossh.FingerprintSHA256(s.PublicKey())), slog.String("container", container), slog.String("deployment", deployment), slog.String("environmentName", ename), diff --git a/internal/sshserver/sessionhandler_test.go b/internal/sshserver/sessionhandler_test.go index 4ed23d9..81cd73d 100644 --- a/internal/sshserver/sessionhandler_test.go +++ b/internal/sshserver/sessionhandler_test.go @@ -1,6 +1,7 @@ package sshserver_test import ( + "crypto/ed25519" "log/slog" "os" "testing" @@ -9,6 +10,7 @@ import ( "github.com/gliderlabs/ssh" "github.com/uselagoon/ssh-portal/internal/sshserver" "go.uber.org/mock/gomock" + gossh "golang.org/x/crypto/ssh" ) func TestExec(t *testing.T) { @@ -67,11 +69,21 @@ func TestExec(t *testing.T) { user, deployment, ).Return(deployment, nil) - sshContext.EXPECT().Value(sshserver.EnvironmentIDKey).Return(0) - sshContext.EXPECT().Value(sshserver.EnvironmentNameKey).Return("test") - sshContext.EXPECT().Value(sshserver.ProjectIDKey).Return(0) - sshContext.EXPECT().Value(sshserver.ProjectNameKey).Return("project") - sshContext.EXPECT().Value(sshserver.SSHFingerprint).Return("fingerprint") + // emulate the auth handler and marshal the details + sshPermissions := ssh.Permissions{Permissions: &gossh.Permissions{}} + sshContext.EXPECT().Permissions().Return(&sshPermissions).Times(5) + sshserver.PermissionsMarshal(sshContext, 1, 2, "foo", "bar") + // set up public key mock + publicKey, _, err := ed25519.GenerateKey(nil) + if err != nil { + tt.Fatal(err) + } + sshPublicKey, err := gossh.NewPublicKey(publicKey) + if err != nil { + tt.Fatal(err) + } + sshSession.EXPECT().PublicKey().Return(sshPublicKey) + // configure remaining mocks winch := make(<-chan ssh.Window) sshSession.EXPECT().Pty().Return(ssh.Pty{}, winch, tc.pty) sshSession.EXPECT().Stderr().Return(os.Stderr) @@ -143,15 +155,23 @@ func TestLogs(t *testing.T) { tc.user, tc.deployment, ).Return(tc.deployment, nil) - sshContext.EXPECT().Value(sshserver.EnvironmentIDKey).Return(0) - sshContext.EXPECT().Value(sshserver.EnvironmentNameKey).Return("test") - sshContext.EXPECT().Value(sshserver.ProjectIDKey).Return(0) - sshContext.EXPECT().Value(sshserver.ProjectNameKey).Return("project") - sshContext.EXPECT().Value(sshserver.SSHFingerprint).Return("fingerprint") - + // emulate the auth handler and marshal the details + sshPermissions := ssh.Permissions{Permissions: &gossh.Permissions{}} + sshContext.EXPECT().Permissions().Return(&sshPermissions).Times(5) + sshserver.PermissionsMarshal(sshContext, 1, 2, "foo", "bar") + // set up public key mock + publicKey, _, err := ed25519.GenerateKey(nil) + if err != nil { + tt.Fatal(err) + } + sshPublicKey, err := gossh.NewPublicKey(publicKey) + if err != nil { + tt.Fatal(err) + } + sshSession.EXPECT().PublicKey().Return(sshPublicKey) // called by context.WithCancel() sshContext.EXPECT().Value(gomock.Any()).Return(nil).AnyTimes() - + // configure remaining mocks sshContext.EXPECT().Done().Return(make(<-chan struct{})).AnyTimes() k8sService.EXPECT().Logs( gomock.Any(), // private childCtx diff --git a/internal/sshserver/sessionhandler_mock_test.go b/internal/sshserver/sshserver_mock_test.go similarity index 54% rename from internal/sshserver/sessionhandler_mock_test.go rename to internal/sshserver/sshserver_mock_test.go index 839f3f2..9a83eac 100644 --- a/internal/sshserver/sessionhandler_mock_test.go +++ b/internal/sshserver/sshserver_mock_test.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: sessionhandler.go +// Source: github.com/uselagoon/ssh-portal/internal/sshserver (interfaces: K8SAPIService,NATSService) // // Generated by this command: // -// mockgen -source=sessionhandler.go -package=sshserver_test -destination=sessionhandler_mock_test.go -write_generate_directive +// mockgen -package=sshserver_test -destination=sshserver_mock_test.go -write_generate_directive . K8SAPIService,NATSService // // Package sshserver_test is a generated GoMock package. @@ -18,7 +18,7 @@ import ( gomock "go.uber.org/mock/gomock" ) -//go:generate mockgen -source=sessionhandler.go -package=sshserver_test -destination=sessionhandler_mock_test.go -write_generate_directive +//go:generate mockgen -package=sshserver_test -destination=sshserver_mock_test.go -write_generate_directive . K8SAPIService,NATSService // MockK8SAPIService is a mock of K8SAPIService interface. type MockK8SAPIService struct { @@ -85,3 +85,59 @@ func (mr *MockK8SAPIServiceMockRecorder) Logs(arg0, arg1, arg2, arg3, arg4, arg5 mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logs", reflect.TypeOf((*MockK8SAPIService)(nil).Logs), arg0, arg1, arg2, arg3, arg4, arg5, arg6) } + +// NamespaceDetails mocks base method. +func (m *MockK8SAPIService) NamespaceDetails(arg0 context.Context, arg1 string) (int, int, string, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NamespaceDetails", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(string) + ret3, _ := ret[3].(string) + ret4, _ := ret[4].(error) + return ret0, ret1, ret2, ret3, ret4 +} + +// NamespaceDetails indicates an expected call of NamespaceDetails. +func (mr *MockK8SAPIServiceMockRecorder) NamespaceDetails(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NamespaceDetails", reflect.TypeOf((*MockK8SAPIService)(nil).NamespaceDetails), arg0, arg1) +} + +// MockNATSService is a mock of NATSService interface. +type MockNATSService struct { + ctrl *gomock.Controller + recorder *MockNATSServiceMockRecorder +} + +// MockNATSServiceMockRecorder is the mock recorder for MockNATSService. +type MockNATSServiceMockRecorder struct { + mock *MockNATSService +} + +// NewMockNATSService creates a new mock instance. +func NewMockNATSService(ctrl *gomock.Controller) *MockNATSService { + mock := &MockNATSService{ctrl: ctrl} + mock.recorder = &MockNATSServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNATSService) EXPECT() *MockNATSServiceMockRecorder { + return m.recorder +} + +// KeyCanAccessEnvironment mocks base method. +func (m *MockNATSService) KeyCanAccessEnvironment(arg0, arg1, arg2 string, arg3, arg4 int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeyCanAccessEnvironment", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// KeyCanAccessEnvironment indicates an expected call of KeyCanAccessEnvironment. +func (mr *MockNATSServiceMockRecorder) KeyCanAccessEnvironment(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyCanAccessEnvironment", reflect.TypeOf((*MockNATSService)(nil).KeyCanAccessEnvironment), arg0, arg1, arg2, arg3, arg4) +} diff --git a/internal/sshtoken/authhandler.go b/internal/sshtoken/authhandler.go index c8b4584..fd539db 100644 --- a/internal/sshtoken/authhandler.go +++ b/internal/sshtoken/authhandler.go @@ -3,37 +3,37 @@ package sshtoken import ( "errors" "log/slog" - "time" "github.com/gliderlabs/ssh" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/google/uuid" "github.com/uselagoon/ssh-portal/internal/lagoondb" gossh "golang.org/x/crypto/ssh" ) -type ctxKey int - const ( - userUUIDkey ctxKey = iota + userUUIDKey = "uselagoon/userUUID" ) -var ( - authnAttemptsTotal = promauto.NewCounter(prometheus.CounterOpts{ - Name: "sshtoken_authentication_attempts_total", - Help: "The total number of ssh-token authentication attempts", - }) - authnSuccessTotal = promauto.NewCounter(prometheus.CounterOpts{ - Name: "sshtoken_authentication_success_total", - Help: "The total number of successful ssh-token authentications", - }) -) +// permissionsMarshal takes the user UUID and stores it in the Extensions field +// of the ssh connection permissions. +// +// The Extensions field is the only way to safely pass information between +// handlers. See https://pkg.go.dev/vuln/GO-2024-3321 +func permissionsMarshal(ctx ssh.Context, userUUID uuid.UUID) { + ctx.Permissions().Extensions = map[string]string{ + userUUIDKey: userUUID.String(), + } +} // pubKeyAuth returns a ssh.PublicKeyHandler which accepts any key which -// matches a user, and the associated user UUID to the ssh context. -func pubKeyAuth(log *slog.Logger, ldb LagoonDBService) ssh.PublicKeyHandler { +// matches a user, and adds the associated user UUID to the ssh permissions +// extensions map. +// +// Note that this function will be called for ALL public keys presented by the +// client, even if the client does not go on to prove ownership of the key by +// signing with it. See https://pkg.go.dev/vuln/GO-2024-3321 +func pubKeyHandler(log *slog.Logger, ldb LagoonDBService) ssh.PublicKeyHandler { return func(ctx ssh.Context, key ssh.PublicKey) bool { - authnAttemptsTotal.Inc() log := log.With(slog.String("sessionID", ctx.SessionID())) // parse SSH public key pubKey, err := gossh.ParsePublicKey(key.Marshal()) @@ -54,17 +54,7 @@ func pubKeyAuth(log *slog.Logger, ldb LagoonDBService) ssh.PublicKeyHandler { } return false } - // update last_used - if err := ldb.SSHKeyUsed(ctx, fingerprint, time.Now()); err != nil { - log.Error("couldn't update ssh key last used: %v", - slog.Any("error", err)) - return false - } - // The SSH key fingerprint was in the database so "authentication" was - // successful. Inject the user UUID into the context so it can be used in - // the session handler. - authnSuccessTotal.Inc() - ctx.SetValue(userUUIDkey, *user.UUID) + permissionsMarshal(ctx, *user.UUID) log.Info("authentication successful", slog.String("userUUID", user.UUID.String())) return true diff --git a/internal/sshtoken/authhandler_test.go b/internal/sshtoken/authhandler_test.go new file mode 100644 index 0000000..921add9 --- /dev/null +++ b/internal/sshtoken/authhandler_test.go @@ -0,0 +1,78 @@ +package sshtoken_test + +import ( + "crypto/ed25519" + "log/slog" + "os" + "testing" + + "github.com/alecthomas/assert/v2" + "github.com/gliderlabs/ssh" + "github.com/google/uuid" + "github.com/uselagoon/ssh-portal/internal/lagoondb" + "github.com/uselagoon/ssh-portal/internal/sshtoken" + gomock "go.uber.org/mock/gomock" + gossh "golang.org/x/crypto/ssh" +) + +func TestPubKeyHandler(t *testing.T) { + log := slog.New(slog.NewJSONHandler(os.Stderr, nil)) + var testCases = map[string]struct { + userBySSHFingerprintErr error + keyFound bool + }{ + "key matches user": { + userBySSHFingerprintErr: nil, + keyFound: true, + }, + "key doesn't match user": { + userBySSHFingerprintErr: lagoondb.ErrNoResult, + keyFound: false, + }, + } + for name, tc := range testCases { + t.Run(name, func(tt *testing.T) { + ctrl := gomock.NewController(tt) + ldbService := NewMockLagoonDBService(ctrl) + sshContext := NewMockContext(ctrl) + // configure callback + callback := sshtoken.PubKeyHandler( + log, + ldbService, + ) + // set up public key mock + publicKey, _, err := ed25519.GenerateKey(nil) + if err != nil { + tt.Fatal(err) + } + sshPublicKey, err := gossh.NewPublicKey(publicKey) + if err != nil { + tt.Fatal(err) + } + fingerprint := gossh.FingerprintSHA256(sshPublicKey) + // configure mocks + userUUID := uuid.Must(uuid.NewRandom()) + ldbService.EXPECT().UserBySSHFingerprint(sshContext, fingerprint). + Return(&lagoondb.User{UUID: &userUUID}, tc.userBySSHFingerprintErr) + sessionID := "abc123" + sshContext.EXPECT().SessionID().Return(sessionID).AnyTimes() + // set up permissions mock + sshPermissions := ssh.Permissions{Permissions: &gossh.Permissions{}} + if tc.keyFound { + // permissions are not touched if access is denied + sshContext.EXPECT().Permissions().Return(&sshPermissions) + } + // execute callback + assert.Equal( + tt, tc.keyFound, callback(sshContext, sshPublicKey), name) + if tc.keyFound { + assert.Equal(tt, + sshPermissions.Permissions.Extensions, + map[string]string{sshtoken.UserUUIDKey: userUUID.String()}, + name) + } else { + assert.Equal(tt, sshPermissions.Permissions.Extensions, nil, name) + } + }) + } +} diff --git a/internal/sshtoken/helper_test.go b/internal/sshtoken/helper_test.go new file mode 100644 index 0000000..181de4c --- /dev/null +++ b/internal/sshtoken/helper_test.go @@ -0,0 +1,10 @@ +package sshtoken + +// These variables are exposed for testing only. +var ( + PubKeyHandler = pubKeyHandler +) + +const ( + UserUUIDKey = userUUIDKey +) diff --git a/internal/sshtoken/serve.go b/internal/sshtoken/serve.go index d01c5c4..878b30f 100644 --- a/internal/sshtoken/serve.go +++ b/internal/sshtoken/serve.go @@ -38,7 +38,7 @@ func Serve( ) error { srv := ssh.Server{ Handler: sessionHandler(log, p, keycloakToken, ldb), - PublicKeyHandler: pubKeyAuth(log, ldb), + PublicKeyHandler: pubKeyHandler(log, ldb), } for _, hk := range hostKeys { if err := srv.SetOption(ssh.HostKeyPEM(hk)); err != nil { diff --git a/internal/sshtoken/sessionhandler.go b/internal/sshtoken/sessionhandler.go index 824315e..ed9f146 100644 --- a/internal/sshtoken/sessionhandler.go +++ b/internal/sshtoken/sessionhandler.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "time" "github.com/gliderlabs/ssh" "github.com/google/uuid" @@ -12,6 +13,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "github.com/uselagoon/ssh-portal/internal/lagoondb" "github.com/uselagoon/ssh-portal/internal/rbac" + gossh "golang.org/x/crypto/ssh" ) // KeycloakTokenService provides methods for querying the Keycloak API for user @@ -218,6 +220,17 @@ func redirectSession( slog.String("sshPort", sshPort)) } +// permissionsUnmarshal extracts the user UUID identified in the pubKeyHandler +// which was stored in the Extensions field of the ssh connection. See +// permissionsMarshal. +func permissionsUnmarshal(ctx ssh.Context) (uuid.UUID, error) { + userUUIDString, ok := ctx.Permissions().Extensions[userUUIDKey] + if !ok { + return uuid.UUID{}, fmt.Errorf("missing userUUID in permissions") + } + return uuid.Parse(userUUIDString) +} + // sessionHandler returns a ssh.Handler which writes a Lagoon access token to // the session stream and then closes the connection. func sessionHandler( @@ -228,12 +241,25 @@ func sessionHandler( ) ssh.Handler { return func(s ssh.Session) { sessionTotal.Inc() - // extract required info from the session context ctx := s.Context() - log := log.With(slog.String("sessionID", ctx.SessionID())) - userUUID, ok := ctx.Value(userUUIDkey).(uuid.UUID) - if !ok { - log.Warn("couldn't get user UUID from context") + fingerprint := gossh.FingerprintSHA256(s.PublicKey()) + log = log.With( + slog.String("fingerprint", fingerprint), + slog.String("sessionID", ctx.SessionID()), + ) + // update last_used, since at this point the key has been used to + // authenticate the session + if err := ldb.SSHKeyUsed(ctx, fingerprint, time.Now()); err != nil { + log.Error("couldn't update ssh key last used: %v", + slog.Any("error", err)) + return + } + // Get the user UUID to pass on to the tokenSession or redirectSession + userUUID, err := permissionsUnmarshal(ctx) + if err != nil { + log.Warn( + "couldn't get userUUID from ssh session context", + slog.Any("error", err)) _, err := fmt.Fprintf(s.Stderr(), "internal error. SID: %s\r\n", ctx.SessionID()) if err != nil { diff --git a/internal/sshtoken/ssh_mock_test.go b/internal/sshtoken/ssh_mock_test.go new file mode 100644 index 0000000..8877798 --- /dev/null +++ b/internal/sshtoken/ssh_mock_test.go @@ -0,0 +1,540 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/gliderlabs/ssh (interfaces: Session,Context) +// +// Generated by this command: +// +// mockgen -package=sshtoken_test -destination=ssh_mock_test.go -write_generate_directive github.com/gliderlabs/ssh Session,Context +// + +// Package sshtoken_test is a generated GoMock package. +package sshtoken_test + +import ( + io "io" + net "net" + reflect "reflect" + time "time" + + ssh "github.com/gliderlabs/ssh" + gomock "go.uber.org/mock/gomock" +) + +//go:generate mockgen -package=sshtoken_test -destination=ssh_mock_test.go -write_generate_directive github.com/gliderlabs/ssh Session,Context + +// MockSession is a mock of Session interface. +type MockSession struct { + ctrl *gomock.Controller + recorder *MockSessionMockRecorder +} + +// MockSessionMockRecorder is the mock recorder for MockSession. +type MockSessionMockRecorder struct { + mock *MockSession +} + +// NewMockSession creates a new mock instance. +func NewMockSession(ctrl *gomock.Controller) *MockSession { + mock := &MockSession{ctrl: ctrl} + mock.recorder = &MockSessionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSession) EXPECT() *MockSessionMockRecorder { + return m.recorder +} + +// Break mocks base method. +func (m *MockSession) Break(arg0 chan<- bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Break", arg0) +} + +// Break indicates an expected call of Break. +func (mr *MockSessionMockRecorder) Break(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Break", reflect.TypeOf((*MockSession)(nil).Break), arg0) +} + +// Close mocks base method. +func (m *MockSession) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockSessionMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSession)(nil).Close)) +} + +// CloseWrite mocks base method. +func (m *MockSession) CloseWrite() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseWrite") + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseWrite indicates an expected call of CloseWrite. +func (mr *MockSessionMockRecorder) CloseWrite() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseWrite", reflect.TypeOf((*MockSession)(nil).CloseWrite)) +} + +// Command mocks base method. +func (m *MockSession) Command() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Command") + ret0, _ := ret[0].([]string) + return ret0 +} + +// Command indicates an expected call of Command. +func (mr *MockSessionMockRecorder) Command() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Command", reflect.TypeOf((*MockSession)(nil).Command)) +} + +// Context mocks base method. +func (m *MockSession) Context() ssh.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(ssh.Context) + return ret0 +} + +// Context indicates an expected call of Context. +func (mr *MockSessionMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockSession)(nil).Context)) +} + +// Environ mocks base method. +func (m *MockSession) Environ() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Environ") + ret0, _ := ret[0].([]string) + return ret0 +} + +// Environ indicates an expected call of Environ. +func (mr *MockSessionMockRecorder) Environ() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Environ", reflect.TypeOf((*MockSession)(nil).Environ)) +} + +// Exit mocks base method. +func (m *MockSession) Exit(arg0 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exit", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Exit indicates an expected call of Exit. +func (mr *MockSessionMockRecorder) Exit(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exit", reflect.TypeOf((*MockSession)(nil).Exit), arg0) +} + +// LocalAddr mocks base method. +func (m *MockSession) LocalAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// LocalAddr indicates an expected call of LocalAddr. +func (mr *MockSessionMockRecorder) LocalAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockSession)(nil).LocalAddr)) +} + +// Permissions mocks base method. +func (m *MockSession) Permissions() ssh.Permissions { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Permissions") + ret0, _ := ret[0].(ssh.Permissions) + return ret0 +} + +// Permissions indicates an expected call of Permissions. +func (mr *MockSessionMockRecorder) Permissions() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Permissions", reflect.TypeOf((*MockSession)(nil).Permissions)) +} + +// Pty mocks base method. +func (m *MockSession) Pty() (ssh.Pty, <-chan ssh.Window, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Pty") + ret0, _ := ret[0].(ssh.Pty) + ret1, _ := ret[1].(<-chan ssh.Window) + ret2, _ := ret[2].(bool) + return ret0, ret1, ret2 +} + +// Pty indicates an expected call of Pty. +func (mr *MockSessionMockRecorder) Pty() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pty", reflect.TypeOf((*MockSession)(nil).Pty)) +} + +// PublicKey mocks base method. +func (m *MockSession) PublicKey() ssh.PublicKey { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublicKey") + ret0, _ := ret[0].(ssh.PublicKey) + return ret0 +} + +// PublicKey indicates an expected call of PublicKey. +func (mr *MockSessionMockRecorder) PublicKey() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicKey", reflect.TypeOf((*MockSession)(nil).PublicKey)) +} + +// RawCommand mocks base method. +func (m *MockSession) RawCommand() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RawCommand") + ret0, _ := ret[0].(string) + return ret0 +} + +// RawCommand indicates an expected call of RawCommand. +func (mr *MockSessionMockRecorder) RawCommand() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RawCommand", reflect.TypeOf((*MockSession)(nil).RawCommand)) +} + +// Read mocks base method. +func (m *MockSession) Read(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockSessionMockRecorder) Read(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockSession)(nil).Read), arg0) +} + +// RemoteAddr mocks base method. +func (m *MockSession) RemoteAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoteAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// RemoteAddr indicates an expected call of RemoteAddr. +func (mr *MockSessionMockRecorder) RemoteAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockSession)(nil).RemoteAddr)) +} + +// SendRequest mocks base method. +func (m *MockSession) SendRequest(arg0 string, arg1 bool, arg2 []byte) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendRequest indicates an expected call of SendRequest. +func (mr *MockSessionMockRecorder) SendRequest(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSession)(nil).SendRequest), arg0, arg1, arg2) +} + +// Signals mocks base method. +func (m *MockSession) Signals(arg0 chan<- ssh.Signal) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Signals", arg0) +} + +// Signals indicates an expected call of Signals. +func (mr *MockSessionMockRecorder) Signals(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Signals", reflect.TypeOf((*MockSession)(nil).Signals), arg0) +} + +// Stderr mocks base method. +func (m *MockSession) Stderr() io.ReadWriter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Stderr") + ret0, _ := ret[0].(io.ReadWriter) + return ret0 +} + +// Stderr indicates an expected call of Stderr. +func (mr *MockSessionMockRecorder) Stderr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stderr", reflect.TypeOf((*MockSession)(nil).Stderr)) +} + +// Subsystem mocks base method. +func (m *MockSession) Subsystem() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Subsystem") + ret0, _ := ret[0].(string) + return ret0 +} + +// Subsystem indicates an expected call of Subsystem. +func (mr *MockSessionMockRecorder) Subsystem() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subsystem", reflect.TypeOf((*MockSession)(nil).Subsystem)) +} + +// User mocks base method. +func (m *MockSession) User() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "User") + ret0, _ := ret[0].(string) + return ret0 +} + +// User indicates an expected call of User. +func (mr *MockSessionMockRecorder) User() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "User", reflect.TypeOf((*MockSession)(nil).User)) +} + +// Write mocks base method. +func (m *MockSession) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockSessionMockRecorder) Write(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockSession)(nil).Write), arg0) +} + +// MockContext is a mock of Context interface. +type MockContext struct { + ctrl *gomock.Controller + recorder *MockContextMockRecorder +} + +// MockContextMockRecorder is the mock recorder for MockContext. +type MockContextMockRecorder struct { + mock *MockContext +} + +// NewMockContext creates a new mock instance. +func NewMockContext(ctrl *gomock.Controller) *MockContext { + mock := &MockContext{ctrl: ctrl} + mock.recorder = &MockContextMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockContext) EXPECT() *MockContextMockRecorder { + return m.recorder +} + +// ClientVersion mocks base method. +func (m *MockContext) ClientVersion() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientVersion") + ret0, _ := ret[0].(string) + return ret0 +} + +// ClientVersion indicates an expected call of ClientVersion. +func (mr *MockContextMockRecorder) ClientVersion() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientVersion", reflect.TypeOf((*MockContext)(nil).ClientVersion)) +} + +// Deadline mocks base method. +func (m *MockContext) Deadline() (time.Time, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Deadline") + ret0, _ := ret[0].(time.Time) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// Deadline indicates an expected call of Deadline. +func (mr *MockContextMockRecorder) Deadline() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deadline", reflect.TypeOf((*MockContext)(nil).Deadline)) +} + +// Done mocks base method. +func (m *MockContext) Done() <-chan struct{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Done") + ret0, _ := ret[0].(<-chan struct{}) + return ret0 +} + +// Done indicates an expected call of Done. +func (mr *MockContextMockRecorder) Done() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Done", reflect.TypeOf((*MockContext)(nil).Done)) +} + +// Err mocks base method. +func (m *MockContext) Err() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Err") + ret0, _ := ret[0].(error) + return ret0 +} + +// Err indicates an expected call of Err. +func (mr *MockContextMockRecorder) Err() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Err", reflect.TypeOf((*MockContext)(nil).Err)) +} + +// LocalAddr mocks base method. +func (m *MockContext) LocalAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// LocalAddr indicates an expected call of LocalAddr. +func (mr *MockContextMockRecorder) LocalAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockContext)(nil).LocalAddr)) +} + +// Lock mocks base method. +func (m *MockContext) Lock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Lock") +} + +// Lock indicates an expected call of Lock. +func (mr *MockContextMockRecorder) Lock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockContext)(nil).Lock)) +} + +// Permissions mocks base method. +func (m *MockContext) Permissions() *ssh.Permissions { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Permissions") + ret0, _ := ret[0].(*ssh.Permissions) + return ret0 +} + +// Permissions indicates an expected call of Permissions. +func (mr *MockContextMockRecorder) Permissions() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Permissions", reflect.TypeOf((*MockContext)(nil).Permissions)) +} + +// RemoteAddr mocks base method. +func (m *MockContext) RemoteAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoteAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// RemoteAddr indicates an expected call of RemoteAddr. +func (mr *MockContextMockRecorder) RemoteAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockContext)(nil).RemoteAddr)) +} + +// ServerVersion mocks base method. +func (m *MockContext) ServerVersion() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServerVersion") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServerVersion indicates an expected call of ServerVersion. +func (mr *MockContextMockRecorder) ServerVersion() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServerVersion", reflect.TypeOf((*MockContext)(nil).ServerVersion)) +} + +// SessionID mocks base method. +func (m *MockContext) SessionID() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SessionID") + ret0, _ := ret[0].(string) + return ret0 +} + +// SessionID indicates an expected call of SessionID. +func (mr *MockContextMockRecorder) SessionID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SessionID", reflect.TypeOf((*MockContext)(nil).SessionID)) +} + +// SetValue mocks base method. +func (m *MockContext) SetValue(arg0, arg1 any) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetValue", arg0, arg1) +} + +// SetValue indicates an expected call of SetValue. +func (mr *MockContextMockRecorder) SetValue(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValue", reflect.TypeOf((*MockContext)(nil).SetValue), arg0, arg1) +} + +// Unlock mocks base method. +func (m *MockContext) Unlock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Unlock") +} + +// Unlock indicates an expected call of Unlock. +func (mr *MockContextMockRecorder) Unlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockContext)(nil).Unlock)) +} + +// User mocks base method. +func (m *MockContext) User() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "User") + ret0, _ := ret[0].(string) + return ret0 +} + +// User indicates an expected call of User. +func (mr *MockContextMockRecorder) User() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "User", reflect.TypeOf((*MockContext)(nil).User)) +} + +// Value mocks base method. +func (m *MockContext) Value(arg0 any) any { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Value", arg0) + ret0, _ := ret[0].(any) + return ret0 +} + +// Value indicates an expected call of Value. +func (mr *MockContextMockRecorder) Value(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Value", reflect.TypeOf((*MockContext)(nil).Value), arg0) +} diff --git a/internal/sshtoken/sshtoken_mock_test.go b/internal/sshtoken/sshtoken_mock_test.go new file mode 100644 index 0000000..8d746e7 --- /dev/null +++ b/internal/sshtoken/sshtoken_mock_test.go @@ -0,0 +1,158 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/uselagoon/ssh-portal/internal/sshtoken (interfaces: LagoonDBService,KeycloakTokenService) +// +// Generated by this command: +// +// mockgen -package=sshtoken_test -destination=sshtoken_mock_test.go -write_generate_directive . LagoonDBService,KeycloakTokenService +// + +// Package sshtoken_test is a generated GoMock package. +package sshtoken_test + +import ( + context "context" + reflect "reflect" + time "time" + + uuid "github.com/google/uuid" + lagoondb "github.com/uselagoon/ssh-portal/internal/lagoondb" + gomock "go.uber.org/mock/gomock" +) + +//go:generate mockgen -package=sshtoken_test -destination=sshtoken_mock_test.go -write_generate_directive . LagoonDBService,KeycloakTokenService + +// MockLagoonDBService is a mock of LagoonDBService interface. +type MockLagoonDBService struct { + ctrl *gomock.Controller + recorder *MockLagoonDBServiceMockRecorder +} + +// MockLagoonDBServiceMockRecorder is the mock recorder for MockLagoonDBService. +type MockLagoonDBServiceMockRecorder struct { + mock *MockLagoonDBService +} + +// NewMockLagoonDBService creates a new mock instance. +func NewMockLagoonDBService(ctrl *gomock.Controller) *MockLagoonDBService { + mock := &MockLagoonDBService{ctrl: ctrl} + mock.recorder = &MockLagoonDBServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLagoonDBService) EXPECT() *MockLagoonDBServiceMockRecorder { + return m.recorder +} + +// EnvironmentByNamespaceName mocks base method. +func (m *MockLagoonDBService) EnvironmentByNamespaceName(arg0 context.Context, arg1 string) (*lagoondb.Environment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnvironmentByNamespaceName", arg0, arg1) + ret0, _ := ret[0].(*lagoondb.Environment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnvironmentByNamespaceName indicates an expected call of EnvironmentByNamespaceName. +func (mr *MockLagoonDBServiceMockRecorder) EnvironmentByNamespaceName(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnvironmentByNamespaceName", reflect.TypeOf((*MockLagoonDBService)(nil).EnvironmentByNamespaceName), arg0, arg1) +} + +// SSHEndpointByEnvironmentID mocks base method. +func (m *MockLagoonDBService) SSHEndpointByEnvironmentID(arg0 context.Context, arg1 int) (string, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSHEndpointByEnvironmentID", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// SSHEndpointByEnvironmentID indicates an expected call of SSHEndpointByEnvironmentID. +func (mr *MockLagoonDBServiceMockRecorder) SSHEndpointByEnvironmentID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSHEndpointByEnvironmentID", reflect.TypeOf((*MockLagoonDBService)(nil).SSHEndpointByEnvironmentID), arg0, arg1) +} + +// SSHKeyUsed mocks base method. +func (m *MockLagoonDBService) SSHKeyUsed(arg0 context.Context, arg1 string, arg2 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSHKeyUsed", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// SSHKeyUsed indicates an expected call of SSHKeyUsed. +func (mr *MockLagoonDBServiceMockRecorder) SSHKeyUsed(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSHKeyUsed", reflect.TypeOf((*MockLagoonDBService)(nil).SSHKeyUsed), arg0, arg1, arg2) +} + +// UserBySSHFingerprint mocks base method. +func (m *MockLagoonDBService) UserBySSHFingerprint(arg0 context.Context, arg1 string) (*lagoondb.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UserBySSHFingerprint", arg0, arg1) + ret0, _ := ret[0].(*lagoondb.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UserBySSHFingerprint indicates an expected call of UserBySSHFingerprint. +func (mr *MockLagoonDBServiceMockRecorder) UserBySSHFingerprint(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserBySSHFingerprint", reflect.TypeOf((*MockLagoonDBService)(nil).UserBySSHFingerprint), arg0, arg1) +} + +// MockKeycloakTokenService is a mock of KeycloakTokenService interface. +type MockKeycloakTokenService struct { + ctrl *gomock.Controller + recorder *MockKeycloakTokenServiceMockRecorder +} + +// MockKeycloakTokenServiceMockRecorder is the mock recorder for MockKeycloakTokenService. +type MockKeycloakTokenServiceMockRecorder struct { + mock *MockKeycloakTokenService +} + +// NewMockKeycloakTokenService creates a new mock instance. +func NewMockKeycloakTokenService(ctrl *gomock.Controller) *MockKeycloakTokenService { + mock := &MockKeycloakTokenService{ctrl: ctrl} + mock.recorder = &MockKeycloakTokenServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockKeycloakTokenService) EXPECT() *MockKeycloakTokenServiceMockRecorder { + return m.recorder +} + +// UserAccessToken mocks base method. +func (m *MockKeycloakTokenService) UserAccessToken(arg0 context.Context, arg1 uuid.UUID) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UserAccessToken", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UserAccessToken indicates an expected call of UserAccessToken. +func (mr *MockKeycloakTokenServiceMockRecorder) UserAccessToken(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserAccessToken", reflect.TypeOf((*MockKeycloakTokenService)(nil).UserAccessToken), arg0, arg1) +} + +// UserAccessTokenResponse mocks base method. +func (m *MockKeycloakTokenService) UserAccessTokenResponse(arg0 context.Context, arg1 uuid.UUID) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UserAccessTokenResponse", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UserAccessTokenResponse indicates an expected call of UserAccessTokenResponse. +func (mr *MockKeycloakTokenServiceMockRecorder) UserAccessTokenResponse(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserAccessTokenResponse", reflect.TypeOf((*MockKeycloakTokenService)(nil).UserAccessTokenResponse), arg0, arg1) +}