lots of updates to config - also added a data migration tool for moving between storage backends

pull/79/head
James Batt 4 years ago
parent 03855d0e41
commit c5dc5da9a0

@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"env": {},
"args": [
"serve",
"--config=config.yaml"
]
}
]
}

@ -12,13 +12,14 @@ RUN npm run codegen
RUN npm run build
### Build stage for the website backend server
FROM golang:1.13.8 as server
RUN apt-get update
RUN apt-get install -y protobuf-compiler libprotobuf-dev
FROM golang:1.13.8-alpine as server
RUN apk add gcc musl-dev
RUN apk add protobuf
RUN apk add protobuf-dev
WORKDIR /code
ENV GOOS=linux
ENV GARCH=amd64
ENV CGO_ENABLED=0
ENV CGO_ENABLED=1
ENV GO111MODULE=on
RUN go get github.com/golang/protobuf/protoc-gen-go@v1.3.5
COPY ./go.mod ./
@ -28,9 +29,10 @@ COPY ./proto/ ./proto/
COPY ./codegen.sh ./
RUN ./codegen.sh
COPY ./main.go ./main.go
COPY ./internal/ ./internal/
COPY ./cmd/ ./cmd/
COPY ./pkg/ ./pkg/
RUN go build -o server
COPY ./internal/ ./internal/
RUN go build -o wg-access-server
### Server
FROM alpine:3.10
@ -38,7 +40,7 @@ RUN apk add iptables
RUN apk add wireguard-tools
RUN apk add curl
ENV CONFIG="/config.yaml"
ENV STORAGE="file:///data"
COPY --from=server /code/server /server
ENV 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 /server
CMD ["wg-access-server", "serve"]

@ -27,13 +27,17 @@ Quick Links:
Here's a quick command to run the server to try it out.
```bash
export ADMIN_PASSWORD="example"
export WIREGUARD_PRIVATE_KEY="$(wg genkey)"
docker run \
-it \
--rm \
--cap-add NET_ADMIN \
--device /dev/net/tun:/dev/net/tun \
-v wg-access-server-data:/data \
-e "WIREGUARD_PRIVATE_KEY=$(wg genkey)" \
-e "ADMIN_PASSWORD=$ADMIN_PASSWORD" \
-e "WIREGUARD_PRIVATE_KEY=$WIREGUARD_PRIVATE_KEY" \
-p 8000:8000/tcp \
-p 51820:51820/udp \
place1/wg-access-server
@ -62,9 +66,12 @@ helm delete my-release
## Running with Docker-Compose
You modify the docker-compose.yml file for you need then run this following command.
Download the the docker-compose.yml file from the repo and run the following command.
```bash
export ADMIN_PASSWORD="example"
export WIREGUARD_PRIVATE_KEY="$(wg genkey)"
docker-compose up
```

@ -0,0 +1,58 @@
package migrate
import (
"github.com/pkg/errors"
"github.com/place1/wg-access-server/internal/storage"
"github.com/sirupsen/logrus"
"gopkg.in/alecthomas/kingpin.v2"
)
func RegisterCommand(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)
cli.Arg("destination", "The destination storage URI").Required().StringVar(&cmd.dest)
return cmd
}
type migratecmd struct {
src string
dest string
}
func (cmd *migratecmd) Name() string {
return "migrate"
}
func (cmd *migratecmd) Run() {
srcBackend, err := storage.NewStorage(cmd.src)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create src storage backend"))
}
if err := srcBackend.Open(); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to connect/open src storage backend"))
}
defer srcBackend.Close()
destBackend, err := storage.NewStorage(cmd.dest)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create destination storage backend"))
}
if err := destBackend.Open(); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to connect/open destination storage backend"))
}
defer destBackend.Close()
srcDevices, err := srcBackend.List("")
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to list all devices from source storage backend"))
}
logrus.Infof("copying %v devices from source --> destination backend", len(srcDevices))
for _, device := range srcDevices {
if err := destBackend.Save(device); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to write device to destination storage backend"))
}
}
}

@ -0,0 +1,309 @@
package serve
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/place1/wg-access-server/internal/services"
"github.com/place1/wg-access-server/internal/storage"
"github.com/place1/wg-access-server/pkg/authnz"
"github.com/place1/wg-access-server/pkg/authnz/authconfig"
"github.com/place1/wg-access-server/pkg/authnz/authsession"
"github.com/vishvananda/netlink"
"golang.org/x/crypto/bcrypt"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/yaml.v2"
"github.com/gorilla/mux"
"github.com/place1/wg-embed/pkg/wgembed"
"github.com/pkg/errors"
"github.com/place1/wg-access-server/internal/config"
"github.com/place1/wg-access-server/internal/devices"
"github.com/place1/wg-access-server/internal/dnsproxy"
"github.com/place1/wg-access-server/internal/network"
"github.com/sirupsen/logrus"
)
func RegisterCommand(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)
return cmd
}
type servecmd struct {
configPath string
webPort int
wireguardPort int
storage string
privateKey string
disableMetadata bool
adminUsername string
adminPassword string
upstreamDNS string
}
func (cmd *servecmd) Name() string {
return "serve"
}
func (cmd *servecmd) Run() {
conf := cmd.ReadConfig()
// The server's IP within the VPN virtual network
vpnip := network.ServerVPNIP(conf.VPN.CIDR)
// WireGuard Server
wg := wgembed.NewNoOpInterface()
if conf.WireGuard.Enabled {
wgimpl, err := wgembed.New(conf.WireGuard.InterfaceName)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create wireguard interface"))
}
defer wgimpl.Close()
wg = wgimpl
logrus.Infof("starting wireguard server on 0.0.0.0:%d", conf.WireGuard.Port)
wgconfig := &wgembed.ConfigFile{
Interface: wgembed.IfaceConfig{
PrivateKey: conf.WireGuard.PrivateKey,
Address: vpnip.String(),
ListenPort: &conf.WireGuard.Port,
},
}
if err := wg.LoadConfig(wgconfig); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to load wireguard config"))
}
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 {
logrus.Fatal(err)
}
}
// DNS Server
if conf.DNS.Enabled {
dns, err := dnsproxy.New(dnsproxy.DNSServerOpts{
Upstream: conf.DNS.Upstream,
})
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to start dns server"))
}
defer dns.Close()
}
// Storage
storageBackend, err := storage.NewStorage(conf.Storage)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create storage backend"))
}
if err := storageBackend.Open(); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to connect/open storage backend"))
}
defer storageBackend.Close()
// Services
deviceManager := devices.New(wg, storageBackend, conf.VPN.CIDR)
if err := deviceManager.StartSync(conf.DisableMetadata); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to sync"))
}
router := mux.NewRouter()
router.Use(services.TracesMiddleware)
router.Use(services.RecoveryMiddleware)
// Health check endpoint
router.PathPrefix("/health").Handler(services.HealthEndpoint())
// Authentication middleware
if conf.Auth.IsEnabled() {
router.Use(authnz.NewMiddleware(conf.Auth, claimsMiddleware(conf)))
} else {
logrus.Warn("[DEPRECATION NOTICE] using wg-access-server without an admin user is deprecated and will be removed in an upcoming minor release.")
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r.WithContext(authsession.SetIdentityCtx(r.Context(), &authsession.AuthSession{
Identity: &authsession.Identity{
Subject: "",
},
})))
})
})
}
// Subrouter for our site (web + api)
site := router.PathPrefix("/").Subrouter()
site.Use(authnz.RequireAuthentication)
// Grpc api
site.PathPrefix("/api").Handler(services.ApiRouter(&services.ApiServices{
Config: conf,
DeviceManager: deviceManager,
Wg: wg,
}))
// 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
address := fmt.Sprintf("0.0.0.0:%d", conf.Port)
srv := &http.Server{
Addr: address,
Handler: publicRouter,
}
// Start Web server
logrus.Infof("web ui listening on %v", address)
if err := srv.ListenAndServe(); err != nil {
logrus.Fatal(errors.Wrap(err, "unable to start http server"))
}
}
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 {
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"))
}
logrus.SetLevel(level)
}
if config.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
}
}
if config.WireGuard.PrivateKey == "" {
if !strings.HasPrefix(config.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)))
}
return config
}
func claimsMiddleware(conf *config.AppConfig) authsession.ClaimsMiddleware {
return func(user *authsession.Identity) error {
if user.Subject == conf.AdminSubject {
user.Claims.Add("admin", "true")
}
return nil
}
}
func defaultInterface() (string, error) {
links, err := netlink.LinkList()
if err != nil {
return "", errors.Wrap(err, "failed to list network interfaces")
}
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)
}
for _, route := range routes {
if route.Dst == nil {
return link.Attrs().Name, nil
}
}
}
return "", errors.New("could not determine the default network interface name")
}
var missingPrivateKey = `missing wireguard private key:
create a key:
$ wg genkey
configure via environment variable:
$ export WIREGUARD_PRIVATE_KEY="<private-key>"
or configure via flag:
$ wg-access-server serve --wireguard-private-key="<private-key>"
or configure via file:
wireguard:
privateKey: "<private-key>"
`

@ -1,7 +1,7 @@
#!/bin/bash
#!/bin/sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
SCRIPT_DIR="$(dirname $0)"
OUT_DIR="$SCRIPT_DIR/proto/proto"
mkdir -p "$OUT_DIR" || true

@ -12,6 +12,10 @@ services:
volumes:
- "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}"
ports:
- "8000:8000/tcp"
- "51820:51820/udp"

@ -144,6 +144,8 @@ 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=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=

@ -1,22 +1,7 @@
package config
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"gopkg.in/yaml.v2"
"github.com/place1/wg-access-server/pkg/authnz/authconfig"
"github.com/vishvananda/netlink"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gopkg.in/alecthomas/kingpin.v2"
)
type AppConfig struct {
@ -93,130 +78,3 @@ type AppConfig struct {
// the server will not require any authentication.
Auth authconfig.AuthConfig `yaml:"auth"`
}
var (
app = kingpin.New("wg-access-server", "An all-in-one WireGuard Access Server & VPN solution")
configPath = app.Flag("config", "Path to a config file").Envar("CONFIG").String()
logLevel = app.Flag("log-level", "Log level (debug, info, error)").Envar("LOG_LEVEL").Default("info").String()
webPort = app.Flag("web-port", "The port that the web ui server will listen on").Envar("WEB_PORT").Default("8000").Int()
wireguardPort = app.Flag("wireguard-port", "The port that the Wireguard server will listen on").Envar("WIREGUARD_PORT").Default("51820").Int()
storage = app.Flag("storage", "The storage backend connection string").Envar("STORAGE").Default("memory://").String()
privateKey = app.Flag("wireguard-private-key", "Wireguard private key").Envar("WIREGUARD_PRIVATE_KEY").String()
disableMetadata = app.Flag("disable-metadata", "Disable metadata collection (i.e. metrics)").Envar("DISABLE_METADATA").Default("false").Bool()
adminUsername = app.Flag("admin-username", "Admin username (defaults to admin)").Envar("ADMIN_USERNAME").String()
adminPassword = app.Flag("admin-password", "Admin password (provide plaintext, stored in-memory only)").Envar("ADMIN_PASSWORD").String()
upstreamDNS = app.Flag("upstream-dns", "An upstream DNS server to proxy DNS traffic to").Envar("UPSTREAM_DNS").String()
)
func Read() *AppConfig {
kingpin.MustParse(app.Parse(os.Args[1:]))
// here we're filling out the config struct
// with values from our flags/defaults.
config := AppConfig{}
config.LogLevel = *logLevel
config.Port = *webPort
config.WireGuard.InterfaceName = "wg0"
config.WireGuard.Port = *wireguardPort
config.VPN.CIDR = "10.44.0.0/24"
config.DisableMetadata = *disableMetadata
config.WireGuard.Enabled = true
config.WireGuard.PrivateKey = *privateKey
config.Storage = *storage
config.VPN.AllowedIPs = []string{"0.0.0.0/0"}
config.DNS.Enabled = true
config.AdminPassword = *adminPassword
config.AdminSubject = *adminUsername
if *upstreamDNS != "" {
config.DNS.Upstream = []string{*upstreamDNS}
}
if *configPath != "" {
if b, err := ioutil.ReadFile(*configPath); err == nil {
if err := yaml.Unmarshal(b, &config); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to bind configuration file"))
}
}
}
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"))
}
logrus.SetLevel(level)
logrus.SetReportCaller(true)
logrus.SetFormatter(&logrus.TextFormatter{
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
return "", fmt.Sprintf("%s:%d", filepath.Base(f.File), f.Line)
},
})
if config.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
}
}
if config.WireGuard.PrivateKey == "" {
logrus.Warn("no private key has been configured! using an in-memory private key that will be lost when the process exits!")
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)))
}
return &config
}
func defaultInterface() (string, error) {
links, err := netlink.LinkList()
if err != nil {
return "", errors.Wrap(err, "failed to list network interfaces")
}
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)
}
for _, route := range routes {
if route.Dst == nil {
return link.Attrs().Name, nil
}
}
}
return "", errors.New("could not determine the default network interface name")
}
// func randomPassword() string {
// letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
// length := 12
// b := make([]rune, length)
// for i := range b {
// b[i] = letterRunes[rand.Intn(len(letterRunes))]
// }
// return string(b)
// }

@ -66,7 +66,7 @@ func (s *FileStorage) List(username string) ([]*Device, error) {
}
p := strings.TrimPrefix(path, s.directory)
p = strings.TrimPrefix(p, string(os.PathSeparator))
if strings.HasPrefix(p, prefix) {
if strings.HasPrefix(p, prefix) && filepath.Ext(path) == ".json" {
files = append(files, path)
}
return nil

@ -2,153 +2,47 @@ package main
import (
"fmt"
"net/http"
"github.com/place1/wg-access-server/internal/services"
"github.com/place1/wg-access-server/internal/storage"
"github.com/place1/wg-access-server/pkg/authnz"
"github.com/place1/wg-access-server/pkg/authnz/authsession"
"github.com/gorilla/mux"
"github.com/place1/wg-embed/pkg/wgembed"
"os"
"path/filepath"
"runtime"
"github.com/pkg/errors"
"github.com/place1/wg-access-server/internal/config"
"github.com/place1/wg-access-server/internal/devices"
"github.com/place1/wg-access-server/internal/dnsproxy"
"github.com/place1/wg-access-server/internal/network"
"github.com/place1/wg-access-server/cmd/migrate"
"github.com/place1/wg-access-server/cmd/serve"
"github.com/sirupsen/logrus"
"gopkg.in/alecthomas/kingpin.v2"
)
func main() {
conf := config.Read()
// The server's IP within the VPN virtual network
vpnip := network.ServerVPNIP(conf.VPN.CIDR)
// WireGuard Server
wg := wgembed.NewNoOpInterface()
if conf.WireGuard.Enabled {
wgimpl, err := wgembed.New(conf.WireGuard.InterfaceName)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create wireguard interface"))
}
defer wgimpl.Close()
wg = wgimpl
logrus.Infof("starting wireguard server on 0.0.0.0:%d", conf.WireGuard.Port)
wgconfig := &wgembed.ConfigFile{
Interface: wgembed.IfaceConfig{
PrivateKey: conf.WireGuard.PrivateKey,
Address: vpnip.String(),
ListenPort: &conf.WireGuard.Port,
},
}
if err := wg.LoadConfig(wgconfig); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to load wireguard config"))
}
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()
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 {
logrus.Fatal(err)
}
}
servecmd = serve.RegisterCommand(app)
migratecmd = migrate.RegisterCommand(app)
)
// DNS Server
if conf.DNS.Enabled {
dns, err := dnsproxy.New(dnsproxy.DNSServerOpts{
Upstream: conf.DNS.Upstream,
})
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to start dns server"))
}
defer dns.Close()
}
func main() {
cmd := kingpin.MustParse(app.Parse(os.Args[1:]))
// Storage
storageBackend, err := storage.NewStorage(conf.Storage)
level, err := logrus.ParseLevel(*logLevel)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create storage backend"))
}
if err := storageBackend.Open(); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to connect/open storage backend"))
}
defer storageBackend.Close()
// Services
deviceManager := devices.New(wg, storageBackend, conf.VPN.CIDR)
if err := deviceManager.StartSync(conf.DisableMetadata); err != nil {
logrus.Fatal(errors.Wrap(err, "failed to sync"))
}
router := mux.NewRouter()
router.Use(services.TracesMiddleware)
router.Use(services.RecoveryMiddleware)
// Health check endpoint
router.PathPrefix("/health").Handler(services.HealthEndpoint())
// Authentication middleware
if conf.Auth.IsEnabled() {
router.Use(authnz.NewMiddleware(conf.Auth, claimsMiddleware(conf)))
} else {
logrus.Warn("[DEPRECATION NOTICE] using wg-access-server without an admin user is deprecated and will be removed in an upcoming minor release.")
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r.WithContext(authsession.SetIdentityCtx(r.Context(), &authsession.AuthSession{
Identity: &authsession.Identity{
Subject: "",
},
})))
})
})
}
// Subrouter for our site (web + api)
site := router.PathPrefix("/").Subrouter()
site.Use(authnz.RequireAuthentication)
// Grpc api
site.PathPrefix("/api").Handler(services.ApiRouter(&services.ApiServices{
Config: conf,
DeviceManager: deviceManager,
Wg: wg,
}))
// 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
address := fmt.Sprintf("0.0.0.0:%d", conf.Port)
srv := &http.Server{
Addr: address,
Handler: publicRouter,
}
// Start Web server
logrus.Infof("web ui listening on %v", address)
if err := srv.ListenAndServe(); err != nil {
logrus.Fatal(errors.Wrap(err, "unable to start http server"))
}
}
func claimsMiddleware(conf *config.AppConfig) authsession.ClaimsMiddleware {
return func(user *authsession.Identity) error {
if user.Subject == conf.AdminSubject {
user.Claims.Add("admin", "true")
}
return 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{
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
return "", fmt.Sprintf("%s:%d", filepath.Base(f.File), f.Line)
},
})
switch cmd {
case servecmd.Name():
servecmd.Run()
case migratecmd.Name():
migratecmd.Run()
default:
logrus.Fatal(fmt.Errorf("unknown command: %s", cmd))
}
}

Loading…
Cancel
Save