/
main.go
137 lines (121 loc) · 4.16 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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2019 Google LLC
//
// 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
//
// https://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.
// Command client performs authenticated requests against an Endpoints API server.
package main
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jws"
)
var (
url = flag.String("url", "", "The request url. Required.")
audience = flag.String("audience", "", "The audience for the JWT. equired")
serviceAccountFile = flag.String("service-account-file", "", "Path to service account JSON file. Required.")
serviceAccountEmail = flag.String("service-account-email", "", "Path email associated with the service account. Required.")
)
func main() {
flag.Parse()
if *audience == "" || *url == "" || *serviceAccountFile == "" || *serviceAccountEmail == "" {
fmt.Println("requires: --url, --audience, --service-account-file, --service-account-email")
os.Exit(1)
}
jwt, err := generateJWT(*serviceAccountFile, *serviceAccountEmail, *audience, 3600)
if err != nil {
log.Fatal(err)
}
resp, err := makeJWTRequest(jwt, *url)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s Response: %s", *url, resp)
}
// [START endpoints_generate_jwt_sa]
// generateJWT creates a signed JSON Web Token using a Google API Service Account.
func generateJWT(saKeyfile, saEmail, audience string, expiryLength int64) (string, error) {
now := time.Now().Unix()
// Build the JWT payload.
jwt := &jws.ClaimSet{
Iat: now,
// expires after 'expiryLength' seconds.
Exp: now + expiryLength,
// Iss must match 'issuer' in the security configuration in your
// swagger spec (e.g. service account email). It can be any string.
Iss: saEmail,
// Aud must be either your Endpoints service name, or match the value
// specified as the 'x-google-audience' in the OpenAPI document.
Aud: audience,
// Sub and Email should match the service account's email address.
Sub: saEmail,
PrivateClaims: map[string]interface{}{"email": saEmail},
}
jwsHeader := &jws.Header{
Algorithm: "RS256",
Typ: "JWT",
}
// Extract the RSA private key from the service account keyfile.
sa, err := ioutil.ReadFile(saKeyfile)
if err != nil {
return "", fmt.Errorf("Could not read service account file: %w", err)
}
conf, err := google.JWTConfigFromJSON(sa)
if err != nil {
return "", fmt.Errorf("Could not parse service account JSON: %w", err)
}
block, _ := pem.Decode(conf.PrivateKey)
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("private key parse error: %w", err)
}
rsaKey, ok := parsedKey.(*rsa.PrivateKey)
// Sign the JWT with the service account's private key.
if !ok {
return "", errors.New("private key failed rsa.PrivateKey type assertion")
}
return jws.Encode(jwsHeader, jwt, rsaKey)
}
// [END endpoints_generate_jwt_sa]
// [START endpoints_jwt_request]
// makeJWTRequest sends an authorized request to your deployed endpoint.
func makeJWTRequest(signedJWT, url string) (string, error) {
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("failed to create HTTP request: %w", err)
}
req.Header.Add("Authorization", "Bearer "+signedJWT)
req.Header.Add("content-type", "application/json")
response, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("HTTP request failed: %w", err)
}
defer response.Body.Close()
responseData, err := ioutil.ReadAll(response.Body)
if err != nil {
return "", fmt.Errorf("failed to parse HTTP response: %w", err)
}
return string(responseData), nil
}
// [END endpoints_jwt_request]