Skip to content

Commit

Permalink
v2.5: various updates
Browse files Browse the repository at this point in the history
* --diable-account-alias flag
* handle IAM user paths
* match SSOPermissionSet for disabling account alias
* Put go.mod in top level, /v2
* Add tests
  • Loading branch information
benkehoe committed Feb 23, 2023
1 parent b8a7ddb commit cbbc898
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 52 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

`aws-whoami` uses [monotonic versioning](https://github.com/benkehoe/monotonic-versioning-manifesto) since v1.0 across both [the older Python implementation](https://github.com/benkehoe/aws-whoami) (compatibility number 1) and this Go implementation (compatibility number 2).

## v2.5

* Add `--disable-account-alias` flag.
* Handle paths in user ARNs.
* Disable account alias check by matching SSO Permission Set name.
* Internal revamp for testing.
* Change repo layout to work better with `go install`.
* Add tests.

## v2.4

* Handle root user
Expand Down
40 changes: 23 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,60 @@

> :warning: This is the successor to [the python implementation](https://github.com/benkehoe/aws-whoami) as a CLI tool. The other is still useful as a Python library.
You should know about [`aws sts get-caller-identity`](https://docs.aws.amazon.com/cli/latest/reference/sts/get-caller-identity.html),
which sensibly returns the identity of the caller. But even with `--output table`, I find this a bit lacking.
That ARN is a lot to visually parse, it doesn't tell you what region your credentials are configured for,
and I am not very good at remembering AWS account numbers. `aws-whoami` makes it better.
You should know about [`aws sts get-caller-identity`](https://docs.aws.amazon.com/cli/latest/reference/sts/get-caller-identity.html), which sensibly returns the identity of the caller.
But even with `--output table`, I find this a bit lacking.
That ARN is a lot to visually parse, it doesn't tell you what region your credentials are configured for, and I am not very good at remembering AWS account numbers. `aws-whoami` makes it better.

```
$ aws-whoami
Account: 123456789012
my-account-alias
Region: us-east-2
AssumedRole: MY-ROLE
AssumedRole: MyRole
RoleSessionName: ben
UserId: SOMEOPAQUEID:ben
Arn: arn:aws:sts::123456789012:assumed-role/MY-ROLE/ben
UserId: AROASOMEOPAQUEID:ben
Arn: arn:aws:sts::123456789012:assumed-role/MyRole/ben
```

Note: if you don't have permissions to [iam:ListAccountAliases](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAccountAliases.html),
your account alias won't appear. See below for disabling this check if getting a permission denied on this call raises flags in your organization.
Note: if you don't have permissions to [iam:ListAccountAliases](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAccountAliases.html), your account alias won't appear.
See below for disabling this check if getting a permission denied on this call raises flags in your organization.

## Install

```
go install github.com/benkehoe/aws-whoami-golang/aws-whoami@latest
go install github.com/benkehoe/aws-whoami-golang/v2/aws-whoami@latest
```

[Or download the latest release for your platform](https://github.com/benkehoe/aws-whoami-golang/releases/latest).


## Options

`aws-whoami` uses [`the AWS Go SDK v2`](https://aws.amazon.com/sdk-for-go/), so it'll pick up your credentials in [the normal ways](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#config-settings-and-precedence),
including with the `--profile` parameter.
`aws-whoami` uses [`the AWS Go SDK v2`](https://aws.amazon.com/sdk-for-go/), so it'll pick up your credentials in [the normal ways](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#config-settings-and-precedence), including with the `--profile` parameter.

If you'd like the output as a JSON object, use the `--json` flag.
See below for field names.

The `--disable-account-alias` flag disables account alias checking (see below).

Use `--version` to output the version.

## Account alias checking

By default, `aws-whoami` calls [`iam.ListAccountAliases`](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAccountAliases.html) to find the account name, if set.
If you don't have access to this API, it swallows that error.
By default, `aws-whoami` calls the [IAM `ListAccountAliases` API](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAccountAliases.html) to find the account name, if set.
If you don't have access to this API (the `iam:ListAccountAliases` IAM action), it swallows that error.
In general this is fine, but if it causes trouble (e.g., raising security alerts in your organization), you can disable it.

To fully disable account alias checking, set the environment variable `AWS_WHOAMI_DISABLE_ACCOUNT_ALIAS` to `true`.
To selectively disable it, you can also set it to a comma-separated list of values that will be matched against the following:
There are two ways to disable account alias checking.
The first is the `--disable-account-alias` flag.
The second, setting the environment variable `AWS_WHOAMI_DISABLE_ACCOUNT_ALIAS`, allows for persistent and selective control.

To fully disable account alias checking, set `AWS_WHOAMI_DISABLE_ACCOUNT_ALIAS` to `true`.
To selectively disable it, you can also set the value to a comma-separated list where each item will be matched against the following:
* The beginning or end of the account number
* The principal Name or ARN
* The principal name or ARN
* The role session name
* The SSO role (permission set) name

## JSON output

Expand All @@ -68,6 +73,7 @@ The JSON object that is printed when using the `--json` flag always includes the

`Type`, `Name`, and `RoleSessionName` (and `SSOPermissionSet`) are split from the ARN for convenience.
`RoleSessionName` is `null` for IAM users.
For the account root, both the `Type` and `Name` are `"root"`.

`SSOPermissionSet` is set if the assumed role name conforms to the format `AWSReservedSSO_{permission-set}_{random-tag}`, otherwise it is `null`.

Expand Down
91 changes: 57 additions & 34 deletions aws-whoami/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import (
"github.com/aws/smithy-go"
)

var Version string = "2.4"
var Version string = "2.5"
var DisableAccountAliasEnvVarName = "AWS_WHOAMI_DISABLE_ACCOUNT_ALIAS"

type Whoami struct {
Account string
Expand All @@ -47,39 +48,42 @@ type Whoami struct {
}

type WhoamiParams struct {
DisableAccountAlias bool
DisableAccountAliasAccounts []string
DisableAccountAlias bool
DisableAccountAliasValues []string
}

func NewWhoamiParams() WhoamiParams {
var params WhoamiParams
getDisableAccountAlias(&params)
disableAccountAliasValue := os.Getenv(DisableAccountAliasEnvVarName)
populateDisableAccountAlias(&params, disableAccountAliasValue)
return params
}

func getDisableAccountAlias(params *WhoamiParams) {
envStr := os.Getenv("AWS_WHOAMI_DISABLE_ACCOUNT_ALIAS")
switch strings.ToLower(envStr) {
func populateDisableAccountAlias(params *WhoamiParams, disableAccountAliasValue string) {
switch strings.ToLower(disableAccountAliasValue) {
case "":
fallthrough
case "0":
fallthrough
case "false":
params.DisableAccountAlias = false
params.DisableAccountAliasValues = nil
return
case "1":
fallthrough
case "true":
params.DisableAccountAlias = true
params.DisableAccountAliasValues = nil
return
default:
accounts := strings.Split(envStr, ",")
accounts := strings.Split(disableAccountAliasValue, ",")
if len(accounts) > 0 {
params.DisableAccountAlias = true
params.DisableAccountAliasAccounts = accounts
params.DisableAccountAliasValues = accounts
return
} else {
params.DisableAccountAlias = false
params.DisableAccountAliasValues = nil
return
}
}
Expand All @@ -89,37 +93,27 @@ func (params WhoamiParams) GetDisableAccountAlias(whoami Whoami) bool {
if !params.DisableAccountAlias {
return false
}
if params.DisableAccountAliasAccounts == nil {
if params.DisableAccountAliasValues == nil {
return true
}
for _, disabledValue := range params.DisableAccountAliasAccounts {
for _, disabledValue := range params.DisableAccountAliasValues {
if strings.HasPrefix(whoami.Account, disabledValue) || strings.HasSuffix(whoami.Account, disabledValue) {
return true
}
if whoami.Arn == disabledValue || whoami.Name == disabledValue {
return true
}
if whoami.Type == "assumed-role" && *whoami.RoleSessionName == disabledValue {
if whoami.RoleSessionName != nil && *whoami.RoleSessionName == disabledValue {
return true
}
if whoami.SSOPermissionSet != nil && *whoami.SSOPermissionSet == disabledValue {
return true
}
}
return false
}

func NewWhoami(awsConfig aws.Config, params WhoamiParams) (Whoami, error) {
stsClient := sts.NewFromConfig(awsConfig)

getCallerIdentityOutput, err := stsClient.GetCallerIdentity(context.TODO(), nil)

if err != nil {
return Whoami{}, err
}

var whoami Whoami
whoami.AccountAliases = make([]string, 0, 1)

whoami.Region = awsConfig.Region

func populateWhoamiFromGetCallerIdentityOutput(whoami *Whoami, getCallerIdentityOutput sts.GetCallerIdentityOutput) error {
whoami.Account = *getCallerIdentityOutput.Account
whoami.Arn = *getCallerIdentityOutput.Arn
whoami.UserId = *getCallerIdentityOutput.UserId
Expand All @@ -132,18 +126,21 @@ func NewWhoami(awsConfig aws.Config, params WhoamiParams) (Whoami, error) {
} else {
arnResourceFields = strings.SplitN(arnFields[len(arnFields)-1], "/", 2)
if len(arnResourceFields) < 2 {
return whoami, fmt.Errorf("arn %v has an unknown format", whoami.Arn)
return fmt.Errorf("arn %v has an unknown format", whoami.Arn)
}
}

whoami.Type = arnResourceFields[0]
if whoami.Type == "assumed-role" {
nameFields := strings.SplitN(arnResourceFields[1], "/", 2)
if len(arnResourceFields) < 2 {
return whoami, fmt.Errorf("arn %v has an unknown format", whoami.Arn)
return fmt.Errorf("arn %v has an unknown format", whoami.Arn)
}
whoami.Name = nameFields[0]
whoami.RoleSessionName = &nameFields[1]
} else if whoami.Type == "user" {
nameFields := strings.Split(arnResourceFields[1], "/")
whoami.Name = nameFields[len(nameFields)-1]
} else {
whoami.Name = arnResourceFields[1]
}
Expand All @@ -156,6 +153,29 @@ func NewWhoami(awsConfig aws.Config, params WhoamiParams) (Whoami, error) {
}
}

return nil
}

func NewWhoami(awsConfig aws.Config, params WhoamiParams) (Whoami, error) {
stsClient := sts.NewFromConfig(awsConfig)

getCallerIdentityOutput, err := stsClient.GetCallerIdentity(context.TODO(), nil)

if err != nil {
return Whoami{}, err
}

var whoami Whoami
whoami.AccountAliases = make([]string, 0, 1)

whoami.Region = awsConfig.Region

err = populateWhoamiFromGetCallerIdentityOutput(&whoami, *getCallerIdentityOutput)

if err != nil {
return whoami, err
}

if !params.GetDisableAccountAlias(whoami) {
iam_client := iam.NewFromConfig(awsConfig)

Expand Down Expand Up @@ -234,6 +254,7 @@ func (whoami Whoami) Format() string {
func main() {
profile := flag.String("profile", "", "A config profile to use")
useJson := flag.Bool("json", false, "Output as JSON")
disableAccountAlias := flag.Bool("disable-account-alias", false, "Disable account alias check")
showVersion := flag.Bool("version", false, "Display the version")
flag.Parse()

Expand All @@ -244,24 +265,26 @@ func main() {

awsConfig, err := config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(*profile))
if err != nil {
log.Fatal(err)
return
log.Fatal(err) // exits
}

whoamiParams := NewWhoamiParams()

if *disableAccountAlias {
whoamiParams.DisableAccountAlias = true
whoamiParams.DisableAccountAliasValues = nil
}

Whoami, err := NewWhoami(awsConfig, whoamiParams)

if err != nil {
log.Fatal(err)
return
log.Fatal(err) // exits
}

if *useJson {
bytes, err := json.Marshal(Whoami)
if err != nil {
log.Fatal(err)
return
log.Fatal(err) // exits
}
fmt.Println(string(bytes))
} else {
Expand Down
Loading

0 comments on commit cbbc898

Please sign in to comment.