introdcution of typesafe ids.

This commit is contained in:
Erik Brakkee 2024-08-15 18:32:59 +02:00
parent c86ea894d1
commit 556315906d
9 changed files with 100 additions and 97 deletions

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"git.wamblee.org/converge/pkg/models"
"git.wamblee.org/converge/pkg/server/matchmaker" "git.wamblee.org/converge/pkg/server/matchmaker"
"git.wamblee.org/converge/pkg/support/websocketutil" "git.wamblee.org/converge/pkg/support/websocketutil"
"log" "log"
@ -15,13 +16,13 @@ import (
_ "time/tzdata" _ "time/tzdata"
) )
func parsePublicId(path string) (publicId string, _ error) { func parsePublicId(path string) (publicId models.RendezVousId, _ error) {
pattern := regexp.MustCompile("/([^/]+)$") pattern := regexp.MustCompile("/([^/]+)$")
matches := pattern.FindStringSubmatch(path) matches := pattern.FindStringSubmatch(path)
if len(matches) != 2 { if len(matches) != 2 {
return "", fmt.Errorf("Invalid URL path '%s'", path) return "", fmt.Errorf("Invalid URL path '%s'", path)
} }
return matches[1], nil return models.RendezVousId(matches[1]), nil
} }
func catchAllHandler(contextPath string) func(w http.ResponseWriter, r *http.Request) { func catchAllHandler(contextPath string) func(w http.ResponseWriter, r *http.Request) {

View File

@ -14,14 +14,14 @@ const NAMESPACE = "converge"
// more efficient state representation for state // more efficient state representation for state
type PrometheusState struct { type PrometheusState struct {
agents map[string]*models.Agent agents map[models.AgentGuid]*models.Agent
clients map[string]*models.Client clients map[models.ClientGuid]*models.Client
} }
func NewPrometheusState(state *models.State) *PrometheusState { func NewPrometheusState(state *models.State) *PrometheusState {
res := PrometheusState{ res := PrometheusState{
agents: make(map[string]*models.Agent), agents: make(map[models.AgentGuid]*models.Agent),
clients: make(map[string]*models.Client), clients: make(map[models.ClientGuid]*models.Client),
} }
for i, _ := range state.Agents { for i, _ := range state.Agents {
res.agents[state.Agents[i].Guid] = &state.Agents[i] res.agents[state.Agents[i].Guid] = &state.Agents[i]
@ -121,9 +121,9 @@ var (
func agentLabels(agent *models.Agent) prometheus.Labels { func agentLabels(agent *models.Agent) prometheus.Labels {
return prometheus.Labels{ return prometheus.Labels{
"agent_guid": agent.Guid, "agent_guid": string(agent.Guid),
"agent_address": agent.RemoteAddr, "agent_address": string(agent.RemoteAddr),
"agent_id": agent.PublicId, "agent_id": string(agent.PublicId),
"agent_username": agent.EnvironmentInfo.Username, "agent_username": agent.EnvironmentInfo.Username,
"agent_hostname": agent.EnvironmentInfo.Hostname, "agent_hostname": agent.EnvironmentInfo.Hostname,
"agent_pwd": agent.EnvironmentInfo.Pwd, "agent_pwd": agent.EnvironmentInfo.Pwd,
@ -134,12 +134,12 @@ func agentLabels(agent *models.Agent) prometheus.Labels {
func clientLabels(client *models.Client) prometheus.Labels { func clientLabels(client *models.Client) prometheus.Labels {
return prometheus.Labels{ return prometheus.Labels{
"client_guid": client.Guid, "client_guid": string(client.Guid),
"client_address": client.RemoteAddr, "client_address": string(client.RemoteAddr),
"client_id": client.ClientId, "client_id": string(client.ClientId),
"agent_id": client.PublicId, "agent_id": string(client.PublicId),
"agent_guid": client.AgentGuid, "agent_guid": string(client.AgentGuid),
"client_sessiontype": client.SessionType, "client_sessiontype": string(client.SessionType),
"client_username": client.EnvironmentInfo.Username, "client_username": client.EnvironmentInfo.Username,
"client_hostname": client.EnvironmentInfo.Hostname, "client_hostname": client.EnvironmentInfo.Hostname,
"client_pwd": client.EnvironmentInfo.Pwd, "client_pwd": client.EnvironmentInfo.Pwd,
@ -154,11 +154,12 @@ func agentActive(agent *models.Agent) {
removeAgentInfoMetrics(prevAgent) removeAgentInfoMetrics(prevAgent)
} }
agentInfo.With(agentLabels(agent)).Set(1) agentInfo.With(agentLabels(agent)).Set(1)
agentGuid := string(agent.Guid)
agentStartTime. agentStartTime.
With(prometheus.Labels{"agent_guid": agent.Guid}). With(prometheus.Labels{"agent_guid": agentGuid}).
Set(float64(agent.StartTime.UnixMilli())) Set(float64(agent.StartTime.UnixMilli()))
agentDuration. agentDuration.
With(prometheus.Labels{"agent_guid": agent.Guid}). With(prometheus.Labels{"agent_guid": agentGuid}).
Set(float64(time.Now().Sub(agent.StartTime).Seconds())) Set(float64(time.Now().Sub(agent.StartTime).Seconds()))
} }
@ -168,12 +169,12 @@ func clientActive(client *models.Client) {
removeClientInfoMetrics(prevClient) removeClientInfoMetrics(prevClient)
} }
clientInfo.With(clientLabels(client)).Set(1) clientInfo.With(clientLabels(client)).Set(1)
clientGuid := string(client.Guid)
clientStartTime. clientStartTime.
With(prometheus.Labels{"client_guid": client.Guid}). With(prometheus.Labels{"client_guid": clientGuid}).
Set(float64(client.StartTime.UnixMilli())) Set(float64(client.StartTime.UnixMilli()))
clientDuration. clientDuration.
With(prometheus.Labels{"client_guid": client.Guid}). With(prometheus.Labels{"client_guid": clientGuid}).
Set(float64(time.Now().Sub(client.StartTime).Seconds())) Set(float64(time.Now().Sub(client.StartTime).Seconds()))
} }
@ -223,23 +224,23 @@ func updateMetrics(state *models.State) {
func updateDurations() { func updateDurations() {
for _, agent := range lastState.agents { for _, agent := range lastState.agents {
agentDuration. agentDuration.
With(prometheus.Labels{"agent_guid": agent.Guid}). With(prometheus.Labels{"agent_guid": string(agent.Guid)}).
Set(float64(time.Now().Sub(agent.StartTime).Seconds())) Set(float64(time.Now().Sub(agent.StartTime).Seconds()))
} }
for _, client := range lastState.clients { for _, client := range lastState.clients {
clientDuration. clientDuration.
With(prometheus.Labels{"client_guid": client.Guid}). With(prometheus.Labels{"client_guid": string(client.Guid)}).
Set(float64(time.Now().Sub(client.StartTime).Seconds())) Set(float64(time.Now().Sub(client.StartTime).Seconds()))
} }
} }
func updateMetricsImpl(state *PrometheusState) { func updateMetricsImpl(state *PrometheusState) {
agentGuids := make(map[string]*models.Agent) agentGuids := make(map[models.AgentGuid]*models.Agent)
clientGuids := make(map[string]*models.Client) clientGuids := make(map[models.ClientGuid]*models.Client)
agentCount.Set(float64(len(state.agents))) agentCount.Set(float64(len(state.agents)))
disconnectedAgents := make(map[string]*models.Agent) disconnectedAgents := make(map[models.AgentGuid]*models.Agent)
for _, agent := range lastState.agents { for _, agent := range lastState.agents {
disconnectedAgents[agent.Guid] = agent disconnectedAgents[agent.Guid] = agent
} }
@ -258,7 +259,7 @@ func updateMetricsImpl(state *PrometheusState) {
clientCount.Set(float64(len(state.clients))) clientCount.Set(float64(len(state.clients)))
// with this app // with this app
disconnectedClients := make(map[string]*models.Client) disconnectedClients := make(map[models.ClientGuid]*models.Client)
for _, client := range lastState.clients { for _, client := range lastState.clients {
disconnectedClients[client.Guid] = client disconnectedClients[client.Guid] = client
} }
@ -283,7 +284,7 @@ func removeAgentInfoMetrics(agent *models.Agent) bool {
func removeAgentMetrics(agent *models.Agent) { func removeAgentMetrics(agent *models.Agent) {
ok1 := removeAgentInfoMetrics(agent) ok1 := removeAgentInfoMetrics(agent)
guidLabels := prometheus.Labels{"agent_guid": agent.Guid} guidLabels := prometheus.Labels{"agent_guid": string(agent.Guid)}
ok2 := agentStartTime.Delete(guidLabels) ok2 := agentStartTime.Delete(guidLabels)
// delayed deletion of the duration sow we are sure the prometheus has the last data. // delayed deletion of the duration sow we are sure the prometheus has the last data.
go func() { go func() {
@ -305,7 +306,7 @@ func removeClientInfoMetrics(client *models.Client) bool {
func removeClientMetrics(client *models.Client) { func removeClientMetrics(client *models.Client) {
ok1 := removeClientInfoMetrics(client) ok1 := removeClientInfoMetrics(client)
guidLabels := prometheus.Labels{"client_guid": client.Guid} guidLabels := prometheus.Labels{"client_guid": string(client.Guid)}
ok2 := clientStartTime.Delete(guidLabels) ok2 := clientStartTime.Delete(guidLabels)
// delayed deletion of the duration sow we are sure the prometheus has the last data. // delayed deletion of the duration sow we are sure the prometheus has the last data.
go func() { go func() {

View File

@ -81,7 +81,7 @@ func main() {
state := models.State{} state := models.State{}
agent := models.Agent{ agent := models.Agent{
Guid: strconv.Itoa(rand.Int()), Guid: models.AgentGuid(strconv.Itoa(rand.Int())),
RemoteAddr: "10.220.1.3:3333", RemoteAddr: "10.220.1.3:3333",
PublicId: "id", PublicId: "id",
StartTime: time.Now().In(japan), StartTime: time.Now().In(japan),
@ -96,13 +96,13 @@ func main() {
} }
state.Agents = append(state.Agents, agent) state.Agents = append(state.Agents, agent)
client := models.Client{ client := models.Client{
Guid: strconv.Itoa(rand.Int()), Guid: models.ClientGuid(strconv.Itoa(rand.Int())),
RemoteAddr: "10.1.3.3", RemoteAddr: models.RemoteAddr("10.1.3.3"),
PublicId: "c1", PublicId: models.RendezVousId("c1"),
AgentGuid: "12342342", AgentGuid: models.AgentGuid("12342342"),
ClientId: "3", ClientId: models.ClientId("3"),
StartTime: time.Now().In(japan), StartTime: time.Now().In(japan),
SessionType: "sftp", SessionType: models.SessionType("sftp"),
} }
state.Clients = append(state.Clients, client) state.Clients = append(state.Clients, client)
return templates2.SessionsTab(&state, netherlands) return templates2.SessionsTab(&state, netherlands)

View File

@ -1,18 +0,0 @@
package models
import (
"git.wamblee.org/converge/pkg/comms"
"time"
)
type Agent struct {
Guid string
RemoteAddr string
PublicId string
StartTime time.Time
// TODO add remote address.
EnvironmentInfo comms.EnvironmentInfo
ExpiryTime time.Time
}

View File

@ -1,17 +1 @@
package models package models
import (
"git.wamblee.org/converge/pkg/comms"
"time"
)
type Client struct {
Guid string
RemoteAddr string
PublicId string
ClientId string
AgentGuid string
StartTime time.Time
SessionType string
EnvironmentInfo comms.EnvironmentInfo
}

View File

@ -1,5 +1,40 @@
package models package models
import (
"git.wamblee.org/converge/pkg/comms"
"time"
)
type RendezVousId string
type AgentGuid string
type ClientGuid string
type ClientId string
type SessionType string
type RemoteAddr string
type Agent struct {
Guid AgentGuid
RemoteAddr RemoteAddr
PublicId RendezVousId
StartTime time.Time
// TODO add remote address.
EnvironmentInfo comms.EnvironmentInfo
ExpiryTime time.Time
}
type Client struct {
Guid ClientGuid
RemoteAddr RemoteAddr
PublicId RendezVousId
ClientId ClientId
AgentGuid AgentGuid
StartTime time.Time
SessionType SessionType
EnvironmentInfo comms.EnvironmentInfo
}
// State is a description of the current state of converge. // State is a description of the current state of converge.
// Created by the server and used for updating the web client // Created by the server and used for updating the web client
// and prometheus metrics. // and prometheus metrics.

View File

@ -30,11 +30,11 @@ type ClientConnection struct {
clientConnection iowrappers2.ReadWriteAddrCloser clientConnection iowrappers2.ReadWriteAddrCloser
} }
func newAgent(commChannel comms.CommChannel, publicId string, agentInfo comms.EnvironmentInfo) *agentConnection { func newAgent(commChannel comms.CommChannel, publicId models.RendezVousId, agentInfo comms.EnvironmentInfo) *agentConnection {
return &agentConnection{ return &agentConnection{
Agent: models.Agent{ Agent: models.Agent{
Guid: strconv.Itoa(rand.Int()), Guid: models.AgentGuid(strconv.Itoa(rand.Int())),
RemoteAddr: commChannel.Session.RemoteAddr().String(), RemoteAddr: models.RemoteAddr(commChannel.Session.RemoteAddr().String()),
PublicId: publicId, PublicId: publicId,
StartTime: time.Now(), StartTime: time.Now(),
EnvironmentInfo: agentInfo, EnvironmentInfo: agentInfo,
@ -43,15 +43,15 @@ func newAgent(commChannel comms.CommChannel, publicId string, agentInfo comms.En
} }
} }
func newClient(publicId string, clientConn iowrappers2.ReadWriteAddrCloser, func newClient(publicId models.RendezVousId, clientConn iowrappers2.ReadWriteAddrCloser,
agentConn net.Conn, agentGuid string) *ClientConnection { agentConn net.Conn, agentGuid models.AgentGuid) *ClientConnection {
return &ClientConnection{ return &ClientConnection{
Client: models.Client{ Client: models.Client{
Guid: strconv.Itoa(rand.Int()), Guid: models.ClientGuid(strconv.Itoa(rand.Int())),
RemoteAddr: clientConn.RemoteAddr().String(), RemoteAddr: models.RemoteAddr(clientConn.RemoteAddr().String()),
PublicId: publicId, PublicId: publicId,
AgentGuid: agentGuid, AgentGuid: agentGuid,
ClientId: strconv.Itoa(clientIdGenerator.IncrementAndGet()), ClientId: models.ClientId(strconv.Itoa(clientIdGenerator.IncrementAndGet())),
StartTime: time.Now(), StartTime: time.Now(),
}, },
agentConnection: agentConn, agentConnection: agentConn,
@ -66,14 +66,14 @@ func (match *ClientConnection) Synchronize() {
type Admin struct { type Admin struct {
// map of public id to agent // map of public id to agent
mutex sync.Mutex mutex sync.Mutex
agents map[string]*agentConnection agents map[models.RendezVousId]*agentConnection
clients []*ClientConnection clients []*ClientConnection
} }
func NewAdmin() *Admin { func NewAdmin() *Admin {
return &Admin{ return &Admin{
mutex: sync.Mutex{}, mutex: sync.Mutex{},
agents: make(map[string]*agentConnection), agents: make(map[models.RendezVousId]*agentConnection),
clients: make([]*ClientConnection, 0), // not strictly needed clients: make([]*ClientConnection, 0), // not strictly needed
} }
} }
@ -93,8 +93,8 @@ func (admin *Admin) CreateNotifification() *models.State {
return &state return &state
} }
func (admin *Admin) getFreeId(publicId string) (string, error) { func (admin *Admin) getFreeId(publicId models.RendezVousId) (models.RendezVousId, error) {
usedIds := make(map[string]bool) usedIds := make(map[models.RendezVousId]bool)
for _, agent := range admin.agents { for _, agent := range admin.agents {
usedIds[agent.PublicId] = true usedIds[agent.PublicId] = true
} }
@ -103,16 +103,16 @@ func (admin *Admin) getFreeId(publicId string) (string, error) {
} }
if usedIds[publicId] { if usedIds[publicId] {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
candidate := publicId + "-" + strconv.Itoa(i) candidate := string(publicId) + "-" + strconv.Itoa(i)
if !usedIds[candidate] { if !usedIds[models.RendezVousId(candidate)] {
return candidate, nil return models.RendezVousId(candidate), nil
} }
} }
} }
return "", fmt.Errorf("Could not allocate agent id based on requested public id '%s'", publicId) return "", fmt.Errorf("Could not allocate agent id based on requested public id '%s'", publicId)
} }
func (admin *Admin) AddAgent(publicId string, agentInfo comms.EnvironmentInfo, conn io.ReadWriteCloser) (*agentConnection, error) { func (admin *Admin) AddAgent(publicId models.RendezVousId, agentInfo comms.EnvironmentInfo, conn io.ReadWriteCloser) (*agentConnection, error) {
admin.mutex.Lock() admin.mutex.Lock()
defer admin.mutex.Unlock() defer admin.mutex.Unlock()
@ -126,7 +126,7 @@ func (admin *Admin) AddAgent(publicId string, agentInfo comms.EnvironmentInfo, c
comms.SendRegistrationMessage(conn, comms.AgentRegistration{ comms.SendRegistrationMessage(conn, comms.AgentRegistration{
Ok: true, Ok: true,
Message: message, Message: message,
Id: publicId, Id: string(publicId),
}) })
} else { } else {
comms.SendRegistrationMessage(conn, comms.AgentRegistration{ comms.SendRegistrationMessage(conn, comms.AgentRegistration{
@ -149,7 +149,7 @@ func (admin *Admin) AddAgent(publicId string, agentInfo comms.EnvironmentInfo, c
return agent, nil return agent, nil
} }
func (admin *Admin) AddClient(publicId string, clientConn iowrappers2.ReadWriteAddrCloser) (*ClientConnection, error) { func (admin *Admin) AddClient(publicId models.RendezVousId, clientConn iowrappers2.ReadWriteAddrCloser) (*ClientConnection, error) {
admin.mutex.Lock() admin.mutex.Lock()
defer admin.mutex.Unlock() defer admin.mutex.Unlock()
@ -172,7 +172,7 @@ func (admin *Admin) AddClient(publicId string, clientConn iowrappers2.ReadWriteA
// Before using this connection for SSH we use it to send client metadata to the // Before using this connection for SSH we use it to send client metadata to the
// agent // agent
err = comms.SendClientInfo(agentConn, comms.ClientInfo{ err = comms.SendClientInfo(agentConn, comms.ClientInfo{
ClientId: client.ClientId, ClientId: string(client.ClientId),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -194,7 +194,7 @@ func (admin *Admin) getAgentConnection(agent *agentConnection) (net.Conn, error)
return agentConn, err return agentConn, err
} }
func (admin *Admin) RemoveAgent(publicId string) error { func (admin *Admin) RemoveAgent(publicId models.RendezVousId) error {
admin.mutex.Lock() admin.mutex.Lock()
defer admin.mutex.Unlock() defer admin.mutex.Unlock()
@ -230,7 +230,7 @@ func (admin *Admin) RemoveClient(client *ClientConnection) error {
return nil return nil
} }
func (admin *Admin) SetSessionType(clientId string, sessionType string) { func (admin *Admin) SetSessionType(clientId models.ClientId, sessionType models.SessionType) {
admin.mutex.Lock() admin.mutex.Lock()
defer admin.mutex.Unlock() defer admin.mutex.Unlock()
for _, client := range admin.clients { for _, client := range admin.clients {

View File

@ -25,7 +25,7 @@ func NewMatchMaker(notifier Notifier) *MatchMaker {
return &converge return &converge
} }
func (converge *MatchMaker) Register(publicId string, conn io.ReadWriteCloser) error { func (converge *MatchMaker) Register(publicId models.RendezVousId, conn io.ReadWriteCloser) error {
serverInfo := comms.ServerInfo{} serverInfo := comms.ServerInfo{}
@ -53,7 +53,7 @@ func (converge *MatchMaker) Register(publicId string, conn io.ReadWriteCloser) e
}, },
func(session comms.SessionInfo) { func(session comms.SessionInfo) {
log.Println("Recceived sessioninfo ", session) log.Println("Recceived sessioninfo ", session)
converge.admin.SetSessionType(session.ClientId, session.SessionType) converge.admin.SetSessionType(models.ClientId(session.ClientId), models.SessionType(session.SessionType))
}, },
func(expiry comms.ExpiryTimeUpdate) { func(expiry comms.ExpiryTimeUpdate) {
agent.ExpiryTime = expiry.ExpiryTime agent.ExpiryTime = expiry.ExpiryTime
@ -68,7 +68,7 @@ func (converge *MatchMaker) Register(publicId string, conn io.ReadWriteCloser) e
return nil return nil
} }
func (converge *MatchMaker) Connect(wsProxyMode bool, publicId string, conn iowrappers2.ReadWriteAddrCloser) error { func (converge *MatchMaker) Connect(wsProxyMode bool, publicId models.RendezVousId, conn iowrappers2.ReadWriteAddrCloser) error {
defer conn.Close() defer conn.Close()
log.Printf("Using wsproxy protocol %v", wsProxyMode) log.Printf("Using wsproxy protocol %v", wsProxyMode)

View File

@ -48,7 +48,7 @@ templ State(state *models.State, location *time.Location) {
</thead> </thead>
for _, agent := range state.Agents { for _, agent := range state.Agents {
<tr> <tr>
<td>{agent.PublicId}</td> <td>{string(agent.PublicId)}</td>
<td>{agent.StartTime.In(location).Format(time.DateTime)}</td> <td>{agent.StartTime.In(location).Format(time.DateTime)}</td>
<td>{agent.ExpiryTime.In(location).Format(time.DateTime)}</td> <td>{agent.ExpiryTime.In(location).Format(time.DateTime)}</td>
<td>{agent.EnvironmentInfo.Username}</td> <td>{agent.EnvironmentInfo.Username}</td>
@ -82,10 +82,10 @@ templ State(state *models.State, location *time.Location) {
</thead> </thead>
for _, client := range state.Clients { for _, client := range state.Clients {
<tr> <tr>
<td>{client.ClientId}</td> <td>{string(client.ClientId)}</td>
<td>{client.StartTime.In(location).Format(time.DateTime)}</td> <td>{client.StartTime.In(location).Format(time.DateTime)}</td>
<td>{client.SessionType}</td> <td>{string(client.SessionType)}</td>
<td>{client.PublicId}</td> <td>{string(client.PublicId)}</td>
<td>{client.EnvironmentInfo.Username}</td> <td>{client.EnvironmentInfo.Username}</td>
<td>{client.EnvironmentInfo.Hostname}</td> <td>{client.EnvironmentInfo.Hostname}</td>
<td>{client.EnvironmentInfo.OS}</td> <td>{client.EnvironmentInfo.OS}</td>