-
Notifications
You must be signed in to change notification settings - Fork 0
/
spawn.go
133 lines (114 loc) · 4.12 KB
/
spawn.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
// Copyright © 2018 Pennock Tech, LLC.
// All rights reserved, except as granted under license.
// Licensed per file LICENSE.txt
package hmetrics
import (
"context"
"errors"
"fmt"
"net/url"
"os"
"regexp"
"strings"
)
// InvalidURLError is an error type, indicating that we could not handle the
// URL which we were asked to use to post metrics. At present, that just means
// that the scheme could not be handled but this might be extended to handle
// other scenarios we realize might cause an issue, without a semver bump.
type InvalidURLError struct {
scheme string
}
// Error is the type-satisfying method which lets an InvalidURLError be an
// "error".
func (e InvalidURLError) Error() string {
return fmt.Sprintf("hmetrics: invalid URL scheme %q", e.scheme)
}
// ErrMissingPoster indicates that you've tried to not provide a callback. We
// don't support that. This is the one scenario for which we considered a
// library panic. Provide a callback. If you want to discard loggable events,
// that's your decision and one which should be explicit in your code.
var ErrMissingPoster = errors.New("hmetrics: given a nil poster callback")
func realSpawn(poster ErrorPoster) (logMessage string, cancel func(), err error) {
if poster == nil {
return "hmetrics: not starting stats export, given no poster", nil, ErrMissingPoster
}
target, ok := os.LookupEnv(EnvKeyEndpoint)
commonFailurePrefix := "hmetrics: not starting stats export, '" + EnvKeyEndpoint + "' "
if !ok {
return commonFailurePrefix + "not found in environ", nil, nil
}
if target == "" {
return commonFailurePrefix + "is empty", nil, nil
}
u, err := url.Parse(target)
if err != nil {
return commonFailurePrefix + "could not be parsed", nil, err
}
switch u.Scheme {
case "http", "https":
default:
return commonFailurePrefix + "has invalid URL scheme", nil, InvalidURLError{scheme: u.Scheme}
}
redacted, err := redactURL(u)
if err != nil {
return commonFailurePrefix + "is badly malformed", nil, err
}
// caveat: the act of redacting will re-order any query params, so the form
// which we return for logging might not be the same as the form which we
// use for connecting, even after accounting for the redaction. This
// shouldn't matter, and it would be nice to normalize the format which we
// use to connect, but we don't dare: we need to treat the URL as being as
// close to opaque as possible. We're taking liberties by double-checking
// for auth information to redact.
ctx, cancel := context.WithCancel(context.Background())
go retryPostLoop(ctx, u, poster)
return fmt.Sprintf("hmetrics: started stats export to %q", redacted), cancel, nil
}
var uuidRegexp *regexp.Regexp
func init() {
// string layout form per RFC4122
//
// This will over-match, because `\b` matches between hex and '-', but we
// want to use ReplaceAllString sanely and RE2 doesn't support negative
// look ahead assertions or resetting the match point, so if we match on a
// `/` at the end, that will keep the `/` from being considered as the
// start of the next sequence, and "uuid/uuid" will only detect the first.
uuidRegexp = regexp.MustCompile(`(^|/)([0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12})\b`)
}
func redactURL(original *url.URL) (*url.URL, error) {
clean, err := url.Parse(original.String())
if err != nil {
return nil, err
}
if clean.User != nil {
if _, hasPassword := clean.User.Password(); hasPassword {
clean.User = url.UserPassword(clean.User.Username(), "redacted")
}
}
if clean.Path != "/" && clean.Path != "" && uuidRegexp.MatchString(clean.Path) {
clean.Path = uuidRegexp.ReplaceAllString(clean.Path, "${1}redacted-uuid-form")
}
if clean.RawQuery == "" {
return clean, nil
}
values, err := url.ParseQuery(clean.RawQuery)
if err != nil {
return nil, err
}
have := make([]string, len(values))
i := 0
for k := range values {
have[i] = k
}
for _, k := range have {
lk := strings.ToLower(k)
if strings.Contains(lk, "token") ||
strings.Contains(lk, "password") ||
strings.Contains(lk, "secret") ||
strings.Contains(lk, "auth") {
values.Set(k, "redacted")
}
}
clean.RawQuery = values.Encode()
return clean, nil
}