Skip to content

Commit

Permalink
feat: implement the initial retrieve context call (#1)
Browse files Browse the repository at this point in the history
**Issue #, if available:**

## Description of changes:

As the initial feature we are displaying the context of the user. For
now we only include the `CallerIdentity` details but this can be
extended in the future.

**Checklist**

<!--- Leave unchecked if your change doesn't seem to apply -->

* [x] Update tests
* [x] Update docs
* [x] PR title follows [conventional commit
semantics](https://www.conventionalcommits.org/en/v1.0.0-beta.2/#commit-message-for-a-fix-using-an-optional-issue-number)

By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.
  • Loading branch information
Nr18 committed Sep 1, 2023
2 parents c14ef0c + 5685019 commit ceb6e44
Show file tree
Hide file tree
Showing 19 changed files with 492 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ complexity: gocyclo gocognit ## Check codebase for complexity
.PHONY: gocyclo
gocyclo:
$(info Calculating gocycle score)
gocyclo -over 3 -avg .
gocyclo -over 3 -ignore "_test" -avg .

.PHONY: gocognit
gocognit:
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Golang Template
# AWS IAM User

![Go Version](https://img.shields.io/github/go-mod/go-version/conijnio/aws-iam-user)
[![License](https://img.shields.io/badge/License-Apache2-green.svg)](./LICENSE)
Expand All @@ -9,7 +9,10 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/conijnio/aws-iam-user)](https://goreportcard.com/report/github.com/conijnio/aws-iam-user)
[![Coverage Status](https://coveralls.io/repos/github/conijnio/aws-iam-user/badge.svg?branch=main)](https://coveralls.io/github/conijnio/aws-iam-user?branch=main)

Template repository for Golang projects.
When using IAM users it could be cumbersome to rotate your `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
This could lead into resources being in a non-compliant state. The `aws-iam-user` tool will address exactly that!

More information can be found on the [documentation pages](https://conijnio.github.io/aws-iam-user/).

## Prerequisites

Expand Down
31 changes: 27 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ package cmd

import (
"fmt"
"github.com/conijnio/aws-iam-user/pkg/core"
"github.com/conijnio/aws-iam-user/pkg/adapters"
"github.com/conijnio/aws-iam-user/pkg/models"
"github.com/spf13/cobra"
"os"
)

var (
Debug bool
osExit = os.Exit
Debug bool
osExit = os.Exit
profile string
region = "eu-west-1" // TODO: Read the default region from the configuration
adapter adapters.IUserAdapter
)

var rootCmd = &cobra.Command{
Expand All @@ -18,13 +22,31 @@ var rootCmd = &cobra.Command{
PreRun: preRun,
Example: "aws-iam-user",
RunE: func(cmd *cobra.Command, args []string) error {
return core.MainRoutine()
user, err := adapter.LoadUser()

if err != nil {
return err
}

renderOutput(user)
return nil
},
}

func renderOutput(user *models.User) {
fmt.Printf("Found the following IAM User using the '%s' profile\n\n", profile)
fmt.Printf("Account: %s\n", user.Account)
fmt.Printf("UserId: %s\n", user.UserId)
fmt.Printf("UserName: %s\n", user.Name)
fmt.Printf("Type: %s\n", user.Type)
fmt.Printf("Arn: %s\n", user.Arn)
}

func init() {
rootCmd.CompletionOptions.DisableDefaultCmd = true
rootCmd.PersistentFlags().BoolVarP(&Debug, "debug", "d", false, "verbose logging")
rootCmd.PersistentFlags().StringVar(&profile, "profile", "default", "The AWS profile used")
rootCmd.PersistentFlags().StringVar(&region, "region", region, "The AWS profile used")
}

func Execute() {
Expand All @@ -40,4 +62,5 @@ func SetVersion(version string) {

func preRun(cmd *cobra.Command, args []string) {
toggleDebug()
adapter = adapters.LoadUserAdapter(region, profile)
}
36 changes: 33 additions & 3 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"bytes"
"errors"
"github.com/conijnio/aws-iam-user/pkg/adapters"
"github.com/conijnio/aws-iam-user/pkg/models"
"github.com/spf13/cobra"
"strings"
"testing"
Expand All @@ -20,24 +22,52 @@ func execute(t *testing.T, c *cobra.Command, args ...string) (string, error) {
return strings.TrimSpace(buf.String()), err
}

type MockClientAdapter struct {
LoadUserFailure bool
}

func (u *MockClientAdapter) LoadUser() (*models.User, error) {
if u.LoadUserFailure {
return &models.User{}, errors.New("failed")
}
return &models.User{}, nil
}

func TestRootCmd(t *testing.T) {
var args []string

adapters.RegisterAdapter("eu-west-1", "default", &MockClientAdapter{})

_, err := execute(t, rootCmd, args...)

if err != nil {
t.Errorf("No error was expected but received: %s", err)
}
}

func TestRootCmdLoadUserFailure(t *testing.T) {
var args []string

adapters.RegisterAdapter("eu-west-1", "default", &MockClientAdapter{
LoadUserFailure: true,
})

_, err := execute(t, rootCmd, args...)

if err == nil {
t.Errorf("Error was expected but received: nil")
}
}

func TestExecute(t *testing.T) {
var got int

oldOsExit := osExit
defer func() { osExit = oldOsExit }()
osExit = func(code int) { got = code }

adapters.RegisterAdapter("eu-west-1", "default", &MockClientAdapter{})

osExit = func(code int) {
got = code
}
Execute()
if exp := 0; got != exp {
t.Errorf("Expected exit code: %d, got: %d", exp, got)
Expand Down
9 changes: 8 additions & 1 deletion docs/content/1-introduction/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,11 @@ weight: 1
pre: "<b>1. </b>"
---

> TODO: Write introduction text
This project removes some cumbersome tasks when you are using the IAM User programmatic access method.

## Viewing Credential details

When you have multiple sets of credentials in your `~.aws/credentials` file. It can become harder to know what credentials belong to what AWS Account.
You can use a descriptive profile name, but what if you did not do that? How do you know to what AWS Account the credentials belong?

The [Retrieve credentials context](../1-user-guide/1-retrieve-credential-context) section of the [User Guide](../1-user-guide) can help you out!
8 changes: 0 additions & 8 deletions docs/content/1-user-guide/1-getting-started.md

This file was deleted.

10 changes: 10 additions & 0 deletions docs/content/1-user-guide/1-retrieve-credential-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: "Retrieve credential context"
weight: 1
---

## Retrieve credential context

```shell
aws-iam-user --profile [Name of your profile] --region [AWS region used for API calls]
```
2 changes: 0 additions & 2 deletions docs/content/1-user-guide/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ weight: 2
pre: "<b>2. </b>"
---

> TODO: Write introduction for the getting started guide!
This user guide has the following chapters:

{{% children %}}
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@ module github.com/conijnio/aws-iam-user
go 1.20

require (
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.37
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5
github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20230830142806-6a3ab3b2c742
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
)

require (
github.com/aws/aws-sdk-go-v2/credentials v1.13.35 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 // indirect
github.com/aws/smithy-go v1.14.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.10.0 // indirect
Expand Down
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
github.com/aws/aws-sdk-go-v2/config v1.18.37 h1:RNAfbPqw1CstCooHaTPhScz7z1PyocQj0UL+l95CgzI=
github.com/aws/aws-sdk-go-v2/config v1.18.37/go.mod h1:8AnEFxW9/XGKCbjYDCJy7iltVNyEI9Iu9qC21UzhhgQ=
github.com/aws/aws-sdk-go-v2/credentials v1.13.35 h1:QpsNitYJu0GgvMBLUIYu9H4yryA5kMksjeIVQfgXrt8=
github.com/aws/aws-sdk-go-v2/credentials v1.13.35/go.mod h1:o7rCaLtvK0hUggAGclf76mNGGkaG5a9KWlp+d9IpcV8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 h1:GPUcE/Yq7Ur8YSUk6lVkoIMWnJNO0HT18GUzCWCgCI0=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 h1:oCvTFSDi67AX0pOX3PuPdGFewvLRU2zzFSrTsgURNo0=
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 h1:dnInJb4S0oy8aQuri1mV6ipLlnZPfnsDNB9BGO9PDNY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20230830142806-6a3ab3b2c742 h1:7OFR7QS8GrtsE9sJr384yxEkV0nT7Ev9vz4WG+c/mhY=
github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20230830142806-6a3ab3b2c742/go.mod h1:qcs782jWmSQW2exwfKW39rOvOJBZ4xzO8dVLoFF62Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand All @@ -20,6 +50,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3 changes: 3 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand All @@ -7,3 +9,4 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
69 changes: 69 additions & 0 deletions pkg/adapters/adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package adapters

import (
"errors"
"github.com/conijnio/aws-iam-user/pkg/models"
"testing"
)

type MockAdapter struct {
LoadUserFailure bool
}

func (u *MockAdapter) LoadUser() (*models.User, error) {
if u.LoadUserFailure {
return &models.User{}, errors.New("failed")
}
return &models.User{}, nil
}

func TestAdapterRegistration(t *testing.T) {
adapter := &MockAdapter{}
RegisterAdapter("eu-west-1", "default", adapter)

if res := HasAdapter("eu-west-1", "default"); !res {
t.Errorf("Expected True received %t", res)
}

if GetAdapter("eu-west-1", "default") != adapter {
t.Errorf("Expected the given adapter to be returned")
}
}

func TestSecondAdapterRegistrationDifferentProfile(t *testing.T) {
adapter := &MockAdapter{}
RegisterAdapter("eu-west-1", "default", adapter)

if res := HasAdapter("eu-west-1", "default"); !res {
t.Errorf("Expected True received %t", res)
}

if GetAdapter("eu-west-1", "default") != adapter {
t.Errorf("Expected the given adapter to be returned")
}
}

func TestThirdAdapterRegistrationDifferentProfile(t *testing.T) {
adapter := &MockAdapter{}
RegisterAdapter("eu-west-1", "default", adapter)

if res := HasAdapter("eu-west-1", "default"); !res {
t.Errorf("Expected True received %t", res)
}

if GetAdapter("eu-west-1", "default") != adapter {
t.Errorf("Expected the given adapter to be returned")
}
}

func TestHasUnknownProfileAdapter(t *testing.T) {
if res := HasAdapter("eu-west-1", "non-existing-adapter"); res {
t.Errorf("Expected False received %t", res)
}
}

func TestHasUnknownRegionAdapter(t *testing.T) {
if res := HasAdapter("eu-unknown-1", "non-existing-adapter"); res {
t.Errorf("Expected False received %t", res)
}
}
30 changes: 30 additions & 0 deletions pkg/adapters/adapters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package adapters

var registeredAdapters = map[string]map[string]IUserAdapter{}

func prepareRegionMap(region string) {
if _, exists := registeredAdapters[region]; !exists {
registeredAdapters[region] = make(map[string]IUserAdapter)
}
}

func RegisterAdapter(region string, profile string, adapter IUserAdapter) {
prepareRegionMap(region)
registeredAdapters[region][profile] = adapter
}

func HasAdapter(region string, profile string) bool {
if _, exists := registeredAdapters[region]; !exists {
return false
}

if _, exists := registeredAdapters[region][profile]; !exists {
return false
}

return true
}

func GetAdapter(region string, profile string) IUserAdapter {
return registeredAdapters[region][profile]
}
Loading

0 comments on commit ceb6e44

Please sign in to comment.