Skip to content

Commit

Permalink
docs; tweaks (#404)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelquigley committed Oct 5, 2023
1 parent 19d93d2 commit 72b80ba
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 74 deletions.
96 changes: 55 additions & 41 deletions docs/guides/self-hosting/oauth/configuring-oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,75 +66,89 @@ With this your Google OAuth client should be configured and ready.

## Configuring a GitHub Client ID

`Settings > Developer Settings > OAuth Apps > Register a new application`
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)

Authorization Callback URL: Use the address of the OAuth frontend you configured above, but add `/github/oauth` to the end of the URL.
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`.

## Enabling Oauth on Access Point
## Configuring your Public Frontend

There is a new stanza in the access point configuration.
The public frontend configuration includes a new `oauth` section:

```yaml
oauth:
port: <host-port> #port to listen on oauth callbacks from
redirect_url: <host-url> #redirect url to feed into oauth flow
hash_key_raw: "<your-key>" #key we will use to sign our access token
providers: #which providers we configure to use.
- name: <provider-name>
client_id: <client-id> #the client id you get from your oauth provider
client_secret: <client-secret> #the client secret you get from your oauth provider
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>

```
Currently we support the following Oauth providers:
- google
- github
In your oauth provider of choice's setup you would be prompted to create a client for accessing their services. It will ask for a redirect url. The format is: `<scheme>://<redirect_url>:<port>/<provider>/oauth` and as an example: `http://zrok.io:28080/google/oauth` This is also where you will find the client_id and client_secret.
The `redirect_host` and `redirect_port` value should correspond with the DNS hostname and port configured as your OAuth frontend.

The port you choose is entirely up to the deployment. Just make sure it is open to receive callbacks from your configured oauth providers.
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!

redirect_url is what we will tell the oauth providers to callback with the authorization result. This will be whatever domain you've chosen to host the access server against without the scheme or port. This will get combined with the above port.
`hash_key` is a unique string for your installation that is used to secure the authentication payloads for your public frontend.

We then secure the response data within a zrok-access cookie. This is secured with the hash_key_raw. This can be any raw string.
`providers` is a list of configured providers for this public frontend. The current implementation supports `google` and `github` as options.

### Required Scopes:
- google
- - Need access to a user's email: ./auth/userinfo.email
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.

### Example
## 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:

An example config would look something like:
```yaml
oauth:
port: 28080
redirect_url: zrok.io
hash_key_raw: "test1234test1234"
providers:
- name: google
client_id: ohfwerouyr972t3riugdf89032r8y230ry.apps.googleusercontent.com
client_secret: SDAFOHWER-qafsfgghrWERFfeqo13g
```
$ 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
```

Note that the client id and secret are jumbled text and do not correlate to actual secrets.
The `--oauth-provider` flag enables OAuth for the share using the specified provider.

We spin up a zitadel oidc server on the specified port that handled all of the oauth handshaking. With the response we create a cookie with the name `zrok-access`.
The `--oauth-email-domains` flag accepts a comma-separated list of authenticated email address domains that are allowed to access the share.

## Enabling Oath on Share
The `--oauth-check-interval` flag specifies how frequently the authentication must be checked.

To utilize the oauth integration on the access point we need to add a few more flags to our share command. There are three new flags:
- `provider` : This is the provider to authenticate against. Options are the same as above dependant on what the acess point is configured for
- `oauth-domains` : A list of valid email domains that are allowed to access the service. for example `gmail.com`
- `oauth-check-interval` : How long a `zrok-access` token is valid for before reinitializing the oauth flow. This is defaultly 3 hours.
An example public share:

That's all it takes!
```
$ zrok share public --backend-mode web --oauth-provider github --oauth-email-domains zrok.io ~/public
```

Now when a user connects to your share they will be prompted with the chosen oauth provider and allowed based on your allowed domains. Simply restarting the service won't force a reauth for users either. Changing the `provider` or `oauth-check-interval` will, however.
9 changes: 5 additions & 4 deletions endpoints/publicProxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ type Config struct {
}

type OauthConfig struct {
RedirectHost string
RedirectPort int
HashKeyRaw string `cf:"+secret"`
Providers []*OauthProviderConfig
RedirectHost string
RedirectPort int
RedirectHttpOnly bool
HashKey string `cf:"+secret"`
Providers []*OauthProviderConfig
}

func (oc *OauthConfig) GetProvider(name string) *OauthProviderConfig {
Expand Down
21 changes: 11 additions & 10 deletions endpoints/publicProxy/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
Expand All @@ -19,6 +13,11 @@ import (
"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 {
Expand All @@ -44,11 +43,11 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
}

hash := md5.New()
n, err := hash.Write([]byte(cfg.HashKeyRaw))
n, err := hash.Write([]byte(cfg.HashKey))
if err != nil {
return err
}
if n != len(cfg.HashKeyRaw) {
if n != len(cfg.HashKey) {
return errors.New("short hash")
}
key := hash.Sum(nil)
Expand Down Expand Up @@ -137,14 +136,16 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
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
}
rDat := []githubUserResp{}
var rDat []githubUserResp
err = json.Unmarshal(response, &rDat)
if err != nil {
logrus.Errorf("Error unmarshalling google oauth response: %v", err)
Expand Down
19 changes: 10 additions & 9 deletions endpoints/publicProxy/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
Expand All @@ -19,6 +13,11 @@ import (
"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 {
Expand All @@ -45,11 +44,11 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
}

hash := md5.New()
n, err := hash.Write([]byte(cfg.HashKeyRaw))
n, err := hash.Write([]byte(cfg.HashKey))
if err != nil {
return err
}
if n != len(cfg.HashKeyRaw) {
if n != len(cfg.HashKey) {
return errors.New("short hash")
}
key := hash.Sum(nil)
Expand Down Expand Up @@ -124,7 +123,9 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
defer func() {
_ = resp.Body.Close()
}()
response, err := io.ReadAll(resp.Body)
if err != nil {
logrus.Errorf("Error reading response body: %v", err)
Expand Down
20 changes: 12 additions & 8 deletions endpoints/publicProxy/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ func NewHTTP(cfg *Config) (*HttpFrontend, error) {
var key []byte
if cfg.Oauth != nil {
hash := md5.New()
n, err := hash.Write([]byte(cfg.Oauth.HashKeyRaw))
n, err := hash.Write([]byte(cfg.Oauth.HashKey))
if err != nil {
return nil, err
}
if n != len(cfg.Oauth.HashKeyRaw) {
if n != len(cfg.Oauth.HashKey) {
return nil, errors.New("short hash")
}
key = hash.Sum(nil)
Expand Down Expand Up @@ -80,17 +80,17 @@ func NewHTTP(cfg *Config) (*HttpFrontend, error) {
}, 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
}
Expand Down Expand Up @@ -344,11 +344,15 @@ func SetZrokCookie(w http.ResponseWriter, domain, email, accessToken, provider s
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) {
http.Redirect(w, r, fmt.Sprintf("http://%s.%s:%d/%s/login?targethost=%s&checkInterval=%s", shrToken, pcfg.Oauth.RedirectHost, pcfg.Oauth.RedirectPort, provider, url.QueryEscape(target), authCheckInterval.String()), http.StatusFound)
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 {
Expand Down
7 changes: 5 additions & 2 deletions etc/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
#
#host_match: zrok.io

# The OAuth configuration is used when enabling OAuth authentication with your public frontend.
#
#oauth:
# redirect_host: zrok.io
# redirect_host: oauth.zrok.io
# redirect_port: 28080
# hash_key_raw: "test1234test1234"
# redirect_http_only: false
# hash_key: "<yourRandomHashKey>"
# providers:
# - name: google
# client_id: <client-id>
Expand Down

0 comments on commit 72b80ba

Please sign in to comment.