Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add whoami command #85

Merged
merged 2 commits into from
Aug 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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