diff --git a/README.md b/README.md index 87a2f1b..f2e1828 100644 --- a/README.md +++ b/README.md @@ -65,20 +65,21 @@ usage: kuberos [] [] [] [ Provides OIDC authentication configuration for kubectl. Flags: - --help Show context-sensitive help (also try --help-long and - --help-man). + --help Show context-sensitive help (also try --help-long and --help-man). --listen=":10003" Address at which to expose HTTP webhook. -d, --debug Run with debug logging. + --extra-scopes=EXTRA-SCOPES ... + List of additional scopes to provide in token. --shutdown-grace-period=1m - Wait this long for sessions to end before shutting - down. + Wait this long for sessions to end before shutting down. + --email-domain=EMAIL-DOMAIN + The email domain to restrict access to. Args: [] OpenID Connect issuer URL. [] OAuth2 client ID. [] File containing OAuth2 client secret. - [] A kubecfg file containing clusters to populate with a - user and contexts. + [] A kubecfg file containing clusters to populate with a user and contexts. ``` The partial `kubeconfig` template should contain only cluster entries and diff --git a/cmd/kuberos/kuberos.go b/cmd/kuberos/kuberos.go index 7ed2568..1ae6dc0 100644 --- a/cmd/kuberos/kuberos.go +++ b/cmd/kuberos/kuberos.go @@ -43,10 +43,11 @@ func logRequests(h http.Handler, log *zap.Logger) http.Handler { func main() { var ( - app = kingpin.New(filepath.Base(os.Args[0]), "Provides OIDC authentication configuration for kubectl.").DefaultEnvars() - listen = app.Flag("listen", "Address at which to expose HTTP webhook.").Default(":10003").String() - debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool() - scopes = app.Flag("scopes", "List of additional scopes to provide in token.").Default("profile", "email").Strings() + app = kingpin.New(filepath.Base(os.Args[0]), "Provides OIDC authentication configuration for kubectl.").DefaultEnvars() + listen = app.Flag("listen", "Address at which to expose HTTP webhook.").Default(":10003").String() + debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool() + scopes = app.Flag("scopes", "List of additional scopes to provide in token.").Default("profile", "email").Strings() + emailDomain = app.Flag("email-domain", "The eamil domain to restrict access to.").String() grace = app.Flag("shutdown-grace-period", "Wait this long for sessions to end before shutting down.").Default("1m").Duration() shutdownEndpoint = app.Flag("shutdown-endpoint", "Insecure HTTP endpoint path (e.g., /quitquitquit) that responds to a GET to shut down kuberos.").String() @@ -81,7 +82,7 @@ func main() { Endpoint: provider.Endpoint(), Scopes: sr.Get(), } - e, err := extractor.NewOIDC(provider.Verifier(&oidc.Config{ClientID: *clientID}), extractor.Logger(log)) + e, err := extractor.NewOIDC(provider.Verifier(&oidc.Config{ClientID: *clientID}), extractor.Logger(log), extractor.EmailDomain(*emailDomain)) kingpin.FatalIfError(err, "cannot setup OIDC extractor") h, err := kuberos.NewHandlers(cfg, e, kuberos.Logger(log)) diff --git a/extractor/oidc.go b/extractor/oidc.go index 303ce72..4443e67 100644 --- a/extractor/oidc.go +++ b/extractor/oidc.go @@ -3,6 +3,7 @@ package extractor import ( "context" "net/http" + "strings" "go.uber.org/zap" "golang.org/x/oauth2" @@ -34,9 +35,10 @@ type OIDC interface { } type oidcExtractor struct { - log *zap.Logger - v *oidc.IDTokenVerifier - h *http.Client + log *zap.Logger + v *oidc.IDTokenVerifier + h *http.Client + emailDomain string } // An Option represents a OIDC extractor option. @@ -58,6 +60,14 @@ func Logger(l *zap.Logger) Option { } } +// EmailDomain adds the given email domain to an OIDC extractor +func EmailDomain(domain string) Option { + return func(o *oidcExtractor) error { + o.emailDomain = domain + return nil + } +} + // NewOIDC creates a new OIDC extractor. func NewOIDC(v *oidc.IDTokenVerifier, oo ...Option) (OIDC, error) { l, err := zap.NewProduction() @@ -104,5 +114,10 @@ func (o *oidcExtractor) Process(ctx context.Context, cfg *oauth2.Config, code st if err := idt.Claims(params); err != nil { return nil, errors.Wrap(err, "cannot extract claims from ID token") } + + if o.emailDomain != "" && !strings.HasSuffix(params.Username, "@"+o.emailDomain) { + return nil, errors.New("Invalid email domain, expecting " + o.emailDomain) + } + return params, nil }