loginsrv

Unnamed repository; edit this file 'description' to name the repository.
git clone git@jamesshield.xyz:repos/loginsrv.git
Log | Files | Refs | README | LICENSE

config.go (9365B)


      1 package login
      2 
      3 import (
      4 	"crypto/rand"
      5 	"encoding/hex"
      6 	"errors"
      7 	"flag"
      8 	"fmt"
      9 	"io/ioutil"
     10 	"os"
     11 	"strings"
     12 	"time"
     13 
     14 	"github.com/tarent/loginsrv/logging"
     15 	"github.com/tarent/loginsrv/oauth2"
     16 )
     17 
     18 var jwtDefaultSecret string
     19 
     20 func init() {
     21 	var err error
     22 	jwtDefaultSecret, err = randStringBytes(64)
     23 	if err != nil {
     24 		panic(err)
     25 	}
     26 }
     27 
     28 // DefaultConfig for the loginsrv handler
     29 func DefaultConfig() *Config {
     30 	return &Config{
     31 		Host:                   "localhost",
     32 		Port:                   "6789",
     33 		LogLevel:               "info",
     34 		JwtSecret:              jwtDefaultSecret,
     35 		JwtAlgo:                "HS512",
     36 		JwtExpiry:              24 * time.Hour,
     37 		JwtRefreshes:           0,
     38 		SuccessURL:             "/",
     39 		Redirect:               true,
     40 		RedirectQueryParameter: "backTo",
     41 		RedirectCheckReferer:   true,
     42 		RedirectHostFile:       "",
     43 		LogoutURL:              "",
     44 		LoginPath:              "/login",
     45 		CookieName:             "jwt_token",
     46 		CookieHTTPOnly:         true,
     47 		CookieSecure:           true,
     48 		Backends:               Options{},
     49 		Oauth:                  Options{},
     50 		GracePeriod:            5 * time.Second,
     51 		UserFile:               "",
     52 		UserEndpoint:           "",
     53 		UserEndpointToken:      "",
     54 		UserEndpointTimeout:    5 * time.Second,
     55 	}
     56 }
     57 
     58 const envPrefix = "LOGINSRV_"
     59 
     60 // Config for the loginsrv handler
     61 type Config struct {
     62 	Host                   string
     63 	Port                   string
     64 	LogLevel               string
     65 	TextLogging            bool
     66 	JwtSecret              string
     67 	JwtSecretFile          string
     68 	JwtAlgo                string
     69 	JwtExpiry              time.Duration
     70 	JwtRefreshes           int
     71 	SuccessURL             string
     72 	Redirect               bool
     73 	RedirectQueryParameter string
     74 	RedirectCheckReferer   bool
     75 	RedirectHostFile       string
     76 	LogoutURL              string
     77 	Template               string
     78 	LoginPath              string
     79 	CookieName             string
     80 	CookieExpiry           time.Duration
     81 	CookieDomain           string
     82 	CookieHTTPOnly         bool
     83 	CookieSecure           bool
     84 	Backends               Options
     85 	Oauth                  Options
     86 	GracePeriod            time.Duration
     87 	UserFile               string
     88 	UserEndpoint           string
     89 	UserEndpointToken      string
     90 	UserEndpointTimeout    time.Duration
     91 }
     92 
     93 // Options is the configuration structure for oauth and backend provider
     94 // key is the providername, value is a options map.
     95 type Options map[string]map[string]string
     96 
     97 // addOauthOpts adds the options for a provider in the form of key=value,key=value,..
     98 func (c *Config) addOauthOpts(providerName, optsKvList string) error {
     99 	opts, err := parseOptions(optsKvList)
    100 	if err != nil {
    101 		return err
    102 	}
    103 
    104 	c.Oauth[providerName] = opts
    105 	return nil
    106 }
    107 
    108 // addBackendOpts adds the options for a provider in the form of key=value,key=value,..
    109 func (c *Config) addBackendOpts(providerName, optsKvList string) error {
    110 	opts, err := parseOptions(optsKvList)
    111 	if err != nil {
    112 		return err
    113 	}
    114 
    115 	c.Backends[providerName] = opts
    116 	return nil
    117 }
    118 
    119 // ResolveFileReferences resolves configuration values, which are dynamically referenced via files
    120 func (c *Config) ResolveFileReferences() error {
    121 	// Try to load the secret from a file, if set
    122 	if c.JwtSecretFile != "" {
    123 		secretBytes, err := ioutil.ReadFile(c.JwtSecretFile)
    124 		if err != nil {
    125 			return err
    126 		}
    127 
    128 		c.JwtSecret = string(secretBytes)
    129 	}
    130 
    131 	return nil
    132 }
    133 
    134 // ConfigureFlagSet adds all flags to the supplied flag set
    135 func (c *Config) ConfigureFlagSet(f *flag.FlagSet) {
    136 	f.StringVar(&c.Host, "host", c.Host, "The host to listen on")
    137 	f.StringVar(&c.Port, "port", c.Port, "The port to listen on")
    138 	f.StringVar(&c.LogLevel, "log-level", c.LogLevel, "The log level")
    139 	f.BoolVar(&c.TextLogging, "text-logging", c.TextLogging, "Log in text format instead of json")
    140 	f.StringVar(&c.JwtSecret, "jwt-secret", c.JwtSecret, "The secret to sign the jwt token")
    141 	f.StringVar(&c.JwtSecretFile, "jwt-secret-file", c.JwtSecretFile, "Path to a file containing the secret to sign the jwt token (overrides jwt-secret)")
    142 	f.StringVar(&c.JwtAlgo, "jwt-algo", c.JwtAlgo, "The singing algorithm to use (ES256, ES384, ES512, RS256, RS384, RS512, HS256, HS384, HS512")
    143 	f.DurationVar(&c.JwtExpiry, "jwt-expiry", c.JwtExpiry, "The expiry duration for the jwt token, e.g. 2h or 3h30m")
    144 	f.IntVar(&c.JwtRefreshes, "jwt-refreshes", c.JwtRefreshes, "The maximum amount of jwt refreshes. 0 by Default")
    145 	f.StringVar(&c.CookieName, "cookie-name", c.CookieName, "The name of the jwt cookie")
    146 	f.BoolVar(&c.CookieHTTPOnly, "cookie-http-only", c.CookieHTTPOnly, "Set the cookie with the http only flag")
    147 	f.BoolVar(&c.CookieSecure, "cookie-secure", c.CookieSecure, "Set the cookie with the secure flag")
    148 	f.DurationVar(&c.CookieExpiry, "cookie-expiry", c.CookieExpiry, "The expiry duration for the cookie, e.g. 2h or 3h30m. Default is browser session")
    149 	f.StringVar(&c.CookieDomain, "cookie-domain", c.CookieDomain, "The optional domain parameter for the cookie")
    150 	f.StringVar(&c.SuccessURL, "success-url", c.SuccessURL, "The url to redirect after login")
    151 	f.BoolVar(&c.Redirect, "redirect", c.Redirect, "Allow dynamic overwriting of the the success by query parameter")
    152 	f.StringVar(&c.RedirectQueryParameter, "redirect-query-parameter", c.RedirectQueryParameter, "URL parameter for the redirect target")
    153 	f.BoolVar(&c.RedirectCheckReferer, "redirect-check-referer", c.RedirectCheckReferer, "When redirecting check that the referer is the same domain")
    154 	f.StringVar(&c.RedirectHostFile, "redirect-host-file", c.RedirectHostFile, "A file containing a list of domains that redirects are allowed to, one domain per line")
    155 
    156 	f.StringVar(&c.LogoutURL, "logout-url", c.LogoutURL, "The url or path to redirect after logout")
    157 	f.StringVar(&c.Template, "template", c.Template, "An alternative template for the login form")
    158 	f.StringVar(&c.LoginPath, "login-path", c.LoginPath, "The path of the login resource")
    159 	f.DurationVar(&c.GracePeriod, "grace-period", c.GracePeriod, "Graceful shutdown grace period")
    160 	f.StringVar(&c.UserFile, "user-file", c.UserFile, "A YAML file with user specific data for the tokens")
    161 	f.StringVar(&c.UserEndpoint, "user-endpoint", c.UserEndpoint, "URL of an endpoint providing user specific data for the tokens")
    162 	f.StringVar(&c.UserEndpointToken, "user-endpoint-token", c.UserEndpointToken, "Authentication token used when communicating with the user endpoint")
    163 	f.DurationVar(&c.UserEndpointTimeout, "user-endpoint-timeout", c.UserEndpointTimeout, "Timeout used when communicating with the user endpoint")
    164 
    165 	// the -backends is deprecated, but we support it for backwards compatibility
    166 	deprecatedBackends := setFunc(func(optsKvList string) error {
    167 		logging.Logger.Warn("DEPRECATED: '-backend' is no longer supported. Please set the backends by explicit parameters")
    168 		opts, err := parseOptions(optsKvList)
    169 		if err != nil {
    170 			return err
    171 		}
    172 		pName, ok := opts["provider"]
    173 		if !ok {
    174 			return errors.New("missing provider name provider=...")
    175 		}
    176 		delete(opts, "provider")
    177 		c.Backends[pName] = opts
    178 		return nil
    179 	})
    180 	f.Var(deprecatedBackends, "backend", "Deprecated, please use the explicit flags")
    181 
    182 	// One option for each oauth provider
    183 	for _, pName := range oauth2.ProviderList() {
    184 		func(pName string) {
    185 			setter := setFunc(func(optsKvList string) error {
    186 				return c.addOauthOpts(pName, optsKvList)
    187 			})
    188 			f.Var(setter, pName, "Oauth config in the form: client_id=..,client_secret=..[,scope=..,][redirect_uri=..]")
    189 		}(pName)
    190 	}
    191 
    192 	// One option for each backend provider
    193 	for _, pName := range ProviderList() {
    194 		func(pName string) {
    195 			setter := setFunc(func(optsKvList string) error {
    196 				return c.addBackendOpts(pName, optsKvList)
    197 			})
    198 			desc, _ := GetProviderDescription(pName)
    199 			f.Var(setter, pName, desc.HelpText)
    200 		}(pName)
    201 	}
    202 }
    203 
    204 // ReadConfig from the commandline args
    205 func ReadConfig() *Config {
    206 	c, err := readConfig(flag.CommandLine, os.Args[1:])
    207 	if err != nil {
    208 		// should never happen, because of flag default policy ExitOnError
    209 		panic(err)
    210 	}
    211 	return c
    212 }
    213 
    214 func readConfig(f *flag.FlagSet, args []string) (*Config, error) {
    215 	config := DefaultConfig()
    216 	config.ConfigureFlagSet(f)
    217 
    218 	// fist use the environment settings
    219 	f.VisitAll(func(f *flag.Flag) {
    220 		if val, isPresent := os.LookupEnv(envName(f.Name)); isPresent {
    221 			f.Value.Set(val)
    222 		}
    223 	})
    224 
    225 	// prefer flags over environment settings
    226 	err := f.Parse(args)
    227 	if err != nil {
    228 		return nil, err
    229 	}
    230 
    231 	if err := config.ResolveFileReferences(); err != nil {
    232 		return nil, err
    233 	}
    234 
    235 	return config, err
    236 }
    237 
    238 func randStringBytes(n int) (string, error) {
    239 	b := make([]byte, n)
    240 	_, err := rand.Read(b)
    241 	if err != nil {
    242 		return "", err
    243 	}
    244 	return hex.EncodeToString(b), nil
    245 }
    246 
    247 func envName(flagName string) string {
    248 	return envPrefix + strings.Replace(strings.ToUpper(flagName), "-", "_", -1)
    249 }
    250 
    251 func parseOptions(b string) (map[string]string, error) {
    252 	opts := map[string]string{}
    253 	pairs := strings.Split(b, ",")
    254 	for _, p := range pairs {
    255 		pair := strings.SplitN(p, "=", 2)
    256 		if len(pair) != 2 {
    257 			return nil, fmt.Errorf("provider configuration has to be in form 'key1=value1,key2=..', but was %v", p)
    258 		}
    259 		opts[pair[0]] = pair[1]
    260 	}
    261 	return opts, nil
    262 }
    263 
    264 // Helper type to wrap a function closure with the Value interface
    265 type setFunc func(optsKvList string) error
    266 
    267 func (f setFunc) Set(value string) error {
    268 	return f(value)
    269 }
    270 
    271 func (f setFunc) String() string {
    272 	return "setFunc"
    273 }