From 49db7578a7aef0c55e6ee809a545e0618d621a09 Mon Sep 17 00:00:00 2001
From: Erik Brakkee
Date: Sat, 3 Aug 2024 18:29:14 +0200
Subject: [PATCH] large parts of the usage is now dynamic. Still need to
generate instructions for authorized keys. A lot of troubleshooting for the
form to cookie persistence.
---
cmd/converge/pagehandler.go | 3 +-
cmd/converge/usage.go | 35 ++-
pkg/server/templates/about.templ | 43 ++++
pkg/server/templates/constants.go | 4 +
pkg/server/templates/usage.templ | 322 +++++++++++++---------------
pkg/server/templates/usageinputs.go | 23 ++
6 files changed, 255 insertions(+), 175 deletions(-)
create mode 100644 pkg/server/templates/usageinputs.go
diff --git a/cmd/converge/pagehandler.go b/cmd/converge/pagehandler.go
index bdc256e..bcc46d7 100644
--- a/cmd/converge/pagehandler.go
+++ b/cmd/converge/pagehandler.go
@@ -3,11 +3,10 @@ package main
import (
templates2 "converge/pkg/server/templates"
"net/http"
- "os"
)
func pageHandler(w http.ResponseWriter, r *http.Request) {
- username, _ := os.LookupEnv("CONVERGE_USERNAME")
+ username := getAgentSshUser()
access := getConvergeAccess(r, username)
switch r.URL.Path {
diff --git a/cmd/converge/usage.go b/cmd/converge/usage.go
index 63304ff..dc53a2a 100644
--- a/cmd/converge/usage.go
+++ b/cmd/converge/usage.go
@@ -1,9 +1,12 @@
package main
import (
+ "converge/pkg/server/templates"
"log"
+ "math/rand"
"net/http"
- "time"
+ "os"
+ "strconv"
)
func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
@@ -14,11 +17,31 @@ func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Error parsing form", http.StatusBadRequest)
return
}
- remote_shells := r.Form["remote-shell"]
- local_shells := r.Form["local-shhell"]
+ ids := r.Form["rendez-vous-id"]
+ id := ""
+ if len(ids) > 0 {
+ id = ids[0]
+ }
+ if id == "" {
+ id = strconv.Itoa(rand.Int() % 1000000)
+ }
+ remoteShells := r.Form["remote-shell"]
+ localShells := r.Form["local-shell"]
keys := r.FormValue("ssh-keys")
- log.Printf("remote_shells %v", remote_shells)
- log.Printf("local_shells %v", local_shells)
+ log.Printf("remote_shells %v", remoteShells)
+ log.Printf("local_shells %v", localShells)
log.Printf("ssh-keys %v", keys)
- w.Write([]byte(time.Now().Format(time.DateTime)))
+
+ access := getConvergeAccess(r, getAgentSshUser())
+
+ usageInputs := templates.NewUsageInputs(id, remoteShells, localShells)
+ err = templates.ShellUsage(access, usageInputs).Render(r.Context(), w)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+}
+
+func getAgentSshUser() string {
+ username, _ := os.LookupEnv("CONVERGE_USERNAME")
+ return username
}
diff --git a/pkg/server/templates/about.templ b/pkg/server/templates/about.templ
index dddd15b..90ee0bf 100644
--- a/pkg/server/templates/about.templ
+++ b/pkg/server/templates/about.templ
@@ -39,6 +39,49 @@ templ About() {
When the timeout of a session is near the user is informed about this with messages
in the shell.
+
+ end-to-end encryoption
+ ssh keys
+ agent options
+ client access
+
+ Local clients: using ssh with a proxy command
+
+
+ wsproxy
is a command that can be used as a proxy command for SSH which performs the connection to the
+ remote server. This command needs to be downloaded only once (see downloads). It does not depend on
+ the converge implementation but only on the websocket standards. Other tools that
+ provide a mapping of stdio to a websocket can also be used instead of wsproxy.
+
+
+ Local clients: using SSH with a local TCP forwarding proxy
+
+
+ This option is less convenient than the proxy command because it requires two separate
+ commands to execute.
+
+
+
+ Local clients can connect using regular ssh and sftp commands through a tunnel that
+ translates a local TCP port to a websocket connection in converge. See
+ the downloads section.
+ This runs a local client that allows SSH to port 10000 and connects to converge using
+ a websocket connection.
+
+
+ Remote shell usage
+
+
+ The agent supports a --shells command-line option by which a comma-separated
+ list of shells can be prepended to the default search path for shells, e.g.
+ --shells zsh,csh,sh
(linux) or cmd,powershell
for
+ windows.
+
+
+
+ The agent sets a agentdir environment variable that points to
+ the directory where the agent is running.
+
}
diff --git a/pkg/server/templates/constants.go b/pkg/server/templates/constants.go
index dac8432..6bf968a 100644
--- a/pkg/server/templates/constants.go
+++ b/pkg/server/templates/constants.go
@@ -1 +1,5 @@
package templates
+
+const BASH = "*.sh"
+const CMD = "cmd"
+const POWERSHELL = "powershell"
diff --git a/pkg/server/templates/usage.templ b/pkg/server/templates/usage.templ
index 4fe25d8..2c830e3 100644
--- a/pkg/server/templates/usage.templ
+++ b/pkg/server/templates/usage.templ
@@ -2,10 +2,118 @@ package templates
import "converge/pkg/models"
+
+templ AgentUsage(access models.ConvergeAccess, shells map[string]bool, usageInputs UsageInputs) {
+
+
Downloading and running the agent
+
+
+ This is what you run on a remote server, typically in your continuous integration job.
+
+
+ if shells[BASH] {
+
{`
+ `}curl http{access.Secure}://{access.HostPort}/static/agent > agent{`
+ chmod 755 agent
+ `}./agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
+ rm -f agent
+ `}
+ }
+ if shells[CMD] || shells[POWERSHELL] {
+
{`
+ `}curl http{access.Secure}://{access.HostPort}/static/agent.exe > agent.exe{`
+ `}agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
+ del agent.exe
+ `}
+ }
+
+
+ The agent has more command-line options than shown here.
+ Download the agent and run it without arguments to
+ see all options.
+
+
+ Tip: Run the above command locally in a similar shell (for instance in a
+ docker container) then try to connect to the shell using the commands below. If that
+ works, then you are all set.
+
+
+
+
Connecting to the agent
+
+
{`
+ `}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}" { access.Username }{"@localhost"} {`
+ `}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}" { access.Username }{"@localhost"} {`
+ `}
+
+
Working with the agent
+
+ if shells[BASH] {
+
{`
+ # cd back to the agent directory
+ cd $agentdir
+
+ # prevent logout when last user exits
+ touch $agentdir/.hold
+ `}
+
+ }
+ if shells[CMD] {
+
{`
+ # cd back to the agent directory
+ cd %agentdir%
+
+ # prevent logout when last user exits
+ echo > %agentdir%\.hold
+ `}
+ }
+ if shells[POWERSHELL] {
+
{`
+ # cd back to the agent directory
+ cd $env:agentdir
+
+ # prevent logout when last user exits
+ $null > $env:agentdir\.hold
+ `}
+ }
+ if shells[CMD] || shells[POWERSHELL] {
+
windows
+
+ NOTE: When running the agent on windows, an exit of the remote session using
+ exit in powershell or command prompt does not terminate the shell completely.
+ To terminate the client ssh session must be killed by closing the terminal.
+ Cleanup of remote processes on the agent appears to work properly despite this
+ problem. It is just that exit of the windows shell (powershell or command prompt)
+ is not detected properly.
+
+ }
+
+}
+
+templ LocalShellUsage(access models.ConvergeAccess, shells map[string]bool, usageInput UsageInputs) {
+
+ if shells[BASH] {
+
bash
+ }
+ if shells[CMD] {
+
cmd
+ }
+ if shells[POWERSHELL] {
+
powershell
+ }
+
+}
+
+templ ShellUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
+
+ @AgentUsage(access, usageInputs.RemoteShells, usageInputs)
+
+ @LocalShellUsage(access, usageInputs.LocalShells, usageInputs)
+}
+
+
templ Usage(access models.ConvergeAccess) {
-
-
Usage