Skip to content

Commit

Permalink
Implement HTTP/HTTPS including permanent redirect (#282)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrsabath authored Jul 6, 2023
2 parents 71e00cd + 12b1590 commit 1d82509
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 171 deletions.
215 changes: 62 additions & 153 deletions tornjak-backend/api/agent/server.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package api

import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -88,7 +87,6 @@ func (s *Server) healthcheck(w http.ResponseWriter, r *http.Request) {
retError(w, emsg, http.StatusBadRequest)
return
}

}

func (s *Server) debugServer(w http.ResponseWriter, r *http.Request) {
Expand All @@ -110,7 +108,6 @@ func (s *Server) debugServer(w http.ResponseWriter, r *http.Request) {
retError(w, emsg, http.StatusBadRequest)
return
}

}

func (s *Server) agentList(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -542,7 +539,7 @@ func (s *Server) home(w http.ResponseWriter, r *http.Request) {
}
}

func (s *Server) GetRouter() *mux.Router {
func (s *Server) GetRouter() http.Handler {
rtr := mux.NewRouter()

// Home
Expand Down Expand Up @@ -585,6 +582,23 @@ func (s *Server) GetRouter() *mux.Router {
return rtr
}

func redirectHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" && r.Method != "HEAD" {
http.Error(w, "Use HTTPS", http.StatusBadRequest)
return
}
target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusFound)
}

func stripPort(hostport string) string {
host, _, err := net.SplitHostPort(hostport)
if err != nil {
return hostport
}
return net.JoinHostPort(host, "443")
}

// HandleRequests connects api links with respective functions
// Functions currently handle the api calls all as post-requests
func (s *Server) HandleRequests() {
Expand All @@ -593,167 +607,67 @@ func (s *Server) HandleRequests() {
log.Fatal("Cannot Configure: ", err)
}

numPorts := 0
errChannel := make(chan error, 3)
rtr := s.GetRouter()

// TLS Stack handling
numPorts := 0 // TODO: replace with workerGroup for thread safety
errChannel := make(chan error, 2)
var httpHandler http.Handler = http.HandlerFunc(redirectHTTP)
serverConfig := s.TornjakConfig.Server
if serverConfig.HTTPConfig == nil {
serverConfig.HTTPConfig = &HTTPConfig{
ListenPort: 80,
}
}

if serverConfig.HttpConfig != nil && serverConfig.HttpConfig.Enabled {
numPorts += 1
listenPort := serverConfig.HttpConfig.ListenPort
tlsType := "HTTP"
go func() {
if listenPort == 0 {
err := errors.Errorf("%s server: Cannot have empty port: %d", tlsType, listenPort)
errChannel <- err
return
}
addr := fmt.Sprintf(":%d", listenPort)
fmt.Printf("Starting to listen on %s...\n", addr)
err := http.ListenAndServe(addr, rtr)
err = errors.Errorf("%s server: Error serving: %v", tlsType, err)
errChannel <- err
// log.Printf("HTTP serve error: %v", err)
}()
if serverConfig.HTTPSConfig == nil {
httpHandler = s.GetRouter()
log.Print("WARNING: Please consider configuring HTTPS to ensure traffic is running on encrypted endpoint!")
}

if serverConfig.TlsConfig != nil && serverConfig.TlsConfig.Enabled {
go func() {
numPorts += 1
listenPort := serverConfig.TlsConfig.ListenPort
certPath := serverConfig.TlsConfig.Cert
keyPath := serverConfig.TlsConfig.Key
tlsType := "TLS"
addr := fmt.Sprintf(":%d", serverConfig.HTTPConfig.ListenPort)
fmt.Printf("Starting to listen on %s...\n", addr)
err := http.ListenAndServe(addr, httpHandler)
if err != nil {
errChannel <- err
}
}()

if serverConfig.HTTPSConfig != nil {
go func() {
if listenPort == 0 {
err := errors.Errorf("%s server: Cannot have empty port: %d", tlsType, listenPort)
errChannel <- err
return
numPorts += 1
if serverConfig.HTTPSConfig.ListenPort == 0 {
serverConfig.HTTPSConfig.ListenPort = 443
}
addr := fmt.Sprintf(":%d", listenPort)
// Create a CA certificate pool and add cert.pem to it
caCert, err := os.ReadFile(certPath)
tls := serverConfig.HTTPSConfig.TLS
tlsConfig, err := tls.Parse()
if err != nil {
err = errors.Errorf("%s server: CA pool error: %v", tlsType, err)
err = fmt.Errorf("failed parsing tls: %w", err)
errChannel <- err
return
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

// Create the TLS Config with the CA pool and enable Client certificate validation
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
}
//tlsConfig.BuildNameToCertificate()

addr := fmt.Sprintf(":%d", serverConfig.HTTPSConfig.ListenPort)
// Create a Server instance to listen on port 8443 with the TLS config
server := &http.Server{
Handler: rtr,
Handler: s.GetRouter(),
Addr: addr,
TLSConfig: tlsConfig,
}

fmt.Printf("Starting to listen with %s on %s...\n", tlsType, addr)
if _, err := os.Stat(certPath); os.IsNotExist(err) {
err = errors.Errorf("%s server: File does not exist %s", tlsType, certPath)
errChannel <- err
return
}
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
err = errors.Errorf("%s server: File does not exist %s", tlsType, keyPath)
errChannel <- err
return
}

err = server.ListenAndServeTLS(certPath, keyPath)
err = errors.Errorf("%s server: Error serving: %v", tlsType, err)
errChannel <- err
}()
}

if serverConfig.MtlsConfig != nil && serverConfig.MtlsConfig.Enabled {
numPorts += 1
listenPort := serverConfig.MtlsConfig.ListenPort
certPath := serverConfig.MtlsConfig.Cert
keyPath := serverConfig.MtlsConfig.Key
caPath := serverConfig.MtlsConfig.Ca
tlsType := "mTLS"

go func() {
if listenPort == 0 {
err := errors.Errorf("%s server: Cannot have empty port: %d", tlsType, listenPort)
errChannel <- err
return
}
addr := fmt.Sprintf(":%d", listenPort)
// Create a CA certificate pool and add cert.pem to it
caCert, err := os.ReadFile(certPath)
fmt.Printf("Starting https on %s...\n", addr)
err = server.ListenAndServeTLS(tls.Cert, tls.Key)
if err != nil {
err = errors.Errorf("%s server: CA pool error: %v", tlsType, err)
errChannel <- err
return
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

// add mTLS CA path to cert pool as well
if _, err := os.Stat(caPath); os.IsNotExist(err) {
err = errors.Errorf("%s server: File does not exist %s", tlsType, caPath)
errChannel <- err
return
}
mTLSCaCert, err := os.ReadFile(caPath)
if err != nil {
err = errors.Errorf("%s server: Could not read file %s: %v", tlsType, caPath, err)
errChannel <- err
return
}
caCertPool.AppendCertsFromPEM(mTLSCaCert)

// Create the TLS Config with the CA pool and enable Client certificate validation

mtlsConfig := &tls.Config{
ClientCAs: caCertPool,
}
mtlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
//mtlsConfig.BuildNameToCertificate()

// Create a Server instance to listen on port 8443 with the TLS config
server := &http.Server{
Handler: rtr,
Addr: addr,
TLSConfig: mtlsConfig,
}

fmt.Printf("Starting to listen with %s on %s...\n", tlsType, addr)
if _, err := os.Stat(certPath); os.IsNotExist(err) {
err = errors.Errorf("%s server: File does not exist %s", tlsType, certPath)
err = fmt.Errorf("server error serving on https: %w", err)
errChannel <- err
return
}
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
log.Fatalf("File does not exist %s", keyPath)
}
err = server.ListenAndServeTLS(certPath, keyPath)
err = errors.Errorf("%s server: Error serving: %v", tlsType, err)
errChannel <- err
}()
}

// no ports opened
if numPorts == 0 {
log.Printf("No connections opened. HINT: at least one HTTP, TLS, or MTLS must be enabled in Tornjak config")
}

// as errors come in, read them, and block
for i := 0; i < numPorts; i++ {
err := <-errChannel
log.Printf("%v", err)
}

}

func stringFromToken(keyToken token.Token) (string, error) {
Expand Down Expand Up @@ -867,11 +781,6 @@ func (s *Server) VerifyConfiguration() error {
return errors.New("'config > server > spire_socket_path' field not defined")
}

serverConfig := s.TornjakConfig.Server
if serverConfig.HttpConfig == nil && serverConfig.TlsConfig == nil && serverConfig.MtlsConfig == nil {
return errors.New("'config > server' must have at least one of HTTP, TLS, or mTLS sections defined")
}

/* Verify Plugins */
if s.TornjakConfig.Plugins == nil {
return errors.New("'config > plugins' field not defined")
Expand Down Expand Up @@ -931,18 +840,18 @@ func (s *Server) Configure() error {

// create plugin component based on type
switch pluginType {
// configure datastore
case "DataStore":
s.Db, err = NewAgentsDB(pluginObject)
if err != nil {
return errors.Errorf("Cannot configure datastore plugin: %v", err)
}
// configure auth
case "UserManagement":
s.Auth, err = NewAuth(pluginObject)
if err != nil {
return errors.Errorf("Cannot configure auth plugin: %v", err)
}
// configure datastore
case "DataStore":
s.Db, err = NewAgentsDB(pluginObject)
if err != nil {
return errors.Errorf("Cannot configure datastore plugin: %v", err)
}
// configure auth
case "UserManagement":
s.Auth, err = NewAuth(pluginObject)
if err != nil {
return errors.Errorf("Cannot configure auth plugin: %v", err)
}
}
// TODO Handle when multiple plugins configured
}
Expand Down
74 changes: 56 additions & 18 deletions tornjak-backend/api/agent/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package api

import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"

"github.com/hashicorp/hcl/hcl/ast"
)

Expand All @@ -23,30 +28,63 @@ type TornjakConfig struct {
/* Server configuration*/

type serverConfig struct {
SPIRESocket string `hcl:"spire_socket_path"`
HttpConfig *httpConfig `hcl:"http"`
TlsConfig *tlsConfig `hcl:"tls"`
MtlsConfig *mtlsConfig `hcl:"mtls"`
SPIRESocket string `hcl:"spire_socket_path"`
HTTPConfig *HTTPConfig `hcl:"http"`
HTTPSConfig *HTTPSConfig `hcl:"https"`
}

type HTTPConfig struct {
ListenPort int `hcl:"port"`
}

type httpConfig struct {
Enabled bool `hcl:"enabled"`
ListenPort int `hcl:"port"`
type HTTPSConfig struct {
*HTTPConfig
TLS TLSConfig `hcl:"tls"`
}

type tlsConfig struct {
Enabled bool `hcl:"enabled"`
ListenPort int `hcl:"port"`
Cert string `hcl:"cert"`
Key string `hcl:"key"`
type TLSConfig struct {
Cert string `hcl:"cert"`
Key string `hcl:"key"`
ClientCA string `hcl:"client_ca"`
}

type mtlsConfig struct {
Enabled bool `hcl:"enabled"`
ListenPort int `hcl:"port"`
Cert string `hcl:"cert"`
Key string `hcl:"key"`
Ca string `hcl:"ca"`
func (t TLSConfig) Parse() (*tls.Config, error) {
serverCertPath := t.Cert
serverKeyPath := t.Key
clientCAPath := t.ClientCA

if _, err := os.Stat(serverCertPath); os.IsNotExist(err) {
return nil, fmt.Errorf("server cert path: %w", err)
}
if _, err := os.Stat(serverKeyPath); os.IsNotExist(err) {
return nil, fmt.Errorf("server key path: %w", err)
}

// Create a CA certificate pool and add cert.pem to it
serverCert, err := os.ReadFile(serverCertPath)
if err != nil {
return nil, fmt.Errorf("server ca pool error: %w", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(serverCert)

// add mTLS CA path to cert pool as well
if _, err := os.Stat(clientCAPath); os.IsNotExist(err) {
return nil, fmt.Errorf("server file does not exist %s", clientCAPath)
}
clientCA, err := os.ReadFile(clientCAPath)
if err != nil {
return nil, fmt.Errorf("server: could not read file %s: %w", clientCAPath, err)
}
caCertPool.AppendCertsFromPEM(clientCA)

// Create the TLS Config with the CA pool and enable Client certificate validation
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
}
//tlsConfig.BuildNameToCertificate()

return tlsConfig, nil
}

/* Plugin types */
Expand Down

0 comments on commit 1d82509

Please sign in to comment.