Skip to content

Commit

Permalink
CB-27601: Open port 7071 for HTTPS communication and use it for com…
Browse files Browse the repository at this point in the history
…munication between salt-bootstrap services of the cluster

- Currently there is neither client- nor server-side validation of certificates, TLS is only used to ensure the communication is encrypted
- The server is provided the same certs used by nginx under `/etc/certs`, but these don't serve a purpose as client-side validation is turned off for the clients
- The `7070` port remains open for HTTP communication
- The communication between nginx and salt-bootstrap still uses the non-TLS `7070` port, as this is an internal call on the machine
  • Loading branch information
szabolcs-horvath committed Nov 28, 2024
1 parent 5db45fa commit e7a9c29
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 19 deletions.
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BINARY=salt-bootstrap

VERSION=0.13.9
VERSION=0.14.0
BUILD_TIME=$(shell date +%FT%T)
LDFLAGS=-ldflags "-X github.com/hortonworks/salt-bootstrap/saltboot.Version=${VERSION} -X github.com/hortonworks/salt-bootstrap/saltboot.BuildTime=${BUILD_TIME}"
GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./.git/*")
Expand Down Expand Up @@ -50,7 +50,10 @@ build-docker:
docker run --rm ${USER_NS} -v "${PWD}":/go/src/github.com/hortonworks/salt-bootstrap -w /go/src/github.com/hortonworks/salt-bootstrap -e VERSION=${VERSION} golang:1.14.3 make build

build-darwin:
GOOS=darwin go build -a -installsuffix cgo ${LDFLAGS} -o build/Darwin_x86_64/${BINARY} main.go
GOOS=darwin GOARCH=amd64 go build -a -installsuffix cgo ${LDFLAGS} -o build/Darwin_x86_64/${BINARY} main.go

build-darwin-arm64:
GOOS=darwin GOARCH=arm64 go build -a -installsuffix cgo ${LDFLAGS} -o build/Darwin_arm64/${BINARY} main.go

build-linux:
GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo ${LDFLAGS} -o build/Linux_x86_64/${BINARY} main.go
Expand Down
35 changes: 30 additions & 5 deletions saltboot/distributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package saltboot

import (
"bytes"
"crypto/tls"
"encoding/json"
"io/ioutil"
"log"
Expand All @@ -17,7 +18,30 @@ import (
"github.com/hortonworks/salt-bootstrap/saltboot/model"
)

func determineProtocol() string {
if HttpsEnabled() {
return "https://"
} else {
return "http://"
}
}

func getHttpClient() *http.Client {
if HttpsEnabled() {
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
} else {
return &http.Client{}
}
}

func DistributeRequest(clients []string, endpoint, user, pass string, requestBody RequestBody) <-chan model.Response {
protocol := determineProtocol()
var wg sync.WaitGroup
wg.Add(len(clients))
c := make(chan model.Response, len(clients))
Expand All @@ -38,16 +62,16 @@ func DistributeRequest(clients []string, endpoint, user, pass string, requestBod
if len(requestBody.Signature) > 0 {
indexString := strconv.Itoa(index)
log.Printf("[distribute] Send signed request to client: %s with index: %s", client, indexString)
req, _ = http.NewRequest("POST", "http://"+clientAddr+endpoint+"?index="+indexString, bytes.NewBufferString(requestBody.SignedPayload))
req, _ = http.NewRequest("POST", protocol+clientAddr+endpoint+"?index="+indexString, bytes.NewBufferString(requestBody.SignedPayload))
req.Header.Set(SIGNATURE, requestBody.Signature)
} else {
log.Printf("[distribute] Send plain request to client: %s", client)
req, _ = http.NewRequest("POST", "http://"+clientAddr+endpoint, bytes.NewBuffer(requestBody.PlainPayload))
req, _ = http.NewRequest("POST", protocol+clientAddr+endpoint, bytes.NewBuffer(requestBody.PlainPayload))
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(user, pass)

httpClient := &http.Client{}
httpClient := getHttpClient()
resp, err := httpClient.Do(req)
if err != nil {
log.Printf("[distribute] [ERROR] Failed to send request to: %s, error: %s", client, err.Error())
Expand Down Expand Up @@ -81,6 +105,7 @@ func DistributeRequest(clients []string, endpoint, user, pass string, requestBod
func DistributeFileUploadRequest(endpoint string, user string, pass string, targets []string, path string,
permissions string, file multipart.File, header *multipart.FileHeader, signature string) <-chan model.Response {

protocol := determineProtocol()
var wg sync.WaitGroup
wg.Add(len(targets))
c := make(chan model.Response, len(targets))
Expand Down Expand Up @@ -123,12 +148,12 @@ func DistributeFileUploadRequest(endpoint string, user string, pass string, targ
targetAddress = target + ":" + strconv.Itoa(DetermineBootstrapPort())
}

req, err := http.NewRequest("POST", "http://"+targetAddress+endpoint, bytes.NewReader(fileContent))
req, err := http.NewRequest("POST", protocol+targetAddress+endpoint, bytes.NewReader(fileContent))
req.Header.Set(SIGNATURE, signature)
req.Header.Set("Content-Type", bodyWriter.FormDataContentType())
req.SetBasicAuth(user, pass)

httpClient := &http.Client{}
httpClient := getHttpClient()
resp, err := httpClient.Do(req)
if err != nil {
log.Printf("[DistributeFileUploadRequest] [ERROR] Failed to send request to: %s, error: %s", target, err.Error())
Expand Down
106 changes: 101 additions & 5 deletions saltboot/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,31 @@ import (
)

const (
defaultPort = 7070
portKey = "SALTBOOT_PORT"
httpsEnabledKey = "SALTBOOT_HTTPS_ENABLED"
portKey = "SALTBOOT_PORT"
defaultPort = 7070
httpsPortKey = "SALTBOOT_HTTPS_PORT"
defaultHttpsPort = 7071
httpsCertFileKey = "SALTBOOT_HTTPS_CERT_FILE"
defaultHttpsCertFile = "/etc/certs/cluster.pem"
httpsKeyFileKey = "SALTBOOT_HTTPS_KEY_FILE"
defaultHttpsKeyFile = "/etc/certs/cluster-key.pem"
httpsCaCertFileKey = "SALTBOOT_HTTPS_CACERT_FILE"
defaultHttpsCaCertFileKey = "/etc/certs/ca.pem"

userKey = "SALTBOOT_USER"
passwdKey = "SALTBOOT_PASSWORD"
signKey = "SALTBOOT_SIGN_KEY"
configLocKey = "SALTBOOT_CONFIG"
defaultConfigLoc = "/etc/salt-bootstrap/security-config.yml"
)

type HttpsConfig struct {
CertFile string `json:"certFile" yaml:"certFile"`
KeyFile string `json:"keyFile" yaml:"keyFile"`
CaCertFile string `json:"caCertFile" yaml:"caCertFile"`
}

type SecurityConfig struct {
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
Expand Down Expand Up @@ -51,17 +67,97 @@ func (sc *SecurityConfig) validate() error {
}

func DetermineBootstrapPort() int {
if HttpsEnabled() {
return DetermineHttpsPort()
} else {
return DetermineHttpPort()
}
}

func HttpsEnabled() bool {
httpsEnabled := os.Getenv(httpsEnabledKey)
log.Printf("[HttpsEnabled] %s: %s", httpsEnabledKey, httpsEnabled)
return httpsEnabled != "" && strings.ToLower(httpsEnabled) != "false"
}

func DetermineHttpsPort() int {
portStr := os.Getenv(httpsPortKey)
log.Printf("[DetermineHttpsPort] %s: %s", httpsPortKey, portStr)
port, err := strconv.Atoi(portStr)
if err != nil {
log.Printf("[DetermineHttpsPort] using default HTTPS port: %d", defaultHttpsPort)
port = defaultHttpsPort
}
return port
}

func DetermineHttpPort() int {
portStr := os.Getenv(portKey)
log.Printf("[determineBootstrapPort] SALTBOOT_PORT: %s", portStr)
log.Printf("[DetermineHttpPort] %s: %s", portKey, portStr)
port, err := strconv.Atoi(portStr)
if err != nil {
log.Printf("[DetermineHttpPort] using default HTTP port: %d", defaultPort)
port = defaultPort
log.Printf("[determineBootstrapPort] using default port: %d", port)
}

return port
}

func GetHttpsConfig() HttpsConfig {
var httpsConfig HttpsConfig
certFileStr := os.Getenv(httpsCertFileKey)
keyFileStr := os.Getenv(httpsKeyFileKey)
caCertFileStr := os.Getenv(httpsCaCertFileKey)
log.Printf("[GetHttpsConfig] %s: %s", httpsCertFileKey, certFileStr)
log.Printf("[GetHttpsConfig] %s: %s", httpsKeyFileKey, keyFileStr)
log.Printf("[GetHttpsConfig] %s: %s", httpsCaCertFileKey, caCertFileStr)

if certFileStr == "" {
httpsConfig.CertFile = defaultHttpsCertFile
log.Printf("[GetHttpsConfig] using default cert file: %s", defaultHttpsCertFile)
} else {
httpsConfig.CertFile = certFileStr
}
if keyFileStr == "" {
httpsConfig.KeyFile = defaultHttpsKeyFile
log.Printf("[GetHttpsConfig] using default key file: %s", defaultHttpsKeyFile)
} else {
httpsConfig.KeyFile = keyFileStr
}
if caCertFileStr == "" {
httpsConfig.CaCertFile = defaultHttpsCaCertFileKey
log.Printf("[GetHttpsConfig] using default ca cert file: %s", defaultHttpsCaCertFileKey)
} else {
httpsConfig.CaCertFile = caCertFileStr
}
return httpsConfig
}

func GetConcatenatedCertFilePath(httpsConfig HttpsConfig) (string, error) {
tmpFile, err := ioutil.TempFile("/tmp", "saltboot-*.pem")
defer tmpFile.Close()
if err != nil {
return "", err
}
serverCert, err := ioutil.ReadFile(httpsConfig.CertFile)
if err != nil {
return "", err
}
caCert, err := ioutil.ReadFile(httpsConfig.CaCertFile)
if err != nil {
return "", err
}
_, err = tmpFile.Write(serverCert)
if err != nil {
return "", err
}
_, err = tmpFile.Write(caCert)
if err != nil {
return "", err
}
log.Printf("[GetConcatenatedCertFilePath] concatenated cert file successfully created: %s", tmpFile.Name())
return tmpFile.Name(), nil
}

func DetermineSecurityDetails(getEnv func(key string) string, securityConfig func() string) (*SecurityConfig, error) {
var config SecurityConfig
configLoc := strings.TrimSpace(getEnv(configLocKey))
Expand Down
124 changes: 122 additions & 2 deletions saltboot/netutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,138 @@ func TestDetermineBootstrapPortDefault(t *testing.T) {
port := DetermineBootstrapPort()

if port != defaultPort {
t.Errorf("port not match to default %d == %d", defaultPort, port)
t.Errorf("port does not match the default port %d == %d", defaultPort, port)
}
}

func TestDetermineBootstrapPortDefaultHttpsFalse(t *testing.T) {
os.Setenv(httpsEnabledKey, "false")

port := DetermineBootstrapPort()

if port != defaultPort {
t.Errorf("port does not match the default port %d == %d", defaultPort, port)
}

os.Unsetenv(httpsEnabledKey)
}

func TestDetermineBootstrapPortDefaultHttps(t *testing.T) {
os.Setenv(httpsEnabledKey, "true")

port := DetermineBootstrapPort()

if port != defaultHttpsPort {
t.Errorf("port does not match the HTTPS default port %d == %d", defaultHttpsPort, port)
}

os.Unsetenv(httpsEnabledKey)
}

func TestDetermineBootstrapPortCustom(t *testing.T) {
os.Setenv(portKey, "8080")

port := DetermineBootstrapPort()

if port != 8080 {
t.Errorf("port not match to default %d == %d", 8080, port)
t.Errorf("port does not match the custom port %d == %d", 8080, port)
}

os.Unsetenv(portKey)
}

func TestDetermineBootstrapPortCustomHttps(t *testing.T) {
os.Setenv(httpsEnabledKey, "true")
os.Setenv(httpsPortKey, "8080")

port := DetermineBootstrapPort()

if port != 8080 {
t.Errorf("port does not match the custom HTTPS port %d == %d", 8080, port)
}

os.Unsetenv(httpsEnabledKey)
os.Unsetenv(httpsPortKey)
}

func TestDetermineHttpsPortDefault(t *testing.T) {
port := DetermineHttpsPort()

if port != defaultHttpsPort {
t.Errorf("port does not match the default HTTPS port %d == %d", defaultHttpsPort, port)
}
}

func TestDetermineHttpsPortCustom(t *testing.T) {
os.Setenv(httpsPortKey, "8080")

port := DetermineHttpsPort()

if port != 8080 {
t.Errorf("port does not match the custom HTTPS port %d == %d", 8080, port)
}

os.Unsetenv(httpsPortKey)
}

func TestDetermineHttpPortDefault(t *testing.T) {
port := DetermineHttpPort()

if port != defaultPort {
t.Errorf("port does not match the default port %d == %d", defaultPort, port)
}
}

func TestDetermineHttpPortCustom(t *testing.T) {
os.Setenv(portKey, "8080")

port := DetermineHttpPort()

if port != 8080 {
t.Errorf("port does not match the custom port %d == %d", 8080, port)
}

os.Unsetenv(portKey)
}

func TestGetHttpsConfigDefault(t *testing.T) {
httpsConfig := GetHttpsConfig()

if httpsConfig.CertFile != defaultHttpsCertFile {
t.Errorf("cert file does not match the default %s == %s", defaultHttpsCertFile, httpsConfig.CertFile)
}
if httpsConfig.KeyFile != defaultHttpsKeyFile {
t.Errorf("key file does not match the default %s == %s", defaultHttpsKeyFile, httpsConfig.KeyFile)
}
if httpsConfig.CaCertFile != defaultHttpsCaCertFileKey {
t.Errorf("ca cert file does not match the default %s == %s", defaultHttpsCaCertFileKey, httpsConfig.CaCertFile)
}
}

func TestGetHttpsConfigCustom(t *testing.T) {
os.Setenv(httpsCertFileKey, "path/certfile.pem")
os.Setenv(httpsKeyFileKey, "path/keyfile.pem")
os.Setenv(httpsCaCertFileKey, "path/ca.pem")

httpsConfig := GetHttpsConfig()

if httpsConfig.CertFile != "path/certfile.pem" {
t.Errorf("cert file does not match the default %s == %s", "path/certfile.pem", httpsConfig.CertFile)
}
if httpsConfig.KeyFile != "path/keyfile.pem" {
t.Errorf("key file does not match the default %s == %s", "path/keyfile.pem", httpsConfig.KeyFile)
}
if httpsConfig.CaCertFile != "path/ca.pem" {
t.Errorf("ca cert file does not match the default %s == %s", "path/ca.pem", httpsConfig.CaCertFile)
}

os.Unsetenv(httpsCertFileKey)
os.Unsetenv(httpsKeyFileKey)
os.Unsetenv(httpsCaCertFileKey)
}

func TestGetConcatenatedCertFilePath(t *testing.T) {

}

func TestConfigfileFoundByEnv(t *testing.T) {
Expand Down
Loading

0 comments on commit e7a9c29

Please sign in to comment.