2026-04-06 00:20:51 -05:00

599 lines
22 KiB
Go

package connector
import (
"AdaptixServer/core/profile"
"AdaptixServer/core/utils/krypt"
"AdaptixServer/core/utils/logs"
"AdaptixServer/core/utils/token"
"crypto/tls"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/Adaptix-Framework/axc2"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
type Teamserver interface {
CreateOTP(otpType string, data interface{}) (string, error)
ValidateOTP(token string) (string, interface{}, bool)
TsClientExists(username string) bool
TsClientDisconnect(username string)
TsClientConnect(username string, version string, socket *websocket.Conn, clientType uint8, consoleTeamMode bool, subscriptions []string)
TsClientSync(username string)
TsClientSubscribe(username string, categories []string, consoleTeamMode *bool)
TsListenerList() (string, error)
TsListenerStart(listenerName string, configType string, config string, createTime int64, watermark string, customData []byte) error
TsListenerEdit(listenerName string, configType string, config string) error
TsListenerStop(listenerName string, configType string) error
TsListenerPause(listenerName string, configType string) error
TsListenerResume(listenerName string, configType string) error
TsListenerGetProfile(listenerName string) (string, []byte, error)
TsListenerInteralHandler(watermark string, data []byte) (string, error)
TsAgentList() (string, error)
TsAgentIsExists(agentId string) bool
TsAgentCreate(agentCrc string, agentId string, beat []byte, listenerName string, ExternalIP string, Async bool) (adaptix.AgentData, error)
TsAgentProcessData(agentId string, bodyData []byte) error
TsAgentGetHostedAll(agentId string, maxDataSize int) ([]byte, error)
TsAgentCommand(agentName string, agentId string, clientName string, hookId string, handlerId string, cmdline string, ui bool, args map[string]any) error
TsAgentBuildSyncOnce(agentName string, config string, listenersName []string) ([]byte, string, error)
TsAgentUpdateData(newAgentData adaptix.AgentData) error
TsAgentUpdateDataPartial(agentId string, updateData interface{}) error
TsAgentTerminate(agentId string, terminateTaskId string) error
TsAgentRemove(agentId string) error
TsAgentConsoleRemove(agentId string) error
TsAgentSetTick(agentId string, listenerName string) error
TsAgentTickUpdate()
TsAgentConsoleOutput(agentId string, messageType int, message string, clearText string, store bool)
TsAgentConsoleOutputClient(agentId string, client string, messageType int, message string, clearText string)
TsAgentConsoleErrorCommand(agentId string, client string, cmdline string, message string, HookId string, HandlerId string)
TsTaskCreate(agentId string, cmdline string, client string, taskData adaptix.TaskData)
TsTaskUpdate(agentId string, data adaptix.TaskData)
TsTaskGetAvailableAll(agentId string, availableSize int) ([]adaptix.TaskData, error)
TsTaskCancel(agentId string, taskId string) error
TsTaskDelete(agentId string, taskId string) error
TsTaskPostHook(hookData adaptix.TaskData, jobIndex int) error
TsTaskSave(hookData adaptix.TaskData) error
TsTaskListCompleted(agentId string, limit int, offset int) ([]byte, error)
TsChatSendMessage(username string, message string)
TsDownloadAdd(agentId string, fileId string, fileName string, fileSize int64) error
TsDownloadUpdate(fileId string, state int, data []byte) error
TsDownloadClose(fileId string, reason int) error
TsDownloadList() (string, error)
TsDownloadSync(fileId string) (string, []byte, error)
TsDownloadDelete(fileId []string) error
TsDownloadGetFilepath(fileId string) (string, error)
TsUploadGetFilepath(fileId string) (string, error)
TsUploadGetFileContent(fileId string) ([]byte, error)
TsScreenshotList() (string, error)
TsScreenshotGetImage(screenId string) ([]byte, error)
TsScreenshotDelete(screenId string) error
TsScreenshotNote(screenId string, note string) error
TsCredentilsList() (string, error)
TsCredentilsAdd(creds []map[string]interface{}) error
TsCredentilsEdit(credId string, username string, password string, realm string, credType string, tag string, storage string, host string) error
TsCredentilsDelete(credsId []string) error
TsCredentialsSetTag(credsId []string, tag string) error
TsTargetsList() (string, error)
TsTargetsAdd(targets []map[string]interface{}) error
TsTargetsEdit(targetId string, computer string, domain string, address string, os int, osDesk string, tag string, info string, alive bool) error
TsTargetDelete(targetsId []string) error
TsTargetSetTag(targetsId []string, tag string) error
TsTargetRemoveSessions(agentsId []string) error
TsClientGuiDisksWindows(taskData adaptix.TaskData, drives []adaptix.ListingDrivesDataWin)
TsClientGuiFilesStatus(taskData adaptix.TaskData)
TsClientGuiFilesWindows(taskData adaptix.TaskData, path string, files []adaptix.ListingFileDataWin)
TsClientGuiFilesUnix(taskData adaptix.TaskData, path string, files []adaptix.ListingFileDataUnix)
TsClientGuiProcessWindows(taskData adaptix.TaskData, process []adaptix.ListingProcessDataWin)
TsClientGuiProcessUnix(taskData adaptix.TaskData, process []adaptix.ListingProcessDataUnix)
TsAgentTerminalCreateChannel(terminalData string, wsconn *websocket.Conn) error
TsAgentBuildCreateChannel(buildData string, wsconn *websocket.Conn) error
TsTunnelList() (string, error)
TsTunnelClientStart(AgentId string, Listen bool, Type int, Info string, Lhost string, Lport int, Client string, Thost string, Tport int, AuthUser string, AuthPass string) (string, error)
TsTunnelClientNewChannel(TunnelData string, wsconn *websocket.Conn) error
TsTunnelClientStop(TunnelId string, Client string) error
TsTunnelStop(TunnelId string) error
TsTunnelClientSetInfo(TunnelId string, Info string) error
TsTunnelCreateSocks4(AgentId string, Info string, Lhost string, Lport int) (string, error)
TsTunnelCreateSocks5(AgentId string, Info string, Lhost string, Lport int, UseAuth bool, Username string, Password string) (string, error)
TsTunnelCreateLportfwd(AgentId string, Info string, Lhost string, Lport int, Thost string, Tport int) (string, error)
TsTunnelStopSocks(AgentId string, Port int)
TsTunnelStopLportfwd(AgentId string, Port int)
TsTunnelStopRportfwd(AgentId string, Port int)
TsTunnelConnectionClose(channelId int, writeOnly bool)
TsTunnelConnectionHalt(channelId int, errorCode byte)
TsTunnelConnectionResume(AgentId string, channelId int, ioDirect bool)
TsTunnelConnectionData(channelId int, data []byte)
TsServiceLoad(configPath string) error
TsServiceUnload(serviceName string) error
TsServiceCall(serviceName string, operator string, function string, args string)
TsServiceList() (string, error)
TsAxScriptLoadUser(name string, script string) error
TsAxScriptUnloadUser(name string) error
TsAxScriptList() (string, error)
TsAxScriptCommands() (string, error)
TsAxScriptResolveHooks(agentName string, agentId string, listenerRegName string, os int, cmdline string, args map[string]interface{}) (string, string, bool, error)
TsAxScriptIsServerHook(id string) bool
TsAxScriptParseAndExecute(agentId string, username string, cmdline string) error
AxGetAgentContext(agentId string) (agentName string, listenerRegName string, osType int, err error)
}
type TsConnector struct {
Interface string
Port int
Hash string
OnlyHash bool
Operators map[string]string
Endpoint string
Cert string
Key string
ManagePasswordHash string
httpServer *profile.TsHttpServer
Engine *gin.Engine
teamserver Teamserver
apiGroup *gin.RouterGroup
publicGroup *gin.RouterGroup
dynamicEndpoints map[string]gin.HandlerFunc
dynamicPublicEndpoints map[string]gin.HandlerFunc
}
func tlsVersionFromString(v string) (uint16, error) {
s := strings.TrimSpace(strings.ToUpper(v))
s = strings.ReplaceAll(s, "_", "")
s = strings.ReplaceAll(s, "-", "")
switch s {
case "", "DEFAULT":
return 0, nil
case "TLS10", "TLS1.0":
return tls.VersionTLS10, nil
case "TLS11", "TLS1.1":
return tls.VersionTLS11, nil
case "TLS12", "TLS1.2":
return tls.VersionTLS12, nil
case "TLS13", "TLS1.3":
return tls.VersionTLS13, nil
default:
return 0, errors.New("unsupported TLS version: " + v)
}
}
func tlsCipherSuiteFromString(name string) (uint16, error) {
key := strings.TrimSpace(strings.ToUpper(name))
key = strings.ReplaceAll(key, "-", "_")
key = strings.ReplaceAll(key, " ", "_")
switch key {
case "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":
return tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, nil
case "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":
return tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, nil
case "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
return tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, nil
case "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
return tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, nil
case "TLS_RSA_WITH_AES_128_GCM_SHA256":
return tls.TLS_RSA_WITH_AES_128_GCM_SHA256, nil
case "TLS_RSA_WITH_AES_256_GCM_SHA384":
return tls.TLS_RSA_WITH_AES_256_GCM_SHA384, nil
default:
return 0, errors.New("unsupported cipher suite: " + name)
}
}
func limitTimeoutMiddleware(cfg profile.TsHTTPConfig) gin.HandlerFunc {
return func(c *gin.Context) {
timeout := time.Duration(cfg.RequestTimeoutSec) * time.Second
if timeout <= 0 {
timeout = 300 * time.Second
}
msg := cfg.RequestTimeoutMessage
if msg == "" {
msg = "504 Gateway Timeout"
}
handler := http.TimeoutHandler(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Next()
}),
timeout,
msg,
)
handler.ServeHTTP(c.Writer, c.Request)
}
}
func default404Middleware(httpError profile.TsHttpError) gin.HandlerFunc {
return func(c *gin.Context) {
if len(c.Errors) > 0 && !c.Writer.Written() {
for header, value := range httpError.Headers {
c.Header(header, value)
}
c.String(httpError.Status, httpError.PageContent)
c.Abort()
return
}
c.Next()
if len(c.Errors) > 0 && !c.Writer.Written() {
for header, value := range httpError.Headers {
c.Header(header, value)
}
c.String(httpError.Status, httpError.PageContent)
}
}
}
func NewTsConnector(ts Teamserver, tsProfile profile.TsProfile, httpServer profile.TsHttpServer) (*TsConnector, error) {
gin.SetMode(gin.ReleaseMode)
var connector = new(TsConnector)
connector.Engine = gin.New()
connector.Engine.Use(gin.Recovery())
connector.teamserver = ts
connector.Interface = tsProfile.Interface
connector.Port = tsProfile.Port
connector.Endpoint = tsProfile.Endpoint
connector.Hash = krypt.SHA256([]byte(tsProfile.Password))
connector.OnlyHash = tsProfile.OnlyPassword
connector.Operators = make(map[string]string, len(tsProfile.Operators))
for username, password := range tsProfile.Operators {
connector.Operators[username] = krypt.SHA256([]byte(password))
}
connector.Key = tsProfile.Key
connector.Cert = tsProfile.Cert
if tsProfile.ManagePassword != "" {
connector.ManagePasswordHash = krypt.SHA256([]byte(tsProfile.ManagePassword))
}
if httpServer.Error == nil {
httpServer.Error = &profile.TsHttpError{}
}
if httpServer.Error.Status == 0 {
httpServer.Error.Status = 404
}
if httpServer.Error.Headers == nil {
httpServer.Error.Headers = map[string]string{}
}
if httpServer.HTTP == nil {
httpServer.HTTP = &profile.TsHTTPConfig{}
}
if httpServer.HTTP.MaxHeaderBytes == 0 {
httpServer.HTTP.MaxHeaderBytes = 8192
}
if httpServer.HTTP.RequestTimeoutSec == 0 {
httpServer.HTTP.RequestTimeoutSec = 300
}
if httpServer.HTTP.RequestTimeoutMessage == "" {
httpServer.HTTP.RequestTimeoutMessage = "504 Gateway Timeout"
}
if httpServer.TLS == nil {
httpServer.TLS = &profile.TsTLSConfig{}
}
if httpServer.TLS.MinVersion == "" {
httpServer.TLS.MinVersion = "TLS1.2"
}
if httpServer.TLS.MaxVersion == "" {
httpServer.TLS.MaxVersion = "TLS1.3"
}
connector.httpServer = &httpServer
connector.dynamicEndpoints = make(map[string]gin.HandlerFunc)
connector.dynamicPublicEndpoints = make(map[string]gin.HandlerFunc)
httpCfg := *httpServer.HTTP
httpErr := *httpServer.Error
public_group := connector.Engine.Group(tsProfile.Endpoint)
public_group.Use(limitTimeoutMiddleware(httpCfg), default404Middleware(httpErr))
connector.publicGroup = public_group
login_group := connector.Engine.Group(tsProfile.Endpoint)
login_group.Use(limitTimeoutMiddleware(httpCfg), default404Middleware(httpErr))
{
login_group.POST("/login", connector.tcLogin)
login_group.POST("/refresh", token.RefreshTokenHandler)
}
otp_group := connector.Engine.Group(tsProfile.Endpoint)
otp_group.Use(connector.validateOTPMiddleware(), default404Middleware(httpErr))
{
otp_group.POST("/otp/upload/temp", connector.tcOTP_UploadTemp)
otp_group.GET("/otp/download/sync", connector.tcOTP_DownloadSync)
otp_group.GET("/connect", connector.tcConnectOTP)
otp_group.GET("/channel", connector.tcChannelOTP)
}
api_group := connector.Engine.Group(tsProfile.Endpoint)
api_group.Use(limitTimeoutMiddleware(httpCfg), token.ValidateAccessToken(), default404Middleware(httpErr))
connector.apiGroup = api_group
{
api_group.POST("/sync", connector.tcSync)
api_group.POST("/subscribe", connector.tcSubscribe)
api_group.POST("/otp/generate", connector.tcOTP_Generate)
api_group.GET("/listener/list", connector.TcListenerList)
api_group.POST("/listener/create", connector.TcListenerStart)
api_group.POST("/listener/edit", connector.TcListenerEdit)
api_group.POST("/listener/stop", connector.TcListenerStop)
api_group.POST("/listener/pause", connector.TcListenerPause)
api_group.POST("/listener/resume", connector.TcListenerResume)
api_group.GET("/agent/list", connector.TcAgentList)
api_group.POST("/agent/generate", connector.TcAgentGenerate)
api_group.POST("/agent/remove", connector.TcAgentRemove)
api_group.POST("/agent/command/file", connector.TcAgentCommandFile)
api_group.POST("/agent/command/execute", connector.TcAgentCommandExecute)
api_group.POST("/agent/command/raw", connector.TcAgentCommandRaw)
api_group.POST("/agent/console/remove", connector.TcAgentConsoleRemove)
api_group.POST("/agent/set/tag", connector.TcAgentSetTag)
api_group.POST("/agent/set/mark", connector.TcAgentSetMark)
api_group.POST("/agent/set/color", connector.TcAgentSetColor)
api_group.POST("/agent/update/data", connector.TcAgentUpdateData)
api_group.GET("/agent/task/list", connector.TcAgentTaskList)
api_group.POST("/agent/task/cancel", connector.TcAgentTaskCancel)
api_group.POST("/agent/task/delete", connector.TcAgentTaskDelete)
api_group.POST("/agent/task/hook", connector.TcAgentTaskHook)
api_group.POST("/agent/task/save", connector.TcAgentTaskSave)
api_group.POST("/chat/send", connector.TcChatSendMessage)
api_group.GET("/download/list", connector.TcDownloadList)
api_group.POST("/download/sync", connector.TcGuiDownloadSync)
api_group.POST("/download/delete", connector.TcGuiDownloadDelete)
api_group.GET("/screen/list", connector.TcScreenshotList)
api_group.GET("/screen/image", connector.TcScreenshotGetImage)
api_group.POST("/screen/setnote", connector.TcScreenshotSetNote)
api_group.POST("/screen/remove", connector.TcScreenshotRemove)
api_group.GET("/creds/list", connector.TcCredentialsList)
api_group.POST("/creds/add", connector.TcCredentialsAdd)
api_group.POST("/creds/edit", connector.TcCredentialsEdit)
api_group.POST("/creds/remove", connector.TcCredentialsRemove)
api_group.POST("/creds/set/tag", connector.TcCredentialsSetTag)
api_group.GET("/targets/list", connector.TcTargetsList)
api_group.POST("/targets/add", connector.TcTargetsAdd)
api_group.POST("/targets/edit", connector.TcTargetEdit)
api_group.POST("/targets/remove", connector.TcTargetRemove)
api_group.POST("/targets/set/tag", connector.TcTargetSetTag)
api_group.GET("/tunnel/list", connector.TcTunnelList)
api_group.POST("/tunnel/start/socks5", connector.TcTunnelStartSocks5)
api_group.POST("/tunnel/start/socks4", connector.TcTunnelStartSocks4)
api_group.POST("/tunnel/start/lportfwd", connector.TcTunnelStartLpf)
api_group.POST("/tunnel/start/rportfwd", connector.TcTunnelStartRpf)
api_group.POST("/tunnel/stop", connector.TcTunnelStop)
api_group.POST("/tunnel/set/info", connector.TcTunnelSetIno)
api_group.GET("/service/list", connector.TcServiceList)
//api_group.POST("/service/load", connector.TcServiceLoad)
//api_group.POST("/service/unload", connector.TcServiceUnload)
api_group.POST("/service/call", connector.TcServiceCall)
//api_group.POST("/axscript/list", connector.TcAxScriptList)
//api_group.POST("/axscript/commands", connector.TcAxScriptCommands)
//api_group.POST("/axscript/load", connector.TcAxScriptLoad)
//api_group.POST("/axscript/unload", connector.TcAxScriptUnload)
}
connector.Engine.NoRoute(limitTimeoutMiddleware(httpCfg), default404Middleware(httpErr), func(c *gin.Context) { _ = c.Error(errors.New("NoRoute")) })
return connector, nil
}
func (tc *TsConnector) endpointKey(method string, path string) string {
return method + ":" + path
}
func (tc *TsConnector) RegisterEndpoint(method string, path string, handler func(c *gin.Context)) error {
if tc.apiGroup == nil {
return errors.New("api group not initialized")
}
key := tc.endpointKey(method, path)
if _, exists := tc.dynamicEndpoints[key]; !exists {
dispatcher := func(c *gin.Context) {
if h, ok := tc.dynamicEndpoints[key]; ok {
h(c)
} else {
c.JSON(404, gin.H{"error": "endpoint not found"})
}
}
switch method {
case "GET":
tc.apiGroup.GET(path, dispatcher)
case "POST":
tc.apiGroup.POST(path, dispatcher)
case "PUT":
tc.apiGroup.PUT(path, dispatcher)
case "DELETE":
tc.apiGroup.DELETE(path, dispatcher)
case "PATCH":
tc.apiGroup.PATCH(path, dispatcher)
default:
return errors.New("unsupported HTTP method: " + method)
}
}
tc.dynamicEndpoints[key] = handler
return nil
}
func (tc *TsConnector) UnregisterEndpoint(method string, path string) error {
key := tc.endpointKey(method, path)
if _, exists := tc.dynamicEndpoints[key]; !exists {
return errors.New("endpoint not registered: " + key)
}
delete(tc.dynamicEndpoints, key)
return nil
}
func (tc *TsConnector) EndpointExists(method string, path string) bool {
key := tc.endpointKey(method, path)
_, exists := tc.dynamicEndpoints[key]
return exists
}
func (tc *TsConnector) RegisterPublicEndpoint(method string, path string, handler func(c *gin.Context)) error {
if tc.publicGroup == nil {
return errors.New("public group not initialized")
}
key := tc.endpointKey(method, path)
if _, exists := tc.dynamicPublicEndpoints[key]; !exists {
dispatcher := func(c *gin.Context) {
if h, ok := tc.dynamicPublicEndpoints[key]; ok {
h(c)
} else {
c.JSON(404, gin.H{"error": "endpoint not found"})
}
}
switch method {
case "GET":
tc.publicGroup.GET(path, dispatcher)
case "POST":
tc.publicGroup.POST(path, dispatcher)
case "PUT":
tc.publicGroup.PUT(path, dispatcher)
case "DELETE":
tc.publicGroup.DELETE(path, dispatcher)
case "PATCH":
tc.publicGroup.PATCH(path, dispatcher)
default:
return errors.New("unsupported HTTP method: " + method)
}
}
tc.dynamicPublicEndpoints[key] = handler
return nil
}
func (tc *TsConnector) UnregisterPublicEndpoint(method string, path string) error {
key := tc.endpointKey(method, path)
if _, exists := tc.dynamicPublicEndpoints[key]; !exists {
return errors.New("public endpoint not registered: " + key)
}
delete(tc.dynamicPublicEndpoints, key)
return nil
}
func (tc *TsConnector) PublicEndpointExists(method string, path string) bool {
key := tc.endpointKey(method, path)
_, exists := tc.dynamicPublicEndpoints[key]
return exists
}
func (tc *TsConnector) Start(finished *chan bool) {
host := fmt.Sprintf("%s:%d", tc.Interface, tc.Port)
if tc.httpServer == nil || tc.httpServer.HTTP == nil || tc.httpServer.TLS == nil {
logs.Error("", "HTTP server configuration is not initialized")
return
}
httpCfg := *tc.httpServer.HTTP
tlsCfgProfile := *tc.httpServer.TLS
minTLS, err := tlsVersionFromString(tlsCfgProfile.MinVersion)
if err != nil {
logs.Error("", "Invalid TLS min_version: "+err.Error())
return
}
maxTLS, err := tlsVersionFromString(tlsCfgProfile.MaxVersion)
if err != nil {
logs.Error("", "Invalid TLS max_version: "+err.Error())
return
}
if minTLS != 0 && maxTLS != 0 && minTLS > maxTLS {
logs.Error("", "Invalid TLS version range: min_version (%v) must be <= max_version (%v)", tlsCfgProfile.MinVersion, tlsCfgProfile.MaxVersion)
return
}
var cipherSuites []uint16
if tlsCfgProfile.CipherSuites != nil {
cipherSuites = make([]uint16, 0, len(tlsCfgProfile.CipherSuites))
for _, cs := range tlsCfgProfile.CipherSuites {
id, err := tlsCipherSuiteFromString(cs)
if err != nil {
logs.Error("", "Invalid TLS cipher_suites: "+err.Error())
return
}
cipherSuites = append(cipherSuites, id)
}
}
tlsConfig := &tls.Config{
PreferServerCipherSuites: false,
}
if minTLS != 0 {
tlsConfig.MinVersion = minTLS
}
if maxTLS != 0 {
tlsConfig.MaxVersion = maxTLS
}
if cipherSuites != nil {
tlsConfig.CipherSuites = cipherSuites
}
if tlsCfgProfile.PreferServerCipherSuites != nil {
tlsConfig.PreferServerCipherSuites = *tlsCfgProfile.PreferServerCipherSuites
}
server := &http.Server{
Addr: host,
Handler: tc.Engine,
TLSConfig: tlsConfig,
ReadTimeout: time.Duration(httpCfg.ReadTimeoutSec) * time.Second,
WriteTimeout: time.Duration(httpCfg.WriteTimeoutSec) * time.Second,
IdleTimeout: time.Duration(httpCfg.IdleTimeoutSec) * time.Second,
MaxHeaderBytes: httpCfg.MaxHeaderBytes,
}
if httpCfg.ReadHeaderTimeoutSec > 0 {
server.ReadHeaderTimeout = time.Duration(httpCfg.ReadHeaderTimeoutSec) * time.Second
}
server.SetKeepAlivesEnabled(!httpCfg.DisableKeepAlives)
if httpCfg.EnableHTTP2 != nil && !*httpCfg.EnableHTTP2 {
server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
}
err = server.ListenAndServeTLS(tc.Cert, tc.Key)
//err := tc.Engine.RunTLS(host, tc.Cert, tc.Key)
if err != nil {
logs.Error("", "Failed to start HTTP Server: "+err.Error())
return
}
*finished <- true
}