updated docs

pull/79/head
James Batt 4 years ago
parent 763675628e
commit 7d039ce89e

@ -3,9 +3,9 @@ name: Build and push docker images
on:
push:
branches:
- "master"
- 'master'
tags:
- "v*.*.*"
- 'v*.*.*'
jobs:
build:
@ -14,14 +14,14 @@ jobs:
- name: Prepare
id: prep
run: |
echo "building ref: $GITHUB_REF"
TAG=null
IMAGE=place1/wg-access-server
REF="${{ github.ref }}"
if [[ "$REF" == refs/heads/master ]]; then
if [[ "$GITHUB_REF" == refs/heads/master ]]; then
TAG="$IMAGE:master"
elif [[ "$REF" == refs/tags/* ]]; then
VERSION="${REF$refs/tags/}"
elif [[ "$GITHUB_REF" == refs/tags/* ]]; then
VERSION="${REF#refs/tags/}"
TAG="$IMAGE:$VERSION,$IMAGE:latest"
fi

@ -10,10 +10,16 @@
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"env": {},
"env": {
"WG_ADMIN_PASSWORD": "example",
"WG_WIREGUARD_PRIVATE_KEY": "4DRYOeSSeZyWRrLw357Pg9sv/RppMGwveTwz7sxM4mo=",
},
"args": [
"serve",
"--config=config.yaml"
"--config=config.yaml",
"--no-wireguard-enabled",
"--no-dns-enabled",
"--port=9001"
]
}
]

@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v0.3.0-rc1]
## [next (v0.3.0)]
### Added
@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- the wireguard private key is now required when the storage backend is persistent (i.e. not `memory://`)
- configuration flags, environment variables and file properties have been refactored for consistency
* all configuration file properties (excluding auth providers) can now be set via flags and environment variables
* all environment variables are prefixed with `WG_` to avoid collisions in hosted environments like Kubernetes
* all flags & environment variables are named consistently
* **breaking:** no functionality has been removed but you'll need to update any flags/envvars that you're using
### Deprecations

@ -39,8 +39,8 @@ FROM alpine:3.10
RUN apk add iptables
RUN apk add wireguard-tools
RUN apk add curl
ENV CONFIG="/config.yaml"
ENV STORAGE="sqlite3:///data/db.sqlite3"
ENV WG_CONFIG="/config.yaml"
ENV WG_STORAGE="sqlite3:///data/db.sqlite3"
COPY --from=server /code/wg-access-server /usr/local/bin/wg-access-server
COPY --from=website /code/build /website/build
CMD ["wg-access-server", "serve"]

@ -0,0 +1,8 @@
package cmd
// Command represents a wg-access-server
// subcommand module
type Command interface {
Name() string
Run()
}

@ -7,7 +7,7 @@ import (
"gopkg.in/alecthomas/kingpin.v2"
)
func RegisterCommand(app *kingpin.Application) *migratecmd {
func Register(app *kingpin.Application) *migratecmd {
cmd := &migratecmd{}
cli := app.Command(cmd.Name(), "Migrate your wg-access-server devices between storage backends. This tool is provided on a best effort bases.")
cli.Arg("source", "The source storage URI").Required().StringVar(&cmd.src)

@ -4,8 +4,11 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/types"
"github.com/place1/wg-access-server/internal/services"
"github.com/place1/wg-access-server/internal/storage"
"github.com/place1/wg-access-server/pkg/authnz"
@ -28,31 +31,31 @@ import (
"github.com/sirupsen/logrus"
)
func RegisterCommand(app *kingpin.Application) *servecmd {
func Register(app *kingpin.Application) *servecmd {
cmd := &servecmd{}
cli := app.Command(cmd.Name(), "Run the server")
cli.Flag("config", "Path to a config file").Envar("CONFIG").StringVar(&cmd.configPath)
cli.Flag("web-port", "The port that the web ui server will listen on").Envar("WEB_PORT").Default("8000").IntVar(&cmd.webPort)
cli.Flag("wireguard-port", "The port that the Wireguard server will listen on").Envar("WIREGUARD_PORT").Default("51820").IntVar(&cmd.wireguardPort)
cli.Flag("storage", "The storage backend connection string").Envar("STORAGE").Default("memory://").StringVar(&cmd.storage)
cli.Flag("wireguard-private-key", "Wireguard private key").Envar("WIREGUARD_PRIVATE_KEY").StringVar(&cmd.privateKey)
cli.Flag("disable-metadata", "Disable metadata collection (i.e. metrics)").Envar("DISABLE_METADATA").Default("false").BoolVar(&cmd.disableMetadata)
cli.Flag("admin-username", "Admin username (defaults to admin)").Envar("ADMIN_USERNAME").Default("admin").StringVar(&cmd.adminUsername)
cli.Flag("admin-password", "Admin password (provide plaintext, stored in-memory only)").Envar("ADMIN_PASSWORD").StringVar(&cmd.adminPassword)
cli.Flag("upstream-dns", "An upstream DNS server to proxy DNS traffic to").Envar("UPSTREAM_DNS").StringVar(&cmd.upstreamDNS)
cli.Flag("config", "Path to a wg-access-server config file").Envar("WG_CONFIG").FileVar(&cmd.ConfigFilePath)
cli.Flag("admin-username", "Admin username (defaults to admin)").Envar("WG_ADMIN_USERNAME").Default("admin").StringVar(&cmd.AppConfig.AdminUsername)
cli.Flag("admin-password", "Admin password (provide plaintext, stored in-memory only)").Envar("WG_ADMIN_PASSWORD").Required().StringVar(&cmd.AppConfig.AdminPassword)
cli.Flag("port", "The port that the web ui server will listen on").Envar("WG_PORT").Default("8000").IntVar(&cmd.AppConfig.Port)
cli.Flag("external-host", "The external origin of the server (e.g. https://mydomain.com)").Envar("WG_EXTERNAL_HOST").StringVar(&cmd.AppConfig.ExternalHost)
cli.Flag("storage", "The storage backend connection string").Envar("WG_STORAGE").Default("memory://").StringVar(&cmd.AppConfig.Storage)
cli.Flag("disable-metadata", "Disable metadata collection (i.e. metrics)").Envar("WG_DISABLE_METADATA").Default("false").BoolVar(&cmd.AppConfig.DisableMetadata)
cli.Flag("wireguard-enabled", "Enable or disable the embedded wireguard server (useful for development)").Envar("WG_WIREGUARD_ENABLED").Default("true").BoolVar(&cmd.AppConfig.WireGuard.Enabled)
cli.Flag("wireguard-interface", "Set the wireguard interface name").Default("wg0").Envar("WG_WIREGUARD_INTERFACE").StringVar(&cmd.AppConfig.WireGuard.Interface)
cli.Flag("wireguard-private-key", "Wireguard private key").Envar("WG_WIREGUARD_PRIVATE_KEY").StringVar(&cmd.AppConfig.WireGuard.PrivateKey)
cli.Flag("wireguard-port", "The port that the Wireguard server will listen on").Envar("WG_WIREGUARD_PORT").Default("51820").IntVar(&cmd.AppConfig.WireGuard.Port)
cli.Flag("vpn-cidr", "The network CIDR for the VPN").Envar("WG_VPN_CIDR").Default("10.44.0.0/24").StringVar(&cmd.AppConfig.VPN.CIDR)
cli.Flag("vpn-gateway-interface", "The gateway network interface (i.e. eth0)").Envar("WG_VPN_GATEWAY_INTERFACE").Default(detectDefaultInterface()).StringVar(&cmd.AppConfig.VPN.GatewayInterface)
cli.Flag("vpn-allowed-ips", "A list of networks that VPN clients will be allowed to connect to via the VPN").Envar("WG_VPN_ALLOWED_IPS").Default("0.0.0.0/1", "128.0.0.0/1").StringsVar(&cmd.AppConfig.VPN.AllowedIPs)
cli.Flag("dns-enabled", "Enable or disable the embedded dns proxy server (useful for development)").Envar("WG_DNS_ENABLED").Default("true").BoolVar(&cmd.AppConfig.DNS.Enabled)
cli.Flag("dns-upstream", "An upstream DNS server to proxy DNS traffic to. Defaults to resolveconf or 1.1.1.1").Envar("WG_DNS_UPSTREAM").Default(detectDNSUpstream()).StringsVar(&cmd.AppConfig.DNS.Upstream)
return cmd
}
type servecmd struct {
configPath string
webPort int
wireguardPort int
storage string
privateKey string
disableMetadata bool
adminUsername string
adminPassword string
upstreamDNS string
ConfigFilePath *os.File
AppConfig config.AppConfig
}
func (cmd *servecmd) Name() string {
@ -68,7 +71,7 @@ func (cmd *servecmd) Run() {
// WireGuard Server
wg := wgembed.NewNoOpInterface()
if conf.WireGuard.Enabled {
wgimpl, err := wgembed.New(conf.WireGuard.InterfaceName)
wgimpl, err := wgembed.New(conf.WireGuard.Interface)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create wireguard interface"))
}
@ -91,7 +94,7 @@ func (cmd *servecmd) Run() {
logrus.Infof("wireguard VPN network is %s", conf.VPN.CIDR)
if err := network.ConfigureForwarding(conf.WireGuard.InterfaceName, conf.VPN.GatewayInterface, conf.VPN.CIDR, conf.VPN.AllowedIPs); err != nil {
if err := network.ConfigureForwarding(conf.WireGuard.Interface, conf.VPN.GatewayInterface, conf.VPN.CIDR, conf.VPN.AllowedIPs); err != nil {
logrus.Fatal(err)
}
}
@ -160,13 +163,6 @@ func (cmd *servecmd) Run() {
// Static website
site.PathPrefix("/").Handler(services.WebsiteRouter())
// publicRouter.NotFoundHandler = authMiddleware.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// if authsession.Authenticated(r.Context()) {
// router.ServeHTTP(w, r)
// } else {
// http.Redirect(w, r, "/signin", http.StatusTemporaryRedirect)
// }
// }))
publicRouter := router
// Listen
@ -184,107 +180,92 @@ func (cmd *servecmd) Run() {
}
func (cmd *servecmd) ReadConfig() *config.AppConfig {
// here we're filling out the config struct
// with values from our flags/defaults.
config := &config.AppConfig{}
config.Port = cmd.webPort
config.WireGuard.InterfaceName = "wg0"
config.WireGuard.Port = cmd.wireguardPort
config.VPN.CIDR = "10.44.0.0/24"
config.DisableMetadata = cmd.disableMetadata
config.WireGuard.Enabled = true
config.WireGuard.PrivateKey = cmd.privateKey
config.Storage = cmd.storage
config.VPN.AllowedIPs = []string{"0.0.0.0/0"}
config.DNS.Enabled = true
config.AdminPassword = cmd.adminPassword
config.AdminSubject = cmd.adminUsername
if cmd.upstreamDNS != "" {
config.DNS.Upstream = []string{cmd.upstreamDNS}
}
if cmd.configPath != "" {
if b, err := ioutil.ReadFile(cmd.configPath); err == nil {
if err := yaml.Unmarshal(b, &config); err != nil {
if cmd.ConfigFilePath != nil {
defer cmd.ConfigFilePath.Close()
if b, err := ioutil.ReadAll(cmd.ConfigFilePath); err == nil {
if err := yaml.Unmarshal(b, &cmd.AppConfig); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to bind configuration file"))
}
}
}
if config.LogLevel != "" {
level, err := logrus.ParseLevel(config.LogLevel)
if err != nil {
logrus.Fatal(errors.Wrap(err, "invalid log level - should be one of fatal, error, warn, info, debug, trace"))
if cmd.AppConfig.LogLevel != "" {
if level, err := logrus.ParseLevel(cmd.AppConfig.LogLevel); err == nil {
logrus.SetLevel(level)
}
logrus.SetLevel(level)
}
if config.DisableMetadata {
if cmd.AppConfig.DisableMetadata {
logrus.Info("Metadata collection has been disabled. No metrics or device connectivity information will be recorded or shown")
}
if config.VPN.GatewayInterface == "" {
iface, err := defaultInterface()
if err != nil {
logrus.Warn(errors.Wrap(err, "failed to set default value for VPN.GatewayInterface"))
} else {
config.VPN.GatewayInterface = iface
}
// set a basic auth entry for the admin user
if cmd.AppConfig.Auth.Basic == nil {
cmd.AppConfig.Auth.Basic = &authconfig.BasicAuthConfig{}
}
pw, err := bcrypt.GenerateFromPassword([]byte(cmd.AppConfig.AdminPassword), bcrypt.DefaultCost)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to generate a bcrypt hash for the provided admin password"))
}
cmd.AppConfig.Auth.Basic.Users = append(cmd.AppConfig.Auth.Basic.Users, fmt.Sprintf("%s:%s", cmd.AppConfig.AdminUsername, string(pw)))
if config.WireGuard.PrivateKey == "" {
if !strings.HasPrefix(config.Storage, "memory://") {
// we'll generate a private key when using memory://
// storage only.
if cmd.AppConfig.WireGuard.PrivateKey == "" {
if !strings.HasPrefix(cmd.AppConfig.Storage, "memory://") {
logrus.Fatal(missingPrivateKey)
}
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to generate a server private key"))
}
config.WireGuard.PrivateKey = key.String()
}
if config.AdminPassword != "" && config.AdminSubject != "" {
if config.Auth.Basic == nil {
config.Auth.Basic = &authconfig.BasicAuthConfig{}
}
// htpasswd.AcceptBcrypt(config.AdminPassword)
pw, err := bcrypt.GenerateFromPassword([]byte(config.AdminPassword), bcrypt.DefaultCost)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to generate a bcrypt hash for the provided admin password"))
}
config.Auth.Basic.Users = append(config.Auth.Basic.Users, fmt.Sprintf("%s:%s", config.AdminSubject, string(pw)))
cmd.AppConfig.WireGuard.PrivateKey = key.String()
}
return config
return &cmd.AppConfig
}
func claimsMiddleware(conf *config.AppConfig) authsession.ClaimsMiddleware {
return func(user *authsession.Identity) error {
if user.Subject == conf.AdminSubject {
if user.Subject == conf.AdminUsername {
user.Claims.Add("admin", "true")
}
return nil
}
}
func defaultInterface() (string, error) {
func detectDNSUpstream() string {
upstream := []string{}
if r, err := resolvconf.Get(); err == nil {
upstream = resolvconf.GetNameservers(r.Content, types.IPv4)
}
if len(upstream) == 0 {
logrus.Warn("failed to get nameservers from /etc/resolv.conf defaulting to 1.1.1.1 for DNS instead")
upstream = []string{"1.1.1.1"}
}
return upstream[0]
}
func detectDefaultInterface() string {
links, err := netlink.LinkList()
if err != nil {
return "", errors.Wrap(err, "failed to list network interfaces")
logrus.Warn(errors.Wrap(err, "failed to list network interfaces"))
return ""
}
for _, link := range links {
routes, err := netlink.RouteList(link, 4)
if err != nil {
return "", errors.Wrapf(err, "failed to list routes for interface %s", link.Attrs().Name)
logrus.Warn(errors.Wrapf(err, "failed to list routes for interface %s", link.Attrs().Name))
return ""
}
for _, route := range routes {
if route.Dst == nil {
return link.Attrs().Name, nil
return link.Attrs().Name
}
}
}
return "", errors.New("could not determine the default network interface name")
logrus.Warn(errors.New("could not determine the default network interface name"))
return ""
}
var missingPrivateKey = `missing wireguard private key:

Binary file not shown.

@ -13,9 +13,9 @@ services:
- "wg-access-server-data:/data"
# - "./config.yaml:/config.yaml" # if you have a custom config file
environment:
- "ADMIN_USERNAME=admin"
- "ADMIN_PASSWORD=${ADMIN_PASSWORD:?\n\nplease set the ADMIN_PASSWORD environment variable:\n export ADMIN_PASSWORD=example\n}"
- "WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY:?\n\nplease set the WIREGUARD_PRIVATE_KEY environment variable:\n export WIREGUARD_PRIVATE_KEY=$(wg genkey)\n}"
- "WG_ADMIN_USERNAME=admin"
- "WG_ADMIN_PASSWORD=${WG_ADMIN_PASSWORD:?\n\nplease set the WG_ADMIN_PASSWORD environment variable:\n export WG_ADMIN_PASSWORD=example\n}"
- "WG_WIREGUARD_PRIVATE_KEY=${WG_WIREGUARD_PRIVATE_KEY:?\n\nplease set the WG_WIREGUARD_PRIVATE_KEY environment variable:\n export WG_WIREGUARD_PRIVATE_KEY=$(wg genkey)\n}"
ports:
- "8000:8000/tcp"
- "51820:51820/udp"

@ -0,0 +1,53 @@
# Configuration
You can configure wg-access-server using environment variables, cli flags or a config file
taking precedence over one another in that order.
The default configuration should work out of the box if you're just looking to try it out.
The only required configuration is an admin password and a wireguard private key. The admin
password can be anything you like. You can generate a wireguard private key by
[following the official docs](https://www.wireguard.com/quickstart/#key-generation).
TLDR:
```bash
wg genkey
```
The config file format is `yaml` and an example is provided [below](#the-config-file-configyaml).
Here's what you can configure:
| Environment Variable | CLI Flag | Config File Path | Required | Default (docker) | Description |
| -------------------------- | ------------------------- | ---------------------- | -------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WG_CONFIG` | `--config` | `loglevel` | | `info` | Global log level |
| `WG_ADMIN_USERNAME` | `--admin-username` | `adminUsername` | | `admin` | The admin account username |
| `WG_ADMIN_PASSWORD` | `--admin-password` | `adminPassword` | Yes | | The admin account password |
| `WG_PORT` | `--port` | `port` | | `8000` | The port the web ui will listen on (http) |
| `WG_EXTERNAL_HOST` | `--external-host` | `externalHost` | | | The external domain for the server (e.g. https://www.mydomain.com) |
| `WG_STORAGE` | `--storage` | `storage` | | `sqlite3:///data/db.sqlite3` | A storage backend connection string. See [storage docs](./3-storage.md) |
| `WG_DISABLE_METADATA` | `--disable-metadata` | `disableMetadata` | | `false` | Turn off collection of device metadata logging. Includes last handshake time and RX/TX bytes only. |
| `WG_WIREGUARD_ENABLED` | `--wireguard-enabled` | `wireguard.enabled` | | `true` | Enable/disable the wireguard server. Useful for development on non-linux machines. |
| `WG_WIREGUARD_INTERFACE` | `--wireguard-interface` | `wireguard.interface` | | `wg0` | The wireguard network interface name |
| `WG_WIREGUARD_PRIVATE_KEY` | `--wireguard-private-key` | `wireguard.privateKey` | Yes | | The wireguard private key. This value is required and must be stable. If this value changes all devices must re-register. |
| `WG_WIREGUARD_PORT` | `--wireguard-port` | `wireguard.port` | | `51820` | The wireguard server port (udp) |
| `WG_VPN_CIDR` | `--vpn-cidr` | `vpn.cidr` | | `10.44.0.0/24` | The VPN network range. VPN clients will be assigned IP addresses in this range. |
| `WG_VPN_GATEWAY_INTERFACE` | `--vpn-gateway-interface` | `vpn.gatewayInterface` | | _default gateway interface (e.g. eth0)_ | The VPN gateway interface. VPN client traffic will be forwarded to this interface. |
| `WG_VPN_ALLOWED_IPS` | `--vpn-allowed-ips` | `vpn.allowedIPs` | | `0.0.0.0/1, 128.0.0.0/1` | Allowed IPs that clients may route through this VPN. This will be set in the client's WireGuard connection file and routing is also enforced by the server using iptables. |
| `WG_DNS_ENABLED` | `--dns-enabled` | `dns.enabled` | | `true` | Enable/disable the embedded DNS proxy server. This is enabled by default and allows VPN clients to avoid DNS leaks by sending all DNS requests to wg-access-server itself. |
| `WG_DNS_UPSTREAM` | `--dns-upstream` | `dns.upstream` | | _resolveconf autodetection or 1.1.1.1_ | The upstream DNS server to proxy DNS requests to. By default the host machine's resolveconf configuration is used to find it's upstream DNS server, otherwise 1.1.1.1 (cloudflare) is used. |
## The Config File (config.yaml)
Here's an example config file to get started with.
```yaml
loglevel: info
storage: sqlite3:///data/db.sqlite3
wireguard:
privateKey: "<some-key>"
dns:
upstream:
- "8.8.8.8"
```

@ -1,13 +1,13 @@
# Storage Backends
# Storage
wg-access-server supports 4 storage backends suitable for different use-cases.
wg-access-server supports 4 storage backends.
| Backend | Persistent | Supports HA | Use Case |
|----------|------------|-------------|----------------------------------------|
| memory | ❌ | ❌ | Local development |
| sqlite3 | ✔️ | ❌ | Production - single instance deployments |
| postgres | ✔️ | ✔️ (soon) | Production - multi instance deployments |
| mysql | ✔️ | ❌ | Production - single instance deployments |
| Backend | Persistent | Supports HA | Use Case |
| -------- | ---------- | ----------- | ---------------------------------------- |
| memory | ❌ | ❌ | Local development |
| sqlite3 | ✔️ | ❌ | Production - single instance deployments |
| postgres | ✔️ | ✔️ (soon) | Production - multi instance deployments |
| mysql | ✔️ | ❌ | Production - single instance deployments |
## Backends

@ -0,0 +1,75 @@
# Authentication
Authentication is pluggable in wg-access-server. Community contributions are welcome
for supporting new authentication backends.
If you're just getting started you can skip over this section and rely on the default
admin account instead.
If your authentication system is not yet supported and you aren't quite ready to
contribute you could try using a project like [dex](https://github.com/dexidp/dex)
or SaaS provider like [Auth0](https://auth0.com/) which supports a wider variety of
authentication protocols. wg-access-server can happily be an OpenID Connect client
to a larger solution like this.
The following authentication backends are currently supported:
| Backend | Use Case | Notes |
| -------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| Basic Auth | Deployments with a static list of users. Simple and great for self-hosters and home use-cases | The wg-access-server admin account is powered by this backend |
| OpenID Connect | For delegating authentication to an existing identity solution | |
| Gitlab | For delegating authentication to gitlab. Supports self-hosted Gitlab. | |
## Configuration
Currently authentication providers are only configurable via the wg-access-server
config file (config.yaml).
Below is an annotated example config section that can be used as a starting point.
```yaml
# Configure zero or more authentication backends
auth:
# HTTP Basic Authentication
basic:
# Users is a list of htpasswd encoded username:password pairs
# supports BCrypt, Sha, Ssha, Md5
# You can create a user using "htpasswd -nB <username>"
users: []
oidc:
# A name for the backend (can be anything you want)
name: "My OIDC Backend"
# Should point to the OIDC Issuer (excluding /.well-known/openid-configuration)
issuer: "https://identity.example.com"
# Your OIDC client credentials which would be provided by your OIDC provider
clientID: "<client-id>"
clientSecret: "<client-secret>"
# List of scopes to request defaults to ["openid"]
scopes:
- openid
# The full redirect URL
# The path can be almost anything as long as it doesn't
# conflict with a path that the web UI uses.
# /callback is recommended.
redirectURL: "https://wg-access-server.example.com/callback"
# You can optionally restrict access to users with an email address
# that matches an allowed domain.
# If empty or omitted then all email domains will be allowed.
emailDomains:
- example.com
# This is an advanced feature that allows you to define
# OIDC claim mapping expressions.
# This feature is used to define wg-access-server admins
# based off a claim in your OIDC token
# See https://github.com/Knetic/govaluate/blob/9aa49832a739dcd78a5542ff189fb82c3e423116/MANUAL.md for how to write rules
userClaimsRules:
admin: "'WireguardAdmins' in group_membership"
gitlab:
name: "My Gitlab Backend"
baseURL: "https://mygitlab.example.com"
clientID: "<client-id>"
clientSecret: "<client-secret>"
redirectURL: "https:///wg-access-server.example.com/callback"
emailDomains:
- example.com
```

@ -1,136 +0,0 @@
# Configuration
## Environment Variables
| Variable | Description |
|-----------------------|-------------|
| CONFIG | Set the config file path |
| WIREGUARD_PRIVATE_KEY | Set the wireguard private key |
| STORAGE | Set the directory where device config will be persisted |
| ADMIN_USERNAME | Set the username (subject) for the admin account |
| ADMIN_PASSWORD | Set the admin account's password. The admin account will be a basic-auth user. Leave blank if your admin username authenticates via a configured authentication backend. |
| UPSTREAM_DNS | Set the upstream DNS server to proxy client DNS requests to. If empty, resolv.conf will be respected. |
| LOG_LEVEL | Set the server's log level (debug, **info**, error, critical) |
| DISABLE_METADATA | If true, the server will not record device level metadata such as the last handshake time, tx/rx data size |
## CLI Flags
All environment variables can be configured via a
CLI flag as well.
For example you can configure `STORAGE` by passing `--storage="<value>"`.
## Config File (config.yaml)
Here's an annotated config file example:
```yaml
# The application's log level.
# Can be debug, info, error
# Optional, defaults to info
loglevel: info
# Disable device metadata storage.
# Device metadata includes the last handshake time,
# total sent/received bytes count, their endpoint IP.
# This metadata is captured from wireguard itself.
# Disabling this flag will not stop wireguard from capturing
# this data.
# See stored data here: https://github.com/Place1/wg-access-server/blob/master/internal/storage/contracts.go#L14
# Optional, defaults to false.
disableMetadata: false
# The port that the web ui server (http) will listen on.
# Optional, defaults to 8000
port: 8000
# Directory that VPN devices (WireGuard peers)
# What type of storage do you want? inmemory (default), file:///some/directory, or postgres, mysql, sqlite3
storage: "memory://"
wireguard:
# The network interface name for wireguard
# Optional, defaults to wg0
interfaceName: wg0
# The WireGuard PrivateKey
# You can generate this value using "$ wg genkey"
# If this value is empty then the server will use an in-memory
# generated key
privateKey: ""
# ExternalAddress is the address (without port) that clients use to connect to the wireguard interface
# By default, this will be empty and the web ui
# will use the current page's origin i.e. window.location.origin
# Optional
externalHost: ""
# The WireGuard ListenPort
# Optional, defaults to 51820
port: 51820
vpn:
# CIDR configures a network address space
# that client (WireGuard peers) will be allocated
# an IP address from.
# Optional
cidr: "10.44.0.0/24"
# GatewayInterface will be used in iptable forwarding
# rules that send VPN traffic from clients to this interface
# Most use-cases will want this interface to have access
# to the outside internet
# If not configured then the server will select the default
# network interface e.g. eth0
# Optional
gatewayInterface: ""
# The "AllowedIPs" for VPN clients.
# This value will be included in client config
# files and in server-side iptable rules
# to enforce network access.
# Optional
allowedIPs:
- "0.0.0.0/0"
dns:
# Enable a DNS proxy for VPN clients.
# Optional, Defaults to true
enabled: true
# upstream DNS servers.
# that the server-side DNS proxy will forward requests to.
# By default /etc/resolv.conf will be used to find upstream
# DNS servers.
# Optional
upstream:
- "1.1.1.1"
# Auth configures optional authentication backends
# to controll access to the web ui.
# Devices will be managed on a per-user basis if any
# auth backends are configured.
# If no authentication backends are configured then
# the server will not require any authentication.
# It's recommended to make use of basic authentication
# or use an upstream HTTP proxy that enforces authentication
# Optional
auth:
# HTTP Basic Authentication
basic:
# Users is a list of htpasswd encoded username:password pairs
# supports BCrypt, Sha, Ssha, Md5
# You can create a user using "htpasswd -nB <username>"
users: []
oidc:
name: "" # anything you want
issuer: "" # Should point to the oidc url without .well-known
clientID: ""
clientSecret: ""
scopes: null # list of scopes, defaults to ["openid"]
redirectURL: "" # full url you want the oidc to redirect to, example: https://vpn-admin.example.com/finish-signin
# See https://github.com/Knetic/govaluate/blob/9aa49832a739dcd78a5542ff189fb82c3e423116/MANUAL.md for how to write rules
userClaimsRules:
admin: "'WireguardAdmins' in group_membership"
# Optionally restrict login to users with an allowed email domain
# if empty or omitted, any email domain will be allowed.
emailDomains:
- example.com
gitlab:
name: ""
baseURL: ""
clientID: ""
clientSecret: ""
redirectURL: ""
# Optionally restrict login to users with an allowed email domain
# if empty or omitted, any email domain will be allowed.
emailDomains:
- example.com
```

@ -3,7 +3,7 @@
You can run wg-access-server using the following example
docker Docker Compose file.
Checkout the [configuration docs](../configuration.md) to learn how wg-access-server
Checkout the [configuration docs](../2-configuration.md) to learn how wg-access-server
can be configured.
```yaml

@ -12,7 +12,6 @@ require (
github.com/docker/libnetwork v0.8.0-dev.2.0.20200217033114-6659f7f4d8c1
github.com/golang/protobuf v1.4.2
github.com/google/uuid v1.1.1
github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.7.4
github.com/gorilla/sessions v1.2.0
github.com/gorilla/websocket v1.4.2 // indirect

@ -72,8 +72,6 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
@ -140,10 +138,6 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/place1/wg-embed v0.2.0 h1:JzdDeDDWqzY7w6/JJ/fW+q/Qc7AzI/i1GqyeFNf/HT0=
github.com/place1/wg-embed v0.2.0/go.mod h1:i09dm8AEkurC4oATFxjvyH0+e1pdmtZoNk2FfPupROI=
github.com/place1/wg-embed v0.3.0 h1:n7piTgnp3MgyceBEAD/A7ZiLA4kH8qkqCTVPLBHj6SE=
github.com/place1/wg-embed v0.3.0/go.mod h1:i09dm8AEkurC4oATFxjvyH0+e1pdmtZoNk2FfPupROI=
github.com/place1/wg-embed v0.4.0 h1:rToHj4+TuI2ruv2mz3Y16vvisv280BuzdojsGGNQ/pM=
github.com/place1/wg-embed v0.4.0/go.mod h1:i09dm8AEkurC4oATFxjvyH0+e1pdmtZoNk2FfPupROI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

@ -5,24 +5,41 @@ import (
)
type AppConfig struct {
LogLevel string `yaml:"loglevel"`
DisableMetadata bool `yaml:"disableMetadata"`
AdminSubject string `yaml:"adminSubject"`
AdminPassword string `yaml:"adminPassword"`
// Set the log level.
// Defaults to "info" (fatal, error, warn, info, debug, trace)
LogLevel string `yaml:"loglevel"`
// Set the superadmin username
// Defaults to "admin"
AdminUsername string `yaml:"adminUsername"`
// Set the superadmin password (required)
AdminPassword string `yaml:"adminPassword"`
// Port sets the port that the web UI will listen on.
// Defaults to 8000
Port int `yaml:"port"`
// ExternalAddress is the address that clients
// use to connect to the wireguard interface
// By default, this will be empty and the web ui
// will use the current page's origin.
ExternalHost string `yaml:"externalHost"`
// The storage backend where device configuration will
// be persisted.
// Supports memory:// file:// postgres:// mysql:// sqlite3://
// Defaults to memory://
Storage string `yaml:"storage"`
Storage string `yaml:"storage"`
// DisableMetadata allows you to turn off collection of device
// metadata including last handshake time & rx/tx bytes
DisableMetadata bool `yaml:"disableMetadata"`
// Configure WireGuard related settings
WireGuard struct {
// Set this to false to disable the embedded wireguard
// server. This is useful for development environments
// on mac and windows where we don't currently support
// the OS's network stack.
Enabled bool `yaml:"enabled"`
// The network interface name of the WireGuard
// network device.
// Defaults to wg0
InterfaceName string `yaml:"interfaceName"`
Interface string `yaml:"interface"`
// The WireGuard PrivateKey
// If this value is lost then any existing
// clients (WireGuard peers) will no longer
@ -31,15 +48,11 @@ type AppConfig struct {
// their connection configuration or setup
// their VPN again using the web ui (easier for most people)
PrivateKey string `yaml:"privateKey"`
// ExternalAddress is the address that clients
// use to connect to the wireguard interface
// By default, this will be empty and the web ui
// will use the current page's origin.
ExternalHost *string `yaml:"externalHost"`
// The WireGuard ListenPort
// Defaults to 51820
Port int `yaml:"port"`
} `yaml:"wireguard"`
// Configure VPN related settings (networking)
VPN struct {
// CIDR configures a network address space
// that client (WireGuard peers) will be allocated
@ -56,8 +69,9 @@ type AppConfig struct {
// files and in server-side iptable rules
// to enforce network access.
// defaults to ["0.0.0.0/1", "128.0.0.0/1"]
AllowedIPs []string `yaml:"AllowedIPs"`
AllowedIPs []string `yaml:"allowedIPs"`
}
// Configure the embeded DNS server
DNS struct {
// Enabled allows you to turn on/off
// the VPN DNS proxy feature.

@ -7,8 +7,6 @@ import (
"strings"
"time"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/types"
"github.com/miekg/dns"
"github.com/patrickmn/go-cache"
"github.com/pkg/errors"
@ -27,23 +25,12 @@ type DNSServer struct {
}
func New(opts DNSServerOpts) (*DNSServer, error) {
upstream := opts.Upstream
if len(upstream) == 0 {
if r, err := resolvconf.Get(); err == nil {
upstream = resolvconf.GetNameservers(r.Content, types.IPv4)
}
}
if len(upstream) == 0 {
logrus.Warn("failed to get nameservers from /etc/resolv.conf defaulting to 1.1.1.1 for DNS instead")
upstream = append(upstream, "1.1.1.1")
if len(opts.Upstream) == 0 {
return nil, errors.New("at least 1 upstream dns server is required for the dns proxy server to function")
}
addr := "0.0.0.0:53"
logrus.Infof("starting dns server on %s with upstreams: %s", addr, strings.Join(upstream, ", "))
logrus.Infof("starting dns server on %s with upstreams: %s", addr, strings.Join(opts.Upstream, ", "))
dnsServer := &DNSServer{
server: &dns.Server{
@ -55,7 +42,7 @@ func New(opts DNSServerOpts) (*DNSServer, error) {
Timeout: 5 * time.Second,
},
cache: cache.New(10*time.Minute, 10*time.Minute),
upstream: upstream,
upstream: opts.Upstream,
}
dnsServer.server.Handler = dnsServer

@ -33,7 +33,7 @@ func (s *ServerService) Info(ctx context.Context, req *proto.InfoReq) (*proto.In
}
return &proto.InfoRes{
Host: stringValue(s.Config.WireGuard.ExternalHost),
Host: stringValue(&s.Config.ExternalHost),
PublicKey: publicKey,
Port: int32(s.Config.WireGuard.Port),
HostVpnIp: network.ServerVPNIP(s.Config.VPN.CIDR).IP.String(),

@ -7,6 +7,7 @@ import (
"runtime"
"github.com/pkg/errors"
"github.com/place1/wg-access-server/cmd"
"github.com/place1/wg-access-server/cmd/migrate"
"github.com/place1/wg-access-server/cmd/serve"
"github.com/sirupsen/logrus"
@ -15,20 +16,24 @@ import (
var (
app = kingpin.New("wg-access-server", "An all-in-one WireGuard Access Server & VPN solution")
logLevel = app.Flag("log-level", "Log level (debug, info, error)").Envar("LOG_LEVEL").Default("info").String()
servecmd = serve.RegisterCommand(app)
migratecmd = migrate.RegisterCommand(app)
logLevel = app.Flag("log-level", "Log level: trace, debug, info, error, fatal").Envar("LOG_LEVEL").Default("info").String()
)
func main() {
cmd := kingpin.MustParse(app.Parse(os.Args[1:]))
// all the subcommands for wg-access-server
commands := []cmd.Command{
serve.Register(app),
migrate.Register(app),
}
// parse CLI arguments
clicmd := kingpin.MustParse(app.Parse(os.Args[1:]))
// set global log level
level, err := logrus.ParseLevel(*logLevel)
if err != nil {
logrus.Fatal(errors.Wrap(err, "invalid log level - should be one of fatal, error, warn, info, debug, trace"))
}
logrus.SetLevel(level)
logrus.SetReportCaller(true)
logrus.SetFormatter(&logrus.TextFormatter{
@ -37,12 +42,10 @@ func main() {
},
})
switch cmd {
case servecmd.Name():
servecmd.Run()
case migratecmd.Name():
migratecmd.Run()
default:
logrus.Fatal(fmt.Errorf("unknown command: %s", cmd))
for _, c := range commands {
if clicmd == c.Name() {
c.Run()
return
}
}
}

Loading…
Cancel
Save