-
Notifications
You must be signed in to change notification settings - Fork 0
/
validator_webhook.go
120 lines (101 loc) · 3.02 KB
/
validator_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
package main
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"net/url"
"os"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/utils/io"
accessrequestsv1 "github.com/spreadshirt/kube-request-access/apis/accessrequests/v1"
"github.com/spreadshirt/kube-request-access/webhooks"
)
type ValidatorWebhook struct {
httpClient *http.Client
webhookURL string
expectedCert *x509.Certificate
}
func NewWebhookValidator(webhookURL string, certFile string) (Validator, error) {
certPEM, err := os.ReadFile(certFile)
if err != nil {
return nil, fmt.Errorf("could not read cert file: %w", err)
}
certBlock, _ := pem.Decode(certPEM)
if certBlock == nil {
return nil, fmt.Errorf("no cert data found")
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("could not load cert: %w", err)
}
u, err := url.Parse(webhookURL)
if err != nil {
return nil, fmt.Errorf("invalid webhook url: %w", err)
}
if u.Scheme != "https" {
return nil, fmt.Errorf("webhook url must be https")
}
clientCerts := x509.NewCertPool()
clientCerts.AddCert(cert)
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: clientCerts,
},
},
}
return &ValidatorWebhook{
httpClient: httpClient,
webhookURL: webhookURL,
expectedCert: cert,
}, nil
}
func (vh *ValidatorWebhook) ValidateAccessRequest(ctx context.Context, admissionRequest *admissionv1.AdmissionRequest, accessRequest *accessrequestsv1.AccessRequest) (*webhooks.ValidationResult, error) {
return vh.sendRequest(ctx, webhooks.ValidateAccessRequestData{
Request: admissionRequest,
AccessRequest: accessRequest,
})
}
func (vh *ValidatorWebhook) sendRequest(ctx context.Context, data interface{}) (*webhooks.ValidationResult, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
err := enc.Encode(data)
if err != nil {
return nil, fmt.Errorf("encode json: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", vh.webhookURL, buf)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("User-Agent", UserAgent)
resp, err := vh.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("send request: %w", err)
}
defer resp.Body.Close()
if resp.TLS == nil {
return nil, fmt.Errorf("not a tls connection, but required")
}
if len(resp.TLS.PeerCertificates) == 0 {
return nil, fmt.Errorf("no certificates sent")
}
if !resp.TLS.PeerCertificates[0].Equal(vh.expectedCert) {
return nil, fmt.Errorf("certificate does not match expected cert")
}
if resp.StatusCode != http.StatusOK {
data, _ := io.ReadAtMost(resp.Body, 10*1024)
return nil, fmt.Errorf("expected status code %d, but got %d: %s", http.StatusOK, resp.StatusCode, data)
}
var validationResult webhooks.ValidationResult
dec := json.NewDecoder(resp.Body)
err = dec.Decode(&validationResult)
if err != nil {
return nil, fmt.Errorf("could not parse validation result: %w", err)
}
return &validationResult, nil
}