-
Notifications
You must be signed in to change notification settings - Fork 12
/
interceptors.reject.server.go
159 lines (135 loc) · 4.45 KB
/
interceptors.reject.server.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
// Copyright 2020 The searKing Author. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http
import (
"encoding/json"
"errors"
"fmt"
"log"
"net"
"net/http"
"strings"
)
var _ http.Handler = &rejectInsecure{}
//go:generate go-option -type "rejectInsecure"
type rejectInsecure struct {
// ErrorLog specifies an optional logger for errors accepting
// connections, unexpected behavior from handlers, and
// underlying FileSystem errors.
// If nil, logging will be done via the log package's standard logger.
ErrorLog *log.Logger
// ForceHttp allows any request, as a shortcut circuit
ForceHttp bool
// AllowedTlsCidrs allows any request which client or proxy's ip included
// a cidr is a CIDR notation IP address and prefix length,
// like "192.0.2.0/24" or "2001:db8::/32", as defined in
// RFC 4632 and RFC 4291.
AllowedTlsCidrs []string
// WhitelistedPaths allows any request which http path matches
WhitelistedPaths []string
next http.Handler
}
// RejectInsecureServerInterceptor returns a new server interceptor with tls check.
// reject the request fulfills tls's constraints,
func RejectInsecureServerInterceptor(next http.Handler, opts ...RejectInsecureOption) *rejectInsecure {
r := &rejectInsecure{
next: next,
}
r.ApplyOptions(opts...)
return r
}
func (m *rejectInsecure) logf(format string, args ...interface{}) {
if m.ErrorLog != nil {
m.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
func (m *rejectInsecure) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if m == nil {
m.next.ServeHTTP(w, r)
return
}
m.rejectInsecureRequests(w, r)
return
}
// rejectInsecureRequests refused if tls's constraints not passed
func (m *rejectInsecure) rejectInsecureRequests(w http.ResponseWriter, r *http.Request) {
if m == nil || m.ForceHttp {
m.next.ServeHTTP(w, r)
return
}
err := DoesRequestSatisfyTlsTermination(r, m.WhitelistedPaths, m.AllowedTlsCidrs)
if err != nil {
m.logf("http: could not serve http connection %v: %v", r.RemoteAddr, err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadGateway)
if err := json.NewEncoder(w).Encode(fmt.Errorf("cannot serve request over insecure http: %w", err)); err != nil {
// There was an error, but there's actually not a lot we can do except log that this happened.
m.logf("http: could not write jsonError to response writer %v: %v", http.StatusBadGateway, err)
}
return
}
m.next.ServeHTTP(w, r)
return
}
// DoesRequestSatisfyTlsTermination returns whether the request fulfills tls's constraints,
// https, path matches any whitelisted paths or ip inclued by any cidr
// whitelistedPath is http path that does not need to be checked
// allowedTLSCIDR is the network includes ip.
func DoesRequestSatisfyTlsTermination(r *http.Request, whitelistedPaths []string, allowedTLSCIDRs []string) error {
// pass if the request is with tls, that is https
if r.TLS != nil {
return nil
}
// check if the http request can be passed
// pass if the request belongs to whitelist
for _, p := range whitelistedPaths {
if r.URL.Path == p {
return nil
}
}
if len(allowedTLSCIDRs) == 0 {
return errors.New("TLS termination is not enabled")
}
if err := matchesAnyCidr(r, allowedTLSCIDRs); err != nil {
return err
}
proto := r.Header.Get("X-Forwarded-Proto")
if proto == "" {
return errors.New("X-Forwarded-Proto header is missing")
}
if proto != "https" {
return fmt.Errorf("expected X-Forwarded-Proto header to be https, got %s", proto)
}
return nil
}
// matchesAnyCidr returns true if any of client and proxy's ip matches any cidr
// a cidr is a CIDR notation IP address and prefix length,
// like "192.0.2.0/24" or "2001:db8::/32", as defined in
// RFC 4632 and RFC 4291.
func matchesAnyCidr(r *http.Request, cidrs []string) error {
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return err
}
check := []string{remoteIP}
// X-Forwarded-For: client1, proxy1, proxy2
for _, fwd := range strings.Split(r.Header.Get("X-Forwarded-For"), ",") {
check = append(check, strings.TrimSpace(fwd))
}
for _, rn := range cidrs {
_, cidr, err := net.ParseCIDR(rn)
if err != nil {
return err
}
for _, ip := range check {
addr := net.ParseIP(ip)
if cidr.Contains(addr) {
return nil
}
}
}
return fmt.Errorf("neither remote address nor any x-forwarded-for values match CIDR cidrs %v: %v, cidrs, check)", cidrs, check)
}