forked from RiotGames/key-conjurer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
switch.go
190 lines (165 loc) · 6.02 KB
/
switch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package main
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/riotgames/key-conjurer/internal/tencent"
"github.com/spf13/cobra"
)
var (
FlagRoleSessionName = "role-session-name"
FlagOutputType = "out"
FlagShellType = "shell"
FlagAWSCLIPath = "awscli"
FlagTencentCLIPath = "tencentcli"
FlagCloudType = "cloud"
)
func init() {
switchCmd.Flags().String(FlagRoleSessionName, "KeyConjurer-AssumeRole", "the name of the role session name that will show up in CloudTrail logs")
switchCmd.Flags().StringP(FlagOutputType, "o", outputTypeEnvironmentVariable, "Format to save new credentials in. Supported outputs: env, awscli,tencentcli")
switchCmd.Flags().String(FlagShellType, shellTypeInfer, "If output type is env, determines which format to output credentials in - by default, the format is inferred based on the execution environment. WSL users may wish to overwrite this to `bash`")
switchCmd.Flags().String(FlagAWSCLIPath, "~/.aws/", "Path for directory used by the aws-cli tool. Default is \"~/.aws\".")
switchCmd.Flags().String(FlagTencentCLIPath, "~/.tencent/", "Path for directory used by the tencent-cli tool. Default is \"~/.tencent\".")
switchCmd.Flags().String(FlagCloudType, "aws", "Choose a cloud vendor. Default is aws. Can choose aws or tencent")
}
var switchCmd = cobra.Command{
Use: "switch <account-id>",
Short: "Switch from the current Cloud (AWS or Tencent) account into the one with the given Account ID.",
Long: `Attempt to AssumeRole into the given Cloud (AWS or Tencent) with the current credentials. You only need to use this if you are a power user or network engineer with access to many accounts.
This is used when a "bastion" account exists which users initially authenticate into and then pivot from that account into other accounts.
This command will fail if you do not have active Cloud credentials.
`,
Example: "keyconjurer switch 123456798",
Args: cobra.ExactArgs(1),
Aliases: []string{"switch-account"},
RunE: func(cmd *cobra.Command, args []string) error {
outputType, _ := cmd.Flags().GetString(FlagOutputType)
shellType, _ := cmd.Flags().GetString(FlagShellType)
cloudType, _ := cmd.Flags().GetString(FlagCloudType)
awsCliPath, _ := cmd.Flags().GetString(FlagAWSCLIPath)
if !isMemberOfSlice(permittedOutputTypes, outputType) {
return invalidValueError(outputType, permittedOutputTypes)
}
if !isMemberOfSlice(permittedShellTypes, shellType) {
return invalidValueError(shellType, permittedShellTypes)
}
// We could read the environment variable for the assumed role ARN, but it might be expired which isn't very useful to the user.
var err error
var creds CloudCredentials
sessionName, _ := cmd.Flags().GetString(FlagRoleSessionName)
switch strings.ToLower(cloudType) {
case cloudAws:
creds, err = getAWSCredentials(args[0], sessionName)
case cloudTencent:
creds, err = getTencentCredentials(args[0], sessionName)
}
if err != nil {
// If this failed, either there was a network error or the user is not authorized to assume into this role
// This can happen if the user is not authenticated using the Bastion instance.
return err
}
switch outputType {
case outputTypeEnvironmentVariable:
creds.WriteFormat(os.Stdout, shellType)
return nil
case outputTypeAWSCredentialsFile:
acc := Account{ID: args[0], Name: args[0]}
newCliEntry := NewCloudCliEntry(creds, &acc)
return SaveCloudCredentialInCLI(awsCliPath, newCliEntry)
default:
return fmt.Errorf("%s is an invalid output type", outputType)
}
},
}
func getTencentCredentials(accountID, roleSessionName string) (creds CloudCredentials, err error) {
region := os.Getenv("TENCENT_REGION")
stsClient, err := tencent.NewSTSClient(region)
if err != nil {
return
}
response, err := stsClient.GetCallerIdentity()
if err != nil {
return
}
arn := response.Response.Arn
roleID := ""
if (*arn) != "" {
arns := strings.Split(*arn, ":")
if len(arns) >= 5 && len(strings.Split(arns[4], "/")) >= 2 {
roleID = strings.Split(arns[4], "/")[1]
}
}
if roleID == "" {
err = fmt.Errorf("roleID is null")
return
}
camClient, err := tencent.NewCAMClient(region)
if err != nil {
return
}
roleName, err := camClient.GetRoleName(roleID)
if err != nil {
return
}
resp, err := stsClient.AssumeRole(fmt.Sprintf("qcs::cam::uin/%s:roleName/%s", accountID, roleName), roleSessionName)
if err != nil {
return
}
creds = CloudCredentials{
AccountID: accountID,
AccessKeyID: *resp.Response.Credentials.TmpSecretId,
SecretAccessKey: *resp.Response.Credentials.TmpSecretKey,
SessionToken: *resp.Response.Credentials.Token,
Expiration: *resp.Response.Expiration,
credentialsType: cloudTencent,
}
return creds, nil
}
func getAWSCredentials(accountID, roleSessionName string) (creds CloudCredentials, err error) {
ctx := context.Background()
sess, err := session.NewSession(aws.NewConfig())
if err != nil {
return
}
c := sts.New(sess)
response, err := c.GetCallerIdentityWithContext(ctx, &sts.GetCallerIdentityInput{})
if err != nil {
return
}
// We need to modify this to change the last section to be role/GL-SuperAdmin
id, err := arn.Parse(*response.Arn)
if err != nil {
return
}
parts := strings.Split(id.Resource, "/")
arn := arn.ARN{
AccountID: accountID,
Partition: "aws",
Service: "iam",
Resource: fmt.Sprintf("role/%s", parts[1]),
Region: id.Region,
}
roleARN := arn.String()
resp, err := c.AssumeRoleWithContext(ctx, &sts.AssumeRoleInput{
RoleArn: &roleARN,
RoleSessionName: &roleSessionName,
})
if err != nil {
return
}
creds = CloudCredentials{
AccountID: accountID,
AccessKeyID: *resp.Credentials.AccessKeyId,
SecretAccessKey: *resp.Credentials.SecretAccessKey,
SessionToken: *resp.Credentials.SessionToken,
Expiration: resp.Credentials.Expiration.Format(time.RFC3339),
credentialsType: cloudAws,
}
return creds, nil
}