Skip to content

Commit

Permalink
Merge pull request #5 from carlpett/serve-over-tls
Browse files Browse the repository at this point in the history
Add flags, serve over TLS by default
  • Loading branch information
carlpett authored Sep 3, 2019
2 parents 605c6d4 + 2eaa1b0 commit 2c58cff
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 14 deletions.
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,21 @@ It validates a JWT token passed in the `Authorization` header against a configur
The configuration format currently only supports a single elliptic curve public key for signature validation, and does not have a facility for rotating keys without restart. Basic support in the configuration format for supporting multiple active keys, and of different types, at once is in place but currently not used.

# Configuration
The service takes a configuration file in YAML format. For example:
A number of flags affect how the service is started:

Flag | Description | Default
---------|-------------|--------------------
--help | Show help | -
--config | Path to configuration file | config.yaml
--log-level| Log level | info
--tls-key | Path to TLS key | `<required>`
--tls-cert | Path to TLS cert | `<required>`
--addr | Address/port to serve traffic in TLS mode | :8443
--insecure | Serve traffic unencrypted over http | false
--insecure-addr | Address/port to serve traffic in insecure mode | :8080

## Configuration file
The service takes a configuration file in YAML format, by default `config.yaml`. For example:

```yaml
validationKeys:
Expand All @@ -27,7 +41,7 @@ With this configuration, a JWT will be validated against the given public key, a

Claims can either be statically set, as in the above example, or passed via query string parameters. The `claimsSource` configuration parameter controls which mode the server operates in, and can be either `static` or `queryString`. Further examples of the two modes are given below.

## Static
### Static

Multiple alternative allowed sets of claims can be configured, for example:

Expand Down Expand Up @@ -68,7 +82,7 @@ claims:

Here, the token claims must **both** have the groups as before, **and** a `location` of `hq`.

## Query string
### Query string
In query string mode, the allowed claims are passed via query string parameters to the /validate endpoint. For example, with `/validate?claims_group=developers&claims_group=administrators&claims_location=hq`, the token claims must **both** have a `group` claim of **either** `developers` or `administrators`, **and** a `location` claim of `hq`.

Each claim must be prefixed with `claims_`. Giving the same claim multiple time results in any value being accepted.
Expand All @@ -81,13 +95,13 @@ If no claims are passed in this mode, the request will be denied.
To use with the NGINX Ingress Controller, first create a deployment and a service for this endpoint. See the [kubernetes/](kubernetes/) directory for example manifests. Then on the ingress object you wish to authenticate, add this annotation for a server in static claims source mode:

```yaml
nginx.ingress.kubernetes.io/auth-url: http://nginx-subrequest-auth-jwt.default.svc.cluster.local:8080/validate
nginx.ingress.kubernetes.io/auth-url: https://nginx-subrequest-auth-jwt.default.svc.cluster.local:8443/validate
```

Or, in query string mode:

```yaml
nginx.ingress.kubernetes.io/auth-url: http://nginx-subrequest-auth-jwt.default.svc.cluster.local:8080/validate?claims_group=developers
nginx.ingress.kubernetes.io/auth-url: https://nginx-subrequest-auth-jwt.default.svc.cluster.local:8443/validate?claims_group=developers
```

Change the url to match the name of the service and namespace you chose when deploying. All requests will now have their JWTs validated before getting passed to the upstream service.
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ module github.com/carlpett/nginx-subrequest-auth-jwt
go 1.12

require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/prometheus/client_golang v1.1.0
go.uber.org/atomic v1.4.0 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.10.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/yaml.v2 v2.2.1
)
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -75,6 +79,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
2 changes: 2 additions & 0 deletions kubernetes/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ spec:
containers:
- name: nginx-subrequest-auth-jwt
image: carlpett/nginx-subrequest-auth-jwt:latest
args:
- --insecure
imagePullPolicy: IfNotPresent
ports:
- name: http
Expand Down
41 changes: 32 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"

Expand All @@ -15,6 +14,7 @@ import (
"github.com/dgrijalva/jwt-go/request"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -54,8 +54,8 @@ type server struct {
StaticClaims []map[string][]string
}

func newServer(logger logger.Logger) (*server, error) {
cfg, err := ioutil.ReadFile("config.yaml")
func newServer(logger logger.Logger, configFilePath string) (*server, error) {
cfg, err := ioutil.ReadFile(configFilePath)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -99,22 +99,45 @@ type config struct {
StaticClaims []map[string][]string `yaml:"claims"`
}

var (
configFilePath = kingpin.Flag("config", "Path to configuration file").Default("config.yaml").ExistingFile()
logLevel = kingpin.Flag("log-level", "Log level").Default("info").Enum("debug", "info", "warn", "error", "fatal")

tlsKey = kingpin.Flag("tls-key", "Path to TLS key").ExistingFile()
tlsCert = kingpin.Flag("tls-cert", "Path to TLS cert").ExistingFile()
bindAddr = kingpin.Flag("addr", "Address/port to serve traffic in TLS mode").Default(":8443").String()

insecure = kingpin.Flag("insecure", "Serve traffic unencrypted over http (default false)").Bool()
insecureBindAddr = kingpin.Flag("insecure-addr", "Address/port to serve traffic in insecure mode").Default(":8080").String()
)

func main() {
logger := logger.NewLogger(os.Getenv("LOG_LEVEL"))
kingpin.HelpFlag.Short('h')
kingpin.Parse()

server, err := newServer(logger)
logger := logger.NewLogger(*logLevel)

server, err := newServer(logger, *configFilePath)
if err != nil {
logger.Fatalw("Couldn't initialize server", "err", err)
}

logger.Infow("Starting server on :8080")

http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/validate", server.validate)
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "OK") })
err = http.ListenAndServe(":8080", nil)

if *insecure {
logger.Infow("Starting server", "addr", *insecureBindAddr)
err = http.ListenAndServe(*insecureBindAddr, nil)
} else {
logger.Infow("Starting server", "addr", *bindAddr)
if *tlsKey == "" || *tlsCert == "" {
logger.Fatalw("tls-key and tls-cert are required in TLS mode")
}
err = http.ListenAndServeTLS(*bindAddr, *tlsCert, *tlsKey, nil)
}
if err != nil {
logger.Fatalw("Error serving http", "err", err)
logger.Fatalw("Error running server", "err", err)
}
}

Expand Down

0 comments on commit 2c58cff

Please sign in to comment.