/
rate_limiter.go
94 lines (78 loc) · 2.97 KB
/
rate_limiter.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
package hooks
import (
"context"
"fmt"
"net/http"
"sync"
"time"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
//+kubebuilder:webhook:path=/validate-core-v1-pod,mutating=false,failurePolicy=fail,sideEffects=None,groups=core,resources=pods,verbs=delete,versions=v1,name=vpod.kb.io,admissionReviewVersions=v1
// log is for logging in this package.
var podlog = logf.Log.WithName("PodDeleteRateLimiter")
type podDeleteRateLimiter struct {
client client.Client
decoder *admission.Decoder
minInterval time.Duration
username string
m sync.Mutex
lastDeleted time.Time
}
func NewPodDeleteRateLimiterHttpHandler(client client.Client, decoder *admission.Decoder, minInterval time.Duration, username string) http.Handler {
return &webhook.Admission{Handler: NewPodDeleteRateLimiter(client, decoder, minInterval, username)}
}
func NewPodDeleteRateLimiter(client client.Client, decoder *admission.Decoder, minInterval time.Duration, username string) admission.Handler {
return &podDeleteRateLimiter{
client: client,
decoder: decoder,
minInterval: minInterval,
username: username,
}
}
func (v *podDeleteRateLimiter) Handle(ctx context.Context, req admission.Request) admission.Response {
if req.Operation != admissionv1.Delete {
podlog.Error(nil, "called with unsupported operation", "operation", req.Operation)
return admission.Errored(http.StatusBadRequest, fmt.Errorf("unsupported admission operation"))
}
obj := &unstructured.Unstructured{}
if err := v.decoder.DecodeRaw(req.OldObject, obj); err != nil {
podlog.Error(err, "could not decode object")
return admission.Errored(http.StatusBadRequest, err)
}
kind := obj.GetKind()
if kind != "Pod" {
podlog.Error(nil, "called with unsupported kind", "kind", kind)
return admission.Errored(http.StatusBadRequest, fmt.Errorf("unsupported resource kind"))
}
name := obj.GetName()
namespace := obj.GetNamespace()
if obj.GetDeletionTimestamp() != nil {
podlog.Info("allowed - deletionTimestamp is already set", "namespace", namespace, "name", name)
return admission.Allowed("ok")
}
if req.UserInfo.Username != v.username {
podlog.Info("allowed - user is not applied rate limit", "namespace", namespace, "name", name, "username", req.UserInfo.Username)
return admission.Allowed("ok")
}
v.m.Lock()
defer v.m.Unlock()
elapsed := time.Since(v.lastDeleted)
if elapsed < v.minInterval {
podlog.Info("denied - rate limit reached", "namespace", namespace, "name", name)
return admission.Denied("rate limited reached")
}
dryRun := false
if req.DryRun != nil {
dryRun = *req.DryRun
}
podlog.Info("allowed - rate limit ok", "namespace", namespace, "name", name, "dry_run", dryRun)
if !dryRun {
v.lastDeleted = time.Now()
}
return admission.Allowed("ok")
}