-
Notifications
You must be signed in to change notification settings - Fork 121
/
http_utils.go
386 lines (316 loc) · 10.7 KB
/
http_utils.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
package peirates
// http_utils.go contains URL requests
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
// HeaderLine contains the left hand side (header name) and right hand side (header value) of an HTTP header.
type HeaderLine struct {
LHS string
RHS string
}
// DoKubernetesAPIRequest makes an API request to a kubernetes API server,
// using the connection parameters and authentication from the provided
// ServerInfo. It marshals the provided query structure to JSON, and
// unmarshalls the response JSON to the response structure pointer.
// For an example of usage, see kubectlAuthCanI.
func DoKubernetesAPIRequest(cfg ServerInfo, httpVerb, apiPath string, query interface{}, response interface{}) error {
queryJSON, err := json.Marshal(query)
if err != nil {
fmt.Printf("[-] KubernetesAPIRequest failed marshalling %s to JSON: %s\n", query, err.Error())
return err
}
jsonReader := bytes.NewReader(queryJSON)
remotePath := cfg.APIServer + "/" + apiPath
req, err := http.NewRequest(httpVerb, remotePath, jsonReader)
if err != nil {
fmt.Printf("[-] KubernetesAPIRequest failed building a request from URL %s : %s\n", remotePath, err.Error())
return err
}
req.Header.Add("Authorization", "Bearer "+cfg.Token)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
responseJSON, err := DoHTTPRequestAndGetBody(req, true, false, cfg.CAPath)
if err != nil {
fmt.Printf("[-] KubernetesAPIRequest failed to access the kubernetes API: %s\n", err.Error())
return err
}
err = json.Unmarshal(responseJSON, response)
if err != nil {
fmt.Printf("[-] KubernetesAPIRequest failed to unmarshal JSON %s: %s\n", responseJSON, err.Error())
return err
}
return nil
}
// DoHTTPRequestAndGetBody performs an HTTP request, and returns the full
// body of the reponse as a string. If ignoreTLSErrors is true, all TLS
// errors, such as invalid certificates, will be ignored. If caCertPath is
// not an empty string, a TLS certificate will be read from the provided path
// and added to the pool of valid certificates.
func DoHTTPRequestAndGetBody(req *http.Request, https bool, ignoreTLSErrors bool, caCertPath string) ([]byte, error) {
client := &http.Client{
Timeout: 5 * time.Second,
}
if https {
caCertPool, err := x509.SystemCertPool()
if err != nil && caCertPath == "" {
fmt.Printf("[-] DoHTTPRequestAndGetBody failed to get system cert pool: %s\n", err.Error())
return []byte{}, err
}
if caCertPath != "" {
caCert, err := os.ReadFile(caCertPath)
if err != nil {
fmt.Printf("[-] DoHTTPRequestAndGetBody failed reading CA cert from %s: %s\n", caCertPath, err.Error())
return []byte{}, err
}
caCertPool.AppendCertsFromPEM(caCert)
}
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
InsecureSkipVerify: ignoreTLSErrors,
},
},
}
}
responseHTTP, err := client.Do(req)
if err != nil {
fmt.Printf("[-] DoHTTPRequestAndGetBody failed to perform the request: %s\n", err.Error())
return []byte{}, err
}
responseBody, err := ioutil.ReadAll(responseHTTP.Body)
if err != nil {
fmt.Printf("[-] DoHTTPRequestAndGetBody failed to read HTTP response body: %s\n", err.Error())
return []byte{}, err
}
if responseHTTP.StatusCode < 200 || responseHTTP.StatusCode > 299 {
fmt.Printf("[-] DoHTTPRequestAndGetBody got a %s status instead of a successful 2XX status. Failing and printing response: \n%s\n", responseHTTP.Status, string(responseBody))
return []byte{}, fmt.Errorf("DoHTTPRequestAndGetBody failed with status %s", responseHTTP.Status)
}
return responseBody, err
}
// GetRequest is a simple helper function for making HTTP GET requests to the
// provided URL with custom headers, and the option to ignore TLS errors.
// For a more advanced helper, see DoHTTPRequestAndGetBody.
func GetRequest(url string, headers []HeaderLine, ignoreTLSErrors bool) (string, int, error) {
req, err := http.NewRequest("GET", url, nil)
client := &http.Client{}
if err != nil {
fmt.Printf("[-] GetRequest failed to construct an HTTP request from URL %s : %s\n", url, err.Error())
return "", 999, err
}
for _, header := range headers {
req.Header.Add(header.LHS, header.RHS)
}
response, err := client.Do(req)
if err != nil {
fmt.Printf("[-] GetRequest could not perform request to %s : %s\n", url, err.Error())
return "", 998, err
}
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
return string(body), response.StatusCode, nil
}
func createHTTPrequest(method string, urlWithoutValues string, headers []HeaderLine, paramLocation string, params map[string]string) (*http.Request, error) {
var err error
// Store a URL starting point that we may put values on.
urlWithData := urlWithoutValues
// Create a data structure for values sent in the body of the request.
var dataSection *strings.Reader = nil
var contentLength string
// If there are parameters, add them to the end of urlWithData
const headerContentType = "Content-Type"
const headerValFormURLEncoded = "application/x-www-form-urlencoded"
if len(params) > 0 {
if paramLocation == "url" {
urlWithData = urlWithData + "?"
for key, value := range params {
urlWithData = urlWithData + key + "=" + value + "&"
}
// Strip the final & off the query string
urlWithData = strings.TrimSuffix(urlWithData, "&")
} else if paramLocation == "body" {
// Add a Content-Type by default that curl would use with -d
// Content-Type: application/x-www-form-urlencoded
contentTypeFormURLEncoded := true
foundContentType := false
for _, header := range headers {
if header.LHS == headerContentType {
foundContentType = true
if header.RHS != headerValFormURLEncoded {
contentTypeFormURLEncoded = false
}
}
}
// Add a Content-Type header.
if !foundContentType {
headers = append(headers, HeaderLine{LHS: headerContentType, RHS: headerValFormURLEncoded})
}
// Now place the values in the body, encoding if content type is x-www-form-urlencoded
if contentTypeFormURLEncoded {
data := url.Values{}
for key, value := range params {
if Verbose {
fmt.Printf("key[%s] value[%s]\n", key, value)
}
data.Set(key, value)
}
encodedData := data.Encode()
dataSection = strings.NewReader(encodedData)
contentLength = strconv.Itoa(len(encodedData))
} else {
var bodySection string
for key, value := range params {
bodySection = bodySection + key + value + "\n"
}
dataSection = strings.NewReader(bodySection)
contentLength = strconv.Itoa(len(bodySection))
}
} else {
println("paramLocation was not url or body.")
return nil, nil
}
}
fmt.Println("[+] Using method " + method + " for URL " + urlWithData)
var request *http.Request
// Build the request, adding in any headers found so far.
if dataSection != nil {
request, err = http.NewRequest(method, urlWithData, dataSection)
request.Header.Add("Content-Length", contentLength)
} else {
request, err = http.NewRequest(method, urlWithData, nil)
}
if err != nil {
println("[-] Error handling data: ", err)
}
for _, header := range headers {
request.Header.Add(header.LHS, header.RHS)
}
return request, nil
}
func curlNonWizard(arguments ...string) (request *http.Request, https bool, ignoreTLSErrors bool, caCertPath string, err error) {
// Scan through the arguments for a method
method := "GET"
var fullURL string
params := make(map[string]string)
var skipArgument bool
for i, argument := range arguments {
if skipArgument {
skipArgument = false
continue
}
if argument == "-X" {
// Method is being set
method = arguments[i+1]
if Verbose {
println("DEBUG: found argument to set method: -X " + method)
}
// Skip the next argument, since we've captured it here already
skipArgument = true
} else if argument == "-k" {
ignoreTLSErrors = true
} else if argument == "-d" {
data := arguments[i+1]
// Strip quotation marks if present
if strings.HasPrefix(data, "\"") && strings.HasSuffix(data, "\"") {
data = data[1 : len(data)-1]
}
// Strip single quotation marks if present
if strings.HasPrefix(data, "'") && strings.HasSuffix(data, "'") {
data = data[1 : len(data)-1]
}
if Verbose {
println("DEBUG: found argument -d " + data)
}
// Parse the argument
if strings.Contains(data, "=") {
keyValuePair := strings.Split(data, "=")
params[keyValuePair[0]] = url.QueryEscape(keyValuePair[1])
} else {
fmt.Printf("ERROR - parameter %s does not contain an = sign - please resubmit this with any -d arguments followed by key=value\n", data)
return nil, false, false, "", errors.New("parameter did not contain key=value pairs")
}
// Skip the next argument, since we've captured it here already
skipArgument = true
} else if strings.HasPrefix(argument, "http://") {
fullURL = argument
} else if strings.HasPrefix(argument, "https://") {
fullURL = argument
https = true
// TODO: Allow user to enter a caCertPath?
caCertPath = ""
}
}
var headers []HeaderLine
paramLocation := "url"
if method == "POST" {
paramLocation = "body"
}
// Make the request and get the response.
request, err = createHTTPrequest(method, fullURL, headers, paramLocation, params)
return request, https, ignoreTLSErrors, caCertPath, err
}
func GetMyIPAddress(interfaceName string) (string, error) {
iface, err := net.InterfaceByName(interfaceName)
if err != nil {
fmt.Printf("Error retrieving interface %s: %v\n", interfaceName, err)
return "", err
}
addrs, err := iface.Addrs()
if err != nil {
fmt.Printf("Error retrieving addresses for interface %s: %v\n", interfaceName, err)
return "", err
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
return ipNet.IP.String(), nil
}
}
return "", errors.New("Could not find a valid IP address for this interface")
}
// GetMyIPAddressesNative gets a list of IP addresses available via Golang's Net library
func GetMyIPAddressesNative() []string {
var ipAddresses []string
ifaces, err := net.Interfaces()
if err != nil {
println("ERROR: could not get interface list")
return nil
}
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
println("ERROR: could not get interface information")
return nil
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
ipString := ip.String()
if ipString != "127.0.0.1" {
println(ipString)
ipAddresses = append(ipAddresses, ipString)
}
}
}
return ipAddresses
}