forked from kreuzwerker/awsu
/
console.go
173 lines (124 loc) · 3.7 KB
/
console.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
package console
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/pkg/errors"
"github.com/gesellix/awsu/config"
"github.com/gesellix/awsu/strategy"
)
// Console is a helper for opening links to the AWS console
type Console struct {
conf *config.Config
profile *config.Profile
}
const (
errCallerIdentity = "failed to determine caller identity"
errFederationMarshal = "failed to marshal federation session"
errFederationRequest = "failed to request federation"
errFederationResponse = "failed to receive federation response body"
errFederationUnmarshal = "failed to unmarshal sign-in token"
errNoSuchProfile = "no such profile %q configured"
)
// New instantiates a new console helper
func New(conf *config.Config) (*Console, error) {
profile, ok := conf.Profiles[conf.Profile]
if !ok {
return nil, fmt.Errorf(errNoSuchProfile, conf.Profile)
}
return &Console{
conf: conf,
profile: profile,
}, nil
}
// Link returns a link to the AWS console
func (c *Console) Link() (string, error) {
var f func() (string, error)
if c.profile.IsLongTerm() {
f = c.longterm
} else if c.profile.ExternalID != "" {
f = c.external
} else {
f = c.internal
}
return f()
}
// external returns a console link to an external / federated session
func (c *Console) external() (string, error) {
creds, err := strategy.Apply(c.conf)
if err != nil {
return "", err
}
fep := map[string]string{
"sessionId": creds.AccessKeyID,
"sessionKey": creds.SessionToken,
"sessionToken": creds.SessionToken,
}
enc, err := json.Marshal(fep)
if err != nil {
return "", errors.Wrapf(err, errFederationMarshal)
}
url := fmt.Sprintf("https://signin.aws.amazon.com/federation?Action=getSigninToken&Session=%s", string(url.QueryEscape(string(enc))))
var buf = bytes.NewBuffer(nil)
res, err := http.Get(url)
if err != nil {
return "", errors.Wrapf(err, errFederationRequest)
}
defer res.Body.Close()
if _, err := io.Copy(buf, res.Body); err != nil {
return "", errors.Wrapf(err, errFederationResponse)
}
var body map[string]string
if err := json.Unmarshal(buf.Bytes(), &body); err != nil {
return "", errors.Wrapf(err, errFederationUnmarshal)
}
var (
destination = "https://console.aws.amazon.com/"
issuer = ""
token = body["SigninToken"]
)
url = fmt.Sprintf("https://signin.aws.amazon.com/federation?Action=login&Issuer=%s&Destination=%s&SigninToken=%s\n",
issuer,
destination,
token)
return url, nil
}
// internal returns a console link to an internal / cross account session
func (c *Console) internal() (string, error) {
a, err := arn.Parse(c.profile.RoleARN)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://signin.aws.amazon.com/switchrole?account=%s&roleName=%s&displayName=%s",
a.AccountID,
strings.TrimPrefix(a.Resource, "role/"),
c.profile.Name)
return url, nil
}
// longterm returns a console link for an IAM user
func (c *Console) longterm() (string, error) {
creds, err := strategy.Apply(c.conf)
if err != nil {
return "", err
}
client := sts.New(creds.NewSession())
res, err := client.GetCallerIdentity(&sts.GetCallerIdentityInput{})
if err != nil {
return "", errors.Wrapf(err, errCallerIdentity)
}
// TODO: make this configurable
region := "eu-west-1"
// 015428540659 is the magic account ID for AWS sign-in
url := fmt.Sprintf("https://signin.aws.amazon.com/oauth?redirect_uri=https://%s.console.aws.amazon.com/console/home?region=%s&client_id=arn:aws:iam::015428540659:user/homepage&response_type=code&iam_user=true&account=%s",
region,
region,
*res.Account,
)
return url, nil
}