254 lines
6.2 KiB
Go
254 lines
6.2 KiB
Go
package converge
|
|
|
|
import (
|
|
"converge/pkg/comms"
|
|
"converge/pkg/concurrency"
|
|
"converge/pkg/iowrappers"
|
|
"converge/pkg/models"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type AgentConnection struct {
|
|
models.Agent
|
|
// server session
|
|
commChannel comms.CommChannel
|
|
}
|
|
|
|
var clientIdGenerator = concurrency.NewAtomicCounter()
|
|
|
|
type ClientConnection struct {
|
|
models.Client
|
|
agent net.Conn
|
|
client iowrappers.ReadWriteAddrCloser
|
|
}
|
|
|
|
func NewAgent(commChannel comms.CommChannel, publicId string, agentInfo comms.AgentInfo) *AgentConnection {
|
|
return &AgentConnection{
|
|
Agent: models.Agent{
|
|
PublicId: publicId,
|
|
StartTime: time.Now(),
|
|
AgentInfo: agentInfo,
|
|
},
|
|
commChannel: commChannel,
|
|
}
|
|
}
|
|
|
|
func NewClient(publicId string, clientConn iowrappers.ReadWriteAddrCloser,
|
|
agentConn net.Conn) *ClientConnection {
|
|
return &ClientConnection{
|
|
Client: models.Client{
|
|
PublicId: publicId,
|
|
ClientId: clientIdGenerator.IncrementAndGet(),
|
|
StartTime: time.Now(),
|
|
},
|
|
agent: agentConn,
|
|
client: clientConn,
|
|
}
|
|
}
|
|
|
|
type Admin struct {
|
|
// map of public id to agent
|
|
mutex sync.Mutex
|
|
agents map[string]*AgentConnection
|
|
clients []*ClientConnection
|
|
}
|
|
|
|
func NewAdmin() *Admin {
|
|
admin := Admin{
|
|
mutex: sync.Mutex{},
|
|
agents: make(map[string]*AgentConnection),
|
|
clients: make([]*ClientConnection, 0), // not strictly needed
|
|
}
|
|
return &admin
|
|
}
|
|
|
|
func (admin *Admin) logStatus() {
|
|
fmt := "%-20s %-20s %-20s %-10s %-15s %-20s\n"
|
|
log.Printf(fmt, "AGENT", "ACTIVE_SINCE", "EXPIRY_TIME",
|
|
"USER", "HOST", "OS")
|
|
for _, agent := range admin.agents {
|
|
agent.commChannel.Session.RemoteAddr()
|
|
log.Printf(fmt, agent.PublicId,
|
|
agent.StartTime.Format(time.DateTime),
|
|
agent.ExpiryTime.Format(time.DateTime),
|
|
agent.AgentInfo.Username,
|
|
agent.AgentInfo.Hostname,
|
|
agent.AgentInfo.OS)
|
|
}
|
|
log.Println("")
|
|
fmt = "%-10s %-20s %-20s %-20s %-20s\n"
|
|
log.Printf(fmt, "CLIENT", "AGENT", "ACTIVE_SINCE", "REMOTE_ADDRESS", "SESSION_TYPE")
|
|
for _, client := range admin.clients {
|
|
log.Printf(fmt,
|
|
strconv.Itoa(client.ClientId),
|
|
client.PublicId,
|
|
client.StartTime.Format(time.DateTime),
|
|
client.client.RemoteAddr(),
|
|
client.SessionType)
|
|
}
|
|
log.Printf("\n")
|
|
}
|
|
|
|
func (admin *Admin) addAgent(publicId string, agentInfo comms.AgentInfo, conn io.ReadWriteCloser) (*AgentConnection, error) {
|
|
admin.mutex.Lock()
|
|
defer admin.mutex.Unlock()
|
|
|
|
agent := admin.agents[publicId]
|
|
if agent != nil {
|
|
return nil, fmt.Errorf("A different agent with same PublicId '%s' already registered", publicId)
|
|
}
|
|
|
|
commChannel, err := comms.NewCommChannel(comms.ConvergeServer, conn)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
agent = NewAgent(commChannel, publicId, agentInfo)
|
|
admin.agents[publicId] = agent
|
|
admin.logStatus()
|
|
return agent, nil
|
|
}
|
|
|
|
func (admin *Admin) addClient(publicId string, clientConn iowrappers.ReadWriteAddrCloser) (*ClientConnection, error) {
|
|
admin.mutex.Lock()
|
|
defer admin.mutex.Unlock()
|
|
|
|
agent := admin.agents[publicId]
|
|
if agent == nil {
|
|
// we should setup on-demend connections ot agents later.
|
|
return nil, fmt.Errorf("No agent found for PublicId '%s'", publicId)
|
|
}
|
|
|
|
agentConn, err := agent.commChannel.Session.Open()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Println("Successful websocket connection to agent")
|
|
log.Println("Sending connection information to agent")
|
|
|
|
client := NewClient(publicId, clientConn, agentConn)
|
|
|
|
// Before using this connection for SSH we use it to send client metadata to the
|
|
// agent
|
|
err = comms.SendClientInfo(agentConn, comms.ClientInfo{
|
|
ClientId: client.ClientId,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
admin.clients = append(admin.clients, client)
|
|
admin.logStatus()
|
|
return client, nil
|
|
}
|
|
|
|
func (admin *Admin) RemoveAgent(publicId string) error {
|
|
admin.mutex.Lock()
|
|
defer admin.mutex.Unlock()
|
|
|
|
agent := admin.agents[publicId]
|
|
if agent == nil {
|
|
return fmt.Errorf("Cannot remove agent: '%s' not found", publicId)
|
|
}
|
|
log.Printf("Removing agent: '%s'", publicId)
|
|
err := agent.commChannel.Session.Close()
|
|
if err != nil {
|
|
log.Printf("Could not close yamux client session for '%s'\n", publicId)
|
|
}
|
|
delete(admin.agents, publicId)
|
|
admin.logStatus()
|
|
return nil
|
|
}
|
|
|
|
func (admin *Admin) RemoveClient(client *ClientConnection) error {
|
|
admin.mutex.Lock()
|
|
defer admin.mutex.Unlock()
|
|
|
|
log.Printf("Removing client: '%d' created at %s\n", client.ClientId,
|
|
client.StartTime.Format(time.DateTime))
|
|
// try to explicitly close connection to the agent.
|
|
_ = client.agent.Close()
|
|
_ = client.client.Close()
|
|
|
|
for i, _client := range admin.clients {
|
|
if _client.ClientId == client.ClientId {
|
|
admin.clients = append(admin.clients[:i], admin.clients[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
admin.logStatus()
|
|
return nil
|
|
}
|
|
|
|
func (admin *Admin) Register(publicId string, conn io.ReadWriteCloser,
|
|
userPassword comms.UserPassword) error {
|
|
defer conn.Close()
|
|
|
|
serverInfo := comms.ServerInfo{
|
|
UserPassword: userPassword,
|
|
}
|
|
|
|
agentInfo, err := comms.ServerInitialization(conn, serverInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
agent, err := admin.addAgent(publicId, agentInfo, conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
admin.RemoveAgent(publicId)
|
|
}()
|
|
|
|
go func() {
|
|
comms.ListenForAgentEvents(agent.commChannel.SideChannel,
|
|
func(info comms.AgentInfo) {
|
|
agent.AgentInfo = info
|
|
admin.logStatus()
|
|
},
|
|
func(session comms.SessionInfo) {
|
|
log.Println("Recceived sessioninfo ", session)
|
|
for _, client := range admin.clients {
|
|
// a bit hacky. There should be at most one client that has an unset session
|
|
// Very unlikely for multiple sessions to start at the same point in time.
|
|
if strconv.Itoa(client.ClientId) == session.ClientId {
|
|
client.SessionType = session.SessionType
|
|
break
|
|
}
|
|
}
|
|
},
|
|
func(expiry comms.ExpiryTimeUpdate) {
|
|
agent.ExpiryTime = expiry.ExpiryTime
|
|
admin.logStatus()
|
|
})
|
|
}()
|
|
|
|
go log.Printf("AgentConnection registered: '%s'\n", publicId)
|
|
for !agent.commChannel.Session.IsClosed() {
|
|
time.Sleep(250 * time.Millisecond)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (admin *Admin) Connect(publicId string, conn iowrappers.ReadWriteAddrCloser) error {
|
|
defer conn.Close()
|
|
client, err := admin.addClient(publicId, conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
admin.RemoveClient(client)
|
|
}()
|
|
log.Printf("Connecting client and agent: '%s'\n", publicId)
|
|
|
|
iowrappers.SynchronizeStreams(client.client, client.agent)
|
|
return nil
|
|
}
|