diff --git a/infrastructure/backup/image/Dockerfile b/infrastructure/backup/image/Dockerfile index 37aa6df..bb0759a 100644 --- a/infrastructure/backup/image/Dockerfile +++ b/infrastructure/backup/image/Dockerfile @@ -6,4 +6,5 @@ RUN /tmp/install.sh ADD local/ /usr/local/lib/dda-backup RUN init-bb.bb ADD resources2 /tmp/ -RUN install -m 0700 -o root -g root /tmp/test-me.bb /usr/local/bin/ +RUN install -m 0700 -o root -g root /tmp/test.bb /usr/local/bin/ +RUN test.bb diff --git a/infrastructure/backup/image/resources2/test-me.bb b/infrastructure/backup/image/resources2/test.bb similarity index 53% rename from infrastructure/backup/image/resources2/test-me.bb rename to infrastructure/backup/image/resources2/test.bb index c00a43a..7f2eecc 100755 --- a/infrastructure/backup/image/resources2/test-me.bb +++ b/infrastructure/backup/image/resources2/test.bb @@ -1,7 +1,5 @@ #!/usr/bin/env bb -(println "initialized") - (require '[babashka.tasks :as tasks] '[dda.backup.cred-rot :as cr] '[dda.backup.restic :as rc] @@ -14,24 +12,19 @@ :files ["test-backup"] :restore-target-directory "test-restore"})) -(def db-config (merge restic-repo {:backup-path "db" - :pg-db "mydb" - :pg-user "user" - :pg-password "password"})) - -(def cred-config (merge restic-repo {:new-password-file "new-pw-file"})) +(def cred-config (merge restic-repo + {:new-password-config {:new-password-file "new-pw" + :valid-from "2024-12-12 00:00:00"}})) (def dry-run {:dry-run true :debug true}) (defn prepare! [] - (spit "/tmp/file_password" "file-password") - (spit "restic-pwd" "ThePassword") + (spit "restic-pwd" "thePassword") + (spit "new-pw" "newPassword") (tasks/shell "mkdir" "-p" "test-backup") (spit "test-backup/file" "I was here") - (spit "new-pw-file" "newpassword") - (tasks/shell "mkdir" "-p" "test-restore") - (pg/create-pg-pass! db-config)) + (tasks/shell "mkdir" "-p" "test-restore")) (defn restic-repo-init! [] @@ -39,6 +32,4 @@ (prepare!) (restic-repo-init!) -(cr/list-passwords! cred-config) -(cr/maybe-add-new! cred-config) -(cr/list-passwords! cred-config) +(cr/change-password! cred-config) diff --git a/src/dda/backup/cred_rot.clj b/src/dda/backup/cred_rot.clj index 1b64370..4d8cce0 100644 --- a/src/dda/backup/cred_rot.clj +++ b/src/dda/backup/cred_rot.clj @@ -3,11 +3,13 @@ [orchestra.core :refer [defn-spec]] [clojure.spec.alpha :as s] [dda.backup.cred-rot.domain :as domain] - [dda.backup.infrastructure :as i])) + [dda.backup.infrastructure :as i] + [cljc.java-time.instant :as inst] + [cljc.java-time.local-date :as ld])) -(s/def ::valid-from domain/timestamp?) +(s/def ::replace-until domain/timestamp?) (s/def ::new-password-file string?) -(s/def ::new-password-config (s/keys :req-un [::new-password-file ::valid-from])) +(s/def ::new-password-config (s/keys :req-un [::new-password-file ::replace-until])) (s/def ::cred-rot (s/keys :req-un [] @@ -18,16 +20,60 @@ [config ::cred-rot] (i/execute-out! (domain/list-passwords-command config) config)) -(defn-spec maybe-add-new! nil? +(defn-spec change-password-step! nil? [config ::cred-rot] - (let [{:keys [new-password-file valid-from]} (:new-password-config config)] + (when-some [new-password-config (:new-password-config config)] + (let [{:keys [new-password-file replace-until]} new-password-config + initial-passwords-list (domain/parse-response (list-passwords! config)) + action (domain/set-new-password-action + (ld/now) + initial-passwords-list + config)] + (cond + (= action :wait-for-new-pwd) + (println "wait till new password is valid") + (= action :set-new-pwd) + (i/execute! (domain/add-password-command config) config) + (= action :remove-old-pwd) + (i/execute! (domain/remove-password-command + config + (:id (first initial-passwords-list)) + (:id (last initial-passwords-list))) + config) + (= action :new-pwd-change-finished) + (println "pw-change sucessfull") + (= action :no-pwd-change-needed) + (println "nothing to do.") + :else + (throw (Exception. (str action))) + ) + (println initial-passwords-list) + (println action) + (println (list-passwords! config)) + action + ))) + +(defn-spec change-password! nil? + [config ::cred-rot] + (while (let [action (change-password-step! config) ] + (and (not= :no-pwd-change-needed action) + (not= :wait-for-new-pwd action))) + (println "call step") + )) + + +(defn-spec set-new-password-old! nil? + [config ::cred-rot] + (let [{:keys [new-password-file replace-until]} (:new-password-config config) + replace-until-date (ld/parse "2024-10-19 13:16:54" domain/timestamp-formatter)] (if (not (nil? new-password-file)) (let [parsed-passwords (domain/parse-response (list-passwords! config))] - (when (> 0 (compare - (:created (last parsed-passwords)) - valid-from)) - (i/execute! (domain/add-password-command config) config)))))) - -(defn-spec replace-old-password! nil? - [] - ) + (println (str "add-new-password: relace until " replace-until)) + (cond + (> 0 (compare + (:created (last parsed-passwords)) + replace-until-date)) + (do (println "add-new-password: set new pw") + (i/execute! (domain/add-password-command config) config)) + :else (println (str "else " (inst/now))))) + (println "add-new-password: there was no new pw configured")))) diff --git a/src/dda/backup/cred_rot/domain.clj b/src/dda/backup/cred_rot/domain.clj index c1b6b64..893222d 100644 --- a/src/dda/backup/cred_rot/domain.clj +++ b/src/dda/backup/cred_rot/domain.clj @@ -27,20 +27,32 @@ (s/def ::hostName (fn [in] (every? #(re-matches lowercase-numeric %) (str/split in #"-")))) ; "2024-10-18 13:08:16" (def timestamp-formatter (df/of-pattern "yyyy-MM-dd HH:mm:ss")) -(defn timestamp? [in] +(defn timestamp-string? [in] (try (ld/parse in timestamp-formatter) true (catch Exception _ false))) +(def timestamp? any?) + (s/def ::created timestamp?) (s/def ::entry (s/keys :opt-un [] :req-un [::current ::id ::userName ::hostName ::created])) (s/def ::response (s/coll-of ::entry)) +(s/def ::set-password-action #{:error-parse-password :error-undefined + :wait-for-new-pwd :set-new-pwd :remove-old-pwd + :new-pwd-change-finished :no-pwd-change-needed}) + +(s/def ::valid-from timestamp-string?) +(s/def ::new-password-file string?) +(s/def ::new-password-config (s/keys :req-un [::new-password-file ::valid-from])) + +(s/def ::cred-rot + (s/keys :req-un [] + :opt-un [::new-password-config])) ; Refer to "docs/CredentialRotation.md" for specifics - (defn-spec base-command ::cd/command [config ::config command ::cd/command] @@ -75,4 +87,37 @@ (defn-spec parse-response ::response [response string?] - (sort-by :created (cc/parse-string response #(keyword %)))) + (->> (cc/parse-string response true) + (map #(merge % {:created (ld/parse (:created %) timestamp-formatter)})) + (sort-by :created) + )) + +(defn-spec set-new-password-action ::set-password-action + [current-date timestamp? + parsed-response ::response + config ::cred-rot] + (if-let [new-password-config (:new-password-config config)] + (let [valid-from (:valid-from new-password-config) + valid-from-date (ld/parse valid-from timestamp-formatter)] + (cond + (> 1 (count parsed-response)) + :error-parse-password + (> 0 (compare current-date valid-from-date)) + :wait-for-new-pwd + (and (<= 0 (compare current-date valid-from-date)) + (= 1 (count parsed-response)) + (> 0 (compare (:created (last parsed-response)) valid-from-date))) + :set-new-pwd + (and (<= 0 (compare current-date valid-from-date)) + (= 2 (count parsed-response)) + (<= 0 (compare (:created (last parsed-response)) valid-from-date)) + (not (:current (last parsed-response)))) + :remove-old-pwd + (and (<= 0 (compare current-date valid-from-date)) + (= 1 (count parsed-response)) + (<= 0 (compare (:created (last parsed-response)) valid-from-date)) + (:current (last parsed-response))) + :new-pwd-change-finished + :else + :error-undefined)) + :no-pwd-change-needed)) diff --git a/test/dda/backup/cred_rot/domain_test.clj b/test/dda/backup/cred_rot/domain_test.clj index c1b1aeb..e713f67 100644 --- a/test/dda/backup/cred_rot/domain_test.clj +++ b/test/dda/backup/cred_rot/domain_test.clj @@ -3,37 +3,9 @@ [clojure.test :refer [deftest is]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] + [cljc.java-time.local-date :as ld] [dda.backup.cred-rot.domain :as cut])) -(deftest test-parse-response - (is (= - [{:current true, - :id "b67161fb", - :userName "root", - :hostName "backup-restore-65bd9b6ff5-z69sn", - :created "2024-10-18 13:16:54"} - {:current false, - :id "521e0760", - :userName "root", - :hostName "backup-restore-65bd9b6ff5-z69sn", - :created "2024-11-18 13:08:16"}] - (cut/parse-response "[ - { - \"current\": false, - \"id\": \"521e0760\", - \"userName\": \"root\", - \"hostName\": \"backup-restore-65bd9b6ff5-z69sn\", - \"created\": \"2024-11-18 13:08:16\" - }, - { - \"current\": true, - \"id\": \"b67161fb\", - \"userName\": \"root\", - \"hostName\": \"backup-restore-65bd9b6ff5-z69sn\", - \"created\": \"2024-10-18 13:16:54\" - } -]")))) - (deftest test-spec-id (is (s/valid? ::cut/id "521e0760")) (is (s/valid? ::cut/id "test")) @@ -54,9 +26,97 @@ (is (valid "backup-restore-65bd9b6ff5-z69sn")))) (deftest test-spec-timestamp - (let [valid #(s/valid? cut/timestamp? %)] + (let [valid #(s/valid? cut/timestamp-string? %)] (is (valid "2024-10-18 13:08:16")) (is (valid "2032-09-01 12:56:59")) (is (not (valid "2024-13-5 13:08:16"))) (is (not (valid "2024-6-42 13:08:16"))) - (is (not (valid "test"))))) \ No newline at end of file + (is (not (valid "test"))))) + +(deftest test-parse-response + (is (= + (ld/parse "2024-10-19 13:16:54" cut/timestamp-formatter) + (:created + (first + (cut/parse-response "[ + { + \"current\": false, + \"id\": \"521e0760\", + \"userName\": \"root\", + \"hostName\": \"backup-restore-65bd9b6ff5-z69sn\", + \"created\": \"2024-11-18 13:08:16\" + }, + { + \"current\": true, + \"id\": \"b67161fb\", + \"userName\": \"root\", + \"hostName\": \"backup-restore-65bd9b6ff5-z69sn\", + \"created\": \"2024-10-19 13:16:54\" + } +]")))))) + +(deftest test-set-new-password-action + (is (= :error-parse-password + (cut/set-new-password-action + (ld/parse "2024-10-19 13:16:54" cut/timestamp-formatter) + [] + {:new-password-config {:new-password-file "new-pw-file" + :valid-from "2024-11-29 12:00:16"}}))) + (is (= :wait-for-new-pwd + (cut/set-new-password-action + (ld/parse "2024-10-19 13:16:54" cut/timestamp-formatter) + [{:current true + :id "a1" + :userName "root" + :hostName "host" + :created (ld/parse "2023-01-01 00:00:00" cut/timestamp-formatter)}] + {:new-password-config {:new-password-file "new-pw-file" + :valid-from "2024-11-29 12:00:16"}}))) + (is (= :set-new-pwd + (cut/set-new-password-action + (ld/parse "2024-11-29 13:16:54" cut/timestamp-formatter) + [{:current true + :id "a1" + :userName "root" + :hostName "host" + :created (ld/parse "2023-01-01 00:00:00" cut/timestamp-formatter)}] + {:new-password-config {:new-password-file "new-pw-file" + :valid-from "2024-11-29 12:00:16"}}))) + + (is (= :remove-old-pwd + (cut/set-new-password-action + (ld/parse "2024-11-29 13:16:55" cut/timestamp-formatter) + [{:current true + :id "a1" + :userName "root" + :hostName "host" + :created (ld/parse "2023-01-01 00:00:00" cut/timestamp-formatter)} + {:current false + :id "a2" + :userName "root" + :hostName "host" + :created (ld/parse "2024-11-29 13:16:54" cut/timestamp-formatter)}] + {:new-password-config {:new-password-file "new-pw-file" + :valid-from "2024-11-29 12:00:16"}}))) + + (is (= :new-pwd-change-finished + (cut/set-new-password-action + (ld/parse "2024-11-29 13:16:55" cut/timestamp-formatter) + [{:current true + :id "a2" + :userName "root" + :hostName "host" + :created (ld/parse "2024-11-29 13:16:54" cut/timestamp-formatter)}] + {:new-password-config {:new-password-file "new-pw-file" + :valid-from "2024-11-29 12:00:16"}}))) + + (is (= :no-pwd-change-needed + (cut/set-new-password-action + (ld/parse "2024-11-29 13:16:55" cut/timestamp-formatter) + [{:current true + :id "a2" + :userName "root" + :hostName "host" + :created (ld/parse "2024-11-29 13:16:54" cut/timestamp-formatter)}] + {}))) + ) \ No newline at end of file