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

Fix HTTPS config and redirect #268 #289

Merged
merged 6 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ Building Tornjak manually can be done with the Makefile. Notable make targets fo
- `make bin/tornjak-backend`: makes the Go executable of the Tornjak backend
- `make bin/tornjak-manager`: makes the Go executable of the Tornjak manager
- `make frontend-local-build`: makes the optimized ReactJS app locally for the Tornjak frontend. Uses environment variable configuration as in tornjak-frontend/.env
- `make container-backend`: containerizes Go executable of the Tornjak backend
- `make container-manager`:containerizes Go executable of the Tornjak manager
- `make container-frontend`: containerizes React JS app for the Tornjak frontend
- `make container-tornjak`: containerizes Tornjak backend with Tornjak frontend
- `make image-tornjak-backend`: containerizes Go executable of the Tornjak backend
- `make image-tornjak-manager`:containerizes Go executable of the Tornjak manager
- `make image-tornjak-frontend`: containerizes React JS app for the Tornjak frontend
- `make image-tornjak`: containerizes Tornjak backend with Tornjak frontend

For usage instructions of the containers, please see our [USAGE document](./USAGE.md) to get started.

Expand Down
1 change: 1 addition & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Please see below for compatibility charts of SPIRE server versions with Tornjak:
| Tornjak version | SPIRE Server version |
| :--------------------- | :------------------- |
| v1.1.x, v1.2.x, v1.3.x | v1.1.x, v1.2.x, v1.3.x, v1.4.x |
| v1.4.x | v1.5.x, v1.6.x, v1.7.x |

## Tornjak Backend

Expand Down
2 changes: 1 addition & 1 deletion docs/conf/agent/base.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ server {

# [required] configure HTTP connection to Tornjak server
http {
port = 10080 # opens at port 10080
port = 10000 # opens at port 10000
}

}
Expand Down
4 changes: 2 additions & 2 deletions docs/conf/agent/full.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ server {

# [required] configure HTTP connection to Tornjak server
http {
port = 10080 # container port for HTTP connection
port = 10000 # container port for HTTP connection
}

# [optional, recommended] configure HTTPS connection to Tornjak server
https {
port = 10443 # [required for HTTPS] container port for HTTPS connection
cert = "sample-keys/tls.pem" # [required for HTTPS] TLS cert
key = "sample-keys/key.pem" # [required for HTTPS] TLS key
ca = "sample-keys/rootCA.pem" # enables mTLS connection for HTTPS port
client_ca = "sample-keys/rootCA.pem" # enables mTLS connection for HTTPS port
}

### END SERVER CONNECTION CONFIGURATION ###
Expand Down
12 changes: 6 additions & 6 deletions docs/config-tornjak-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,30 @@ Runs the tornjak server.
The Tornjak config that is passed in must follow a specific format. Examples of this format can be found [below](#sample-configuration-files). In general, it is split into the `server` section with [general Tornjak server configs](#general-tornjak-server-configs), and the `plugins` section.

## General Tornjak Server Configs
The server config will contain information for the three potential connections: HTTP, TLS, and mTLS. See below for sample configuration:
The server config will contain information for the two potential connections: HTTP and HTTPS. HTTPS can be configured to follow TLS or mTLS protocol. See below for sample configuration:

```hcl
server {

spire_socket_path = "unix:///tmp/spire-server/private/api.sock" # socket to communicate with SPIRE server

http { # required block
port = 10080 # if HTTP enabled, opens HTTP listen port at container port 10080
port = 10000 # if HTTP enabled, opens HTTP listen port at container port 10080
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved
}

https {
https { # optional, recommended block
port = 10443 # if enabled, opens HTTPS listen port at container port 10443
cert = "sample-keys/tls.pem" # path of certificate for TLS
key = "sample-keys/key.pem" # path of keys for TLS
ca = "sample-keys/userCA.pem" # [optional, enables mTLS] User CA
client_ca = "sample-keys/userCA.pem" # [optional, enables mTLS] User CA
}

}
```

We have three connection types that can be opened by the server simultaneously: HTTP, TLS, and mTLS. At least one must be enabled, or the program will exit immediately. If one connection crashes, the error is logged, and the others will still run. When all crash, the Tornjak server exits and the container terminates.
We have two connection types that can be opened by the server simultaneously: HTTP and HTTPS. HTTP is always enabled, and requires a port configuration. The HTTPS connection is optional, but recommended for production use case. When HTTPS is enabled, the HTTP connection will redirect to the HTTPS URL.
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved

If a specific section is omitted or not enabled, that connection will not be created. If all are omitted or disabled, the program will exit immediately with an appropriate error log.
Under the HTTPS block, the fields `port`, `cert`, and `key` are required and enable TLS connection. To enable mTLS, you must additionally include the `client_ca` field.
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved

For examples on enabling TLS and mTLS connections, please see [our TLS and mTLS documentation](../sample-keys/README.md).

Expand Down
39 changes: 25 additions & 14 deletions examples/tls_mtls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Now that we have mounted the relevant files in step 1, we must configure the Tor

More details on the configmap can be found [in our config documentation](../../docs/config-tornjak-server.md).

One Tornjak server can open three connections simultaneously: HTTP, TLS, and mTLS, and at least one must be enabled. A configuration that opens all three is provided in [the configmap in this directory](./tornjak-configmap.yaml)
One Tornjak server can open two connections simultaneously: HTTP and HTTPS. HTTP is always enabled, and when HTTPS is enabled, the HTTP port reroutes to the HTTPS URL. A configuration that enables TLS is provided in [the configmap in this directory](./tornjak-configmap.yaml).
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved

To learn about the configurations each of the TLS and mTLS, expand below sections.

Expand All @@ -171,17 +171,16 @@ The TLS configuration requires the port number on which to open the connection a
```
server {
...
tls {
enabled = true
port = 20000 # container port for TLS connection
cert = "server/tls.crt" # TLS cert <TODO check paths>
https {
port = 10443 # container port for TLS connection
cert = "server/tls.crt" # TLS cert
key = "server/tls.key" # TLS key
}
...
}
```

In the above configuration, we create TLS connection at `localhost:20000` that uses certificate key pair at paths `server/tls.cert` and `server/tls.key` respectively. An example of the TLS configuration is found in the current directory at `tornjak-configmap.yaml`.
In the above configuration, we create TLS connection at `localhost:10443` that uses certificate key pair at paths `server/tls.cert` and `server/tls.key` respectively. An example of the TLS configuration is found in the current directory at `tornjak-configmap.yaml`.

A call to this port will require a CA that can verify the `cert/key` pair given. We can see that making a curl command to this port will create an error. First port-forward this connection to `localhost:20000`

Expand All @@ -194,18 +193,17 @@ The mTLS configuration, much like the TLS configuration, requires the port numbe
```
server {
...
mtls {
enabled = true
port = 30000 # container port for mTLS connection
https {
port = 10443 # container port for mTLS connection
cert = "server/tls.crt" # mTLS cert
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved
key = "server/tls.key" # mTLS key
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved
ca = "users/userCA.crt" # mTLS CA
ca = "users/userCA.crt" # mTLS CA [Removing this line creates a TLS connection]
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved
}
...
}
```

The above configuration enables mTLS at `localhost:30000` that uses certificate/key pair at paths `server/tls.crt` and `server/tls.key` respectively. It verifies caller certificate/key pairs with ca certificate at path `server/CA/rootCA.pem`. An example of the TLS configuration is found in the current directory at `tornjak-configmap.yaml`.
The above configuration enables mTLS at `localhost:10443` that uses certificate/key pair at paths `server/tls.crt` and `server/tls.key` respectively. It verifies caller certificate/key pairs with ca certificate at path `server/CA/rootCA.pem`. An example of the TLS configuration is found in the current directory at `tornjak-configmap.yaml`.

A call to this port requires a CA that can verify the `cert/key` pair given, as well as a cert/key pair signed by the CA with the `ca` certificate.

Expand All @@ -224,16 +222,16 @@ Now if we take a look at the logs, you can see the relevant connections have bee
kubectl logs -n spire spire-server-0 -c tornjak-backend
```

If we try to open the service to `localhost:20000`:
If we try to open the service to `localhost:10443`:

```
kubectl -n spire port-forward spire-server-0 20000:20000
kubectl -n spire port-forward spire-server-0 10443:10443
```

Then attempt curl command:

```
curl https://localhost:20000
curl https://localhost:10443
```

```
Expand All @@ -255,6 +253,19 @@ We will show how to make a proper curl command in the next section.

Now that we have opened TLS and mTLS connection, we may make calls. You will need the url to access the endpoints. If you have deployed locally on Minikube, as in the quickstart, you will need to port-forward the container to localhost.

### Redirect from HTTP Port

Notice that with this new configuration, a curl command to the HTTP port gives the URL of the HTTPS port:
maia-iyer marked this conversation as resolved.
Show resolved Hide resolved

```
curl http://<Tornjak_HTTPS_endpoint>
```

```
<a href="https://localhost:10443/">Found</a>.
```


### Make a TLS call

In order to make a TLS call we need only a CA certificate that can validate the certificate/key pair given to Tornjak in step 1. In our case, we can use the certificate within `CA-server`:
Expand Down
15 changes: 3 additions & 12 deletions examples/tls_mtls/tornjak-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,14 @@ data:

# configure HTTP connection to Tornjak server
http {
enabled = true
port = 10000 # opens at port 10000
}

tls {
enabled = true
port = 20000
https {
port = 10443
cert = "server/tls.crt"
key = "server/tls.key"
}

mtls {
enabled = true
port = 30000
cert = "server/tls.crt"
key = "server/tls.key"
ca = "users/rootCA.crt"
# client_ca = "users/rootCA.crt"
}
}

Expand Down
61 changes: 35 additions & 26 deletions tornjak-backend/api/agent/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,21 +582,22 @@ func (s *Server) GetRouter() http.Handler {
return rtr
}

func redirectHTTP(w http.ResponseWriter, r *http.Request) {
func (s *Server) 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()
target := "https://" + s.stripPort(r.Host) + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusFound)
}

func stripPort(hostport string) string {
func (s *Server) stripPort(hostport string) string {
host, _, err := net.SplitHostPort(hostport)
if err != nil {
return hostport
}
return net.JoinHostPort(host, "443")
addr := fmt.Sprintf("%d", s.TornjakConfig.Server.HTTPSConfig.ListenPort)
return net.JoinHostPort(host, addr)
}

// HandleRequests connects api links with respective functions
Expand All @@ -607,38 +608,33 @@ func (s *Server) HandleRequests() {
log.Fatal("Cannot Configure: ", err)
}

numPorts := 0 // TODO: replace with workerGroup for thread safety
// 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,
}
err = fmt.Errorf("HTTP Config error: no port configured")
errChannel <- err
return
}

if serverConfig.HTTPSConfig == nil {
httpHandler = s.GetRouter()
log.Print("WARNING: Please consider configuring HTTPS to ensure traffic is running on encrypted endpoint!")
}
// default router does not redirect
httpHandler := s.GetRouter()
numPorts := 1

numPorts = 1
go func() {
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 {
if serverConfig.HTTPSConfig == nil { // warn when HTTPS not configured
log.Print("WARNING: Please consider configuring HTTPS to ensure traffic is running on encrypted endpoint!")
} else {
numPorts += 1

go func() {
if serverConfig.HTTPSConfig.ListenPort == 0 {
serverConfig.HTTPSConfig.ListenPort = 443
// Fail because this is required field in this section
err = fmt.Errorf("HTTPS Config error: no port configured")
errChannel <- err
return
}
tls := serverConfig.HTTPSConfig.TLS
tls := serverConfig.HTTPSConfig
tlsConfig, err := tls.Parse()
if err != nil {
err = fmt.Errorf("failed parsing tls: %w", err)
Expand All @@ -661,8 +657,21 @@ func (s *Server) HandleRequests() {
errChannel <- err
}
}()

// set http handler to reroute
httpHandler = http.HandlerFunc(s.redirectHTTP)

}

go func() {
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
}
}()

// as errors come in, read them, and block
for i := 0; i < numPorts; i++ {
err := <-errChannel
Expand Down
44 changes: 24 additions & 20 deletions tornjak-backend/api/agent/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,23 @@ type HTTPConfig struct {

type HTTPSConfig struct {
ListenPort int `hcl:"port"`
TLS TLSConfig `hcl:"tls"`
Cert string `hcl:"cert"`
Key string `hcl:"key"`
ClientCA string `hcl:"client_ca"`
}

type TLSConfig struct {
Cert string `hcl:"cert"`
Key string `hcl:"key"`
ClientCA string `hcl:"client_ca"`
}
func (h HTTPSConfig) Parse() (*tls.Config, error) {
serverCertPath := h.Cert
serverKeyPath := h.Key
clientCAPath := h.ClientCA

func (t TLSConfig) Parse() (*tls.Config, error) {
serverCertPath := t.Cert
serverKeyPath := t.Key
clientCAPath := t.ClientCA
mtls := (clientCAPath != "")

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

// Create a CA certificate pool and add cert.pem to it
Expand All @@ -68,20 +66,26 @@ func (t TLSConfig) Parse() (*tls.Config, error) {
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)
if mtls {
// 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)
}
caCertPool.AppendCertsFromPEM(clientCA)

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

if mtls {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
//tlsConfig.BuildNameToCertificate()

return tlsConfig, nil
Expand Down
Loading