forked from vadv/gopher-lua-libs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
134 lines (116 loc) · 3.45 KB
/
client.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
package chef
import (
"crypto/rsa"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"time"
lua_http "github.com/alexjx/gopher-lua-libs/http/client/interface"
lua "github.com/yuin/gopher-lua"
)
// From https://github.com/go-chef/chef/
const (
ChefVersion = "11.12.0" // default client version
)
type luaChefClient struct {
lua_http.LuaHTTPClient
key *rsa.PrivateKey
name string
url *url.URL
}
func checkChefClient(L *lua.LState, n int) *luaChefClient {
ud := L.CheckUserData(n)
if v, ok := ud.Value.(*luaChefClient); ok {
return v
}
L.ArgError(n, "chef_client_ud expected")
return nil
}
func (c *luaChefClient) request(method, path string, body io.Reader) ([]byte, error) {
req, err := c.newRequest(method, path, body)
if err != nil {
return nil, err
}
res, err := c.LuaHTTPClient.DoRequest(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
err = checkResponse(res)
if err != nil {
return nil, err
}
return ioutil.ReadAll(res.Body)
}
func (c *luaChefClient) newRequest(method string, requestURL string, body io.Reader) (*http.Request, error) {
relativeURL, err := url.Parse(requestURL)
if err != nil {
return nil, err
}
u := c.url.ResolveReference(relativeURL)
req, err := http.NewRequest(method, u.String(), body)
if err != nil {
return nil, err
}
values := req.URL.Query()
req.URL.RawQuery = values.Encode()
myBody := &luaBody{body}
if body != nil {
// Detect Content-type
req.Header.Set("Content-Type", myBody.contentType())
}
// Calculate the body hash
req.Header.Set("X-Ops-Content-Hash", myBody.hash())
// don't have to check this works, signRequest only emits error when signing hash is not valid, and we baked that in
c.signRequest(req)
return req, nil
}
func (c *luaChefClient) signRequest(request *http.Request) error {
// sanitize the path for the chef-server
// chef-server doesn't support '//' in the Hash Path.
var endpoint string
if request.URL.Path != "" {
endpoint = path.Clean(request.URL.Path)
request.URL.Path = endpoint
} else {
endpoint = request.URL.Path
}
vals := map[string]string{
"Method": request.Method,
"Hashed Path": hashStr(endpoint),
"Accept": "application/json",
"X-Chef-Version": ChefVersion,
"X-Ops-Timestamp": time.Now().UTC().Format(time.RFC3339),
"X-Ops-UserId": c.name,
"X-Ops-Sign": "algorithm=sha1;version=1.0",
"X-Ops-Content-Hash": request.Header.Get("X-Ops-Content-Hash"),
}
for _, key := range []string{"Method", "Accept", "X-Chef-Version", "X-Ops-Timestamp", "X-Ops-UserId", "X-Ops-Sign"} {
request.Header.Set(key, vals[key])
}
// To validate the signature it seems to be very particular
var content string
for _, key := range []string{"Method", "Hashed Path", "X-Ops-Content-Hash", "X-Ops-Timestamp", "X-Ops-UserId"} {
content += fmt.Sprintf("%s:%s\n", key, vals[key])
}
content = strings.TrimSuffix(content, "\n")
// generate signed string of headers
// Since we've gone through additional validation steps above,
// we shouldn't get an error at this point
signature, err := generateSignature(c.key, content)
if err != nil {
return err
}
// TODO: THIS IS CHEF PROTOCOL SPECIFIC
// Signature is made up of n 60 length chunks
base64sig := base64BlockEncode(signature, 60)
// roll over the auth slice and add the apropriate header
for index, value := range base64sig {
request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
}
return nil
}