Skip to content

Commit

Permalink
feat: Add whoami command (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
patricksanders authored Aug 27, 2021
1 parent 815553e commit 8912fd2
Show file tree
Hide file tree
Showing 16 changed files with 301 additions and 118 deletions.
4 changes: 3 additions & 1 deletion cmd/credential_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"fmt"
"time"

"github.com/netflix/weep/internal/aws"

"github.com/netflix/weep/internal/creds"
"github.com/netflix/weep/internal/util"

Expand Down Expand Up @@ -114,7 +116,7 @@ func runCredentialProcess(cmd *cobra.Command, args []string) error {
return nil
}

func printCredentialProcess(credentials *creds.AwsCredentials) {
func printCredentialProcess(credentials *aws.Credentials) {
expirationTimeFormat := credentials.Expiration.Format(time.RFC3339)

credentialProcessOutput := &creds.CredentialProcess{
Expand Down
4 changes: 3 additions & 1 deletion cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"os"
"strings"

"github.com/netflix/weep/internal/aws"

"github.com/netflix/weep/internal/creds"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -60,7 +62,7 @@ func isFish() bool {
}
}

func printExport(creds *creds.AwsCredentials) {
func printExport(creds *aws.Credentials) {
if isFish() {
// fish has a different way of setting variables than bash/zsh and others
fmt.Printf("set -x AWS_ACCESS_KEY_ID %s && set -x AWS_SECRET_ACCESS_KEY %s && set -x AWS_SESSION_TOKEN %s\n",
Expand Down
4 changes: 3 additions & 1 deletion cmd/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"strconv"
"time"

"github.com/netflix/weep/internal/aws"

"github.com/netflix/weep/internal/creds"
"github.com/netflix/weep/internal/util"

Expand Down Expand Up @@ -153,7 +155,7 @@ func isExpiring(filename, profile string, thresholdMinutes int) (bool, error) {
return false, nil
}

func writeCredentialsFile(credentials *creds.AwsCredentials, profile, filename string) error {
func writeCredentialsFile(credentials *aws.Credentials, profile, filename string) error {
var credentialsINI *ini.File
var err error

Expand Down
5 changes: 5 additions & 0 deletions cmd/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ system.

var versionShortHelp = "Print version information"
var versionLongHelp = ``

var whoamiShortHelp = "Print information about current AWS credentials"
var whoamiLongHelp = `The whoami command retrieves information about your AWS credentials from AWS STS using the default
credential provider chain. If SWAG (https://github.com/Netflix-Skunkworks/swag-api) is enabled, weep will
attempt to enrich the output with additional data.`
59 changes: 59 additions & 0 deletions cmd/whoami.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cmd

import (
"fmt"
"os"
"strings"
"text/tabwriter"

"github.com/netflix/weep/internal/aws"
"github.com/netflix/weep/internal/swag"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func init() {
rootCmd.AddCommand(whoamiCmd)
}

var whoamiCmd = &cobra.Command{
Use: "whoami",
Short: whoamiShortHelp,
Long: whoamiLongHelp,
RunE: runWhoami,
SilenceUsage: true,
}

func runWhoami(cmd *cobra.Command, args []string) error {
session := aws.GetSession()
callerIdentity, err := aws.GetCallerIdentity(session)
if err != nil {
return err
}
var name string
if viper.GetBool("swag.enable") {
name, err = swag.AccountName(*callerIdentity.Account)
if err != nil {
cmd.Printf("Failed to get account info from SWAG: %v\n", err)
}
}
role := roleFromArn(*callerIdentity.Arn)

w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
fmt.Fprintf(w, "Role:\t%s\n", role)
if name != "" {
fmt.Fprintf(w, "Account:\t%s (%s)\n", name, *callerIdentity.Account)
} else {
fmt.Fprintf(w, "Account:\t%s\n", *callerIdentity.Account)
}
fmt.Fprintf(w, "ARN:\t%s\n", *callerIdentity.Arn)
fmt.Fprintf(w, "UserId:\t%s\n", *callerIdentity.UserId)
w.Flush()

return nil
}

func roleFromArn(arn string) string {
parts := strings.Split(arn, "/")
return parts[1]
}
4 changes: 4 additions & 0 deletions configs/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ service:
- debug
args: # Args are command arguments. This configuration will start the metadata service with credentials for roleName
- roleName
swag: # Optionally use SWAG (https://github.com/Netflix-Skunkworks/swag-api) for AWS account information
enabled: false
use_mtls: false
url: https://swag.example.com/api
#challenge_settings: # (Optional) Username can be provided. If it is not provided, user will be prompted on first authentication attempt
# user: you@example.com
mtls_settings: # only needed if authentication_method is mtls
Expand Down
55 changes: 31 additions & 24 deletions internal/creds/aws.go → internal/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package creds
package aws

import (
"fmt"
Expand All @@ -25,9 +25,13 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/netflix/weep/internal/logging"
)

var log = logging.GetLogger()

// getSessionName returns the AWS session name, or defaults to weep if we can't find one.
func getSessionName(session *sts.STS) string {
identity, err := session.GetCallerIdentity(&sts.GetCallerIdentityInput{})
Expand All @@ -46,8 +50,8 @@ func getSessionName(session *sts.STS) string {
return splitId[1]
}

// getAssumeRoleCredentials uses the provided credentials to assume the role specified by roleArn.
func getAssumeRoleCredentials(id, secret, token, roleArn string) (string, string, string, error) {
// GetAssumeRoleCredentials uses the provided credentials to assume the role specified by roleArn.
func GetAssumeRoleCredentials(id, secret, token, roleArn string) (string, string, string, error) {
region := viper.GetString("aws.region")
staticCreds := credentials.NewStaticCredentials(id, secret, token)
awsSession := session.Must(session.NewSessionWithOptions(session.Options{
Expand All @@ -73,32 +77,35 @@ func getAssumeRoleCredentials(id, secret, token, roleArn string) (string, string
return *stsCreds.Credentials.AccessKeyId, *stsCreds.Credentials.SecretAccessKey, *stsCreds.Credentials.SessionToken, nil
}

// GetCredentialsC uses the provided Client to request credentials from ConsoleMe then
// follows the provided chain of roles to assume. Roles are assumed in the order in which
// they appear in the assumeRole slice.
func GetCredentialsC(client HTTPClient, role string, ipRestrict bool, assumeRole []string) (*AwsCredentials, error) {
resp, err := client.GetRoleCredentials(role, ipRestrict)
if err != nil {
return nil, err
}
func GetSession() *session.Session {
return session.Must(session.NewSession())
}

for _, assumeRoleArn := range assumeRole {
resp.AccessKeyId, resp.SecretAccessKey, resp.SessionToken, err = getAssumeRoleCredentials(resp.AccessKeyId, resp.SecretAccessKey, resp.SessionToken, assumeRoleArn)
if err != nil {
return nil, fmt.Errorf("role assumption failed for %s: %s", assumeRoleArn, err)
}
func GetCallerIdentity(awsSession *session.Session) (*sts.GetCallerIdentityOutput, error) {
if awsSession == nil {
awsSession = GetSession()
}

return resp, nil
stsSession := sts.New(awsSession)
input := &sts.GetCallerIdentityInput{}
return stsSession.GetCallerIdentity(input)
}

// GetCredentials requests credentials from ConsoleMe then follows the provided chain of roles to
// assume. Roles are assumed in the order in which they appear in the assumeRole slice.
func GetCredentials(role string, ipRestrict bool, assumeRole []string, region string) (*AwsCredentials, error) {
client, err := GetClient(region)
func ListAccountAliases(awsSession *session.Session) ([]*string, error) {
aliases := make([]*string, 0)
pageNum := 0
if awsSession == nil {
awsSession = GetSession()
}
iamSession := iam.New(awsSession)
input := &iam.ListAccountAliasesInput{}
err := iamSession.ListAccountAliasesPages(input, func(page *iam.ListAccountAliasesOutput, lastPage bool) bool {
pageNum++
fmt.Println(page)
aliases = append(aliases, page.AccountAliases...)
return !*page.IsTruncated
})
if err != nil {
return nil, err
}

return GetCredentialsC(client, role, ipRestrict, assumeRole)
return aliases, nil
}
11 changes: 11 additions & 0 deletions internal/aws/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package aws

import "github.com/netflix/weep/internal/types"

type Credentials struct {
AccessKeyId string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
SessionToken string `json:"SessionToken"`
Expiration types.Time `json:"Expiration"`
RoleArn string `json:"RoleArn"`
}
19 changes: 11 additions & 8 deletions internal/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
"testing"
"time"

"github.com/netflix/weep/internal/aws"
"github.com/netflix/weep/internal/types"

"github.com/netflix/weep/internal/creds"
"github.com/netflix/weep/internal/errors"
)
Expand Down Expand Up @@ -189,11 +192,11 @@ func TestCredentialCache_SetDefault(t *testing.T) {
expectedRole := "a"
expectedExpiration := time.Unix(1, 0).Round(0)
testClient, err := creds.GetTestClient(creds.ConsolemeCredentialResponseType{
Credentials: &creds.AwsCredentials{
Credentials: &aws.Credentials{
AccessKeyId: "a",
SecretAccessKey: "b",
SessionToken: "c",
Expiration: creds.Time(time.Unix(1, 0)),
Expiration: types.Time(time.Unix(1, 0)),
RoleArn: "e",
},
})
Expand All @@ -217,11 +220,11 @@ func TestCredentialCache_DefaultLastUpdated(t *testing.T) {
RoleCredentials: map[string]*creds.RefreshableProvider{},
}
testClient, err := creds.GetTestClient(creds.ConsolemeCredentialResponseType{
Credentials: &creds.AwsCredentials{
Credentials: &aws.Credentials{
AccessKeyId: "a",
SecretAccessKey: "b",
SessionToken: "c",
Expiration: creds.Time(time.Unix(1, 0)),
Expiration: types.Time(time.Unix(1, 0)),
RoleArn: "e",
},
})
Expand Down Expand Up @@ -260,11 +263,11 @@ func TestCredentialCache_DefaultArn(t *testing.T) {
RoleCredentials: map[string]*creds.RefreshableProvider{},
}
testClient, err := creds.GetTestClient(creds.ConsolemeCredentialResponseType{
Credentials: &creds.AwsCredentials{
Credentials: &aws.Credentials{
AccessKeyId: "a",
SecretAccessKey: "b",
SessionToken: "c",
Expiration: creds.Time(time.Unix(1, 0)),
Expiration: types.Time(time.Unix(1, 0)),
RoleArn: "e",
},
})
Expand Down Expand Up @@ -339,11 +342,11 @@ func TestCredentialCache_GetOrSet(t *testing.T) {
RoleCredentials: tc.CacheContents,
}
client, err := creds.GetTestClient(creds.ConsolemeCredentialResponseType{
Credentials: &creds.AwsCredentials{
Credentials: &aws.Credentials{
AccessKeyId: "a",
SecretAccessKey: "b",
SessionToken: "c",
Expiration: creds.Time(time.Unix(1, 0)),
Expiration: types.Time(time.Unix(1, 0)),
RoleArn: tc.ExpectedResult.RoleArn,
},
})
Expand Down
3 changes: 3 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func init() {
viper.SetDefault("service.run", []string{"service", "run"})
viper.SetDefault("service.args", []string{})
viper.SetDefault("service.flags", []string{})
viper.SetDefault("swag.enable", false)
viper.SetDefault("swag.use_mtls", false)
viper.SetDefault("swag.url", "")

// Set aliases for backward-compatibility
viper.RegisterAlias("server.ecs_credential_provider_port", "server.port")
Expand Down
Loading

0 comments on commit 8912fd2

Please sign in to comment.