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 }