Skip to content

Commit

Permalink
Merge pull request #5 from jamescun/token-auth
Browse files Browse the repository at this point in the history
Add Token Authentication through HTTP Authorization header
  • Loading branch information
jamescun authored Feb 21, 2020
2 parents 0f1f184 + 8b7a1f8 commit bc85809
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 4 deletions.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,18 @@ Options:
--tls-key TLS private key
--tks-cert TLS certificate file
--tls-client-ca enable mutual TLS authentication (mTLS) of the client
--token opaque value provided by the client to authenticate
requests. may be specified multiple times.

Environment Variables:
WGAPI_TOKENS comma seperated list of authentication tokens, equivalent to
calling --token one or more times.

Warnings:
WG-API can perform sensitive network operations, as such it should not be
publicly exposed. It should be bound to the local interface only, or
failing that, be behind an authenticating proxy or have mTLS enabled.
Additionally authentication tokens should be configured.
```

The only required argument is `--device`, which tells WG-API which WireGuard device to control. To control multiple WireGuard devices, launch multiple instances of WG-API.
Expand All @@ -76,7 +83,26 @@ By default, this launches WG-API on `localhost:8080` which may conflict with the
$ wg-api --device=<my device> --listen=localhost:1234
```

**NOTE:** `--listen` will not prevent you from binding the server to a public interface. Care should be taken to prevent public access to the WG-API server; such as binding it only to a local interface, placing an authenticating reverse proxy in-front of it or using mTLS (detailed below).
**NOTE:** `--listen` will not prevent you from binding the server to a public interface. Care should be taken to prevent public access to the WG-API server; such as binding it only to a local interface, enabling auth tokens, placing an authenticating reverse proxy in-front of it or using mTLS (detailed below).

Authentication tokens can be provided either on the command line or via an environment variable. `--token` may be specified multiple times, or a comma-seperated list may be provided with the `WGAPI_TOKENS` environment variable. Environment variables are preferred as the token may be visible from process lists when using the command line `--token`.

```sh
$ WGAPI_TOKENS=<random string> wg-api --device=<my device>
```

Then provided as part of the HTTP exchange in the HTTP `Authorization` header as the `Token` scheme.

```sh
$ curl http://localhost:8080 -H "Authorization: Token <random string>" ...
```

```
POST / HTTP/1.1
Host: localhost:8080
Authorization: Token <random string>
Content-Type: application/json
```

WG-API can optional listen using TLS and HTTP/2. To enable TLS, you will also need a TLS Certificate and matching private key.

Expand All @@ -99,6 +125,8 @@ All calls are made using the POST method, and require the `Content-Type` header

The structures expected by the server can be found in [client/client.go](client/client.go).

Authentication may optionally be configured. This is supplied via the `Authorization` header as the `Token` scheme. See [Configuring WG-API](##Configuring-WG-API) for an example.


### GetDeviceInfo

Expand Down
40 changes: 38 additions & 2 deletions cmd/wg-api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"

wireguardapi "github.com/jamescun/wg-api"
"github.com/jamescun/wg-api/server"
"github.com/jamescun/wg-api/server/jsonrpc"

flag "github.com/spf13/pflag"
"golang.zx2c4.com/wireguard/wgctrl"
)

Expand All @@ -33,11 +34,18 @@ Options:
--tls-key TLS private key
--tks-cert TLS certificate file
--tls-client-ca enable mutual TLS authentication (mTLS) of the client
--token opaque value provided by the client to authenticate
requests. may be specified multiple times.
Environment Variables:
WGAPI_TOKENS comma seperated list of authentication tokens, equivalent to
calling --token one or more times.
Warnings:
WG-API can perform sensitive network operations, as such it should not be
publicly exposed. It should be bound to the local interface only, or
failing that, be behind an authenticating proxy or have mTLS enabled.
Additionally authentication tokens should be configured.
`

var (
Expand All @@ -52,6 +60,7 @@ var (
tlsKey = flag.String("tls-key", "", "")
tlsCert = flag.String("tls-cert", "", "")
tlsClientCA = flag.String("tls-client-ca", "", "")
authTokens = flag.StringArray("token", nil, "")
)

func main() {
Expand Down Expand Up @@ -99,9 +108,21 @@ func main() {
exitError("could not create WG-API server: %s", err)
}

handler := jsonrpc.HTTP(server.Logger(svc))

if tokens := envArray("WGAPI_TOKENS"); len(tokens) > 0 {
*authTokens = append(*authTokens, tokens...)
}

if len(*authTokens) > 0 {
handler = server.AuthTokens(*authTokens...)(handler)
}

handler = server.PreventReferer(handler)

s := &http.Server{
Addr: *listenAddr,
Handler: server.PreventReferer(jsonrpc.HTTP(server.Logger(svc))),
Handler: handler,
}

if *enableTLS {
Expand Down Expand Up @@ -157,3 +178,18 @@ func loadCertificatePool(filename string) (*x509.CertPool, error) {

return pool, nil
}

func envArray(name string) []string {
env := os.Getenv(name)
if env == "" {
return nil
}

vv := strings.Split(env, ",")

for i, v := range vv {
vv[i] = strings.TrimSpace(v)
}

return vv
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/jamescun/wg-api

go 1.13

require golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200205215550-e35592f146e4
require (
github.com/spf13/pflag v1.0.5
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200205215550-e35592f146e4
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkf
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w=
Expand Down
29 changes: 29 additions & 0 deletions server/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package server
import (
"log"
"net/http"
"strings"
"time"

"github.com/jamescun/wg-api/server/jsonrpc"
Expand Down Expand Up @@ -42,3 +43,31 @@ func Logger(next jsonrpc.Handler) jsonrpc.Handler {
log.Printf("info: request: method=%q remote_addr=%s duration=%s\n", r.Method, r.RemoteAddr(), t2.Sub(t1))
})
}

// AuthTokens only allows a request to continue if one of the pre-configured
// tokens is provided by the client in the Authorization header, otherwise
// a HTTP 403 Forbidden is returned and the request terminated.
func AuthTokens(tokens ...string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := strings.TrimSpace(strings.TrimPrefix(r.Header.Get("Authorization"), "Token "))

if !stringInSlice(token, tokens) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}

next.ServeHTTP(w, r)
})
}
}

func stringInSlice(s string, vv []string) bool {
for _, v := range vv {
if v == s {
return true
}
}

return false
}

0 comments on commit bc85809

Please sign in to comment.