Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement HTTP/HTTPS including permanent redirect #282

Merged
merged 1 commit into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permanent redirect to HTTPS.

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()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no https configured, then just let http handle the api requests.

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