Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ help: ## Display this help message and exit
build: clean bindata.go ## Build the binary
@echo "Compiling subspace..."
@CGO_ENABLED=0 \
go build -v --compiler gc --ldflags "-extldflags -static -s -w -X main.version=${BUILD_VERSION}" -o subspace ./cmd/subspace \
go build -v --compiler gc --ldflags "-extldflags -static -s -w -X github.com/subspacecommunity/subspace/cmd/subspace/cli.Version=${BUILD_VERSION}" -o subspace ./cmd/subspace \
&& rm cmd/subspace/bindata.go
@echo "+++ subspace compiled"

Expand Down
150 changes: 150 additions & 0 deletions cmd/subspace/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package cli

import (
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var (
// Version will be set by the make build command.
Version string
// StartupConfig will be set by the SetStartupConfig function.
StartupConfig *startupConfig
)

type startupConfig struct {
DataDir string `mapstructure:"data_dir"`
Backlink string
HttpHost string `mapstructure:"http_host"`
HttpAddr string `mapstructure:"http_addr"`
HttpInsecure bool `mapstructure:"http_insecure"`
LetsEncrypt bool
Debug bool
Theme string
DisableDns bool `mapstructure:"disable_dns"`
AllowedIps string `mapstructure:"allowed_ips"`
EndpointHost string `mapstructure:"endpoint_host"`
ListenPort string
Ipv4Cidr string `mapstructure:"ipv4_cidr"`
Ipv4Gw string `mapstructure:"ipv4_gw"`
Ipv4Pref string `mapstructure:"ipv4_pref"`
Ipv4NatEnabled bool `mapstructure:"ipv4_nat_enabled"`
Ipv6Cidr string `mapstructure:"ipv6_cidr"`
Ipv6Gw string `mapstructure:"ipv6_gw"`
Ipv6Pref string `mapstructure:"ipv6_pref"`
Ipv6NatEnabled bool `mapstructure:"ipv6_nat_enabled"`
}

// Execute parse command line arguments and loads the startup config.
// The following sources are considered:
// * Command line arguments
// * Environment variables
// * Config file
// * Defaults set in the code
func SetStartupConfig() {
rootCmd := &cobra.Command{
Use: "subspace",
// This will make cobra add "--version" option
Version: Version,
Short: "Subspace is a frontend for Wireguard configuration & user management",
// we must have an empty run function (or child commands), otherwise help won't print usage string
// see https://github.com/spf13/cobra/blob/6d00909120c77b54b0c9974a4e20ffc540901b98/command.go#L527
Run: func(cmd *cobra.Command, args []string) {},
}
var err error
StartupConfig, err = loadStartupConfig(rootCmd)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
os.Exit(1)
}

// Perform command validation

// Since Cobra is not driving the main function, we need to take care of exiting
// after a call to help or version
help, _ := rootCmd.Flags().GetBool("help")
version, _ := rootCmd.Flags().GetBool("version")
if help || version {
os.Exit(0)
}

if StartupConfig.HttpHost == "" {
fmt.Fprintf(os.Stderr, "--http-host is required (either through the command line or a config file)\n\n")
fmt.Println(rootCmd.UsageString())
os.Exit(1)
}
}

func loadStartupConfig(rootCmd *cobra.Command) (*startupConfig, error) {
rootCmd.Flags().String("config", "", "config file (optional)")
rootCmd.Flags().String("datadir", "/data", "data dir")
rootCmd.Flags().String("backlink", "/", "backlink (optional)")
rootCmd.Flags().String("http-host", "", "HTTP host")
rootCmd.Flags().String("http-addr", ":80", "HTTP listen address")
rootCmd.Flags().Bool("http-insecure", false, "enable sessions cookies for http (no https) not recommended")
rootCmd.Flags().Bool("letsencrypt", true, "enable TLS using Let's Encrypt on port 443")
rootCmd.Flags().Bool("debug", false, "debug mode")
rootCmd.Flags().String("theme", "green", "Semantic-ui theme to use")

if err := rootCmd.Execute(); err != nil {
return nil, err
}

startupConfig, err := unifyConfig(rootCmd)
return startupConfig, err
}

func unifyConfig(rootCmd *cobra.Command) (*startupConfig, error) {
viper.BindPFlag("data_dir", rootCmd.Flags().Lookup("datadir"))
viper.BindPFlag("backlink", rootCmd.Flags().Lookup("backlink"))
viper.BindPFlag("http_host", rootCmd.Flags().Lookup("http-host"))
viper.BindPFlag("http_addr", rootCmd.Flags().Lookup("http-addr"))
viper.BindPFlag("http_insecure", rootCmd.Flags().Lookup("http-insecure"))
viper.BindPFlag("letsencrypt", rootCmd.Flags().Lookup("letsencrypt"))
viper.BindPFlag("debug", rootCmd.Flags().Lookup("debug"))
viper.BindPFlag("theme", rootCmd.Flags().Lookup("theme"))

// Look for the config file in the current directory if the path isn't absolute
viper.AddConfigPath(".")
cfgFile := rootCmd.Flags().Lookup("config").Value.String()
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file: %s", err)
} else {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}

viper.SetEnvPrefix("subspace")
viper.AutomaticEnv()

// Setting defaults
viper.SetDefault("disable_dns", false)
viper.SetDefault("allowed_ips", "0.0.0.0/0, ::/0")
viper.SetDefault("listenport", "51820")
viper.SetDefault("ipv4_cidr", "24")
viper.SetDefault("ipv4_gw", "10.99.97.1")
viper.SetDefault("ipv4_pref", "10.99.97.")
viper.SetDefault("ipv4_nat_enabled", true)
viper.SetDefault("ipv6_cidr", "64")
viper.SetDefault("ipv6_gw", "fd00::10:97:1")
viper.SetDefault("ipv6_pref", "fd00::10:97:")
viper.SetDefault("ipv6_nat_enabled", true)

// if endpoint_host was not explicitly set, use http_host
if viper.GetString("endpoint_host") == "" {
httpHost := viper.GetString("http_host")
viper.Set("endpoint_host", httpHost)
}

var startupConfig startupConfig
err := viper.Unmarshal(&startupConfig)
if err != nil {
return nil, fmt.Errorf("unable to decode configuration into startupConfig struct, %v", err)
}
return &startupConfig, nil
}
92 changes: 92 additions & 0 deletions cmd/subspace/cli/cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cli

import (
"io/ioutil"
"os"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)

func create_root_cmd(args ...string) *cobra.Command {
rootCmd := &cobra.Command{
Use: "subspace",
Run: func(_ *cobra.Command, args []string) {},
}
rootCmd.SetArgs(args)
return rootCmd
}

func create_config_file(config string) (string, error) {
file, err := ioutil.TempFile("", "subspace.*.yaml")
if err != nil {
return "", err
}
ioutil.WriteFile(file.Name(), []byte(config), 0644)
return file.Name(), nil
}

func TestDefaults(t *testing.T) {
rootCmd := create_root_cmd()
startupConfig, err := loadStartupConfig(rootCmd)
assert.Equal(t, err, nil, "loadStartupConfig should succeed")
assert.Equal(t, startupConfig.DataDir, "/data")
assert.Equal(t, startupConfig.Backlink, "/")
assert.Equal(t, startupConfig.HttpHost, "")
assert.Equal(t, startupConfig.HttpAddr, ":80")
assert.Equal(t, startupConfig.HttpInsecure, false)
assert.Equal(t, startupConfig.LetsEncrypt, true)
assert.Equal(t, startupConfig.Debug, false)
assert.Equal(t, startupConfig.Theme, "green")
assert.Equal(t, startupConfig.DisableDns, false)
assert.Equal(t, startupConfig.AllowedIps, "0.0.0.0/0, ::/0")
assert.Equal(t, startupConfig.EndpointHost, "")
assert.Equal(t, startupConfig.ListenPort, "51820")
assert.Equal(t, startupConfig.Ipv4Cidr, "24")
assert.Equal(t, startupConfig.Ipv4Gw, "10.99.97.1")
assert.Equal(t, startupConfig.Ipv4Pref, "10.99.97.")
assert.Equal(t, startupConfig.Ipv4NatEnabled, true)
assert.Equal(t, startupConfig.Ipv6Cidr, "64")
assert.Equal(t, startupConfig.Ipv6Gw, "fd00::10:97:1")
assert.Equal(t, startupConfig.Ipv6Pref, "fd00::10:97:")
assert.Equal(t, startupConfig.Ipv6NatEnabled, true)
}

func TestConfigFile(t *testing.T) {
config := `
listenport: 56876
disable_dns: true
ipv4_cidr: 30
`
fileName, err := create_config_file(config)
rootCmd := create_root_cmd("--config", fileName)
assert.Equal(t, err, nil, "create_config_file should succeed")
defer os.Remove(fileName)
startupConfig, err := loadStartupConfig(rootCmd)
assert.Equal(t, err, nil, "loadStartupConfig should succeed")
assert.Equal(t, startupConfig.ListenPort, "56876")
assert.Equal(t, startupConfig.DisableDns, true)
assert.Equal(t, startupConfig.Ipv4Cidr, "30")
}

func TestConfigMix(t *testing.T) {
os.Setenv("SUBSPACE_IPV4_CIDR", "8")
config := `
listenport: 56876
letsencrypt: false
ipv4_cidr: 30
`
fileName, err := create_config_file(config)
rootCmd := create_root_cmd("--config", fileName, "--letsencrypt", "true")
assert.Equal(t, err, nil, "create_config_file should succeed")
defer os.Remove(fileName)
startupConfig, err := loadStartupConfig(rootCmd)
assert.Equal(t, err, nil, "loadStartupConfig should succeed")
assert.Equal(t, startupConfig.ListenPort, "56876")
// command line has priority over config file
assert.Equal(t, startupConfig.LetsEncrypt, true)
// environment variable has priority over config file
assert.Equal(t, startupConfig.Ipv4Cidr, "8")
os.Unsetenv("SUBSPACE_IPV4_CIDR")
}
11 changes: 6 additions & 5 deletions cmd/subspace/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"time"

"github.com/pquerna/otp/totp"
"github.com/subspacecommunity/subspace/cmd/subspace/cli"
)

var (
Expand Down Expand Up @@ -52,7 +53,7 @@ func (p Profile) NameClean() string {
}

func (p Profile) WireGuardConfigPath() string {
return fmt.Sprintf("%s/wireguard/clients/%s.conf", datadir, p.ID)
return fmt.Sprintf("%s/wireguard/clients/%s.conf", cli.StartupConfig.DataDir, p.ID)
}

func (p Profile) WireGuardConfigName() string {
Expand Down Expand Up @@ -95,14 +96,14 @@ type Config struct {
}

func NewConfig(filename string) (*Config, error) {
filename = filepath.Join(datadir, filename)
filename = filepath.Join(cli.StartupConfig.DataDir, filename)
c := &Config{filename: filename}
b, err := ioutil.ReadFile(filename)

// Create new config with defaults
if os.IsNotExist(err) {
c.Info = &Info{
Email: "null",
Email: "null",
HashKey: RandomString(32),
BlockKey: RandomString(32),
}
Expand Down Expand Up @@ -155,7 +156,7 @@ func (c *Config) generateSAMLKeyPair() error {
NotAfter: time.Now().AddDate(5, 0, 0),
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: httpHost,
CommonName: cli.StartupConfig.HttpHost,
Organization: []string{"Subspace"},
},
BasicConstraintsValid: true,
Expand Down Expand Up @@ -443,7 +444,7 @@ func (c *Config) ResetTotp() error {
func (c *Config) GenerateTOTP() error {
key, err := totp.Generate(
totp.GenerateOpts{
Issuer: httpHost,
Issuer: cli.StartupConfig.HttpHost,
AccountName: c.Info.Email,
},
)
Expand Down
65 changes: 15 additions & 50 deletions cmd/subspace/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/crewjam/saml/samlsp"
"github.com/julienschmidt/httprouter"
"github.com/pquerna/otp/totp"
"github.com/subspacecommunity/subspace/cmd/subspace/cli"
"golang.org/x/crypto/bcrypt"

qrcode "github.com/skip2/go-qrcode"
Expand Down Expand Up @@ -414,54 +415,18 @@ func profileAddHandler(w *Web) {
return
}

ipv4Pref := "10.99.97."
if pref := getEnv("SUBSPACE_IPV4_PREF", "nil"); pref != "nil" {
ipv4Pref = pref
}
ipv4Gw := "10.99.97.1"
if gw := getEnv("SUBSPACE_IPV4_GW", "nil"); gw != "nil" {
ipv4Gw = gw
}
ipv4Cidr := "24"
if cidr := getEnv("SUBSPACE_IPV4_CIDR", "nil"); cidr != "nil" {
ipv4Cidr = cidr
}
ipv6Pref := "fd00::10:97:"
if pref := getEnv("SUBSPACE_IPV6_PREF", "nil"); pref != "nil" {
ipv6Pref = pref
}
ipv6Gw := "fd00::10:97:1"
if gw := getEnv("SUBSPACE_IPV6_GW", "nil"); gw != "nil" {
ipv6Gw = gw
}
ipv6Cidr := "64"
if cidr := getEnv("SUBSPACE_IPV6_CIDR", "nil"); cidr != "nil" {
ipv6Cidr = cidr
}
listenport := "51820"
if port := getEnv("SUBSPACE_LISTENPORT", "nil"); port != "nil" {
listenport = port
}
endpointHost := httpHost
if eh := getEnv("SUBSPACE_ENDPOINT_HOST", "nil"); eh != "nil" {
endpointHost = eh
}
allowedips := "0.0.0.0/0, ::/0"
if ips := getEnv("SUBSPACE_ALLOWED_IPS", "nil"); ips != "nil" {
allowedips = ips
}
ipv4Enabled := true
if enable := getEnv("SUBSPACE_IPV4_NAT_ENABLED", "1"); enable == "0" {
ipv4Enabled = false
}
ipv6Enabled := true
if enable := getEnv("SUBSPACE_IPV6_NAT_ENABLED", "1"); enable == "0" {
ipv6Enabled = false
}
disableDNS := false
if shouldDisableDNS := getEnv("SUBSPACE_DISABLE_DNS", "0"); shouldDisableDNS == "1" {
disableDNS = true
}
ipv4Pref := cli.StartupConfig.Ipv4Pref
ipv4Gw := cli.StartupConfig.Ipv4Gw
ipv4Cidr := cli.StartupConfig.Ipv4Cidr
ipv6Pref := cli.StartupConfig.Ipv6Pref
ipv6Gw := cli.StartupConfig.Ipv6Gw
ipv6Cidr := cli.StartupConfig.Ipv6Cidr
listenport := cli.StartupConfig.ListenPort
endpointHost := cli.StartupConfig.EndpointHost
allowedips := cli.StartupConfig.AllowedIps
ipv4Enabled := cli.StartupConfig.Ipv4NatEnabled
ipv6Enabled := cli.StartupConfig.Ipv6NatEnabled
disableDNS := cli.StartupConfig.DisableDns

script := `
cd {{$.Datadir}}/wireguard
Expand Down Expand Up @@ -509,7 +474,7 @@ WGCLIENT
}{
profile,
endpointHost,
datadir,
cli.StartupConfig.DataDir,
ipv4Gw,
ipv6Gw,
ipv4Pref,
Expand Down Expand Up @@ -695,7 +660,7 @@ rm clients/{{$.Profile.ID}}.conf
Datadir string
Profile Profile
}{
datadir,
cli.StartupConfig.DataDir,
profile,
})
if err != nil {
Expand Down
Loading