Skip to content

Commit 64982ce

Browse files
authored
Merge pull request #277 from okta/mfa_oob
New `direct` command for Direct Authentication (out-of-bounds MFA)
2 parents 1ef737b + f3cf988 commit 64982ce

File tree

15 files changed

+776
-144
lines changed

15 files changed

+776
-144
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 2.5.0 (May 20, 2025)
4+
5+
* New `direct` command for OOB MFA password grant auth flow [#277](https://github.com/okta/okta-aws-cli/pull/277), thanks [@monde](https://github.com/monde)!
6+
7+
### ENHANCEMENTS
8+
39
## 2.4.1 (February 10, 2025)
410

511
### BUG FIXES

README.md

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ retrieve AWS IAM temporary credentials for use in AWS CLI, AWS SDKs, and other
99
tools accessing the AWS API. It has two primary commands:
1010

1111
- `web` - combined human and device authorization
12-
- `m2m` - headless authorization
12+
- `m2m` - headless authorization
13+
- `direct` - [direct authorization](https://developer.okta.com/docs/guides/configure-direct-auth-grants/dmfaoobov/main/) (OOB MFA)
1314

1415
```shell
1516
# *nix, export statements
@@ -26,10 +27,11 @@ SETX AWS_SESSION_TOKEN AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5T...
2627

2728
```
2829

29-
The result of both the `web` and `m2m` operations is to secure and emit [IAM temporary
30+
The result of both the `web`, `m2m`, and `direct` operations are to secure and
31+
emit [IAM temporary
3032
credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html).
31-
The credentials have three different output formats that are chosen by the user:
32-
[environment
33+
The credentials have three different output formats that are chosen by the
34+
user: [environment
3335
variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html),
3436
AWS [credentials
3537
file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html),
@@ -45,11 +47,13 @@ format.
4547
- [Multiple AWS environments](#multiple-aws-environments)
4648
- [M2M Command](#m2m-command)
4749
- [M2M Command Requirements](#m2m-command-requirements)
50+
- [Direct Command](#direct-command)
51+
- [Direct Command Requirements](#direct-command-requirements)
4852
- [List-Profiles Command](#list-profiles-command)
4953
- [Configuration](#configuration)
5054
- [Global settings](#global-settings)
5155
- [Web command settings](#web-command-settings)
52-
- [M2M command settings](#m2m-command-settings)
56+
- [Direct command settings](#direct-command-settings)
5357
- [Friendly IdP and Role menu labels](#friendly-idp-and-role-menu-labels)
5458
- [Configuration by profile name](#configuration-by-profile-name)
5559
- [Debug okta.yaml](#debug-oktayaml)
@@ -68,6 +72,7 @@ format.
6872
| (empty) | When executed without a subcommand **and** without arguments `okta-aws-cli` will print the online help and exit. With arguments it defaults to the `web` command. |
6973
| `web` | Human oriented retrieval of temporary IAM credentials through Okta authentication and device authorization. |
7074
| `m2m` | Machine/headless oriented retrieval of temporary IAM credentials through Okta authentication with a private key. **IMPORTANT!** This a not a feature intended for a human use case. Be sure to use industry state of the art secrets management techniques with the private key. |
75+
| `direct` | Human or machine/headless oriented retrieval of temporary IAM credentials through out-of-bounds MFA [Direct Authentication](https://developer.okta.com/docs/guides/configure-direct-auth-grants/dmfaoobov/main/) |
7176
| `list-profiles` | Lists profile names in ~/.okta/okta.yaml. |
7277
| `debug` | Debug okta.yaml config file and exit. |
7378

@@ -193,6 +198,9 @@ Okta Console.
193198
use industry state of the art secrets management techniques with the private
194199
key.***
195200

201+
**NOTE**: The `m2m` command only operates with an Okta OIDC app, not the Okta
202+
AWS Federation app.
203+
196204
```shell
197205
# This example presumes its arguments are set as environment variables such as
198206
# one may find in a headless CI environment.
@@ -311,6 +319,92 @@ role of the `sts:AssumeRoleWithWebIdentity` action type. This setting is on the
311319
trust relationship tab when viewing a specific role in the AWS Console. Also
312320
note the ARNs of these roles for later use.
313321

322+
## Direct Command
323+
324+
**NOTE**: The `direct` command only operates with an Okta OIDC app, not the
325+
Okta AWS Federation app.
326+
327+
```shell
328+
# use the shell to read in username/password, both can be set directly as CLI
329+
# flags for the headless use case
330+
331+
# zsh style
332+
# read "myusername?Okta Username: " && read -s "mypassword?Okta Password: " && echo
333+
334+
# bash style
335+
$ read -p "Okta Username: " myusername && read -s -p "Okta Password: " mypassword && echo
336+
337+
Okta Username: [email protected]
338+
Okta Password:
339+
340+
$ okta-aws-cli direct \
341+
--format noop \
342+
--org-domain test.okta.com \
343+
--oidc-client-id 0oa123 \
344+
--authz-id aus456 \
345+
--aws-iam-role arn:aws:iam::1234567890:role/my-role \
346+
--username "${myusername}" \
347+
--password "${mypassword}" \
348+
--exec -- aws sts get-caller-identity
349+
350+
{
351+
"UserId": "ZYZ789:okta-aws-cli",
352+
"Account": "1234567890",
353+
"Arn": "arn:aws:sts::1234567890:assumed-role/my-role"
354+
}
355+
```
356+
357+
The `direct` command makes use of the [direct
358+
authorization](https://developer.okta.com/docs/guides/configure-direct-auth-grants/dmfaoobov/main/)
359+
flow that uses a username/password grant. It is a multifactor authentication
360+
flow doing push to [Okta
361+
Verify](https://help.okta.com/en-us/content/topics/mobile/okta-verify-overview.htm)
362+
for the second factor. This will allow for a user experience similar to Nike
363+
Gimme Creds on a Classic classic org with a username and password.
364+
365+
Follow the directions in the [direct
366+
authorization](https://developer.okta.com/docs/guides/configure-direct-auth-grants/dmfaoobov/main/)
367+
documentation to set up the required resources for this flow. The documentation
368+
specifically covers
369+
370+
- authenticator settings for your org
371+
- setting up an authorization server
372+
- setting up the up the OIDC app app
373+
- required authentication policy settings
374+
375+
When executed `okta-aws-cli direct` requests an access token that is associated
376+
with the Okta service application. The token is then exchanged and challenged
377+
during the out-of-bounds authorization with a push to Okta Verify. After the
378+
operator acknowledges the request in Okta Verity the Okta authorization server
379+
returns a final access token. This token is presented to AWS STS using
380+
[AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html).
381+
AWS and Okta communicate directly by OIDC protocol to confirm authorization for
382+
IAM credentials.
383+
384+
Given access is granted by AWS the result of `okta-aws-cli direct` is a set
385+
made up of `Access Key ID`, `Secret Access Key`, and `Session Token` of [AWS
386+
credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
387+
for the AWS CLI. The Okta AWS CLI expresses the AWS credentials as [environment
388+
variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html),
389+
or appended (or overwrites existing values) to an AWS CLI [credentials
390+
file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html),
391+
or emits JSON in [process
392+
credentials](https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html)
393+
format.
394+
395+
The `Session Token` has a default expiry of 60 minutes.
396+
397+
### Direct Command Requirements
398+
399+
Direct is an integration of:
400+
401+
- Otka's [Direct Authorization](https://developer.okta.com/docs/guides/configure-direct-auth-grants/dmfaoobov/main/), and out-of-bounds MFA flow
402+
- [Okta API service app](https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/main/)
403+
- Okta [custom](https://developer.okta.com/docs/guides/customize-authz-server/main/) authorization server
404+
- [Okta access policy](https://developer.okta.com/docs/guides/configure-access-policy/main/) associated with the service app and have rule(s) for the client credentials flow
405+
- [AWS IAM OpenID Connect (OIDC) identity provider](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html)
406+
407+
314408
## List-Profiles Command
315409

316410
```shell
@@ -373,8 +467,8 @@ These global settings are optional unless marked otherwise:
373467
|-----|-----|-----|-----|
374468
| AWS Region (**optional**) | AWS region (will override ENV VAR `AWS_REGION` and `AWS_DEFAULT_REGION`) e.g. `us-east-2` | `--aws-region [value]` | `OKTA_AWSCLI_AWS_REGION` |
375469
| Okta Org Domain (**required**) | Full host and domain name of the Okta org e.g. `my-org.okta.com` or the custom domain value | `--org-domain [value]` | `OKTA_AWSCLI_ORG_DOMAIN` |
376-
| OIDC Client ID (**required**) | For `web` the OIDC native application / [Allowed Web SSO Client ID](#allowed-web-sso-client-id), for `m2m` the API services app ID | `--oidc-client-id [value]` | `OKTA_AWSCLI_OIDC_CLIENT_ID` |
377-
| AWS IAM Role ARN (**optional** for `web`, **required** for `m2m`) | For web preselects the role list to this preferred IAM role for the given IAM Identity Provider. For `m2m` | `--aws-iam-role [value]` | `OKTA_AWSCLI_IAM_ROLE` |
470+
| OIDC Client ID (**required**) | For `web` the OIDC native application / [Allowed Web SSO Client ID](#allowed-web-sso-client-id), for `m2m` and `direct` the API services app ID | `--oidc-client-id [value]` | `OKTA_AWSCLI_OIDC_CLIENT_ID` |
471+
| AWS IAM Role ARN (**optional** for `web`, **required** for `m2m` and `direct`) | For web preselects the role list to this preferred IAM role for the given IAM Identity Provider. For `m2m` and `direct` | `--aws-iam-role [value]` | `OKTA_AWSCLI_IAM_ROLE` |
378472
| AWS Session Duration | The lifetime, in seconds, of the AWS credentials. Must be between 60 and 43200. | `--aws-session-duration [value]` | `OKTA_AWSCLI_SESSION_DURATION` |
379473
| Output format | Default is `env-var`. Options: `env-var` for output to environment variables, `aws-credentials` for output to AWS credentials file, `process-credentials` for credentials as JSON, or `noop` for no output which can be useful with `--exec` | `--format [value]` | `OKTA_AWSCLI_FORMAT` |
380474
| Profile | Default is `default` | `--profile [value]` | `OKTA_AWSCLI_PROFILE` |
@@ -390,7 +484,6 @@ These global settings are optional unless marked otherwise:
390484
| Short user agent. HTTP requests made to the Okta API have user-agent set to `okta-aws-cli` allowing for a simple UA value in a policy rule. | `true` if flag is present | `--short-user-agent` | `OKTA_AWSCLI_SHORT_USER_AGENT=true` |
391485

392486

393-
394487
### Web command settings
395488

396489
If the OIDC Native App doesn't also have the `okta.users.read.self` grant the
@@ -474,6 +567,17 @@ These settings are optional unless marked otherwise:
474567
| Custom scope name | The custom scope established in the custom authorization server. Default `okta-m2m-access` | `--custom-scope [value]` | `OKTA_AWSCLI_CUSTOM_SCOPE` |
475568
| Custom STS Role Session Name | Customize STS Role Session Name. Default `okta-aws-cli` | `--aws-sts-role-session-name [value]` | `OKTA_AWSCLI_STS_ROLE_SESSION_NAME` |
476569

570+
### Direct command settings
571+
572+
These settings are optional unless marked otherwise:
573+
574+
| Name | Description | Command line flag | ENV var and .env file value |
575+
|-----|-----|-----|-----|
576+
| Username (**required**) | The username of the operator | `--username [value]` | `OKTA_AWSCLI_USERNAME` |
577+
| Password (**required**) | The password of the operator | `--password [value]` | `OKTA_AWSCLI_PASSWORD` |
578+
| Authorization Server ID | The ID of the Okta authorization server, set ID for a custom authorization server, will use default otherwise. Default `default` | `--authz-id [value]` | `OKTA_AWSCLI_AUTHZ_ID` |
579+
| Custom STS Role Session Name | Customize STS Role Session Name. Default `okta-aws-cli` | `--aws-sts-role-session-name [value]` | `OKTA_AWSCLI_STS_ROLE_SESSION_NAME` |
580+
477581
### Friendly IdP and Role menu labels
478582

479583
When the operator has many AWS Federation apps listing the AWS IAM IdP ARNs can

cmd/root/direct/direct.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (c) 2023-Present, Okta, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package direct
18+
19+
import (
20+
"github.com/spf13/cobra"
21+
22+
"github.com/okta/okta-aws-cli/internal/config"
23+
"github.com/okta/okta-aws-cli/internal/directauth"
24+
cliFlag "github.com/okta/okta-aws-cli/internal/flag"
25+
)
26+
27+
var (
28+
flags = []cliFlag.Flag{
29+
{
30+
Name: config.UsernameFlag,
31+
Short: "a",
32+
Value: "",
33+
Usage: "Username",
34+
EnvVar: config.UsernameEnvVar,
35+
},
36+
{
37+
Name: config.PasswordFlag,
38+
Short: "b",
39+
Value: "",
40+
Usage: "Password",
41+
EnvVar: config.PasswordEnvVar,
42+
},
43+
{
44+
Name: config.AuthzIDFlag,
45+
Short: "u",
46+
Value: "",
47+
Usage: "Custom Authorization Server ID",
48+
EnvVar: config.AuthzIDEnvVar,
49+
},
50+
{
51+
Name: config.AWSSTSRoleSessionNameFlag,
52+
Short: "q",
53+
Value: "okta-aws-cli",
54+
Usage: "STS Role Session Name",
55+
EnvVar: config.AWSSTSRoleSessionNameEnvVar,
56+
},
57+
}
58+
requiredFlags = []interface{}{"org-domain", "oidc-client-id", "aws-iam-role", []string{"username", "password"}}
59+
)
60+
61+
// NewDirectCommand Sets up the direct cobra sub command
62+
func NewDirectCommand() *cobra.Command {
63+
cmd := &cobra.Command{
64+
Use: "direct",
65+
Short: "Direct authorization with multifactor out-of-band flow",
66+
RunE: func(cmd *cobra.Command, args []string) error {
67+
config, err := config.NewEvaluatedConfig()
68+
if err != nil {
69+
return err
70+
}
71+
72+
err = cliFlag.CheckRequiredFlags(requiredFlags)
73+
if err != nil {
74+
return err
75+
}
76+
77+
da, err := directauth.NewDirectAuthentication(config)
78+
if err != nil {
79+
return err
80+
}
81+
return da.EstablishIAMCredentials()
82+
},
83+
}
84+
85+
cliFlag.MakeFlagBindings(cmd, flags, false)
86+
87+
return cmd
88+
}

cmd/root/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/spf13/cobra"
2727

2828
debugCmd "github.com/okta/okta-aws-cli/cmd/root/debug"
29+
"github.com/okta/okta-aws-cli/cmd/root/direct"
2930
"github.com/okta/okta-aws-cli/cmd/root/m2m"
3031
"github.com/okta/okta-aws-cli/cmd/root/web"
3132
"github.com/okta/okta-aws-cli/internal/ansi"
@@ -171,6 +172,8 @@ func init() {
171172
rootCmd.AddCommand(webCmd)
172173
m2mCmd := m2m.NewM2MCommand()
173174
rootCmd.AddCommand(m2mCmd)
175+
directCmd := direct.NewDirectCommand()
176+
rootCmd.AddCommand(directCmd)
174177
debugCfgCmd := debugCmd.NewDebugCommand()
175178
rootCmd.AddCommand(debugCfgCmd)
176179
listProfilesCmd := profileslist.NewProfilesListCommand()

internal/aws/aws.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ package aws
1919
import (
2020
"encoding/json"
2121
"time"
22+
23+
"github.com/aws/aws-sdk-go/aws"
24+
"github.com/aws/aws-sdk-go/aws/session"
25+
"github.com/aws/aws-sdk-go/service/sts"
26+
"github.com/okta/okta-aws-cli/internal/config"
27+
"github.com/okta/okta-aws-cli/internal/okta"
2228
)
2329

2430
// CredentialContainer denormalized struct of all the values can be presented in
@@ -87,3 +93,37 @@ func (c *ProcessCredential) MarshalJSON() ([]byte, error) {
8793
}
8894
return json.Marshal(obj)
8995
}
96+
97+
// AssumeRoleWithWebIdentity helper function to make the assume role with web identity AWS API call
98+
func AssumeRoleWithWebIdentity(cfg *config.Config, at *okta.AccessToken) (cc *CredentialContainer, err error) {
99+
awsCfg := aws.NewConfig().WithHTTPClient(cfg.HTTPClient())
100+
region := cfg.AWSRegion()
101+
if region != "" {
102+
awsCfg = awsCfg.WithRegion(region)
103+
}
104+
sess, err := session.NewSession(awsCfg)
105+
if err != nil {
106+
return
107+
}
108+
109+
svc := sts.New(sess)
110+
input := &sts.AssumeRoleWithWebIdentityInput{
111+
DurationSeconds: aws.Int64(cfg.AWSSessionDuration()),
112+
RoleArn: aws.String(cfg.AWSIAMRole()),
113+
RoleSessionName: aws.String(cfg.AWSSTSRoleSessionName()),
114+
WebIdentityToken: &at.AccessToken,
115+
}
116+
svcResp, err := svc.AssumeRoleWithWebIdentity(input)
117+
if err != nil {
118+
return
119+
}
120+
121+
cc = &CredentialContainer{
122+
AccessKeyID: *svcResp.Credentials.AccessKeyId,
123+
SecretAccessKey: *svcResp.Credentials.SecretAccessKey,
124+
SessionToken: *svcResp.Credentials.SessionToken,
125+
Expiration: svcResp.Credentials.Expiration,
126+
}
127+
128+
return cc, nil
129+
}

0 commit comments

Comments
 (0)