-
Notifications
You must be signed in to change notification settings - Fork 341
/
webhook.go
132 lines (108 loc) · 3.36 KB
/
webhook.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
package auth
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/opentracing/opentracing-go"
log "github.com/sirupsen/logrus"
"golang.org/x/net/http/httpguts"
"github.com/zalando/skipper/filters"
)
const (
WebhookName = "webhook"
)
type WebhookOptions struct {
Timeout time.Duration
MaxIdleConns int
Tracer opentracing.Tracer
}
type (
webhookSpec struct {
options WebhookOptions
}
webhookFilter struct {
authClient *authClient
forwardResponseHeaderKeys []string
}
)
// NewWebhook creates a new auth filter specification
// to validate authorization for requests via an
// external web hook.
func NewWebhook(timeout time.Duration) filters.Spec {
return WebhookWithOptions(WebhookOptions{Timeout: timeout, Tracer: opentracing.NoopTracer{}})
}
// WebhookWithOptions creates a new auth filter specification
// to validate authorization of requests via an external web
// hook.
func WebhookWithOptions(o WebhookOptions) filters.Spec {
return &webhookSpec{options: o}
}
func (*webhookSpec) Name() string {
return WebhookName
}
// CreateFilter creates an auth filter. The first argument is an URL
// string. The second, optional, argument is a comma separated list of
// headers to forward from from webhook response.
//
// s.CreateFilter("https://my-auth-service.example.org/auth")
// s.CreateFilter("https://my-auth-service.example.org/auth", "X-Auth-User,X-Auth-User-Roles")
//
func (ws *webhookSpec) CreateFilter(args []interface{}) (filters.Filter, error) {
if l := len(args); l == 0 || l > 2 {
return nil, filters.ErrInvalidFilterParameters
}
s, ok := args[0].(string)
if !ok {
return nil, filters.ErrInvalidFilterParameters
}
forwardResponseHeaderKeys := make([]string, 0)
if len(args) > 1 {
// Capture headers that should be forwarded from webhook responses.
headerKeysOption, ok := args[1].(string)
if !ok {
return nil, filters.ErrInvalidFilterParameters
}
headerKeys := strings.Split(headerKeysOption, ",")
for _, header := range headerKeys {
valid := httpguts.ValidHeaderFieldName(header)
if !valid {
return nil, fmt.Errorf("header %s is invalid", header)
}
forwardResponseHeaderKeys = append(forwardResponseHeaderKeys, http.CanonicalHeaderKey(header))
}
}
ac, err := newAuthClient(s, webhookSpanName, ws.options.Timeout, ws.options.MaxIdleConns, ws.options.Tracer)
if err != nil {
return nil, filters.ErrInvalidFilterParameters
}
return &webhookFilter{authClient: ac, forwardResponseHeaderKeys: forwardResponseHeaderKeys}, nil
}
func copyHeader(to, from http.Header) {
for k, v := range from {
to[http.CanonicalHeaderKey(k)] = v
}
}
func (f *webhookFilter) Request(ctx filters.FilterContext) {
resp, err := f.authClient.getWebhook(ctx)
if err != nil {
log.Errorf("Failed to make authentication webhook request: %v.", err)
}
// errors, redirects, auth errors, webhook errors
if err != nil || resp.StatusCode >= 300 {
unauthorized(ctx, "", invalidAccess, f.authClient.url.Hostname(), WebhookName)
return
}
// copy required headers from webhook response into the current request
for _, hk := range f.forwardResponseHeaderKeys {
if h, ok := resp.Header[hk]; ok {
ctx.Request().Header[hk] = h
}
}
authorized(ctx, WebhookName)
}
func (*webhookFilter) Response(filters.FilterContext) {}
// Close cleans-up the authClient
func (f *webhookFilter) Close() {
f.authClient.Close()
}