2021-08-19 17:49:43 +00:00
package main
import (
"context"
2021-10-21 21:49:42 +00:00
"crypto/rand"
2021-08-19 20:58:50 +00:00
"crypto/sha256"
2021-08-19 17:49:43 +00:00
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path"
2021-10-01 02:48:11 +00:00
"path/filepath"
2021-08-19 17:49:43 +00:00
"regexp"
"runtime"
2021-08-30 20:05:15 +00:00
"strconv"
2021-08-19 17:49:43 +00:00
"strings"
2021-09-23 23:01:13 +00:00
"sync"
2021-08-19 17:49:43 +00:00
"time"
"git.sequentialread.com/forest/easypki.git/pkg/easypki"
easypkiStore "git.sequentialread.com/forest/easypki.git/pkg/store"
2021-09-14 22:31:42 +00:00
netstat "git.sequentialread.com/forest/go-netstat"
2021-09-30 22:19:28 +00:00
child "git.sequentialread.com/forest/greenhouse-daemon/child-process-service"
2021-08-19 17:49:43 +00:00
greenhousePKI "git.sequentialread.com/forest/greenhouse/pki"
errors "git.sequentialread.com/forest/pkg-errors"
)
type DaemonConfig struct {
APIToken string
ServerName string
TunnelsEnabled bool
GUITunnels [ ] GUITunnel
}
type DaemonAPI struct {
2021-09-23 23:01:13 +00:00
Config * DaemonConfig
ConfigurationMutex sync . Mutex
ConfigService * ConfigService
TenantInfo * TenantInfo
HTTPClient * http . Client
DaemonPath string
CloudURL string
UseUnixSockets bool
CACertFile string
TLSCertFile string
TLSKeyFile string
2021-08-19 17:49:43 +00:00
2021-09-30 22:19:28 +00:00
ThresholdService * child . ChildProcessService
CaddyService * child . ChildProcessService
2021-10-01 00:22:40 +00:00
LogManager child . LogManager
2021-08-19 17:49:43 +00:00
ApplyConfigStatuses [ ] string
ApplyConfigStatusIndex int
ApplyConfigStatusError string
2021-10-21 21:49:42 +00:00
TelemetryID string
2021-08-19 17:49:43 +00:00
}
type ThresholdConfig struct {
DebugLog bool
ClientId string
GreenhouseDomain string
GreenhouseAPIToken string
GreenhouseThresholdPort int
CaCertificate string
// TODO rename Tls to TLS
ClientTlsKey string
ClientTlsCertificate string
AdminUnixSocket string
AdminAPIPort int
AdminAPICACertificateFile string
AdminAPITlsKeyFile string
AdminAPITlsCertificateFile string
// TODO Metrics MetricsConfig
}
type Status struct {
2021-09-30 22:19:28 +00:00
NeedsAPIToken bool ` json:"needs_api_token" `
HashedToken string ` json:"hashed_api_token" `
Threshold * child . ChildProcessStatus ` json:"threshold" `
Caddy * child . ChildProcessStatus ` json:"caddy" `
ApplyConfigStatuses [ ] string ` json:"apply_config_statuses" `
ApplyConfigStatusIndex int ` json:"apply_config_status_index" `
ApplyConfigStatusError string ` json:"apply_config_status_error" `
TenantInfo * TenantInfo ` json:"tenant_info" `
ServerName string ` json:"server_name" `
GUITunnels [ ] GUITunnel ` json:"tunnels" `
UpdateTenantInfoMessage string ` json:"update_tenant_info_message" `
2021-10-21 21:49:42 +00:00
DaemonTelemetryID string ` json:"daemon_telemetry_id" `
2021-08-19 17:49:43 +00:00
}
type TenantInfo struct {
ThresholdServers [ ] string
ClientStates map [ string ] ThresholdClientState
Listeners [ ] ThresholdTunnel
AuthorizedDomains [ ] string
PortStart int
PortEnd int
EmailAddress string
}
type GUITunnel struct {
2021-08-22 01:29:54 +00:00
Protocol string ` json:"protocol" `
HasSubdomain bool ` json:"has_subdomain" `
Subdomain string ` json:"subdomain" `
Domain string ` json:"domain" `
PublicPort int ` json:"public_port" `
DestinationType string ` json:"destination_type" `
DestinationHostname string ` json:"destination_hostname" `
DestinationPort int ` json:"destination_port" `
DestinationFolderPath string ` json:"destination_folder_path" `
2021-08-19 17:49:43 +00:00
}
2021-09-14 22:31:42 +00:00
type ListeningSocket struct {
LocalAddr string
RemoteAddr string
State string
PID int
ProcessCommand string
}
2021-08-19 17:49:43 +00:00
var nonAlphanumericRegexp * regexp . Regexp
func ( tunnel * GUITunnel ) GetServiceId ( ) string {
if nonAlphanumericRegexp == nil {
nonAlphanumericRegexp = regexp . MustCompile ( "[^a-zA-Z0-9_-]+" )
}
destination := fmt . Sprintf ( "localhost_%d" , tunnel . DestinationPort )
if tunnel . DestinationType == "host_port" {
hostname := strings . ToLower ( tunnel . DestinationHostname )
hostname = nonAlphanumericRegexp . ReplaceAllString ( hostname , "_" )
destination = fmt . Sprintf ( "%s_%d" , hostname , tunnel . DestinationPort )
}
2021-08-22 01:29:54 +00:00
if tunnel . DestinationType == "folder" {
2021-09-21 17:19:13 +00:00
hashBytes := sha256 . Sum256 ( [ ] byte ( fmt . Sprintf ( "094wbnmpj905=-0m24jh65e0-j,bfdio5490h6%s" , tunnel . DestinationFolderPath ) ) )
destination = fmt . Sprintf ( "%x" , hashBytes [ 10 : 20 ] )
2021-08-19 17:49:43 +00:00
}
2021-08-22 01:29:54 +00:00
return fmt . Sprintf ( "autogenerated_%s_%s" , tunnel . DestinationType , destination )
2021-08-19 17:49:43 +00:00
}
2021-09-21 17:19:13 +00:00
func ( tunnel * GUITunnel ) GetStringDisplay ( omitDestination bool ) string {
2021-08-19 17:49:43 +00:00
domain := tunnel . Domain
if tunnel . HasSubdomain {
2021-09-21 17:19:13 +00:00
if tunnel . Subdomain == "" {
domain = fmt . Sprintf ( "<blank>.%s" , domain )
} else {
domain = fmt . Sprintf ( "%s.%s" , tunnel . Subdomain , domain )
}
2021-08-19 17:49:43 +00:00
}
portPart := ""
if tunnel . Protocol != "https" {
portPart = fmt . Sprintf ( ":%d" , tunnel . PublicPort )
}
2021-09-21 17:19:13 +00:00
sourcePart := fmt . Sprintf ( "%s://%s%s" , tunnel . Protocol , domain , portPart )
if omitDestination {
return sourcePart
}
2021-08-19 17:49:43 +00:00
destination := fmt . Sprintf ( "127.0.0.1:%d" , tunnel . DestinationPort )
if tunnel . DestinationType == "host_port" {
destination = fmt . Sprintf ( "%s:%d" , tunnel . DestinationHostname , tunnel . DestinationPort )
}
2021-08-22 01:29:54 +00:00
if tunnel . DestinationType == "folder" {
destination = tunnel . DestinationFolderPath
2021-08-19 17:49:43 +00:00
}
2021-09-21 17:19:13 +00:00
return fmt . Sprintf ( "%s -> %s" , sourcePart , destination )
2021-08-19 17:49:43 +00:00
}
type ThresholdTunnel struct {
ClientId string
ListenPort int
ListenAddress string
ListenHostnameGlob string
BackEndService string
HaProxyProxyProtocol bool
}
type ThresholdClientState struct {
CurrentState string
LastState string
}
const greenhouseThresholdPort = 9056
const daemonAdminPort = 9572
const daemonSocketFile = "/var/run/greenhouse-daemon.sock"
const thresholdAdminPort = 9573
const thresholdAdminSocketFile = "/var/run/greenhouse-daemon-threshold.sock"
// TODO this is currently hardcoded in caddy-config.json
const caddyAdminPort = 9574
const caddyAdminSocketFile = "/var/run/greenhouse-daemon-caddy-admin.sock"
const mainCAName = "greenhouse_daemon_localhost_ca"
2021-10-01 00:22:40 +00:00
var log child . LogManager
2021-10-21 21:49:42 +00:00
var daemonTelemetryId string
var daemonTelemetryAccount string
2021-08-30 20:05:15 +00:00
2021-08-19 17:49:43 +00:00
func main ( ) {
2021-08-30 20:05:15 +00:00
2021-08-19 17:49:43 +00:00
var err error
daemonPath := os . Getenv ( "GREENHOUSE_DAEMON_PATH" )
2021-10-01 02:48:11 +00:00
daemonExecutablePath := daemonPath
2021-08-19 17:49:43 +00:00
if daemonPath == "" {
if runtime . GOOS == "linux" || runtime . GOOS == "bsd" {
daemonPath = "/opt/greenhouse-daemon"
} else if runtime . GOOS == "darwin" {
daemonPath = "/Library/Application Support/greenhouse-daemon"
} else if runtime . GOOS == "windows" {
2021-10-01 00:22:40 +00:00
daemonPath = fmt . Sprintf ( ` %s\greenhouse-background-service ` , os . Getenv ( "ProgramData" ) )
2021-10-15 15:30:25 +00:00
daemonExecutablePath = "C:\\Program Files (x86)\\greenhouse\\background-service"
2021-10-01 02:48:11 +00:00
executableLocation , err := os . Executable ( )
if err == nil {
daemonExecutablePath = filepath . Dir ( executableLocation )
}
2021-08-19 17:49:43 +00:00
} else {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because operating system '%s' is not supported yet\n\n" , runtime . GOOS )
2021-08-19 17:49:43 +00:00
}
}
2021-10-01 02:48:11 +00:00
log = child . NewDaemonLogManager ( daemonPath , filepath . Join ( daemonPath , "daemon" ) )
2021-10-01 00:22:40 +00:00
go log . ConsumeFromChannel ( )
2021-08-30 20:05:15 +00:00
2021-08-19 17:49:43 +00:00
cloudURL := os . Getenv ( "GREENHOUSE_DAEMON_CLOUD_URL" )
if cloudURL == "" {
2021-09-23 23:01:13 +00:00
cloudURL = "https://greenhouse-alpha.server.garden"
2021-08-19 17:49:43 +00:00
}
useUnixSockets := false
useUnixSocketsString := strings . ToLower ( os . Getenv ( "GREENHOUSE_DAEMON_USE_UNIX_SOCKETS" ) )
if useUnixSocketsString == "yes" || useUnixSocketsString == "true" || useUnixSocketsString == "1" || useUnixSocketsString == "t" || useUnixSocketsString == "y" {
useUnixSockets = true
}
2021-09-27 16:33:45 +00:00
thresholdExecutable := "greenhouse-threshold"
2021-08-19 17:49:43 +00:00
if runtime . GOOS == "windows" {
2021-09-27 16:33:45 +00:00
thresholdExecutable = "greenhouse-threshold.exe"
2021-08-19 17:49:43 +00:00
}
2021-10-01 02:48:11 +00:00
thresholdExecutable = filepath . Join ( daemonExecutablePath , thresholdExecutable )
2021-08-19 17:49:43 +00:00
thresholdArguments := [ ] string { "-mode" , "client" , "-configFile" , "threshold-config.json" }
2021-09-27 16:33:45 +00:00
caddyExecutable := "greenhouse-caddy"
2021-08-19 17:49:43 +00:00
if runtime . GOOS == "windows" {
2021-09-27 16:33:45 +00:00
caddyExecutable = "greenhouse-caddy.exe"
2021-08-19 17:49:43 +00:00
}
2021-10-01 02:48:11 +00:00
caddyExecutable = filepath . Join ( daemonExecutablePath , caddyExecutable )
2021-10-14 04:08:40 +00:00
caddyConfigAbsPath , err := filepath . Abs ( "caddy-config.json" )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because can't resolve absolute path of \"caddy-config.json\": %+v\n\n" , err )
2021-10-14 04:08:40 +00:00
return
}
caddyArguments := [ ] string { "run" , "-config" , caddyConfigAbsPath }
2021-08-19 17:49:43 +00:00
// TODO this might need to be changed for windows/mac
// https://caddyserver.com/docs/conventions#data-directory
caddyEnvironment := [ ] string {
2021-10-01 02:48:11 +00:00
fmt . Sprintf ( "XDG_DATA_HOME=%s" , filepath . Join ( daemonPath , "caddyData" ) ) ,
fmt . Sprintf ( "XDG_CONFIG_HOME=%s" , filepath . Join ( daemonPath , "caddyConfig" ) ) ,
2021-08-19 17:49:43 +00:00
}
2021-10-21 21:49:42 +00:00
daemonTelemetryIdBytes , err := ioutil . ReadFile ( filepath . Join ( daemonPath , "daemon-telemetry-id.txt" ) )
if err == nil {
daemonTelemetryId = string ( daemonTelemetryIdBytes )
} else {
buffer := make ( [ ] byte , 4 )
n , err := rand . Read ( buffer )
if n != 4 || err != nil {
fatalfWithTelemetry ( "can't start the greenhouse-daemon, can't read 4 random bytes: read: %d err: %+v" , n , err )
}
daemonTelemetryId = fmt . Sprintf ( "%x" , buffer )
ioutil . WriteFile ( filepath . Join ( daemonPath , "daemon-telemetry-id.txt" ) , [ ] byte ( daemonTelemetryId ) , 0755 )
}
2021-08-19 17:49:43 +00:00
var daemonConfig DaemonConfig
2021-10-01 02:48:11 +00:00
daemonConfigBytes , err := ioutil . ReadFile ( filepath . Join ( daemonPath , "daemon-config.json" ) )
2021-08-19 17:49:43 +00:00
// if the file doesn't exist, just skip it.
if err == nil {
err = json . Unmarshal ( daemonConfigBytes , & daemonConfig )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon, can't parse daemon-config.json %+v" , err )
2021-08-19 17:49:43 +00:00
}
}
var tenantInfo TenantInfo
2021-10-01 02:48:11 +00:00
tenantInfoBytes , err := ioutil . ReadFile ( filepath . Join ( daemonPath , "daemon-tenant-info.json" ) )
2021-08-19 17:49:43 +00:00
// if the file doesn't exist, just skip it.
if err == nil {
err = json . Unmarshal ( tenantInfoBytes , & tenantInfo )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon, can't parse daemon-tenant-info.json %+v" , err )
2021-08-19 17:49:43 +00:00
}
}
2021-10-21 21:49:42 +00:00
daemonTelemetryAccount = tenantInfo . EmailAddress
2021-08-19 17:49:43 +00:00
var thresholdConfig ThresholdConfig
2021-10-01 02:48:11 +00:00
thresholdConfigBytes , err := ioutil . ReadFile ( filepath . Join ( daemonPath , "threshold-config.json" ) )
2021-08-19 17:49:43 +00:00
// if the file doesn't exist, just skip it.
if err == nil {
err = json . Unmarshal ( thresholdConfigBytes , & thresholdConfig )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon, can't parse threshold-config.json %+v" , err )
2021-08-19 17:49:43 +00:00
}
}
caddyOnPreStart := func ( ) error {
2021-08-19 20:58:50 +00:00
// caddy does not know how to clean up after itself when (its admin API specifically) is listening on a unix socket,
2021-08-19 17:49:43 +00:00
// so we have to do that ourselves.
if useUnixSockets {
os . Remove ( caddyAdminSocketFile )
2021-08-19 20:58:50 +00:00
2021-08-19 17:49:43 +00:00
}
return nil
}
2021-10-07 14:52:46 +00:00
log . Printf ( "thresholdExecutable: %s, caddyExecutable: %s\n" , thresholdExecutable , caddyExecutable )
2021-08-19 17:49:43 +00:00
daemonAPIInstance := DaemonAPI {
2021-09-23 23:01:13 +00:00
Config : & daemonConfig ,
ConfigurationMutex : sync . Mutex { } ,
TenantInfo : & tenantInfo ,
2021-08-19 17:49:43 +00:00
HTTPClient : & http . Client {
Timeout : time . Second * 10 ,
} ,
2021-10-01 00:22:40 +00:00
DaemonPath : daemonPath ,
CloudURL : cloudURL ,
UseUnixSockets : useUnixSockets ,
2021-10-01 02:48:11 +00:00
CACertFile : filepath . Join ( daemonPath , fmt . Sprintf ( "%s.crt" , mainCAName ) ) ,
TLSCertFile : filepath . Join ( daemonPath , "greenhouse-daemon.crt" ) ,
TLSKeyFile : filepath . Join ( daemonPath , "greenhouse-daemon.key" ) ,
2021-10-01 00:22:40 +00:00
ThresholdService : child . NewChildProcessService (
daemonPath ,
thresholdExecutable ,
thresholdArguments ,
[ ] string { } ,
nil ,
daemonConfig . TunnelsEnabled ,
child . NewDaemonLogManager ( daemonPath , thresholdExecutable ) ,
) ,
CaddyService : child . NewChildProcessService (
daemonPath ,
caddyExecutable ,
caddyArguments ,
caddyEnvironment ,
caddyOnPreStart ,
daemonConfig . TunnelsEnabled ,
child . NewDaemonLogManager ( daemonPath , thresholdExecutable ) ,
) ,
2021-10-21 21:49:42 +00:00
LogManager : log ,
TelemetryID : daemonTelemetryId ,
2021-08-19 17:49:43 +00:00
}
go daemonAPIInstance . ThresholdService . MainLoop ( )
go daemonAPIInstance . CaddyService . MainLoop ( )
2021-08-19 20:58:50 +00:00
missingAnyCertsOrKeys := false
certsAndKeysToCheck := [ ] string {
daemonAPIInstance . CACertFile ,
daemonAPIInstance . TLSCertFile ,
daemonAPIInstance . TLSKeyFile ,
}
for _ , filepath := range certsAndKeysToCheck {
_ , err := os . Stat ( filepath )
if os . IsNotExist ( err ) {
missingAnyCertsOrKeys = true
break
}
}
if missingAnyCertsOrKeys && ! useUnixSockets {
err = GenerateTLSCertificatesAndKeys ( daemonPath )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because GenerateTLSCertificatesAndKeys returned %+v" , err )
2021-08-19 20:58:50 +00:00
}
}
2021-08-19 17:49:43 +00:00
// authenticatedHTTPClient, err := daemonAPIInstance.MakeServiceClient()
// if err != nil {
// log.Fatalf("can't start the greenhouse-daemon because: %+v", err)
// }
// daemonAPIInstance.CaddyConfigService = NewCaddyConfigService(authenticatedHTTPClient)
// the unix socket will only be used if useUnixSockets == true
thresholdClient , err := daemonAPIInstance . MakeServiceClient ( thresholdAdminSocketFile )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because can't make threshold client: %+v" , err )
2021-08-19 17:49:43 +00:00
}
caddyAdminClient , err := daemonAPIInstance . MakeServiceClient ( caddyAdminSocketFile )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because can't make caddy admin client: %+v" , err )
2021-08-19 17:49:43 +00:00
}
thresholdAdminURL := fmt . Sprintf ( "https://127.0.0.1:%d" , thresholdAdminPort )
caddyAdminURL := fmt . Sprintf ( "https://127.0.0.1:%d" , caddyAdminPort )
if useUnixSockets {
thresholdAdminURL = "http://unix"
caddyAdminURL = "http://unix"
}
daemonAPIInstance . ConfigService = & ConfigService {
ClientId : thresholdConfig . ClientId ,
EmailAddress : tenantInfo . EmailAddress ,
UseUnixSockets : useUnixSockets ,
ThresholdAdminBaseURL : thresholdAdminURL ,
CaddyAdminBaseURL : caddyAdminURL ,
ThresholdClient : thresholdClient ,
CaddyAdminClient : caddyAdminClient ,
2021-10-21 21:49:42 +00:00
TelemetryID : daemonTelemetryId ,
2021-08-19 17:49:43 +00:00
}
if daemonAPIInstance . Config . TunnelsEnabled && len ( daemonAPIInstance . Config . GUITunnels ) > 0 {
2021-08-30 20:05:15 +00:00
log . Printf ( "greenhouse-daemon enabling configured tunnels:\n" )
2021-08-19 17:49:43 +00:00
for _ , tunnel := range daemonAPIInstance . Config . GUITunnels {
2021-09-21 17:19:13 +00:00
log . Printf ( " %s" , tunnel . GetStringDisplay ( false ) )
2021-08-19 17:49:43 +00:00
}
2021-09-21 17:19:13 +00:00
2021-09-23 23:01:13 +00:00
log . Printf ( "startup aquiring ConfigurationMutex lock...\n" )
daemonAPIInstance . ConfigurationMutex . Lock ( )
2021-09-21 17:19:13 +00:00
thresholdConfig , caddyConfig , err := daemonAPIInstance . ConfigService . PrepareConfigs ( daemonAPIInstance . Config . GUITunnels )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because daemon-config.json is invalid: %+v" , err )
2021-09-21 17:19:13 +00:00
}
2021-09-23 23:01:13 +00:00
completionChannel := make ( chan bool )
go daemonAPIInstance . applyConfigAsync ( daemonAPIInstance . Config . GUITunnels , thresholdConfig , caddyConfig , completionChannel )
go ( func ( ) {
<- completionChannel
log . Printf ( "startup releasing ConfigurationMutex lock\n" )
daemonAPIInstance . ConfigurationMutex . Unlock ( )
} ) ( )
2021-08-19 17:49:43 +00:00
}
var listener net . Listener
if useUnixSockets {
os . Remove ( daemonSocketFile )
listenAddress , err := net . ResolveUnixAddr ( "unix" , daemonSocketFile )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because net.ResolveUnixAddr() returned %+v" , err )
2021-08-19 17:49:43 +00:00
}
listener , err = net . ListenUnix ( "unix" , listenAddress )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because net.ListenUnix(\"unix\", \"%s\") returned %+v" , listenAddress , err )
2021-08-19 17:49:43 +00:00
}
log . Printf ( "greenhouse-daemon about to start listening at http://%s 😈\n" , daemonSocketFile )
} else {
addrString := fmt . Sprintf ( "127.0.0.1:%d" , daemonAdminPort )
addr , err := net . ResolveTCPAddr ( "tcp" , addrString )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because net.ResolveTCPAddr(%s) returned %+v" , addrString , err )
2021-08-19 17:49:43 +00:00
}
tcpListener , err := net . ListenTCP ( "tcp" , addr )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "can't start the greenhouse-daemon because net.ListenTCP(%s) returned %+v" , addrString , err )
2021-08-19 17:49:43 +00:00
}
tlsCert , err := tls . LoadX509KeyPair ( daemonAPIInstance . TLSCertFile , daemonAPIInstance . TLSKeyFile )
if err != nil {
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry (
2021-08-19 17:49:43 +00:00
"can't start the greenhouse-daemon because tls.LoadX509KeyPair(%s,%s) returned %+v" ,
daemonAPIInstance . TLSCertFile , daemonAPIInstance . TLSKeyFile , err ,
2021-10-21 21:49:42 +00:00
)
2021-08-19 17:49:43 +00:00
}
tlsConfig := & tls . Config {
Certificates : [ ] tls . Certificate { tlsCert } ,
}
tlsConfig . BuildNameToCertificate ( )
listener = tls . NewListener ( tcpListener , tlsConfig )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-started" , daemonTelemetryAccount , daemonTelemetryId , "success!!" )
2021-08-19 17:49:43 +00:00
log . Printf ( "greenhouse-daemon about to start listening at https://%s 😈\n" , addrString )
}
server := http . Server {
Handler : & daemonAPIInstance ,
ReadTimeout : 10 * time . Second ,
WriteTimeout : 10 * time . Second ,
}
2021-10-07 14:52:46 +00:00
go ( func ( ) {
err = server . Serve ( listener )
2021-10-21 21:49:42 +00:00
fatalfWithTelemetry ( "server.Serve returned %+v" , err )
2021-10-07 14:52:46 +00:00
} ) ( )
2021-10-10 16:05:19 +00:00
sigs := child . GetSignalChannelOSIndependent ( )
2021-10-07 14:52:46 +00:00
done := make ( chan bool , 1 )
go func ( ) {
sig := <- sigs
2021-10-10 17:23:55 +00:00
log . Printf ( "Greenhouse daemon recieved signal: %s\n" , sig )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-signal" , daemonTelemetryAccount , daemonTelemetryId , sig . String ( ) )
2021-10-07 14:52:46 +00:00
daemonAPIInstance . CaddyService . Enabled = false
daemonAPIInstance . ThresholdService . Enabled = false
attempts := 0
2021-10-10 17:23:55 +00:00
for attempts < 15 {
2021-10-07 14:52:46 +00:00
attempts ++
time . Sleep ( 500 * time . Millisecond )
caddyStatus := daemonAPIInstance . CaddyService . Status ( )
thresholdStatus := daemonAPIInstance . ThresholdService . Status ( )
2021-10-10 17:23:55 +00:00
log . Printf (
"Greenhouse daemon is waiting for caddy (enabled: %t, running: %t) and threshold (enabled: %t, running: %t) to stop... \n" ,
caddyStatus . Enabled , caddyStatus . PID > 0 , thresholdStatus . Enabled , thresholdStatus . PID > 0 ,
)
2021-10-07 14:52:46 +00:00
if caddyStatus . PID < 1 && thresholdStatus . PID < 1 {
log . Printf ( "Caddy and threshold have stopped\n" )
done <- true
return
}
}
2021-10-21 21:49:42 +00:00
postTelemetry ( "daemon-timed-out-waiting-threshold-and-caddy" , daemonTelemetryAccount , daemonTelemetryId , "oof" )
2021-10-07 14:52:46 +00:00
done <- true
} ( )
2021-08-19 17:49:43 +00:00
2021-10-07 14:52:46 +00:00
fmt . Println ( "greenhouse-daemon is running" )
<- done
fmt . Println ( "exiting" )
2021-08-19 17:49:43 +00:00
}
func ( daemon * DaemonAPI ) ServeHTTP ( responseWriter http . ResponseWriter , request * http . Request ) {
switch path . Clean ( request . URL . Path ) {
2021-09-27 16:33:45 +00:00
case "/ping" :
responseWriter . Header ( ) . Set ( "Content-Type" , "text/plain" )
responseWriter . Write ( [ ] byte ( "pong\n" ) )
2021-08-30 20:05:15 +00:00
case "/logs" :
2021-08-19 17:49:43 +00:00
service := request . URL . Query ( ) . Get ( "service" )
2021-08-30 20:05:15 +00:00
countString := request . URL . Query ( ) . Get ( "count" )
if countString == "" {
countString = "3000"
}
count , err := strconv . Atoi ( countString )
if err != nil {
errorMessage := fmt . Sprintf ( "greenhouse-daemon: 400 bad request: invalid count: %s" , countString )
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
2021-10-01 00:22:40 +00:00
logManagerMap := map [ string ] child . LogManager {
2021-08-30 20:05:15 +00:00
"caddy" : daemon . CaddyService . LogManager ,
"threshold" : daemon . ThresholdService . LogManager ,
"daemon" : daemon . LogManager ,
}
2021-10-01 00:22:40 +00:00
var logManagers [ ] child . LogManager
2021-09-21 17:19:13 +00:00
if service == "all" || service == "" {
2021-10-01 00:22:40 +00:00
logManagers = [ ] child . LogManager {
2021-08-30 20:05:15 +00:00
logManagerMap [ "caddy" ] ,
logManagerMap [ "threshold" ] ,
logManagerMap [ "daemon" ] ,
}
2021-08-19 17:49:43 +00:00
} else {
2021-08-30 20:05:15 +00:00
logManager , hasLogManager := logManagerMap [ service ]
if ! hasLogManager {
http . Error ( responseWriter , "404 not found, try /log?service=daemon, /log?service=caddy, /log?service=threshold or /log?service=all" , http . StatusNotFound )
return
}
2021-10-01 00:22:40 +00:00
logManagers = [ ] child . LogManager { logManager }
2021-08-30 20:05:15 +00:00
}
2021-09-30 22:19:28 +00:00
logIterators := make ( [ ] * child . LogIterator , len ( logManagers ) )
2021-08-30 20:05:15 +00:00
for i := 0 ; i < len ( logManagers ) ; i ++ {
iterator , err := logManagers [ i ] . Iterator ( )
if err != nil {
2021-10-01 00:22:40 +00:00
errorMessage := fmt . Sprintf ( "greenhouse-daemon: 500 internal server error: failed to open log iterator" )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-logs" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-21 17:19:13 +00:00
log . Printf ( "%s: %+v\n" , errorMessage , err )
2021-08-30 20:05:15 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
logIterators [ i ] = iterator
}
2021-09-30 22:19:28 +00:00
logs , err := child . Joinerate ( logIterators , count )
2021-08-30 20:05:15 +00:00
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: failed to read log file(s)"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-logs" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-21 17:19:13 +00:00
log . Printf ( "%s: %+v\n" , errorMessage , err )
2021-08-30 20:05:15 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
2021-08-19 17:49:43 +00:00
return
}
2021-08-30 20:05:15 +00:00
2021-08-30 20:15:55 +00:00
logBytes , err := json . Marshal ( logs )
2021-08-19 17:49:43 +00:00
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: json serialization failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-logs" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-21 17:19:13 +00:00
log . Printf ( "%s: %+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
responseWriter . Header ( ) . Set ( "Content-Type" , "application/json" )
responseWriter . Write ( logBytes )
case "/status" :
updateTenantInfo := request . URL . Query ( ) . Get ( "updateTenantInfo" )
updateTenantInfoMessage := "n/a"
if updateTenantInfo != "" {
if daemon . Config == nil || daemon . Config . APIToken == "" {
http . Error ( responseWriter , "greenhouse-daemon: 400 bad request. please register before sending this request" , http . StatusBadRequest )
return
}
2021-08-21 19:32:04 +00:00
err , statusCode , errorMessage , tenantInfo := daemon . UpdateTenantInfo ( nil )
2021-08-19 17:49:43 +00:00
if err != nil {
updateTenantInfoMessage = fmt . Sprintf ( "greenhouse-daemon can't get your account info: %d %s: %s" , statusCode , errorMessage , err )
log . Printf ( "%s:\n%+v\n" , updateTenantInfoMessage , err )
} else if statusCode != 200 {
updateTenantInfoMessage = fmt . Sprintf ( "greenhouse-daemon can't get your account info: greenhouse returned HTTP %d: %s" , statusCode , errorMessage )
fmt . Printf ( "greenhouse-daemon can't get your account info: greenhouse returned HTTP %d: %s\n" , statusCode , errorMessage )
} else {
daemon . TenantInfo = tenantInfo
2021-10-21 21:49:42 +00:00
daemonTelemetryAccount = tenantInfo . EmailAddress
2021-08-19 17:49:43 +00:00
daemon . ConfigService . EmailAddress = tenantInfo . EmailAddress
updateTenantInfoMessage = "success"
}
}
2021-08-19 20:58:50 +00:00
hashedTokenArray := sha256 . Sum256 ( [ ] byte ( daemon . Config . APIToken ) )
2021-08-19 17:49:43 +00:00
status := & Status {
NeedsAPIToken : daemon . Config . APIToken == "" ,
2021-08-19 20:58:50 +00:00
HashedToken : fmt . Sprintf ( "%x" , hashedTokenArray [ : ] ) ,
2021-08-19 17:49:43 +00:00
Threshold : daemon . ThresholdService . Status ( ) ,
Caddy : daemon . CaddyService . Status ( ) ,
TenantInfo : daemon . TenantInfo ,
2021-08-22 01:29:54 +00:00
ServerName : daemon . Config . ServerName ,
2021-08-19 17:49:43 +00:00
GUITunnels : daemon . Config . GUITunnels ,
UpdateTenantInfoMessage : updateTenantInfoMessage ,
ApplyConfigStatuses : daemon . ApplyConfigStatuses ,
ApplyConfigStatusIndex : daemon . ApplyConfigStatusIndex ,
2021-09-21 17:19:13 +00:00
ApplyConfigStatusError : daemon . ApplyConfigStatusError ,
2021-10-21 21:49:42 +00:00
DaemonTelemetryID : daemon . TelemetryID ,
2021-08-19 17:49:43 +00:00
}
statusBytes , err := json . MarshalIndent ( status , "" , " " )
//log.Printf("statusBytes: %s\n\n", statusBytes)
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: json serialization failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-get-status" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
responseWriter . Header ( ) . Set ( "Content-Type" , "application/json" )
responseWriter . Write ( statusBytes )
case "/register" :
2021-10-24 19:00:29 +00:00
serverName := strings . ToLower ( request . URL . Query ( ) . Get ( "serverName" ) )
2021-10-18 20:17:24 +00:00
2021-10-24 19:00:29 +00:00
subdomainRegex := regexp . MustCompile ( "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$" )
2021-10-18 20:17:24 +00:00
if ! subdomainRegex . MatchString ( serverName ) {
http . Error ( responseWriter , "400 Bad Request, the serverName must be a valid subdomain. It should only contain letters, numbers, and dashes" , http . StatusBadRequest )
return
}
2021-08-19 17:49:43 +00:00
authorizationHeader := request . Header . Get ( "Authorization" )
apiToken := strings . TrimPrefix ( authorizationHeader , "Bearer " )
2021-08-21 19:32:04 +00:00
// First order of business: get info for this tenant based on the apiToken to ensure they don't already
// have a connected server named `serverName`.
2021-09-23 23:01:13 +00:00
err , statusCode , statusString , tenantInfo := daemon . GetTenantInfo ( apiToken )
2021-08-21 19:32:04 +00:00
if err != nil || statusCode != 200 {
errorMessage := fmt . Sprintf ( "greenhouse-daemon: %d %s" , statusCode , statusString )
http . Error ( responseWriter , errorMessage , statusCode )
return
}
if tenantInfo . ClientStates [ serverName ] . CurrentState == "ClientConnected" {
errorMessage := fmt . Sprintf ( "greenhouse-daemon: 409 conflict, you already have a connected server named '%s'" , serverName )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , errorMessage )
2021-08-21 19:32:04 +00:00
http . Error ( responseWriter , errorMessage , http . StatusConflict )
return
}
2021-08-19 17:49:43 +00:00
// set the api token in the daemon config first because it's required by UpdateTenantInfo
daemon . Config . APIToken = apiToken
daemon . Config . ServerName = serverName
// make sure we can get the tenant info before we configure threshold.
2021-08-21 19:32:04 +00:00
err , statusCode , errorMessage , tenantInfo := daemon . UpdateTenantInfo ( tenantInfo )
2021-08-19 17:49:43 +00:00
if err != nil || statusCode != 200 {
errorMessage := fmt . Sprintf ( "greenhouse-daemon failed to get tenant info: %d %s" , statusCode , errorMessage )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: HTTP %d err: %s\n" , errorMessage , statusCode , err ) )
2021-08-19 17:49:43 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
http . Error ( responseWriter , errorMessage , statusCode )
return
}
daemon . TenantInfo = tenantInfo
2021-10-21 21:49:42 +00:00
daemonTelemetryAccount = tenantInfo . EmailAddress
2021-08-19 17:49:43 +00:00
daemon . ConfigService . EmailAddress = tenantInfo . EmailAddress
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , daemonTelemetryAccount )
2021-08-19 17:49:43 +00:00
// TODO real greenhouse API url
registerURL := fmt . Sprintf ( "%s/api/client_config?serverName=%s" , daemon . CloudURL , serverName )
configRequest , err := http . NewRequest ( "POST" , registerURL , nil )
if err != nil {
2021-09-14 22:31:42 +00:00
errorMessage := "greenhouse-daemon: 500 internal server error"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
2021-08-19 17:49:43 +00:00
return
}
configRequest . Header . Add ( "Authorization" , fmt . Sprintf ( "Bearer %s" , apiToken ) )
configResponse , err := daemon . HTTPClient . Do ( configRequest )
if err != nil {
2021-08-21 19:32:04 +00:00
errorMessage := fmt . Sprintf ( "greenhouse-daemon: 503 bad gateway, can't reach greenhouse cloud at %s" , registerURL )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusBadGateway )
return
}
if configResponse . StatusCode != http . StatusOK {
errorMessage := fmt . Sprintf (
"greenhouse-daemon: %d %s, the server at %s returned HTTP %d" ,
configResponse . StatusCode , configResponse . Status , registerURL , configResponse . StatusCode ,
)
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , configResponse . StatusCode )
return
}
configBytes , err := ioutil . ReadAll ( configResponse . Body )
if err != nil {
errorMessage := fmt . Sprintf ( "greenhouse-daemon: 503 bad gateway, read error on %s. please try again" , registerURL )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusBadGateway )
return
}
var thresholdConfig ThresholdConfig
err = json . Unmarshal ( configBytes , & thresholdConfig )
if err != nil {
errorMessage := fmt . Sprintf ( "greenhouse-daemon: 500 internal server error, %s did not return json" , registerURL )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
thresholdConfig . GreenhouseThresholdPort = greenhouseThresholdPort
thresholdConfig . GreenhouseAPIToken = apiToken
if ! daemon . UseUnixSockets {
thresholdConfig . AdminAPIPort = thresholdAdminPort
thresholdConfig . AdminAPICACertificateFile = daemon . CACertFile
thresholdConfig . AdminAPITlsCertificateFile = daemon . TLSCertFile
thresholdConfig . AdminAPITlsKeyFile = daemon . TLSKeyFile
} else {
thresholdConfig . AdminUnixSocket = thresholdAdminSocketFile
}
daemon . ConfigService . ClientId = thresholdConfig . ClientId
configBytesToWrite , err := json . MarshalIndent ( thresholdConfig , "" , " " )
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: json serialization failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
2021-10-01 02:48:11 +00:00
err = ioutil . WriteFile ( filepath . Join ( daemon . DaemonPath , "threshold-config.json" ) , configBytesToWrite , 0600 )
2021-08-19 17:49:43 +00:00
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: write threshold config file failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
err = daemon . writeDaemonConfigJSON ( )
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: writeDaemonConfigJSON failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-register" , daemonTelemetryAccount , daemonTelemetryId , "success!!" )
2021-08-19 17:49:43 +00:00
responseWriter . Write ( [ ] byte ( "OK" ) )
2021-10-18 20:17:24 +00:00
case "/unregister" :
os . Remove ( filepath . Join ( daemon . DaemonPath , "threshold-config.json" ) )
os . Remove ( filepath . Join ( daemon . DaemonPath , "daemon-config.json" ) )
os . Remove ( filepath . Join ( daemon . DaemonPath , "daemon-tenant-info.json" ) )
daemon . ConfigService . ClientId = ""
daemon . TenantInfo = nil
daemon . ConfigService . EmailAddress = ""
daemon . Config . APIToken = ""
daemon . Config . ServerName = ""
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-unregister" , daemonTelemetryAccount , daemonTelemetryId , "success!!" )
2021-10-18 20:17:24 +00:00
responseWriter . Write ( [ ] byte ( "OK" ) )
2021-08-19 17:49:43 +00:00
case "/apply_config" :
requestBytes , err := ioutil . ReadAll ( request . Body )
if err != nil {
errorMessage := "greenhouse-daemon: 500 apply_config failed: http read error"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-apply-config" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
log . Printf ( "apply_config: %s\n\n" , string ( requestBytes ) )
var tunnels [ ] GUITunnel
err = json . Unmarshal ( requestBytes , & tunnels )
if err != nil {
2021-10-21 21:49:42 +00:00
errorMessage := "greenhouse-daemon: 400 bad request: invalid json:"
go postTelemetry ( "daemon-apply-config" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusBadRequest )
return
}
2021-09-23 23:01:13 +00:00
log . Printf ( "/apply_config aquiring ConfigurationMutex lock...\n" )
//timeBeforeLockAquired := time.Now()
daemon . ConfigurationMutex . Lock ( )
// if time.Since(timeBeforeLockAquired) > time.Second * 9 {
// log.Println("blahblah TODO do I need this?")
// }
2021-09-21 17:19:13 +00:00
thresholdConfig , caddyConfig , err := daemon . ConfigService . PrepareConfigs ( tunnels )
if err != nil {
errorMessage := fmt . Sprintf ( "greenhouse-daemon: 400 bad request: %s" , err )
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-apply-config" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-21 17:19:13 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
http . Error ( responseWriter , errorMessage , http . StatusBadRequest )
return
}
2022-02-10 03:32:47 +00:00
// bytez1, _ := json.MarshalIndent(thresholdConfig, "", " ")
// log.Printf("thresholdConfig: %s\n\n", string(bytez1))
// bytez2, _ := json.MarshalIndent(caddyConfig, "", " ")
// log.Printf("caddyConfig: %s\n\n", string(bytez2))
2021-08-19 17:49:43 +00:00
daemon . ThresholdService . Enabled = true
daemon . CaddyService . Enabled = true
daemon . Config . TunnelsEnabled = true
daemon . Config . GUITunnels = tunnels
err = daemon . writeDaemonConfigJSON ( )
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: writeDaemonConfigJSON failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-apply-config" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
2021-08-19 17:49:43 +00:00
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-apply-config" , daemonTelemetryAccount , daemonTelemetryId , "started..." )
2021-09-23 23:01:13 +00:00
completionChannel := make ( chan bool )
go daemon . applyConfigAsync ( tunnels , thresholdConfig , caddyConfig , completionChannel )
go ( func ( ) {
<- completionChannel
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-apply-config" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf (
"completed: ApplyConfigStatusIndex: %d, error: '%s'" , daemon . ApplyConfigStatusIndex , daemon . ApplyConfigStatusError ,
) )
2021-09-23 23:01:13 +00:00
log . Printf ( "/apply_config releasing ConfigurationMutex lock\n" )
daemon . ConfigurationMutex . Unlock ( )
} ) ( )
2021-08-19 17:49:43 +00:00
2021-09-14 22:31:42 +00:00
case "/netstat" :
entries , err := netstat . TCPSocks ( func ( s * netstat . SockTabEntry ) bool {
return s . State == netstat . Listen
} )
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: netstat.TCPSocks failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-netstat" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
toReturn := [ ] ListeningSocket { }
2021-10-14 17:30:06 +00:00
everyPidWasZero := true
2021-09-14 22:31:42 +00:00
for _ , entry := range entries {
toAppend := ListeningSocket {
LocalAddr : entry . LocalAddr . String ( ) ,
RemoteAddr : entry . RemoteAddr . String ( ) ,
State : entry . State . String ( ) ,
}
if entry . Process != nil {
2021-10-14 17:30:06 +00:00
if entry . Process . Pid != 0 {
everyPidWasZero = false
}
2021-09-14 22:31:42 +00:00
toAppend . PID = entry . Process . Pid
toAppend . ProcessCommand = entry . Process . Name
}
toReturn = append ( toReturn , toAppend )
}
2021-10-14 17:30:06 +00:00
if everyPidWasZero {
for _ , entry := range toReturn {
entry . ProcessCommand = "<unknown>"
}
}
2021-09-14 22:31:42 +00:00
netstatBytes , err := json . MarshalIndent ( toReturn , "" , " " )
if err != nil {
errorMessage := "greenhouse-daemon: 500 internal server error: json serialization failed"
2021-10-21 21:49:42 +00:00
go postTelemetry ( "daemon-netstat" , daemonTelemetryAccount , daemonTelemetryId , fmt . Sprintf ( "%s: %s\n" , errorMessage , err ) )
2021-09-14 22:31:42 +00:00
log . Printf ( "%s:\n%+v\n" , errorMessage , err )
http . Error ( responseWriter , errorMessage , http . StatusInternalServerError )
return
}
responseWriter . Header ( ) . Set ( "Content-Type" , "application/json" )
responseWriter . Write ( netstatBytes )
2021-08-19 17:49:43 +00:00
default :
2021-08-30 20:05:15 +00:00
http . Error ( responseWriter , "greenhouse-daemon: 404 not found, try GET /status, GET /logs, or POST /register" , http . StatusNotFound )
2021-08-19 17:49:43 +00:00
}
}
2021-09-23 23:01:13 +00:00
func ( daemon * DaemonAPI ) applyConfigAsync ( tunnels [ ] GUITunnel , thresholdConfig * ThresholdTunnelsConfig , caddyConfig * map [ string ] * CaddyApp , completionChannel chan bool ) {
2021-08-19 17:49:43 +00:00
daemon . ApplyConfigStatusIndex = 0
2021-09-24 21:41:31 +00:00
daemon . ApplyConfigStatusError = ""
2021-08-19 17:49:43 +00:00
daemon . ApplyConfigStatuses = [ ] string {
"waiting for underlying services to start" ,
"creating threshold tunnels" ,
"testing threshold tunnels" ,
"configuring caddy" ,
"waiting for caddy to obtain https certificates from Let's Encrypt" ,
"final testing" ,
}
log . Printf ( "waiting for threshold to start...\n" )
2021-09-23 23:01:13 +00:00
thresholdStarted := false
startedWaiting := time . Now ( )
for ! thresholdStarted && time . Since ( startedWaiting ) < time . Second * 10 {
2021-08-19 17:49:43 +00:00
thresholdStatus := daemon . ThresholdService . Status ( )
//jsonBytes, _ := json.MarshalIndent(thresholdStatus, "", " ")
//log.Printf("thresholdStatus: %s\n\n", string(jsonBytes))
// TODO use the health status instead of uptime
if thresholdStatus . PID != 0 && time . Since ( thresholdStatus . Started ) > time . Second {
2021-09-23 23:01:13 +00:00
thresholdStarted = true
2021-08-19 17:49:43 +00:00
}
time . Sleep ( time . Millisecond * 700 )
}
2021-09-23 23:01:13 +00:00
if ! thresholdStarted {
daemon . ApplyConfigStatusError = "timed out after 10 seconds waiting for threshold to start"
log . Printf ( "timed out after 10 seconds waiting for threshold to start\n" )
completionChannel <- false
return
}
2021-08-19 17:49:43 +00:00
log . Printf ( "waiting for caddy to start...\n" )
2021-09-23 23:01:13 +00:00
caddyStarted := false
startedWaiting = time . Now ( )
for ! caddyStarted && time . Since ( startedWaiting ) < time . Second * 10 {
2021-08-19 17:49:43 +00:00
caddyStatus := daemon . CaddyService . Status ( )
//jsonBytes, _ := json.MarshalIndent(caddyStatus, "", " ")
//log.Printf("caddyStatus: %s\n\n", string(jsonBytes))
// TODO use the health status instead of uptime
if caddyStatus . PID != 0 && time . Since ( caddyStatus . Started ) > time . Second {
2021-09-23 23:01:13 +00:00
caddyStarted = true
2021-08-19 17:49:43 +00:00
}
time . Sleep ( time . Millisecond * 700 )
}
2021-09-23 23:01:13 +00:00
if ! caddyStarted {
daemon . ApplyConfigStatusError = "timed out after 10 seconds waiting for caddy to start"
log . Printf ( "timed out after 10 seconds waiting for caddy to start\n" )
completionChannel <- false
return
}
2021-08-19 17:49:43 +00:00
log . Printf ( "services are started!\n" )
// TODO make this an option in the UI: allow the user to clear all other clients tunnels..?
newTunnelListeners := map [ string ] bool { }
for _ , listener := range thresholdConfig . Listeners {
newTunnelListeners [ fmt . Sprintf ( "%s:%d" , listener . ListenHostnameGlob , listener . ListenPort ) ] = true
}
//jsonBytes, _ := json.MarshalIndent(daemon.TenantInfo.Listeners, "", " ")
//log.Printf("existing listeners: %s\n\n", jsonBytes)
for _ , listener := range daemon . TenantInfo . Listeners {
conflictingListener := newTunnelListeners [ fmt . Sprintf ( "%s:%d" , listener . ListenHostnameGlob , listener . ListenPort ) ]
if listener . ClientId != daemon . ConfigService . ClientId && ! conflictingListener {
thresholdConfig . Listeners = append ( thresholdConfig . Listeners , listener )
}
}
//jsonBytes, _ = json.MarshalIndent(thresholdConfig, "", " ")
//log.Printf("new listeners: %s\n\n", jsonBytes)
daemon . ApplyConfigStatusIndex ++
2021-09-21 17:19:13 +00:00
err := daemon . ConfigService . ConfigureThreshold ( thresholdConfig )
2021-08-19 17:49:43 +00:00
if err != nil {
daemon . ApplyConfigStatusError = fmt . Sprintf ( "error creating threshold tunnels: %s" , err )
log . Printf ( "error creating threshold tunnels: %+v\n" , err )
2021-09-23 23:01:13 +00:00
completionChannel <- false
2021-08-19 17:49:43 +00:00
return
}
daemon . ApplyConfigStatusIndex ++
2022-02-10 03:32:47 +00:00
err = daemon . ConfigService . TestThreshold ( thresholdConfig , daemon . TenantInfo . AuthorizedDomains [ 0 ] )
2021-08-19 17:49:43 +00:00
if err != nil {
daemon . ApplyConfigStatusError = fmt . Sprintf ( "testing threshold tunnels: %s" , err )
log . Printf ( "testing threshold tunnels failed: %+v\n" , err )
2021-09-23 23:01:13 +00:00
completionChannel <- false
2021-08-19 17:49:43 +00:00
return
}
daemon . ApplyConfigStatusIndex ++
err = daemon . ConfigService . ConfigureCaddy ( caddyConfig )
if err != nil {
daemon . ApplyConfigStatusError = fmt . Sprintf ( "error configuring caddy: %s" , err )
log . Printf ( "error configuring caddy: %+v\n" , err )
2021-09-23 23:01:13 +00:00
completionChannel <- false
2021-08-19 17:49:43 +00:00
return
}
daemon . ApplyConfigStatusIndex ++
err = daemon . ConfigService . EnsureCaddyACMECompletes ( thresholdConfig )
if err != nil {
daemon . ApplyConfigStatusError = fmt . Sprintf ( "caddy failed to obtain https certificates from Let's Encrypt: %s" , err )
log . Printf ( "caddy failed to obtain https certificates from Let's Encrypt: %+v\n" , err )
2021-09-23 23:01:13 +00:00
completionChannel <- false
2021-08-19 17:49:43 +00:00
return
}
daemon . ApplyConfigStatusIndex ++
err = daemon . ConfigService . TestFinalTunnels ( tunnels )
if err != nil {
daemon . ApplyConfigStatusError = fmt . Sprintf ( "testing threshold tunnels: %s" , err )
//log.Printf("testing threshold tunnels: %+v\n")
2021-09-23 23:01:13 +00:00
completionChannel <- false
2021-08-19 17:49:43 +00:00
return
}
daemon . ApplyConfigStatusIndex ++
2021-09-23 23:01:13 +00:00
completionChannel <- true
2021-08-19 17:49:43 +00:00
}
2021-08-21 19:32:04 +00:00
func ( daemon * DaemonAPI ) GetTenantInfo ( apiToken string ) ( error , int , string , * TenantInfo ) {
2021-09-18 20:01:34 +00:00
2021-08-19 17:49:43 +00:00
tenantInfoUrl := fmt . Sprintf ( "%s/api/tenant_info" , daemon . CloudURL )
2021-09-18 20:01:34 +00:00
//log.Printf("GET %s \n", tenantInfoUrl)
2021-08-19 17:49:43 +00:00
tenantInfoRequest , err := http . NewRequest ( "POST" , tenantInfoUrl , nil )
if err != nil {
return err , 500 , "internal server error" , nil
}
2021-08-21 19:32:04 +00:00
tenantInfoRequest . Header . Add ( "Authorization" , fmt . Sprintf ( "Bearer %s" , apiToken ) )
2021-08-19 17:49:43 +00:00
tenantInfoResponse , err := daemon . HTTPClient . Do ( tenantInfoRequest )
if err != nil {
return err , 503 , fmt . Sprintf ( "bad gateway, can't reach the server at %s" , tenantInfoUrl ) , nil
}
if tenantInfoResponse . StatusCode != http . StatusOK {
errorMessage := fmt . Sprintf (
"%s, the server at %s returned HTTP %d" ,
tenantInfoResponse . Status , tenantInfoUrl , tenantInfoResponse . StatusCode ,
)
return err , tenantInfoResponse . StatusCode , errorMessage , nil
}
tenantInfoBytes , err := ioutil . ReadAll ( tenantInfoResponse . Body )
if err != nil {
return err , 503 , fmt . Sprintf ( "bad gateway, read error on %s. please try again" , tenantInfoUrl ) , nil
}
//log.Printf("tenantInfoBytes: %s\n\n", tenantInfoBytes)
var tenantInfo TenantInfo
err = json . Unmarshal ( tenantInfoBytes , & tenantInfo )
if err != nil {
return err , 500 , fmt . Sprintf ( "internal server error, %s did not return json" , tenantInfoUrl ) , nil
}
2021-08-21 19:32:04 +00:00
return nil , 200 , "ok" , & tenantInfo
}
func ( daemon * DaemonAPI ) UpdateTenantInfo ( tenantInfo * TenantInfo ) ( error , int , string , * TenantInfo ) {
if tenantInfo == nil {
var err error
var statusCode int
var statusString string
err , statusCode , statusString , tenantInfo = daemon . GetTenantInfo ( daemon . Config . APIToken )
if err != nil || statusCode != 200 {
return err , statusCode , statusString , nil
}
2021-10-21 21:49:42 +00:00
daemonTelemetryAccount = tenantInfo . EmailAddress
2021-08-21 19:32:04 +00:00
}
2021-08-19 17:49:43 +00:00
tenantInfoBytesToWrite , err := json . MarshalIndent ( daemon . TenantInfo , "" , " " )
if err != nil {
return err , 500 , "internal server error: json serialization failed" , nil
}
2021-10-01 02:48:11 +00:00
err = ioutil . WriteFile ( filepath . Join ( daemon . DaemonPath , "daemon-tenant-info.json" ) , tenantInfoBytesToWrite , 0600 )
2021-08-19 17:49:43 +00:00
if err != nil {
return err , 500 , "internal server error: write threshold config file failed" , nil
}
2021-09-18 20:01:34 +00:00
//log.Printf("tenantInfoBytesToWrite: %s\n\n", tenantInfoBytesToWrite)
2021-08-19 17:49:43 +00:00
2021-08-21 19:32:04 +00:00
return nil , 200 , "ok" , tenantInfo
2021-08-19 17:49:43 +00:00
}
func ( daemon * DaemonAPI ) writeDaemonConfigJSON ( ) error {
configBytesToWrite , err := json . MarshalIndent ( daemon . Config , "" , " " )
if err != nil {
return err
}
2021-10-01 02:48:11 +00:00
err = ioutil . WriteFile ( filepath . Join ( daemon . DaemonPath , "daemon-config.json" ) , configBytesToWrite , 0600 )
2021-08-19 17:49:43 +00:00
if err != nil {
return err
}
return nil
}
func ( daemon * DaemonAPI ) MakeServiceClient ( socketFile string ) ( * http . Client , error ) {
if daemon . UseUnixSockets {
return & http . Client {
Transport : & http . Transport {
DialContext : func ( ctx context . Context , _ , _ string ) ( net . Conn , error ) {
return net . Dial ( "unix" , socketFile )
} ,
} ,
Timeout : 15 * time . Second ,
} , nil
} else {
cert , err := tls . LoadX509KeyPair ( daemon . TLSCertFile , daemon . TLSKeyFile )
if err != nil {
log . Printf ( fmt . Sprintf ( "can't MakeServiceClient() because tls.LoadX509KeyPair returned: \n%+v\n" , err ) )
return nil , errors . Wrap ( err , "can't MakeServiceClient() because tls.LoadX509KeyPair returned" )
}
caCertPool := x509 . NewCertPool ( )
caCert , err := ioutil . ReadFile ( daemon . CACertFile )
if err != nil {
return nil , errors . Wrap ( err , "can't MakeServiceClient() because can't read the CA cert file" )
}
ok := caCertPool . AppendCertsFromPEM ( caCert )
if ! ok {
return nil , errors . Errorf ( "Failed to add CA certificate '%s' to cert pool\n" , daemon . CACertFile )
}
tlsClientConfig := & tls . Config {
Certificates : [ ] tls . Certificate { cert } ,
RootCAs : caCertPool ,
}
tlsClientConfig . BuildNameToCertificate ( )
return & http . Client {
Transport : & http . Transport {
TLSClientConfig : tlsClientConfig ,
} ,
Timeout : 15 * time . Second ,
} , nil
}
}
func GenerateTLSCertificatesAndKeys ( daemonPath string ) error {
pkiService := greenhousePKI . NewPKIService ( & easypki . EasyPKI { Store : & easypkiStore . InMemory { } } )
mainCA , err := pkiService . GetCACertificate ( mainCAName )
if err != nil {
return errors . Wrap ( err , "GetCACertificate" )
}
mainCABytes := pem . EncodeToMemory ( & pem . Block {
Bytes : mainCA . Raw ,
Type : "CERTIFICATE" ,
} )
2021-10-01 02:48:11 +00:00
err = ioutil . WriteFile ( filepath . Join ( daemonPath , fmt . Sprintf ( "%s.crt" , mainCAName ) ) , mainCABytes , 0600 )
2021-08-19 17:49:43 +00:00
if err != nil {
return errors . Wrap ( err , "Write daemon CA certificate" )
}
expiry := time . Now ( ) . Add ( time . Hour * time . Duration ( 24 * 31 * 12 * 99 ) )
daemonKey , daemonCert , err := pkiService . GetServerKeyPair ( mainCAName , "greenhouse-daemon" , [ ] net . IP { net . ParseIP ( "127.0.0.1" ) } , expiry )
if err != nil {
return errors . Wrap ( err , "GetServerKeyPair" )
}
daemonKeyBytes := pem . EncodeToMemory ( & pem . Block {
Bytes : x509 . MarshalPKCS1PrivateKey ( daemonKey ) ,
Type : "RSA PRIVATE KEY" ,
} )
daemonCertBytes := pem . EncodeToMemory ( & pem . Block {
Bytes : daemonCert . Raw ,
Type : "CERTIFICATE" ,
} )
2021-10-01 02:48:11 +00:00
err = ioutil . WriteFile ( filepath . Join ( daemonPath , "greenhouse-daemon.crt" ) , daemonCertBytes , 0600 )
2021-08-19 17:49:43 +00:00
if err != nil {
return errors . Wrap ( err , "Write daemon TLS certificate" )
}
2021-10-01 02:48:11 +00:00
err = ioutil . WriteFile ( filepath . Join ( daemonPath , "greenhouse-daemon.key" ) , daemonKeyBytes , 0600 )
2021-08-19 17:49:43 +00:00
if err != nil {
return errors . Wrap ( err , "Write daemon TLS key" )
}
return nil
}
// func brotliCompress(utf8String []byte) ([]byte, error) {
// out := bytes.Buffer{}
// // Quality controls the compression-speed vs compression-density trade-offs.
// // The higher the quality, the slower the compression. Range is 0 to 11.
// writer := brotli.NewWriterOptions(&out, brotli.WriterOptions{Quality: 1})
// in := bytes.NewReader(utf8String)
// n, err := io.Copy(writer, in)
// if err != nil {
// return nil, err
// }
// if int(n) != len(utf8String) {
// return nil, errors.Errorf("brotli compress failed, unable to compress all bytes")
// }
// if err := writer.Close(); err != nil {
// return nil, err
// }
// return out.Bytes(), nil
// }
// func brotliDecompress(brotliCompressedBytes []byte) ([]byte, error) {
// return ioutil.ReadAll(brotli.NewReader(bytes.NewReader(brotliCompressedBytes)))
// }