diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go
index 0332db1..1ea0f69 100755
--- a/cmd/agent/agent.go
+++ b/cmd/agent/agent.go
@@ -291,6 +291,16 @@ func main() {
if insecure {
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
+
+ // Authentiocation
+
+ authorizedKeys, err := NewAuthorizedPublicKeys(authorizedKeysFile)
+ if err != nil {
+ os.Exit(1)
+ }
+
+ // Connect to server
+
conn, _, err := dialer.Dial(wsURL, nil)
if err != nil {
log.Println("WebSocket connection error:", err)
@@ -327,18 +337,7 @@ func main() {
panic(err)
}
- // Authentiocation
-
- authorizedKeys, err := NewAuthorizedPublicKeys(authorizedKeysFile)
- if err != nil {
- os.Exit(1)
- }
// initial check
- pubkeys, _ := authorizedKeys.Parse()
- if len(pubkeys) == 0 {
- log.Printf("No public keys found in '%s', exiting", authorizedKeysFile)
- os.Exit(1)
- }
go comms.ListenForServerEvents(commChannel)
diff --git a/cmd/converge/converge.go b/cmd/converge/converge.go
index 0d73952..f93a228 100644
--- a/cmd/converge/converge.go
+++ b/cmd/converge/converge.go
@@ -1,7 +1,6 @@
package main
import (
- "converge/pkg/models"
"converge/pkg/server/converge"
"converge/pkg/support/websocketutil"
"fmt"
@@ -95,9 +94,10 @@ func main() {
printHelp("")
}
- notifications := make(chan *models.State, 10)
+ notifications := NewStateNotifier()
admin := converge.NewAdmin(notifications)
- websessions := converge.NewWebSessions(notifications)
+ websessions := converge.NewWebSessions(notifications.webNotificationChannel)
+ setupPrometheus(notifications.prometheusNotificationChannel)
// For agents connecting
registrationService := websocketutil.WebSocketService{
@@ -148,7 +148,6 @@ func main() {
// websocket endpoints
- // TODO remove, simulate contextpath
context := HttpContext{path: contextpath}
context.HandleFunc("agent/", registrationService.Handle)
diff --git a/cmd/converge/notifier.go b/cmd/converge/notifier.go
new file mode 100644
index 0000000..b266315
--- /dev/null
+++ b/cmd/converge/notifier.go
@@ -0,0 +1,20 @@
+package main
+
+import "converge/pkg/models"
+
+type StateNotifier struct {
+ webNotificationChannel chan *models.State
+ prometheusNotificationChannel chan *models.State
+}
+
+func NewStateNotifier() *StateNotifier {
+ return &StateNotifier{
+ webNotificationChannel: make(chan *models.State, 10),
+ prometheusNotificationChannel: make(chan *models.State, 10),
+ }
+}
+
+func (notifier StateNotifier) Publish(state *models.State) {
+ notifier.webNotificationChannel <- state
+ notifier.prometheusNotificationChannel <- state
+}
diff --git a/cmd/converge/prometheus.go b/cmd/converge/prometheus.go
new file mode 100644
index 0000000..48fd9b3
--- /dev/null
+++ b/cmd/converge/prometheus.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+ "converge/pkg/models"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "log"
+ "net/http"
+ "strconv"
+)
+
+const NAMESPACE = "converge"
+
+var (
+ agentCount = promauto.NewGauge(prometheus.GaugeOpts{
+ Namespace: NAMESPACE,
+ Name: "agent_count",
+ Help: "Current number of agents",
+ })
+ clientCount = promauto.NewGauge(prometheus.GaugeOpts{
+ Namespace: NAMESPACE,
+ Name: "client_count",
+ Help: "Current number of clients",
+ })
+
+ agentInfo = promauto.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Namespace: NAMESPACE,
+ Name: "agent_info",
+ Help: "A flexible gauge with dynamic labels, always set to 1",
+ },
+ []string{"id", "os"}, // Label names
+ )
+ clientInfo = promauto.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Namespace: NAMESPACE,
+ Name: "client_info",
+ Help: "A flexible gauge with dynamic labels, always set to 1",
+ },
+ []string{"id", "agentid"}, // Label names
+ )
+)
+
+func agentLabels(agent models.Agent) prometheus.Labels {
+ return prometheus.Labels{
+ "id": agent.PublicId,
+ "os": agent.AgentInfo.OS,
+ }
+}
+
+func clientLabels(client models.Client) prometheus.Labels {
+ return prometheus.Labels{
+ "id": strconv.Itoa(client.ClientId),
+ "agentid": client.PublicId,
+ }
+}
+
+func agentActive(agent models.Agent) {
+ agentInfo.With(agentLabels(agent)).Set(1)
+}
+
+func clientActive(client models.Client) {
+ clientInfo.With(clientLabels(client)).Set(1)
+}
+
+func setupPrometheus(notifications chan *models.State) {
+ go func() {
+ for {
+ state := <-notifications
+ updateMetrics(state)
+ }
+ }()
+ http.Handle("/metrics", promhttp.Handler())
+}
+
+func updateMetrics(state *models.State) {
+ // This implemnetation has a small probability that the metric will be in a partially
+ // initialized state. This is however unlikely. It would lead to in incorrect determination
+ // that an agent or client is not available. However, each agent and client will have a UID
+ // so that is still possible to identify the client or agent even though some values might
+ // become 0.
+
+ log.Printf("Got notification %v", *state)
+ agentCount.Set(float64(len(state.Agents)))
+ agentInfo.Reset()
+ for _, agent := range state.Agents {
+ agentActive(agent)
+ }
+ clientCount.Set(float64(len(state.Clients)))
+ clientInfo.Reset()
+ for _, client := range state.Clients {
+ clientActive(client)
+ }
+}
diff --git a/cmd/converge/usage.go b/cmd/converge/usage.go
index 419a594..cae5df1 100644
--- a/cmd/converge/usage.go
+++ b/cmd/converge/usage.go
@@ -29,22 +29,22 @@ func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
remoteShells := r.Form["remote-shell"]
localShells := r.Form["local-shell"]
keysString := r.FormValue("ssh-keys")
- sshPublicKeys := make([]string, 0)
- for _, line := range strings.Split(keysString, "\n") {
- line := strings.TrimSpace(line)
- if line != "" {
- sshPublicKeys = append(sshPublicKeys, line)
- }
- }
access := getConvergeAccess(r)
- usageInputs := templates.NewUsageInputs(id, sshPublicKeys, remoteShells, localShells)
+ downloadCommand := r.FormValue("download-command")
+
+ sshPublicKeys := strings.Split(keysString, "\n")
+ usageInputs := templates.NewUsageInputs(id, sshPublicKeys, remoteShells, localShells, downloadCommand)
matched, _ := regexp.MatchString("^[a-zA-Z0-9-_]+$", id)
if !matched {
usageInputs.ErrorMessages = append(usageInputs.ErrorMessages, "ID may consist only of alphanumeric characters, '-', and '_'")
}
validPubKeys := 0
for index, pubkey := range sshPublicKeys {
+ pubkey = strings.TrimSpace(pubkey)
+ if pubkey == "" {
+ continue
+ }
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubkey))
if err != nil {
keysummary := pubkey
@@ -52,14 +52,14 @@ func generateCLIExammple(w http.ResponseWriter, r *http.Request) {
keysummary = keysummary[:20] + " ... " + keysummary[len(pubkey)-20:]
}
usageInputs.ErrorMessages = append(usageInputs.ErrorMessages,
- fmt.Sprintf("ssh public key %d: %s: %s", index, keysummary, err.Error()))
+ fmt.Sprintf("ssh public key line %d: %s: %s", index+1, keysummary, err.Error()))
} else {
validPubKeys++
}
}
if validPubKeys == 0 {
usageInputs.ErrorMessages = append(usageInputs.ErrorMessages,
- "No valid public keys configured, password authentication will be used which is less secure.")
+ "No valid public keys configured. Without these the agent will not work.")
}
err = templates.ShellUsage(access, usageInputs).Render(r.Context(), w)
diff --git a/cmd/wsproxy/wsproxy.go b/cmd/wsproxy/wsproxy.go
index 143914d..9b91c99 100644
--- a/cmd/wsproxy/wsproxy.go
+++ b/cmd/wsproxy/wsproxy.go
@@ -43,7 +43,7 @@ func printHelp(msg string) {
if msg != "" {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", msg)
}
- usage := "Usage: wsproxy [--insecure] ws[s]://[:port]/client/\n\n" +
+ usage := "Usage: wsproxy [--id ] [--insecure] ws[s]://[:port]/client/\n" +
"\n" +
"Here is the rendez-vous id of a continuous integration job\n" +
"\n" +
diff --git a/go.mod b/go.mod
index 8969114..cb454ed 100755
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,13 @@ require (
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/kr/fs v0.1.0 // indirect
+ github.com/prometheus/client_golang v1.19.1 // indirect
+ github.com/prometheus/client_model v0.5.0 // indirect
+ github.com/prometheus/common v0.48.0 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
golang.org/x/sys v0.22.0 // indirect
+ google.golang.org/protobuf v1.33.0 // indirect
)
diff --git a/go.sum b/go.sum
index f66c644..cd8d6e5 100755
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,10 @@ github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg=
github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -27,6 +31,14 @@ github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
+github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -68,6 +80,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/pkg/server/converge/admin.go b/pkg/server/converge/admin.go
index f9d5866..2545ce8 100644
--- a/pkg/server/converge/admin.go
+++ b/pkg/server/converge/admin.go
@@ -59,10 +59,10 @@ type Admin struct {
mutex sync.Mutex
agents map[string]*AgentConnection
clients []*ClientConnection
- notifications chan *models.State
+ notifications Notifier
}
-func NewAdmin(notifications chan *models.State) *Admin {
+func NewAdmin(notifications Notifier) *Admin {
admin := Admin{
mutex: sync.Mutex{},
agents: make(map[string]*AgentConnection),
@@ -120,7 +120,7 @@ func (admin *Admin) logStatus() {
notification := admin.createNotification()
notification.Ascii = strings.Join(lines, "\n")
- admin.notifications <- notification
+ admin.notifications.Publish(notification)
}
func (admin *Admin) getFreeId(publicId string) (string, error) {
diff --git a/pkg/server/converge/notifier.go b/pkg/server/converge/notifier.go
new file mode 100644
index 0000000..361ca9a
--- /dev/null
+++ b/pkg/server/converge/notifier.go
@@ -0,0 +1,7 @@
+package converge
+
+import "converge/pkg/models"
+
+type Notifier interface {
+ Publish(state *models.State)
+}
diff --git a/pkg/server/templates/about.templ b/pkg/server/templates/about.templ
index a6a72f1..1c5761f 100644
--- a/pkg/server/templates/about.templ
+++ b/pkg/server/templates/about.templ
@@ -16,56 +16,10 @@ templ About() {
by configuring the build job to connect to a Converge server using an agent program.
The agent program can be downloaded from within the CI job using curl or wget.
Next, an end-user can connect to the Converge server, a rendez-vous server, that connects
- the client and server together.
+ the client and server together based on a common identifier specified by both client and
+ server.
-
other tools
-
-
Using available existing tools such as
- breakpoint in combination
- with a websocket tunneling tool such as
- wstunnel a similar solution can be
- obtained. There are however some problems with these solutions that converge is
- trying to address:
-
-
-
-
-
deployment: Breakpoint uses an embedded SSH server which is a really good idea but
- uses the QUIC protocol for connecting to a rendez-vous server. The rendez-vous server than
- exposes a random port for every client. This make deployment on kubernetes really hard
- where fixed ports must be used and QUIC is also not a widely supported protocol.
-
The problem with the random ports can be solved by using wstunnel running together
- with breakpoint server in a kubernetes pod, where wstunnel can forward traffic over an
- extern websocket connection to the local random port that breakpoint server is listening on.
-
breakpoint leaves it open on how users install the breakpoint executable (agent).
-
Because of the hacky nature of this setup, it is very difficult for users to use
- and troubleshoot when things go wrong.
-
-
-
- Converve server addresses these issues in the following ways:
-
-
Use the websocket protocol both for agents and for clients, providing a fixed port and
- a supported protocol for kubernetes deploymment.
-
Providing online documentation where the instructions take into account the
- hostname and protocol where converge is running allowing users to cut and paste
- instructions that can be used without modification. In the usage page the users
- can even generate the correct agent startup commands and client connection commands
- based on the type of shell they are connecting to.
-
Converge server provides out of the box downloads of required software. This makes sure
- client and server are always up to date. In addition a protocol version check is done.
-
User-friendly error messages can be given to users in most case when things do not work
- out because of wsproxy, an SSH proxy command that also talk to the server
- to tell the user if a connection is accepted and if not why not.
-
A live screen showing the current sessions that are running.
-
Interactivity in the user's session with notifications about timeouts and a very
- simple inactivity timmeout mechanism.
-
Possibility for the user to define his own shell.
-
Support for unix like bash shells and command prompt and powershell.
-
-
-
how it works
@@ -73,22 +27,21 @@ templ About() {
The steps involved are as follows:
-
The agent connects to converge server. If no id is specified than a new id will
- be generated. The ids specified by different agents must be unique. If the agent
- specifies an id that is already in use, then a new id will be generated.
- When started the agent will echo the commands to connect to it in its output.
+
The agent connects to converge server and specifies an id, the so-called rendez-vous id,
+ identifying the agent.
+ The agent outputs ane example command that can be used to connect to this agent.
-
- Since the emmbedded SSH server in the agent will allow multiple clients to connect
- to it, it wants to listen for copnnections. By default it cannot do this, it just
- setup a connection to the converge server, but the converge server can in general
- not connect back to it because of networking. Therefore, a multiplexing library is
- used to establish multiple virtual connections over a single TCP connection.
- The agent can now listen for connections from clients.
+
The agent sets up multiplexing of connections together with converge server
+ which allows it to listen on incoming connections.
-
The agent connects to the converge server using the commmand specified by the agent.
- The converge server can then match the agent with the client based on the id and
- the connectio at network level is established.
+
This is used by the agent for running an embedded SSH server nad listening for
+ incoming connection requests from clients.
+
+
The client/user connects to the converge server using the commmand specified by the agent.
+ This uses the same id as that used by the agent. The converge server can now match these
+ ids an set up an end-to-end connection from client to agent. The role of converge server
+ is simply in matching these ids and connecting the two websocket connections (from agent
+ and from client) together by copying data between them as it arrives.
The embedded SSH server now performs authentication, after successful login,
a shell is spwaned and the network connection of the user is connected to it.
@@ -99,6 +52,15 @@ templ About() {
+
With regards to the rendez-vous id there are the following remarks:
+
+
If no id is specified than an id is generated.
+
If the agent uses an id already in use by another agent, then converge server will
+ generate a new id.
+
+ The agent will always print the id and command required to connect to it to standard output.
+
+
Security
@@ -107,13 +69,13 @@ templ About() {
data between client and agent.
-
Currently converge server still supports password based login but this will be disabled.
- Image two people configuring an agent with the same id where one of the agents actually
- gets it and other gets a new id. Now, with a password each user can access each other's
- agents. This is of course highly confusing and undesirable. Converge server already support
- authorized keys but this is not yet mandatory. I is made extremely easy through the
- usage page to configure this, so the additional complexity should
- not be an issue.
+
Using authorized keys is a secure way of connectiong. When running the agent, the authorized keys
+ must be put in a file, allowing only the designated users to connect. The file containing authorized keys
+ can also be edited during a session with the agent, allowing more people to be added when required without
+ having to start over again.
+ Using authorized keys is made easy through the
+ usage page, which provides the exact commands to execute based
+ on the target environmnet.
SSH and SFTP
@@ -128,14 +90,13 @@ templ About() {
There is a timeout mechanism in the agent such that jobs do not hang indefinitely
waiting for a connection. This mechanism is useful to make sure build agents do not keep
build agents occupied for a long time. By default, the agent exits with status 0 when
- the first client exits after logging in.
+ the first client exits after logging in. The timeout is an inactivity timeout which is reset
+ every time the user presses a key on the keyboard.
When the user touches a .hold file, the agent keeps waiting for connections even
- after the last client logs out, taking into account the timeout.
+ after the last client logs out, taking into account the timeout. By default the agent
+ exits when the last user has logged out.
-
The sessions have an inactivity timeout. Any keypress on the keyboard by a user
- is interpreted as activity.
-
Remote shell usage
@@ -150,6 +111,59 @@ templ About() {
The agent sets a agentdir environment variable that points to
the directory where the agent is running.
+
+
other tools
+
+
Using available existing tools such as
+ breakpoint in combination
+ with a websocket tunneling tool such as
+ wstunnel a similar solution can be
+ obtained. There are however some problems with these solutions that converge is
+ trying to address:
+
+
+
+
+
Breakpoint uses an embedded SSH server which is a really good idea but
+ uses the QUIC protocol for connecting to a rendez-vous server. The rendez-vous server than
+ exposes a random port for every client. This make deployment on kubernetes really hard
+ where fixed ports must be used and QUIC is also not a widely supported protocol.
+
The problem with the random ports can be solved by using wstunnel running together
+ with breakpoint server in a kubernetes pod, where wstunnel can forward traffic over an
+ extern websocket connection to the local random port that breakpoint server is listening on.
+
breakpoint leaves it open on how users install the breakpoint executable (agent).
+
Because of the hacky nature of this setup, it is very difficult for users to use
+ and troubleshoot when things go wrong.
+
+
+
+ Converve server addresses these issues in the following ways:
+
+
Use the websocket protocol both for agents and for clients, providing a fixed port and
+ a supported protocol for kubernetes deploymment. Websockets are also supported by
+ kubernetes ingress controllers so this makes it easy to deploy on kubernetes.
+
Providing online documentation where the instructions take into account the
+ hostname and protocol where converge is running allowing users to cut and paste
+ instructions that can be used without modification. In the usage page the users
+ can even generate the correct agent startup commands and client connection commands
+ based on the type of shell they are connecting to.
+
Converge server provides out of the box downloads of required software. This makes sure
+ client and server are always up to date and can be downloaded in any continuous integration
+ job without having to package the required executables in an ad-hoc way.
+ In addition a protocol version check is done.
+
User-friendly error messages can be given to users in most case when things do not work
+ out because of wsproxy, an SSH proxy command that also talk to the server
+ to tell the user if a connection is accepted and if not why not.
+
A live screen showing the current sessions that are running. The sessionw webpage provides
+ additional feedback about the running sessions.
+
Interactivity in the user's session with notifications about timeouts and a very
+ simple inactivity timeout mechanism.
+
Possibility for the user to define his own shell.
+
Support for unix like bash shells and command prompt and powershell.
+ You need to install wsproxy, which is available in the downloads section and
+ make it availebla in your path. This utility needs to be downloaded only once, if a newer version of
+ wsproxy is needed it will tell you about that.
+
+
+
The embedded ssh server in the agent supports both ssh and sftp.
This requires the wsproxy utility which is available in the
- downloads section. This utility must be downloaded
- only once since it is quite generic. It will warn you when it a newer version must
- be downloaded.
-
-
For other ssh clients that do not support the openssh ProxyCommand option, there is another
way to connect. In this method, a local port forwarder is started that forwards a local port
to the webserver. Then you can start an ssh client that connects to the local tcp port.
@@ -183,6 +183,13 @@ templ Usage(access models.ConvergeAccess) {