From 9839a44ff880905f2c198b0ede1612844d33a204 Mon Sep 17 00:00:00 2001 From: Gwonsoo Lee Date: Sat, 8 Aug 2020 17:08:53 +0900 Subject: [PATCH] add profile feature --- README.md | 20 +++ cmd/setup/add.go | 6 +- cmd/setup/assume.go | 294 ++++++++++++++++++++++++++++++++++++------- cmd/setup/delete.go | 6 +- cmd/setup/edit.go | 6 +- cmd/setup/help.go | 6 +- cmd/setup/init.go | 6 +- cmd/setup/list.go | 6 +- cmd/setup/root.go | 11 +- cmd/setup/upgrade.go | 39 ++++++ cmd/setup/version.go | 8 +- cmd/setup/who.go | 39 ++++++ go.mod | 1 + go.sum | 2 + 14 files changed, 381 insertions(+), 69 deletions(-) create mode 100644 cmd/setup/upgrade.go create mode 100644 cmd/setup/who.go diff --git a/README.md b/README.md index f859f85..5a6de93 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,21 @@ $ brew install setup $ setup version ``` +* If you want to use profile feature in aws cli, then you need to upgrade to the latest version +- required version >= 1.1.0 +- If you finish upgrade, then you should upgrade configuration via `setup upgrade` +```bash +$ brew upgrade setup +$ brew version +v1.1.0 + +$ setup upgrade +``` ## How to use ### First init setup. * Session name should be your original IAM user name in the account from which you log in through console. +* This will be stored as configuration for default profile. ```bash $ setup init ? Your session name: @@ -65,3 +76,12 @@ $ setup ls [current role list] ``` + +## Use profile +* You can use profile with `--profile=` +```bash +$ setup list --profile test +$ setup add --profile test +$ setup edit --profile test +$ setup delete --profile test +``` diff --git a/cmd/setup/add.go b/cmd/setup/add.go index 22ce2b5..6fef76b 100644 --- a/cmd/setup/add.go +++ b/cmd/setup/add.go @@ -24,9 +24,9 @@ import ( // addCmd represents the add command var addCmd = &cobra.Command{ - Use: "add", - Short: "Add new assume role", - Long: `Add new assume role`, + Use: "add", + Short: "Add new assume role", + Long: `Add new assume role`, Aliases: []string{"a"}, Run: func(cmd *cobra.Command, args []string) { if err := AddNewAssumeRole(args); err != nil { diff --git a/cmd/setup/assume.go b/cmd/setup/assume.go index f883e43..c003573 100644 --- a/cmd/setup/assume.go +++ b/cmd/setup/assume.go @@ -17,7 +17,6 @@ package setup import ( "fmt" - "github.com/mitchellh/go-homedir" "gopkg.in/yaml.v2" "io" "io/ioutil" @@ -26,8 +25,14 @@ import ( "strings" "github.com/AlecAivazis/survey/v2" - "github.com/GwonsooLee/kubenx/pkg/aws" "github.com/GwonsooLee/kubenx/pkg/color" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/defaults" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" ) var ( @@ -35,6 +40,7 @@ var ( ) type AssumeList struct { + Profile string `yaml:"profile"` SessionName string `yaml:"session_name"` AssumeRoleList []AssumeRole `yaml:"assume_role_list"` } @@ -85,9 +91,15 @@ func Assume(out io.Writer, args []string) error { return fmt.Errorf("wrong command. please check `setup help") } - assumeCreds := aws.AssumeRole(targetRole.RoleArn, assumeMap.SessionName) + assumeCreds, err := getAssumeCreds(targetRole.RoleArn, assumeMap.SessionName) + if err != nil { + return err + } pbcopy := exec.Command("pbcopy") + if pbcopy == nil { + + } in, _ := pbcopy.StdinPipe() if err := pbcopy.Start(); err != nil { @@ -121,20 +133,53 @@ func Assume(out io.Writer, args []string) error { } func getAssumeList() (*AssumeList, error) { + aList, err := getTotalAssumeList() + if err != nil { + return nil, err + } + + if len(aList) == 0 { + return nil, fmt.Errorf("You need to upgrade setup, please use `setup upgrade`") + } + + profile := viper.GetString("profile") + for _, as := range aList { + if as.Profile == profile { + return &as, nil + } + } + + return nil, nil +} + +func getTotalAssumeList() ([]AssumeList, error) { if !checkFileExists(configFile) { return nil, fmt.Errorf("%s does not exist. please use `setup init`", configFile) } - a := AssumeList{} + aList := []AssumeList{} + + file, err := ioutil.ReadFile(configFile) + if err != nil { + return nil, err + } + + yaml.Unmarshal(file, &aList) + + return aList, nil +} + +func getSingleAssumeList() (*AssumeList, error) { + al := AssumeList{} file, err := ioutil.ReadFile(configFile) if err != nil { return nil, err } - yaml.Unmarshal(file, &a) + yaml.Unmarshal(file, &al) - return &a, nil + return &al, nil } func home() string { @@ -147,12 +192,45 @@ func AddNewAssumeRole(args []string) error { return fmt.Errorf("usage: setup add [key]") } + currentProfile := viper.GetString("profile") + color.Blue.Fprintf(os.Stdout, "current profile: %s", currentProfile) + aList, err := getTotalAssumeList() + if err != nil { + return err + } + + hasProfile := false + for _, al := range aList { + if al.Profile == currentProfile { + hasProfile = true + } + } + + var sessionName string + if ! hasProfile { + color.Red.Fprintf(os.Stdout, "register new profile first, %s", currentProfile) + prompt := &survey.Input{ + Message: "New session name: ", + } + survey.AskOne(prompt, &sessionName) + + aList = append(aList, AssumeList{ + Profile: currentProfile, + SessionName: sessionName, + AssumeRoleList: []AssumeRole{}, + }) + } + var target string if len(args) == 0 { prompt := &survey.Input{ Message: "Key: ", } survey.AskOne(prompt, &target) + + if len(target) == 0 { + return fmt.Errorf("you have to input key") + } } else { target = args[0] } @@ -163,21 +241,20 @@ func AddNewAssumeRole(args []string) error { } survey.AskOne(prompt, &assumeRole) - if assumeRole == "" { + if len(assumeRole) == 0 { return fmt.Errorf("you have to specify ARN of IAM role") } - al, err := getAssumeList() - if err != nil { - return fmt.Errorf(err.Error()) + for i, al := range aList { + if al.Profile == currentProfile { + aList[i].AssumeRoleList = append(al.AssumeRoleList, AssumeRole{ + Key: target, + RoleArn: assumeRole, + }) + } } - al.AssumeRoleList = append(al.AssumeRoleList, AssumeRole{ - Key: target, - RoleArn: assumeRole, - }) - - if err := SyncFile(*al); err != nil { + if err := SyncFile(aList); err != nil { return err } @@ -202,18 +279,52 @@ func Setup() error { } survey.AskOne(prompt, &sessionName) - al := AssumeList{} - al.SessionName = sessionName + aList := []AssumeList{} + aList = append(aList, AssumeList{ + Profile: "default", + SessionName: sessionName, + }) - if err := SyncFile(al); err != nil { + if err := SyncFile(aList); err != nil { return err } return nil } -func SyncFile(al AssumeList) error { - writeData, err := yaml.Marshal(al) +func Upgrade() error { + aList, err := getTotalAssumeList() + if err != nil { + return err + } + + if len(aList) == 0 { + al, err := getSingleAssumeList() + if err != nil { + return err + } + + if al == nil { + return fmt.Errorf("you do not have any setup profile, please use `setup init`") + } + + al.Profile = "default" + + aList = append(aList, *al) + + if err := SyncFile(aList); err != nil { + return err + } + color.Blue.Fprintf(os.Stdout, "successfully upgrade to latest") + } else { + color.Blue.Fprintf(os.Stdout, "already latest version setup") + } + + return nil +} + +func SyncFile(aList []AssumeList) error { + writeData, err := yaml.Marshal(aList) if err != nil { return err } @@ -238,32 +349,43 @@ func ListRole() error { out := os.Stdout color.Green.Fprintf(out, "[current role list]") - color.Cyan.Fprintf(out, strings.Join(assumeList,"\n")) + color.Cyan.Fprintf(out, strings.Join(assumeList, "\n")) return nil } func EditRole() error { out := os.Stdout - al, err := getAssumeList() + + currentProfile := viper.GetString("profile") + color.Blue.Fprintf(out, "current profile: %s", currentProfile) + + aList, err := getTotalAssumeList() if err != nil { - return fmt.Errorf(err.Error()) + return err } - assumeList := []string{} - for _, v := range al.AssumeRoleList { - assumeList = append(assumeList, v.Key) + newA := AssumeList{} + for _, as := range aList { + if as.Profile == currentProfile { + newA = as + } + } + + assumeKeys := []string{} + for _, v := range newA.AssumeRoleList { + assumeKeys = append(assumeKeys, v.Key) } var target string prompt := &survey.Select{ Message: "Choose account to edit:", - Options: assumeList, + Options: assumeKeys, } survey.AskOne(prompt, &target) - if target == "" { - fmt.Errorf("you have to choose key") + if len(target) == 0{ + return fmt.Errorf("you have to choose key") } var assumeRole string @@ -271,17 +393,23 @@ func EditRole() error { Message: "New role ARN: ", }, &assumeRole) - if assumeRole == "" { - fmt.Errorf("you have to specifiy assume role ARN") + if len(assumeRole) == 0 { + return fmt.Errorf("you have to specifiy assume role ARN") } - for i, v := range al.AssumeRoleList { + for i, v := range newA.AssumeRoleList { if v.Key == target { - al.AssumeRoleList[i].RoleArn = assumeRole + newA.AssumeRoleList[i].RoleArn = assumeRole + } + } + + for i, as := range aList { + if as.Profile == currentProfile { + aList[i] = newA } } - if err := SyncFile(*al); err != nil { + if err := SyncFile(aList); err != nil { return err } @@ -292,20 +420,31 @@ func EditRole() error { func DeleteCmd() error { out := os.Stdout - al, err := getAssumeList() + + currentProfile := viper.GetString("profile") + color.Blue.Fprintf(out, "current profile: %s", currentProfile) + + aList, err := getTotalAssumeList() if err != nil { - return fmt.Errorf(err.Error()) + return err } - assumeList := []string{} - for _, v := range al.AssumeRoleList { - assumeList = append(assumeList, v.Key) + newA := AssumeList{} + for _, as := range aList { + if as.Profile == currentProfile { + newA = as + } + } + + assumeKeys := []string{} + for _, v := range newA.AssumeRoleList { + assumeKeys = append(assumeKeys, v.Key) } var target string prompt := &survey.Select{ Message: "Choose account to delete:", - Options: assumeList, + Options: assumeKeys, } survey.AskOne(prompt, &target) @@ -314,15 +453,21 @@ func DeleteCmd() error { } newList := []AssumeRole{} - for _, v := range al.AssumeRoleList { + for _, v := range newA.AssumeRoleList { if v.Key != target { newList = append(newList, v) } } - al.AssumeRoleList = newList + newA.AssumeRoleList = newList + + for i, as := range aList { + if as.Profile == currentProfile { + aList[i] = newA + } + } - if err := SyncFile(*al); err != nil { + if err := SyncFile(aList); err != nil { return err } @@ -330,3 +475,64 @@ func DeleteCmd() error { return nil } + +func getAssumeCreds(arn string, session_name string) (*sts.Credentials, error) { + svc := getSTSSession() + input := &sts.AssumeRoleInput{ + RoleArn: aws.String(arn), + RoleSessionName: aws.String(session_name), + } + + result, err := svc.AssumeRole(input) + if err != nil { + return nil, err + } + return result.Credentials, nil +} + +func getSTSSession() *sts.STS { + ResetAWSEnvironmentVariable() + + awsRegion := viper.GetString("region") + profile := viper.GetString("profile") + + sess := session.Must( + session.NewSession(&aws.Config{ + Credentials: credentials.NewCredentials(&credentials.SharedCredentialsProvider{ + Filename: defaults.SharedCredentialsFilename(), + Profile: profile, + }), + }), + ) + svc := sts.New(sess, &aws.Config{Region: aws.String(awsRegion)}) + + return svc +} + +func ResetAWSEnvironmentVariable() { + os.Unsetenv("AWS_ACCESS_KEY_ID") + os.Unsetenv("AWS_SECRET_ACCESS_KEY") + os.Unsetenv("AWS_SESSION_TOKEN") +} + +func WhoAmI() error { + stsClient := getSTSSessionWithoutReset() + + result, err := stsClient.GetCallerIdentity(&sts.GetCallerIdentityInput{}) + if err != nil { + return err + } + + fmt.Println(result) + + return nil +} + +func getSTSSessionWithoutReset() *sts.STS { + awsRegion := viper.GetString("region") + + sess := session.Must(session.NewSession()) + svc := sts.New(sess, &aws.Config{Region: aws.String(awsRegion)}) + + return svc +} diff --git a/cmd/setup/delete.go b/cmd/setup/delete.go index d562efc..9919fb8 100644 --- a/cmd/setup/delete.go +++ b/cmd/setup/delete.go @@ -24,9 +24,9 @@ import ( // initCmd represents the init command var deleteCmd = &cobra.Command{ - Use: "delete", - Short: "Delete key", - Long: "Delete key", + Use: "delete", + Short: "Delete key", + Long: "Delete key", Aliases: []string{"d"}, Run: func(cmd *cobra.Command, args []string) { if err := DeleteCmd(); err != nil { diff --git a/cmd/setup/edit.go b/cmd/setup/edit.go index a78919f..fad88d9 100644 --- a/cmd/setup/edit.go +++ b/cmd/setup/edit.go @@ -24,9 +24,9 @@ import ( // initCmd represents the init command var editCmd = &cobra.Command{ - Use: "edit", - Short: "edit role arn of specific key", - Long: "edit role arn of specific key", + Use: "edit", + Short: "edit role arn of specific key", + Long: "edit role arn of specific key", Aliases: []string{"e"}, Run: func(cmd *cobra.Command, args []string) { if err := EditRole(); err != nil { diff --git a/cmd/setup/help.go b/cmd/setup/help.go index 801c816..0c832cb 100644 --- a/cmd/setup/help.go +++ b/cmd/setup/help.go @@ -21,9 +21,9 @@ import ( // helpCmd represents the help command var helpCmd = &cobra.Command{ - Use: "help", - Short: "show help", - Long: `show help`, + Use: "help", + Short: "show help", + Long: `show help`, Aliases: []string{"h"}, Run: func(cmd *cobra.Command, args []string) { cmd.Parent().Help() diff --git a/cmd/setup/init.go b/cmd/setup/init.go index b2c28c6..9f80f94 100644 --- a/cmd/setup/init.go +++ b/cmd/setup/init.go @@ -24,9 +24,9 @@ import ( // initCmd represents the init command var initCmd = &cobra.Command{ - Use: "init", - Short: "Init setup", - Long: `Init setup`, + Use: "init", + Short: "Init setup", + Long: `Init setup`, Aliases: []string{"i"}, Run: func(cmd *cobra.Command, args []string) { if err := Setup(); err != nil { diff --git a/cmd/setup/list.go b/cmd/setup/list.go index e95ef14..1a6c1e1 100644 --- a/cmd/setup/list.go +++ b/cmd/setup/list.go @@ -24,9 +24,9 @@ import ( // initCmd represents the init command var listCmd = &cobra.Command{ - Use: "list", - Short: "show assume role list", - Long: `show assume role list`, + Use: "list", + Short: "show assume role list", + Long: `show assume role list`, Aliases: []string{"ls"}, Run: func(cmd *cobra.Command, args []string) { if err := ListRole(); err != nil { diff --git a/cmd/setup/root.go b/cmd/setup/root.go index daa0a05..4f070fb 100644 --- a/cmd/setup/root.go +++ b/cmd/setup/root.go @@ -25,32 +25,37 @@ import ( ) var cfgFile string +var profile string +var region string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "setup", Short: "A command line tool for assuming role on AWS environment", Long: `A command line tool for assuming role on AWS environment`, + SilenceUsage: true, Args: func(cmd *cobra.Command, args []string) error { return Assume(os.Stdout, args) }, Run: func(cmd *cobra.Command, args []string) { }, - } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { - fmt.Println(err) os.Exit(1) } } func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.setup.yaml)") + rootCmd.PersistentFlags().StringVar(&profile, "profile", "default", "AWS profile") + rootCmd.PersistentFlags().StringVar(®ion, "region", "ap-northeast-2", "AWS region") + + viper.BindPFlag("profile", rootCmd.PersistentFlags().Lookup("profile")) + viper.BindPFlag("region", rootCmd.PersistentFlags().Lookup("region")) } // initConfig reads in config file and ENV variables if set. diff --git a/cmd/setup/upgrade.go b/cmd/setup/upgrade.go new file mode 100644 index 0000000..d84c7b7 --- /dev/null +++ b/cmd/setup/upgrade.go @@ -0,0 +1,39 @@ +/* +Copyright © 2020 DevopsArtFactory gwonsoo.lee@gmail.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package setup + +import ( + "os" + + "github.com/GwonsooLee/kubenx/pkg/color" + "github.com/spf13/cobra" +) + +// initCmd represents the init command +var upgradeCmd = &cobra.Command{ + Use: "upgrade", + Short: "Upgrade configuration to latest version", + Long: "Upgrade configuration to latest version", + Run: func(cmd *cobra.Command, args []string) { + if err := Upgrade(); err != nil { + color.Red.Fprintln(os.Stdout, err.Error()) + } + }, +} + +func init() { + rootCmd.AddCommand(upgradeCmd) +} diff --git a/cmd/setup/version.go b/cmd/setup/version.go index 92ba6f5..1b949fa 100644 --- a/cmd/setup/version.go +++ b/cmd/setup/version.go @@ -24,12 +24,12 @@ import ( // versionCmd represents the version command var versionCmd = &cobra.Command{ - Use: "version", - Short: "Check version of setup", - Long: "Check version of setup", + Use: "version", + Short: "Check version of setup", + Long: "Check version of setup", Aliases: []string{"v"}, Run: func(cmd *cobra.Command, args []string) { - version := "1.0.0" + version := "1.1.0" color.Blue.Fprintf(os.Stdout, "v%s", version) }, } diff --git a/cmd/setup/who.go b/cmd/setup/who.go new file mode 100644 index 0000000..22d1d73 --- /dev/null +++ b/cmd/setup/who.go @@ -0,0 +1,39 @@ +/* +Copyright © 2020 DevopsArtFactory gwonsoo.lee@gmail.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package setup + +import ( + "os" + + "github.com/GwonsooLee/kubenx/pkg/color" + "github.com/spf13/cobra" +) + +// initCmd represents the init command +var whoCmd = &cobra.Command{ + Use: "who", + Short: "check current setup user", + Long: `check current setup user`, + Run: func(cmd *cobra.Command, args []string) { + if err := WhoAmI(); err != nil { + color.Red.Fprintln(os.Stdout, err.Error()) + } + }, +} + +func init() { + rootCmd.AddCommand(whoCmd) +} diff --git a/go.mod b/go.mod index ee170f2..e3b7b0e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/AlecAivazis/survey/v2 v2.1.0 github.com/GwonsooLee/kubenx v1.0.0 + github.com/aws/aws-sdk-go v1.29.29 github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.3.3 // indirect diff --git a/go.sum b/go.sum index de5c06f..0c10f9f 100644 --- a/go.sum +++ b/go.sum @@ -37,7 +37,9 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apaxa-go/eval v0.0.0-20171223182326-1d18b251d679 h1:iTmI8Fqjh2VU/hADCC/Nq9pvDCHCK2UNh0KfERvnkIM= github.com/apaxa-go/eval v0.0.0-20171223182326-1d18b251d679/go.mod h1:yCWGQBpTUzqKEfPFn43j9Spr7GLwB8ytxK4Q0tPuZ7g= +github.com/apaxa-go/helper v0.0.0-20180607175117-61d31b1c31c3 h1:badF2fxl2BsWu2f01OYRU9cNnbrOSoOlayijH7r9ip4= github.com/apaxa-go/helper v0.0.0-20180607175117-61d31b1c31c3/go.mod h1:42ENZ1Wd+1+1pgQWSQ/naAWaaP/uKw1zmnrMzBBNyTQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=