diff --git a/cmd/ack-ram-tool/main.go b/cmd/ack-ram-tool/main.go index c1db6141..b4e1863f 100644 --- a/cmd/ack-ram-tool/main.go +++ b/cmd/ack-ram-tool/main.go @@ -7,6 +7,7 @@ import ( "github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider" "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl" + "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/auth" "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/credentialplugin" "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/exportcredentials" "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/rbac" @@ -44,6 +45,7 @@ func init() { version.SetupVersionCmd(rootCmd) rbac.SetupCmd(rootCmd) exportcredentials.SetupCmd(rootCmd) + auth.SetupCmd(rootCmd) rootCmd.PersistentFlags().StringVar(&ctl.GlobalOption.Region, "region-id", "", "The region to use"+ diff --git a/pkg/ctl/auth/root.go b/pkg/ctl/auth/root.go new file mode 100644 index 00000000..b6f79ed1 --- /dev/null +++ b/pkg/ctl/auth/root.go @@ -0,0 +1,19 @@ +package auth + +import ( + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "auth", + Short: "Utils for auth.", + Long: `Utils for auth.`, +} + +func init() { + setupWhoamiCmdCmd(rootCmd) +} + +func SetupCmd(root *cobra.Command) { + root.AddCommand(rootCmd) +} diff --git a/pkg/ctl/auth/whoami.go b/pkg/ctl/auth/whoami.go new file mode 100644 index 00000000..4cfd6c7c --- /dev/null +++ b/pkg/ctl/auth/whoami.go @@ -0,0 +1,58 @@ +package auth + +import ( + "context" + "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl" + "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/common" + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + "os" +) + +var whoamiCmd = &cobra.Command{ + Use: "whoami", + Short: "Check who you are", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + ctl.GlobalOption.Verbose = true + client := common.GetClientOrDie() + + acc, err := client.GetCallerIdentity(context.TODO()) + common.ExitIfError(err) + table := tablewriter.NewWriter(os.Stdout) + //table.SetAutoMergeCells(true) + table.SetAutoWrapText(true) + table.SetAutoFormatHeaders(false) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetHeaderLine(false) + table.EnableBorder(false) + table.SetTablePadding(" ") + table.SetNoWhiteSpace(true) + + table.Append([]string{"AccountId", acc.RootUId}) + table.Append([]string{"IdentityType", acc.IdentityType()}) + if acc.Role.RoleId != "" { + table.Append([]string{"RoleId", acc.Role.RoleId}) + table.Append([]string{"RoleName", acc.Role.RoleName}) + } else if acc.User.Id != "" { + table.Append([]string{"UserId", acc.User.Id}) + table.Append([]string{"UserName", acc.User.Name}) + } + table.Append([]string{"Arn", acc.Arn}) + table.Append([]string{"PrincipalId", acc.PrincipalId}) + + table.Render() + }, +} + +func setupWhoamiCmdCmd(rootCmd *cobra.Command) { + rootCmd.AddCommand(whoamiCmd) + + whoamiCmd.Flags().StringVar( + &ctl.GlobalOption.FinalAssumeRoleAnotherRoleArn, "role-arn", "", + "Assume an RAM Role ARN when send request or sign token") +} diff --git a/pkg/ctl/common/client.go b/pkg/ctl/common/client.go index c38f6b3b..5407ef88 100644 --- a/pkg/ctl/common/client.go +++ b/pkg/ctl/common/client.go @@ -1,10 +1,10 @@ package common import ( + "context" "fmt" "os" - "github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudgo" "github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudgo/env" "github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/credentialsgov13" "github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider" @@ -92,6 +92,7 @@ func getCredential(opt getCredentialOption) (provider.CredentialsProvider, error ignoreAliyuncli := opt.ignoreAliyuncli aliyuncliProfileName := opt.aliyuncliProfileName + // env if credentialFilePath == "" && aliyuncliConfigFilePath == "" { if sessionName != "" { _ = os.Setenv(env.EnvRoleSessionName, sessionName) @@ -99,15 +100,24 @@ func getCredential(opt getCredentialOption) (provider.CredentialsProvider, error if !ignoreEnv { log.Logger.Debug("try to get credentials from environment variables") // TODO: support ecs ALIBABA_CLOUD_ECS_METADATA - if cred, err := env.NewCredentialsProvider(env.CredentialsProviderOptions{ + cred, _ := env.NewCredentialsProvider(env.CredentialsProviderOptions{ STSEndpoint: opt.stsEndpoint, - }); err == nil && cred != nil { - log.Logger.Debugf("use credentials from environment variables") - return cred, err + }) + if cred != nil { + _, err := cred.Credentials(context.TODO()) + if err == nil || !provider.IsNotEnableError(err) { + log.Logger.Debugf("use credentials from environment variables") + return cred, nil + } else { + log.Logger.Debugf("get credentials from environment variables failed: %+v, try another method", + err) + } } log.Logger.Debug("not found credentials from environment variables") } } + + // aliyun cli config if aliyuncliConfigFilePath == "" { aliyuncliConfigFilePath, _ = utils.ExpandPath("~/.aliyun/config.json") if path, err := checkFileExist(aliyuncliConfigFilePath); err != nil && os.IsNotExist(err) { @@ -117,7 +127,6 @@ func getCredential(opt getCredentialOption) (provider.CredentialsProvider, error aliyuncliConfigFilePath = path } } - if !ignoreAliyuncli { if path, err := checkFileExist(aliyuncliConfigFilePath); err != nil { return nil, fmt.Errorf("read file %s: %w", aliyuncliConfigFilePath, err) @@ -144,6 +153,7 @@ func getCredential(opt getCredentialOption) (provider.CredentialsProvider, error } } + // ini config if credentialFilePath == "" { path, err := checkFileExist(credentials.PATHCredentialFile) if err != nil && os.IsNotExist(err) { @@ -153,19 +163,22 @@ func getCredential(opt getCredentialOption) (provider.CredentialsProvider, error credentialFilePath = path } - log.Logger.Debugf("get default credentials from %s", utils.ShortHomePath(credentialFilePath)) + log.Logger.Debugf("try to get default credentials from %s", utils.ShortHomePath(credentialFilePath)) if path, err := checkFileExist(credentialFilePath); err != nil { return nil, fmt.Errorf("read file %s: %w", credentialFilePath, err) } else { credentialFilePath = path } - _ = os.Setenv(credentials.ENVCredentialFile, credentialFilePath) - cred, err := credentials.NewCredential(nil) + cred, err := provider.NewIniConfigProvider(provider.INIConfigProviderOptions{ + ConfigPath: credentialFilePath, + STSEndpoint: opt.stsEndpoint, + Logger: log.ProviderLogger(), + }) if err != nil { return nil, err } - return alibabacloudgo.NewCredentialsProviderWrapper(cred), nil + return cred, nil } func GetClientOrDie() *openapi.Client { diff --git a/pkg/openapi/sts.go b/pkg/openapi/sts.go index ecff0d02..7d8ed2e5 100644 --- a/pkg/openapi/sts.go +++ b/pkg/openapi/sts.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/AliyunContainerService/ack-ram-tool/pkg/types" "github.com/alibabacloud-go/tea/tea" + "strings" "time" "github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/oidctoken" @@ -42,33 +43,58 @@ func (c *Client) GetCallerIdentity(ctx context.Context) (*types.Account, error) return nil, fmt.Errorf("unkown resp: %s", resp.String()) } body := resp.Body + arn := getRealArn(tea.StringValue(body.Arn)) switch tea.StringValue(body.IdentityType) { case "Account": return &types.Account{ - Type: types.AccountTypeRoot, - RootUId: tea.StringValue(body.AccountId), + PrincipalId: tea.StringValue(body.PrincipalId), + Arn: arn, + Type: types.AccountTypeRoot, + RootUId: tea.StringValue(body.AccountId), User: types.RamUser{ Id: tea.StringValue(body.UserId), }, }, nil case "RAMUser": + parts := strings.Split(arn, "/") + name := parts[len(parts)-1] return &types.Account{ - Type: types.AccountTypeUser, - RootUId: tea.StringValue(body.AccountId), + PrincipalId: tea.StringValue(body.PrincipalId), + Arn: arn, + Type: types.AccountTypeUser, + RootUId: tea.StringValue(body.AccountId), User: types.RamUser{ - Id: tea.StringValue(body.UserId), + Id: tea.StringValue(body.UserId), + Name: name, }, }, nil case "AssumedRoleUser": + parts := strings.Split(arn, "/") + name := parts[len(parts)-1] return &types.Account{ - Type: types.AccountTypeRole, - RootUId: tea.StringValue(body.AccountId), + PrincipalId: tea.StringValue(body.PrincipalId), + Arn: arn, + Type: types.AccountTypeRole, + RootUId: tea.StringValue(body.AccountId), Role: types.RamRole{ - RoleId: tea.StringValue(body.RoleId), - Arn: tea.StringValue(body.Arn), + RoleId: tea.StringValue(body.RoleId), + Arn: tea.StringValue(body.Arn), + RoleName: name, }, }, nil } return nil, fmt.Errorf("unkown resp: %s", resp.String()) } + +func getRealArn(raw string) string { + roleKey := ":assumed-role/" + arn := raw + if strings.Contains(raw, roleKey) { + arn = strings.Replace(arn, roleKey, ":role/", 1) + parts := strings.Split(arn, "/") + parts = parts[:len(parts)-1] + arn = strings.Join(parts, "/") + } + return arn +} diff --git a/pkg/types/ram.go b/pkg/types/ram.go index d9ed2ff7..43694439 100644 --- a/pkg/types/ram.go +++ b/pkg/types/ram.go @@ -34,6 +34,9 @@ type Account struct { RootUId string User RamUser Role RamRole + + PrincipalId string + Arn string } type RamRole struct { @@ -267,6 +270,18 @@ func NewFakeAccount(uid int64) Account { return acc } +func (a *Account) IdentityType() string { + switch a.Type { + case AccountTypeRoot: + return "Account" + case AccountTypeUser: + return "RAMUser" + case AccountTypeRole: + return "AssumedRoleUser" + } + return "" +} + func (a *Account) MarkDeleted() { switch a.Type { case AccountTypeUser: diff --git a/vendor/github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider/aliyuncli.go b/vendor/github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider/aliyuncli.go index a53dc1cd..8247b83e 100644 --- a/vendor/github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider/aliyuncli.go +++ b/vendor/github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider/aliyuncli.go @@ -11,6 +11,7 @@ import ( "os/exec" "regexp" "strings" + "sync" "time" ) @@ -30,6 +31,9 @@ type profileWrapper struct { type CLIConfigProvider struct { profile *profileWrapper logger Logger + + pr CredentialsProvider + lock sync.Mutex } type CLIConfigProviderOptions struct { @@ -89,13 +93,30 @@ func loadProfile(path string, name string, conf *Configuration) (*Configuration, } func (c *CLIConfigProvider) Credentials(ctx context.Context) (*Credentials, error) { - p, err := c.profile.getProvider() + p, err := c.getAndUpdateProvider() if err != nil { return nil, err } return p.Credentials(ctx) } +func (c *CLIConfigProvider) getAndUpdateProvider() (CredentialsProvider, error) { + c.lock.Lock() + defer c.lock.Unlock() + + if c.pr != nil { + return c.pr, nil + } + + pr, err := c.profile.getProvider() + if err != nil { + return nil, err + } + c.pr = pr + + return pr, nil +} + func (p *profileWrapper) getProvider() (CredentialsProvider, error) { cp := p.cp