* session will now expire some time after last user
activity and updated documentation. * downloads will now download again. Because of hx-boost the downloads where rendered in the browser. Now disabling hx-boost for the downloads section. * relative link for sessions page
This commit is contained in:
parent
5a91d86b39
commit
21463a5cad
@ -60,21 +60,40 @@ func SftpHandler(sess ssh.Session) {
|
||||
}
|
||||
}
|
||||
|
||||
type UserActivityDetector struct {
|
||||
session io.ReadWriter
|
||||
}
|
||||
|
||||
func (user UserActivityDetector) Read(p []byte) (int, error) {
|
||||
n, err := user.session.Read(p)
|
||||
if err == nil && n > 0 {
|
||||
session.UserActivityDetected()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (user UserActivityDetector) Write(p []byte) (int, error) {
|
||||
return user.session.Write(p)
|
||||
}
|
||||
|
||||
func sshServer(hostKeyFile string, shellCommand string,
|
||||
passwordHandler ssh.PasswordHandler,
|
||||
authorizedPublicKeys AuthorizedPublicKeys) *ssh.Server {
|
||||
ssh.Handle(func(s ssh.Session) {
|
||||
ssh.Handle(func(sshSession ssh.Session) {
|
||||
workingDirectory, _ := os.Getwd()
|
||||
env := append(os.Environ(), fmt.Sprintf("agentdir=%s", workingDirectory))
|
||||
process, err := terminal.PtySpawner.Start(s, env, shellCommand)
|
||||
process, err := terminal.PtySpawner.Start(sshSession, env, shellCommand)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sessionInfo := comms.NewSessionInfo(
|
||||
s.LocalAddr().String(), "ssh",
|
||||
sshSession.LocalAddr().String(), "ssh",
|
||||
)
|
||||
session.Login(sessionInfo, s)
|
||||
iowrappers.SynchronizeStreams("shell -- ssh", process.Pipe(), s)
|
||||
session.Login(sessionInfo, sshSession)
|
||||
activityDetector := UserActivityDetector{
|
||||
session: sshSession,
|
||||
}
|
||||
iowrappers.SynchronizeStreams("shell -- ssh", process.Pipe(), activityDetector)
|
||||
session.LogOut(sessionInfo.ClientId)
|
||||
// will cause addition goroutines to remmain alive when the SSH
|
||||
// session is killed. For now acceptable since the agent is a short-lived
|
||||
@ -171,8 +190,8 @@ func printHelp(msg string) {
|
||||
" password based access is possible. The password is configured on the converge server\n" +
|
||||
"--authorized-keys: SSH authorized keys file in openssh format. By default .authorized_keys in the\n" +
|
||||
" directory where the agent is started is used.\n" +
|
||||
"--warning-time: advance warning time before sessio ends\n" +
|
||||
"--expiry-time: expiry time of the session\n" +
|
||||
"--warning-time: advance warning time before sessio ends (default '5m')\n" +
|
||||
"--expiry-time: expiry time of the session (default '10m')\n" +
|
||||
"--check-interval: interval at which expiry is checked\n" +
|
||||
"--insecure: allow invalid certificates\n" +
|
||||
"--shells: comma-separated list of shells to add to the front of theshell search path\n" +
|
||||
@ -191,12 +210,13 @@ func getArg(args []string) (value string, ret []string) {
|
||||
return args[1], args[1:]
|
||||
}
|
||||
|
||||
func parseDuration(args []string, val string) (time.Duration, []string) {
|
||||
duration, err := time.ParseDuration(val)
|
||||
func parseDuration(args []string) (time.Duration, []string) {
|
||||
arg, args := getArg(args)
|
||||
duration, err := time.ParseDuration(arg)
|
||||
if err != nil {
|
||||
printHelp(fmt.Sprintf("Error parsing duration: %v\n", err))
|
||||
}
|
||||
return duration, args[1:]
|
||||
return duration, args
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -224,18 +244,17 @@ func main() {
|
||||
additionalShells := []string{}
|
||||
commaSeparated := ""
|
||||
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
|
||||
val := ""
|
||||
switch args[0] {
|
||||
case "--id":
|
||||
id, args = getArg(args)
|
||||
case "--authorized-keys":
|
||||
authorizedKeysFile, args = getArg(args)
|
||||
case "--warning-time":
|
||||
advanceWarningTime, args = parseDuration(args, val)
|
||||
advanceWarningTime, args = parseDuration(args)
|
||||
case "--expiry-time":
|
||||
agentExpriryTime, args = parseDuration(args, val)
|
||||
agentExpriryTime, args = parseDuration(args)
|
||||
case "--check-interval":
|
||||
tickerInterval, args = parseDuration(args, val)
|
||||
tickerInterval, args = parseDuration(args)
|
||||
case "--insecure":
|
||||
insecure = true
|
||||
case "--shells":
|
||||
@ -247,6 +266,13 @@ func main() {
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
if 2*advanceWarningTime > agentExpriryTime {
|
||||
printHelp("The warning time should be at most half the expiry time")
|
||||
}
|
||||
if 4*tickerInterval > agentExpriryTime {
|
||||
printHelp("The check interval should be at most 1/4 of the agent interval")
|
||||
}
|
||||
|
||||
shells = append(additionalShells, shells...)
|
||||
|
||||
id = getId(id)
|
||||
|
@ -1,5 +1,10 @@
|
||||
|
||||
You can extend expiry of the session using
|
||||
The session is automatically extended with %v every time the user is active.
|
||||
If you get a warning that expiry of the session is near then simply pressing
|
||||
any key on the keyboard will extend it.
|
||||
|
||||
If the last users logs out, then by default the agent will exit with status 0.
|
||||
To prevent this, create a .hold file in the agent directory as follows:
|
||||
|
||||
{{ if eq .os "windows" -}}
|
||||
# cmd
|
||||
@ -11,9 +16,6 @@ You can extend expiry of the session using
|
||||
{{- end }}
|
||||
|
||||
The expiry time is equal to the modification time of the .hold
|
||||
file with the expiry duration (%v) added.
|
||||
|
||||
To prevent the agent from exiting after the last session is gone,
|
||||
also use the above command in any shell.
|
||||
|
||||
|
||||
file with the expiry duration added. By creating a .hold
|
||||
file with a timestamp in the future, the session can be extended
|
||||
beyond the default session timeout.
|
||||
|
@ -33,13 +33,14 @@ type AgentState struct {
|
||||
startTime time.Time
|
||||
|
||||
// Advance warning time to notify the user of something important happening
|
||||
advanceWarningTime time.Duration
|
||||
advanceWarningDuration time.Duration
|
||||
|
||||
// session expiry time
|
||||
agentExpriryTime time.Duration
|
||||
agentExpiryDuration time.Duration
|
||||
|
||||
// Last expiry time reported to the user.
|
||||
lastExpiryTimmeReported time.Time
|
||||
lastExpiryTimeReported time.Time
|
||||
expiryIsNear bool
|
||||
|
||||
// ticker
|
||||
tickerInterval time.Duration
|
||||
@ -48,7 +49,7 @@ type AgentState struct {
|
||||
// map of unique session id to a session
|
||||
clients map[string]*AgentSession
|
||||
|
||||
lastUserLoginTime time.Time
|
||||
lastUserActivityTime time.Time
|
||||
agentUsed bool
|
||||
}
|
||||
|
||||
@ -84,19 +85,18 @@ func ConfigureAgent(commChannel comms.CommChannel,
|
||||
state = AgentState{
|
||||
commChannel: commChannel,
|
||||
startTime: time.Now(),
|
||||
advanceWarningTime: advanceWarningTime,
|
||||
agentExpriryTime: agentExpiryTime,
|
||||
lastExpiryTimmeReported: time.Time{},
|
||||
advanceWarningDuration: advanceWarningTime,
|
||||
agentExpiryDuration: agentExpiryTime,
|
||||
lastExpiryTimeReported: time.Time{},
|
||||
tickerInterval: tickerInterval,
|
||||
ticker: time.NewTicker(tickerInterval),
|
||||
clients: make(map[string]*AgentSession),
|
||||
|
||||
lastUserLoginTime: time.Time{},
|
||||
lastUserActivityTime: time.Time{},
|
||||
agentUsed: false,
|
||||
}
|
||||
|
||||
log.Printf("Agent expires at %s",
|
||||
state.expiryTime(holdFilename).Format(time.DateTime))
|
||||
log.Printf("Agent expiry duration is %v", state.agentExpiryDuration)
|
||||
|
||||
comms.Send(state.commChannel.SideChannel,
|
||||
comms.ConvergeMessage{
|
||||
@ -131,8 +131,23 @@ func LogOut(clientId string) {
|
||||
}
|
||||
}
|
||||
|
||||
func UserActivityDetected() {
|
||||
events <- func() {
|
||||
userActivityDetected()
|
||||
}
|
||||
}
|
||||
|
||||
// Internal interface synchronous
|
||||
|
||||
func userActivityDetected() {
|
||||
state.lastUserActivityTime = time.Now()
|
||||
if state.expiryIsNear {
|
||||
messageUsers("User activity detected, session extended.")
|
||||
sendExpiryTimeUpdateEvent()
|
||||
state.expiryIsNear = false
|
||||
}
|
||||
}
|
||||
|
||||
func monitorHoldFile() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
@ -200,7 +215,7 @@ func login(sessionInfo comms.SessionInfo, sshSession ssh.Session) {
|
||||
sshSession: sshSession,
|
||||
}
|
||||
state.clients[sessionInfo.ClientId] = &agentSession
|
||||
state.lastUserLoginTime = time.Now()
|
||||
state.lastUserActivityTime = time.Now()
|
||||
state.agentUsed = true
|
||||
|
||||
err := comms.SendWithTimeout(state.commChannel.SideChannel,
|
||||
@ -218,7 +233,7 @@ func login(sessionInfo comms.SessionInfo, sshSession ssh.Session) {
|
||||
|
||||
func printHelpMessage(sshSession ssh.Session) {
|
||||
printMessage(sshSession, fmt.Sprintf(helpMessage,
|
||||
state.agentExpriryTime))
|
||||
state.agentExpiryDuration))
|
||||
}
|
||||
|
||||
func formatHelpMessage() string {
|
||||
@ -279,14 +294,14 @@ func fileExists(filename string) bool {
|
||||
|
||||
func (state *AgentState) expiryTime(filename string) time.Time {
|
||||
if !state.agentUsed {
|
||||
return state.startTime.Add(state.agentExpriryTime)
|
||||
return state.startTime.Add(state.agentExpiryDuration)
|
||||
}
|
||||
expiryTime := time.Time{}
|
||||
stats, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
expiryTime = stats.ModTime().Add(state.agentExpriryTime)
|
||||
expiryTime = stats.ModTime().Add(state.agentExpiryDuration)
|
||||
}
|
||||
userLoginBaseExpiryTime := state.lastUserLoginTime.Add(state.agentExpriryTime)
|
||||
userLoginBaseExpiryTime := state.lastUserActivityTime.Add(state.agentExpiryDuration)
|
||||
if userLoginBaseExpiryTime.After(expiryTime) {
|
||||
expiryTime = userLoginBaseExpiryTime
|
||||
}
|
||||
@ -294,18 +309,22 @@ func (state *AgentState) expiryTime(filename string) time.Time {
|
||||
}
|
||||
|
||||
func holdFileChange() {
|
||||
newExpiryTIme := state.expiryTime(holdFilename)
|
||||
if newExpiryTIme != state.lastExpiryTimmeReported {
|
||||
newExpiryTime := state.expiryTime(holdFilename)
|
||||
if newExpiryTime != state.lastExpiryTimeReported {
|
||||
message := fmt.Sprintf("Expiry time of session is now %s\n",
|
||||
newExpiryTIme.Format(time.DateTime))
|
||||
newExpiryTime.Format(time.DateTime))
|
||||
message += holdFileMessage()
|
||||
messageUsers(message)
|
||||
state.lastExpiryTimmeReported = newExpiryTIme
|
||||
comms.Send(state.commChannel.SideChannel,
|
||||
state.lastExpiryTimeReported = newExpiryTime
|
||||
sendExpiryTimeUpdateEvent()
|
||||
}
|
||||
}
|
||||
|
||||
func sendExpiryTimeUpdateEvent() error {
|
||||
return comms.Send(state.commChannel.SideChannel,
|
||||
comms.ConvergeMessage{
|
||||
Value: comms.NewExpiryTimeUpdate(state.expiryTime(holdFilename)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Behavior to implement
|
||||
@ -329,18 +348,21 @@ func check() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if expiryTime.Sub(now) < state.advanceWarningTime {
|
||||
state.expiryIsNear = expiryTime.Sub(now) < state.advanceWarningDuration
|
||||
if state.expiryIsNear {
|
||||
messageUsers(
|
||||
fmt.Sprintf("Session will expire at %s", expiryTime.Format(time.DateTime)))
|
||||
for _, session := range state.clients {
|
||||
printHelpMessage(session.sshSession)
|
||||
}
|
||||
fmt.Sprintf("Session will expire at %s, press any key to extend it.", expiryTime.Format(time.DateTime)))
|
||||
//for _, session := range state.clients {
|
||||
// printHelpMessage(session.sshSession)
|
||||
//}
|
||||
}
|
||||
|
||||
if state.agentUsed && !fileExists(holdFilename) && len(state.clients) == 0 {
|
||||
log.Printf("All clients disconnected and no '%s' file found, exiting", holdFilename)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
sendExpiryTimeUpdateEvent()
|
||||
}
|
||||
|
||||
func messageUsers(message string) {
|
||||
|
@ -2,7 +2,7 @@ package templates
|
||||
|
||||
|
||||
templ Downloads() {
|
||||
<div>
|
||||
<div hx-boost="false">
|
||||
|
||||
<h1>downloads</h1>
|
||||
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
|
||||
templ Sessions(state *models.State, loc *time.Location) {
|
||||
<div hx-ext="ws" ws-connect="/ws/sessions">
|
||||
<div hx-ext="ws" ws-connect="../ws/sessions">
|
||||
<h1>sessions</h1>
|
||||
|
||||
<div id="status">
|
||||
|
@ -115,7 +115,7 @@ templ Usage(secure string, host string, username string) {
|
||||
# cd back to the agent directory
|
||||
cd $agentdir
|
||||
|
||||
# extend session lifetime
|
||||
# prevent logout when last user exits
|
||||
touch $agentdir/.hold
|
||||
`}</pre>
|
||||
|
||||
@ -128,7 +128,7 @@ templ Usage(secure string, host string, username string) {
|
||||
# cd back to the agent directory
|
||||
cd %agentdir%
|
||||
|
||||
# extend session lifetime
|
||||
# prevent logout when last user exits
|
||||
echo > %agentdir%\.hold
|
||||
`}</pre>
|
||||
|
||||
@ -138,7 +138,7 @@ templ Usage(secure string, host string, username string) {
|
||||
# cd back to the agent directory
|
||||
cd $env:agentdir
|
||||
|
||||
# extend session lifetime
|
||||
# prevent logout when last user exits
|
||||
$null > $env:agentdir\.hold
|
||||
`}</pre>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user