diff --git a/build.py b/build.py index fb2b9dd..3df17fa 100644 --- a/build.py +++ b/build.py @@ -56,7 +56,7 @@ def test_schema(project): "java -jar target/uberjar/c4k-forgejo-standalone.jar " + "src/test/resources/forgejo-test/valid-config.yaml " + "src/test/resources/forgejo-test/valid-auth.yaml | " - + "kubeconform --kubernetes-version 1.23.0 --strict --skip Certificate -", + + """kubeconform --kubernetes-version 1.23.0 --strict --skip "Certificate, Middleware" -""", shell=True, check=True, ) diff --git a/src/main/cljc/dda/c4k_forgejo/core.cljc b/src/main/cljc/dda/c4k_forgejo/core.cljc index 356751f..71338b8 100644 --- a/src/main/cljc/dda/c4k_forgejo/core.cljc +++ b/src/main/cljc/dda/c4k_forgejo/core.cljc @@ -9,6 +9,7 @@ [dda.c4k-common.postgres :as postgres])) (def config-defaults {:issuer "staging", :deploy-federated "false"}) +(def rate-limit-defaults {:max-rate 10, :max-concurrent-requests 5}) (def config? (s/keys :req-un [::forgejo/fqdn ::forgejo/mailer-from @@ -30,7 +31,7 @@ (def vol? (s/keys :req-un [::forgejo/volume-total-storage-size])) -(defn k8s-objects [config auth] +(defn k8s-objects [config auth] ; ToDo: ADR for generate functions - vector or no vector? (let [storage-class (if (contains? config :postgres-data-volume-path) :manual :local-path)] (map yaml/to-string (filter #(not (nil? %)) @@ -46,11 +47,12 @@ (postgres/generate-service) (forgejo/generate-deployment config) (forgejo/generate-service) - (forgejo/generate-service-ssh) + (forgejo/generate-service-ssh) (forgejo/generate-data-volume config) (forgejo/generate-appini-env config) - (forgejo/generate-secrets auth)] - (forgejo/generate-ingress-and-cert config) + (forgejo/generate-secrets auth) + (forgejo/generate-rate-limit-middleware rate-limit-defaults)] ; this does not have a vector as output + (forgejo/generate-rate-limit-ingress-and-cert config) ; this function has a vector as output (when (contains? config :restic-repository) [(backup/generate-config config) (backup/generate-secret auth) diff --git a/src/main/cljc/dda/c4k_forgejo/forgejo.cljc b/src/main/cljc/dda/c4k_forgejo/forgejo.cljc index 23b370e..7d2a5fb 100644 --- a/src/main/cljc/dda/c4k_forgejo/forgejo.cljc +++ b/src/main/cljc/dda/c4k_forgejo/forgejo.cljc @@ -42,6 +42,8 @@ (s/def ::mailer-pw pred/bash-env-string?) (s/def ::issuer pred/letsencrypt-issuer?) (s/def ::volume-total-storage-size (partial pred/int-gt-n? 5)) +(s/def ::max-rate int?) +(s/def ::max-concurrent-requests int?) (def config? (s/keys :req-un [::fqdn ::mailer-from @@ -53,6 +55,9 @@ ::default-app-name ::service-domain-whitelist])) +(def rate-limit-config? (s/keys :req-un [::max-rate + ::max-concurrent-requests])) + (def auth? (s/keys :req-un [::postgres/postgres-db-user ::postgres/postgres-db-password ::mailer-user ::mailer-pw])) (def vol? (s/keys :req-un [::volume-total-storage-size])) @@ -119,6 +124,26 @@ :fqdns [fqdn]} config)))) +(defn-spec generate-rate-limit-ingress-and-cert pred/map-or-seq? + [config config?] + (-> + (generate-ingress-and-cert config) ; returns a vector + (#(assoc-in % ; Attention: heavily relying on the output order of ing/generate-ingress-and-cert + [1 :metadata :annotations :traefik.ingress.kubernetes.io/router.middlewares] + (str + (-> (second %) :metadata :annotations :traefik.ingress.kubernetes.io/router.middlewares) + ", default-ratelimit@kubernetescrd"))))) + + +; using :average and :burst seems sensible, :period may be interesting for fine tuning later on +(defn-spec generate-rate-limit-middleware pred/map-or-seq? + [config rate-limit-config?] + (let [{:keys [max-rate max-concurrent-requests]} config] + (-> + (yaml/load-as-edn "forgejo/middleware-ratelimit.yaml") + (cm/replace-key-value :average max-rate) + (cm/replace-key-value :burst max-concurrent-requests)))) + (defn-spec generate-data-volume pred/map-or-seq? [config vol?] (let [{:keys [volume-total-storage-size]} config diff --git a/src/main/resources/forgejo/middleware-ratelimit.yaml b/src/main/resources/forgejo/middleware-ratelimit.yaml new file mode 100644 index 0000000..0f6c49d --- /dev/null +++ b/src/main/resources/forgejo/middleware-ratelimit.yaml @@ -0,0 +1,8 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: ratelimit +spec: + rateLimit: # Config options for rate limiting: https://doc.traefik.io/traefik/middlewares/http/ratelimit/ + average: AVG + burst: BRS \ No newline at end of file diff --git a/src/test/cljc/dda/c4k_forgejo/forgejo_test.cljc b/src/test/cljc/dda/c4k_forgejo/forgejo_test.cljc index 71805a3..54a6070 100644 --- a/src/test/cljc/dda/c4k_forgejo/forgejo_test.cljc +++ b/src/test/cljc/dda/c4k_forgejo/forgejo_test.cljc @@ -130,3 +130,26 @@ :storage-c2 "15Gi"} (th/map-diff (cut/generate-data-volume {:volume-total-storage-size 1}) (cut/generate-data-volume {:volume-total-storage-size 15}))))) + +(deftest should-generate-middleware-ratelimit + (is (= {:apiVersion "traefik.containo.us/v1alpha1", + :kind "Middleware", + :metadata {:name "ratelimit"}, + :spec {:rateLimit {:average 10, :burst 5}}} + (cut/generate-rate-limit-middleware {:max-rate 10, :max-concurrent-requests 5})))) + +(deftest should-generate-middleware-ratelimit-ingress-and-cert + (is (= {:traefik.ingress.kubernetes.io/router.entrypoints "web, websecure", + :traefik.ingress.kubernetes.io/router.middlewares + "default-redirect-https@kubernetescrd, default-ratelimit@kubernetescrd", + :metallb.universe.tf/address-pool "public"} + (-> (second + (cut/generate-rate-limit-ingress-and-cert + {:fqdn "test.de" + :mailer-from "" + :mailer-host "m.t.de" + :mailer-port "123" + :service-noreply-address "" + :average 10 + :burst 5})) + :metadata :annotations))))