git.iko.soy/typeable/oauth2-proxy
on master help sign in
Moved test and options to correct places
commit4d94ffc974836857f927122ec6abfe11973987d5 authorTero Pihlaja <tero.pihlaja@turckvilant.com> dateWed 27 May 2020, 12:57 (6 years ago) parent9126a1b
4 files changed+24−925
options.go−627 options_test.go−284 pkg/apis/options/options.go+16−14 pkg/validation/options_test.go+8
options.godeleted−627
@@ -1,627 +0,0 @@
1−package main
2−
3−import (
4− "context"
5− "crypto"
6− "crypto/tls"
7− "encoding/base64"
8− "fmt"
9− "io/ioutil"
10− "net/http"
11− "net/url"
12− "os"
13− "regexp"
14− "strings"
15− "time"
16−
17− oidc "github.com/coreos/go-oidc"
18− "github.com/dgrijalva/jwt-go"
19− "github.com/mbland/hmacauth"
20− "github.com/pusher/oauth2_proxy/pkg/apis/options"
21− sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
22− "github.com/pusher/oauth2_proxy/pkg/encryption"
23− "github.com/pusher/oauth2_proxy/pkg/logger"
24− "github.com/pusher/oauth2_proxy/pkg/sessions"
25− "github.com/pusher/oauth2_proxy/providers"
26− "gopkg.in/natefinch/lumberjack.v2"
27−)
28−
29−// Options holds Configuration Options that can be set by Command Line Flag,
30−// or Config File
31−type Options struct {
32− ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix" env:"OAUTH2_PROXY_PROXY_PREFIX"`
33− PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"`
34− ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"`
35− HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"`
36− HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"`
37− RedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"`
38− ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
39− ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
40− TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"`
41− TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"`
42− ProxyTimeOut time.Duration `flag:"proxy-time-out" cfg:"proxy_time_out" env:"OAUTH2_PROXY_PROXY_TIME_OUT"`
43−
44− AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"`
45− KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_KEYCLOAK_GROUP"`
46− AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"`
47− BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team" env:"OAUTH2_PROXY_BITBUCKET_TEAM"`
48− BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository" env:"OAUTH2_PROXY_BITBUCKET_REPOSITORY"`
49− EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"`
50− WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"`
51− GitHubOrg string `flag:"github-org" cfg:"github_org" env:"OAUTH2_PROXY_GITHUB_ORG"`
52− GitHubTeam string `flag:"github-team" cfg:"github_team" env:"OAUTH2_PROXY_GITHUB_TEAM"`
53− GitLabGroup string `flag:"gitlab-group" cfg:"gitlab_group" env:"OAUTH2_PROXY_GITLAB_GROUP"`
54− GoogleGroups []string `flag:"google-group" cfg:"google_group" env:"OAUTH2_PROXY_GOOGLE_GROUPS"`
55− GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email" env:"OAUTH2_PROXY_GOOGLE_ADMIN_EMAIL"`
56− GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json" env:"OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON"`
57− HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE"`
58− DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM"`
59− CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR"`
60− Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
61− Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
62−
63− // Embed CookieOptions
64− options.CookieOptions
65−
66− // Embed SessionOptions
67− options.SessionOptions
68−
69− Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
70− SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
71− SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
72− ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
73− PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
74− BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
75− PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
76− PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"`
77− SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
78− PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
79− SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
80− SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_UPSTREAM_INSECURE_SKIP_VERIFY"`
81− SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
82− SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
83− PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
84− SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"`
85− FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"`
86−
87− // These options allow for other providers besides Google, with
88− // potential overrides.
89− Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
90− OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
91− InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"`
92− SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"`
93− OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_PROXY_OIDC_JWKS_URL"`
94− LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"`
95− RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"`
96− ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"`
97− ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"`
98− ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"`
99− Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"`
100− ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"`
101−
102− // Configuration values for logging
103− LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_PROXY_LOGGING_FILENAME"`
104− LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE"`
105− LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE"`
106− LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS"`
107− LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME"`
108− LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS"`
109− StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING"`
110− StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"`
111− RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"`
112− RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"`
113− ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS"`
114− SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"`
115− AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"`
116− AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"`
117− SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
118− AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
119− JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
120− JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file" env:"OAUTH2_PROXY_JWT_KEY_FILE"`
121− PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"`
122− GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"`
123−
124− // internal values that are set after config validation
125− redirectURL *url.URL
126− proxyURLs []*url.URL
127− CompiledRegex []*regexp.Regexp
128− provider providers.Provider
129− sessionStore sessionsapi.SessionStore
130− signatureData *SignatureData
131− oidcVerifier *oidc.IDTokenVerifier
132− jwtBearerVerifiers []*oidc.IDTokenVerifier
133−}
134−
135−// SignatureData holds hmacauth signature hash and key
136−type SignatureData struct {
137− hash crypto.Hash
138− key string
139−}
140−
141−// NewOptions constructs a new Options with defaulted values
142−func NewOptions() *Options {
143− return &Options{
144− ProxyPrefix: "/oauth2",
145− PingPath: "/ping",
146− ProxyWebSockets: true,
147− HTTPAddress: "127.0.0.1:4180",
148− HTTPSAddress: ":443",
149− ProxyTimeOut: 30, //TIME_OUT in seconds
150− DisplayHtpasswdForm: true,
151− CookieOptions: options.CookieOptions{
152− CookieName: "_oauth2_proxy",
153− CookieSecure: true,
154− CookieHTTPOnly: true,
155− CookieExpire: time.Duration(168) * time.Hour,
156− CookieRefresh: time.Duration(0),
157− },
158− SessionOptions: options.SessionOptions{
159− Type: "cookie",
160− },
161− SetXAuthRequest: false,
162− SkipAuthPreflight: false,
163− PassBasicAuth: true,
164− PassUserHeaders: true,
165− PassAccessToken: false,
166− PassHostHeader: true,
167− SetAuthorization: false,
168− PassAuthorization: false,
169− ApprovalPrompt: "force",
170− InsecureOIDCAllowUnverifiedEmail: false,
171− SkipOIDCDiscovery: false,
172− LoggingFilename: "",
173− LoggingMaxSize: 100,
174− LoggingMaxAge: 7,
175− LoggingMaxBackups: 0,
176− LoggingLocalTime: true,
177− LoggingCompress: false,
178− ExcludeLoggingPaths: "",
179− SilencePingLogging: false,
180− StandardLogging: true,
181− StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
182− RequestLogging: true,
183− RequestLoggingFormat: logger.DefaultRequestLoggingFormat,
184− AuthLogging: true,
185− AuthLoggingFormat: logger.DefaultAuthLoggingFormat,
186− }
187−}
188−
189−// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
190−type jwtIssuer struct {
191− issuerURI string
192− audience string
193−}
194−
195−func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
196− parsed, err := url.Parse(toParse)
197− if err != nil {
198− return nil, append(msgs, fmt.Sprintf(
199− "error parsing %s-url=%q %s", urltype, toParse, err))
200− }
201− return parsed, msgs
202−}
203−
204−// Validate checks that required options are set and validates those that they
205−// are of the correct format
206−func (o *Options) Validate() error {
207− if o.SSLInsecureSkipVerify {
208− // TODO: Accept a certificate bundle.
209− insecureTransport := &http.Transport{
210− TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
211− }
212− http.DefaultClient = &http.Client{Transport: insecureTransport}
213− }
214−
215− msgs := make([]string, 0)
216− if o.CookieSecret == "" {
217− msgs = append(msgs, "missing setting: cookie-secret")
218− }
219− if o.ClientID == "" {
220− msgs = append(msgs, "missing setting: client-id")
221− }
222− // login.gov uses a signed JWT to authenticate, not a client-secret
223− if o.ClientSecret == "" && o.Provider != "login.gov" {
224− msgs = append(msgs, "missing setting: client-secret")
225− }
226− if o.AuthenticatedEmailsFile == "" && len(o.EmailDomains) == 0 && o.HtpasswdFile == "" {
227− msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
228− "\n use email-domain=* to authorize all email addresses")
229− }
230−
231− if o.OIDCIssuerURL != "" {
232−
233− ctx := context.Background()
234−
235− // Construct a manual IDTokenVerifier from issuer URL & JWKS URI
236− // instead of metadata discovery if we enable -skip-oidc-discovery.
237− // In this case we need to make sure the required endpoints for
238− // the provider are configured.
239− if o.SkipOIDCDiscovery {
240− if o.LoginURL == "" {
241− msgs = append(msgs, "missing setting: login-url")
242− }
243− if o.RedeemURL == "" {
244− msgs = append(msgs, "missing setting: redeem-url")
245− }
246− if o.OIDCJwksURL == "" {
247− msgs = append(msgs, "missing setting: oidc-jwks-url")
248− }
249− keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
250− o.oidcVerifier = oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
251− ClientID: o.ClientID,
252− })
253− } else {
254− // Configure discoverable provider data.
255− provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
256− if err != nil {
257− return err
258− }
259− o.oidcVerifier = provider.Verifier(&oidc.Config{
260− ClientID: o.ClientID,
261− })
262−
263− o.LoginURL = provider.Endpoint().AuthURL
264− o.RedeemURL = provider.Endpoint().TokenURL
265− }
266− if o.Scope == "" {
267− o.Scope = "openid email profile"
268− }
269− }
270−
271− if o.SkipJwtBearerTokens {
272− // If we are using an oidc provider, go ahead and add that provider to the list
273− if o.oidcVerifier != nil {
274− o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, o.oidcVerifier)
275− }
276− // Configure extra issuers
277− if len(o.ExtraJwtIssuers) > 0 {
278− var jwtIssuers []jwtIssuer
279− jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
280− for _, jwtIssuer := range jwtIssuers {
281− verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
282− if err != nil {
283− msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
284− }
285− o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier)
286− }
287− }
288− }
289−
290− o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
291−
292− for _, u := range o.Upstreams {
293− upstreamURL, err := url.Parse(u)
294− if err != nil {
295− msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
296− } else {
297− if upstreamURL.Path == "" {
298− upstreamURL.Path = "/"
299− }
300− o.proxyURLs = append(o.proxyURLs, upstreamURL)
301− }
302− }
303−
304− for _, u := range o.SkipAuthRegex {
305− CompiledRegex, err := regexp.Compile(u)
306− if err != nil {
307− msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
308− continue
309− }
310− o.CompiledRegex = append(o.CompiledRegex, CompiledRegex)
311− }
312− msgs = parseProviderInfo(o, msgs)
313−
314− var cipher *encryption.Cipher
315− if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.CookieRefresh != time.Duration(0)) {
316− validCookieSecretSize := false
317− for _, i := range []int{16, 24, 32} {
318− if len(secretBytes(o.CookieSecret)) == i {
319− validCookieSecretSize = true
320− }
321− }
322− var decoded bool
323− if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
324− decoded = true
325− }
326− if validCookieSecretSize == false {
327− var suffix string
328− if decoded {
329− suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.CookieSecret)
330− }
331− msgs = append(msgs, fmt.Sprintf(
332− "cookie_secret must be 16, 24, or 32 bytes "+
333− "to create an AES cipher when "+
334− "pass_access_token == true or "+
335− "cookie_refresh != 0, but is %d bytes.%s",
336− len(secretBytes(o.CookieSecret)), suffix))
337− } else {
338− var err error
339− cipher, err = encryption.NewCipher(secretBytes(o.CookieSecret))
340− if err != nil {
341− msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
342− }
343− }
344− }
345−
346− o.SessionOptions.Cipher = cipher
347− sessionStore, err := sessions.NewSessionStore(&o.SessionOptions, &o.CookieOptions)
348− if err != nil {
349− msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
350− } else {
351− o.sessionStore = sessionStore
352− }
353−
354− if o.CookieRefresh >= o.CookieExpire {
355− msgs = append(msgs, fmt.Sprintf(
356− "cookie_refresh (%s) must be less than "+
357− "cookie_expire (%s)",
358− o.CookieRefresh.String(),
359− o.CookieExpire.String()))
360− }
361−
362− if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
363− if len(o.GoogleGroups) < 1 {
364− msgs = append(msgs, "missing setting: google-group")
365− }
366− if o.GoogleAdminEmail == "" {
367− msgs = append(msgs, "missing setting: google-admin-email")
368− }
369− if o.GoogleServiceAccountJSON == "" {
370− msgs = append(msgs, "missing setting: google-service-account-json")
371− }
372− }
373−
374− msgs = parseSignatureKey(o, msgs)
375− msgs = validateCookieName(o, msgs)
376− msgs = setupLogger(o, msgs)
377−
378− if len(msgs) != 0 {
379− return fmt.Errorf("Invalid configuration:\n %s",
380− strings.Join(msgs, "\n "))
381− }
382− return nil
383−}
384−
385−func parseProviderInfo(o *Options, msgs []string) []string {
386− p := &providers.ProviderData{
387− Scope: o.Scope,
388− ClientID: o.ClientID,
389− ClientSecret: o.ClientSecret,
390− ApprovalPrompt: o.ApprovalPrompt,
391− }
392− p.LoginURL, msgs = parseURL(o.LoginURL, "login", msgs)
393− p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs)
394− p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs)
395− p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
396− p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
397−
398− o.provider = providers.New(o.Provider, p)
399− switch p := o.provider.(type) {
400− case *providers.AzureProvider:
401− p.Configure(o.AzureTenant)
402− case *providers.GitHubProvider:
403− p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
404− case *providers.KeycloakProvider:
405− p.SetGroup(o.KeycloakGroup)
406− case *providers.GoogleProvider:
407− if o.GoogleServiceAccountJSON != "" {
408− file, err := os.Open(o.GoogleServiceAccountJSON)
409− if err != nil {
410− msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
411− } else {
412− p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file)
413− }
414− }
415− case *providers.BitbucketProvider:
416− p.SetTeam(o.BitbucketTeam)
417− p.SetRepository(o.BitbucketRepository)
418− case *providers.OIDCProvider:
419− p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
420− if o.oidcVerifier == nil {
421− msgs = append(msgs, "oidc provider requires an oidc issuer URL")
422− } else {
423− p.Verifier = o.oidcVerifier
424− }
425− case *providers.GitLabProvider:
426− p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
427− p.Group = o.GitLabGroup
428− p.EmailDomains = o.EmailDomains
429−
430− if o.oidcVerifier != nil {
431− p.Verifier = o.oidcVerifier
432− } else {
433− // Initialize with default verifier for gitlab.com
434− ctx := context.Background()
435−
436− provider, err := oidc.NewProvider(ctx, "https://gitlab.com")
437− if err != nil {
438− msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
439− } else {
440− p.Verifier = provider.Verifier(&oidc.Config{
441− ClientID: o.ClientID,
442− })
443−
444− p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
445− p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
446− }
447− }
448− case *providers.LoginGovProvider:
449− p.AcrValues = o.AcrValues
450− p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
451−
452− // JWT key can be supplied via env variable or file in the filesystem, but not both.
453− switch {
454− case o.JWTKey != "" && o.JWTKeyFile != "":
455− msgs = append(msgs, "cannot set both jwt-key and jwt-key-file options")
456− case o.JWTKey == "" && o.JWTKeyFile == "":
457− msgs = append(msgs, "login.gov provider requires a private key for signing JWTs")
458− case o.JWTKey != "":
459− // The JWT Key is in the commandline argument
460− signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(o.JWTKey))
461− if err != nil {
462− msgs = append(msgs, "could not parse RSA Private Key PEM")
463− } else {
464− p.JWTKey = signKey
465− }
466− case o.JWTKeyFile != "":
467− // The JWT key is in the filesystem
468− keyData, err := ioutil.ReadFile(o.JWTKeyFile)
469− if err != nil {
470− msgs = append(msgs, "could not read key file: "+o.JWTKeyFile)
471− }
472− signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
473− if err != nil {
474− msgs = append(msgs, "could not parse private key from PEM file:"+o.JWTKeyFile)
475− } else {
476− p.JWTKey = signKey
477− }
478− }
479− }
480− return msgs
481−}
482−
483−func parseSignatureKey(o *Options, msgs []string) []string {
484− if o.SignatureKey == "" {
485− return msgs
486− }
487−
488− components := strings.Split(o.SignatureKey, ":")
489− if len(components) != 2 {
490− return append(msgs, "invalid signature hash:key spec: "+
491− o.SignatureKey)
492− }
493−
494− algorithm, secretKey := components[0], components[1]
495− var hash crypto.Hash
496− var err error
497− if hash, err = hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
498− return append(msgs, "unsupported signature hash algorithm: "+
499− o.SignatureKey)
500− }
501− o.signatureData = &SignatureData{hash: hash, key: secretKey}
502− return msgs
503−}
504−
505−// parseJwtIssuers takes in an array of strings in the form of issuer=audience
506−// and parses to an array of jwtIssuer structs.
507−func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
508− var parsedIssuers []jwtIssuer
509− for _, jwtVerifier := range issuers {
510− components := strings.Split(jwtVerifier, "=")
511− if len(components) < 2 {
512− msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier))
513− continue
514− }
515− uri, audience := components[0], strings.Join(components[1:], "=")
516− parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience})
517− }
518− return parsedIssuers, msgs
519−}
520−
521−// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
522−// a verifier for that issuer.
523−func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
524− config := &oidc.Config{
525− ClientID: jwtIssuer.audience,
526− }
527− // Try as an OpenID Connect Provider first
528− var verifier *oidc.IDTokenVerifier
529− provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI)
530− if err != nil {
531− // Try as JWKS URI
532− jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
533− _, err := http.NewRequest("GET", jwksURI, nil)
534− if err != nil {
535− return nil, err
536− }
537− verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
538− } else {
539− verifier = provider.Verifier(config)
540− }
541− return verifier, nil
542−}
543−
544−func validateCookieName(o *Options, msgs []string) []string {
545− cookie := &http.Cookie{Name: o.CookieName}
546− if cookie.String() == "" {
547− return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.CookieName))
548− }
549− return msgs
550−}
551−
552−func addPadding(secret string) string {
553− padding := len(secret) % 4
554− switch padding {
555− case 1:
556− return secret + "==="
557− case 2:
558− return secret + "=="
559− case 3:
560− return secret + "="
561− default:
562− return secret
563− }
564−}
565−
566−// secretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary
567−func secretBytes(secret string) []byte {
568− b, err := base64.URLEncoding.DecodeString(addPadding(secret))
569− if err == nil {
570− return []byte(addPadding(string(b)))
571− }
572− return []byte(secret)
573−}
574−
575−func setupLogger(o *Options, msgs []string) []string {
576− // Setup the log file
577− if len(o.LoggingFilename) > 0 {
578− // Validate that the file/dir can be written
579− file, err := os.OpenFile(o.LoggingFilename, os.O_WRONLY|os.O_CREATE, 0666)
580− if err != nil {
581− if os.IsPermission(err) {
582− return append(msgs, "unable to write to log file: "+o.LoggingFilename)
583− }
584− }
585− file.Close()
586−
587− logger.Printf("Redirecting logging to file: %s", o.LoggingFilename)
588−
589− logWriter := &lumberjack.Logger{
590− Filename: o.LoggingFilename,
591− MaxSize: o.LoggingMaxSize, // megabytes
592− MaxAge: o.LoggingMaxAge, // days
593− MaxBackups: o.LoggingMaxBackups,
594− LocalTime: o.LoggingLocalTime,
595− Compress: o.LoggingCompress,
596− }
597−
598− logger.SetOutput(logWriter)
599− }
600−
601− // Supply a sanity warning to the logger if all logging is disabled
602− if !o.StandardLogging && !o.AuthLogging && !o.RequestLogging {
603− logger.Print("Warning: Logging disabled. No further logs will be shown.")
604− }
605−
606− // Pass configuration values to the standard logger
607− logger.SetStandardEnabled(o.StandardLogging)
608− logger.SetAuthEnabled(o.AuthLogging)
609− logger.SetReqEnabled(o.RequestLogging)
610− logger.SetStandardTemplate(o.StandardLoggingFormat)
611− logger.SetAuthTemplate(o.AuthLoggingFormat)
612− logger.SetReqTemplate(o.RequestLoggingFormat)
613−
614− excludePaths := make([]string, 0)
615− excludePaths = append(excludePaths, strings.Split(o.ExcludeLoggingPaths, ",")...)
616− if o.SilencePingLogging {
617− excludePaths = append(excludePaths, o.PingPath)
618− }
619−
620− logger.SetExcludePaths(excludePaths)
621−
622− if !o.LoggingLocalTime {
623− logger.SetFlags(logger.Flags() | logger.LUTC)
624− }
625−
626− return msgs
627−}
options_test.godeleted−284
@@ -1,284 +0,0 @@
1−package main
2−
3−import (
4− "crypto"
5− "fmt"
6− "net/url"
7− "strings"
8− "testing"
9− "time"
10−
11− "github.com/stretchr/testify/assert"
12−)
13−
14−func testOptions() *Options {
15− o := NewOptions()
16− o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
17− o.CookieSecret = "foobar"
18− o.ClientID = "bazquux"
19− o.ClientSecret = "xyzzyplugh"
20− o.EmailDomains = []string{"*"}
21− return o
22−}
23−
24−func errorMsg(msgs []string) string {
25− result := make([]string, 0)
26− result = append(result, "Invalid configuration:")
27− result = append(result, msgs...)
28− return strings.Join(result, "\n ")
29−}
30−
31−func TestNewOptions(t *testing.T) {
32− o := NewOptions()
33− o.EmailDomains = []string{"*"}
34− err := o.Validate()
35− assert.NotEqual(t, nil, err)
36−
37− expected := errorMsg([]string{
38− "missing setting: cookie-secret",
39− "missing setting: client-id",
40− "missing setting: client-secret"})
41− assert.Equal(t, expected, err.Error())
42−}
43−
44−func TestGoogleGroupOptions(t *testing.T) {
45− o := testOptions()
46− o.GoogleGroups = []string{"googlegroup"}
47− err := o.Validate()
48− assert.NotEqual(t, nil, err)
49−
50− expected := errorMsg([]string{
51− "missing setting: google-admin-email",
52− "missing setting: google-service-account-json"})
53− assert.Equal(t, expected, err.Error())
54−}
55−
56−func TestGoogleGroupInvalidFile(t *testing.T) {
57− o := testOptions()
58− o.GoogleGroups = []string{"test_group"}
59− o.GoogleAdminEmail = "admin@example.com"
60− o.GoogleServiceAccountJSON = "file_doesnt_exist.json"
61− err := o.Validate()
62− assert.NotEqual(t, nil, err)
63−
64− expected := errorMsg([]string{
65− "invalid Google credentials file: file_doesnt_exist.json",
66− })
67− assert.Equal(t, expected, err.Error())
68−}
69−
70−func TestInitializedOptions(t *testing.T) {
71− o := testOptions()
72− assert.Equal(t, nil, o.Validate())
73−}
74−
75−// Note that it's not worth testing nonparseable URLs, since url.Parse()
76−// seems to parse damn near anything.
77−func TestRedirectURL(t *testing.T) {
78− o := testOptions()
79− o.RedirectURL = "https://myhost.com/oauth2/callback"
80− assert.Equal(t, nil, o.Validate())
81− expected := &url.URL{
82− Scheme: "https", Host: "myhost.com", Path: "/oauth2/callback"}
83− assert.Equal(t, expected, o.redirectURL)
84−}
85−
86−func TestProxyTimeOut(t *testing.T) {
87− o := testOptions()
88− o.ProxyTimeOut = 40
89− assert.Equal(t, nil, o.Validate())
90− expected := 40
91− assert.Equal(t, expected, o.ProxyTimeOut)
92−}
93−
94−func TestProxyURLs(t *testing.T) {
95− o := testOptions()
96− o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
97− assert.Equal(t, nil, o.Validate())
98− expected := []*url.URL{
99− {Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
100− // note the '/' was added
101− {Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
102− }
103− assert.Equal(t, expected, o.proxyURLs)
104−}
105−
106−func TestProxyURLsError(t *testing.T) {
107− o := testOptions()
108− o.Upstreams = append(o.Upstreams, "127.0.0.1:8081")
109− err := o.Validate()
110− assert.NotEqual(t, nil, err)
111−
112− expected := errorMsg([]string{
113− "error parsing upstream: parse 127.0.0.1:8081: " +
114− "first path segment in URL cannot contain colon"})
115− assert.Equal(t, expected, err.Error())
116−}
117−
118−func TestCompiledRegex(t *testing.T) {
119− o := testOptions()
120− regexps := []string{"/foo/.*", "/ba[rz]/quux"}
121− o.SkipAuthRegex = regexps
122− assert.Equal(t, nil, o.Validate())
123− actual := make([]string, 0)
124− for _, regex := range o.CompiledRegex {
125− actual = append(actual, regex.String())
126− }
127− assert.Equal(t, regexps, actual)
128−}
129−
130−func TestCompiledRegexError(t *testing.T) {
131− o := testOptions()
132− o.SkipAuthRegex = []string{"(foobaz", "barquux)"}
133− err := o.Validate()
134− assert.NotEqual(t, nil, err)
135−
136− expected := errorMsg([]string{
137− "error compiling regex=\"(foobaz\" error parsing regexp: " +
138− "missing closing ): `(foobaz`",
139− "error compiling regex=\"barquux)\" error parsing regexp: " +
140− "unexpected ): `barquux)`"})
141− assert.Equal(t, expected, err.Error())
142−
143− o.SkipAuthRegex = []string{"foobaz", "barquux)"}
144− err = o.Validate()
145− assert.NotEqual(t, nil, err)
146−
147− expected = errorMsg([]string{
148− "error compiling regex=\"barquux)\" error parsing regexp: " +
149− "unexpected ): `barquux)`"})
150− assert.Equal(t, expected, err.Error())
151−}
152−
153−func TestDefaultProviderApiSettings(t *testing.T) {
154− o := testOptions()
155− assert.Equal(t, nil, o.Validate())
156− p := o.provider.Data()
157− assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
158− p.LoginURL.String())
159− assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
160− p.RedeemURL.String())
161− assert.Equal(t, "", p.ProfileURL.String())
162− assert.Equal(t, "profile email", p.Scope)
163−}
164−
165−func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
166− o := testOptions()
167− assert.Equal(t, nil, o.Validate())
168−
169− assert.Equal(t, false, o.PassAccessToken)
170− o.PassAccessToken = true
171− o.CookieSecret = "cookie of invalid length-"
172− assert.NotEqual(t, nil, o.Validate())
173−
174− o.PassAccessToken = false
175− o.CookieRefresh = time.Duration(24) * time.Hour
176− assert.NotEqual(t, nil, o.Validate())
177−
178− o.CookieSecret = "16 bytes AES-128"
179− assert.Equal(t, nil, o.Validate())
180−
181− o.CookieSecret = "24 byte secret AES-192--"
182− assert.Equal(t, nil, o.Validate())
183−
184− o.CookieSecret = "32 byte secret for AES-256------"
185− assert.Equal(t, nil, o.Validate())
186−}
187−
188−func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
189− o := testOptions()
190− assert.Equal(t, nil, o.Validate())
191−
192− o.CookieSecret = "0123456789abcdefabcd"
193− o.CookieRefresh = o.CookieExpire
194− assert.NotEqual(t, nil, o.Validate())
195−
196− o.CookieRefresh -= time.Duration(1)
197− assert.Equal(t, nil, o.Validate())
198−}
199−
200−func TestBase64CookieSecret(t *testing.T) {
201− o := testOptions()
202− assert.Equal(t, nil, o.Validate())
203−
204− // 32 byte, base64 (urlsafe) encoded key
205− o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
206− assert.Equal(t, nil, o.Validate())
207−
208− // 32 byte, base64 (urlsafe) encoded key, w/o padding
209− o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
210− assert.Equal(t, nil, o.Validate())
211−
212− // 24 byte, base64 (urlsafe) encoded key
213− o.CookieSecret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
214− assert.Equal(t, nil, o.Validate())
215−
216− // 16 byte, base64 (urlsafe) encoded key
217− o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA=="
218− assert.Equal(t, nil, o.Validate())
219−
220− // 16 byte, base64 (urlsafe) encoded key, w/o padding
221− o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA"
222− assert.Equal(t, nil, o.Validate())
223−}
224−
225−func TestValidateSignatureKey(t *testing.T) {
226− o := testOptions()
227− o.SignatureKey = "sha1:secret"
228− assert.Equal(t, nil, o.Validate())
229− assert.Equal(t, o.signatureData.hash, crypto.SHA1)
230− assert.Equal(t, o.signatureData.key, "secret")
231−}
232−
233−func TestValidateSignatureKeyInvalidSpec(t *testing.T) {
234− o := testOptions()
235− o.SignatureKey = "invalid spec"
236− err := o.Validate()
237− assert.Equal(t, err.Error(), "Invalid configuration:\n"+
238− " invalid signature hash:key spec: "+o.SignatureKey)
239−}
240−
241−func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
242− o := testOptions()
243− o.SignatureKey = "unsupported:default secret"
244− err := o.Validate()
245− assert.Equal(t, err.Error(), "Invalid configuration:\n"+
246− " unsupported signature hash algorithm: "+o.SignatureKey)
247−}
248−
249−func TestValidateCookie(t *testing.T) {
250− o := testOptions()
251− o.CookieName = "_valid_cookie_name"
252− assert.Equal(t, nil, o.Validate())
253−}
254−
255−func TestValidateCookieBadName(t *testing.T) {
256− o := testOptions()
257− o.CookieName = "_bad_cookie_name{}"
258− err := o.Validate()
259− assert.Equal(t, err.Error(), "Invalid configuration:\n"+
260− fmt.Sprintf(" invalid cookie name: %q", o.CookieName))
261−}
262−
263−func TestSkipOIDCDiscovery(t *testing.T) {
264− o := testOptions()
265− o.Provider = "oidc"
266− o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
267− o.SkipOIDCDiscovery = true
268−
269− err := o.Validate()
270− assert.Equal(t, "Invalid configuration:\n"+
271− fmt.Sprintf(" missing setting: login-url\n missing setting: redeem-url\n missing setting: oidc-jwks-url"), err.Error())
272−
273− o.LoginURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in"
274− o.RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in"
275− o.OIDCJwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
276−
277− assert.Equal(t, nil, o.Validate())
278−}
279−
280−func TestGCPHealthcheck(t *testing.T) {
281− o := testOptions()
282− o.GCPHealthChecks = true
283− assert.Equal(t, nil, o.Validate())
284−}
pkg/apis/options/options.go+16−14
@@ -23,20 +23,21 @@ type SignatureData struct {
2323// Options holds Configuration Options that can be set by Command Line Flag,
2424// or Config File
2525type Options struct {
26− ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix"`
27− PingPath string `flag:"ping-path" cfg:"ping_path"`
28− ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
29− HTTPAddress string `flag:"http-address" cfg:"http_address"`
30− HTTPSAddress string `flag:"https-address" cfg:"https_address"`
31− ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
32− RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header"`
33− ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
34− RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
35− ClientID string `flag:"client-id" cfg:"client_id"`
36− ClientSecret string `flag:"client-secret" cfg:"client_secret"`
37− ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file"`
38− TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file"`
39− TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file"`
26+ ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix"`
27+ PingPath string `flag:"ping-path" cfg:"ping_path"`
28+ ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
29+ HTTPAddress string `flag:"http-address" cfg:"http_address"`
30+ HTTPSAddress string `flag:"https-address" cfg:"https_address"`
31+ ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
32+ RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header"`
33+ ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
34+ RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
35+ ClientID string `flag:"client-id" cfg:"client_id"`
36+ ClientSecret string `flag:"client-secret" cfg:"client_secret"`
37+ ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file"`
38+ TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file"`
39+ TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file"`
40+ ProxyTimeOut time.Duration `flag:"proxy-time-out" cfg:"proxy_time_out" env:"OAUTH2_PROXY_PROXY_TIME_OUT"`
4041
4142 AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
4243 KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group"`
@@ -166,6 +167,7 @@ func NewOptions() *Options {
166167 ProxyWebSockets: true,
167168 HTTPAddress: "127.0.0.1:4180",
168169 HTTPSAddress: ":443",
170+ ProxyTimeOut: 30, //TIME_OUT in seconds
169171 RealClientIPHeader: "X-Real-IP",
170172 ForceHTTPS: false,
171173 DisplayHtpasswdForm: true,
pkg/validation/options_test.go+8
@@ -140,6 +140,14 @@ func TestRedirectURL(t *testing.T) {
140140 assert.Equal(t, expected, o.GetRedirectURL())
141141}
142142
143+func TestProxyTimeOut(t *testing.T) {
144+ o := testOptions()
145+ o.ProxyTimeOut = 40
146+ assert.Equal(t, nil, o.Validate())
147+ expected := 40
148+ assert.Equal(t, expected, o.ProxyTimeOut)
149+}
150+
143151func TestProxyURLs(t *testing.T) {
144152 o := testOptions()
145153 o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")