From 5dbf79fd99cea3fb1c4c9fd6fdd25481737db63d Mon Sep 17 00:00:00 2001 From: Mike Mondragon Date: Fri, 29 Sep 2023 14:42:17 -0700 Subject: [PATCH 1/3] Adjust User-Agent values/calculation --- internal/agent/agent.go | 47 ------------------------------- internal/config/config.go | 8 ++++++ internal/m2mauth/m2mauth.go | 11 ++++---- internal/utils/utils.go | 8 ++++++ internal/webssoauth/webssoauth.go | 20 +++++++------ 5 files changed, 34 insertions(+), 60 deletions(-) delete mode 100644 internal/agent/agent.go diff --git a/internal/agent/agent.go b/internal/agent/agent.go deleted file mode 100644 index ba5e75a..0000000 --- a/internal/agent/agent.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2022-Present, Okta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package agent - -import "runtime" - -// UserAgent Helper for a stylized User Agent header in HTTP requests -type UserAgent struct { - goVersion string - osName string - osVersion string - appVersion string -} - -// NewUserAgent Create an new User Agent -func NewUserAgent(version string) UserAgent { - ua := UserAgent{ - goVersion: runtime.Version(), - osName: runtime.GOOS, - osVersion: runtime.GOARCH, - appVersion: version, - } - - return ua -} - -func (ua UserAgent) String() string { - userAgentString := "okta-aws-cli/" + ua.appVersion + " " - userAgentString += "golang/" + ua.goVersion + " " - userAgentString += ua.osName + "/" + ua.osVersion - - return userAgentString -} diff --git a/internal/config/config.go b/internal/config/config.go index f5487c0..7dfeb66 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -22,6 +22,7 @@ import ( "net/url" "os" "path/filepath" + "runtime" "strings" "time" @@ -29,6 +30,13 @@ import ( "gopkg.in/yaml.v2" ) +// UserAgentValue the user agent value +var UserAgentValue string + +func init() { + UserAgentValue = fmt.Sprintf("okta-aws-cli/%s (%s; %s; %s)", Version, runtime.Version(), runtime.GOOS, runtime.GOARCH) +} + const ( // Version app version Version = "2.0.0-beta.0" diff --git a/internal/m2mauth/m2mauth.go b/internal/m2mauth/m2mauth.go index 439e4f4..f0e2d55 100644 --- a/internal/m2mauth/m2mauth.go +++ b/internal/m2mauth/m2mauth.go @@ -199,14 +199,15 @@ func (m *M2MAuthentication) accessToken() (*okta.AccessToken, error) { query.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") query.Add("client_assertion", clientAssertion) tokenRequestURL += "?" + query.Encode() - tokenRequest, err := http.NewRequest("POST", tokenRequestURL, tokenRequestBuff) + req, err := http.NewRequest("POST", tokenRequestURL, tokenRequestBuff) if err != nil { return nil, err } - - tokenRequest.Header.Add("Accept", utils.ApplicationJSON) - tokenRequest.Header.Add(utils.ContentType, utils.ApplicationXFORM) - resp, err := m.config.HTTPClient().Do(tokenRequest) + req.Header.Add("Accept", utils.ApplicationJSON) + req.Header.Add(utils.ContentType, utils.ApplicationXFORM) + req.Header.Add(utils.UserAgentHeader, config.UserAgentValue) + req.Header.Add(utils.XOktaAWSCLIOperationHeader, utils.XOktaAWSCLIM2MOperation) + resp, err := m.config.HTTPClient().Do(req) if err != nil { return nil, err } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 65147dd..dab36aa 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -23,4 +23,12 @@ const ( ApplicationJSON = "application/json" // ApplicationXFORM content type value for web form ApplicationXFORM = "application/x-www-form-urlencoded" + // UserAgentHeader user agent header + UserAgentHeader = "User-Agent" + // XOktaAWSCLIOperationHeader the okta aws cli header + XOktaAWSCLIOperationHeader = "X-Okta-Aws-Cli-Operation" + // XOktaAWSCLIWebOperation web op value for the x okta aws cli header + XOktaAWSCLIWebOperation = "web" + // XOktaAWSCLIM2MOperation m2m op value for the x okta aws cli header + XOktaAWSCLIM2MOperation = "m2m" ) diff --git a/internal/webssoauth/webssoauth.go b/internal/webssoauth/webssoauth.go index d4e530c..7675c08 100644 --- a/internal/webssoauth/webssoauth.go +++ b/internal/webssoauth/webssoauth.go @@ -43,7 +43,6 @@ import ( brwsr "github.com/pkg/browser" "golang.org/x/net/html" - "github.com/okta/okta-aws-cli/internal/agent" oaws "github.com/okta/okta-aws-cli/internal/aws" boff "github.com/okta/okta-aws-cli/internal/backoff" "github.com/okta/okta-aws-cli/internal/config" @@ -55,7 +54,6 @@ import ( const ( amazonAWS = "amazon_aws" accept = "Accept" - userAgent = "User-Agent" nameKey = "name" saml2Attribute = "saml2:attribute" samlAttributesRole = "https://aws.amazon.com/SAML/Attributes/Role" @@ -531,7 +529,8 @@ func (w *WebSSOAuthentication) fetchSAMLAssertion(at *okta.AccessToken) (asserti return assertion, err } req.Header.Add(accept, "text/html") - req.Header.Add(userAgent, agent.NewUserAgent(config.Version).String()) + req.Header.Add(utils.UserAgentHeader, config.UserAgentValue) + req.Header.Add(utils.XOktaAWSCLIOperationHeader, utils.XOktaAWSCLIWebOperation) resp, err := w.config.HTTPClient().Do(req) if err != nil { @@ -573,7 +572,8 @@ func (w *WebSSOAuthentication) fetchSSOWebToken(clientID, awsFedAppID string, at } req.Header.Add(accept, utils.ApplicationJSON) req.Header.Add(utils.ContentType, utils.ApplicationXFORM) - req.Header.Add(userAgent, agent.NewUserAgent(config.Version).String()) + req.Header.Add(utils.UserAgentHeader, config.UserAgentValue) + req.Header.Add(utils.XOktaAWSCLIOperationHeader, utils.XOktaAWSCLIWebOperation) resp, err := w.config.HTTPClient().Do(req) if err != nil { @@ -659,7 +659,8 @@ func (w *WebSSOAuthentication) listFedApps(clientID string, at *okta.AccessToken req.Header.Add(accept, utils.ApplicationJSON) req.Header.Add(utils.ContentType, utils.ApplicationJSON) - req.Header.Add(userAgent, agent.NewUserAgent(config.Version).String()) + req.Header.Add(utils.UserAgentHeader, config.UserAgentValue) + req.Header.Add(utils.XOktaAWSCLIOperationHeader, utils.XOktaAWSCLIWebOperation) req.Header.Add("Authorization", fmt.Sprintf("%s %s", at.TokenType, at.AccessToken)) resp, err := w.config.HTTPClient().Do(req) if resp.StatusCode == http.StatusForbidden { @@ -708,7 +709,8 @@ func (w *WebSSOAuthentication) accessToken(deviceAuth *okta.DeviceAuthorization) } req.Header.Add(accept, utils.ApplicationJSON) req.Header.Add(utils.ContentType, utils.ApplicationXFORM) - req.Header.Add(userAgent, agent.NewUserAgent(config.Version).String()) + req.Header.Add(utils.UserAgentHeader, config.UserAgentValue) + req.Header.Add(utils.XOktaAWSCLIOperationHeader, utils.XOktaAWSCLIWebOperation) var bodyBytes []byte @@ -779,7 +781,8 @@ func (w *WebSSOAuthentication) authorize() (*okta.DeviceAuthorization, error) { } req.Header.Add(accept, utils.ApplicationJSON) req.Header.Add(utils.ContentType, utils.ApplicationXFORM) - req.Header.Add(userAgent, agent.NewUserAgent(config.Version).String()) + req.Header.Add(utils.UserAgentHeader, config.UserAgentValue) + req.Header.Add(utils.XOktaAWSCLIOperationHeader, utils.XOktaAWSCLIWebOperation) resp, err := w.config.HTTPClient().Do(req) if err != nil { @@ -877,7 +880,8 @@ func (w *WebSSOAuthentication) isClassicOrg() bool { return false } req.Header.Add(accept, utils.ApplicationJSON) - req.Header.Add(userAgent, agent.NewUserAgent(config.Version).String()) + req.Header.Add(utils.UserAgentHeader, config.UserAgentValue) + req.Header.Add(utils.XOktaAWSCLIOperationHeader, utils.XOktaAWSCLIWebOperation) resp, err := w.config.HTTPClient().Do(req) if err != nil { From 772dd94de0dc5158985febc07ed02a72347f43fe Mon Sep 17 00:00:00 2001 From: Mike Mondragon Date: Mon, 2 Oct 2023 15:09:00 -0700 Subject: [PATCH 2/3] AWS CLI Process credential provider JSON output. Feature request from #110 and #119 being added into the v2 release. --- CHANGELOG.md | 13 +++- README.md | 17 +++++ cmd/root/root.go | 2 +- internal/aws/aws.go | 34 +++++++++- internal/aws/aws_test.go | 35 ++++++++++ internal/config/config.go | 9 ++- internal/m2mauth/m2mauth.go | 2 + internal/output/output.go | 2 + internal/output/process_credentials.go | 50 ++++++++++++++ internal/output/process_credentials_test.go | 45 +++++++++++++ internal/webssoauth/webssoauth.go | 73 +++++++++++++-------- 11 files changed, 247 insertions(+), 35 deletions(-) create mode 100644 internal/aws/aws_test.go create mode 100644 internal/output/process_credentials.go create mode 100644 internal/output/process_credentials_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index eb9f5d6..a647cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ in the naming convention for `okta-aws-cli` specific names. | `OKTA_OIDC_CLIENT_ID` | `OKTA_AWSCLI_OIDC_CLIENT_ID` | | `OKTA_AWS_ACCOUNT_FEDERATION_APP_ID` | `OKTA_AWSCLI_AWS_ACCOUNT_FEDERATION_APP_ID` | -### (expected) Credentials output as JSON +### (Completed) Process credential provider output as JSON Emits IAM temporary credentials as JSON in [process credentials](https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html) @@ -71,6 +71,17 @@ $ okta-aws-cli web \ --open-browser-command "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --profile-directory='Profile 1'" ``` +## 2.0.0-beta.1 (October 2, 2023) + +Support for AWS CLI [process credential provider](https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html) + +``` +# $/.aws/config +[default] +# presumes OKTA_AWSCLI_* env vars are set +credential_process = okta-aws-cli m2m --format process-credentials +``` + ## 2.0.0-beta.0 (September 29, 2023) ### New commands diff --git a/README.md b/README.md index caafac4..8cee78e 100644 --- a/README.md +++ b/README.md @@ -679,6 +679,23 @@ aws --profile example s3 ls Unable to parse config file: /home/user/.aws/credentials ``` +### Process credentials provider + +`okta-aws-cli` supports JSON output for the AWS CLI [credential process +argument](https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html). +Add this line to the `default` section of `$/.aws/config`. First m2m example +presumes `m2m` arguments are in `OKTA_AWSCLI_*` environment variables, AWS CLI +passes those through. Second web example has args spelled out directly in the +credential process values. + +M2M example: + +`credential_process = okta-aws-cli m2m --format process-credentials` + +Web example: + +`credential_process = okta-aws-cli web --format process-credentials --oidc-client-id abc --org-domain test.okat.com --aws-iam-idp arn:aws:iam::123:saml-provider/my-idp --aws-iam-role arn:aws:iam::294719231913:role/s3 --open-browser` + ### Help ```shell diff --git a/cmd/root/root.go b/cmd/root/root.go index ae68a38..bb60b15 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -82,7 +82,7 @@ func init() { Name: config.FormatFlag, Short: "f", Value: "", - Usage: "Output format. [env-var|aws-credentials]", + Usage: "Output format. [env-var|aws-credentials|process-credentials]", EnvVar: config.FormatEnvVar, }, { diff --git a/internal/aws/aws.go b/internal/aws/aws.go index c7cde96..69e950a 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -16,9 +16,37 @@ package aws +import ( + "encoding/json" + "time" +) + // Credential Convenience representation of an AWS credential. type Credential struct { - AccessKeyID string `ini:"aws_access_key_id"` - SecretAccessKey string `ini:"aws_secret_access_key"` - SessionToken string `ini:"aws_session_token"` + // NOTE JSON process credentials format uses same came cased names as this struct + AccessKeyID string `ini:"aws_access_key_id" json:"AccessKeyId,omitempty"` + SecretAccessKey string `ini:"aws_secret_access_key" json:"SecretAccessKey,omitempty"` + SessionToken string `ini:"aws_session_token" json:"SessionToken,omitempty"` + Version int `ini:"aws_version" json:"Version,omitempty"` + Expiration *time.Time `ini:"aws_expiration" json:"Expiration,omitempty"` +} + +// MarshalJSON ensure Expiration date time is formatted RFC 3339 format. +func (c *Credential) MarshalJSON() ([]byte, error) { + type Alias Credential + var exp string + if c.Expiration != nil { + exp = c.Expiration.Format(time.RFC3339) + } + + obj := &struct { + *Alias + Expiration string `json:"Expiration"` + }{ + Alias: (*Alias)(c), + } + if exp != "" { + obj.Expiration = exp + } + return json.Marshal(obj) } diff --git a/internal/aws/aws_test.go b/internal/aws/aws_test.go new file mode 100644 index 0000000..eb68a8f --- /dev/null +++ b/internal/aws/aws_test.go @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023-Present, Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package aws + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestCredentialJSON(t *testing.T) { + hbtGo := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + c := Credential{ + Expiration: &hbtGo, + } + credStr, err := json.Marshal(c) + require.NoError(t, err) + require.Equal(t, `{"Expiration":"2009-11-10T23:00:00Z"}`, string(credStr)) +} diff --git a/internal/config/config.go b/internal/config/config.go index 7dfeb66..00cfc23 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -39,12 +39,14 @@ func init() { const ( // Version app version - Version = "2.0.0-beta.0" + Version = "2.0.0-beta.1" // AWSCredentialsFormat format const AWSCredentialsFormat = "aws-credentials" // EnvVarFormat format const EnvVarFormat = "env-var" + // ProcessCredentialsFormat format const + ProcessCredentialsFormat = "process-credentials" // AuthzIDFlag cli flag const AuthzIDFlag = "authz-id" @@ -814,6 +816,11 @@ awscli: return nil } +// IsProcessCredentialsFormat is our format process credentials? +func (c *Config) IsProcessCredentialsFormat() bool { + return c.format == ProcessCredentialsFormat +} + type realClock struct{} func (realClock) Now() time.Time { return time.Now() } diff --git a/internal/m2mauth/m2mauth.go b/internal/m2mauth/m2mauth.go index f0e2d55..f269bbd 100644 --- a/internal/m2mauth/m2mauth.go +++ b/internal/m2mauth/m2mauth.go @@ -119,7 +119,9 @@ func (m *M2MAuthentication) awsAssumeRoleWithWebIdentity(at *okta.AccessToken) ( AccessKeyID: *svcResp.Credentials.AccessKeyId, SecretAccessKey: *svcResp.Credentials.SecretAccessKey, SessionToken: *svcResp.Credentials.SessionToken, + Expiration: svcResp.Credentials.Expiration, } + return credential, nil } diff --git a/internal/output/output.go b/internal/output/output.go index 0f773fe..ec8b561 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -37,6 +37,8 @@ func RenderAWSCredential(cfg *config.Config, ac *aws.Credential) error { case config.AWSCredentialsFormat: expiry := time.Now().Add(time.Duration(cfg.AWSSessionDuration()) * time.Second).Format(time.RFC3339) o = NewAWSCredentialsFile(cfg.LegacyAWSVariables(), cfg.ExpiryAWSVariables(), expiry) + case config.ProcessCredentialsFormat: + o = NewProcessCredentials() default: o = NewEnvVar(cfg.LegacyAWSVariables()) fmt.Fprintf(os.Stderr, "\n") diff --git a/internal/output/process_credentials.go b/internal/output/process_credentials.go new file mode 100644 index 0000000..a9f1053 --- /dev/null +++ b/internal/output/process_credentials.go @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023-Present, Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package output + +import ( + "encoding/json" + "fmt" + + "github.com/okta/okta-aws-cli/internal/aws" + "github.com/okta/okta-aws-cli/internal/config" +) + +// ProcessCredentials AWS CLI Process Credentials output formatter +// https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html +type ProcessCredentials struct{} + +// NewProcessCredentials Creates a new ProcessCredentials +func NewProcessCredentials() *ProcessCredentials { + return &ProcessCredentials{} +} + +// Output Satisfies the Outputter interface and outputs AWS credentials as JSON +// to STDOUT +func (p *ProcessCredentials) Output(c *config.Config, ac *aws.Credential) error { + // See AWS docs: "Note As of this writing, the Version key must be set to 1. + // This might increment over time as the structure evolves." + ac.Version = 1 + + credJSON, err := json.MarshalIndent(ac, "", " ") + if err != nil { + return err + } + + fmt.Printf("%s", credJSON) + return nil +} diff --git a/internal/output/process_credentials_test.go b/internal/output/process_credentials_test.go new file mode 100644 index 0000000..af4a943 --- /dev/null +++ b/internal/output/process_credentials_test.go @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023-Present, Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package output + +import ( + "encoding/json" + "testing" + "time" + + "github.com/okta/okta-aws-cli/internal/aws" + "github.com/stretchr/testify/require" +) + +func TestProcessCredentials(t *testing.T) { + credsJSON := ` +{ + "Version": 1, + "AccessKeyId": "an AWS access key", + "SecretAccessKey": "your AWS secret access key", + "SessionToken": "the AWS session token for temporary credentials", + "Expiration": "2009-11-10T23:00:00Z" +}` + result := aws.Credential{} + err := json.Unmarshal([]byte(credsJSON), &result) + require.NoError(t, err) + require.Equal(t, "an AWS access key", result.AccessKeyID) + require.Equal(t, "your AWS secret access key", result.SecretAccessKey) + require.Equal(t, "the AWS session token for temporary credentials", result.SessionToken) + when := time.Time(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)) + require.Equal(t, &when, result.Expiration) +} diff --git a/internal/webssoauth/webssoauth.go b/internal/webssoauth/webssoauth.go index 7675c08..931cc6c 100644 --- a/internal/webssoauth/webssoauth.go +++ b/internal/webssoauth/webssoauth.go @@ -112,15 +112,17 @@ var stderrIsOutAskOpt = func(options *survey.AskOptions) error { } // NewWebSSOAuthentication New Web SSO Authentication constructor -func NewWebSSOAuthentication(config *config.Config) (token *WebSSOAuthentication, err error) { - if err != nil { - return nil, err - } +func NewWebSSOAuthentication(cfg *config.Config) (token *WebSSOAuthentication, err error) { token = &WebSSOAuthentication{ - config: config, + config: cfg, } if token.isClassicOrg() { - return nil, fmt.Errorf("%q is a Classic org, okta-aws-cli is an-OIE only tool", config.OrgDomain()) + return nil, fmt.Errorf("%q is a Classic org, okta-aws-cli is an-OIE only tool", cfg.OrgDomain()) + } + if cfg.IsProcessCredentialsFormat() { + if cfg.AWSIAMIdP() == "" || cfg.AWSIAMRole() == "" || !cfg.OpenBrowser() { + return nil, fmt.Errorf("arguments --%s , --%s , and --%s must be set for %q format", config.AWSIAMIdPFlag, config.AWSIAMRoleFlag, config.OpenBrowserFlag, cfg.Format()) + } } return token, nil } @@ -213,15 +215,15 @@ func (w *WebSSOAuthentication) choiceFriendlyLabelIDP(alternative string, oktaCo if label, ok := oktaConfig.AWSCLI.IDPS[arn]; ok { if w.config.Debug() { - fmt.Fprintf(os.Stderr, " found IdP ARN %q having friendly label %q\n", arn, label) + w.consolePrint(" found IdP ARN %q having friendly label %q\n", arn, label) } return label } else if w.config.Debug() { - fmt.Fprintf(os.Stderr, " did not find friendly label for IdP ARN\n") - fmt.Fprintf(os.Stderr, arnPrintFmt, arn) - fmt.Fprintf(os.Stderr, " in okta.yaml awscli.idps map:\n") + w.consolePrint(" did not find friendly label for IdP ARN\n") + w.consolePrint(arnPrintFmt, arn) + w.consolePrint(" in okta.yaml awscli.idps map:\n") for arn, label := range oktaConfig.AWSCLI.IDPS { - fmt.Fprintf(os.Stderr, arnLabelPrintFmt, arn, label) + w.consolePrint(arnLabelPrintFmt, arn, label) } } return alternative @@ -238,14 +240,16 @@ func (w *WebSSOAuthentication) selectFedApp(apps []*okta.Application) (string, e // when OKTA_AWSCLI_IAM_IDP / --aws-iam-idp is set if w.config.AWSIAMIdP() == app.Settings.App.IdentityProviderARN { - idpData := idpTemplateData{ - IDP: choiceLabel, + if !w.config.IsProcessCredentialsFormat() { + idpData := idpTemplateData{ + IDP: choiceLabel, + } + rich, _, err := core.RunTemplate(idpSelectedTemplate, idpData) + if err != nil { + return "", err + } + fmt.Fprintln(os.Stderr, rich) } - rich, _, err := core.RunTemplate(idpSelectedTemplate, idpData) - if err != nil { - return "", err - } - fmt.Fprintln(os.Stderr, rich) return app.ID, nil } @@ -327,6 +331,7 @@ func (w *WebSSOAuthentication) awsAssumeRoleWithSAML(iar *idpAndRole, assertion AccessKeyID: *svcResp.Credentials.AccessKeyId, SecretAccessKey: *svcResp.Credentials.SecretAccessKey, SessionToken: *svcResp.Credentials.SessionToken, + Expiration: svcResp.Credentials.Expiration, } return credential, nil } @@ -341,15 +346,15 @@ func (w *WebSSOAuthentication) choiceFriendlyLabelRole(arn string, oktaConfig *c if label, ok := oktaConfig.AWSCLI.ROLES[arn]; ok { if w.config.Debug() { - fmt.Fprintf(os.Stderr, " found Role ARN %q having friendly label %q\n", arn, label) + w.consolePrint(" found Role ARN %q having friendly label %q\n", arn, label) } return label } else if w.config.Debug() { - fmt.Fprintf(os.Stderr, " did not find friendly label for Role ARN\n") - fmt.Fprintf(os.Stderr, arnPrintFmt, arn) - fmt.Fprintf(os.Stderr, " in okta.yaml awscli.roles map:\n") + w.consolePrint(" did not find friendly label for Role ARN\n") + w.consolePrint(arnPrintFmt, arn) + w.consolePrint(" in okta.yaml awscli.roles map:\n") for arn, label := range oktaConfig.AWSCLI.ROLES { - fmt.Fprintf(os.Stderr, arnLabelPrintFmt, arn, label) + w.consolePrint(arnLabelPrintFmt, arn, label) } } return arn @@ -368,11 +373,13 @@ func (w *WebSSOAuthentication) promptForRole(idp string, roleARNs []string) (rol roleData := roleTemplateData{ Role: roleLabel, } - rich, _, err := core.RunTemplate(roleSelectedTemplate, roleData) - if err != nil { - return "", err + if !w.config.IsProcessCredentialsFormat() { + rich, _, err := core.RunTemplate(roleSelectedTemplate, roleData) + if err != nil { + return "", err + } + fmt.Fprintln(os.Stderr, rich) } - fmt.Fprintln(os.Stderr, rich) return roleARN, nil } @@ -628,12 +635,12 @@ func (w *WebSSOAuthentication) promptAuthentication(da *okta.DeviceAuthorization openMsg = "System web browser will open" } - fmt.Fprintf(os.Stderr, prompt, openMsg, qrCode, da.VerificationURIComplete) + w.consolePrint(prompt, openMsg, qrCode, da.VerificationURIComplete) if w.config.OpenBrowser() { brwsr.Stdout = os.Stderr if err := brwsr.OpenURL(da.VerificationURIComplete); err != nil { - fmt.Fprintf(os.Stderr, "Failed to open activation URL with system browser: %v\n", err) + w.consolePrint("Failed to open activation URL with system browser: %v\n", err) } } } @@ -965,3 +972,11 @@ func (w *WebSSOAuthentication) cacheAccessToken(at *okta.AccessToken) { configPath := filepath.Join(cUser.HomeDir, dotOktaDir, tokenFileName) _ = os.WriteFile(configPath, atJSON, 0o600) } + +func (w *WebSSOAuthentication) consolePrint(format string, a ...any) { + if w.config.IsProcessCredentialsFormat() { + return + } + + fmt.Fprintf(os.Stderr, format, a...) +} From ff7650f90ab3c92f24d889624a057ee51bb88bdb Mon Sep 17 00:00:00 2001 From: Mike Mondragon Date: Mon, 2 Oct 2023 15:23:24 -0700 Subject: [PATCH 3/3] Stop flogging those requesting support for non-standard options that required backporting from other CLIs ... Closes #142 --- README.md | 4 ++-- internal/aws/aws.go | 1 - internal/output/aws_credentials_file.go | 6 ------ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8cee78e..489a314 100644 --- a/README.md +++ b/README.md @@ -367,8 +367,8 @@ These global settings are optional unless marked otherwise: | Cache Okta access token at `$HOME/.okta/awscli-access-token.json` to reduce need to open device authorization URL | `true` if flag is present | `--cache-access-token` | `OKTA_AWSCLI_CACHE_ACCESS_TOKEN=true` | | Alternate AWS credentials file path | Path to alternative credentials file other than AWS CLI default | `--aws-credentials` | `OKTA_AWSCLI_AWS_CREDENTIALS` | | (Over)write the given profile to the AWS credentials file. WARNING: When enabled, overwriting can inadvertently remove dangling comments and extraneous formatting from the creds file. | `true` if flag is present | `--write-aws-credentials` | `OKTA_AWSCLI_WRITE_AWS_CREDENTIALS=true` | -| Emit deprecated AWS variable `aws_security_token` with duplicated value from `aws_session_token` | `true` if flag is present | `--legacy-aws-variables` | `OKTA_AWSCLI_LEGACY_AWS_VARIABLES=true` | -| Emit expiry timestamp `x_security_token_expires` in RFC3339 format for the session/security token (AWS credentials file only) | `true` if flag is present | `--expiry-aws-variables` | `OKTA_AWSCLI_EXPIRY_AWS_VARIABLES=true` | +| Emit deprecated AWS variable `aws_security_token` with duplicated value from `aws_session_token`. AWS CLI removed any reference and documentation for `aws_security_token` in November 2014. | `true` if flag is present | `--legacy-aws-variables` | `OKTA_AWSCLI_LEGACY_AWS_VARIABLES=true` | +| Emit expiry timestamp `x_security_token_expires` in RFC3339 format for the session/security token (AWS credentials file only). This is a non-standard profile variable. | `true` if flag is present | `--expiry-aws-variables` | `OKTA_AWSCLI_EXPIRY_AWS_VARIABLES=true` | | Print operational information to the screen for debugging purposes | `true` if flag is present | `--debug` | `OKTA_AWSCLI_DEBUG=true` | | Verbosely print all API calls/responses to the screen | `true` if flag is present | `--debug-api-calls` | `OKTA_AWSCLI_DEBUG_API_CALLS=true` | | HTTP/HTTPS Proxy support | HTTP/HTTPS URL of proxy service (based on golang [net/http/httpproxy](https://pkg.go.dev/golang.org/x/net/http/httpproxy) package) | n/a | `HTTP_PROXY` or `HTTPS_PROXY` | diff --git a/internal/aws/aws.go b/internal/aws/aws.go index 69e950a..c1d0af2 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -23,7 +23,6 @@ import ( // Credential Convenience representation of an AWS credential. type Credential struct { - // NOTE JSON process credentials format uses same came cased names as this struct AccessKeyID string `ini:"aws_access_key_id" json:"AccessKeyId,omitempty"` SecretAccessKey string `ini:"aws_secret_access_key" json:"SecretAccessKey,omitempty"` SessionToken string `ini:"aws_session_token" json:"SessionToken,omitempty"` diff --git a/internal/output/aws_credentials_file.go b/internal/output/aws_credentials_file.go index f01d583..b8647e4 100644 --- a/internal/output/aws_credentials_file.go +++ b/internal/output/aws_credentials_file.go @@ -156,12 +156,6 @@ func updateINI(config *ini.File, profile string, legacyVars bool, expiryVars boo if len(comments) > 0 { fmt.Fprintf(os.Stderr, "WARNING: Commented out %q profile keys \"%s\". Uncomment if third party tools use these values.\n", profile, strings.Join(comments, "\", \"")) } - if legacyVars { - fmt.Fprintf(os.Stderr, "WARNING: %q includes legacy variable \"aws_security_token\". Update tools making use of this deprecated value.\n", profile) - } - if expiryVars { - fmt.Fprintf(os.Stderr, "WARNING: %q includes 3rd party variable \"x_security_token_expires\".\n", profile) - } return config, nil }