/
util.go
146 lines (124 loc) · 4.37 KB
/
util.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
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package iamauth
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"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/hashicorp/consul-awsauth/responses"
"github.com/hashicorp/go-hclog"
)
type LoginInput struct {
Creds *credentials.Credentials
IncludeIAMEntity bool
STSEndpoint string
STSRegion string
Logger hclog.Logger
ServerIDHeaderValue string
// Customizable header names
ServerIDHeaderName string
GetEntityMethodHeader string
GetEntityURLHeader string
GetEntityHeadersHeader string
GetEntityBodyHeader string
}
// GenerateLoginData populates the necessary data to send for the bearer token.
// https://github.com/hashicorp/go-secure-stdlib/blob/main/awsutil/generate_credentials.go#L232-L301
func GenerateLoginData(in *LoginInput) (map[string]interface{}, error) {
cfg := aws.Config{
Credentials: in.Creds,
// These are empty strings by default (i.e. not enabled)
Region: aws.String(in.STSRegion),
Endpoint: aws.String(in.STSEndpoint),
STSRegionalEndpoint: endpoints.RegionalSTSEndpoint,
}
stsSession, err := session.NewSessionWithOptions(session.Options{Config: cfg})
if err != nil {
return nil, err
}
svc := sts.New(stsSession)
stsRequest, _ := svc.GetCallerIdentityRequest(nil)
// Include the iam:GetRole or iam:GetUser request in headers.
if in.IncludeIAMEntity {
entityRequest, err := formatSignedEntityRequest(svc, in)
if err != nil {
return nil, err
}
headersJson, err := json.Marshal(entityRequest.HTTPRequest.Header)
if err != nil {
return nil, err
}
requestBody, err := ioutil.ReadAll(entityRequest.HTTPRequest.Body)
if err != nil {
return nil, err
}
stsRequest.HTTPRequest.Header.Add(in.GetEntityMethodHeader, entityRequest.HTTPRequest.Method)
stsRequest.HTTPRequest.Header.Add(in.GetEntityURLHeader, entityRequest.HTTPRequest.URL.String())
stsRequest.HTTPRequest.Header.Add(in.GetEntityHeadersHeader, string(headersJson))
stsRequest.HTTPRequest.Header.Add(in.GetEntityBodyHeader, string(requestBody))
}
// Inject the required auth header value, if supplied, and then sign the request including that header
if in.ServerIDHeaderValue != "" {
stsRequest.HTTPRequest.Header.Add(in.ServerIDHeaderName, in.ServerIDHeaderValue)
}
stsRequest.Sign()
// Now extract out the relevant parts of the request
headersJson, err := json.Marshal(stsRequest.HTTPRequest.Header)
if err != nil {
return nil, err
}
requestBody, err := ioutil.ReadAll(stsRequest.HTTPRequest.Body)
if err != nil {
return nil, err
}
return map[string]interface{}{
"iam_http_request_method": stsRequest.HTTPRequest.Method,
"iam_request_url": base64.StdEncoding.EncodeToString([]byte(stsRequest.HTTPRequest.URL.String())),
"iam_request_headers": base64.StdEncoding.EncodeToString(headersJson),
"iam_request_body": base64.StdEncoding.EncodeToString(requestBody),
}, nil
}
func formatSignedEntityRequest(svc *sts.STS, in *LoginInput) (*request.Request, error) {
// We need to retrieve the IAM user or role for the iam:GetRole or iam:GetUser request.
// GetCallerIdentity returns this and requires no permissions.
resp, err := svc.GetCallerIdentity(nil)
if err != nil {
return nil, err
}
arn, err := responses.ParseArn(*resp.Arn)
if err != nil {
return nil, err
}
iamSession, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Credentials: svc.Config.Credentials,
},
})
if err != nil {
return nil, err
}
iamSvc := iam.New(iamSession)
var req *request.Request
switch arn.Type {
case "role", "assumed-role":
req, _ = iamSvc.GetRoleRequest(&iam.GetRoleInput{RoleName: &arn.FriendlyName})
case "user":
req, _ = iamSvc.GetUserRequest(&iam.GetUserInput{UserName: &arn.FriendlyName})
default:
return nil, fmt.Errorf("entity %s is not an IAM role or IAM user", arn.Type)
}
// Inject the required auth header value, if supplied, and then sign the request including that header
if in.ServerIDHeaderValue != "" {
req.HTTPRequest.Header.Add(in.ServerIDHeaderName, in.ServerIDHeaderValue)
}
req.Sign()
return req, nil
}