prometheus support step 1
updates to documentation.
This commit is contained in:
parent
68804761bf
commit
9d1c6d6616
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
20
cmd/converge/notifier.go
Normal file
20
cmd/converge/notifier.go
Normal file
@ -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
|
||||
}
|
95
cmd/converge/prometheus.go
Normal file
95
cmd/converge/prometheus.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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]://<host>[:port]/client/<ID>\n\n" +
|
||||
usage := "Usage: wsproxy [--id <ID>] [--insecure] ws[s]://<host>[:port]/client/<ID>\n" +
|
||||
"\n" +
|
||||
"Here <ID> is the rendez-vous id of a continuous integration job\n" +
|
||||
"\n" +
|
||||
|
7
go.mod
7
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
|
||||
)
|
||||
|
14
go.sum
14
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=
|
||||
|
@ -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) {
|
||||
|
7
pkg/server/converge/notifier.go
Normal file
7
pkg/server/converge/notifier.go
Normal file
@ -0,0 +1,7 @@
|
||||
package converge
|
||||
|
||||
import "converge/pkg/models"
|
||||
|
||||
type Notifier interface {
|
||||
Publish(state *models.State)
|
||||
}
|
@ -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.
|
||||
</p>
|
||||
|
||||
<h2>other tools</h2>
|
||||
|
||||
<p>Using available existing tools such as
|
||||
<a href="https://github.com/namespacelabs/breakpoint">breakpoint</a> in combination
|
||||
with a websocket tunneling tool such as
|
||||
<a href="https://github.com/erebe/wstunnel">wstunnel</a> a similar solution can be
|
||||
obtained. There are however some problems with these solutions that converge is
|
||||
trying to address:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ul>
|
||||
<li>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.</li>
|
||||
<li>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.</li>
|
||||
<li>breakpoint leaves it open on how users install the breakpoint executable (agent). </li>
|
||||
<li>Because of the hacky nature of this setup, it is very difficult for users to use
|
||||
and troubleshoot when things go wrong. </li>
|
||||
</ul>
|
||||
|
||||
</p>
|
||||
Converve server addresses these issues in the following ways:
|
||||
<ul>
|
||||
<li>Use the websocket protocol both for agents and for clients, providing a fixed port and
|
||||
a supported protocol for kubernetes deploymment.</li>
|
||||
<li>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. </li>
|
||||
<li>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. </li>
|
||||
<li>User-friendly error messages can be given to users in most case when things do not work
|
||||
out because of <code>wsproxy</code>, an SSH proxy command that also talk to the server
|
||||
to tell the user if a connection is accepted and if not why not. </li>
|
||||
<li>A live screen showing the current sessions that are running. </li>
|
||||
<li>Interactivity in the user's session with notifications about timeouts and a very
|
||||
simple inactivity timmeout mechanism. </li>
|
||||
<li>Possibility for the user to define his own shell. </li>
|
||||
<li>Support for unix like bash shells and command prompt and powershell. </li>
|
||||
</ul>
|
||||
<p>
|
||||
</p>
|
||||
|
||||
|
||||
<h2>how it works</h2>
|
||||
@ -73,22 +27,21 @@ templ About() {
|
||||
<p>
|
||||
The steps involved are as follows:
|
||||
<ul>
|
||||
<li>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.
|
||||
<li>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.
|
||||
</li>
|
||||
<li>
|
||||
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.
|
||||
<li>The agent sets up multiplexing of connections together with converge server
|
||||
which allows it to listen on incoming connections.
|
||||
</li>
|
||||
<li>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.
|
||||
<li>This is used by the agent for running an embedded SSH server nad listening for
|
||||
incoming connection requests from clients.
|
||||
</li>
|
||||
<li>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.
|
||||
</li>
|
||||
<li>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() {
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>With regards to the rendez-vous id there are the following remarks:
|
||||
<ul>
|
||||
<li> If no id is specified than an id is generated. </li>
|
||||
<li> If the agent uses an id already in use by another agent, then converge server will
|
||||
generate a new id. </li>
|
||||
</ul>
|
||||
The agent will always print the id and command required to connect to it to standard output.
|
||||
</p>
|
||||
|
||||
<h2>Security</h2>
|
||||
|
||||
<p>
|
||||
@ -107,13 +69,13 @@ templ About() {
|
||||
data between client and agent.
|
||||
</p>
|
||||
|
||||
<p>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
|
||||
<a href="usage.html">usage</a> page to configure this, so the additional complexity should
|
||||
not be an issue.
|
||||
<p>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
|
||||
<a href="usage.html">usage</a> page, which provides the exact commands to execute based
|
||||
on the target environmnet.
|
||||
</p>
|
||||
|
||||
<h2>SSH and SFTP</h2>
|
||||
@ -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.
|
||||
</p>
|
||||
<p>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.
|
||||
</p>
|
||||
<p>The sessions have an inactivity timeout. Any keypress on the keyboard by a user
|
||||
is interpreted as activity. </p>
|
||||
|
||||
|
||||
<h2>Remote shell usage</h2>
|
||||
|
||||
@ -150,6 +111,59 @@ templ About() {
|
||||
The agent sets a <coder>agentdir</coder> environment variable that points to
|
||||
the directory where the agent is running.
|
||||
</p>
|
||||
|
||||
<h2>other tools</h2>
|
||||
|
||||
<p>Using available existing tools such as
|
||||
<a href="https://github.com/namespacelabs/breakpoint">breakpoint</a> in combination
|
||||
with a websocket tunneling tool such as
|
||||
<a href="https://github.com/erebe/wstunnel">wstunnel</a> a similar solution can be
|
||||
obtained. There are however some problems with these solutions that converge is
|
||||
trying to address:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ul>
|
||||
<li>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.</li>
|
||||
<li>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.</li>
|
||||
<li>breakpoint leaves it open on how users install the breakpoint executable (agent). </li>
|
||||
<li>Because of the hacky nature of this setup, it is very difficult for users to use
|
||||
and troubleshoot when things go wrong. </li>
|
||||
</ul>
|
||||
|
||||
</p>
|
||||
Converve server addresses these issues in the following ways:
|
||||
<ul>
|
||||
<li>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. </li>
|
||||
<li>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. </li>
|
||||
<li>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. </li>
|
||||
<li>User-friendly error messages can be given to users in most case when things do not work
|
||||
out because of <code>wsproxy</code>, an SSH proxy command that also talk to the server
|
||||
to tell the user if a connection is accepted and if not why not. </li>
|
||||
<li>A live screen showing the current sessions that are running. The sessionw webpage provides
|
||||
additional feedback about the running sessions. </li>
|
||||
<li>Interactivity in the user's session with notifications about timeouts and a very
|
||||
simple inactivity timeout mechanism. </li>
|
||||
<li>Possibility for the user to define his own shell. </li>
|
||||
<li>Support for unix like bash shells and command prompt and powershell. </li>
|
||||
</ul>
|
||||
<p>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -3,3 +3,11 @@ package templates
|
||||
const BASH = "*.sh"
|
||||
const CMD = "cmd"
|
||||
const POWERSHELL = "powershell"
|
||||
|
||||
const CURL = "curl"
|
||||
const WGET = "wget"
|
||||
|
||||
var DOWNLOAD_COMMAND = map[string]string{
|
||||
CURL: "curl -o",
|
||||
WGET: "wget -O",
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ templ Downloads() {
|
||||
<td>wsproxy</td>
|
||||
<td><a href="../downloads/wsproxy">wsproxy</a></td>
|
||||
<td><a href="../downloads/wsproxy.exe">wsproxy.exe</a></td>
|
||||
<td>SSH proxy command that can be directly used by ssh</td>
|
||||
<td>SSH proxy command that allows ssh to connect using websockets.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tcptows</td>
|
||||
|
@ -19,7 +19,7 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
||||
|
||||
if usageInputs.RemoteShells[BASH] {
|
||||
<code-sample id="run-agent-bash">@templ.Raw(addSshKeys(BASH, usageInputs.SshKeys)) <br/>
|
||||
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent > agent <br/>
|
||||
{DOWNLOAD_COMMAND[usageInputs.DownloadCommand]} agent http{access.Secure}://{access.BaseUrl}/downloads/agent<br/>
|
||||
chmod 755 agent <br/>
|
||||
./agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}<br/>
|
||||
rm -f agent <br/>
|
||||
@ -27,15 +27,15 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
||||
}
|
||||
if usageInputs.RemoteShells[CMD] {
|
||||
<code-sample id="run-agent-cmd">@templ.Raw(addSshKeys(CMD, usageInputs.SshKeys)) <br/>
|
||||
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe <br/>
|
||||
agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
|
||||
{DOWNLOAD_COMMAND[usageInputs.DownloadCommand]} agent.exe http{access.Secure}://{access.BaseUrl}/downloads/agent.exe <br/>
|
||||
.\agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
|
||||
del agent.exe <br/>
|
||||
</code-sample>
|
||||
}
|
||||
if usageInputs.RemoteShells[POWERSHELL] {
|
||||
<code-sample id="run-agent-powershell">@templ.Raw(addSshKeys(POWERSHELL, usageInputs.SshKeys)) <br/>
|
||||
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe <br/>
|
||||
agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
|
||||
{DOWNLOAD_COMMAND[usageInputs.DownloadCommand]} agent.exe http{access.Secure}://{access.BaseUrl}/downloads/agent.exe <br/>
|
||||
.\agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
|
||||
del agent.exe <br/>
|
||||
</code-sample>
|
||||
}
|
||||
@ -54,6 +54,13 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
||||
|
||||
<h2>Connecting to the agent</h2>
|
||||
|
||||
<p>
|
||||
You need to install <code>wsproxy</code>, which is available in the <a href="downloads.html">downloads</a> section and
|
||||
make it availebla in your path. This utility needs to be downloaded only once, if a newer version of
|
||||
<code>wsproxy</code> is needed it will tell you about that.
|
||||
</p>
|
||||
|
||||
|
||||
<p>The embedded ssh server in the agent supports both ssh and sftp.
|
||||
</p>
|
||||
|
||||
@ -64,13 +71,6 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
||||
sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}" {"localhost"}
|
||||
</code-sample>
|
||||
|
||||
|
||||
<p>This requires the <code>wsproxy</code> utility which is available in the
|
||||
<a href="downloads.html">downloads</a> 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.
|
||||
</p>
|
||||
|
||||
<p>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) {
|
||||
<input id="remote-shell-2" name="remote-shell" type="radio" value={POWERSHELL}> <label for="remote-shell-2">power shell</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="minimal-width"><label for="download-command">download method</label></td>
|
||||
<td>
|
||||
<input checked id="download-command-0" name="download-command" type="radio" value={CURL}> <label for="download-command-0">curl</label>
|
||||
<input id="download-command-1" name="download-command" type="radio" value={WGET}> <label for="download-command-1">wget</label>
|
||||
</td>
|
||||
</tr>
|
||||
<!--tr>
|
||||
<td class="minimal-width"><label for="local-shell">local environment</label></td>
|
||||
<td>
|
||||
|
@ -9,16 +9,19 @@ type UsageInputs struct {
|
||||
SshKeys []string
|
||||
ErrorMessages []string
|
||||
|
||||
RemoteShells map[string]bool
|
||||
LocalShells map[string]bool
|
||||
RemoteShells map[string]bool
|
||||
LocalShells map[string]bool
|
||||
DownloadCommand string
|
||||
}
|
||||
|
||||
func NewUsageInputs(id string, sshPublicKeys []string, remoteShells []string, localShells []string) UsageInputs {
|
||||
func NewUsageInputs(id string, sshPublicKeys []string, remoteShells []string, localShells []string,
|
||||
downloadCommand string) UsageInputs {
|
||||
inputs := UsageInputs{
|
||||
Id: id,
|
||||
SshKeys: sshPublicKeys,
|
||||
RemoteShells: make(map[string]bool),
|
||||
LocalShells: make(map[string]bool),
|
||||
Id: id,
|
||||
SshKeys: sshPublicKeys,
|
||||
RemoteShells: make(map[string]bool),
|
||||
LocalShells: make(map[string]bool),
|
||||
DownloadCommand: downloadCommand,
|
||||
}
|
||||
for _, remoteShell := range remoteShells {
|
||||
inputs.RemoteShells[remoteShell] = true
|
||||
|
Loading…
Reference in New Issue
Block a user