Skip to content

Commit

Permalink
Fix HTTPS config and redirect #268 (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrsabath committed Jul 13, 2023
2 parents a9affa2 + 3e72200 commit 33a7c9f
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 104 deletions.
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 10000
}
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 are opened by the server simultaneously: HTTP and HTTPS. HTTP is always operational. The optional HTTPS connection is recommended for production use case. When HTTPS is configured, the HTTP connection will redirect to the HTTPS (port and service).

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 to enable TLS connection. To enable the mutual TLS (mTLS), you must additionally include the `client_ca` field, so the verification can be done bi-directionally.

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

Expand Down
43 changes: 27 additions & 16 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 opens two connections simultaneously: HTTP and HTTPS. HTTP is always enabled, and when HTTPS is enabled, the HTTP port reroutes to the HTTPS port. A configuration that enables TLS is provided in [the configmap](./tornjak-configmap.yaml).

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
cert = "server/tls.crt" # mTLS cert
key = "server/tls.key" # mTLS key
ca = "users/userCA.crt" # mTLS CA
https {
port = 10443 # container port for mTLS connection
cert = "server/tls.crt" # TLS cert
key = "server/tls.key" # TLS key
ca = "users/userCA.crt" # user CA for mTLS [Removing this line creates a TLS connection]
}
...
}
```

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 returns a permanent redirect to the URL of the HTTPS port:

```
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
102 changes: 59 additions & 43 deletions tornjak-backend/api/agent/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"path/filepath"
"strings"
"time"
"crypto/tls"

"github.com/cenkalti/backoff/v4"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -582,21 +583,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,22 +609,67 @@ 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()
// default router does not redirect
httpHandler := s.GetRouter()
numPorts := 1

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

httpHandler = http.HandlerFunc(s.redirectHTTP)
canStartHTTPS := true
httpsConfig := serverConfig.HTTPSConfig
var tlsConfig *tls.Config


if serverConfig.HTTPSConfig.ListenPort == 0 {
// Fail because this is required field in this section
err = fmt.Errorf("HTTPS Config error: no port configured. Starting insecure HTTP connection at %d...", serverConfig.HTTPConfig.ListenPort)
errChannel <- err
httpHandler = s.GetRouter()
canStartHTTPS = false
} else {
tlsConfig, err = httpsConfig.Parse()
if err != nil {
err = fmt.Errorf("failed parsing HTTPS config: %w. Starting insecure HTTP connection at %d...", err, serverConfig.HTTPConfig.ListenPort)
errChannel <- err
httpHandler = s.GetRouter()
canStartHTTPS = false
}
}

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

fmt.Printf("Starting https on %s...\n", addr)
err = server.ListenAndServeTLS(httpsConfig.Cert, httpsConfig.Key)
if err != nil {
err = fmt.Errorf("server error serving on https: %w", err)
errChannel <- err
}
}()
}
}

numPorts = 1
go func() {
addr := fmt.Sprintf(":%d", serverConfig.HTTPConfig.ListenPort)
fmt.Printf("Starting to listen on %s...\n", addr)
Expand All @@ -632,37 +679,6 @@ func (s *Server) HandleRequests() {
}
}()

if serverConfig.HTTPSConfig != nil {
numPorts += 1
go func() {
if serverConfig.HTTPSConfig.ListenPort == 0 {
serverConfig.HTTPSConfig.ListenPort = 443
}
tls := serverConfig.HTTPSConfig.TLS
tlsConfig, err := tls.Parse()
if err != nil {
err = fmt.Errorf("failed parsing tls: %w", err)
errChannel <- err
return
}

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

fmt.Printf("Starting https on %s...\n", addr)
err = server.ListenAndServeTLS(tls.Cert, tls.Key)
if err != nil {
err = fmt.Errorf("server error serving on https: %w", err)
errChannel <- err
}
}()
}

// as errors come in, read them, and block
for i := 0; i < numPorts; i++ {
err := <-errChannel
Expand Down
Loading

0 comments on commit 33a7c9f

Please sign in to comment.