package main
import (
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"strings"
"time"
"git.sequentialread.com/forest/greenhouse/pki"
errors "git.sequentialread.com/forest/pkg-errors"
)
const mainCAName = "greenhouse_CA"
const managementAPIAuthCAName = "greenhouse_management_api_auth_CA"
const thresholdCertsDomain = "greenhouse.server.garden"
type ThresholdProvisioningService struct {
BaseHTTPService
PKI * pki . PKIService
}
type ThresholdTunnel struct {
ClientId string
ListenPort int
ListenAddress string
ListenHostnameGlob string
BackEndService string
HaProxyProxyProtocol bool
}
func ( tunnel ThresholdTunnel ) String ( ) string {
return fmt . Sprintf (
"%s:%d (%s) -> %s (%s,HaProxyProxyProtocol=%t)" ,
tunnel . ListenAddress , tunnel . ListenPort , tunnel . ListenHostnameGlob , tunnel . ClientId , tunnel . BackEndService , tunnel . HaProxyProxyProtocol ,
)
}
type ThresholdClientConfig struct {
ClientId string
GreenhouseDomain string
GreenhouseAPIToken string
ServiceToLocalAddrMap * map [ string ] string
GreenhouseThresholdPort int
CaCertificate string
ClientTlsKey string
ClientTlsCertificate string
}
func NewThresholdProvisioningService ( pkiService * pki . PKIService ) * ThresholdProvisioningService {
toReturn := & ThresholdProvisioningService { PKI : pkiService }
// toReturn.ClientFactory = func() (*http.Client, *time.Time, error) {
// clientId := fmt.Sprintf("%d.%s", adminTenantId, adminThresholdNodeId)
// adminTenantClientCertSubject := fmt.Sprintf("%s@%s", clientId, thresholdCertsDomain)
// caCert, err := pkiService.GetCACertificate(mainCAName)
// if err != nil {
// return nil, nil, err
// }
// expiryTime := time.Now().Add(time.Hour * 24)
// cert, err := pkiService.GetClientTLSCertificate(mainCAName, adminTenantClientCertSubject, expiryTime)
// if err != nil {
// return nil, nil, err
// }
// caCertPool := x509.NewCertPool()
// caCertPool.AddCert(caCert)
// tlsClientConfig := &tls.Config{
// Certificates: []tls.Certificate{cert},
// RootCAs: caCertPool,
// }
// tlsClientConfig.BuildNameToCertificate()
// return &http.Client{
// Transport: &http.Transport{
// TLSClientConfig: tlsClientConfig,
// },
// Timeout: 10 * time.Second,
// }, &expiryTime, nil
// }
return toReturn
}
func ( service * ThresholdProvisioningService ) GetClientConfig ( tenantId int , greenhouseDomain , nodeId , apiKey string ) ( * ThresholdClientConfig , error ) {
clientId := fmt . Sprintf ( "%d.%s" , tenantId , nodeId )
certificateSubject := fmt . Sprintf ( "%s@%s" , clientId , thresholdCertsDomain )
mainCA , err := service . PKI . GetCACertificate ( mainCAName )
if err != nil {
return nil , errors . Wrap ( err , "GetClientConfig(): GetCACertificate" )
}
mainCABytes := pem . EncodeToMemory ( & pem . Block {
Bytes : mainCA . Raw ,
Type : "CERTIFICATE" ,
} )
expiry := time . Now ( ) . Add ( time . Hour * time . Duration ( 24 * 31 * 12 * 99 ) )
thresholdKey , thresholdCert , err := service . PKI . GetClientKeyPair ( mainCAName , certificateSubject , [ ] string { certificateSubject } , expiry )
if err != nil {
return nil , errors . Wrap ( err , "GetClientConfig(): GetClientKeyPair" )
}
thresholdKeyBytes := pem . EncodeToMemory ( & pem . Block {
Bytes : x509 . MarshalPKCS1PrivateKey ( thresholdKey ) ,
Type : "RSA PRIVATE KEY" ,
} )
thresholdCertBytes := pem . EncodeToMemory ( & pem . Block {
Bytes : thresholdCert . Raw ,
Type : "CERTIFICATE" ,
} )
clientConfig := ThresholdClientConfig {
ClientId : clientId ,
GreenhouseDomain : greenhouseDomain ,
GreenhouseAPIToken : apiKey ,
GreenhouseThresholdPort : 9056 ,
CaCertificate : string ( mainCABytes ) ,
ClientTlsKey : string ( thresholdKeyBytes ) ,
ClientTlsCertificate : string ( thresholdCertBytes ) ,
}
return & clientConfig , nil
}
func ( service * ThresholdProvisioningService ) GetServerInstallScript ( tlsCertificateSubject string , tlsCertificateIps [ ] net . IP ) ( string , error ) {
installScript := ` # ! / bin / bash
# set - x
set - e
echo "creating threshold user and group"
useradd threshold
# groupadd : group ' threshold ' already exists
# groupadd threshold
usermod - a - G threshold threshold
echo "establishing threshold folder"
mkdir / opt / threshold
chown threshold : threshold / opt / threshold
chmod 500 / opt / threshold
echo "downloading threshold tar file"
curl - sS "{{ARTIFACT_URL}}" > / tmp / threshold . gz
# TODO actually verify the checksum here .
# echo "verifying checksum"
# CORRECT_CHECKSUM = "$(sha256sum /tmp/threshold-{{ARCH}}.tar.gz | grep '{{TAR_SHA256}}' | wc -l)"
# if [ $ CORRECT_CHECKSUM - ne 1 ] ; then
# echo "bad checksum on /tmp/threshold-{{ARCH}}.tar.gz:"
# sha256sum / tmp / threshold - { { ARCH } } . tar . gz
# echo "expected {{TAR_SHA256}}."
# exit 1
# fi
echo "unarchiving threshold binary"
gzip -- stdout -- decompress "/tmp/threshold.gz" > "/opt/threshold/threshold"
chown threshold : threshold / opt / threshold / threshold
chmod 500 / opt / threshold / threshold
echo "use setcap to allow threshold binary to listen on all ports without running as admin"
setcap cap_net_bind_service = + ep / opt / threshold / threshold
echo "writing threshold config file"
echo '
{
"DebugLog" : false ,
"Domain" : "{{THRESHOLD_DOMAIN}}" ,
"MultiTenantMode" : true ,
"MultiTenantInternalAPIListenPort" : 9057 ,
"MultiTenantInternalAPICaCertificateFile" : "greenhouse_management_api_auth_CA.crt" ,
"ListenPort" : 9056 ,
"UseTls" : true ,
"CaCertificateFilesGlob" : "greenhouse_CA.crt" ,
"ServerTlsKeyFile" : "threshold.key" ,
"ServerTlsCertificateFile" : "threshold.crt" ,
"Metrics" : {
"PrometheusMetricsAPIPort" : 9090
}
}
' > / opt / threshold / config . json
echo "writing x.509 certificate files"
echo ' { { GREENHOUSE_MANAGEMENT_API_AUTH_CA } } ' > / opt / threshold / greenhouse_management_api_auth_CA . crt
chown threshold : threshold / opt / threshold / greenhouse_management_api_auth_CA . crt
chmod 400 / opt / threshold / greenhouse_management_api_auth_CA . crt
echo ' { { GREENHOUSE_CA } } ' > / opt / threshold / greenhouse_CA . crt
chown threshold : threshold / opt / threshold / greenhouse_CA . crt
chmod 400 / opt / threshold / greenhouse_CA . crt
echo ' { { THRESHOLD_CERT } } ' > / opt / threshold / threshold . crt
chown threshold : threshold / opt / threshold / threshold . crt
chmod 400 / opt / threshold / threshold . crt
echo ' { { THRESHOLD_KEY } } ' > / opt / threshold / threshold . key
chown threshold : threshold / opt / threshold / threshold . key
chmod 400 / opt / threshold / threshold . key
echo "writing threshold systemd service unit file"
echo '
[ Unit ]
Description = TCP reverse tunnel for greenhouse . server . garden
After = network . target
[ Service ]
Type = simple
Restart = always
# wait at least 5 seconds before restarting if it crashes
RestartSec = 5
# never give up on restarting this service if it crashes
# / etc / systemd / system / threshold . service : 14 : Unknown lvalue ' StartLimitIntervalSec ' i
# StartLimitIntervalSec = 0
WorkingDirectory = / opt / threshold
ExecStart = / opt / threshold / threshold - mode server - configFile / opt / threshold / config . json
User = threshold
Group = threshold
[ Install ]
WantedBy = multi - user . target
' > / etc / systemd / system / threshold . service
chown threshold : threshold / etc / systemd / system / threshold . service
chmod 755 / etc / systemd / system / threshold . service
echo "enabling threshold systemd service"
systemctl daemon - reload
systemctl enable threshold . service
echo "starting threshold systemd service"
systemctl start threshold . service
sleep 1
echo "printing threshold logs:"
echo ""
journalctl - u threshold . service - n 1000 -- no - pager
echo ""
echo ""
echo ""
echo "checking threshold service status:"
echo ""
systemctl status threshold . service -- no - pager
echo ""
echo ""
echo "threshold has started!"
`
managementAPIAuthCA , err := service . PKI . GetCACertificate ( managementAPIAuthCAName )
if err != nil {
return "" , errors . Wrap ( err , "GetCACertificate" )
}
managementAPIAuthCABytes := pem . EncodeToMemory ( & pem . Block {
Bytes : managementAPIAuthCA . Raw ,
Type : "CERTIFICATE" ,
} )
mainCA , err := service . PKI . GetCACertificate ( mainCAName )
if err != nil {
return "" , errors . Wrap ( err , "GetCACertificate" )
}
mainCABytes := pem . EncodeToMemory ( & pem . Block {
Bytes : mainCA . Raw ,
Type : "CERTIFICATE" ,
} )
expiry := time . Now ( ) . Add ( time . Hour * time . Duration ( 24 * 31 * 12 * 99 ) )
thresholdKey , thresholdCert , err := service . PKI . GetServerKeyPair ( mainCAName , tlsCertificateSubject , tlsCertificateIps , expiry )
thresholdKeyBytes := pem . EncodeToMemory ( & pem . Block {
Bytes : x509 . MarshalPKCS1PrivateKey ( thresholdKey ) ,
Type : "RSA PRIVATE KEY" ,
} )
thresholdCertBytes := pem . EncodeToMemory ( & pem . Block {
Bytes : thresholdCert . Raw ,
Type : "CERTIFICATE" ,
} )
substitutions := map [ string ] string {
"ARTIFACT_URL" : "https://picopublish.sequentialread.com/files/threshold-0.0.0-a6ca9d0-6b18-linux-amd64.gz" ,
"THRESHOLD_SHA256" : "0105ea6154aafd711c52626345d0442f3af0ca8a982ff1453b66fd76e7145c0e" ,
"THRESHOLD_DOMAIN" : "greenhouse.server.garden" ,
"GREENHOUSE_MANAGEMENT_API_AUTH_CA" : string ( managementAPIAuthCABytes ) ,
"GREENHOUSE_CA" : string ( mainCABytes ) ,
"THRESHOLD_CERT" : string ( thresholdCertBytes ) ,
"THRESHOLD_KEY" : string ( thresholdKeyBytes ) ,
}
for k , v := range substitutions {
installScript = strings . ReplaceAll ( installScript , fmt . Sprintf ( "{{%s}}" , k ) , v )
}
return installScript , nil
}