Skip to content

Commit

Permalink
Merge pull request #398 from openziti/oauth-testing
Browse files Browse the repository at this point in the history
OAuth for Public Frontends (#45, #404)
  • Loading branch information
michaelquigley authored Oct 6, 2023
2 parents 2388447 + 6c140dc commit 859b2d7
Show file tree
Hide file tree
Showing 52 changed files with 2,282 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*.db
automated-release-build
etc/dev.yml
etc/dev-metrics.yml
etc/dev-frontend.yml

# Dependencies
/node_modules/
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion cmd/zrok/reserve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion cmd/zrok/sharePrivate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 23 additions & 8 deletions cmd/zrok/sharePublic.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,23 @@ import (
"os/signal"
"strings"
"syscall"
"time"
)

func init() {
shareCmd.AddCommand(newSharePublicCommand().cmd)
}

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 {
Expand All @@ -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 (<username:password>,...)")
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 <target>")

cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
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
}
Expand Down Expand Up @@ -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 {
Expand Down
10 changes: 6 additions & 4 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 16 additions & 3 deletions controller/sharePrivate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
19 changes: 16 additions & 3 deletions controller/sharePublic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
29 changes: 19 additions & 10 deletions controller/zrokEdgeSdk/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
7 changes: 7 additions & 0 deletions docs/guides/self-hosting/oauth/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "OAuth",
"position": 70,
"link": {
"type": "generated-index"
}
}
154 changes: 154 additions & 0 deletions docs/guides/self-hosting/oauth/configuring-oauth.md
Original file line number Diff line number Diff line change
@@ -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: "<yourRandomHashKey>"
providers:
- name: google
client_id: <client-id>
client_secret: <client-secret>
- name: github
client_id: <client-id>
client_secret: <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 <target> [flags]
Flags:
-b, --backend-mode string The backend mode {proxy, web, caddy} (default "proxy")
--basic-auth stringArray Basic authentication users (<username:password>,...)
--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 <target>
--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
```

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

1 comment on commit 859b2d7

@vercel
Copy link

@vercel vercel bot commented on 859b2d7 Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

zrok – ./

zrok-openziti.vercel.app
zrok.vercel.app
zrok-git-main-openziti.vercel.app

Please sign in to comment.