Skip to content

Commit

Permalink
Idtoken Service Account auth (#75)
Browse files Browse the repository at this point in the history
* Allow IAP auth through idtoken from service account

* Refactor ExecAllocatorOption setup into a utility function to fix duplication warning. Also allow self signed certificate for all login methods

Co-authored-by: Brian Gann <[email protected]>
  • Loading branch information
marcusramberg and briangann authored Oct 12, 2022
1 parent 81491b1 commit de05036
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 61 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ NOTE: Flags with parameters should use an "equals" (-autofit=true, -URL=https://
```TEXT
-URL string
URL to Grafana server (default "https://play.grafana.org")
-audience string
idtoken audience
-auto-login
oauth_auto_login is enabled in grafana config
-autofit
Expand All @@ -67,6 +69,8 @@ NOTE: Flags with parameters should use an "equals" (-autofit=true, -URL=https://
Fieldname for the username (default "username")
-ignore-certificate-errors
Ignore SSL/TLS certificate error
-keyfile string
idtoken json credentials (default "key.json")
-kiosk-mode string
Kiosk Display Mode [full|tv|disabled]
full = No TOPNAV and No SIDEBAR
Expand Down Expand Up @@ -146,6 +150,10 @@ They can also be used instead of a configuration file.
Username html input name value
KIOSK_GOAUTH_FIELD_PASSWORD string
Password html input name value
KIOSK_IDTOKEN_KEYFILE string
JSON Credentials for idtoken
KIOSK_IDTOKEN_AUDIENCE string
Audience for idtoken, tpyically your oauth client id
```

### Hosted Grafana using grafana.com authentication
Expand Down Expand Up @@ -216,6 +224,14 @@ This will login to a Generic Oauth service, configured on Grafana. Oauth_auto_lo
go run pkg/cmd/grafana-kiosk/main.go -URL=https://my.grafana.oauth/playlists/play/1 -login-method=goauth -username=test -password=test -field-username=username -field-password=password -auto-login=true
```

### Google Idtoken authentication

This allows you to log in through Google Identity Aware Proxy using a service account through injecting authorization headers with bearer tokens into each request. the idtoken library will generate new tokens as needed on expiry, allowing grafana kiosk mode without exposing a fully privileged google user on your kiosk device.

```bash
./bin/grafana-kiosk -URL=https://play.grafana.org/playlists/play/1 -login-method=idtoken -keyfile /tmp/foo.json -audience myoauthid.apps.googleusercontent.com
```

## LXDE Options

The `-lxde` option initializes settings for the desktop.
Expand Down
11 changes: 10 additions & 1 deletion pkg/cmd/grafana-kiosk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type Args struct {
IsPlayList bool
OauthAutoLogin bool
LXDEEnabled bool
Audience string
KeyFile string
LXDEHome string
ConfigPath string
Mode string
Expand All @@ -38,7 +40,7 @@ func ProcessArgs(cfg interface{}) Args {

flagSettings := flag.NewFlagSet("grafana-kiosk", flag.ContinueOnError)
flagSettings.StringVar(&processedArgs.ConfigPath, "c", "", "Path to configuration file (config.yaml)")
flagSettings.StringVar(&processedArgs.LoginMethod, "login-method", "anon", "[anon|local|gcom|goauth]")
flagSettings.StringVar(&processedArgs.LoginMethod, "login-method", "anon", "[anon|local|gcom|goauth|idtoken]")
flagSettings.StringVar(&processedArgs.Username, "username", "guest", "username")
flagSettings.StringVar(&processedArgs.Password, "password", "guest", "password")
flagSettings.StringVar(&processedArgs.Mode, "kiosk-mode", "full", "Kiosk Display Mode [full|tv|disabled]\nfull = No TOPNAV and No SIDEBAR\ntv = No SIDEBAR\ndisabled = omit option\n")
Expand All @@ -52,6 +54,7 @@ func ProcessArgs(cfg interface{}) Args {
flagSettings.BoolVar(&processedArgs.OauthAutoLogin, "auto-login", false, "oauth_auto_login is enabled in grafana config")
flagSettings.StringVar(&processedArgs.UsernameField, "field-username", "username", "Fieldname for the username")
flagSettings.StringVar(&processedArgs.PasswordField, "field-password", "password", "Fieldname for the password")
flagSettings.StringVar(&a.Audience, "audience", "", "idtoken audience")

fu := flagSettings.Usage
flagSettings.Usage = func() {
Expand Down Expand Up @@ -159,6 +162,9 @@ func main() {
cfg.GOAUTH.AutoLogin = args.OauthAutoLogin
cfg.GOAUTH.UsernameField = args.UsernameField
cfg.GOAUTH.PasswordField = args.PasswordField

cfg.IDTOKEN.Audience = args.Audience
cfg.IDTOKEN.KeyFile = args.KeyFile
}

summary(&cfg)
Expand Down Expand Up @@ -192,6 +198,9 @@ func main() {
case "goauth":
log.Printf("Launching Generic Oauth login kiosk")
kiosk.GrafanaKioskGenericOauth(&cfg)
case "idtoken":
log.Printf("Launching idtoken oauth kiosk")
kiosk.GrafanaKioskIdToken(&cfg)
default:
log.Printf("Launching ANON login kiosk")
kiosk.GrafanaKioskAnonymous(&cfg)
Expand Down
17 changes: 1 addition & 16 deletions pkg/kiosk/anonymous_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,7 @@ func GrafanaKioskAnonymous(cfg *Config) {
log.Println("Using temp dir:", dir)
defer os.RemoveAll(dir)

opts := []chromedp.ExecAllocatorOption{
chromedp.NoFirstRun,
chromedp.NoDefaultBrowserCheck,
chromedp.Flag("noerrdialogs", true),
chromedp.Flag("kiosk", true),
chromedp.Flag("bwsi", true),
chromedp.Flag("incognito", true),
chromedp.Flag("disable-sync", true),
chromedp.Flag("disable-notifications", true),
chromedp.Flag("disable-overlay-scrollbar", true),
chromedp.Flag("ignore-certificate-errors", cfg.Target.IgnoreCertificateErrors),
chromedp.Flag("test-type", cfg.Target.IgnoreCertificateErrors),
chromedp.Flag("window-position", cfg.General.WindowPosition),
chromedp.Flag("check-for-update-interval", "31536000"),
chromedp.UserDataDir(dir),
}
opts := generateExecutorOptions(dir, cfg.General.WindowPosition, cfg.Target.IgnoreCertificateErrors)

allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
Expand Down
4 changes: 4 additions & 0 deletions pkg/kiosk/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ type Config struct {
UsernameField string `yaml:"fieldname-username" env:"KIOSK_GOAUTH_FIELD_USER" env-description:"Username html input name value"`
PasswordField string `yaml:"fieldname-password" env:"KIOSK_GOAUTH_FIELD_PASSWORD" env-description:"Password html input name value"`
} `yaml:"goauth"`
IDTOKEN struct {
KeyFile string `yaml:"idtoken-keyfile" env:"KIOSK_IDTOKEN_KEYFILE" env-description:"JSON Credentials for idtoken"`
Audience string `yaml:"idtoken-audience" env:"KIOSK_IDTOKEN_AUDIENCE" env-description:"Audience for idtoken, tpyically your oauth client id"`
} `yaml:"goauth"`
}
15 changes: 1 addition & 14 deletions pkg/kiosk/grafana_com_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,7 @@ func GrafanaKioskGCOM(cfg *Config) {
log.Println("Using temp dir:", dir)
defer os.RemoveAll(dir)

opts := []chromedp.ExecAllocatorOption{
chromedp.NoFirstRun,
chromedp.NoDefaultBrowserCheck,
chromedp.Flag("noerrdialogs", true),
chromedp.Flag("kiosk", true),
chromedp.Flag("bwsi", true),
chromedp.Flag("incognito", true),
chromedp.Flag("disable-sync", true),
chromedp.Flag("disable-notifications", true),
chromedp.Flag("disable-overlay-scrollbar", true),
chromedp.Flag("window-position", cfg.General.WindowPosition),
chromedp.Flag("check-for-update-interval", "31536000"),
chromedp.UserDataDir(dir),
}
opts := generateExecutorOptions(dir, cfg.General.WindowPosition, cfg.Target.IgnoreCertificateErrors)

allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
Expand Down
15 changes: 1 addition & 14 deletions pkg/kiosk/grafana_genericoauth_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,7 @@ func GrafanaKioskGenericOauth(cfg *Config) {
log.Println("Using temp dir:", dir)
defer os.RemoveAll(dir)

opts := []chromedp.ExecAllocatorOption{
chromedp.NoFirstRun,
chromedp.NoDefaultBrowserCheck,
chromedp.Flag("noerrdialogs", true),
chromedp.Flag("kiosk", true),
chromedp.Flag("bwsi", true),
chromedp.Flag("incognito", true),
chromedp.Flag("disable-sync", true),
chromedp.Flag("disable-notifications", true),
chromedp.Flag("disable-overlay-scrollbar", true),
chromedp.Flag("window-position", cfg.General.WindowPosition),
chromedp.Flag("check-for-update-interval", "31536000"),
chromedp.UserDataDir(dir),
}
opts := generateExecutorOptions(dir, cfg.General.WindowPosition, cfg.Target.IgnoreCertificateErrors)

allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
Expand Down
91 changes: 91 additions & 0 deletions pkg/kiosk/grafana_idtoken_login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package kiosk

import (
"context"
"time"

"fmt"
"io/ioutil"
"log"
"os"

"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/fetch"

"github.com/chromedp/chromedp"

"google.golang.org/api/idtoken"
)

// GrafanaKioskGenericOauth creates a chrome-based kiosk using a oauth2 authenticated account
func GrafanaKioskIdToken(cfg *Config) {
dir, err := ioutil.TempDir("", "chromedp-example")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir)

opts := generateExecutorOptions(dir, cfg.General.WindowPosition, cfg.Target.IgnoreCertificateErrors)

allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()

// also set up a custom logger
taskCtx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
defer cancel()

listenChromeEvents(taskCtx, targetCrashed)

// ensure that the browser process is started
if err := chromedp.Run(taskCtx); err != nil {
panic(err)
}

var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList)
log.Println("Navigating to ", generatedURL)

log.Printf("Token is using audience %s and reading from %s\n",cfg.IDTOKEN.Audience, cfg.IDTOKEN.KeyFile)
ts, err := idtoken.NewTokenSource(context.Background(), cfg.IDTOKEN.Audience, idtoken.WithCredentialsFile(cfg.IDTOKEN.KeyFile))
if err != nil {
panic(err)
}

chromedp.ListenTarget(taskCtx, func(ev interface{}) {
switch ev := ev.(type) {
case *fetch.EventRequestPaused:
go func() {
fetchReq := fetch.ContinueRequest(ev.RequestID)
for k, v := range ev.Request.Headers {
fetchReq.Headers = append(fetchReq.Headers, &fetch.HeaderEntry{Name: k, Value: fmt.Sprintf("%v", v)})
}
token, err := ts.Token()
if err != nil {
panic(fmt.Errorf("idtoken.NewClient: %v", err))
}
fetchReq.Headers = append(fetchReq.Headers, &fetch.HeaderEntry{Name: "Authorization", Value: "Bearer " + token.AccessToken})
fetchReq.Do(GetExecutor(taskCtx))
}()
}
})

if err := chromedp.Run(taskCtx, enableFetch(generatedURL)); err != nil {
panic(err)
}
log.Println("Sleeping 2 seconds before exit.")
time.Sleep(2 * time.Second)
log.Println("Exit...")
}

func GetExecutor(ctx context.Context) context.Context {
c := chromedp.FromContext(ctx)
return cdp.WithExecutor(ctx, c.Target)
}


func enableFetch(url string) chromedp.Tasks {
return chromedp.Tasks{
fetch.Enable(),
chromedp.Navigate(url),
chromedp.WaitVisible("notinputPassword", chromedp.ByID),
}
}
17 changes: 1 addition & 16 deletions pkg/kiosk/local_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,7 @@ func GrafanaKioskLocal(cfg *Config) {
log.Println("Using temp dir:", dir)
defer os.RemoveAll(dir)

opts := []chromedp.ExecAllocatorOption{
chromedp.NoFirstRun,
chromedp.NoDefaultBrowserCheck,
chromedp.Flag("noerrdialogs", true),
chromedp.Flag("kiosk", true),
chromedp.Flag("bwsi", true),
chromedp.Flag("incognito", true),
chromedp.Flag("disable-sync", true),
chromedp.Flag("disable-notifications", true),
chromedp.Flag("disable-overlay-scrollbar", true),
chromedp.Flag("ignore-certificate-errors", cfg.Target.IgnoreCertificateErrors),
chromedp.Flag("test-type", cfg.Target.IgnoreCertificateErrors),
chromedp.Flag("window-position", cfg.General.WindowPosition),
chromedp.Flag("check-for-update-interval", "31536000"),
chromedp.UserDataDir(dir),
}
opts := generateExecutorOptions(dir, cfg.General.WindowPosition, cfg.Target.IgnoreCertificateErrors)

allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
Expand Down
21 changes: 21 additions & 0 deletions pkg/kiosk/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package kiosk
import (
"log"
"net/url"

"github.com/chromedp/chromedp"
)

// GenerateURL constructs URL with appropriate parameters for kiosk mode.
Expand Down Expand Up @@ -35,3 +37,22 @@ func GenerateURL(anURL string, kioskMode string, autoFit bool, isPlayList bool)

return parsedURI.String()
}

func generateExecutorOptions(dir string, windowPosition string, ignoreCertificateErrors bool) []chromedp.ExecAllocatorOption {
return []chromedp.ExecAllocatorOption{
chromedp.NoFirstRun,
chromedp.NoDefaultBrowserCheck,
chromedp.Flag("noerrdialogs", true),
chromedp.Flag("kiosk", true),
chromedp.Flag("bwsi", true),
chromedp.Flag("incognito", true),
chromedp.Flag("disable-sync", true),
chromedp.Flag("disable-notifications", true),
chromedp.Flag("disable-overlay-scrollbar", true),
chromedp.Flag("window-position", windowPosition),
chromedp.Flag("check-for-update-interval", "31536000"),
chromedp.Flag("ignore-certificate-errors", ignoreCertificateErrors),
chromedp.Flag("test-type", ignoreCertificateErrors),
chromedp.UserDataDir(dir),
}
}

0 comments on commit de05036

Please sign in to comment.