-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
123 lines (102 loc) · 3.58 KB
/
main.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
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"strings"
"github.com/go-httpproxy/httpproxy"
"google.golang.org/api/iamcredentials/v1"
)
var serviceAccountPtr *string = nil
type tokenResponse struct {
AccessToken string `json:"access_token,omitempty"`
ExpiresIn string `json:"expires_in,omitempty"`
TokenType string `json:"token_type,omitempty"`
}
func onError(ctx *httpproxy.Context, where string, err *httpproxy.Error, opErr error) {
// Log errors.
log.Printf("ERR: %s: %s [%s]", where, err, opErr)
}
func onAccept(ctx *httpproxy.Context, w http.ResponseWriter, r *http.Request) bool {
// Force requests to be proxied
if !r.URL.IsAbs() {
r.URL.Scheme = "http"
r.URL.Host = "169.254.169.254"
}
if r.Method == "GET" && strings.HasPrefix(r.URL.Path, "/computeMetadata/v1/instance/service-accounts/default") {
if strings.ToLower(r.Header.Get("Metadata-Flavor")) == "google" {
parts := strings.Split(strings.TrimRight(r.URL.Path, "/"), "/")
switch action := parts[len(parts)-1]; action {
case "token":
ctx := context.Background()
iamcredentialsService, err := iamcredentials.NewService(ctx)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Failed to initialize iamcredentials service: %s\n", err.Error())))
return true
}
accessTokenRequest := iamcredentials.GenerateAccessTokenRequest{
Lifetime: "3600s",
Scope: []string{"https://www.googleapis.com/auth/cloud-platform"}}
iamcredentialsProjectsService := iamcredentialsService.Projects
iamcredentialsServiceAccounts := iamcredentialsProjectsService.ServiceAccounts
serviceAccount := fmt.Sprintf("projects/-/serviceAccounts/%s", *serviceAccountPtr)
generateAccessTokenCall := iamcredentialsServiceAccounts.GenerateAccessToken(serviceAccount, &accessTokenRequest)
token, err := generateAccessTokenCall.Do()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Failed to generate token: %s\n", err.Error())))
return true
}
response := tokenResponse{AccessToken: token.AccessToken, ExpiresIn: token.ExpireTime, TokenType: "Bearer"}
responseBytes, err := json.Marshal(response)
w.Write(responseBytes)
case "email":
w.Write([]byte(*serviceAccountPtr))
default:
return false
}
} else {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("Missing Metadata-Flavor:Google header.\n"))
return true
}
return true
}
return false
}
func onConnect(ctx *httpproxy.Context, host string) (ConnectAction httpproxy.ConnectAction, newHost string) {
// Don't support CONNECT, we don't need SSL
return httpproxy.ConnectNone, host
}
func onRequest(ctx *httpproxy.Context, req *http.Request) (resp *http.Response) {
// Overwrite to metadata endpoint IP address
req.URL.Host = "169.254.169.254"
// Log proxying requests.
log.Printf("INFO: Proxy: %s %s", req.Method, req.URL.String())
return
}
func onResponse(ctx *httpproxy.Context, req *http.Request, resp *http.Response) {
resp.Header.Add("Via", "gce-impersonation-proxy")
}
func main() {
serviceAccountPtr = flag.String("I", "", "Service Account to impersonate")
bindAddress := flag.String("B", "127.0.0.1:80", "Bind address")
flag.Parse()
if *serviceAccountPtr == "" || *bindAddress == "" {
flag.PrintDefaults()
os.Exit(1)
}
prx, _ := httpproxy.NewProxy()
prx.OnError = onError
prx.OnAccept = onAccept
prx.OnAuth = nil
prx.OnConnect = onConnect
prx.OnRequest = onRequest
prx.OnResponse = onResponse
log.Fatal(http.ListenAndServe(*bindAddress, prx))
}