-
Notifications
You must be signed in to change notification settings - Fork 73
/
headers.go
364 lines (319 loc) · 11.7 KB
/
headers.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
package httpbp
import (
"encoding/base64"
"fmt"
"net/http"
"sort"
"strings"
"time"
"github.com/reddit/baseplate.go/secrets"
"github.com/reddit/baseplate.go/signing"
)
const (
// EdgeContextHeader is the key use to get the raw edge context from
// the HTTP request headers.
EdgeContextHeader = "X-Edge-Request"
// EdgeContextSignatureHeader is the key use to get the signature for
// the edge context headers from the HTTP request headers.
EdgeContextSignatureHeader = "X-Edge-Request-Signature"
// ParentIDHeader is the key use to get the span parent ID from
// the HTTP request headers.
ParentIDHeader = "X-Parent"
// SpanIDHeader is the key use to get the span ID from the HTTP
// request headers.
SpanIDHeader = "X-Span"
// SpanFlagsHeader is the key use to get the span flags from the HTTP
// request headers.
SpanFlagsHeader = "X-Flags"
// SpanSampledHeader is the key use to get the sampled flag from the
// HTTP request headers.
SpanSampledHeader = "X-Sampled"
// SpanSignatureHeader is the key use to get the signature for
// the span headers from the HTTP request headers.
SpanSignatureHeader = "X-Span-Signature"
// TraceIDHeader is the key use to get the trace ID from the HTTP
// request headers.
TraceIDHeader = "X-Trace"
)
// Headers is an interface to collect all of the HTTP headers for a particular
// baseplate resource (spans and edge contexts) into a struct that provides an
// easy way to convert them into HTTP headers.
//
// This interface exists so we can avoid having to do runtime checks on maps to
// ensure that they have the right keys set when we are trying to sign or verify
// a set of HTTP headers.
type Headers interface {
// AsMap returns the Headers struct as a map of header keys to header
// values.
AsMap() map[string]string
}
// EdgeContextHeaders implements the Headers interface for HTTP EdgeContext
// headers.
type EdgeContextHeaders struct {
EdgeRequest string
}
// NewEdgeContextHeaders returns a new EdgeContextHeaders object from the given
// HTTP headers.
func NewEdgeContextHeaders(h http.Header) (EdgeContextHeaders, error) {
ec, err := decodeEdgeContextHeader(h.Get(EdgeContextHeader))
return EdgeContextHeaders{EdgeRequest: string(ec)}, err
}
// SetEdgeContextHeader attach EdgeRequestContext into response header.
func SetEdgeContextHeader(header string, w http.ResponseWriter) {
w.Header().Set(EdgeContextHeader, encodeEdgeContextHeader([]byte(header)))
}
// AsMap returns the EdgeContextHeaders as a map of header keys to header
// values.
func (s EdgeContextHeaders) AsMap() map[string]string {
return map[string]string{
EdgeContextHeader: s.EdgeRequest,
}
}
// SpanHeaders implements the Headers interface for HTTP Span headers.
type SpanHeaders struct {
TraceID string
ParentID string
SpanID string
Flags string
Sampled string
}
// NewSpanHeaders returns a new SpanHeaders object from the given HTTP headers.
func NewSpanHeaders(h http.Header) SpanHeaders {
return SpanHeaders{
TraceID: h.Get(TraceIDHeader),
ParentID: h.Get(ParentIDHeader),
SpanID: h.Get(SpanIDHeader),
Flags: h.Get(SpanFlagsHeader),
Sampled: h.Get(SpanSampledHeader),
}
}
// AsMap returns the SpanHeaders as a map of header keys to header values.
func (s SpanHeaders) AsMap() map[string]string {
return map[string]string{
TraceIDHeader: s.TraceID,
ParentIDHeader: s.ParentID,
SpanIDHeader: s.SpanID,
SpanFlagsHeader: s.Flags,
SpanSampledHeader: s.Sampled,
}
}
var (
_ Headers = EdgeContextHeaders{}
_ Headers = SpanHeaders{}
)
// HeaderTrustHandler provides an interface PopulateBaseplateRequestContext to
// verify that it should trust the HTTP headers it receives.
type HeaderTrustHandler interface {
// TrustEdgeContext informs the function returned by PopulateBaseplateRequestContext
// if it can trust the HTTP headers that can be used to create an edge
// context.
//
// If it can trust those headers, then the headers will be copied into the
// context object to be later used to initialize the edge context for the
// request.
TrustEdgeContext(r *http.Request) bool
// TrustSpan informs the function returned by PopulateBaseplateRequestContext
// if it can trust the HTTP headers that can be used to create a server
// span.
//
// If it can trust those headers, then the headers will be copied into the
// context object to later be used to initialize the server span for the
// request.
TrustSpan(r *http.Request) bool
}
// NeverTrustHeaders implements the HeaderTrustHandler interface and always
// returns false.
//
// This handler is appropriate when your service is exposed to the public
// internet and also do not expect to receive these headers anyways, or simply
// does not care to parse these headers.
type NeverTrustHeaders struct{}
// TrustEdgeContext always returns false. The edge context headers will never
// be added to the context.
func (h NeverTrustHeaders) TrustEdgeContext(r *http.Request) bool {
return false
}
// TrustSpan always returns false. The span headers will never be added to the
// context.
func (h NeverTrustHeaders) TrustSpan(r *http.Request) bool {
return false
}
// AlwaysTrustHeaders implements the HeaderTrustHandler interface and always
// returns true.
//
// This handler is appropriate when your service only accept calls from within a
// secure network and you feel comfortable always trusting these headers.
type AlwaysTrustHeaders struct{}
// TrustEdgeContext always returns true. The edge context headers will always
// be added to the context.
func (h AlwaysTrustHeaders) TrustEdgeContext(r *http.Request) bool {
return true
}
// TrustSpan always returns true. The span headers will always be added to the
// context.
func (h AlwaysTrustHeaders) TrustSpan(r *http.Request) bool {
return true
}
// TrustHeaderSignature implements the HeaderTrustHandler interface and
// checks the headers for a valid signature header. If the headers are signed,
// then they can be trusted and the Trust request returns true. If there is no
// signature or the signature is invalid, then the Trust request returns false.
//
// For both the span and edge context headers, the trust handler expects the
// caller to provide the signature of a message in the following format:
//
// "{header0}:{value0}|{header1}|{value1}|...|{headerN}:{valueN}"
//
// where the headers are sorted lexicographically. Additionally, the signature
// should be generated using the baseplate provided `signing.Sign` function.
//
// TrustHeaderSignature provides implementations for both signing and
// verifying edge context and span headers.
//
// This handler is appropriate when your service wants to be able to trust
// headers that come from trusted sources, but also receives calls from
// un-trusted sources that you would not want to accept these headers from. One
// example would be an HTTP API that is exposed to clients over the public
// internet where you would not trust these headers but is also used internally
// where you want to accept these headers.
type TrustHeaderSignature struct {
secrets *secrets.Store
edgeContextSecretPath string
spanSecretPath string
}
// TrustHeaderSignatureArgs is used as input to create a new
// TrustHeaderSignature.
type TrustHeaderSignatureArgs struct {
SecretsStore *secrets.Store
EdgeContextSecretPath string
SpanSecretPath string
}
// NewTrustHeaderSignature returns a new HMACTrustHandler that uses the
// provided TrustHeaderSignatureArgs
func NewTrustHeaderSignature(args TrustHeaderSignatureArgs) TrustHeaderSignature {
return TrustHeaderSignature{
secrets: args.SecretsStore,
edgeContextSecretPath: args.EdgeContextSecretPath,
spanSecretPath: args.SpanSecretPath,
}
}
func (h TrustHeaderSignature) signHeaders(headers Headers, secretPath string, expiresIn time.Duration) (string, error) {
secret, err := h.secrets.GetVersionedSecret(secretPath)
if err != nil {
return "", err
}
return signing.Sign(signing.SignArgs{
Message: headerMessage(headers),
Secret: secret,
ExpiresIn: expiresIn,
})
}
func (h TrustHeaderSignature) verifyHeaders(headers Headers, signature string, secretPath string) (bool, error) {
if signature == "" {
return false, nil
}
secret, err := h.secrets.GetVersionedSecret(secretPath)
if err != nil {
return false, err
}
if err = signing.Verify(headerMessage(headers), signature, secret); err != nil {
return false, err
}
return true, nil
}
// SignEdgeContextHeader signs the edge context header using signing.Sign.
//
// The message that is signed has the following format:
//
// "X-Edge-Request:{headerValue}
func (h TrustHeaderSignature) SignEdgeContextHeader(headers EdgeContextHeaders, expiresIn time.Duration) (string, error) {
return h.signHeaders(headers, h.edgeContextSecretPath, expiresIn)
}
// VerifyEdgeContextHeader verifies the edge context header using signing.Verify.
func (h TrustHeaderSignature) VerifyEdgeContextHeader(headers EdgeContextHeaders, signature string) (bool, error) {
return h.verifyHeaders(headers, signature, h.edgeContextSecretPath)
}
// SignSpanHeaders signs the given span headers using signing.Sign.
//
// The message that is signed has the following format:
//
// "{header0}:{value0}|{header1}|{value1}|...|{headerN}:{valueN}"
//
// where the headers are sorted lexicographically.
func (h TrustHeaderSignature) SignSpanHeaders(headers SpanHeaders, expiresIn time.Duration) (string, error) {
return h.signHeaders(headers, h.spanSecretPath, expiresIn)
}
// VerifySpanHeaders verifies the edge context header using signing.Verify.
func (h TrustHeaderSignature) VerifySpanHeaders(headers SpanHeaders, signature string) (bool, error) {
return h.verifyHeaders(headers, signature, h.spanSecretPath)
}
// TrustEdgeContext returns true if the request has the header
// "X-Edge-Request-Signature" set and is a valid signature of the header:
//
// "X-Edge-Request"
//
// The message that should be signed is:
//
// "X-Edge-Request:{headerValue}"
func (h TrustHeaderSignature) TrustEdgeContext(r *http.Request) bool {
signature := r.Header.Get(EdgeContextSignatureHeader)
edgeContextHeader, err := NewEdgeContextHeaders(r.Header)
if err != nil {
return false
}
ok, err := h.VerifyEdgeContextHeader(edgeContextHeader, signature)
if err != nil {
return false
}
return ok
}
// TrustSpan returns true if the request has the header
// "X-Span-Signature" set and is a valid signature of the headers:
//
// "X-Flags"
// "X-Parent"
// "X-Sampled"
// "X-Span"
// "X-Trace"
//
// The message that should be signed is:
//
// "{header0}:{value0}|{header1}|{value1}|...|{headerN}:{valueN}"
//
// where the headers are sorted lexicographically.
func (h TrustHeaderSignature) TrustSpan(r *http.Request) bool {
signature := r.Header.Get(SpanSignatureHeader)
ok, err := h.VerifySpanHeaders(NewSpanHeaders(r.Header), signature)
if err != nil {
return false
}
return ok
}
var (
_ HeaderTrustHandler = AlwaysTrustHeaders{}
_ HeaderTrustHandler = NeverTrustHeaders{}
_ HeaderTrustHandler = TrustHeaderSignature{}
)
func headerMessage(h Headers) []byte {
headers := h.AsMap()
components := make([]string, 0, len(headers))
keys := make([]string, 0, len(headers))
for key := range headers {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
components = append(components, fmt.Sprintf("%s:%s", key, headers[key]))
}
return []byte(strings.Join(components, "|"))
}
// encodeEdgeContextHeader does the appropriate base64 encoding from the raw
// edge context header.
func encodeEdgeContextHeader(raw []byte) string {
return base64.StdEncoding.EncodeToString([]byte(raw))
}
// decodeEdgeContextHeader does the appropriate base64 decoding from the http
// edge context header into raw edge context header.
func decodeEdgeContextHeader(s string) ([]byte, error) {
return base64.StdEncoding.DecodeString(s)
}