Skip to content

Commit

Permalink
Merge pull request #676 from hiruna/feature/okta_cache
Browse files Browse the repository at this point in the history
Use Okta Sessions API
  • Loading branch information
Mark Wolfe authored Jun 30, 2021
2 parents beaaece + 752a5b7 commit a882ec4
Show file tree
Hide file tree
Showing 12 changed files with 579 additions and 96 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ Commands:
--config=CONFIG Path/filename of saml2aws config file (env: SAML2AWS_CONFIGFILE)
--cache-saml Caches the SAML response (env: SAML2AWS_CACHE_SAML)
--cache-file=CACHE-FILE The location of the SAML cache file (env: SAML2AWS_SAML_CACHE_FILE)
--disable-sessions Do not use Okta sessions. Uses Okta sessions by default. (env: SAML2AWS_OKTA_DISABLE_SESSIONS)
--disable-remember-device Do not remember Okta MFA device. Remembers MFA device by default. (env: SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE)
login [<flags>]
Login to a SAML 2.0 IDP and convert the SAML assertion to an STS token.
Expand All @@ -199,7 +201,8 @@ Commands:
The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE)
--cache-saml Caches the SAML response (env: SAML2AWS_CACHE_SAML)
--cache-file=CACHE-FILE The location of the SAML cache file (env: SAML2AWS_SAML_CACHE_FILE)
--disable-sessions Do not use Okta sessions. Uses Okta sessions by default. (env: SAML2AWS_OKTA_DISABLE_SESSIONS)
--disable-remember-device Do not remember Okta MFA device. Remembers MFA device by default. (env: SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE)
exec [<flags>] [<command>...]
Exec the supplied command with env vars from STS token.
Expand Down Expand Up @@ -677,6 +680,23 @@ there is a file per saml2aws profile, the cache directory is called `saml2aws` a

You can toggle `--cache-saml` during `login` or during `list-roles`, and you can set it once during `configure` and use it implicitly.

# Okta Sessions

This requires the use of the keychain (local credentials store). If you disabled the keychain using `--disable-keychain`, Okta sessions will also be disabled.

Okta sessions are enabled by default. This will store the Okta session locally and save your device for MFA. This means that if the session has not yet expired, you will not be prompted for MFA.

* To disable remembering the device, you can toggle `--disable-remember-device` during `login` or `configure` commands.
* To disable using Okta sessions, you can toggle `--disable-sessions` during `login` or `configure` commands.
* This will also disable the Okta MFA remember device feature

Use the `--force` flag during `login` command to prompt for AWS role selection.

If Okta sessions are disabled via any of the methods mentioned above, the login process will default to the standard authentication process (without using sessions).

Please note that your Okta session duration and MFA policies are governed by your Okta host organization.


# License

This code is Copyright (c) 2018 [Versent](http://versent.com.au) and released under the MIT license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE.md file for more details.
Expand Down
6 changes: 6 additions & 0 deletions cmd/saml2aws/commands/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"log"
"os"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -187,6 +188,11 @@ func resolveLoginDetails(account *cfg.IDPAccount, loginFlags *flags.LoginExecFla
return nil, errors.Wrap(err, "error loading saved password")
}
}
} else { // if user disabled keychain, dont use Okta sessions & dont remember Okta MFA device
if strings.ToLower(account.Provider) == "okta" {
account.DisableSessions = true
account.DisableRememberDevice = true
}
}

// log.Printf("%s %s", savedUsername, savedPassword)
Expand Down
34 changes: 34 additions & 0 deletions cmd/saml2aws/commands/login_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -29,6 +30,39 @@ func TestResolveLoginDetailsWithFlags(t *testing.T) {
assert.Equal(t, &creds.LoginDetails{Username: "wolfeidau", Password: "testtestlol", URL: "https://id.example.com", MFAToken: "123456"}, loginDetails)
}

func TestOktaResolveLoginDetailsWithFlags(t *testing.T) {

// Default state - user did not supply values for DisableSessions and DisableSessions
commonFlags := &flags.CommonFlags{URL: "https://id.example.com", Username: "testuser", Password: "testtestlol", MFAToken: "123456", SkipPrompt: true}
loginFlags := &flags.LoginExecFlags{CommonFlags: commonFlags}

idpa := &cfg.IDPAccount{
URL: "https://id.example.com",
MFA: "none",
Provider: "Okta",
Username: "testuser",
}
loginDetails, err := resolveLoginDetails(idpa, loginFlags)

assert.Nil(t, err)
assert.False(t, idpa.DisableSessions, fmt.Errorf("default state, DisableSessions should be false"))
assert.False(t, idpa.DisableRememberDevice, fmt.Errorf("default state, DisableRememberDevice should be false"))
assert.Equal(t, &creds.LoginDetails{Username: "testuser", Password: "testtestlol", URL: "https://id.example.com", MFAToken: "123456"}, loginDetails)

// User disabled keychain, resolveLoginDetails should set the account's DisableSessions and DisableSessions fields to true

commonFlags = &flags.CommonFlags{URL: "https://id.example.com", Username: "testuser", Password: "testtestlol", MFAToken: "123456", SkipPrompt: true, DisableKeychain: true}
loginFlags = &flags.LoginExecFlags{CommonFlags: commonFlags}

loginDetails, err = resolveLoginDetails(idpa, loginFlags)

assert.Nil(t, err)
assert.True(t, idpa.DisableSessions, fmt.Errorf("user disabled keychain, DisableSessions should be true"))
assert.True(t, idpa.DisableRememberDevice, fmt.Errorf("user disabled keychain, DisableRememberDevice should be true"))
assert.Equal(t, &creds.LoginDetails{Username: "testuser", Password: "testtestlol", URL: "https://id.example.com", MFAToken: "123456"}, loginDetails)

}

func TestResolveRoleSingleEntry(t *testing.T) {

adminRole := &saml2aws.AWSRole{
Expand Down
6 changes: 5 additions & 1 deletion cmd/saml2aws/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func main() {
app.Flag("aws-urn", "The URN used by SAML when you login. (env: SAML2AWS_AWS_URN)").Envar("SAML2AWS_AWS_URN").StringVar(&commonFlags.AmazonWebservicesURN)
app.Flag("skip-prompt", "Skip prompting for parameters during login.").BoolVar(&commonFlags.SkipPrompt)
app.Flag("session-duration", "The duration of your AWS Session. (env: SAML2AWS_SESSION_DURATION)").Envar("SAML2AWS_SESSION_DURATION").IntVar(&commonFlags.SessionDuration)
app.Flag("disable-keychain", "Do not use keychain at all.").Envar("SAML2AWS_DISABLE_KEYCHAIN").BoolVar(&commonFlags.DisableKeychain)
app.Flag("disable-keychain", "Do not use keychain at all. This will also disable Okta sessions & remembering MFA device. (env: SAML2AWS_DISABLE_KEYCHAIN)").Envar("SAML2AWS_DISABLE_KEYCHAIN").BoolVar(&commonFlags.DisableKeychain)
app.Flag("region", "AWS region to use for API requests, e.g. us-east-1, us-gov-west-1, cn-north-1 (env: SAML2AWS_REGION)").Envar("SAML2AWS_REGION").Short('r').StringVar(&commonFlags.Region)

// `configure` command and settings
Expand All @@ -94,6 +94,8 @@ func main() {
cmdConfigure.Flag("credentials-file", "The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE)").Envar("SAML2AWS_CREDENTIALS_FILE").StringVar(&commonFlags.CredentialsFile)
cmdConfigure.Flag("cache-saml", "Caches the SAML response (env: SAML2AWS_CACHE_SAML)").Envar("SAML2AWS_CACHE_SAML").BoolVar(&commonFlags.SAMLCache)
cmdConfigure.Flag("cache-file", "The location of the SAML cache file (env: SAML2AWS_SAML_CACHE_FILE)").Envar("SAML2AWS_SAML_CACHE_FILE").StringVar(&commonFlags.SAMLCacheFile)
cmdConfigure.Flag("disable-sessions", "Do not use Okta sessions. Uses Okta sessions by default. (env: SAML2AWS_OKTA_DISABLE_SESSIONS)").Envar("SAML2AWS_OKTA_DISABLE_SESSIONS").BoolVar(&commonFlags.DisableSessions)
cmdConfigure.Flag("disable-remember-device", "Do not remember Okta MFA device. Remembers MFA device by default. (env: SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE)").Envar("SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE").BoolVar(&commonFlags.DisableRememberDevice)
configFlags := commonFlags

// `login` command and settings
Expand All @@ -109,6 +111,8 @@ func main() {
cmdLogin.Flag("credentials-file", "The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE)").Envar("SAML2AWS_CREDENTIALS_FILE").StringVar(&commonFlags.CredentialsFile)
cmdLogin.Flag("cache-saml", "Caches the SAML response (env: SAML2AWS_CACHE_SAML)").Envar("SAML2AWS_CACHE_SAML").BoolVar(&commonFlags.SAMLCache)
cmdLogin.Flag("cache-file", "The location of the SAML cache file (env: SAML2AWS_SAML_CACHE_FILE)").Envar("SAML2AWS_SAML_CACHE_FILE").StringVar(&commonFlags.SAMLCacheFile)
cmdLogin.Flag("disable-sessions", "Do not use Okta sessions. Uses Okta sessions by default. (env: SAML2AWS_OKTA_DISABLE_SESSIONS)").Envar("SAML2AWS_OKTA_DISABLE_SESSIONS").BoolVar(&commonFlags.DisableSessions)
cmdLogin.Flag("disable-remember-device", "Do not remember Okta MFA device. Remembers MFA device by default. (env: SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE)").Envar("SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE").BoolVar(&commonFlags.DisableRememberDevice)

// `exec` command and settings
cmdExec := app.Command("exec", "Exec the supplied command with env vars from STS token.")
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ require (
github.com/tidwall/gjson v1.1.1
github.com/tidwall/match v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/sys v0.0.0-20210603125802-9665404d3644 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.5 // indirect
gopkg.in/ini.v1 v1.62.0
Expand Down
9 changes: 5 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -239,8 +239,9 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
Expand Down
8 changes: 8 additions & 0 deletions helper/credentials/saml.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ func LookupCredentials(loginDetails *creds.LoginDetails, provider string) error
loginDetails.Username = username
loginDetails.Password = password

// If the provider is Okta, check for existing Okta Session Cookie (sid)
if provider == "Okta" {
_, oktaSessionCookie, err := CurrentHelper.Get(loginDetails.URL + "/sessionCookie")
if err == nil {
loginDetails.OktaSessionCookie = oktaSessionCookie
}
}

if provider == "OneLogin" {
id, secret, err := CurrentHelper.Get(path.Join(loginDetails.URL, "/auth/oauth2/v2/token"))
if err != nil {
Expand Down
53 changes: 30 additions & 23 deletions pkg/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,35 @@ const (

// IDPAccount saml IDP account
type IDPAccount struct {
Name string `ini:"name"`
AppID string `ini:"app_id"` // used by OneLogin and AzureAD
URL string `ini:"url"`
Username string `ini:"username"`
Provider string `ini:"provider"`
MFA string `ini:"mfa"`
SkipVerify bool `ini:"skip_verify"`
Timeout int `ini:"timeout"`
AmazonWebservicesURN string `ini:"aws_urn"`
SessionDuration int `ini:"aws_session_duration"`
Profile string `ini:"aws_profile"`
ResourceID string `ini:"resource_id"` // used by F5APM
Subdomain string `ini:"subdomain"` // used by OneLogin
RoleARN string `ini:"role_arn"`
Region string `ini:"region"`
HttpAttemptsCount string `ini:"http_attempts_count"`
HttpRetryDelay string `ini:"http_retry_delay"`
CredentialsFile string `ini:"credentials_file"`
SAMLCache bool `ini:"saml_cache"`
SAMLCacheFile string `ini:"saml_cache_file"`
TargetURL string `ini:"target_url"`
Name string `ini:"name"`
AppID string `ini:"app_id"` // used by OneLogin and AzureAD
URL string `ini:"url"`
Username string `ini:"username"`
Provider string `ini:"provider"`
MFA string `ini:"mfa"`
SkipVerify bool `ini:"skip_verify"`
Timeout int `ini:"timeout"`
AmazonWebservicesURN string `ini:"aws_urn"`
SessionDuration int `ini:"aws_session_duration"`
Profile string `ini:"aws_profile"`
ResourceID string `ini:"resource_id"` // used by F5APM
Subdomain string `ini:"subdomain"` // used by OneLogin
RoleARN string `ini:"role_arn"`
Region string `ini:"region"`
HttpAttemptsCount string `ini:"http_attempts_count"`
HttpRetryDelay string `ini:"http_retry_delay"`
CredentialsFile string `ini:"credentials_file"`
SAMLCache bool `ini:"saml_cache"`
SAMLCacheFile string `ini:"saml_cache_file"`
TargetURL string `ini:"target_url"`
DisableRememberDevice bool `ini:"disable_remember_device"` // used by Okta
DisableSessions bool `ini:"disable_sessions"` // used by Okta
}

func (ia IDPAccount) String() string {
var appID string
var policyID string
var oktaCfg string
switch ia.Provider {
case "OneLogin":
appID = fmt.Sprintf(`
Expand All @@ -66,9 +69,13 @@ func (ia IDPAccount) String() string {
case "AzureAD":
appID = fmt.Sprintf(`
AppID: %s`, ia.AppID)
case "Okta":
oktaCfg = fmt.Sprintf(`
DisableSessions: %v
DisableRememberDevice: %v`, ia.DisableSessions, ia.DisableSessions)
}

return fmt.Sprintf(`account {%s%s
return fmt.Sprintf(`account {%s%s%s
URL: %s
Username: %s
Provider: %s
Expand All @@ -79,7 +86,7 @@ func (ia IDPAccount) String() string {
Profile: %s
RoleARN: %s
Region: %s
}`, appID, policyID, ia.URL, ia.Username, ia.Provider, ia.MFA, ia.SkipVerify, ia.AmazonWebservicesURN, ia.SessionDuration, ia.Profile, ia.RoleARN, ia.Region)
}`, appID, policyID, oktaCfg, ia.URL, ia.Username, ia.Provider, ia.MFA, ia.SkipVerify, ia.AmazonWebservicesURN, ia.SessionDuration, ia.Profile, ia.RoleARN, ia.Region)
}

// Validate validate the required / expected fields are set
Expand Down
17 changes: 9 additions & 8 deletions pkg/creds/creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package creds

// LoginDetails used to authenticate
type LoginDetails struct {
ClientID string // used by OneLogin
ClientSecret string // used by OneLogin
Username string
Password string
MFAToken string
DuoMFAOption string
URL string
StateToken string // used by Okta
ClientID string // used by OneLogin
ClientSecret string // used by OneLogin
Username string
Password string
MFAToken string
DuoMFAOption string
URL string
StateToken string // used by Okta
OktaSessionCookie string // used by Okta
}
56 changes: 32 additions & 24 deletions pkg/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,32 @@ import (

// CommonFlags flags common to all of the `saml2aws` commands (except `help`)
type CommonFlags struct {
AppID string
ClientID string
ClientSecret string
ConfigFile string
IdpAccount string
IdpProvider string
MFA string
MFAToken string
URL string
Username string
Password string
RoleArn string
AmazonWebservicesURN string
SessionDuration int
SkipPrompt bool
SkipVerify bool
Profile string
Subdomain string
ResourceID string
DisableKeychain bool
Region string
CredentialsFile string
SAMLCache bool
SAMLCacheFile string
AppID string
ClientID string
ClientSecret string
ConfigFile string
IdpAccount string
IdpProvider string
MFA string
MFAToken string
URL string
Username string
Password string
RoleArn string
AmazonWebservicesURN string
SessionDuration int
SkipPrompt bool
SkipVerify bool
Profile string
Subdomain string
ResourceID string
DisableKeychain bool
Region string
CredentialsFile string
SAMLCache bool
SAMLCacheFile string
DisableRememberDevice bool
DisableSessions bool
}

// LoginExecFlags flags for the Login / Exec commands
Expand Down Expand Up @@ -106,4 +108,10 @@ func ApplyFlagOverrides(commonFlags *CommonFlags, account *cfg.IDPAccount) {
if commonFlags.SAMLCacheFile != "" {
account.SAMLCacheFile = commonFlags.SAMLCacheFile
}
if commonFlags.DisableRememberDevice {
account.DisableRememberDevice = commonFlags.DisableRememberDevice
}
if commonFlags.DisableSessions {
account.DisableSessions = commonFlags.DisableSessions
}
}
Loading

0 comments on commit a882ec4

Please sign in to comment.