diff --git a/.gitignore b/.gitignore index 12c12704..ff99a82e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ *.db automated-release-build etc/dev.yml -etc/dev-metrics.yml +etc/dev-frontend.yml # Dependencies /node_modules/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c472bc4..996b1395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v0.4.7 + +FEATURE: OAuth authentication with the ability to restrict authenticated users to specified domains for `zrok share public`. Supports both Google and GitHub authentication in this version. More authentication providers, and extensibility to come in future `zrok` releases. See the OAuth configuration guide at `docs/guides/self-hosting/oauth/configuring-oauth.md` for details (https://github.com/openziti/zrok/issues/45, https://github.com/openziti/zrok/issues/404) + +CHANGE: `--basic-auth` realm now presented as the share token rather than as `zrok` in `publicProxy` frontend implementation + # v0.4.6 FEATURE: New `--backend-mode caddy`, which pre-processes a `Caddyfile` allowing a `bind` statement to work like this: `bind {{ .ZrokBindAddress }}`. Allows development of complicated API gateways and multi-backend shares, while maintaining the simple, ephemeral sharing model provided by `zrok` (https://github.com/openziti/zrok/issues/391) diff --git a/cmd/zrok/reserve.go b/cmd/zrok/reserve.go index a4d552fc..a5f0173c 100644 --- a/cmd/zrok/reserve.go +++ b/cmd/zrok/reserve.go @@ -83,7 +83,7 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) { req := &sdk.ShareRequest{ BackendMode: sdk.BackendMode(cmd.backendMode), ShareMode: shareMode, - Auth: cmd.basicAuth, + BasicAuth: cmd.basicAuth, Target: target, } if shareMode == sdk.PublicShareMode { diff --git a/cmd/zrok/sharePrivate.go b/cmd/zrok/sharePrivate.go index ae10692e..3c6de78b 100644 --- a/cmd/zrok/sharePrivate.go +++ b/cmd/zrok/sharePrivate.go @@ -99,7 +99,7 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) { req := &sdk.ShareRequest{ BackendMode: sdk.BackendMode(cmd.backendMode), ShareMode: sdk.PrivateShareMode, - Auth: cmd.basicAuth, + BasicAuth: cmd.basicAuth, Target: target, } shr, err := sdk.CreateShare(root, req) diff --git a/cmd/zrok/sharePublic.go b/cmd/zrok/sharePublic.go index 00f4cd55..b0181be3 100644 --- a/cmd/zrok/sharePublic.go +++ b/cmd/zrok/sharePublic.go @@ -15,6 +15,7 @@ import ( "os/signal" "strings" "syscall" + "time" ) func init() { @@ -22,12 +23,15 @@ func init() { } type sharePublicCommand struct { - basicAuth []string - frontendSelection []string - backendMode string - headless bool - insecure bool - cmd *cobra.Command + basicAuth []string + frontendSelection []string + backendMode string + headless bool + insecure bool + oauthProvider string + oauthEmailDomains []string + oauthCheckInterval time.Duration + cmd *cobra.Command } func newSharePublicCommand() *sharePublicCommand { @@ -37,11 +41,17 @@ func newSharePublicCommand() *sharePublicCommand { Args: cobra.ExactArgs(1), } command := &sharePublicCommand{cmd: cmd} - cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share") cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy}") cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless") cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for ") + + cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") + cmd.Flags().StringVar(&command.oauthProvider, "oauth-provider", "", "Enable OAuth provider [google, github]") + cmd.Flags().StringArrayVar(&command.oauthEmailDomains, "oauth-email-domains", []string{}, "Allow only these email domains to authenticate via OAuth") + cmd.Flags().DurationVar(&command.oauthCheckInterval, "oauth-check-interval", 3*time.Hour, "Maximum lifetime for OAuth authentication; reauthenticate after expiry") + cmd.MarkFlagsMutuallyExclusive("basic-auth", "oauth-provider") + cmd.Run = command.run return command } @@ -95,9 +105,14 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { BackendMode: sdk.BackendMode(cmd.backendMode), ShareMode: sdk.PublicShareMode, Frontends: cmd.frontendSelection, - Auth: cmd.basicAuth, + BasicAuth: cmd.basicAuth, Target: target, } + if cmd.oauthProvider != "" { + req.OauthProvider = cmd.oauthProvider + req.OauthEmailDomains = cmd.oauthEmailDomains + req.OauthAuthorizationCheckInterval = cmd.oauthCheckInterval + } shr, err := sdk.CreateShare(root, req) if err != nil { if !panicInstead { diff --git a/controller/controller.go b/controller/controller.go index 7d509dc8..5c41ab4e 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -20,10 +20,12 @@ import ( "github.com/pkg/errors" ) -var cfg *config.Config -var str *store.Store -var idb influxdb2.Client -var limitsAgent *limits.Agent +var ( + cfg *config.Config + str *store.Store + idb influxdb2.Client + limitsAgent *limits.Agent +) func Run(inCfg *config.Config) error { cfg = inCfg diff --git a/controller/sharePrivate.go b/controller/sharePrivate.go index 96830cdb..7a06a797 100644 --- a/controller/sharePrivate.go +++ b/controller/sharePrivate.go @@ -14,11 +14,24 @@ func newPrivateResourceAllocator() *privateResourceAllocator { } func (a *privateResourceAllocator) allocate(envZId, shrToken string, params share.ShareParams, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, frontendEndpoints []string, err error) { - var authUsers []*sdk.AuthUser + var authUsers []*sdk.AuthUserConfig for _, authUser := range params.Body.AuthUsers { - authUsers = append(authUsers, &sdk.AuthUser{authUser.Username, authUser.Password}) + authUsers = append(authUsers, &sdk.AuthUserConfig{Username: authUser.Username, Password: authUser.Password}) } - cfgZId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, edge) + authScheme, err := sdk.ParseAuthScheme(params.Body.AuthScheme) + if err != nil { + return "", nil, err + } + options := &zrokEdgeSdk.FrontendOptions{ + AuthScheme: authScheme, + BasicAuthUsers: authUsers, + Oauth: &sdk.OauthConfig{ + Provider: params.Body.OauthProvider, + EmailDomains: params.Body.OauthEmailDomains, + AuthorizationCheckInterval: params.Body.OauthAuthorizationCheckInterval, + }, + } + cfgZId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, options, edge) if err != nil { return "", nil, err } diff --git a/controller/sharePublic.go b/controller/sharePublic.go index b7522b4c..93a73ac8 100644 --- a/controller/sharePublic.go +++ b/controller/sharePublic.go @@ -14,11 +14,24 @@ func newPublicResourceAllocator() *publicResourceAllocator { } func (a *publicResourceAllocator) allocate(envZId, shrToken string, frontendZIds, frontendTemplates []string, params share.ShareParams, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, frontendEndpoints []string, err error) { - var authUsers []*sdk.AuthUser + var authUsers []*sdk.AuthUserConfig for _, authUser := range params.Body.AuthUsers { - authUsers = append(authUsers, &sdk.AuthUser{authUser.Username, authUser.Password}) + authUsers = append(authUsers, &sdk.AuthUserConfig{Username: authUser.Username, Password: authUser.Password}) } - cfgId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, edge) + authScheme, err := sdk.ParseAuthScheme(params.Body.AuthScheme) + if err != nil { + return "", nil, err + } + options := &zrokEdgeSdk.FrontendOptions{ + AuthScheme: authScheme, + BasicAuthUsers: authUsers, + Oauth: &sdk.OauthConfig{ + Provider: params.Body.OauthProvider, + EmailDomains: params.Body.OauthEmailDomains, + AuthorizationCheckInterval: params.Body.OauthAuthorizationCheckInterval, + }, + } + cfgId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, options, edge) if err != nil { return "", nil, err } diff --git a/controller/zrokEdgeSdk/config.go b/controller/zrokEdgeSdk/config.go index b0b57509..8c31aeb8 100644 --- a/controller/zrokEdgeSdk/config.go +++ b/controller/zrokEdgeSdk/config.go @@ -11,18 +11,27 @@ import ( "time" ) -func CreateConfig(cfgTypeZId, envZId, shrToken string, authSchemeStr string, authUsers []*sdk.AuthUser, edge *rest_management_api_client.ZitiEdgeManagement) (cfgZId string, err error) { - authScheme, err := sdk.ParseAuthScheme(authSchemeStr) - if err != nil { - return "", err - } - cfg := &sdk.ProxyConfig{ - AuthScheme: authScheme, +type FrontendOptions struct { + AuthScheme sdk.AuthScheme + BasicAuthUsers []*sdk.AuthUserConfig + Oauth *sdk.OauthConfig +} + +func CreateConfig(cfgTypeZId, envZId, shrToken string, options *FrontendOptions, edge *rest_management_api_client.ZitiEdgeManagement) (cfgZId string, err error) { + cfg := &sdk.FrontendConfig{ + AuthScheme: options.AuthScheme, } if cfg.AuthScheme == sdk.Basic { - cfg.BasicAuth = &sdk.BasicAuth{} - for _, authUser := range authUsers { - cfg.BasicAuth.Users = append(cfg.BasicAuth.Users, &sdk.AuthUser{Username: authUser.Username, Password: authUser.Password}) + cfg.BasicAuth = &sdk.BasicAuthConfig{} + for _, authUser := range options.BasicAuthUsers { + cfg.BasicAuth.Users = append(cfg.BasicAuth.Users, &sdk.AuthUserConfig{Username: authUser.Username, Password: authUser.Password}) + } + } + if cfg.AuthScheme == sdk.Oauth && options.Oauth != nil { + cfg.OauthAuth = &sdk.OauthConfig{ + Provider: options.Oauth.Provider, + EmailDomains: options.Oauth.EmailDomains, + AuthorizationCheckInterval: options.Oauth.AuthorizationCheckInterval, } } cfgCrt := &rest_model.ConfigCreate{ diff --git a/docs/guides/self-hosting/oauth/_category_.json b/docs/guides/self-hosting/oauth/_category_.json new file mode 100644 index 00000000..10219e16 --- /dev/null +++ b/docs/guides/self-hosting/oauth/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "OAuth", + "position": 70, + "link": { + "type": "generated-index" + } +} diff --git a/docs/guides/self-hosting/oauth/configuring-oauth.md b/docs/guides/self-hosting/oauth/configuring-oauth.md new file mode 100644 index 00000000..6eaa1a11 --- /dev/null +++ b/docs/guides/self-hosting/oauth/configuring-oauth.md @@ -0,0 +1,154 @@ +# OAuth Public Frontend Configuration + +As of `v0.4.7`, `zrok` includes OAuth integration for both Google and GitHub for `zrok access public` public frontends. + +This integration allows you to create public shares and request that the public frontend authenticate your users against either the Google or GitHub OAuth endpoints (using the user's Google or GitHub accounts). Additionally, you can restrict the email address domain associated with the count to a list of domains that you provide when you create the share. + +This is a first step towards a more comprehensive portfolio of user authentication strategies in future `zrok` releases. + +## Planning for the OAuth Frontend + +The current implementation of the OAuth public frontend uses a HTTP listener to handle redirects from OAuth providers. You'll need to configure a DNS name and a port for this listener that is accessible by your end users. We'll refer to this listener as the "OAuth frontend" in this guide. + +We'll use the public DNS address of the OAuth frontend when creating the Google and GitHub OAuth clients below. This address is typically configured into these clients as the "redirect URL" where these clients will send the authenticated users after authentication. + +The `zrok` OAuth frontend will capture the successful authentication and forward the user back to their original destination. + +## Configuring a Google OAuth Client ID + +### OAuth Content Screen + +Before you can configure an OAuth Client ID in Google Cloud, you have to configure the "OAuth content screen". + +In the Google Cloud console, navigate to: `APIs & Services > Credentials > OAuth content screen` + +![](images/google_oauth_content_screen_2.png) + +Here you can give your `zrok` public frontend an identity and branding to match your deployment. + +![](images/google_oauth_content_screen_3.png) + +Describe what domains are authorized to access your public frontend and establish contact information. + +![](images/google_oauth_content_screen_4.png) + +Add a non-sensitive scope for `../auth/userinfo.email`. This is important as it allows the `zrok` OAuth frontend to receive the email address of the authenticated user. + +![](images/google_oauth_content_screen_5.png) + +![](images/google_oauth_content_screen_6.png) + +Now your OAuth content screen is configured. + +### Create the OAuth 2.0 Client ID + +Next we create the OAuth Client ID for your public frontend. + +In the Google Cloud Console, navigate to: `APIs & Services > Credentials > + Create Credentials` + +![](images/google_create_credentials_1.png) + +Select `OAuth client ID` from the `+ Create Credentials` dropdown. + +![](images/google_create_credentials_2.png) + +Application type is `Web Application`. + +![](images/google_create_credentials_3.png) + +The most important bit here is the "Authorized redirect URIs". You're going to want to put a URL here that matches the `zrok` OAuth frontend address that you configured at the start of this guide, but at the end of the URL you're going to append `/google/oauth` to the URL. + +![](images/google_create_credentials_4.png) + +Save the client ID and the client secret. You'll configure these into your `frontend.yml`. + +With this your Google OAuth client should be configured and ready. + +## Configuring a GitHub Client ID + +Register a new OAuth application through the GitHub settings for the account that owns the application. + +Navigate to:`Settings > Developer Settings > OAuth Apps > Register a new application` + +![](images/github_create_oauth_application_1.png) + +![](images/github_create_oauth_application_2.png) + +The "Authorized callback URL" should be configured to match the OAuth frontend address you configured at the start of this guide, with `/github/oauth` appended to the end. + +![](images/github_create_oauth_application_3.png) + +Create a new client secret. + +![](images/github_create_oauth_application_4.png) + +Save the client ID and the client secret. You'll configure these into your `frontend.yml`. + +## Configuring your Public Frontend + +The public frontend configuration includes a new `oauth` section: + +```yaml +oauth: + redirect_host: oauth.zrok.io + redirect_port: 28080 + redirect_http_only: false + hash_key: "" + providers: + - name: google + client_id: + client_secret: + - name: github + client_id: + client_secret: + +``` + +The `redirect_host` and `redirect_port` value should correspond with the DNS hostname and port configured as your OAuth frontend. + +The `redirect_http_only` is useful in development environments where your OAuth frontend is not running behind an HTTPS reverse proxy. Should not be enabled in production environments! + +`hash_key` is a unique string for your installation that is used to secure the authentication payloads for your public frontend. + +`providers` is a list of configured providers for this public frontend. The current implementation supports `google` and `github` as options. + +Both the `google` and `github` providers accept a `client_id` and `client_secret` parameter. These values are provided when you configure the OAuth clients at Google or GitHub. + +## Enabling OAuth on a Public Share + +With your public frontend configured to support OAuth, you can test this by creating a public share. There are new command line options to support this: + +``` +$ zrok share public +Error: accepts 1 arg(s), received 0 +Usage: + zrok share public [flags] + +Flags: + -b, --backend-mode string The backend mode {proxy, web, caddy} (default "proxy") + --basic-auth stringArray Basic authentication users (,...) + --frontends stringArray Selected frontends to use for the share (default [public]) + --headless Disable TUI and run headless + -h, --help help for public + --insecure Enable insecure TLS certificate validation for + --oauth-check-interval duration Maximum lifetime for OAuth authentication; reauthenticate after expiry (default 3h0m0s) + --oauth-email-domains stringArray Allow only these email domains to authenticate via OAuth + --oauth-provider string Enable OAuth provider [google, github] + +Global Flags: + -p, --panic Panic instead of showing pretty errors + -v, --verbose Enable verbose logging +``` + +The `--oauth-provider` flag enables OAuth for the share using the specified provider. + +The `--oauth-email-domains` flag accepts a comma-separated list of authenticated email address domains that are allowed to access the share. + +The `--oauth-check-interval` flag specifies how frequently the authentication must be checked. + +An example public share: + +``` +$ zrok share public --backend-mode web --oauth-provider github --oauth-email-domains zrok.io ~/public +``` + diff --git a/docs/guides/self-hosting/oauth/images/github_create_oauth_application_1.png b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_1.png new file mode 100755 index 00000000..3db1a56b Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_1.png differ diff --git a/docs/guides/self-hosting/oauth/images/github_create_oauth_application_2.png b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_2.png new file mode 100755 index 00000000..21301615 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_2.png differ diff --git a/docs/guides/self-hosting/oauth/images/github_create_oauth_application_3.png b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_3.png new file mode 100755 index 00000000..ec9066d0 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_3.png differ diff --git a/docs/guides/self-hosting/oauth/images/github_create_oauth_application_4.png b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_4.png new file mode 100755 index 00000000..9a423125 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_4.png differ diff --git a/docs/guides/self-hosting/oauth/images/github_create_oauth_application_5.png b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_5.png new file mode 100755 index 00000000..29f20011 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/github_create_oauth_application_5.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_create_credentials_1.png b/docs/guides/self-hosting/oauth/images/google_create_credentials_1.png new file mode 100755 index 00000000..29acfc01 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_create_credentials_1.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_create_credentials_2.png b/docs/guides/self-hosting/oauth/images/google_create_credentials_2.png new file mode 100755 index 00000000..5f2eec04 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_create_credentials_2.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_create_credentials_3.png b/docs/guides/self-hosting/oauth/images/google_create_credentials_3.png new file mode 100755 index 00000000..c6de679c Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_create_credentials_3.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_create_credentials_4.png b/docs/guides/self-hosting/oauth/images/google_create_credentials_4.png new file mode 100755 index 00000000..b946be96 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_create_credentials_4.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_1.png b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_1.png new file mode 100755 index 00000000..b0fb0101 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_1.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_2.png b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_2.png new file mode 100755 index 00000000..0608b599 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_2.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_3.png b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_3.png new file mode 100755 index 00000000..cac72036 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_3.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_4.png b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_4.png new file mode 100755 index 00000000..3299d21a Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_4.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_5.png b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_5.png new file mode 100755 index 00000000..f2975d8c Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_5.png differ diff --git a/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_6.png b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_6.png new file mode 100755 index 00000000..8b993442 Binary files /dev/null and b/docs/guides/self-hosting/oauth/images/google_oauth_content_screen_6.png differ diff --git a/endpoints/publicProxy/config.go b/endpoints/publicProxy/config.go index cb939e7e..ed91c56b 100644 --- a/endpoints/publicProxy/config.go +++ b/endpoints/publicProxy/config.go @@ -1,14 +1,43 @@ package publicProxy import ( + "context" + "fmt" "github.com/michaelquigley/cf" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + zhttp "github.com/zitadel/oidc/v2/pkg/http" + "strings" ) type Config struct { Identity string Address string HostMatch string + Oauth *OauthConfig +} + +type OauthConfig struct { + RedirectHost string + RedirectPort int + RedirectHttpOnly bool + HashKey string `cf:"+secret"` + Providers []*OauthProviderConfig +} + +func (oc *OauthConfig) GetProvider(name string) *OauthProviderConfig { + for _, provider := range oc.Providers { + if provider.Name == name { + return provider + } + } + return nil +} + +type OauthProviderConfig struct { + Name string + ClientId string + ClientSecret string `cf:"+secret"` } func DefaultConfig() *Config { @@ -24,3 +53,18 @@ func (c *Config) Load(path string) error { } return nil } + +func configureOauthHandlers(ctx context.Context, cfg *Config, tls bool) error { + if cfg.Oauth == nil { + logrus.Info("no oauth configuration; skipping oauth handler startup") + return nil + } + if err := configureGoogleOauth(cfg.Oauth, tls); err != nil { + return err + } + if err := configureGithubOauth(cfg.Oauth, tls); err != nil { + return err + } + zhttp.StartServer(ctx, fmt.Sprintf("%s:%d", strings.Split(cfg.Address, ":")[0], cfg.Oauth.RedirectPort)) + return nil +} diff --git a/endpoints/publicProxy/github.go b/endpoints/publicProxy/github.go new file mode 100644 index 00000000..ea97bc02 --- /dev/null +++ b/endpoints/publicProxy/github.go @@ -0,0 +1,186 @@ +package publicProxy + +import ( + "crypto/md5" + "encoding/json" + "errors" + "fmt" + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "github.com/zitadel/oidc/v2/pkg/client/rp" + zhttp "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" + "golang.org/x/oauth2" + githubOAuth "golang.org/x/oauth2/github" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +func configureGithubOauth(cfg *OauthConfig, tls bool) error { + scheme := "http" + if tls { + scheme = "https" + } + + providerCfg := cfg.GetProvider("github") + if providerCfg == nil { + logrus.Info("unable to find provider config for github; skipping") + return nil + } + clientID := providerCfg.ClientId + callbackPath := "/github/oauth" + redirectUrl := fmt.Sprintf("%s://%s", scheme, cfg.RedirectHost) + rpConfig := &oauth2.Config{ + ClientID: clientID, + ClientSecret: providerCfg.ClientSecret, + RedirectURL: fmt.Sprintf("%v:%v%v", redirectUrl, cfg.RedirectPort, callbackPath), + Scopes: []string{"user:email"}, + Endpoint: githubOAuth.Endpoint, + } + + hash := md5.New() + n, err := hash.Write([]byte(cfg.HashKey)) + if err != nil { + return err + } + if n != len(cfg.HashKey) { + return errors.New("short hash") + } + key := hash.Sum(nil) + + u, err := url.Parse(redirectUrl) + if err != nil { + logrus.Errorf("unable to parse redirect url: %v", err) + return err + } + parts := strings.Split(u.Hostname(), ".") + domain := parts[len(parts)-2] + "." + parts[len(parts)-1] + + cookieHandler := zhttp.NewCookieHandler(key, key, zhttp.WithUnsecure(), zhttp.WithDomain(domain)) + + options := []rp.Option{ + rp.WithCookieHandler(cookieHandler), + rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)), + //rp.WithPKCE(cookieHandler), //Github currently doesn't support pkce. Update when that changes. + } + + relyingParty, err := rp.NewRelyingPartyOAuth(rpConfig, options...) + if err != nil { + return err + } + + type IntermediateJWT struct { + State string `json:"state"` + Host string `json:"host"` + AuthorizationCheckInterval string `json:"authorizationCheckInterval"` + jwt.RegisteredClaims + } + + type githubUserResp struct { + Email string + Primary bool + Verified bool + Visibility string + } + + authHandlerWithQueryState := func(party rp.RelyingParty) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + host, err := url.QueryUnescape(r.URL.Query().Get("targethost")) + if err != nil { + logrus.Errorf("Unable to unescape target host: %v", err) + } + rp.AuthURLHandler(func() string { + id := uuid.New().String() + t := jwt.NewWithClaims(jwt.SigningMethodHS256, IntermediateJWT{ + id, + host, + r.URL.Query().Get("checkInterval"), + jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + Issuer: "zrok", + Subject: "intermediate_token", + ID: id, + }, + }) + s, err := t.SignedString(key) + if err != nil { + logrus.Errorf("Unable to sign intermediate JWT: %v", err) + } + return s + }, party, rp.WithURLParam("access_type", "offline"))(w, r) + } + } + + http.Handle("/github/login", authHandlerWithQueryState(relyingParty)) + getEmail := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty) { + parsedUrl, err := url.Parse("https://api.github.com/user/emails") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + req := &http.Request{ + Method: http.MethodGet, + URL: parsedUrl, + Header: make(http.Header), + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokens.AccessToken)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + logrus.Error("Error getting user info from github: " + err.Error() + "\n") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer func() { + _ = resp.Body.Close() + }() + response, err := io.ReadAll(resp.Body) + if err != nil { + logrus.Errorf("Error reading response body: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + var rDat []githubUserResp + err = json.Unmarshal(response, &rDat) + if err != nil { + logrus.Errorf("Error unmarshalling google oauth response: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + primaryEmail := "" + for _, email := range rDat { + if email.Primary { + primaryEmail = email.Email + break + } + } + + token, err := jwt.ParseWithClaims(state, &IntermediateJWT{}, func(t *jwt.Token) (interface{}, error) { + return key, nil + }) + if err != nil { + http.Error(w, fmt.Sprintf("After intermediate token parse: %v", err.Error()), http.StatusInternalServerError) + return + } + + authCheckInterval := 3 * time.Hour + i, err := time.ParseDuration(token.Claims.(*IntermediateJWT).AuthorizationCheckInterval) + if err != nil { + logrus.Errorf("unable to parse authorization check interval: %v. Defaulting to 3 hours", err) + } else { + authCheckInterval = i + } + + SetZrokCookie(w, domain, primaryEmail, tokens.AccessToken, "github", authCheckInterval, key) + http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound) + } + + http.Handle(callbackPath, rp.CodeExchangeHandler(getEmail, relyingParty)) + return nil +} diff --git a/endpoints/publicProxy/google.go b/endpoints/publicProxy/google.go new file mode 100644 index 00000000..23133c44 --- /dev/null +++ b/endpoints/publicProxy/google.go @@ -0,0 +1,166 @@ +package publicProxy + +import ( + "crypto/md5" + "encoding/json" + "errors" + "fmt" + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "github.com/zitadel/oidc/v2/pkg/client/rp" + zhttp "github.com/zitadel/oidc/v2/pkg/http" + "github.com/zitadel/oidc/v2/pkg/oidc" + "golang.org/x/oauth2" + googleOauth "golang.org/x/oauth2/google" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +func configureGoogleOauth(cfg *OauthConfig, tls bool) error { + scheme := "http" + if tls { + scheme = "https" + } + + providerCfg := cfg.GetProvider("google") + if providerCfg == nil { + logrus.Info("unable to find provider config for google. Skipping.") + return nil + } + + clientID := providerCfg.ClientId + callbackPath := "/google/oauth" + redirectUrl := fmt.Sprintf("%s://%s", scheme, cfg.RedirectHost) + rpConfig := &oauth2.Config{ + ClientID: clientID, + ClientSecret: providerCfg.ClientSecret, + RedirectURL: fmt.Sprintf("%v:%v%v", redirectUrl, cfg.RedirectPort, callbackPath), + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, + Endpoint: googleOauth.Endpoint, + } + + hash := md5.New() + n, err := hash.Write([]byte(cfg.HashKey)) + if err != nil { + return err + } + if n != len(cfg.HashKey) { + return errors.New("short hash") + } + key := hash.Sum(nil) + + u, err := url.Parse(redirectUrl) + if err != nil { + logrus.Errorf("unable to parse redirect url: %v", err) + return err + } + parts := strings.Split(u.Hostname(), ".") + domain := parts[len(parts)-2] + "." + parts[len(parts)-1] + + cookieHandler := zhttp.NewCookieHandler(key, key, zhttp.WithUnsecure(), zhttp.WithDomain(domain)) + + options := []rp.Option{ + rp.WithCookieHandler(cookieHandler), + rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)), + rp.WithPKCE(cookieHandler), + } + + relyingParty, err := rp.NewRelyingPartyOAuth(rpConfig, options...) + if err != nil { + return err + } + + type IntermediateJWT struct { + State string `json:"state"` + Host string `json:"host"` + AuthorizationCheckInterval string `json:"authorizationCheckInterval"` + jwt.RegisteredClaims + } + + type googleOauthEmailResp struct { + Email string + } + + authHandlerWithQueryState := func(party rp.RelyingParty) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + host, err := url.QueryUnescape(r.URL.Query().Get("targethost")) + if err != nil { + logrus.Errorf("Unable to unescape target host: %v", err) + } + rp.AuthURLHandler(func() string { + id := uuid.New().String() + t := jwt.NewWithClaims(jwt.SigningMethodHS256, IntermediateJWT{ + id, + host, + r.URL.Query().Get("checkInterval"), + jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + Issuer: "zrok", + Subject: "intermediate_token", + ID: id, + }, + }) + s, err := t.SignedString(key) + if err != nil { + logrus.Errorf("Unable to sign intermediate JWT: %v", err) + } + return s + }, party, rp.WithURLParam("access_type", "offline"))(w, r) + } + } + + http.Handle("/google/login", authHandlerWithQueryState(relyingParty)) + getEmail := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty) { + resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + url.QueryEscape(tokens.AccessToken)) + if err != nil { + logrus.Error("Error getting user info from google: " + err.Error() + "\n") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer func() { + _ = resp.Body.Close() + }() + response, err := io.ReadAll(resp.Body) + if err != nil { + logrus.Errorf("Error reading response body: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + logrus.Infof("Response from google userinfo endpoint: %s", string(response)) + rDat := googleOauthEmailResp{} + err = json.Unmarshal(response, &rDat) + if err != nil { + logrus.Errorf("Error unmarshalling google oauth response: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + token, err := jwt.ParseWithClaims(state, &IntermediateJWT{}, func(t *jwt.Token) (interface{}, error) { + return key, nil + }) + if err != nil { + http.Error(w, fmt.Sprintf("After intermediate token parse: %v", err.Error()), http.StatusInternalServerError) + return + } + + authCheckInterval := 3 * time.Hour + i, err := time.ParseDuration(token.Claims.(*IntermediateJWT).AuthorizationCheckInterval) + if err != nil { + logrus.Errorf("unable to parse authorization check interval: %v. Defaulting to 3 hours", err) + } else { + authCheckInterval = i + } + + SetZrokCookie(w, domain, rDat.Email, tokens.AccessToken, "google", authCheckInterval, key) + http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound) + } + + http.Handle(callbackPath, rp.CodeExchangeHandler(getEmail, relyingParty)) + return nil +} diff --git a/endpoints/publicProxy/http.go b/endpoints/publicProxy/http.go index 4403c4b1..c98060ff 100644 --- a/endpoints/publicProxy/http.go +++ b/endpoints/publicProxy/http.go @@ -2,11 +2,14 @@ package publicProxy import ( "context" + "crypto/md5" "fmt" + "github.com/golang-jwt/jwt/v5" "github.com/openziti/sdk-golang/ziti" "github.com/openziti/zrok/endpoints" "github.com/openziti/zrok/endpoints/publicProxy/healthUi" "github.com/openziti/zrok/endpoints/publicProxy/notFoundUi" + "github.com/openziti/zrok/endpoints/publicProxy/unauthorizedUi" "github.com/openziti/zrok/environment" "github.com/openziti/zrok/sdk" "github.com/openziti/zrok/util" @@ -17,20 +20,34 @@ import ( "net/http/httputil" "net/url" "strings" + "time" ) -type httpFrontend struct { +type HttpFrontend struct { cfg *Config zCtx ziti.Context handler http.Handler } -func NewHTTP(cfg *Config) (*httpFrontend, error) { - env, err := environment.LoadRoot() +func NewHTTP(cfg *Config) (*HttpFrontend, error) { + var key []byte + if cfg.Oauth != nil { + hash := md5.New() + n, err := hash.Write([]byte(cfg.Oauth.HashKey)) + if err != nil { + return nil, err + } + if n != len(cfg.Oauth.HashKey) { + return nil, errors.New("short hash") + } + key = hash.Sum(nil) + } + + root, err := environment.LoadRoot() if err != nil { return nil, errors.Wrap(err, "error loading environment root") } - zCfgPath, err := env.ZitiIdentityNamed(cfg.Identity) + zCfgPath, err := root.ZitiIdentityNamed(cfg.Identity) if err != nil { return nil, errors.Wrapf(err, "error getting ziti identity '%v' from environment", cfg.Identity) } @@ -52,26 +69,28 @@ func NewHTTP(cfg *Config) (*httpFrontend, error) { return nil, err } proxy.Transport = zTransport - - handler := authHandler(util.NewProxyHandler(proxy), "zrok", cfg, zCtx) - return &httpFrontend{ + if err := configureOauthHandlers(context.Background(), cfg, false); err != nil { + return nil, err + } + handler := authHandler(util.NewProxyHandler(proxy), cfg, key, zCtx) + return &HttpFrontend{ cfg: cfg, zCtx: zCtx, handler: handler, }, nil } -func (self *httpFrontend) Run() error { - return http.ListenAndServe(self.cfg.Address, self.handler) +func (f *HttpFrontend) Run() error { + return http.ListenAndServe(f.cfg.Address, f.handler) } type zitiDialContext struct { ctx ziti.Context } -func (self *zitiDialContext) Dial(_ context.Context, _ string, addr string) (net.Conn, error) { +func (c *zitiDialContext) Dial(_ context.Context, _ string, addr string) (net.Conn, error) { shrToken := strings.Split(addr, ":")[0] // ignore :port (we get passed 'host:port') - conn, err := self.ctx.Dial(shrToken) + conn, err := c.ctx.Dial(shrToken) if err != nil { return conn, err } @@ -128,9 +147,9 @@ func hostTargetReverseProxy(cfg *Config, ctx ziti.Context) *httputil.ReverseProx return &httputil.ReverseProxy{Director: director} } -func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Context) http.HandlerFunc { +func authHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - shrToken := resolveService(cfg.HostMatch, r.Host) + shrToken := resolveService(pcfg.HostMatch, r.Host) if shrToken != "" { if svc, found := endpoints.GetRefreshedService(shrToken, ctx); found { if cfg, found := svc.Config[sdk.ZrokProxyConfig]; found { @@ -145,7 +164,7 @@ func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Conte logrus.Debugf("auth scheme basic '%v", shrToken) inUser, inPass, ok := r.BasicAuth() if !ok { - writeUnauthorizedResponse(w, realm) + basicAuthRequired(w, shrToken) return } authed := false @@ -179,15 +198,96 @@ func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Conte } if !authed { - writeUnauthorizedResponse(w, realm) + basicAuthRequired(w, shrToken) return } handler.ServeHTTP(w, r) + case string(sdk.Oauth): + logrus.Debugf("auth scheme oauth '%v'", shrToken) + + if oauthCfg, found := cfg["oauth"]; found { + if provider, found := oauthCfg.(map[string]interface{})["provider"]; found { + var authCheckInterval time.Duration + if checkInterval, found := oauthCfg.(map[string]interface{})["authorization_check_interval"]; !found { + logrus.Errorf("Missing authorization check interval in share config. Defaulting to 3 hours") + authCheckInterval = 3 * time.Hour + } else { + i, err := time.ParseDuration(checkInterval.(string)) + if err != nil { + logrus.Errorf("unable to parse authorization check interval in share config (%v). Defaulting to 3 hours", checkInterval) + authCheckInterval = 3 * time.Hour + } else { + authCheckInterval = i + } + } + + target := fmt.Sprintf("%s%s", r.Host, r.URL.Path) + + cookie, err := r.Cookie("zrok-access") + if err != nil { + logrus.Errorf("unable to get 'zrok-access' cookie: %v", err) + oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval) + return + } + tkn, err := jwt.ParseWithClaims(cookie.Value, &ZrokClaims{}, func(t *jwt.Token) (interface{}, error) { + if pcfg.Oauth == nil { + return nil, fmt.Errorf("missing oauth configuration for access point; unable to parse jwt") + } + return key, nil + }) + if err != nil { + logrus.Errorf("unable to parse jwt: %v", err) + oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval) + return + } + claims := tkn.Claims.(*ZrokClaims) + if claims.Provider != provider { + logrus.Error("provider mismatch; restarting auth flow") + oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval) + return + } + if claims.AuthorizationCheckInterval != authCheckInterval { + logrus.Error("authorization check interval mismatch; restarting auth flow") + oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval) + return + } + if validDomains, found := oauthCfg.(map[string]interface{})["email_domains"]; found { + if castedDomains, ok := validDomains.([]interface{}); !ok { + logrus.Error("invalid email domain format") + return + } else { + if len(castedDomains) > 0 { + found := false + for _, domain := range castedDomains { + if strings.HasSuffix(claims.Email, domain.(string)) { + found = true + break + } + } + if !found { + logrus.Warnf("invalid email domain") + unauthorizedUi.WriteUnauthorized(w) + return + } + } + } + } + handler.ServeHTTP(w, r) + return + + } else { + logrus.Warnf("%v -> no provider for '%v'", r.RemoteAddr, provider) + notFoundUi.WriteNotFound(w) + } + } else { + logrus.Warnf("%v -> no oauth cfg for '%v'", r.RemoteAddr, shrToken) + notFoundUi.WriteNotFound(w) + } default: logrus.Infof("invalid auth scheme '%v'", scheme) - writeUnauthorizedResponse(w, realm) + basicAuthRequired(w, shrToken) return } } else { @@ -209,14 +309,53 @@ func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Conte } } -func writeUnauthorizedResponse(w http.ResponseWriter, realm string) { +type ZrokClaims struct { + Email string `json:"email"` + AccessToken string `json:"accessToken"` + Provider string `json:"provider"` + AuthorizationCheckInterval time.Duration `json:"authorizationCheckInterval"` + jwt.RegisteredClaims +} + +func SetZrokCookie(w http.ResponseWriter, domain, email, accessToken, provider string, checkInterval time.Duration, key []byte) { + tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, ZrokClaims{ + Email: email, + AccessToken: accessToken, + Provider: provider, + AuthorizationCheckInterval: checkInterval, + }) + sTkn, err := tkn.SignedString(key) + if err != nil { + http.Error(w, fmt.Sprintf("after signing cookie token: %v", err.Error()), http.StatusInternalServerError) + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "zrok-access", + Value: sTkn, + MaxAge: int(checkInterval.Seconds()), + Domain: domain, + Path: "/", + Expires: time.Now().Add(checkInterval), + //Secure: true, //When tls gets added have this be configured on if tls + }) +} + +func basicAuthRequired(w http.ResponseWriter, realm string) { w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) w.WriteHeader(401) - w.Write([]byte("No Authorization\n")) + _, _ = w.Write([]byte("No Authorization\n")) +} + +func oauthLoginRequired(w http.ResponseWriter, r *http.Request, shrToken string, pcfg *Config, provider, target string, authCheckInterval time.Duration) { + scheme := "https" + if pcfg.Oauth != nil && pcfg.Oauth.RedirectHttpOnly { + scheme = "http" + } + http.Redirect(w, r, fmt.Sprintf("%s://%s.%s:%d/%s/login?targethost=%s&checkInterval=%s", scheme, shrToken, pcfg.Oauth.RedirectHost, pcfg.Oauth.RedirectPort, provider, url.QueryEscape(target), authCheckInterval.String()), http.StatusFound) } func resolveService(hostMatch string, host string) string { - logrus.Debugf("host = '%v'", host) if hostMatch == "" || strings.Contains(host, hostMatch) { tokens := strings.Split(host, ".") if len(tokens) > 0 { diff --git a/endpoints/publicProxy/unauthorizedUi/embed.go b/endpoints/publicProxy/unauthorizedUi/embed.go new file mode 100644 index 00000000..c280963b --- /dev/null +++ b/endpoints/publicProxy/unauthorizedUi/embed.go @@ -0,0 +1,6 @@ +package unauthorizedUi + +import "embed" + +//go:embed index.html +var FS embed.FS diff --git a/endpoints/publicProxy/unauthorizedUi/handler.go b/endpoints/publicProxy/unauthorizedUi/handler.go new file mode 100644 index 00000000..e64e348b --- /dev/null +++ b/endpoints/publicProxy/unauthorizedUi/handler.go @@ -0,0 +1,21 @@ +package unauthorizedUi + +import ( + "github.com/sirupsen/logrus" + "net/http" +) + +func WriteUnauthorized(w http.ResponseWriter) { + if data, err := FS.ReadFile("index.html"); err == nil { + w.WriteHeader(http.StatusNotFound) + n, err := w.Write(data) + if n != len(data) { + logrus.Errorf("short write") + return + } + if err != nil { + logrus.Error(err) + return + } + } +} diff --git a/endpoints/publicProxy/unauthorizedUi/index.html b/endpoints/publicProxy/unauthorizedUi/index.html new file mode 100644 index 00000000..b2920124 --- /dev/null +++ b/endpoints/publicProxy/unauthorizedUi/index.html @@ -0,0 +1,400 @@ + + + + + + + + + + + zrok + + + +
+ +
+
+

Unauthorized

+
+
+
+ + diff --git a/etc/frontend.yml b/etc/frontend.yml new file mode 100644 index 00000000..99f878ea --- /dev/null +++ b/etc/frontend.yml @@ -0,0 +1,20 @@ +# Setting the `host_match` setting will cause a `zrok access public` to ignore `Host` headers that do not contain the +# configured string. This will allow you to let a load balancer access the frontend by IP address for health check +# purposes, and will allow `Host` headers that match the configured DNS name to be routed through `zrok`. +# +#host_match: zrok.io + +# The OAuth configuration is used when enabling OAuth authentication with your public frontend. +# +#oauth: +# redirect_host: oauth.zrok.io +# redirect_port: 28080 +# redirect_http_only: false +# hash_key: "" +# providers: +# - name: google +# client_id: +# client_secret: +# - name: github +# client_id: +# client_secret: \ No newline at end of file diff --git a/etc/http-frontend.yml b/etc/http-frontend.yml deleted file mode 100644 index 3bd07f0b..00000000 --- a/etc/http-frontend.yml +++ /dev/null @@ -1,5 +0,0 @@ -# Setting the `host_match` setting will cause a `zrok access public` to ignore `Host` headers that do not contain the -# configured string. This will allow you to let a load balancer access the frontend by IP address for health check -# purposes, and will allow `Host` headers that match the configured DNS name to be routed through `zrok`. -# -host_match: zrok.io diff --git a/go.mod b/go.mod index 372c846b..ab9fdf36 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( github.com/go-openapi/strfmt v0.21.7 github.com/go-openapi/swag v0.22.4 github.com/go-openapi/validate v0.22.1 + github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.5.0 github.com/iancoleman/strcase v0.2.0 github.com/influxdata/influxdb-client-go/v2 v2.11.0 @@ -41,14 +43,18 @@ require ( github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 github.com/wneessen/go-mail v0.2.7 + github.com/zitadel/oidc/v2 v2.7.0 go.uber.org/zap v1.25.0 golang.org/x/crypto v0.12.0 golang.org/x/net v0.14.0 + golang.org/x/oauth2 v0.10.0 golang.org/x/time v0.3.0 nhooyr.io/websocket v1.8.7 ) require ( + cloud.google.com/go/compute v1.20.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/BurntSushi/toml v1.3.2 // indirect @@ -98,7 +104,6 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/glog v1.1.0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -108,8 +113,9 @@ require ( github.com/google/go-tpm v0.3.3 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/schema v1.2.0 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.12 // indirect @@ -150,6 +156,7 @@ require ( github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.13.0 // indirect + github.com/muhlemmer/gu v0.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect @@ -221,6 +228,7 @@ require ( golang.org/x/term v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/tools v0.10.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect diff --git a/go.sum b/go.sum index 469ba824..c1454c49 100644 --- a/go.sum +++ b/go.sum @@ -39,7 +39,9 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -640,6 +642,10 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -766,6 +772,7 @@ github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.4.3 h1:2n9BZ0YQiXGESUSR+6FLg0WWWE80u+mIz35f0uHWcIE= github.com/jedib0t/go-pretty/v6 v6.4.3/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI= +github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= @@ -985,6 +992,8 @@ github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= +github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= @@ -1166,6 +1175,7 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -1351,6 +1361,8 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/zitadel/oidc/v2 v2.7.0 h1:IGX4EDk6tegTjUSsZDWeTfLseFU0BdJ/Glf1tgys2lU= +github.com/zitadel/oidc/v2 v2.7.0/go.mod h1:zkUkVJS0sDVy9m0UA9RgO3f8i/C0rtjvXU36UJj7T+0= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1635,6 +1647,7 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/rest_client_zrok/share/oauth_authenticate_parameters.go b/rest_client_zrok/share/oauth_authenticate_parameters.go new file mode 100644 index 00000000..cc305f14 --- /dev/null +++ b/rest_client_zrok/share/oauth_authenticate_parameters.go @@ -0,0 +1,184 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package share + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewOauthAuthenticateParams creates a new OauthAuthenticateParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewOauthAuthenticateParams() *OauthAuthenticateParams { + return &OauthAuthenticateParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewOauthAuthenticateParamsWithTimeout creates a new OauthAuthenticateParams object +// with the ability to set a timeout on a request. +func NewOauthAuthenticateParamsWithTimeout(timeout time.Duration) *OauthAuthenticateParams { + return &OauthAuthenticateParams{ + timeout: timeout, + } +} + +// NewOauthAuthenticateParamsWithContext creates a new OauthAuthenticateParams object +// with the ability to set a context for a request. +func NewOauthAuthenticateParamsWithContext(ctx context.Context) *OauthAuthenticateParams { + return &OauthAuthenticateParams{ + Context: ctx, + } +} + +// NewOauthAuthenticateParamsWithHTTPClient creates a new OauthAuthenticateParams object +// with the ability to set a custom HTTPClient for a request. +func NewOauthAuthenticateParamsWithHTTPClient(client *http.Client) *OauthAuthenticateParams { + return &OauthAuthenticateParams{ + HTTPClient: client, + } +} + +/* +OauthAuthenticateParams contains all the parameters to send to the API endpoint + + for the oauth authenticate operation. + + Typically these are written to a http.Request. +*/ +type OauthAuthenticateParams struct { + + // Code. + Code string + + // State. + State *string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the oauth authenticate params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *OauthAuthenticateParams) WithDefaults() *OauthAuthenticateParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the oauth authenticate params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *OauthAuthenticateParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the oauth authenticate params +func (o *OauthAuthenticateParams) WithTimeout(timeout time.Duration) *OauthAuthenticateParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the oauth authenticate params +func (o *OauthAuthenticateParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the oauth authenticate params +func (o *OauthAuthenticateParams) WithContext(ctx context.Context) *OauthAuthenticateParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the oauth authenticate params +func (o *OauthAuthenticateParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the oauth authenticate params +func (o *OauthAuthenticateParams) WithHTTPClient(client *http.Client) *OauthAuthenticateParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the oauth authenticate params +func (o *OauthAuthenticateParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithCode adds the code to the oauth authenticate params +func (o *OauthAuthenticateParams) WithCode(code string) *OauthAuthenticateParams { + o.SetCode(code) + return o +} + +// SetCode adds the code to the oauth authenticate params +func (o *OauthAuthenticateParams) SetCode(code string) { + o.Code = code +} + +// WithState adds the state to the oauth authenticate params +func (o *OauthAuthenticateParams) WithState(state *string) *OauthAuthenticateParams { + o.SetState(state) + return o +} + +// SetState adds the state to the oauth authenticate params +func (o *OauthAuthenticateParams) SetState(state *string) { + o.State = state +} + +// WriteToRequest writes these params to a swagger request +func (o *OauthAuthenticateParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // query param code + qrCode := o.Code + qCode := qrCode + if qCode != "" { + + if err := r.SetQueryParam("code", qCode); err != nil { + return err + } + } + + if o.State != nil { + + // query param state + var qrState string + + if o.State != nil { + qrState = *o.State + } + qState := qrState + if qState != "" { + + if err := r.SetQueryParam("state", qState); err != nil { + return err + } + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/rest_client_zrok/share/oauth_authenticate_responses.go b/rest_client_zrok/share/oauth_authenticate_responses.go new file mode 100644 index 00000000..c4457878 --- /dev/null +++ b/rest_client_zrok/share/oauth_authenticate_responses.go @@ -0,0 +1,208 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package share + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" +) + +// OauthAuthenticateReader is a Reader for the OauthAuthenticate structure. +type OauthAuthenticateReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *OauthAuthenticateReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewOauthAuthenticateOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 302: + result := NewOauthAuthenticateFound() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewOauthAuthenticateInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code()) + } +} + +// NewOauthAuthenticateOK creates a OauthAuthenticateOK with default headers values +func NewOauthAuthenticateOK() *OauthAuthenticateOK { + return &OauthAuthenticateOK{} +} + +/* +OauthAuthenticateOK describes a response with status code 200, with default header values. + +testing +*/ +type OauthAuthenticateOK struct { +} + +// IsSuccess returns true when this oauth authenticate o k response has a 2xx status code +func (o *OauthAuthenticateOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this oauth authenticate o k response has a 3xx status code +func (o *OauthAuthenticateOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this oauth authenticate o k response has a 4xx status code +func (o *OauthAuthenticateOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this oauth authenticate o k response has a 5xx status code +func (o *OauthAuthenticateOK) IsServerError() bool { + return false +} + +// IsCode returns true when this oauth authenticate o k response a status code equal to that given +func (o *OauthAuthenticateOK) IsCode(code int) bool { + return code == 200 +} + +func (o *OauthAuthenticateOK) Error() string { + return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateOK ", 200) +} + +func (o *OauthAuthenticateOK) String() string { + return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateOK ", 200) +} + +func (o *OauthAuthenticateOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewOauthAuthenticateFound creates a OauthAuthenticateFound with default headers values +func NewOauthAuthenticateFound() *OauthAuthenticateFound { + return &OauthAuthenticateFound{} +} + +/* +OauthAuthenticateFound describes a response with status code 302, with default header values. + +redirect back to share +*/ +type OauthAuthenticateFound struct { + + /* Redirect URL + */ + Location string +} + +// IsSuccess returns true when this oauth authenticate found response has a 2xx status code +func (o *OauthAuthenticateFound) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this oauth authenticate found response has a 3xx status code +func (o *OauthAuthenticateFound) IsRedirect() bool { + return true +} + +// IsClientError returns true when this oauth authenticate found response has a 4xx status code +func (o *OauthAuthenticateFound) IsClientError() bool { + return false +} + +// IsServerError returns true when this oauth authenticate found response has a 5xx status code +func (o *OauthAuthenticateFound) IsServerError() bool { + return false +} + +// IsCode returns true when this oauth authenticate found response a status code equal to that given +func (o *OauthAuthenticateFound) IsCode(code int) bool { + return code == 302 +} + +func (o *OauthAuthenticateFound) Error() string { + return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateFound ", 302) +} + +func (o *OauthAuthenticateFound) String() string { + return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateFound ", 302) +} + +func (o *OauthAuthenticateFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // hydrates response header location + hdrLocation := response.GetHeader("location") + + if hdrLocation != "" { + o.Location = hdrLocation + } + + return nil +} + +// NewOauthAuthenticateInternalServerError creates a OauthAuthenticateInternalServerError with default headers values +func NewOauthAuthenticateInternalServerError() *OauthAuthenticateInternalServerError { + return &OauthAuthenticateInternalServerError{} +} + +/* +OauthAuthenticateInternalServerError describes a response with status code 500, with default header values. + +internal server error +*/ +type OauthAuthenticateInternalServerError struct { +} + +// IsSuccess returns true when this oauth authenticate internal server error response has a 2xx status code +func (o *OauthAuthenticateInternalServerError) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this oauth authenticate internal server error response has a 3xx status code +func (o *OauthAuthenticateInternalServerError) IsRedirect() bool { + return false +} + +// IsClientError returns true when this oauth authenticate internal server error response has a 4xx status code +func (o *OauthAuthenticateInternalServerError) IsClientError() bool { + return false +} + +// IsServerError returns true when this oauth authenticate internal server error response has a 5xx status code +func (o *OauthAuthenticateInternalServerError) IsServerError() bool { + return true +} + +// IsCode returns true when this oauth authenticate internal server error response a status code equal to that given +func (o *OauthAuthenticateInternalServerError) IsCode(code int) bool { + return code == 500 +} + +func (o *OauthAuthenticateInternalServerError) Error() string { + return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateInternalServerError ", 500) +} + +func (o *OauthAuthenticateInternalServerError) String() string { + return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateInternalServerError ", 500) +} + +func (o *OauthAuthenticateInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} diff --git a/rest_client_zrok/share/share_responses.go b/rest_client_zrok/share/share_responses.go index 23b4b180..8e1c6150 100644 --- a/rest_client_zrok/share/share_responses.go +++ b/rest_client_zrok/share/share_responses.go @@ -41,6 +41,12 @@ func (o *ShareReader) ReadResponse(response runtime.ClientResponse, consumer run return nil, err } return nil, result + case 422: + result := NewShareUnprocessableEntity() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result case 500: result := NewShareInternalServerError() if err := result.readResponse(response, consumer, o.formats); err != nil { @@ -217,6 +223,57 @@ func (o *ShareNotFound) readResponse(response runtime.ClientResponse, consumer r return nil } +// NewShareUnprocessableEntity creates a ShareUnprocessableEntity with default headers values +func NewShareUnprocessableEntity() *ShareUnprocessableEntity { + return &ShareUnprocessableEntity{} +} + +/* +ShareUnprocessableEntity describes a response with status code 422, with default header values. + +unprocessable +*/ +type ShareUnprocessableEntity struct { +} + +// IsSuccess returns true when this share unprocessable entity response has a 2xx status code +func (o *ShareUnprocessableEntity) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this share unprocessable entity response has a 3xx status code +func (o *ShareUnprocessableEntity) IsRedirect() bool { + return false +} + +// IsClientError returns true when this share unprocessable entity response has a 4xx status code +func (o *ShareUnprocessableEntity) IsClientError() bool { + return true +} + +// IsServerError returns true when this share unprocessable entity response has a 5xx status code +func (o *ShareUnprocessableEntity) IsServerError() bool { + return false +} + +// IsCode returns true when this share unprocessable entity response a status code equal to that given +func (o *ShareUnprocessableEntity) IsCode(code int) bool { + return code == 422 +} + +func (o *ShareUnprocessableEntity) Error() string { + return fmt.Sprintf("[POST /share][%d] shareUnprocessableEntity ", 422) +} + +func (o *ShareUnprocessableEntity) String() string { + return fmt.Sprintf("[POST /share][%d] shareUnprocessableEntity ", 422) +} + +func (o *ShareUnprocessableEntity) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + // NewShareInternalServerError creates a ShareInternalServerError with default headers values func NewShareInternalServerError() *ShareInternalServerError { return &ShareInternalServerError{} diff --git a/rest_model_zrok/share_request.go b/rest_model_zrok/share_request.go index 31f89533..c2c1d012 100644 --- a/rest_model_zrok/share_request.go +++ b/rest_model_zrok/share_request.go @@ -40,6 +40,16 @@ type ShareRequest struct { // frontend selection FrontendSelection []string `json:"frontendSelection"` + // oauth authorization check interval + OauthAuthorizationCheckInterval string `json:"oauthAuthorizationCheckInterval,omitempty"` + + // oauth email domains + OauthEmailDomains []string `json:"oauthEmailDomains"` + + // oauth provider + // Enum: [github google] + OauthProvider string `json:"oauthProvider,omitempty"` + // reserved Reserved bool `json:"reserved,omitempty"` @@ -60,6 +70,10 @@ func (m *ShareRequest) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateOauthProvider(formats); err != nil { + res = append(res, err) + } + if err := m.validateShareMode(formats); err != nil { res = append(res, err) } @@ -147,6 +161,48 @@ func (m *ShareRequest) validateBackendMode(formats strfmt.Registry) error { return nil } +var shareRequestTypeOauthProviderPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["github","google"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + shareRequestTypeOauthProviderPropEnum = append(shareRequestTypeOauthProviderPropEnum, v) + } +} + +const ( + + // ShareRequestOauthProviderGithub captures enum value "github" + ShareRequestOauthProviderGithub string = "github" + + // ShareRequestOauthProviderGoogle captures enum value "google" + ShareRequestOauthProviderGoogle string = "google" +) + +// prop value enum +func (m *ShareRequest) validateOauthProviderEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, shareRequestTypeOauthProviderPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *ShareRequest) validateOauthProvider(formats strfmt.Registry) error { + if swag.IsZero(m.OauthProvider) { // not required + return nil + } + + // value enum + if err := m.validateOauthProviderEnum("oauthProvider", "body", m.OauthProvider); err != nil { + return err + } + + return nil +} + var shareRequestTypeShareModePropEnum []interface{} func init() { diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go index 35e22c01..ee155ad8 100644 --- a/rest_server_zrok/embedded_spec.go +++ b/rest_server_zrok/embedded_spec.go @@ -865,6 +865,9 @@ func init() { "404": { "description": "not found" }, + "422": { + "description": "unprocessable" + }, "500": { "description": "internal server error", "schema": { @@ -1484,6 +1487,22 @@ func init() { "type": "string" } }, + "oauthAuthorizationCheckInterval": { + "type": "string" + }, + "oauthEmailDomains": { + "type": "array", + "items": { + "type": "string" + } + }, + "oauthProvider": { + "type": "string", + "enum": [ + "github", + "google" + ] + }, "reserved": { "type": "boolean" }, @@ -2462,6 +2481,9 @@ func init() { "404": { "description": "not found" }, + "422": { + "description": "unprocessable" + }, "500": { "description": "internal server error", "schema": { @@ -3081,6 +3103,22 @@ func init() { "type": "string" } }, + "oauthAuthorizationCheckInterval": { + "type": "string" + }, + "oauthEmailDomains": { + "type": "array", + "items": { + "type": "string" + } + }, + "oauthProvider": { + "type": "string", + "enum": [ + "github", + "google" + ] + }, "reserved": { "type": "boolean" }, diff --git a/rest_server_zrok/operations/share/oauth_authenticate.go b/rest_server_zrok/operations/share/oauth_authenticate.go new file mode 100644 index 00000000..8384df4b --- /dev/null +++ b/rest_server_zrok/operations/share/oauth_authenticate.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package share + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// OauthAuthenticateHandlerFunc turns a function with the right signature into a oauth authenticate handler +type OauthAuthenticateHandlerFunc func(OauthAuthenticateParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn OauthAuthenticateHandlerFunc) Handle(params OauthAuthenticateParams) middleware.Responder { + return fn(params) +} + +// OauthAuthenticateHandler interface for that can handle valid oauth authenticate params +type OauthAuthenticateHandler interface { + Handle(OauthAuthenticateParams) middleware.Responder +} + +// NewOauthAuthenticate creates a new http.Handler for the oauth authenticate operation +func NewOauthAuthenticate(ctx *middleware.Context, handler OauthAuthenticateHandler) *OauthAuthenticate { + return &OauthAuthenticate{Context: ctx, Handler: handler} +} + +/* + OauthAuthenticate swagger:route GET /oauth/authorize share oauthAuthenticate + +OauthAuthenticate oauth authenticate API +*/ +type OauthAuthenticate struct { + Context *middleware.Context + Handler OauthAuthenticateHandler +} + +func (o *OauthAuthenticate) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewOauthAuthenticateParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/rest_server_zrok/operations/share/oauth_authenticate_parameters.go b/rest_server_zrok/operations/share/oauth_authenticate_parameters.go new file mode 100644 index 00000000..e7fe0e18 --- /dev/null +++ b/rest_server_zrok/operations/share/oauth_authenticate_parameters.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package share + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewOauthAuthenticateParams creates a new OauthAuthenticateParams object +// +// There are no default values defined in the spec. +func NewOauthAuthenticateParams() OauthAuthenticateParams { + + return OauthAuthenticateParams{} +} + +// OauthAuthenticateParams contains all the bound params for the oauth authenticate operation +// typically these are obtained from a http.Request +// +// swagger:parameters oauthAuthenticate +type OauthAuthenticateParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: query + */ + Code string + /* + In: query + */ + State *string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewOauthAuthenticateParams() beforehand. +func (o *OauthAuthenticateParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qCode, qhkCode, _ := qs.GetOK("code") + if err := o.bindCode(qCode, qhkCode, route.Formats); err != nil { + res = append(res, err) + } + + qState, qhkState, _ := qs.GetOK("state") + if err := o.bindState(qState, qhkState, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindCode binds and validates parameter Code from query. +func (o *OauthAuthenticateParams) bindCode(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("code", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("code", "query", raw); err != nil { + return err + } + o.Code = raw + + return nil +} + +// bindState binds and validates parameter State from query. +func (o *OauthAuthenticateParams) bindState(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.State = &raw + + return nil +} diff --git a/rest_server_zrok/operations/share/oauth_authenticate_responses.go b/rest_server_zrok/operations/share/oauth_authenticate_responses.go new file mode 100644 index 00000000..7965c0b0 --- /dev/null +++ b/rest_server_zrok/operations/share/oauth_authenticate_responses.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package share + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" +) + +// OauthAuthenticateOKCode is the HTTP code returned for type OauthAuthenticateOK +const OauthAuthenticateOKCode int = 200 + +/* +OauthAuthenticateOK testing + +swagger:response oauthAuthenticateOK +*/ +type OauthAuthenticateOK struct { +} + +// NewOauthAuthenticateOK creates OauthAuthenticateOK with default headers values +func NewOauthAuthenticateOK() *OauthAuthenticateOK { + + return &OauthAuthenticateOK{} +} + +// WriteResponse to the client +func (o *OauthAuthenticateOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(200) +} + +// OauthAuthenticateFoundCode is the HTTP code returned for type OauthAuthenticateFound +const OauthAuthenticateFoundCode int = 302 + +/* +OauthAuthenticateFound redirect back to share + +swagger:response oauthAuthenticateFound +*/ +type OauthAuthenticateFound struct { + /*Redirect URL + + */ + Location string `json:"location"` +} + +// NewOauthAuthenticateFound creates OauthAuthenticateFound with default headers values +func NewOauthAuthenticateFound() *OauthAuthenticateFound { + + return &OauthAuthenticateFound{} +} + +// WithLocation adds the location to the oauth authenticate found response +func (o *OauthAuthenticateFound) WithLocation(location string) *OauthAuthenticateFound { + o.Location = location + return o +} + +// SetLocation sets the location to the oauth authenticate found response +func (o *OauthAuthenticateFound) SetLocation(location string) { + o.Location = location +} + +// WriteResponse to the client +func (o *OauthAuthenticateFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // response header location + + location := o.Location + if location != "" { + rw.Header().Set("location", location) + } + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(302) +} + +// OauthAuthenticateInternalServerErrorCode is the HTTP code returned for type OauthAuthenticateInternalServerError +const OauthAuthenticateInternalServerErrorCode int = 500 + +/* +OauthAuthenticateInternalServerError internal server error + +swagger:response oauthAuthenticateInternalServerError +*/ +type OauthAuthenticateInternalServerError struct { +} + +// NewOauthAuthenticateInternalServerError creates OauthAuthenticateInternalServerError with default headers values +func NewOauthAuthenticateInternalServerError() *OauthAuthenticateInternalServerError { + + return &OauthAuthenticateInternalServerError{} +} + +// WriteResponse to the client +func (o *OauthAuthenticateInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(500) +} diff --git a/rest_server_zrok/operations/share/oauth_authenticate_urlbuilder.go b/rest_server_zrok/operations/share/oauth_authenticate_urlbuilder.go new file mode 100644 index 00000000..505fdba6 --- /dev/null +++ b/rest_server_zrok/operations/share/oauth_authenticate_urlbuilder.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package share + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// OauthAuthenticateURL generates an URL for the oauth authenticate operation +type OauthAuthenticateURL struct { + Code string + State *string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *OauthAuthenticateURL) WithBasePath(bp string) *OauthAuthenticateURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *OauthAuthenticateURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *OauthAuthenticateURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/oauth/authorize" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + codeQ := o.Code + if codeQ != "" { + qs.Set("code", codeQ) + } + + var stateQ string + if o.State != nil { + stateQ = *o.State + } + if stateQ != "" { + qs.Set("state", stateQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *OauthAuthenticateURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *OauthAuthenticateURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *OauthAuthenticateURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on OauthAuthenticateURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on OauthAuthenticateURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *OauthAuthenticateURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/rest_server_zrok/operations/share/share_responses.go b/rest_server_zrok/operations/share/share_responses.go index 556210b6..d49c044e 100644 --- a/rest_server_zrok/operations/share/share_responses.go +++ b/rest_server_zrok/operations/share/share_responses.go @@ -108,6 +108,31 @@ func (o *ShareNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.P rw.WriteHeader(404) } +// ShareUnprocessableEntityCode is the HTTP code returned for type ShareUnprocessableEntity +const ShareUnprocessableEntityCode int = 422 + +/* +ShareUnprocessableEntity unprocessable + +swagger:response shareUnprocessableEntity +*/ +type ShareUnprocessableEntity struct { +} + +// NewShareUnprocessableEntity creates ShareUnprocessableEntity with default headers values +func NewShareUnprocessableEntity() *ShareUnprocessableEntity { + + return &ShareUnprocessableEntity{} +} + +// WriteResponse to the client +func (o *ShareUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(422) +} + // ShareInternalServerErrorCode is the HTTP code returned for type ShareInternalServerError const ShareInternalServerErrorCode int = 500 diff --git a/sdk/config.go b/sdk/config.go index e9d4c318..38748e78 100644 --- a/sdk/config.go +++ b/sdk/config.go @@ -4,26 +4,35 @@ import "github.com/pkg/errors" const ZrokProxyConfig = "zrok.proxy.v1" -type ProxyConfig struct { - AuthScheme AuthScheme `json:"auth_scheme"` - BasicAuth *BasicAuth `json:"basic_auth"` +type FrontendConfig struct { + AuthScheme AuthScheme `json:"auth_scheme"` + BasicAuth *BasicAuthConfig `json:"basic_auth"` + OauthAuth *OauthConfig `json:"oauth"` } -type BasicAuth struct { - Users []*AuthUser `json:"users"` +type BasicAuthConfig struct { + Users []*AuthUserConfig `json:"users"` } -type AuthUser struct { +type AuthUserConfig struct { Username string `json:"username"` Password string `json:"password"` } +type OauthConfig struct { + Provider string `json:"provider"` + EmailDomains []string `json:"email_domains"` + AuthorizationCheckInterval string `json:"authorization_check_interval"` +} + func ParseAuthScheme(authScheme string) (AuthScheme, error) { switch authScheme { case string(None): return None, nil case string(Basic): return Basic, nil + case string(Oauth): + return Oauth, nil default: return None, errors.Errorf("unknown auth scheme '%v'", authScheme) } diff --git a/sdk/model.go b/sdk/model.go index 9b2fe215..fa0310eb 100644 --- a/sdk/model.go +++ b/sdk/model.go @@ -1,5 +1,7 @@ package sdk +import "time" + type BackendMode string const ( @@ -18,11 +20,14 @@ const ( ) type ShareRequest struct { - BackendMode BackendMode - ShareMode ShareMode - Frontends []string - Auth []string - Target string + BackendMode BackendMode + ShareMode ShareMode + Target string + Frontends []string + BasicAuth []string + OauthProvider string + OauthEmailDomains []string + OauthAuthorizationCheckInterval time.Duration } type Share struct { @@ -56,4 +61,5 @@ type AuthScheme string const ( None AuthScheme = "none" Basic AuthScheme = "basic" + Oauth AuthScheme = "oauth" ) diff --git a/sdk/share.go b/sdk/share.go index dccf8eb6..9a1838b8 100644 --- a/sdk/share.go +++ b/sdk/share.go @@ -26,18 +26,22 @@ func CreateShare(root env_core.Root, request *ShareRequest) (*Share, error) { return nil, errors.Errorf("unknown share mode '%v'", request.ShareMode) } - if len(request.Auth) > 0 { + if len(request.BasicAuth) > 0 { out.Body.AuthScheme = string(Basic) - for _, pair := range request.Auth { - tokens := strings.Split(pair, ":") + for _, basicAuthUser := range request.BasicAuth { + tokens := strings.Split(basicAuthUser, ":") if len(tokens) == 2 { out.Body.AuthUsers = append(out.Body.AuthUsers, &rest_model_zrok.AuthUser{Username: strings.TrimSpace(tokens[0]), Password: strings.TrimSpace(tokens[1])}) } else { - return nil, errors.Errorf("invalid username:password pair '%v'", pair) + return nil, errors.Errorf("invalid username:password '%v'", basicAuthUser) } } } + if request.OauthProvider != "" { + out.Body.AuthScheme = string(Oauth) + } + zrok, err := root.Client() if err != nil { return nil, errors.Wrap(err, "error getting zrok client") @@ -70,12 +74,15 @@ func newPrivateShare(root env_core.Root, request *ShareRequest) *share.SharePara func newPublicShare(root env_core.Root, request *ShareRequest) *share.ShareParams { req := share.NewShareParams() req.Body = &rest_model_zrok.ShareRequest{ - EnvZID: root.Environment().ZitiIdentity, - ShareMode: string(request.ShareMode), - FrontendSelection: request.Frontends, - BackendMode: string(request.BackendMode), - BackendProxyEndpoint: request.Target, - AuthScheme: string(None), + EnvZID: root.Environment().ZitiIdentity, + ShareMode: string(request.ShareMode), + FrontendSelection: request.Frontends, + BackendMode: string(request.BackendMode), + BackendProxyEndpoint: request.Target, + AuthScheme: string(None), + OauthEmailDomains: request.OauthEmailDomains, + OauthProvider: request.OauthProvider, + OauthAuthorizationCheckInterval: request.OauthAuthorizationCheckInterval.String(), } return req } diff --git a/specs/zrok.yml b/specs/zrok.yml index ae80158a..88539ac1 100644 --- a/specs/zrok.yml +++ b/specs/zrok.yml @@ -577,6 +577,8 @@ paths: description: unauthorized 404: description: not found + 422: + description: unprocessable 500: description: internal server error schema: @@ -978,6 +980,15 @@ definitions: type: array items: $ref: "#/definitions/authUser" + oauthProvider: + type: string + enum: ["github", "google"] + oauthEmailDomains: + type: array + items: + type: string + oauthAuthorizationCheckInterval: + type: string reserved: type: boolean diff --git a/ui/src/api/types.js b/ui/src/api/types.js index c4e6b95b..5f06119b 100644 --- a/ui/src/api/types.js +++ b/ui/src/api/types.js @@ -253,6 +253,9 @@ * @property {string} backendProxyEndpoint * @property {string} authScheme * @property {module:types.authUser[]} authUsers + * @property {string} oauthProvider + * @property {string[]} oauthEmailDomains + * @property {string} oauthAuthorizationCheckInterval * @property {boolean} reserved */