From cf5c8cc8a90904fe233cd3470c32b608c8f69db1 Mon Sep 17 00:00:00 2001 From: Ronan SALMON Date: Wed, 23 Nov 2022 10:18:33 +0100 Subject: [PATCH] Adds support for Grafana API key Auth --- README.md | 15 ++++++-- pkg/cmd/grafana-kiosk/main.go | 11 ++++-- pkg/kiosk/apikey_login.go | 67 +++++++++++++++++++++++++++++++++++ pkg/kiosk/config.go | 3 ++ 4 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 pkg/kiosk/apikey_login.go diff --git a/README.md b/README.md index 1971064..d37579c 100644 --- a/README.md +++ b/README.md @@ -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") + -apikey string + apikey -audience string idtoken audience -auto-login @@ -78,7 +80,7 @@ NOTE: Flags with parameters should use an "equals" (-autofit=true, -URL=https:// disabled = omit option (default "full") -login-method string - [anon|local|gcom|goauth|idtoken] (default "anon") + [anon|local|gcom|goauth|idtoken|apikey] (default "anon") -lxde Initialize LXDE for kiosk mode -lxde-home string @@ -137,7 +139,7 @@ They can also be used instead of a configuration file. KIOSK_IS_PLAYLIST bool URL is a playlist (default "false") KIOSK_LOGIN_METHOD string - [anon|local|gcom|goauth|idtoken] (default "anon") + [anon|local|gcom|goauth|idtoken|apikey] (default "anon") KIOSK_LOGIN_PASSWORD string password (default "guest") KIOSK_URL string @@ -154,6 +156,8 @@ They can also be used instead of a configuration file. JSON Credentials for idtoken KIOSK_IDTOKEN_AUDIENCE string Audience for idtoken, tpyically your oauth client id + KIOSK_APIKEY_APIKEY string + Grafana API keys ``` ### Hosted Grafana using grafana.com authentication @@ -204,6 +208,13 @@ This will take the browser to a playlist on play.grafana.org in fullscreen kiosk ./bin/grafana-kiosk -URL=https://play.grafana.org/playlists/play/1 -login-method=anon -kiosk-mode=tv ``` +### Grafana Server with Api Key + +This will take the browser to the default dashboard on play.grafana.org in fullscreen kiosk mode: +```bash +./bin/grafana-kiosk -URL=https://play.grafana.org -login-method apikey --apikey "xxxxxxxxxxxxxxx" -kiosk-mode=tv +``` + ### Grafana Server with Generic Oauth This will login to a Generic Oauth service, configured on Grafana. Oauth_auto_login is disabeld. As Oauth provider is Keycloak used. diff --git a/pkg/cmd/grafana-kiosk/main.go b/pkg/cmd/grafana-kiosk/main.go index 912b444..5ea53c5 100644 --- a/pkg/cmd/grafana-kiosk/main.go +++ b/pkg/cmd/grafana-kiosk/main.go @@ -22,6 +22,7 @@ type Args struct { LXDEEnabled bool Audience string KeyFile string + Apikey string LXDEHome string ConfigPath string Mode string @@ -40,7 +41,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|idtoken]") + flagSettings.StringVar(&processedArgs.LoginMethod, "login-method", "anon", "[anon|local|gcom|goauth|idtoken|apikey]") 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") @@ -56,6 +57,7 @@ func ProcessArgs(cfg interface{}) Args { flagSettings.StringVar(&processedArgs.PasswordField, "field-password", "password", "Fieldname for the password") flagSettings.StringVar(&processedArgs.Audience, "audience", "", "idtoken audience") flagSettings.StringVar(&processedArgs.KeyFile, "keyfile", "key.json", "idtoken json credentials") + flagSettings.StringVar(&processedArgs.Apikey, "apikey", "", "apikey") fu := flagSettings.Usage flagSettings.Usage = func() { @@ -126,7 +128,7 @@ func main() { // validate auth methods switch args.LoginMethod { - case "goauth", "anon", "local", "gcom", "idtoken": + case "goauth", "anon", "local", "gcom", "idtoken", "apikey": default: log.Println("Invalid auth method", args.LoginMethod) os.Exit(-1) @@ -166,6 +168,8 @@ func main() { cfg.IDTOKEN.Audience = args.Audience cfg.IDTOKEN.KeyFile = args.KeyFile + + cfg.APIKEY.Apikey = args.Apikey } summary(&cfg) @@ -202,6 +206,9 @@ func main() { case "idtoken": log.Printf("Launching idtoken oauth kiosk") kiosk.GrafanaKioskIDToken(&cfg) + case "apikey": + log.Printf("Launching apikey kiosk") + kiosk.GrafanaKioskApikey(&cfg) default: log.Printf("Launching ANON login kiosk") kiosk.GrafanaKioskAnonymous(&cfg) diff --git a/pkg/kiosk/apikey_login.go b/pkg/kiosk/apikey_login.go new file mode 100644 index 0000000..360cc68 --- /dev/null +++ b/pkg/kiosk/apikey_login.go @@ -0,0 +1,67 @@ +package kiosk + +import ( + "context" + "log" + "os" + "time" + + + "github.com/chromedp/cdproto/network" + "github.com/chromedp/chromedp" +) + +// GrafanaKioskApikey creates a chrome-based kiosk using a grafana api key. +func GrafanaKioskApikey(cfg *Config) { + dir, err := os.MkdirTemp(os.TempDir(), "chromedp-kiosk") + if err != nil { + panic(err) + } + + log.Println("Using temp dir:", dir) + 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, consoleAPICall|targetCrashed) + + // ensure that the browser process is started + if err := chromedp.Run(taskCtx); err != nil { + panic(err) + } + + // Give browser time to load next page (this can be prone to failure, explore different options vs sleeping) + time.Sleep(2000 * time.Millisecond) + + var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + + log.Println("Navigating to ", generatedURL) + /* + Launch chrome and look for main-view element + */ + headers := map[string]interface{}{ + "Authorization": "Bearer " + cfg.APIKEY.Apikey, + } + if err := chromedp.Run(taskCtx, + network.Enable(), + network.SetExtraHTTPHeaders(network.Headers(headers)), + chromedp.Navigate(generatedURL), + chromedp.WaitVisible(`//div[@class="main-view"]`, chromedp.BySearch), + // wait forever (for now) + chromedp.WaitVisible("notinputPassword", chromedp.ByID), + ); err != nil { + panic(err) + } + + log.Println("Sleep before exit...") + // wait here for the process to exit + time.Sleep(2000 * time.Millisecond) + log.Println("Exit...") +} diff --git a/pkg/kiosk/config.go b/pkg/kiosk/config.go index fa9b24a..fcbe92b 100644 --- a/pkg/kiosk/config.go +++ b/pkg/kiosk/config.go @@ -26,4 +26,7 @@ type Config struct { KeyFile string `yaml:"idtoken-keyfile" env:"KIOSK_IDTOKEN_KEYFILE" env-default:"key.json" 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:"idtoken"` + APIKEY struct { + Apikey string `yaml:"apikey" env:"KIOSK_APIKEY_APIKEY" env-description:"APIKEY"` + } `yaml:"apikey"` }