-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathvalidate.go
More file actions
148 lines (127 loc) · 3.59 KB
/
validate.go
File metadata and controls
148 lines (127 loc) · 3.59 KB
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
package api
import (
"encoding/json"
"errors"
"net"
"regexp"
"strconv"
"strings"
"github.com/asaskevich/govalidator"
"github.com/moonrhythm/validator"
)
type ValidateError struct {
err *validator.Error
}
func (err *ValidateError) Error() string {
return err.err.Error()
}
func (err *ValidateError) OKError() {}
func (err *ValidateError) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Message string `json:"message"`
Items []string `json:"items"`
}{"api: validate error", err.err.Strings()})
}
func (err *ValidateError) Items() []error {
return err.err.Errors()
}
func WrapValidate(v *validator.Validator) error {
if err := v.Error(); err != nil {
return &ValidateError{err.(*validator.Error)}
}
return nil
}
func IsValidateError(err error) bool {
var e *ValidateError
return errors.As(err, &e)
}
// helper
var reEnvName = regexp.MustCompile(`^[-._a-zA-Z][-._a-zA-Z0-9]*$`)
func validEnvName(env map[string]string) bool {
for k := range env {
if !reEnvName.MatchString(k) {
return false
}
}
return true
}
func validImage(image string) bool {
if strings.HasSuffix(image, "@") {
return false
}
return true
}
func validRouteTarget(target string) bool {
for _, x := range routeTargetPrefix {
if strings.HasPrefix(target, x) {
return true
}
}
return false
}
func validURL(url string) bool {
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
return govalidator.IsURL(url)
}
return false
}
// validExternalTarget reports whether target is a phase-1 external upstream
// route: http://<ip>[:port] pointing at a customer-owned server. Only IP
// literals are accepted (no hostnames yet), and the IP must be globally
// routable — the guard blocks SSRF into loopback/private/link-local space
// (incl. the cloud metadata endpoint at 169.254.169.254) and the
// unspecified/multicast/CGNAT ranges. https:// is intentionally not accepted
// yet; phase 1 is HTTP-only.
func validExternalTarget(target string) bool {
hostport, ok := strings.CutPrefix(target, "http://")
if !ok || hostport == "" {
return false
}
host, ok := splitHostPort(hostport)
if !ok {
return false
}
ip := net.ParseIP(host)
if ip == nil {
return false
}
return isPublicIP(ip)
}
// splitHostPort extracts the host from an "ip" or "ip:port" string, validating
// the port if present. A bare IPv6 literal may be bracketed ("[::1]") or not
// ("::1"). The port is optional (the caller supplies the default).
func splitHostPort(hostport string) (host string, ok bool) {
if h, p, err := net.SplitHostPort(hostport); err == nil {
n, err := strconv.Atoi(p)
if err != nil || n < 1 || n > 65535 {
return "", false
}
return h, true
}
// No port present. Strip brackets from a bare IPv6 literal.
host = hostport
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return host, true
}
// isPublicIP reports whether ip is a globally routable unicast address — i.e.
// not in any range that could be turned into a request against internal
// infrastructure. IsPrivate covers 10/8, 172.16/12, 192.168/16 and fc00::/7;
// IsLinkLocalUnicast covers 169.254/16 (cloud metadata) and fe80::/10.
func isPublicIP(ip net.IP) bool {
if ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() ||
ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() ||
ip.IsMulticast() || ip.IsInterfaceLocalMulticast() {
return false
}
if ip4 := ip.To4(); ip4 != nil {
switch {
case ip4[0] == 0: // 0.0.0.0/8
return false
case ip4[0] == 100 && ip4[1]&0xc0 == 64: // 100.64.0.0/10 carrier-grade NAT
return false
}
}
return true
}