1
1
package channels
2
2
3
3
import (
4
- "bytes"
5
4
"io"
6
5
"sync"
7
6
@@ -14,6 +13,69 @@ import (
14
13
gossh "golang.org/x/crypto/ssh"
15
14
)
16
15
16
+ type Recorder struct {
17
+ queue chan string
18
+ channel gossh.Channel
19
+ }
20
+
21
+ func NewRecorder (sess * session.Session , camera * session.Camera , channel gossh.Channel ) io.WriteCloser {
22
+ queue := make (chan string , 100 )
23
+ go func () {
24
+ recording := true
25
+
26
+ for {
27
+ msg , ok := <- queue
28
+ if ! ok {
29
+ return
30
+ }
31
+
32
+ if ! recording {
33
+ continue
34
+ }
35
+
36
+ if err := camera .WriteFrame (& models.SessionRecorded { //nolint:errcheck
37
+ UID : sess .UID ,
38
+ Namespace : sess .Lookup ["domain" ],
39
+ Message : msg ,
40
+ Width : int (sess .Pty .Columns ),
41
+ Height : int (sess .Pty .Rows ),
42
+ }); err != nil {
43
+ log .WithError (err ).
44
+ WithFields (log.Fields {"session" : sess .UID , "sshid" : sess .SSHID }).
45
+ Warning ("failed to send the session frame to record" )
46
+
47
+ recording = false
48
+ }
49
+ }
50
+ }()
51
+
52
+ return & Recorder {
53
+ queue : queue ,
54
+ channel : channel ,
55
+ }
56
+ }
57
+
58
+ func (c * Recorder ) record (msg string ) {
59
+ c .queue <- msg
60
+ }
61
+
62
+ func (c * Recorder ) Write (data []byte ) (int , error ) {
63
+ read , err := c .channel .Write (data )
64
+ if err != nil {
65
+ return read , err
66
+ }
67
+
68
+ go c .record (string (data ))
69
+
70
+ return read , nil
71
+ }
72
+
73
+ func (c * Recorder ) Close () error {
74
+ close (c .queue )
75
+
76
+ return c .channel .CloseWrite ()
77
+ }
78
+
17
79
// pipe pipes data between client and agent, and vise versa, recoding each frame when ShellHub instance are Cloud or
18
80
// Enterprise.
19
81
func pipe (ctx gliderssh.Context , sess * session.Session , client gossh.Channel , agent gossh.Channel ) {
@@ -42,69 +104,19 @@ func pipe(ctx gliderssh.Context, sess *session.Session, client gossh.Channel, ag
42
104
defer wg .Done ()
43
105
defer client .CloseWrite () //nolint:errcheck
44
106
45
- // NOTE: As the copy required to record the session seem to be inefficient, if we don't have a record URL
46
- // defined, we use an [io.Copy] for the data piping between agent and client.
47
107
recordURL := ctx .Value ("RECORD_URL" ).(string )
48
108
if (envs .IsEnterprise () || envs .IsCloud ()) && recordURL != "" {
49
- // NOTE: Recoding variable is used to control if the frames will be recorded. If something wrong happens in
50
- // this process, to spare resources, we don't send frames anymore for this session.
51
- recording := true
52
-
53
109
camera , err := sess .Record (ctx , recordURL )
54
110
if err != nil {
55
111
log .WithError (err ).
56
112
WithFields (log.Fields {"session" : sess .UID , "sshid" : sess .SSHID , "record_url" : recordURL }).
57
113
Warning ("failed to connect to session record endpoint" )
58
-
59
- recording = false
60
114
}
61
115
62
116
defer camera .Close ()
63
117
64
- buffer := make ([]byte , 1024 )
65
- for {
66
- read , err := a .Read (buffer )
67
- // The occurrence of io.EOF is expected when the connection ends.
68
- // This indicates that we have reached the end of the input stream, and we need
69
- // to break out of the loop to handle the termination of the connection
70
- if err == io .EOF {
71
- break
72
- }
73
- // Unlike io.EOF, when 'err' is simply not nil, it signifies an unexpected error,
74
- // and we need to log to handle it appropriately.
75
- if err != nil {
76
- log .WithError (err ).
77
- WithFields (log.Fields {"session" : sess .UID , "sshid" : sess .SSHID }).
78
- Warning ("failed to read from stdout in pty client" )
79
-
80
- break
81
- }
82
-
83
- if _ , err = io .Copy (client , bytes .NewReader (buffer [:read ])); err != nil && err != io .EOF {
84
- log .WithError (err ).
85
- WithFields (log.Fields {"session" : sess .UID , "sshid" : sess .SSHID }).
86
- Warning ("failed to copy from stdout in pty client" )
87
-
88
- break
89
- }
90
-
91
- if recording {
92
- if err := camera .WriteFrame (& models.SessionRecorded { //nolint:errcheck
93
- UID : sess .UID ,
94
- Namespace : sess .Lookup ["domain" ],
95
- Message : string (buffer [:read ]),
96
- Width : int (sess .Pty .Columns ),
97
- Height : int (sess .Pty .Rows ),
98
- }); err != nil {
99
- log .WithError (err ).
100
- WithFields (log.Fields {"session" : sess .UID , "sshid" : sess .SSHID }).
101
- Warning ("failed to send the session frame to record" )
102
-
103
- recording = false
104
-
105
- continue
106
- }
107
- }
118
+ if _ , err := io .Copy (NewRecorder (sess , camera , client ), a ); err != nil && err != io .EOF {
119
+ log .WithError (err ).Error ("failed on coping data from client to agent" )
108
120
}
109
121
} else {
110
122
if _ , err := io .Copy (client , a ); err != nil && err != io .EOF {
0 commit comments