Skip to content

Commit

Permalink
Merge pull request #2 from dpastoor/tn-add-auth-licensing
Browse files Browse the repository at this point in the history
  • Loading branch information
dpastoor authored Feb 8, 2023
2 parents 986f3a6 + 8d7c90a commit 7e0cda5
Show file tree
Hide file tree
Showing 21 changed files with 862 additions and 164 deletions.
61 changes: 58 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,61 @@
# (W)ork(b)ench (I)nstaller

A hacking project done at Posit Workweek 2022 with the solutions
engineering team.
## Functionality
- Verify Workbench is installed & output version
- Ask which languages will be used
- R is required
- Scan and output of found R installations
- If no /opt/R locations found, tell user about the Posit Installation recommendations
- Python is optional
- Scan and output of found Python installations
- If no /opt/python locations found, tell user about the Posit Installation recommendations
- Ask if Jupyter should be installed
- Ask which Python location Jupyter should be installed into
- Install jupyter, jupyterlab, rsp_jupyter, rsconnect_jupyter and workbench_jupyterlab
- Install and enable Jupyter Notebook extensions
- Ask if SSL should be setup
- Ask for cert location
- Ask for cert key location
- Ask for desired authentication method
- Current choices are:
- SAML
- Ask for IdP metadata URL
- Ask for IdP username attribute (default provided)
- Link to IdP setup in Admin guide provided
- OIDC
- Link to IdP setup in Admin guide provided
- Ask for IdP client-id
- Ask for IdP client-secret
- Ask for IdP issuer URL
- Ask for IdP username claim (default provided)
- AD/LDAP
- Provide links to support articles for integrating Active Directory for the operating systems below (detected automatically)
- Ubuntu
- RHEL
- PAM
- Provide link for PAM customization
- Other
- Provide link for other authentication methods
- Ask for Workbench license key
- Activate Workbench

Let's make configuring workbench easier :-)

## Assumptions
- Single server
- SQLite database
- Workbench has already been installed
- R has already been installed
- Python has already been installed
- Internet access (online installation)

## TODO
- Display progress for command outputs
- Present user at the end with all known configuration info
- Write out configuration files
- Verify SSL certs
- Install Workbench from WBI
- Present possible R versions and allow user to install from WBI
- Present possible Python versions and allow user to install from WBI
- Provide a branch for HA setup
- PostgreSQL details
- NFS details
154 changes: 83 additions & 71 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package cmd
import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/dpastoor/wbi/internal/authentication"
"github.com/dpastoor/wbi/internal/config"
"github.com/dpastoor/wbi/internal/jupyter"
"github.com/dpastoor/wbi/internal/langscanner"
"github.com/dpastoor/wbi/internal/languages"
"github.com/dpastoor/wbi/internal/license"
"github.com/dpastoor/wbi/internal/os"
"github.com/dpastoor/wbi/internal/ssl"
"github.com/dpastoor/wbi/internal/workbench"
"github.com/samber/lo"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand All @@ -25,92 +29,100 @@ func newSetup(setupOpts setupOpts) error {

var WBConfig config.WBConfig

//TODO: check if workbench installed

// Ask language question
var qs = []*survey.Question{
{
Name: "languages",
Prompt: &survey.MultiSelect{
Message: "What languages will you use",
Options: []string{"R", "python"},
Default: []string{"R", "python"},
},
},
// Workbench installation
_, err := workbench.VerifyWorkbench()
if err != nil {
return err
}

// Determine OS
osType, err := os.DetectOS()
if err != nil {
return err
}

// Languages
selectedLanguages, err := languages.PromptAndRespond()
if err != nil {
return fmt.Errorf("issue selecting languages: %w", err)
}

WBConfig.RConfig.Paths, err = languages.ScanAndHandleRVersions()
if err != nil {
return fmt.Errorf("issue finding R locations: %w", err)
}

if lo.Contains(selectedLanguages, "python") {
WBConfig.PythonConfig.Paths, err = languages.ScanAndHandlePythonVersions()
if err != nil {
return fmt.Errorf("issue finding Python locations: %w", err)
}
}
languageAnswers := struct {
Languages []string `survey:"languages"`
}{}
err := survey.Ask(qs, &languageAnswers)
fmt.Println("You just chose the languages: ", languageAnswers.Languages)
WBConfig.RConfig, err = langscanner.ScanAndHandleRVersions()
WBConfig.PythonConfig, err = langscanner.ScanAndHandlePythonVersions()

// If python found -- setup jupyter or ask to setup jupyter or check

// Jupyter
if len(WBConfig.PythonConfig.Paths) > 0 {
// Ask if jupyter should be installed
jupyterInstallName := true
jupyterInstallPrompt := &survey.Confirm{
Message: "Would you like to install Jupyter?",
jupyterChoice, err := jupyter.InstallPrompt()
if err != nil {
return fmt.Errorf("issue selecting Jupyter: %w", err)
}
survey.AskOne(jupyterInstallPrompt, &jupyterInstallName)
// If Jupyter, then do the install steps
if jupyterInstallName {
// Allow the user to select a version of Python to target
jupyterPythonTarget := ""
jupyterPythonPrompt := &survey.Select{
Message: "Select a Python kernel to install Jupyter into:",
Options: WBConfig.PythonConfig.Paths,

if jupyterChoice {
jupyterPythonTarget, err := jupyter.KernelPrompt(&WBConfig.PythonConfig)
if err != nil {
return fmt.Errorf("issue selecting Python location for Jupyter: %w", err)
}
survey.AskOne(jupyterPythonPrompt, &jupyterPythonTarget)
// Install Jupyter
jupyterInstallError := jupyter.InstallJupyter(jupyterPythonTarget)
if jupyterInstallError != nil {
log.Fatal(jupyterInstallError)

if jupyterPythonTarget != "" {
err := jupyter.InstallJupyter(jupyterPythonTarget)
if err != nil {
return fmt.Errorf("issue installing Jupyter: %w", err)
}
}
}
}

// Handle SSL cert
// * ask if want SSL
sslCertName := false
sslCertPrompt := &survey.Confirm{
Message: "Would you like to use SSL?",
// SSL
useSSL, err := ssl.PromptSSL()
if err != nil {
return fmt.Errorf("issue selecting if SSL is to be used: %w", err)
}
survey.AskOne(sslCertPrompt, &sslCertName)

if sslCertName {
// Ask for cert and key locations
certLocationName := ""
certLocationPrompt := &survey.Input{
Message: "Filepath to SSL certificate:",
if useSSL {
sslCertPath, err := ssl.PromptSSLFilePath()
if err != nil {
return fmt.Errorf("issue with the provided SSL cert path: %w", err)
}
survey.AskOne(certLocationPrompt, &certLocationName)

certKeyLocationName := ""
certKeyLocationPrompt := &survey.Input{
Message: "Filepath to SSL certificate key:",
sslCertKeyPath, err := ssl.PromptSSLKeyFilePath()
if err != nil {
return fmt.Errorf("issue with the provided SSL cert key path: %w", err)
}
survey.AskOne(certKeyLocationPrompt, &certKeyLocationName)
// Check to make sure cert and key are valid
certVerificationError := ssl.VerifySSLCertAndKey(certLocationName, certKeyLocationName)
if certVerificationError != nil {
log.Fatal(certVerificationError)
verifySSLCert := ssl.VerifySSLCertAndKey(sslCertPath, sslCertKeyPath)
if verifySSLCert != nil {
return fmt.Errorf("could not verify the SSL cert: %w", err)
}
fmt.Println("SSL successfully setup and verified")
}

// Handle authentication
choosenAuthenticationName := ""
choosenAuthenticationPrompt := &survey.Select{
Message: "Choose an authentication method:",
Options: []string{"SAML", "OIDC", "Active Directory/LDAP", "PAM", "Other"},
// Authentication
WBConfig.AuthType, err = authentication.PromptAndConvertAuthType()
if err != nil {
return fmt.Errorf("issue entering and converting AuthType: %w", err)
}
AuthErr := authentication.HandleAuthChoice(&WBConfig, osType)
if AuthErr != nil {
return fmt.Errorf("issue handling authentication: %w", AuthErr)
}
survey.AskOne(choosenAuthenticationPrompt, &choosenAuthenticationName)
// TODO: Handle based on the choosen method

// TODO: Handle license key
// Licensing
licenseKey, err := license.PromptLicense()
if err != nil {
return fmt.Errorf("issue entering license key: %w", err)
}
ActivateErr := license.ActivateLicenseKey(licenseKey)
if ActivateErr != nil {
return fmt.Errorf("issue activating license key: %w", ActivateErr)
}

return err
return nil
}

func setSetupOpts(setupOpts *setupOpts) {
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.7.4 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
Expand Down Expand Up @@ -232,6 +234,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down Expand Up @@ -348,6 +352,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
Expand Down
1 change: 0 additions & 1 deletion internal/authentication/ldap.go

This file was deleted.

91 changes: 91 additions & 0 deletions internal/authentication/oidc.go
Original file line number Diff line number Diff line change
@@ -1 +1,92 @@
package authentication

import (
"errors"
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/dpastoor/wbi/internal/config"
)

// Run functions and store values in the OIDCConfig
func HandleOIDCConfig(OIDCConfig *config.OIDCConfig) error {
OIDCConfig.AuthOpenID = 1

ClientID, err := PromptOIDCClientID()
OIDCConfig.ClientID = ClientID
if err != nil {
return fmt.Errorf("PromptOIDCClientID: %w", err)
}

ClientSecret, err := PromptOIDCClientSecret()
OIDCConfig.ClientSecret = ClientSecret
if err != nil {
return fmt.Errorf("PromptOIDCClientSecret: %w", err)
}

AuthOpenIDIssuer, err := PromptOIDCIssuerURL()
OIDCConfig.AuthOpenIDIssuer = AuthOpenIDIssuer
if err != nil {
return fmt.Errorf("PromptOIDCIssuerURL: %w", err)
}

AuthOpenIDUsernameClaim, err := PromptOIDCUsernameClaim()
OIDCConfig.AuthOpenIDUsernameClaim = AuthOpenIDUsernameClaim
if err != nil {
return fmt.Errorf("PromptOIDCUsernameClaim: %w", err)
}
return nil
}

// Prompt asking users to provide a client-id for OIDC
func PromptOIDCClientID() (string, error) {
name := ""
prompt := &survey.Input{
Message: "OpenID Connect IdP provided client-id:",
}
err := survey.AskOne(prompt, &name)
if err != nil {
return "", errors.New("there was an issue with the OIDC client-id prompt")
}
return name, nil
}

// Prompt asking users to provide a client-secret for OIDC
func PromptOIDCClientSecret() (string, error) {
name := ""
prompt := &survey.Input{
Message: "OpenID Connect IdP provided client-secret:",
}
err := survey.AskOne(prompt, &name)
if err != nil {
return "", errors.New("there was an issue with the OIDC client-secret prompt")
}
return name, nil
}

// Prompt asking users to provide an issuer URL for OIDC
func PromptOIDCIssuerURL() (string, error) {
name := ""
prompt := &survey.Input{
Message: "OpenID Connect IdP provided issuer URL:",
}
err := survey.AskOne(prompt, &name)
if err != nil {
return "", errors.New("there was an issue with the OIDC IdP issuer URL prompt")
}
return name, nil
}

// Prompt asking users to provide a username claim for OIDC
func PromptOIDCUsernameClaim() (string, error) {
name := "preferred_username"
prompt := &survey.Input{
Message: "OpenID Connect IdP provided username claim:",
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")
}
return name, nil
}
1 change: 0 additions & 1 deletion internal/authentication/pam.go

This file was deleted.

Loading

0 comments on commit 7e0cda5

Please sign in to comment.