alot more changes
Build and Publish / Metadata (push) Successful in 8s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 36s

This commit is contained in:
mats committed 2026-09-21 18:59:13 +02:00
1 parent 23243ddbdc
commit 0ed2935f04
40 files changed
+2889 -115

No files matched your search

+92
View File
@@ -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, ",") {