Fetch of authorization token

Added support for configuration of root CAs for trust validation.

Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
Issue-ID: NONRTRIC-735
Change-Id: I9ee9e73eeb1f9f94a7ea73342d4ddee25066729f
diff --git a/auth-token-fetch/HTTPClient.go b/auth-token-fetch/HTTPClient.go
index ab76b13..a765461 100644
--- a/auth-token-fetch/HTTPClient.go
+++ b/auth-token-fetch/HTTPClient.go
@@ -23,6 +23,7 @@
 import (
 	"bytes"
 	"crypto/tls"
+	"crypto/x509"
 	"fmt"
 	"io"
 
@@ -38,10 +39,10 @@
 	Do(*http.Request) (*http.Response, error)
 }
 
-func CreateHttpClient(cert tls.Certificate, timeout time.Duration) *http.Client {
+func CreateHttpClient(cert tls.Certificate, caCerts *x509.CertPool, timeout time.Duration) *http.Client {
 	return &http.Client{
 		Timeout:   timeout,
-		Transport: createTransport(cert),
+		Transport: createTransport(cert, caCerts),
 	}
 }
 
@@ -89,9 +90,11 @@
 	return putError
 }
 
-func createTransport(cert tls.Certificate) *http.Transport {
+func createTransport(cert tls.Certificate, caCerts *x509.CertPool) *http.Transport {
 	return &http.Transport{
 		TLSClientConfig: &tls.Config{
+			ClientCAs: caCerts,
+			RootCAs:   caCerts,
 			Certificates: []tls.Certificate{
 				cert,
 			},
diff --git a/auth-token-fetch/HTTPClient_test.go b/auth-token-fetch/HTTPClient_test.go
index e0a4cd1..7b8deb0 100644
--- a/auth-token-fetch/HTTPClient_test.go
+++ b/auth-token-fetch/HTTPClient_test.go
@@ -43,7 +43,7 @@
 func Test_CreateClient(t *testing.T) {
 	assertions := require.New(t)
 
-	client := CreateHttpClient(tls.Certificate{}, 5*time.Second)
+	client := CreateHttpClient(tls.Certificate{}, nil, 5*time.Second)
 
 	transport := client.Transport
 	assertions.Equal("*http.Transport", reflect.TypeOf(transport).String())
diff --git a/auth-token-fetch/config.go b/auth-token-fetch/config.go
index 18d610d..dfeeb96 100644
--- a/auth-token-fetch/config.go
+++ b/auth-token-fetch/config.go
@@ -33,6 +33,7 @@
 type Config struct {
 	LogLevel                log.Level
 	CertPath                string
+	CACertsPath             string
 	KeyPath                 string
 	AuthServiceUrl          string
 	GrantType               string
@@ -44,14 +45,15 @@
 
 func NewConfig() *Config {
 	return &Config{
-		CertPath:                getEnv("CERT_PATH", "security/tls.crt"),
-		KeyPath:                 getEnv("CERT_KEY_PATH", "security/tls.key"),
+		CertPath:                getEnv("CERT_PATH", "security/tls.crt", false),
+		KeyPath:                 getEnv("CERT_KEY_PATH", "security/tls.key", false),
+		CACertsPath:             getEnv("ROOT_CA_CERTS_PATH", "", false),
 		LogLevel:                getLogLevel(),
-		GrantType:               getEnv("CREDS_GRANT_TYPE", ""),
-		ClientSecret:            getEnv("CREDS_CLIENT_SECRET", ""),
-		ClientId:                getEnv("CREDS_CLIENT_ID", ""),
-		AuthTokenOutputFileName: getEnv("OUTPUT_FILE", "/tmp/authToken.txt"),
-		AuthServiceUrl:          getEnv("AUTH_SERVICE_URL", "https://localhost:39687/example-singlelogin-sever/login"),
+		GrantType:               getEnv("CREDS_GRANT_TYPE", "", false),
+		ClientSecret:            getEnv("CREDS_CLIENT_SECRET", "", true),
+		ClientId:                getEnv("CREDS_CLIENT_ID", "", false),
+		AuthTokenOutputFileName: getEnv("OUTPUT_FILE", "/tmp/authToken.txt", false),
+		AuthServiceUrl:          getEnv("AUTH_SERVICE_URL", "https://localhost:39687/example-singlelogin-sever/login", false),
 		RefreshMarginSeconds:    getEnvAsInt("REFRESH_MARGIN_SECONDS", 5, 1, 3600),
 	}
 }
@@ -61,21 +63,29 @@
 		return fmt.Errorf("missing CERT_PATH and/or CERT_KEY_PATH")
 	}
 
+	if configuration.CACertsPath == "" {
+		log.Warn("No Root CA certs loaded, no trust validation may be performed")
+	}
+
 	return nil
 }
 
-func getEnv(key string, defaultVal string) string {
+func getEnv(key string, defaultVal string, secret bool) string {
 	if value, exists := os.LookupEnv(key); exists {
-		log.Debugf("Using value: '%v' for '%v'", value, key)
+		if !secret {
+			log.Debugf("Using value: '%v' for '%v'", value, key)
+		}
 		return value
 	} else {
-		log.Debugf("Using default value: '%v' for '%v'", defaultVal, key)
+		if !secret {
+			log.Debugf("Using default value: '%v' for '%v'", defaultVal, key)
+		}
 		return defaultVal
 	}
 }
 
 func getEnvAsInt(name string, defaultVal int, min int, max int) int {
-	valueStr := getEnv(name, "")
+	valueStr := getEnv(name, fmt.Sprint(defaultVal), false)
 	if value, err := strconv.Atoi(valueStr); err == nil {
 		if value < min || value > max {
 			log.Warnf("Value out of range: '%v' for variable: '%v'. Default value: '%v' will be used", valueStr, name, defaultVal)
@@ -90,7 +100,7 @@
 }
 
 func getLogLevel() log.Level {
-	logLevelStr := getEnv("LOG_LEVEL", "Info")
+	logLevelStr := getEnv("LOG_LEVEL", "Info", false)
 	if loglevel, err := log.ParseLevel(logLevelStr); err == nil {
 		return loglevel
 	} else {
diff --git a/auth-token-fetch/config_test.go b/auth-token-fetch/config_test.go
index 8b441c1..92a63d8 100644
--- a/auth-token-fetch/config_test.go
+++ b/auth-token-fetch/config_test.go
@@ -39,6 +39,7 @@
 	os.Setenv("OUTPUT_FILE", "OUTPUT_FILE")
 	os.Setenv("AUTH_SERVICE_URL", "AUTH_SERVICE_URL")
 	os.Setenv("REFRESH_MARGIN_SECONDS", "33")
+	os.Setenv("ROOT_CA_CERTS_PATH", "ROOT_CA_CERTS_PATH")
 
 	t.Cleanup(func() {
 		os.Clearenv()
@@ -52,9 +53,11 @@
 		ClientSecret:            "CREDS_CLIENT_SECRET",
 		ClientId:                "CREDS_CLIENT_ID",
 		AuthTokenOutputFileName: "OUTPUT_FILE",
+		CACertsPath:             "ROOT_CA_CERTS_PATH",
 		RefreshMarginSeconds:    33,
 	}
 	got := NewConfig()
+	assertions.Equal(nil, validateConfiguration(got))
 
 	assertions.Equal(&wantConfig, got)
 }
diff --git a/auth-token-fetch/main.go b/auth-token-fetch/main.go
index 9a63534..41f49d3 100644
--- a/auth-token-fetch/main.go
+++ b/auth-token-fetch/main.go
@@ -22,8 +22,8 @@
 
 import (
 	"crypto/tls"
+	"crypto/x509"
 	"encoding/json"
-	"fmt"
 	"io/ioutil"
 	"net/http"
 	"net/url"
@@ -74,14 +74,10 @@
 		log.Fatalf("Stopping due to error: %v", err)
 	}
 
-	var cert tls.Certificate
-	if c, err := loadCertificate(context.Config.CertPath, context.Config.KeyPath); err == nil {
-		cert = c
-	} else {
-		log.Fatalf("Stopping due to error: %v", err)
-	}
+	cert := loadCertificate(context.Config.CertPath, context.Config.KeyPath)
+	caCerts := loadCaCerts(context.Config.CACertsPath)
 
-	webClient := CreateHttpClient(cert, 10*time.Second)
+	webClient := CreateHttpClient(cert, caCerts, 10*time.Second)
 
 	go periodicRefreshIwtToken(webClient, context)
 }
@@ -142,15 +138,29 @@
 	return jwt, err
 }
 
-func loadCertificate(certPath string, keyPath string) (tls.Certificate, error) {
+func loadCertificate(certPath string, keyPath string) tls.Certificate {
 	log.WithFields(log.Fields{"certPath": certPath, "keyPath": keyPath}).Debug("Loading cert")
-	if cert, err := tls.LoadX509KeyPair(certPath, keyPath); err == nil {
-		return cert, nil
+	cert, err := tls.LoadX509KeyPair(certPath, keyPath)
+	if check(err) {
+		return cert
 	} else {
-		return tls.Certificate{}, fmt.Errorf("cannot create x509 keypair from cert file %s and key file %s due to: %v", certPath, keyPath, err)
+		log.Fatalf("cannot create x509 keypair from cert file %s and key file %s due to: %v", certPath, keyPath, err)
+		return tls.Certificate{}
 	}
 }
 
+func loadCaCerts(caCertsPath string) *x509.CertPool {
+	var err error
+	if caCertsPath == "" {
+		return nil
+	}
+	caCert, err := ioutil.ReadFile(caCertsPath)
+	check(err)
+	caCertPool := x509.NewCertPool()
+	caCertPool.AppendCertsFromPEM(caCert)
+	return caCertPool
+}
+
 func keepAlive() {
 	channel := make(chan int)
 	<-channel
diff --git a/auth-token-fetch/main_test.go b/auth-token-fetch/main_test.go
index c575614..2222bd9 100644
--- a/auth-token-fetch/main_test.go
+++ b/auth-token-fetch/main_test.go
@@ -103,6 +103,7 @@
 
 	configuration := NewConfig()
 	configuration.AuthTokenOutputFileName = "/tmp/authToken" + fmt.Sprint(time.Now().UnixNano())
+	configuration.CACertsPath = configuration.CertPath
 	context := NewContext(configuration)
 
 	start(context)