From ddaadbf1b7bfaac7036199f98c36ff53acf0ff04 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Sat, 29 Apr 2023 19:07:13 +0000 Subject: [PATCH 1/6] Remove auth from config command --- cmd/config.go | 104 ++---------------------------- cmd/config_test.go | 154 --------------------------------------------- 2 files changed, 6 insertions(+), 252 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index bb09007..47b521e 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -16,15 +16,10 @@ type configCmd struct { } type configOpts struct { - certPath string - keyPath string - url string - source string - authType string - idpURL string - usernameClaim string - clientID string - clientSecret string + certPath string + keyPath string + url string + source string } func newConfig(configOpts configOpts, item string) error { @@ -33,20 +28,6 @@ func newConfig(configOpts configOpts, item string) error { if err != nil { return fmt.Errorf("failed to write SSL config for Workbench: %w", err) } - } else if item == "auth" { - if configOpts.authType == "saml" { - err := workbench.WriteSAMLAuthConfig(configOpts.idpURL) - if err != nil { - return fmt.Errorf("failed to write SAML auth config for Workbench: %w", err) - } - } else if configOpts.authType == "oidc" { - err := workbench.WriteOIDCAuthConfig(configOpts.idpURL, configOpts.usernameClaim, configOpts.clientID, configOpts.clientSecret) - if err != nil { - return fmt.Errorf("failed to write OIDC auth config for Workbench: %w", err) - } - } else { - return fmt.Errorf("invalid auth type provided, please provide one of the following: saml, oidc") - } } else if item == "repo" { err := workbench.WriteRepoConfig(configOpts.url, configOpts.source) if err != nil { @@ -58,7 +39,7 @@ func newConfig(configOpts configOpts, item string) error { return fmt.Errorf("failed to write Connect URL config for Workbench: %w", err) } } else { - return fmt.Errorf("invalid item provided, please provide one of the following: ssl, auth, repo, connect-url") + return fmt.Errorf("invalid item provided, please provide one of the following: ssl, repo, connect-url") } return nil } @@ -68,11 +49,6 @@ func setConfigOpts(configOpts *configOpts) { configOpts.keyPath = viper.GetString("key-path") configOpts.url = viper.GetString("url") configOpts.source = viper.GetString("source") - configOpts.authType = viper.GetString("auth-type") - configOpts.idpURL = viper.GetString("idp-url") - configOpts.usernameClaim = viper.GetString("username-claim") - configOpts.clientID = viper.GetString("client-id") - configOpts.clientSecret = viper.GetString("client-secret") } func (opts *configOpts) Validate(args []string) error { @@ -83,29 +59,6 @@ func (opts *configOpts) Validate(args []string) error { return fmt.Errorf("too many arguments provided, please provide only one argument") } - // the auth-type flag is only valid for auth - if opts.authType != "" && args[0] != "auth" { - return fmt.Errorf("the auth-type flag is only valid for auth") - } - - // the idp-url flag is only valid for argument auth and auth-type saml or oidc - if opts.idpURL != "" && (args[0] != "auth" || (opts.authType != "saml" && opts.authType != "oidc")) { - return fmt.Errorf("the idp-url flag is only valid with auth as an argument and a auth-type flag of saml or oidc") - } - - // the username-claim flag is only valid for argument auth and auth-type oidc - if opts.usernameClaim != "" && (args[0] != "auth" || opts.authType != "oidc") { - return fmt.Errorf("the username-claim flag is only valid with auth as an argument and a auth-type flag of oidc") - } - // the client-id flag is only valid for argument auth and auth-type oidc - if opts.clientID != "" && (args[0] != "auth" || opts.authType != "oidc") { - return fmt.Errorf("the client-id flag is only valid with auth as an argument and a auth-type flag of oidc") - } - // the client-secret flag is only valid for argument auth and auth-type oidc - if opts.clientSecret != "" && (args[0] != "auth" || opts.authType != "oidc") { - return fmt.Errorf("the client-secret flag is only valid with auth as an argument and a auth-type flag of oidc") - } - // the cert-path flag is required for ssl if opts.certPath == "" && args[0] == "ssl" { return fmt.Errorf("the cert-path flag is required for ssl") @@ -154,30 +107,6 @@ func (opts *configOpts) Validate(args []string) error { return fmt.Errorf("the source flag only allows cran and pypi") } - // the auth-type flag is required for auth - if opts.authType == "" && args[0] == "auth" { - return fmt.Errorf("the auth-type flag is required for auth") - } - - // the idp-url flag is required for argument auth and auth-type saml - if opts.idpURL == "" && args[0] == "auth" && (opts.authType == "saml" || opts.authType == "oidc") { - return fmt.Errorf("the idp-url flag is required for argument auth and auth-type flag of saml or odic") - } - - // the client-id flag is required for argument auth and auth-type oidc - if opts.clientID == "" && args[0] == "auth" && opts.authType == "oidc" { - return fmt.Errorf("the client-id flag is required for argument auth and auth-type flag of odic") - } - // the client-secret flag is required for argument auth and auth-type oidc - if opts.clientSecret == "" && args[0] == "auth" && opts.authType == "oidc" { - return fmt.Errorf("the client-secret flag is required for argument auth and auth-type flag of odic") - } - - // the only valid authType flags are saml and oidc - if args[0] == "auth" && (opts.authType != "saml" && opts.authType != "oidc") { - return fmt.Errorf("the auth-type flag only allows saml and oidc") - } - return nil } @@ -191,12 +120,6 @@ func newConfigCmd() *configCmd { "To configure TLS/SSL:", " wbi config ssl --cert-path [PATH-TO-CERTIFICATE-FILE] --key-path [PATH-TO-KEY-FILE]", "", - "To configure SAML Authentication:", - " wbi config auth --auth-type saml --idp-url [IDP-SAML-METADATA-URL]", - "", - "To configure OIDC Authentication:", - " wbi config auth --auth-type oidc --idp-url [IDP-OIC-ISSUER-URL] --client-id [CLIENT-ID] --client-secret [CLIENT-SECRET]", - "", "To configure a default package repository:", " wbi config repo --url [REPO-BASE-URL] --source cran", " wbi config repo --url [REPO-BASE-URL] --source pypi", @@ -207,7 +130,7 @@ func newConfigCmd() *configCmd { cmd := &cobra.Command{ Use: "config [item]", - Short: "Configure SSL, Authentication, package repos, or a Connect server in Posit Workbench", + Short: "Configure SSL, package repos, or a Connect server in Posit Workbench", Example: strings.Join(exampleText, "\n"), PreRunE: func(cmd *cobra.Command, args []string) error { setConfigOpts(&root.opts) @@ -238,21 +161,6 @@ func newConfigCmd() *configCmd { cmd.Flags().StringP("source", "s", "", "Repository source (cran or pypi)") viper.BindPFlag("source", cmd.Flags().Lookup("source")) - cmd.Flags().StringP("auth-type", "a", "", "Authentication type (saml or oidc)") - viper.BindPFlag("auth-type", cmd.Flags().Lookup("auth-type")) - - cmd.Flags().StringP("idp-url", "i", "", "") - viper.BindPFlag("idp-url", cmd.Flags().Lookup("idp-url")) - - cmd.Flags().StringP("username-claim", "", "", "IdP Metdata URL for SAML or OIDC") - viper.BindPFlag("username-claim", cmd.Flags().Lookup("username-claim")) - - cmd.Flags().StringP("client-id", "", "", "OIDC Client ID") - viper.BindPFlag("client-id", cmd.Flags().Lookup("client-id")) - - cmd.Flags().StringP("client-secret", "", "", "OIDC Client Secret") - viper.BindPFlag("client-secret", cmd.Flags().Lookup("client-secret")) - root.cmd = cmd return root } diff --git a/cmd/config_test.go b/cmd/config_test.go index 4a57712..25363b9 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -50,82 +50,6 @@ func TestConfigParamsValidate(t *testing.T) { flags: configOpts{certPath: "cert.crt", keyPath: "cert.key", source: "cran"}, expectError: "the source flag is only valid for repo", }, - "ssl argument with auth-type flag fails": { - args: []string{"ssl"}, - flags: configOpts{certPath: "cert.crt", keyPath: "cert.key", authType: "saml"}, - expectError: "the auth-type flag is only valid for auth", - }, - "ssl argument with idp-url flag fails": { - args: []string{"ssl"}, - flags: configOpts{certPath: "cert.crt", keyPath: "cert.key", idpURL: "https://www.example.com"}, - expectError: "the idp-url flag is only valid with auth as an argument and a auth-type flag of saml or oidc", - }, - "ssl argument with username-claim flag fails": { - args: []string{"ssl"}, - flags: configOpts{certPath: "cert.crt", keyPath: "cert.key", usernameClaim: "user"}, - expectError: "the username-claim flag is only valid with auth as an argument and a auth-type flag of oidc", - }, - "ssl argument with client-id flag fails": { - args: []string{"ssl"}, - flags: configOpts{certPath: "cert.crt", keyPath: "cert.key", clientID: "user"}, - expectError: "the client-id flag is only valid with auth as an argument and a auth-type flag of oidc", - }, - "ssl argument with client-secret flag fails": { - args: []string{"ssl"}, - flags: configOpts{certPath: "cert.crt", keyPath: "cert.key", clientSecret: "user"}, - expectError: "the client-secret flag is only valid with auth as an argument and a auth-type flag of oidc", - }, - // auth argument tests - "auth argument only fails": { - args: []string{"auth"}, - flags: configOpts{}, - expectError: "the auth-type flag is required for auth", - }, - "auth argument with the auth-type flag fails": { - args: []string{"auth"}, - flags: configOpts{authType: "saml"}, - expectError: "the idp-url flag is required for argument auth and auth-type flag of saml or odic", - }, - "auth argument with the auth-type saml and idp-url flags set succeeds": { - args: []string{"auth"}, - flags: configOpts{authType: "saml", idpURL: "https://www.example.com"}, - expectError: "", - }, - "auth argument with the auth-type oidc and idp-url flags set fails": { - args: []string{"auth"}, - flags: configOpts{authType: "oidc", idpURL: "https://www.example.com"}, - expectError: "the client-id flag is required for argument auth and auth-type flag of odic", - }, - "auth argument with the auth-type oidc and idp-url and client-id flags set fails": { - args: []string{"auth"}, - flags: configOpts{authType: "oidc", idpURL: "https://www.example.com", clientID: "dadawdaw"}, - expectError: "the client-secret flag is required for argument auth and auth-type flag of odic", - }, - "auth argument with the auth-type oidc and idp-url, client-id, and client-secret flags set succeeds": { - args: []string{"auth"}, - flags: configOpts{authType: "oidc", idpURL: "https://www.example.com", clientID: "adwada", clientSecret: "adawdaw"}, - expectError: "", - }, - "auth argument with url flag fails": { - args: []string{"auth"}, - flags: configOpts{authType: "saml", idpURL: "https://www.example.com", url: "https://www.packagemanager.rstudio.com"}, - expectError: "the url flag is only valid for repo and connect-url", - }, - "auth argument with source flag fails": { - args: []string{"auth"}, - flags: configOpts{authType: "saml", idpURL: "https://www.example.com", source: "cran"}, - expectError: "the source flag is only valid for repo", - }, - "auth argument with cert-path flag fails": { - args: []string{"auth"}, - flags: configOpts{authType: "saml", idpURL: "https://www.example.com", certPath: "cert.crt"}, - expectError: "the cert-path flag is only valid for ssl", - }, - "auth argument with key-path flag fails": { - args: []string{"auth"}, - flags: configOpts{authType: "saml", idpURL: "https://www.example.com", keyPath: "cert.key"}, - expectError: "the key-path flag is only valid for ssl", - }, // repo argument tests "repo argument only fails": { args: []string{"repo"}, @@ -147,31 +71,6 @@ func TestConfigParamsValidate(t *testing.T) { flags: configOpts{url: "https://packagemanager.posit.co", source: "pypi"}, expectError: "", }, - "repo argument with auth-type flag fails": { - args: []string{"repo"}, - flags: configOpts{url: "https://packagemanager.posit.co", source: "cran", authType: "saml"}, - expectError: "the auth-type flag is only valid for auth", - }, - "repo argument with idp-url flag fails": { - args: []string{"repo"}, - flags: configOpts{url: "https://packagemanager.posit.co", source: "cran", idpURL: "https://www.example.com"}, - expectError: "the idp-url flag is only valid with auth as an argument and a auth-type flag of saml or oidc", - }, - "repo argument with username-claim flag fails": { - args: []string{"repo"}, - flags: configOpts{url: "https://packagemanager.posit.co", source: "cran", usernameClaim: "user"}, - expectError: "the username-claim flag is only valid with auth as an argument and a auth-type flag of oidc", - }, - "repo argument with client-id flag fails": { - args: []string{"repo"}, - flags: configOpts{url: "https://packagemanager.posit.co", source: "cran", clientID: "user"}, - expectError: "the client-id flag is only valid with auth as an argument and a auth-type flag of oidc", - }, - "repo argument with client-secret flag fails": { - args: []string{"repo"}, - flags: configOpts{url: "https://packagemanager.posit.co", source: "cran", clientSecret: "user"}, - expectError: "the client-secret flag is only valid with auth as an argument and a auth-type flag of oidc", - }, "repo argument with cert-path flag fails": { args: []string{"repo"}, flags: configOpts{url: "https://packagemanager.posit.co", source: "cran", certPath: "cert.crt"}, @@ -198,31 +97,6 @@ func TestConfigParamsValidate(t *testing.T) { flags: configOpts{url: "https://colorado.posit.co/rsc", source: "cran"}, expectError: "the source flag is only valid for repo", }, - "connect-url argument with auth-type flag fails": { - args: []string{"connect-url"}, - flags: configOpts{url: "https://packagemanager.posit.co", authType: "saml"}, - expectError: "the auth-type flag is only valid for auth", - }, - "connect-url argument with idp-url flag fails": { - args: []string{"connect-url"}, - flags: configOpts{url: "https://packagemanager.posit.co", idpURL: "https://www.example.com"}, - expectError: "the idp-url flag is only valid with auth as an argument and a auth-type flag of saml or oidc", - }, - "connect-url argument with username-claim flag fails": { - args: []string{"connect-url"}, - flags: configOpts{url: "https://packagemanager.posit.co", usernameClaim: "user"}, - expectError: "the username-claim flag is only valid with auth as an argument and a auth-type flag of oidc", - }, - "connect-url argument with client-id flag fails": { - args: []string{"connect-url"}, - flags: configOpts{url: "https://packagemanager.posit.co", clientID: "user"}, - expectError: "the client-id flag is only valid with auth as an argument and a auth-type flag of oidc", - }, - "connect-url argument with client-secret flag fails": { - args: []string{"connect-url"}, - flags: configOpts{url: "https://packagemanager.posit.co", clientSecret: "user"}, - expectError: "the client-secret flag is only valid with auth as an argument and a auth-type flag of oidc", - }, "connect-url argument with cert-path flag fails": { args: []string{"connect-url"}, flags: configOpts{url: "https://packagemanager.posit.co", certPath: "cert.crt"}, @@ -261,34 +135,6 @@ func TestConfigParamsValidate(t *testing.T) { // TODO TestConfigSSLCommandIntegration tests the config command with the ssl arg in a Docker container. -// TestConfigAuthSAMLCommandIntegration tests the config command with the auth arg, auth-type flag set to saml and idp-url flag set in a Docker container. -func TestConfigAuthSAMLCommandIntegration(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - // TODO actually verify the contents of rserver.conf - t.Parallel() - - installCommand := []string{"./wbi", "config", "auth", "--auth-type=saml", "--idp-url=https://www.example.com"} - successMessage := []string{"=== Writing to the file /etc/rstudio/rserver.conf:"} - - IntegrationContainerRunner(t, "Dockerfile.Workbench", installCommand, successMessage, false) -} - -// TestConfigAuthOIDCCommandIntegration tests the config command with the auth arg, auth-type flag set to oidc, idp-url flag, client-id flag and client-secret flag set in a Docker container. -func TestConfigAuthOIDCCommandIntegration(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - // TODO actually verify the contents of rserver.conf - t.Parallel() - - installCommand := []string{"./wbi", "config", "auth", "--auth-type=oidc", "--idp-url=https://www.example.com", "--client-id=awdawdawd", "--client-secret=adwawdawdfgawa"} - successMessage := []string{"=== Writing to the file /etc/rstudio/rserver.conf:"} - - IntegrationContainerRunner(t, "Dockerfile.Workbench", installCommand, successMessage, false) -} - // TestConfigRepoCRANCommandIntegration tests the config command with the repo arg, url flag set to Public Package Manager and source flag set to cran in a Docker container. func TestConfigRepoCRANCommandIntegration(t *testing.T) { if testing.Short() { From 1cd9029e2e5f8acfd639c49945d7dbf2acb3abff Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Sat, 29 Apr 2023 19:09:53 +0000 Subject: [PATCH 2/6] Remove auth from setup command and steps of setup --- README.md | 2 +- cmd/setup.go | 20 ++------------------ cmd/setup_test.go | 2 +- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a6b061c..b411d1b 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ You can also pass the `--step` flag to begin at a certain spot in the interactiv sudo wbi setup --step workbench ``` -The following steps are valid options: start, prereqs, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, auth, packagemanager, connect, restart, status, verify. +The following steps are valid options: start, prereqs, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, packagemanager, connect, restart, status, verify. ## Assumptions - Single server diff --git a/cmd/setup.go b/cmd/setup.go index f234c32..f4a90e8 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/samber/lo" - "github.com/sol-eng/wbi/internal/authentication" "github.com/sol-eng/wbi/internal/connect" "github.com/sol-eng/wbi/internal/jupyter" "github.com/sol-eng/wbi/internal/languages" @@ -203,21 +202,6 @@ func newSetup(setupOpts setupOpts) error { return fmt.Errorf("error writing ssl configuration to file rserver.conf: %w", err) } } - step = "auth" - } - - if step == "auth" { - // Authentication - authChoice, err := authentication.PromptAuth() - if err != nil { - return fmt.Errorf("issue selecting if Authentication is to be setup: %w", err) - } - if authChoice { - err = authentication.PromptAndConfigAuth(osType) - if err != nil { - return fmt.Errorf("issue prompting and configuring Authentication: %w", err) - } - } step = "packagemanager" } @@ -310,7 +294,7 @@ func (opts *setupOpts) Validate(args []string) error { return fmt.Errorf("no arguments are supported for this command") } // ensure step is valid - validSteps := []string{"start", "prereqs", "firewall", "security", "languages", "r", "python", "workbench", "license", "jupyter", "prodrivers", "ssl", "auth", "packagemanager", "connect", "restart", "status", "verify"} + validSteps := []string{"start", "prereqs", "firewall", "security", "languages", "r", "python", "workbench", "license", "jupyter", "prodrivers", "ssl", "packagemanager", "connect", "restart", "status", "verify"} if opts.step != "" && !lo.Contains(validSteps, opts.step) { return fmt.Errorf("invalid step: %s", opts.step) } @@ -352,7 +336,7 @@ func newSetupCmd() *setupCmd { SilenceUsage: true, } - stepHelp := `The step to start at. Valid steps are: start, prereqs, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, auth, packagemanager, connect, restart, status, verify.` + stepHelp := `The step to start at. Valid steps are: start, prereqs, firewall, security, languages, r, python, workbench, license, jupyter, prodrivers, ssl, packagemanager, connect, restart, status, verify.` cmd.Flags().StringP("step", "s", "", stepHelp) viper.BindPFlag("step", cmd.Flags().Lookup("step")) diff --git a/cmd/setup_test.go b/cmd/setup_test.go index f4887cd..baddb7e 100644 --- a/cmd/setup_test.go +++ b/cmd/setup_test.go @@ -26,7 +26,7 @@ func TestSetupParamsValidate(t *testing.T) { }, "no argument with a valid step succeeds": { args: []string{}, - flags: setupOpts{step: "auth"}, + flags: setupOpts{step: "packagemanager"}, expectError: "", }, "no argument with an invalid step fails": { From 6490c7b6adf451ddc950c88c20f71efab25879e4 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Sat, 29 Apr 2023 19:11:58 +0000 Subject: [PATCH 3/6] Remove auth functionality --- README.md | 4 - internal/authentication/oidc.go | 73 ---------------- internal/authentication/prompt.go | 137 ------------------------------ internal/authentication/saml.go | 41 --------- internal/config/config.go | 14 --- internal/workbench/config.go | 112 ------------------------ 6 files changed, 381 deletions(-) delete mode 100644 internal/authentication/oidc.go delete mode 100644 internal/authentication/prompt.go delete mode 100644 internal/authentication/saml.go diff --git a/README.md b/README.md index b411d1b..7b8eff0 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,6 @@ The following steps are valid options: start, prereqs, firewall, security, langu ### SSL - Record and specify where to put cert and key paths -### Authentication -- Provide information about PAM and AD/LDAP setup steps -- Record and specify where to put values for SAML and OIDC SSO setups - ### Posit Package Manager integration - Record, validate, and specify config for Posit Package Manger URL and R/Python repos - Automatically generate, validate and specify config for Posit Public Package Manager diff --git a/internal/authentication/oidc.go b/internal/authentication/oidc.go deleted file mode 100644 index 903a7d1..0000000 --- a/internal/authentication/oidc.go +++ /dev/null @@ -1,73 +0,0 @@ -package authentication - -import ( - "errors" - - "github.com/AlecAivazis/survey/v2" - log "github.com/sirupsen/logrus" -) - -// Prompt asking users to provide a client-id for OIDC -func PromptOIDCClientID() (string, error) { - name := "" - messageText := "OpenID Connect IdP provided client-id:" - prompt := &survey.Input{ - Message: messageText, - } - err := survey.AskOne(prompt, &name) - if err != nil { - return "", errors.New("there was an issue with the OIDC client-id prompt") - } - log.Info(messageText) - log.Info(name) - return name, nil -} - -// Prompt asking users to provide a client-secret for OIDC -func PromptOIDCClientSecret() (string, error) { - name := "" - messageText := "OpenID Connect IdP provided client-secret:" - prompt := &survey.Input{ - Message: messageText, - } - err := survey.AskOne(prompt, &name) - if err != nil { - return "", errors.New("there was an issue with the OIDC client-secret prompt") - } - log.Info(messageText) - log.Info(name) - return name, nil -} - -// Prompt asking users to provide an issuer URL for OIDC -func PromptOIDCIssuerURL() (string, error) { - name := "" - messageText := "OpenID Connect IdP provided issuer URL:" - prompt := &survey.Input{ - Message: messageText, - } - err := survey.AskOne(prompt, &name) - if err != nil { - return "", errors.New("there was an issue with the OIDC IdP issuer URL prompt") - } - log.Info(messageText) - log.Info(name) - return name, nil -} - -// Prompt asking users to provide a username claim for OIDC -func PromptOIDCUsernameClaim() (string, error) { - name := "preferred_username" - messageText := "OpenID Connect IdP provided username claim:" - prompt := &survey.Input{ - Message: messageText, - Default: "preferred_username", - } - err := survey.AskOne(prompt, &name) - if err != nil { - return "", errors.New("there was an issue with the OIDC IdP username claim prompt") - } - log.Info(messageText) - log.Info(name) - return name, nil -} diff --git a/internal/authentication/prompt.go b/internal/authentication/prompt.go deleted file mode 100644 index d2ce5a1..0000000 --- a/internal/authentication/prompt.go +++ /dev/null @@ -1,137 +0,0 @@ -package authentication - -import ( - "errors" - "fmt" - - log "github.com/sirupsen/logrus" - - "github.com/AlecAivazis/survey/v2" - "github.com/sol-eng/wbi/internal/config" - "github.com/sol-eng/wbi/internal/system" - "github.com/sol-eng/wbi/internal/workbench" -) - -// Prompt asking users if they wish to setup Authentication -func PromptAuth() (bool, error) { - name := false - messageText := "Would you like to setup Authentication?" - prompt := &survey.Confirm{ - Message: messageText, - } - err := survey.AskOne(prompt, &name) - if err != nil { - return false, errors.New("there was an issue with the Authentication prompt") - } - log.Info(messageText) - log.Info(fmt.Sprintf("%v", name)) - return name, nil -} - -func PromptAndConfigAuth(osType config.OperatingSystem) error { - authType, err := PromptAndConvertAuthType() - if err != nil { - return fmt.Errorf("issue entering and converting AuthType: %w", err) - } - err = HandleAuthChoice(authType, osType) - if err != nil { - return fmt.Errorf("issue handling authentication: %w", err) - } - return nil -} - -func PromptAndConvertAuthType() (config.AuthType, error) { - - authChoiceRaw, err := PromptAuthentication() - if err != nil { - return config.Other, fmt.Errorf("PromptAuthentication: %w", err) - } - authChoice, err := ConvertAuthType(authChoiceRaw) - if err != nil { - return config.Other, fmt.Errorf("PromptAuthentication: %w", err) - } - return authChoice, nil -} - -// Prompt asking user for their desired authentication method -func PromptAuthentication() (string, error) { - name := "" - messageText := "Choose an authentication method:" - prompt := &survey.Select{ - Message: messageText, - Options: []string{"SAML", "OIDC", "Active Directory/LDAP", "PAM", "Other"}, - } - err := survey.AskOne(prompt, &name) - if err != nil { - return "", errors.New("there was an issue with the authentication prompt") - } - log.Info(messageText) - log.Info(name) - return name, nil -} - -// Convert authChoice from a string to a proper AuthType type -func ConvertAuthType(authChoice string) (config.AuthType, error) { - switch authChoice { - case "SAML": - return config.SAML, nil - case "OIDC": - return config.OIDC, nil - case "Active Directory/LDAP": - return config.LDAP, nil - case "PAM": - return config.PAM, nil - case "Other": - return config.Other, nil - } - return config.Other, errors.New("unknown AuthType was selected") -} - -// Route an authentication choice to the proper prompts/information -func HandleAuthChoice(authType config.AuthType, targetOS config.OperatingSystem) error { - switch authType { - case config.SAML: - idpURL, err := PromptSAMLMetadataURL() - if err != nil { - return fmt.Errorf("issue entering SAML metadata URL: %w", err) - } - err = workbench.WriteSAMLAuthConfig(idpURL) - if err != nil { - return fmt.Errorf("failed to write SAML auth config: %w", err) - } - system.PrintAndLogInfo("Setting up SAML based authentication is a 2 step process. Step 1 was just completed by wbi writing the configuration file, however your SAML setup may require further configuration. \n\nTo complete step 2, you must configure your identify provider with Workbench following steps outlined here: https://docs.posit.co/ide/server-pro/authenticating_users/saml_sso.html#step-2.-configure-your-identity-provider-with-workbench") - case config.OIDC: - system.PrintAndLogInfo("Setting up OpenID Connect based authentication is a 2 step process. First configure your OpenID provider with the steps outlined here to complete step 1: https://docs.posit.co/ide/server-pro/authenticating_users/openid_connect_authentication.html#configuring-your-openid-provider \n\n As you register Workbench in the IdP, save the client-id and client-secret. Follow the next prompts to complete step 2.") - - clientID, err := PromptOIDCClientID() - if err != nil { - return fmt.Errorf("issue entering OIDC client ID: %w", err) - } - clientSecret, err := PromptOIDCClientSecret() - if err != nil { - return fmt.Errorf("issue entering OIDC client secret: %w", err) - } - idpURL, err := PromptOIDCIssuerURL() - if err != nil { - return fmt.Errorf("issue entering OIDC issuer URL: %w", err) - } - err = workbench.WriteOIDCAuthConfig(idpURL, "", clientID, clientSecret) - if err != nil { - return fmt.Errorf("failed to write OIDC auth config: %w", err) - } - case config.LDAP: - switch targetOS { - case config.Ubuntu18, config.Ubuntu20, config.Ubuntu22: - system.PrintAndLogInfo("Posit Workbench connects to LDAP via PAM. Please follow this article for more details on configuration: \nhttps://support.posit.co/hc/en-us/articles/360024137174-Integrating-Ubuntu-with-Active-Directory-for-RStudio-Workbench-RStudio-Server-Pro") - case config.Redhat7, config.Redhat8, config.Redhat9: - system.PrintAndLogInfo("Posit Workbench connects to LDAP via PAM. Please follow this article for more details on configuration: \nhttps://support.posit.co/hc/en-us/articles/360016587973-Integrating-RStudio-Workbench-RStudio-Server-Pro-with-Active-Directory-using-CentOS-RHEL") - default: - log.Fatal("Unsupported operating system") - } - case config.PAM: - system.PrintAndLogInfo("PAM requires no additional configuration, however there are some username considerations and home directory provisioning steps that can be taken. To learn more please visit: https://docs.posit.co/ide/server-pro/authenticating_users/pam_authentication.html") - case config.Other: - system.PrintAndLogInfo("To learn about configuring your desired method of authentication please visit: https://docs.posit.co/ide/server-pro/authenticating_users/authenticating_users.html") - } - return nil -} diff --git a/internal/authentication/saml.go b/internal/authentication/saml.go deleted file mode 100644 index e669b38..0000000 --- a/internal/authentication/saml.go +++ /dev/null @@ -1,41 +0,0 @@ -package authentication - -import ( - "errors" - - "github.com/AlecAivazis/survey/v2" - log "github.com/sirupsen/logrus" -) - -// Prompt asking users to provide a username attribute for SAML -func PromptSAMLAttribute() (string, error) { - name := "Username" - messageText := "SAML IdP username attribute:" - prompt := &survey.Input{ - Message: messageText, - Default: "Username", - } - err := survey.AskOne(prompt, &name) - if err != nil { - return "", errors.New("there was an issue with the SAML username attribute prompt") - } - log.Info(messageText) - log.Info(name) - return name, nil -} - -// Prompt asking users to provide a metadata URL for SAML -func PromptSAMLMetadataURL() (string, error) { - name := "" - messageText := "SAML IdP metadata URL:" - prompt := &survey.Input{ - Message: messageText, - } - err := survey.AskOne(prompt, &name) - if err != nil { - return "", errors.New("there was an issue with the SAML IdP URL prompt") - } - log.Info(messageText) - log.Info(name) - return name, nil -} diff --git a/internal/config/config.go b/internal/config/config.go index 5d6fc83..0eba49f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,19 +1,5 @@ package config -type AuthType int - -const ( - SAML AuthType = iota + 1 - OIDC - LDAP - PAM - Other -) - -func (d AuthType) String() string { - return [...]string{"SAML", "OIDC", "LDAP", "PAM", "Other"}[d] -} - type OperatingSystem int const ( diff --git a/internal/workbench/config.go b/internal/workbench/config.go index 158f2b3..a32a7cf 100644 --- a/internal/workbench/config.go +++ b/internal/workbench/config.go @@ -118,118 +118,6 @@ func WriteConnectURLConfig(url string) error { return nil } -// WriteSAMLAuthConfig writes the SAML auth config to the Workbench config file -func WriteSAMLAuthConfig(idpUrl string) error { - // check to ensure the lines don't already exist - linesCheck := []string{ - "auth-saml=", - "auth-saml-metadata-url=", - } - filepath := "/etc/rstudio/rserver.conf" - - var linesExist bool - for _, value := range linesCheck { - matched, err := system.CheckStringExists(value, filepath) - if err == nil && matched { - linesExist = true - } - } - - if !linesExist { - writeLines := []string{ - "auth-saml=1", - "auth-saml-metadata-url=" + idpUrl, - } - - system.PrintAndLogInfo("\n=== Writing to the file " + filepath + ":") - err := system.WriteStrings(writeLines, filepath, 0644) - if err != nil { - return fmt.Errorf("failed to write config: %w", err) - } - } else { - return fmt.Errorf("at least one line already exists in rserver.conf") - } - - return nil -} - -// WriteOIDCAuthConfig writes the OIDC auth config to the Workbench config file -func WriteOIDCAuthConfig(idpURL string, usernameClaim string, clientID string, clientSecret string) error { - // default in the configuration https://docs.posit.co/ide/server-pro/authenticating_users/openid_connect_authentication.html#openid-claims - if usernameClaim == "" { - usernameClaim = "preferred_username" - } - - // check to ensure the lines don't already exist - linesCheck := []string{ - "auth-openid=", - "auth-openid-issuer=", - "auth-openid-username-claim=", - } - filepathRserver := "/etc/rstudio/rserver.conf" - - var linesExist bool - for _, value := range linesCheck { - matched, err := system.CheckStringExists(value, filepathRserver) - if err == nil && matched { - linesExist = true - } - } - - if !linesExist { - // rserver.conf config options - writeLinesRserver := []string{ - "auth-openid=1", - "auth-openid-issuer=" + idpURL, - "auth-openid-username-claim=" + usernameClaim, - } - - system.PrintAndLogInfo("\n=== Writing to the file " + filepathRserver + ":") - err := system.WriteStrings(writeLinesRserver, filepathRserver, 0644) - if err != nil { - return fmt.Errorf("failed to write config: %w", err) - } - } else { - return fmt.Errorf("at least one line already exists in rserver.conf") - } - - if clientID != "" && clientSecret != "" { - - // check to ensure the lines don't already exist - linesCheck := []string{ - "client-id=", - "client-secret=", - } - filepathClientSecret := "/etc/rstudio/openid-client-secret" - - var linesExist bool - for _, value := range linesCheck { - matched, err := system.CheckStringExists(value, filepathClientSecret) - if err == nil && matched { - linesExist = true - } - } - - if !linesExist { - // openid-client-secret config options - writeLinesClientSecret := []string{ - "client-id=" + clientID, - "client-secret=" + clientSecret, - } - - system.PrintAndLogInfo("\n=== Writing to the file " + filepathClientSecret + ":") - err := system.WriteStrings(writeLinesClientSecret, filepathClientSecret, 0644) - if err != nil { - return fmt.Errorf("failed to write config: %w", err) - } - } else { - return fmt.Errorf("at least one line already exists in openid-client-secret") - } - } - - return nil -} - // WriteJupyterConfig writes the Jupyter config to the Workbench config file func WriteJupyterConfig(jupyterPath string) error { // TODO check to ensure line doesn't already exist and ideally take out the default commented out line to reduce confusion From e054403a81dd9407c1c1202b55f57ab14f3858d0 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Sat, 29 Apr 2023 19:14:04 +0000 Subject: [PATCH 4/6] Remove saml and oidc from prereq prompt --- internal/operatingsystem/prompt.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/operatingsystem/prompt.go b/internal/operatingsystem/prompt.go index 8f3d559..140bcdc 100644 --- a/internal/operatingsystem/prompt.go +++ b/internal/operatingsystem/prompt.go @@ -65,10 +65,8 @@ func PromptInstallPrereqs() (bool, error) { "3. The version of R and Python you would like to set as defaults\n" + "4. Your Workbench license key string in this form: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX\n" + "5. The location on this server of your SSL key and certificate files (optional)\n" + - "6. The IdP Metadata URL (optional, only for SAML SSO integration)\n" + - "7. The IdP Issuer URL, client id and client secret (optional, only for OIDC SSO integration)\n" + - "8. The URL and repo name for your instance of Posit Package Manager (optional)\n" + - "9. The URL for your instance of Posit Connect (optional)\n\n" + + "6. The URL and repo name for your instance of Posit Package Manager (optional)\n" + + "7. The URL for your instance of Posit Connect (optional)\n\n" + "Please confirm that you're ready to install Workbench" prompt := &survey.Confirm{ Message: messageText, From 776666069a1da1bc5acaba7afd8bbdebf022b438 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Sat, 29 Apr 2023 19:31:12 +0000 Subject: [PATCH 5/6] Add authentication message to the end with helpful links --- cmd/setup.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cmd/setup.go b/cmd/setup.go index f4a90e8..6cb0a2f 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/samber/lo" + "github.com/sol-eng/wbi/internal/config" "github.com/sol-eng/wbi/internal/connect" "github.com/sol-eng/wbi/internal/jupyter" "github.com/sol-eng/wbi/internal/languages" @@ -280,7 +281,23 @@ func newSetup(setupOpts setupOpts) error { step = "done" } - system.PrintAndLogInfo("\nThanks for using wbi!") + var adDocURL string + switch osType { + case config.Ubuntu18, config.Ubuntu20, config.Ubuntu22: + adDocURL = "https://support.posit.co/hc/en-us/articles/360024137174-Integrating-Ubuntu-with-Active-Directory-for-RStudio-Workbench-RStudio-Server-Pro" + case config.Redhat7, config.Redhat8, config.Redhat9: + adDocURL = "https://support.posit.co/hc/en-us/articles/360016587973-Integrating-RStudio-Workbench-RStudio-Server-Pro-with-Active-Directory-using-CentOS-RHEL" + } + + finalMessage := "\nThank you for using wbi! \n\n" + + "Workbench is now configured using the default PAM authentication method. Users with local Linux accounts and home directories should be able to log in to Workbench. \n\n" + + "Workbench integrates with a variety of Authentication types. To learn more about specific integrations, visit the documentation links below:\n" + + "For more information on PAM authentication https://docs.posit.co/ide/server-pro/authenticating_users/pam_authentication.html. \n" + "For more information on Active Directory authentication " + adDocURL + ". \n" + + "For more information on SAML Single Sign-On authentication https://docs.posit.co/ide/server-pro/authenticating_users/saml_sso.html. \n" + + "For more information on OpenID Connect Single Sign-On authentication https://docs.posit.co/ide/server-pro/authenticating_users/openid_connect_authentication.html. \n" + + "For more information on Proxied Authentication https://docs.posit.co/ide/server-pro/authenticating_users/proxied_authentication.html." + + system.PrintAndLogInfo(finalMessage) return nil } From 55d93936283ecdbcd6a29a888e3d9343d1bd6730 Mon Sep 17 00:00:00 2001 From: Trevor Nederlof Date: Sat, 29 Apr 2023 19:48:09 +0000 Subject: [PATCH 6/6] Fix to guard against no additional python kernels to register --- internal/jupyter/prompt.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/jupyter/prompt.go b/internal/jupyter/prompt.go index f6e2479..8f400ad 100644 --- a/internal/jupyter/prompt.go +++ b/internal/jupyter/prompt.go @@ -121,15 +121,17 @@ func ScanPromptInstallAndConfigJupyter() error { pythonVersionsLeft := removeString(pythonVersions, jupyterPythonTarget) // remove any non opt locations from the default selections defaultPythonVersions := removeNonOptPython(pythonVersionsLeft) - additionalPythonTargets, err := AdditionalKernelPrompt(pythonVersionsLeft, defaultPythonVersions) - if err != nil { - return fmt.Errorf("issue selecting additional Python kernels to register: %w", err) - } - // if one or more versions is selected then automatically register them - if len(additionalPythonTargets) > 0 { - err = RegisterJupyterKernels(additionalPythonTargets) + if len(pythonVersionsLeft) > 0 { + additionalPythonTargets, err := AdditionalKernelPrompt(pythonVersionsLeft, defaultPythonVersions) if err != nil { - return fmt.Errorf("issue registering additional Python kernels: %w", err) + return fmt.Errorf("issue selecting additional Python kernels to register: %w", err) + } + // if one or more versions is selected then automatically register them + if len(additionalPythonTargets) > 0 { + err = RegisterJupyterKernels(additionalPythonTargets) + if err != nil { + return fmt.Errorf("issue registering additional Python kernels: %w", err) + } } } }