Skip to content

Commit 2385c38

Browse files
stubbiclaude
andauthored
feat: add Redis support for rate limiting (#19)
* feat: add Redis support for rate limiting in multi-replica deployments Adds a new `redis` field to the Instance CRD spec with two modes: - `managed`: Operator provisions a Redis 7 StatefulSet with PVC, Service, liveness/readiness probes, and security context (non-root, dropped caps) - `external`: User provides a Redis URL or Secret reference The operator injects PAPERCLIP_RATE_LIMIT_REDIS_URL into Paperclip server pods, enabling shared rate limit state across replicas. Resources created for managed mode: - StatefulSet (1 replica, redis:7-alpine, AOF persistence, LRU eviction) - Service (ClusterIP, port 6379) - PVC (1Gi default, configurable size and storage class) Closes #18 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve lint failure and harden Redis implementation Fix gofmt alignment in redis.go that caused CI lint failure. Also: - Add Restricted PSS fields (SeccompProfile, drop ALL capabilities) - Derive Redis maxmemory from container memory limit (75%) instead of hardcoding 256mb - Add NetworkPolicy egress rule for managed Redis on port 6379 - Add RedisReady status condition in reconciler - Add plaintext credential warning to ExternalURL CRD field - Add 16 unit tests for Redis builders, env vars, and NetworkPolicy - Regenerate CRD manifests and sync Helm chart Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: security hardening across operator P0 fixes: - Fix JSON injection in bootstrap Job by sanitizing adminName before embedding in shell script JSON payload - Remove unused pods/exec and pods/log RBAC from operator ClusterRole (only needed in sandbox Role, not the operator itself) - Add plaintext credential warning to Database.ExternalURL matching the existing Redis.ExternalURL warning P1 fixes: - Add NetworkPolicies for managed Database and Redis pods restricting ingress to only Paperclip server pods and denying all egress - Add automountServiceAccountToken:false to Database, Redis, and bootstrap Job pods (none need K8s API access) - Add full SecurityContext to bootstrap Job (was completely missing): PodSecurityContext + container seccomp, capabilities drop ALL P2 fixes: - Add Restricted PSS compliance (seccompProfile RuntimeDefault + capabilities drop ALL) to Paperclip main container, database container, and onboard init container - Add CIDR validation pattern to NetworkPolicy AllowIngressCIDRs and AllowEgressCIDRs fields Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9802926 commit 2385c38

14 files changed

Lines changed: 1279 additions & 31 deletions

File tree

api/v1alpha1/paperclipinstance_types.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ type InstanceSpec struct {
5252
// +optional
5353
ObjectStorage *ObjectStorageSpec `json:"objectStorage,omitempty"`
5454

55+
// Redis configures Redis for rate limiting and caching in multi-replica deployments.
56+
// +optional
57+
Redis *RedisSpec `json:"redis,omitempty"`
58+
5559
// Heartbeat configures the agent heartbeat scheduler.
5660
// +optional
5761
Heartbeat HeartbeatSpec `json:"heartbeat,omitempty"`
@@ -211,6 +215,8 @@ type DatabaseSpec struct {
211215
Mode string `json:"mode,omitempty"`
212216

213217
// ExternalURL is the PostgreSQL connection string for external mode.
218+
// WARNING: This value is stored in plaintext in the CRD spec (etcd). If the URL contains
219+
// credentials, use ExternalURLSecretRef instead to reference a Secret.
214220
// +optional
215221
ExternalURL string `json:"externalURL,omitempty"`
216222

@@ -333,6 +339,50 @@ type ObjectStorageSpec struct {
333339
CredentialsSecretRef *corev1.LocalObjectReference `json:"credentialsSecretRef,omitempty"`
334340
}
335341

342+
// RedisSpec configures Redis for rate limiting and caching.
343+
type RedisSpec struct {
344+
// Mode selects the Redis mode: "managed" (operator-provisioned) or "external" (user-provided URL).
345+
// +kubebuilder:default="managed"
346+
// +kubebuilder:validation:Enum=managed;external
347+
// +optional
348+
Mode string `json:"mode,omitempty"`
349+
350+
// ExternalURL is the Redis connection string for external mode (e.g. "redis://host:6379").
351+
// WARNING: This value is stored in plaintext in the CRD spec (etcd). If the URL contains
352+
// credentials, use ExternalURLSecretRef instead to reference a Secret.
353+
// +optional
354+
ExternalURL string `json:"externalURL,omitempty"`
355+
356+
// ExternalURLSecretRef references a Secret key containing the Redis URL.
357+
// +optional
358+
ExternalURLSecretRef *corev1.SecretKeySelector `json:"externalURLSecretRef,omitempty"`
359+
360+
// Managed configures the operator-managed Redis instance.
361+
// +optional
362+
Managed ManagedRedisSpec `json:"managed,omitempty"`
363+
}
364+
365+
// ManagedRedisSpec configures the operator-managed Redis instance.
366+
type ManagedRedisSpec struct {
367+
// Image is the Redis container image.
368+
// +kubebuilder:default="redis:7-alpine"
369+
// +optional
370+
Image string `json:"image,omitempty"`
371+
372+
// StorageSize is the PVC size for Redis data. Defaults to 1Gi.
373+
// +kubebuilder:default="1Gi"
374+
// +optional
375+
StorageSize resource.Quantity `json:"storageSize,omitempty"`
376+
377+
// StorageClass is the storage class for the Redis PVC.
378+
// +optional
379+
StorageClass *string `json:"storageClass,omitempty"`
380+
381+
// Resources specifies compute resources for the Redis container.
382+
// +optional
383+
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
384+
}
385+
336386
// HeartbeatSpec configures the agent heartbeat scheduler.
337387
type HeartbeatSpec struct {
338388
// Enabled controls whether the heartbeat scheduler runs. Defaults to true.
@@ -510,10 +560,14 @@ type NetworkPolicySpec struct {
510560

511561
// AllowIngressCIDRs specifies additional CIDR blocks allowed to reach the Paperclip service.
512562
// +optional
563+
// +listType=set
564+
// +kubebuilder:validation:items:Pattern=`^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$`
513565
AllowIngressCIDRs []string `json:"allowIngressCIDRs,omitempty"`
514566

515567
// AllowEgressCIDRs specifies additional CIDR blocks the pod can reach.
516568
// +optional
569+
// +listType=set
570+
// +kubebuilder:validation:items:Pattern=`^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$`
517571
AllowEgressCIDRs []string `json:"allowEgressCIDRs,omitempty"`
518572
}
519573

@@ -867,6 +921,12 @@ type ManagedResources struct {
867921
DatabaseService string `json:"databaseService,omitempty"`
868922
// +optional
869923
DatabasePVC string `json:"databasePVC,omitempty"`
924+
// +optional
925+
RedisStatefulSet string `json:"redisStatefulSet,omitempty"`
926+
// +optional
927+
RedisService string `json:"redisService,omitempty"`
928+
// +optional
929+
RedisPVC string `json:"redisPVC,omitempty"`
870930
}
871931

872932
// BackupStatus tracks the state of a backup operation.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/paperclip-operator/templates/crds/paperclip.inc_instances.yaml

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,8 +1672,10 @@ spec:
16721672
description: Database configures the PostgreSQL connection.
16731673
properties:
16741674
externalURL:
1675-
description: ExternalURL is the PostgreSQL connection string for
1676-
external mode.
1675+
description: |-
1676+
ExternalURL is the PostgreSQL connection string for external mode.
1677+
WARNING: This value is stored in plaintext in the CRD spec (etcd). If the URL contains
1678+
credentials, use ExternalURLSecretRef instead to reference a Secret.
16771679
type: string
16781680
externalURLSecretRef:
16791681
description: ExternalURLSecretRef references a Secret containing
@@ -5621,6 +5623,131 @@ spec:
56215623
- tcp
56225624
type: string
56235625
type: object
5626+
redis:
5627+
description: Redis configures Redis for rate limiting and caching
5628+
in multi-replica deployments.
5629+
properties:
5630+
externalURL:
5631+
description: |-
5632+
ExternalURL is the Redis connection string for external mode (e.g. "redis://host:6379").
5633+
WARNING: This value is stored in plaintext in the CRD spec (etcd). If the URL contains
5634+
credentials, use ExternalURLSecretRef instead to reference a Secret.
5635+
type: string
5636+
externalURLSecretRef:
5637+
description: ExternalURLSecretRef references a Secret key containing
5638+
the Redis URL.
5639+
properties:
5640+
key:
5641+
description: The key of the secret to select from. Must be
5642+
a valid secret key.
5643+
type: string
5644+
name:
5645+
default: ""
5646+
description: |-
5647+
Name of the referent.
5648+
This field is effectively required, but due to backwards compatibility is
5649+
allowed to be empty. Instances of this type with an empty value here are
5650+
almost certainly wrong.
5651+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
5652+
type: string
5653+
optional:
5654+
description: Specify whether the Secret or its key must be
5655+
defined
5656+
type: boolean
5657+
required:
5658+
- key
5659+
type: object
5660+
x-kubernetes-map-type: atomic
5661+
managed:
5662+
description: Managed configures the operator-managed Redis instance.
5663+
properties:
5664+
image:
5665+
default: redis:7-alpine
5666+
description: Image is the Redis container image.
5667+
type: string
5668+
resources:
5669+
description: Resources specifies compute resources for the
5670+
Redis container.
5671+
properties:
5672+
claims:
5673+
description: |-
5674+
Claims lists the names of resources, defined in spec.resourceClaims,
5675+
that are used by this container.
5676+
5677+
This is an alpha field and requires enabling the
5678+
DynamicResourceAllocation feature gate.
5679+
5680+
This field is immutable. It can only be set for containers.
5681+
items:
5682+
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
5683+
properties:
5684+
name:
5685+
description: |-
5686+
Name must match the name of one entry in pod.spec.resourceClaims of
5687+
the Pod where this field is used. It makes that resource available
5688+
inside a container.
5689+
type: string
5690+
request:
5691+
description: |-
5692+
Request is the name chosen for a request in the referenced claim.
5693+
If empty, everything from the claim is made available, otherwise
5694+
only the result of this request.
5695+
type: string
5696+
required:
5697+
- name
5698+
type: object
5699+
type: array
5700+
x-kubernetes-list-map-keys:
5701+
- name
5702+
x-kubernetes-list-type: map
5703+
limits:
5704+
additionalProperties:
5705+
anyOf:
5706+
- type: integer
5707+
- type: string
5708+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
5709+
x-kubernetes-int-or-string: true
5710+
description: |-
5711+
Limits describes the maximum amount of compute resources allowed.
5712+
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
5713+
type: object
5714+
requests:
5715+
additionalProperties:
5716+
anyOf:
5717+
- type: integer
5718+
- type: string
5719+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
5720+
x-kubernetes-int-or-string: true
5721+
description: |-
5722+
Requests describes the minimum amount of compute resources required.
5723+
If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
5724+
otherwise to an implementation-defined value. Requests cannot exceed Limits.
5725+
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
5726+
type: object
5727+
type: object
5728+
storageClass:
5729+
description: StorageClass is the storage class for the Redis
5730+
PVC.
5731+
type: string
5732+
storageSize:
5733+
anyOf:
5734+
- type: integer
5735+
- type: string
5736+
default: 1Gi
5737+
description: StorageSize is the PVC size for Redis data. Defaults
5738+
to 1Gi.
5739+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
5740+
x-kubernetes-int-or-string: true
5741+
type: object
5742+
mode:
5743+
default: managed
5744+
description: 'Mode selects the Redis mode: "managed" (operator-provisioned)
5745+
or "external" (user-provided URL).'
5746+
enum:
5747+
- managed
5748+
- external
5749+
type: string
5750+
type: object
56245751
resources:
56255752
description: Resources specifies the compute resources for the Paperclip
56265753
container.
@@ -5921,14 +6048,18 @@ spec:
59216048
description: AllowEgressCIDRs specifies additional CIDR blocks
59226049
the pod can reach.
59236050
items:
6051+
pattern: ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$
59246052
type: string
59256053
type: array
6054+
x-kubernetes-list-type: set
59266055
allowIngressCIDRs:
59276056
description: AllowIngressCIDRs specifies additional CIDR blocks
59286057
allowed to reach the Paperclip service.
59296058
items:
6059+
pattern: ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$
59306060
type: string
59316061
type: array
6062+
x-kubernetes-list-type: set
59326063
enabled:
59336064
default: true
59346065
description: Enabled controls whether a NetworkPolicy is created.
@@ -7762,6 +7893,12 @@ spec:
77627893
type: string
77637894
persistentVolumeClaim:
77647895
type: string
7896+
redisPVC:
7897+
type: string
7898+
redisService:
7899+
type: string
7900+
redisStatefulSet:
7901+
type: string
77657902
service:
77667903
type: string
77677904
serviceAccount:

charts/paperclip-operator/templates/rbac.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ rules:
2525
- apiGroups: [""]
2626
resources: ["pods"]
2727
verbs: ["create", "delete", "get", "list", "patch", "watch"]
28-
- apiGroups: [""]
29-
resources: ["pods/exec"]
30-
verbs: ["create", "get"]
31-
- apiGroups: [""]
32-
resources: ["pods/log"]
33-
verbs: ["get"]
3428
- apiGroups: [""]
3529
resources: ["events"]
3630
verbs: ["create", "patch"]

0 commit comments

Comments
 (0)