Skip to content

Commit e047efa

Browse files
Merge pull request from GHSA-c8xw-vjgf-94hr
Signed-off-by: pashakostohrys <[email protected]>
1 parent f978e04 commit e047efa

File tree

5 files changed

+132
-15
lines changed

5 files changed

+132
-15
lines changed

server/application/terminal.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"time"
88

9+
util_session "github.com/argoproj/argo-cd/v2/util/session"
910
"github.com/argoproj/gitops-engine/pkg/utils/kube"
1011
log "github.com/sirupsen/logrus"
1112
v1 "k8s.io/api/core/v1"
@@ -37,11 +38,12 @@ type terminalHandler struct {
3738
allowedShells []string
3839
namespace string
3940
enabledNamespaces []string
41+
sessionManager util_session.SessionManager
4042
}
4143

4244
// NewHandler returns a new terminal handler.
4345
func NewHandler(appLister applisters.ApplicationLister, namespace string, enabledNamespaces []string, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
44-
appResourceTree AppResourceTreeFn, allowedShells []string) *terminalHandler {
46+
appResourceTree AppResourceTreeFn, allowedShells []string, sessionManager util_session.SessionManager) *terminalHandler {
4547
return &terminalHandler{
4648
appLister: appLister,
4749
db: db,
@@ -51,6 +53,7 @@ func NewHandler(appLister applisters.ApplicationLister, namespace string, enable
5153
allowedShells: allowedShells,
5254
namespace: namespace,
5355
enabledNamespaces: enabledNamespaces,
56+
sessionManager: sessionManager,
5457
}
5558
}
5659

@@ -222,7 +225,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
222225

223226
fieldLog.Info("terminal session starting")
224227

225-
session, err := newTerminalSession(w, r, nil)
228+
session, err := newTerminalSession(w, r, nil, s.sessionManager)
226229
if err != nil {
227230
http.Error(w, "Failed to start terminal session", http.StatusBadRequest)
228231
return
@@ -282,6 +285,11 @@ type TerminalMessage struct {
282285
Cols uint16 `json:"cols"`
283286
}
284287

288+
// TerminalCommand is the struct for websocket commands,For example you need ask client to reconnect
289+
type TerminalCommand struct {
290+
Code int
291+
}
292+
285293
// startProcess executes specified commands in the container and connects it up with the ptyHandler (a session)
286294
func startProcess(k8sClient kubernetes.Interface, cfg *rest.Config, namespace, podName, containerName string, cmd []string, ptyHandler PtyHandler) error {
287295
req := k8sClient.CoreV1().RESTClient().Post().

server/application/websocket.go

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package application
33
import (
44
"encoding/json"
55
"fmt"
6+
"github.com/argoproj/argo-cd/v2/common"
7+
httputil "github.com/argoproj/argo-cd/v2/util/http"
8+
util_session "github.com/argoproj/argo-cd/v2/util/session"
69
"net/http"
710
"sync"
811
"time"
@@ -12,6 +15,11 @@ import (
1215
"k8s.io/client-go/tools/remotecommand"
1316
)
1417

18+
const (
19+
ReconnectCode = 1
20+
ReconnectMessage = "\nReconnect because the token was refreshed...\n"
21+
)
22+
1523
var upgrader = func() websocket.Upgrader {
1624
upgrader := websocket.Upgrader{}
1725
upgrader.HandshakeTimeout = time.Second * 2
@@ -23,25 +31,40 @@ var upgrader = func() websocket.Upgrader {
2331

2432
// terminalSession implements PtyHandler
2533
type terminalSession struct {
26-
wsConn *websocket.Conn
27-
sizeChan chan remotecommand.TerminalSize
28-
doneChan chan struct{}
29-
tty bool
30-
readLock sync.Mutex
31-
writeLock sync.Mutex
34+
wsConn *websocket.Conn
35+
sizeChan chan remotecommand.TerminalSize
36+
doneChan chan struct{}
37+
tty bool
38+
readLock sync.Mutex
39+
writeLock sync.Mutex
40+
sessionManager util_session.SessionManager
41+
token *string
42+
}
43+
44+
// getToken get auth token from web socket request
45+
func getToken(r *http.Request) (string, error) {
46+
cookies := r.Cookies()
47+
return httputil.JoinCookies(common.AuthCookieName, cookies)
3248
}
3349

3450
// newTerminalSession create terminalSession
35-
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*terminalSession, error) {
51+
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager util_session.SessionManager) (*terminalSession, error) {
52+
token, err := getToken(r)
53+
if err != nil {
54+
return nil, err
55+
}
56+
3657
conn, err := upgrader.Upgrade(w, r, responseHeader)
3758
if err != nil {
3859
return nil, err
3960
}
4061
session := &terminalSession{
41-
wsConn: conn,
42-
tty: true,
43-
sizeChan: make(chan remotecommand.TerminalSize),
44-
doneChan: make(chan struct{}),
62+
wsConn: conn,
63+
tty: true,
64+
sizeChan: make(chan remotecommand.TerminalSize),
65+
doneChan: make(chan struct{}),
66+
sessionManager: sessionManager,
67+
token: &token,
4568
}
4669
return session, nil
4770
}
@@ -78,8 +101,40 @@ func (t *terminalSession) Next() *remotecommand.TerminalSize {
78101
}
79102
}
80103

104+
// reconnect send reconnect code to client and ask them init new ws session
105+
func (t *terminalSession) reconnect() (int, error) {
106+
reconnectCommand, _ := json.Marshal(TerminalCommand{
107+
Code: ReconnectCode,
108+
})
109+
reconnectMessage, _ := json.Marshal(TerminalMessage{
110+
Operation: "stdout",
111+
Data: ReconnectMessage,
112+
})
113+
t.writeLock.Lock()
114+
err := t.wsConn.WriteMessage(websocket.TextMessage, reconnectMessage)
115+
if err != nil {
116+
log.Errorf("write message err: %v", err)
117+
return 0, err
118+
}
119+
err = t.wsConn.WriteMessage(websocket.TextMessage, reconnectCommand)
120+
if err != nil {
121+
log.Errorf("write message err: %v", err)
122+
return 0, err
123+
}
124+
t.writeLock.Unlock()
125+
return 0, nil
126+
}
127+
81128
// Read called in a loop from remotecommand as long as the process is running
82129
func (t *terminalSession) Read(p []byte) (int, error) {
130+
// check if token still valid
131+
_, newToken, err := t.sessionManager.VerifyToken(*t.token)
132+
// err in case if token is revoked, newToken in case if refresh happened
133+
if err != nil || newToken != "" {
134+
// need to send reconnect code in case if token was refreshed
135+
return t.reconnect()
136+
}
137+
83138
t.readLock.Lock()
84139
_, message, err := t.wsConn.ReadMessage()
85140
t.readLock.Unlock()

server/application/websocket_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package application
2+
3+
import (
4+
"encoding/json"
5+
"github.com/gorilla/websocket"
6+
"github.com/stretchr/testify/assert"
7+
"net/http"
8+
"net/http/httptest"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func reconnect(w http.ResponseWriter, r *http.Request) {
14+
var upgrader = websocket.Upgrader{}
15+
c, err := upgrader.Upgrade(w, r, nil)
16+
if err != nil {
17+
return
18+
}
19+
20+
ts := terminalSession{wsConn: c}
21+
_, _ = ts.reconnect()
22+
}
23+
24+
func TestReconnect(t *testing.T) {
25+
26+
s := httptest.NewServer(http.HandlerFunc(reconnect))
27+
defer s.Close()
28+
29+
u := "ws" + strings.TrimPrefix(s.URL, "http")
30+
31+
// Connect to the server
32+
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
33+
assert.NoError(t, err)
34+
35+
defer ws.Close()
36+
37+
_, p, _ := ws.ReadMessage()
38+
39+
var message TerminalMessage
40+
41+
err = json.Unmarshal(p, &message)
42+
43+
assert.NoError(t, err)
44+
assert.Equal(t, message.Data, ReconnectMessage)
45+
46+
}

server/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
975975
}
976976
mux.Handle("/api/", handler)
977977

978-
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells).
978+
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, *a.sessionMgr).
979979
WithFeatureFlagMiddleware(a.settingsMgr.GetSettings)
980980
th := util_session.WithAuthMiddleware(a.DisableAuth, a.sessionMgr, terminal)
981981
mux.Handle("/terminal", th)
@@ -988,6 +988,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
988988
// will be added in mux.
989989
registerExtensions(mux, a)
990990
}
991+
991992
mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandler, ctx, gwmux, conn)
992993
mustRegisterGWHandler(clusterpkg.RegisterClusterServiceHandler, ctx, gwmux, conn)
993994
mustRegisterGWHandler(applicationpkg.RegisterApplicationServiceHandler, ctx, gwmux, conn)

ui/src/app/applications/components/pod-terminal-viewer/pod-terminal-viewer.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,14 @@ export const PodTerminalViewer: React.FC<PodTerminalViewerProps> = ({
7272

7373
const onConnectionMessage = (e: MessageEvent) => {
7474
const msg = JSON.parse(e.data);
75-
connSubject.next(msg);
75+
if (!msg?.Code) {
76+
connSubject.next(msg);
77+
} else {
78+
// Do reconnect due to refresh token event
79+
onConnectionClose();
80+
setupConnection()
81+
}
82+
7683
};
7784

7885
const onConnectionOpen = () => {

0 commit comments

Comments
 (0)