docs; tweaks (#404)

This commit is contained in:
Michael Quigley 2023-10-05 13:34:27 -04:00
parent 19d93d2825
commit 72b80bac94
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
6 changed files with 104 additions and 80 deletions

View File

@ -66,75 +66,89 @@ With this your Google OAuth client should be configured and ready.
## Configuring a GitHub Client ID ## 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_1.png)
![](images/github_create_oauth_application_2.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) ![](images/github_create_oauth_application_3.png)
Create a new client secret.
![](images/github_create_oauth_application_4.png) ![](images/github_create_oauth_application_4.png)
Save the client ID and the client secret. You'll configure these into your `frontend.yml`. 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 ```yaml
oauth: oauth:
port: <host-port> #port to listen on oauth callbacks from redirect_host: oauth.zrok.io
redirect_url: <host-url> #redirect url to feed into oauth flow redirect_port: 28080
hash_key_raw: "<your-key>" #key we will use to sign our access token redirect_http_only: false
providers: #which providers we configure to use. hash_key: "<yourRandomHashKey>"
- 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
```
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 port you choose is entirely up to the deployment. Just make sure it is open to receive callbacks from your configured oauth providers.
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.
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.
### Required Scopes:
- google
- - Need access to a user's email: ./auth/userinfo.email
### Example
An example config would look something like:
```yaml
oauth:
port: 28080
redirect_url: zrok.io
hash_key_raw: "test1234test1234"
providers: providers:
- name: google - name: google
client_id: ohfwerouyr972t3riugdf89032r8y230ry.apps.googleusercontent.com client_id: <client-id>
client_secret: SDAFOHWER-qafsfgghrWERFfeqo13g client_secret: <client-secret>
- name: github
client_id: <client-id>
client_secret: <client-secret>
``` ```
Note that the client id and secret are jumbled text and do not correlate to actual secrets. The `redirect_host` and `redirect_port` value should correspond with the DNS hostname and port configured as your OAuth frontend.
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 `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!
## Enabling Oath on Share `hash_key` is a unique string for your installation that is used to secure the authentication payloads for your public frontend.
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: `providers` is a list of configured providers for this public frontend. The current implementation supports `google` and `github` as options.
- `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.
That's all it takes! 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
```
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.

View File

@ -18,10 +18,11 @@ type Config struct {
} }
type OauthConfig struct { type OauthConfig struct {
RedirectHost string RedirectHost string
RedirectPort int RedirectPort int
HashKeyRaw string `cf:"+secret"` RedirectHttpOnly bool
Providers []*OauthProviderConfig HashKey string `cf:"+secret"`
Providers []*OauthProviderConfig
} }
func (oc *OauthConfig) GetProvider(name string) *OauthProviderConfig { func (oc *OauthConfig) GetProvider(name string) *OauthProviderConfig {

View File

@ -5,12 +5,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -19,6 +13,11 @@ import (
"github.com/zitadel/oidc/v2/pkg/oidc" "github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
githubOAuth "golang.org/x/oauth2/github" githubOAuth "golang.org/x/oauth2/github"
"io"
"net/http"
"net/url"
"strings"
"time"
) )
func configureGithubOauth(cfg *OauthConfig, tls bool) error { func configureGithubOauth(cfg *OauthConfig, tls bool) error {
@ -44,11 +43,11 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
} }
hash := md5.New() hash := md5.New()
n, err := hash.Write([]byte(cfg.HashKeyRaw)) n, err := hash.Write([]byte(cfg.HashKey))
if err != nil { if err != nil {
return err return err
} }
if n != len(cfg.HashKeyRaw) { if n != len(cfg.HashKey) {
return errors.New("short hash") return errors.New("short hash")
} }
key := hash.Sum(nil) key := hash.Sum(nil)
@ -137,14 +136,16 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
defer resp.Body.Close() defer func() {
_ = resp.Body.Close()
}()
response, err := io.ReadAll(resp.Body) response, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
logrus.Errorf("Error reading response body: %v", err) logrus.Errorf("Error reading response body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
rDat := []githubUserResp{} var rDat []githubUserResp
err = json.Unmarshal(response, &rDat) err = json.Unmarshal(response, &rDat)
if err != nil { if err != nil {
logrus.Errorf("Error unmarshalling google oauth response: %v", err) logrus.Errorf("Error unmarshalling google oauth response: %v", err)

View File

@ -5,12 +5,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -19,6 +13,11 @@ import (
"github.com/zitadel/oidc/v2/pkg/oidc" "github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
googleOauth "golang.org/x/oauth2/google" googleOauth "golang.org/x/oauth2/google"
"io"
"net/http"
"net/url"
"strings"
"time"
) )
func configureGoogleOauth(cfg *OauthConfig, tls bool) error { func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
@ -45,11 +44,11 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
} }
hash := md5.New() hash := md5.New()
n, err := hash.Write([]byte(cfg.HashKeyRaw)) n, err := hash.Write([]byte(cfg.HashKey))
if err != nil { if err != nil {
return err return err
} }
if n != len(cfg.HashKeyRaw) { if n != len(cfg.HashKey) {
return errors.New("short hash") return errors.New("short hash")
} }
key := hash.Sum(nil) key := hash.Sum(nil)
@ -124,7 +123,9 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
defer resp.Body.Close() defer func() {
_ = resp.Body.Close()
}()
response, err := io.ReadAll(resp.Body) response, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
logrus.Errorf("Error reading response body: %v", err) logrus.Errorf("Error reading response body: %v", err)

View File

@ -33,11 +33,11 @@ func NewHTTP(cfg *Config) (*HttpFrontend, error) {
var key []byte var key []byte
if cfg.Oauth != nil { if cfg.Oauth != nil {
hash := md5.New() hash := md5.New()
n, err := hash.Write([]byte(cfg.Oauth.HashKeyRaw)) n, err := hash.Write([]byte(cfg.Oauth.HashKey))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if n != len(cfg.Oauth.HashKeyRaw) { if n != len(cfg.Oauth.HashKey) {
return nil, errors.New("short hash") return nil, errors.New("short hash")
} }
key = hash.Sum(nil) key = hash.Sum(nil)
@ -80,17 +80,17 @@ func NewHTTP(cfg *Config) (*HttpFrontend, error) {
}, nil }, nil
} }
func (self *HttpFrontend) Run() error { func (f *HttpFrontend) Run() error {
return http.ListenAndServe(self.cfg.Address, self.handler) return http.ListenAndServe(f.cfg.Address, f.handler)
} }
type zitiDialContext struct { type zitiDialContext struct {
ctx ziti.Context 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') 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 { if err != nil {
return conn, err return conn, err
} }
@ -344,11 +344,15 @@ func SetZrokCookie(w http.ResponseWriter, domain, email, accessToken, provider s
func basicAuthRequired(w http.ResponseWriter, realm string) { func basicAuthRequired(w http.ResponseWriter, realm string) {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401) 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) { 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 { func resolveService(hostMatch string, host string) string {

View File

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