alot more changes
This commit is contained in:
1 parent
23243ddbdc
commit
0ed2935f04
40 files changed
+2889
-115
No files matched your search
@@ -166,6 +166,79 @@ type Status struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Add manually adds one public IP to the blocker-managed set. It uses the
|
||||
// same expiry, allowlist, ConfigMap ownership, and dry-run rules as automatic
|
||||
// blocks.
|
||||
func (b *Blocker) Add(ctx context.Context, address string) (Status, error) {
|
||||
addr, err := netip.ParseAddr(strings.TrimSpace(address))
|
||||
if err != nil || honeypot.Untrackable(address) {
|
||||
return Status{}, fmt.Errorf("only public unicast IPs can be blocked")
|
||||
}
|
||||
for _, p := range b.cfg.Allow {
|
||||
if p.Contains(addr) {
|
||||
return Status{}, fmt.Errorf("IP is on the operator allowlist")
|
||||
}
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
now := b.now()
|
||||
cms := b.client.CoreV1().ConfigMaps(b.cfg.Namespace)
|
||||
cm, err := cms.Get(ctx, b.cfg.ConfigMap, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return Status{}, fmt.Errorf("get configmap: %w", err)
|
||||
}
|
||||
previous := map[string]int64{}
|
||||
if raw := cm.Annotations[AnnotationManaged]; raw != "" {
|
||||
_ = json.Unmarshal([]byte(raw), &previous)
|
||||
}
|
||||
managed := map[string]int64{}
|
||||
for ip, exp := range previous {
|
||||
if exp > now.Unix() {
|
||||
managed[ip] = exp
|
||||
}
|
||||
}
|
||||
if _, exists := managed[addr.String()]; !exists && len(managed) >= b.cfg.MaxEntries {
|
||||
return Status{}, fmt.Errorf("managed block list is full")
|
||||
}
|
||||
managed[addr.String()] = now.Add(b.cfg.BlockTTL).Unix()
|
||||
|
||||
status := Status{Enabled: true, DryRun: b.cfg.DryRun, Evaluated: now, Managed: managed, LastWrite: b.lastWrite}
|
||||
if b.cfg.DryRun {
|
||||
b.last = status
|
||||
return status, nil
|
||||
}
|
||||
if !b.lastWrite.IsZero() && now.Sub(b.lastWrite) < b.cfg.MinWrite {
|
||||
return status, fmt.Errorf("block list was recently updated; try again later")
|
||||
}
|
||||
|
||||
current := splitCIDRs(cm.Data[b.cfg.Key])
|
||||
manual := make([]string, 0, len(current))
|
||||
for _, entry := range current {
|
||||
if _, wasOurs := previous[entry]; !wasOurs {
|
||||
manual = append(manual, entry)
|
||||
}
|
||||
}
|
||||
desired := append(manual, managedList(managed)...)
|
||||
if cm.Annotations == nil {
|
||||
cm.Annotations = map[string]string{}
|
||||
}
|
||||
if cm.Data == nil {
|
||||
cm.Data = map[string]string{}
|
||||
}
|
||||
state, _ := json.Marshal(managed)
|
||||
cm.Annotations[AnnotationManaged] = string(state)
|
||||
cm.Data[b.cfg.Key] = strings.Join(dedupSorted(desired), ",")
|
||||
if _, err := cms.Update(ctx, cm, metav1.UpdateOptions{}); err != nil {
|
||||
return Status{}, fmt.Errorf("update configmap: %w", err)
|
||||
}
|
||||
b.lastWrite, b.lastErr = now, ""
|
||||
status.Wrote, status.LastWrite = true, now
|
||||
b.last = status
|
||||
log.Printf("blocker: manually added %s (expires %s)", addr, time.Unix(managed[addr.String()], 0).UTC().Format(time.RFC3339))
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func New(cfg Config, client kubernetes.Interface, source Source) *Blocker {
|
||||
d := DefaultConfig()
|
||||
if cfg.Interval <= 0 {
|
||||
@@ -350,6 +423,25 @@ func (b *Blocker) HandleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(s)
|
||||
}
|
||||
|
||||
// HandleAdd serves POST /api/blocker/ips/{ip}. Register it behind
|
||||
// authentication; it never permits private or allowlisted addresses.
|
||||
func (b *Blocker) HandleAdd(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := b.Add(r.Context(), r.PathValue("ip"))
|
||||
if err != nil {
|
||||
code := http.StatusBadRequest
|
||||
if strings.Contains(err.Error(), "recently updated") {
|
||||
code = http.StatusTooManyRequests
|
||||
}
|
||||
http.Error(w, err.Error(), code)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if status.DryRun {
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(status)
|
||||
}
|
||||
|
||||
func splitCIDRs(v string) []string {
|
||||
var out []string
|
||||
for _, p := range strings.Split(v, ",") {
|
||||
|
||||
Reference in new issue
Block a user