From 921329f49df0b7e036aede2556ed008e3627ccc0 Mon Sep 17 00:00:00 2001 From: isindir Date: Sun, 20 Jun 2021 21:57:48 +0100 Subject: [PATCH] feat: add target secret annotation, which will trigger it being replaced and owned by sopssecret (#81) --- README.md | 15 ++++ api/v1alpha3/sopssecret_types.go | 6 ++ controllers/sopssecret_controller.go | 10 ++- docs/index.yaml | 112 ++++++++++++++++----------- docs/sops-secrets-operator-0.9.1.tgz | Bin 0 -> 10398 bytes 5 files changed, 96 insertions(+), 47 deletions(-) create mode 100644 docs/sops-secrets-operator-0.9.1.tgz diff --git a/README.md b/README.md index d295208a..8691e580 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,21 @@ sops --encrypt \ > access to one of these is needed. For more information see `sops` > documentation. +## Changing ownership of existing secrets + +If there is a need to re-own existing `Secrets` by `SopsSecret`, following annotation should +be added to the target kubernetes native secret: + +```yaml +... +metadata: + annotations: + "sopssecret/managed": "true" +... +``` +> previously unmanaged secret will be replaced by `SopsSecret` owned at the next rescheduled + reconciliation event. + ## Example procedure to upgrade from one `SopsSecret` API version to another Please see document here: [SopsSecret API and Operator Upgrade](docs/api_upgrade_example/README.md) diff --git a/api/v1alpha3/sopssecret_types.go b/api/v1alpha3/sopssecret_types.go index e965f3f3..416a76cf 100644 --- a/api/v1alpha3/sopssecret_types.go +++ b/api/v1alpha3/sopssecret_types.go @@ -11,6 +11,12 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. // For upstream reference, see https://github.com/mozilla/sops/blob/master/stores/stores.go +const ( + // SopsSecretManagedAnnotation is the name for the annotation for + // flagging the existing secret be managed by SopsSecret controller. + SopsSecretManagedAnnotation = "sopssecret/managed" +) + // SopsSecretSpec defines the desired state of SopsSecret type SopsSecretSpec struct { // Secrets template is a list of definitions to create Kubernetes Secrets diff --git a/controllers/sopssecret_controller.go b/controllers/sopssecret_controller.go index bc7b934f..50d578f0 100644 --- a/controllers/sopssecret_controller.go +++ b/controllers/sopssecret_controller.go @@ -181,7 +181,7 @@ func (r *SopsSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) return reconcile.Result{Requeue: true, RequeueAfter: time.Duration(r.RequeueAfter) * time.Minute}, nil } - if !metav1.IsControlledBy(foundSecret, instance) { + if !metav1.IsControlledBy(foundSecret, instance) && !isAnnotatedToBeManaged(foundSecret) { instanceEncrypted.Status.Message = "Child secret is not owned by controller error" r.Status().Update(context.Background(), instanceEncrypted) @@ -203,6 +203,9 @@ func (r *SopsSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) foundSecret.Type = newSecret.Type foundSecret.ObjectMeta.Annotations = newSecret.ObjectMeta.Annotations foundSecret.ObjectMeta.Labels = newSecret.ObjectMeta.Labels + if isAnnotatedToBeManaged(origSecret) { + foundSecret.ObjectMeta.OwnerReferences = newSecret.ObjectMeta.OwnerReferences + } if !apiequality.Semantic.DeepEqual(origSecret, foundSecret) { r.Log.Info( @@ -246,6 +249,11 @@ func (r *SopsSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } +// checks if the annotation equals to "true", and it's case sensitive +func isAnnotatedToBeManaged(secret *corev1.Secret) bool { + return secret.Annotations[isindirv1alpha3.SopsSecretManagedAnnotation] == "true" +} + // SetupWithManager sets up the controller with the Manager. func (r *SopsSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { diff --git a/docs/index.yaml b/docs/index.yaml index db14d40a..04e23d7c 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -1,9 +1,29 @@ apiVersion: v1 entries: sops-secrets-operator: + - apiVersion: v2 + appVersion: 0.3.1 + created: "2021-06-20T21:15:54.000434+01:00" + description: Helm chart deploys sops-secrets-operator + digest: 6228534471c0bab5eca824d98963adce99d4754bf03f37130529ac52536c79ad + keywords: + - gitops + - sops + - kms + - encryption + maintainers: + - email: isindir@users.sf.net + name: isindir + name: sops-secrets-operator + sources: + - https://github.com/isindir/sops-secrets-operator.git + type: application + urls: + - https://isindir.github.io/sops-secrets-operator/sops-secrets-operator-0.9.1.tgz + version: 0.9.1 - apiVersion: v2 appVersion: 0.3.0 - created: "2021-05-29T18:01:47.055559+01:00" + created: "2021-06-20T21:15:53.99951+01:00" description: Helm chart deploys sops-secrets-operator digest: b7f077f8acac1b6ec60d0c0eb6326ab33cf3e4b9fb1ee8d94b1fa21f96aa7383 keywords: @@ -23,7 +43,7 @@ entries: version: 0.9.0 - apiVersion: v2 appVersion: 0.2.2 - created: "2021-05-29T18:01:47.054563+01:00" + created: "2021-06-20T21:15:53.998382+01:00" description: Helm chart deploys sops-secrets-operator digest: 39d3d35a28a405e7baf82d507fa642966c2705ac3ca2f10077186a7fec5de1f6 keywords: @@ -43,7 +63,7 @@ entries: version: 0.8.4 - apiVersion: v2 appVersion: 0.2.1 - created: "2021-05-29T18:01:47.052996+01:00" + created: "2021-06-20T21:15:53.99716+01:00" description: Helm chart deploys sops-secrets-operator digest: 4a1a3299532a4ec61acb61db45d763385bc3c2bd50c9c1707e3ba258498b5ee5 keywords: @@ -63,7 +83,7 @@ entries: version: 0.8.3 - apiVersion: v2 appVersion: 0.2.1 - created: "2021-05-29T18:01:47.04894+01:00" + created: "2021-06-20T21:15:53.995079+01:00" description: Helm chart deploys sops-secrets-operator digest: d328b4e165c3945430e196a853836dcee9982929fe24455021ddb885099d5334 keywords: @@ -83,7 +103,7 @@ entries: version: 0.8.2 - apiVersion: v2 appVersion: 0.2.0 - created: "2021-05-29T18:01:47.044543+01:00" + created: "2021-06-20T21:15:53.993817+01:00" description: Helm chart deploys sops-secrets-operator digest: d0ac8b738d0f10d64b2fb78c4386efe91de39aa88a4b107fdf9d93a82d18573c keywords: @@ -103,7 +123,7 @@ entries: version: 0.8.1 - apiVersion: v2 appVersion: 0.2.0 - created: "2021-05-29T18:01:47.040062+01:00" + created: "2021-06-20T21:15:53.992113+01:00" description: Helm chart deploys sops-secrets-operator digest: 289d7c6c96f858fe15427b1858fbfcdec373fc345acf52e667df4ca5ee729c10 keywords: @@ -123,7 +143,7 @@ entries: version: 0.8.0 - apiVersion: v2 appVersion: 0.1.17 - created: "2021-05-29T18:01:47.038736+01:00" + created: "2021-06-20T21:15:53.991091+01:00" description: sops secrets operator digest: 1c3c4bba7d66a7621beced04856d9904260558fe10369513743bc322d69482c1 keywords: @@ -143,7 +163,7 @@ entries: version: 0.7.6 - apiVersion: v2 appVersion: 0.1.16 - created: "2021-05-29T18:01:47.037488+01:00" + created: "2021-06-20T21:15:53.990143+01:00" description: sops secrets operator digest: c526d5d4b9c7c2cce1d9da2c75b4e9be7a994f24dce159a659189414a8725eae keywords: @@ -163,7 +183,7 @@ entries: version: 0.7.5 - apiVersion: v2 appVersion: 0.1.16 - created: "2021-05-29T18:01:47.036313+01:00" + created: "2021-06-20T21:15:53.989104+01:00" description: sops secrets operator digest: 572c9015988b76869b58997e02a0c64152283e559721e4883d54f1258a57e8b7 keywords: @@ -183,7 +203,7 @@ entries: version: 0.7.4 - apiVersion: v2 appVersion: 0.1.15 - created: "2021-05-29T18:01:47.033939+01:00" + created: "2021-06-20T21:15:53.988048+01:00" description: sops secrets operator digest: 84365f8e919ba9d3a00cfa50435cce6c63a8383357b2fde062b7aab8baeca6eb keywords: @@ -203,7 +223,7 @@ entries: version: 0.7.3 - apiVersion: v2 appVersion: 0.1.14 - created: "2021-05-29T18:01:47.032602+01:00" + created: "2021-06-20T21:15:53.986993+01:00" description: sops secrets operator digest: a1f2375080df20421701a33179b8e947ee682a70084d83d85da707889871ad64 keywords: @@ -223,7 +243,7 @@ entries: version: 0.7.2 - apiVersion: v2 appVersion: 0.1.13 - created: "2021-05-29T18:01:47.031197+01:00" + created: "2021-06-20T21:15:53.985912+01:00" description: sops secrets operator digest: 2e81dc4e4d49d9cd802aff263f005e04fb57df07f33b3ce8643ab287dfd3a7fb keywords: @@ -243,7 +263,7 @@ entries: version: 0.7.1 - apiVersion: v2 appVersion: 0.1.12 - created: "2021-05-29T18:01:47.030069+01:00" + created: "2021-06-20T21:15:53.984831+01:00" description: sops secrets operator digest: 81f59ed60bfa8204ed285476f9ed96a45a6f4e7cc6940a5d246c9241573d93d5 keywords: @@ -263,7 +283,7 @@ entries: version: 0.7.0 - apiVersion: v2 appVersion: 0.1.12 - created: "2021-05-29T18:01:47.028988+01:00" + created: "2021-06-20T21:15:53.983808+01:00" description: sops secrets operator digest: 91c3fbda73ba2d860bdaa21e37bf9afbc260ff767b377a144d0181d116a7ee34 keywords: @@ -283,7 +303,7 @@ entries: version: 0.6.8 - apiVersion: v2 appVersion: 0.1.12 - created: "2021-05-29T18:01:47.027987+01:00" + created: "2021-06-20T21:15:53.982686+01:00" description: sops secrets operator digest: 89d9d41d70d4dafcfb957bd48776ad779d0cef7dbb1ab2daf0b745a53dd6e3c6 maintainers: @@ -298,7 +318,7 @@ entries: version: 0.6.7 - apiVersion: v2 appVersion: 0.1.11 - created: "2021-05-29T18:01:47.026968+01:00" + created: "2021-06-20T21:15:53.981646+01:00" description: sops secrets operator digest: 7b0a65fd6fa9bafa3fd11bfef1a5f91f1e17d8cb8ad65b6377ffdc4d12495d01 maintainers: @@ -313,7 +333,7 @@ entries: version: 0.6.6 - apiVersion: v2 appVersion: 0.1.10 - created: "2021-05-29T18:01:47.025862+01:00" + created: "2021-06-20T21:15:53.980547+01:00" description: sops secrets operator digest: fac31d6cc862cb7b9a81aee52ba1fc4183d70bdcb7424c3dbdd087fb53246b30 maintainers: @@ -328,7 +348,7 @@ entries: version: 0.6.5 - apiVersion: v2 appVersion: 0.1.9 - created: "2021-05-29T18:01:47.024385+01:00" + created: "2021-06-20T21:15:53.979293+01:00" description: sops secrets operator digest: 01347c27e37dfff999ebcee12aae6d0aafa092d7c3b221d566cdf0abe71f4d5a maintainers: @@ -343,7 +363,7 @@ entries: version: 0.6.4 - apiVersion: v2 appVersion: 0.1.8 - created: "2021-05-29T18:01:47.022604+01:00" + created: "2021-06-20T21:15:53.977477+01:00" description: sops secrets operator digest: 6348b1b1b0e8d3df3926e437b2c0f4ad63268d26e2cb54cbecbb564102e6b19c maintainers: @@ -358,7 +378,7 @@ entries: version: 0.6.3 - apiVersion: v2 appVersion: 0.1.7 - created: "2021-05-29T18:01:47.02114+01:00" + created: "2021-06-20T21:15:53.976383+01:00" description: sops secrets operator digest: 710c1c9fa73a2ebf791fda4a608b5e29072d42c0b68c803c7bbeed54a582fd7f maintainers: @@ -373,7 +393,7 @@ entries: version: 0.6.2 - apiVersion: v2 appVersion: 0.1.7 - created: "2021-05-29T18:01:47.020028+01:00" + created: "2021-06-20T21:15:53.975385+01:00" description: sops secrets operator digest: f2a606c3837843241bb9d59adc02c38e1cca98753c602b9f758cc61d735ca7cd maintainers: @@ -388,7 +408,7 @@ entries: version: 0.6.1 - apiVersion: v2 appVersion: 0.1.6 - created: "2021-05-29T18:01:47.018963+01:00" + created: "2021-06-20T21:15:53.974319+01:00" description: sops secrets operator digest: a2bbf9b39ec5f5b82965037f8f245fb3122adbe31b1c7d336fa1f4cddb228b88 maintainers: @@ -403,7 +423,7 @@ entries: version: 0.6.0 - apiVersion: v1 appVersion: 0.1.8 - created: "2021-05-29T18:01:47.017692+01:00" + created: "2021-06-20T21:15:53.973312+01:00" description: sops secrets operator digest: b89986787f33bb6ed9fb0c658431be8646302e9c1a24537c26269c62249fa071 maintainers: @@ -417,7 +437,7 @@ entries: version: 0.5.3 - apiVersion: v1 appVersion: 0.1.7 - created: "2021-05-29T18:01:47.016535+01:00" + created: "2021-06-20T21:15:53.972242+01:00" description: sops secrets operator digest: 9467709cf6fbe8d9d779cedf15fe388af172b609f3ca452ef3d8894f39d999df maintainers: @@ -431,7 +451,7 @@ entries: version: 0.5.2 - apiVersion: v1 appVersion: 0.1.7 - created: "2021-05-29T18:01:47.015303+01:00" + created: "2021-06-20T21:15:53.969667+01:00" description: sops secrets operator digest: b54b5d8497564ddc04bd6d8b105eb0a3559e82ae1f6aab2f59ed3e426f119287 maintainers: @@ -445,7 +465,7 @@ entries: version: 0.5.1 - apiVersion: v1 appVersion: 0.1.6 - created: "2021-05-29T18:01:47.014445+01:00" + created: "2021-06-20T21:15:53.968905+01:00" description: sops secrets operator digest: 177f1ed214d6e72eda589a6ab155a417c1a4229bfda11e87f24af125a3542ad1 maintainers: @@ -459,7 +479,7 @@ entries: version: 0.5.0 - apiVersion: v2 appVersion: 0.1.5 - created: "2021-05-29T18:01:47.013605+01:00" + created: "2021-06-20T21:15:53.968148+01:00" description: sops secrets operator digest: 1535e130357afa883db0b3d30735c817d3b7d412fe5bdfd71534d0c08defa7d1 maintainers: @@ -474,7 +494,7 @@ entries: version: 0.4.8 - apiVersion: v2 appVersion: 0.1.5 - created: "2021-05-29T18:01:47.012552+01:00" + created: "2021-06-20T21:15:53.967242+01:00" description: sops secrets operator digest: 19b11dc2d1945f3c436a7d03763b4391d4a382fc13ea515d25422827d859d6d0 maintainers: @@ -489,7 +509,7 @@ entries: version: 0.4.7 - apiVersion: v2 appVersion: 0.1.5 - created: "2021-05-29T18:01:47.011724+01:00" + created: "2021-06-20T21:15:53.96649+01:00" description: sops secrets operator digest: c839e5d3374b948d27ad49643411f4891fdec44d179dea06423bb0d6e29d5e32 maintainers: @@ -504,7 +524,7 @@ entries: version: 0.4.6 - apiVersion: v2 appVersion: 0.1.4 - created: "2021-05-29T18:01:47.010645+01:00" + created: "2021-06-20T21:15:53.965712+01:00" description: sops secrets operator digest: c71f9f66be32f8b9d3c8d780b09b2455a40fd9755314004efd2bb8d379dafe3c maintainers: @@ -519,7 +539,7 @@ entries: version: 0.4.5 - apiVersion: v2 appVersion: 0.1.3 - created: "2021-05-29T18:01:47.009804+01:00" + created: "2021-06-20T21:15:53.964813+01:00" description: sops secrets operator digest: f3f2f89d4ef6018776df0a12a63dd2f9c9519b9d1ac03a9a405e31d0fd902ba0 maintainers: @@ -534,7 +554,7 @@ entries: version: 0.4.4 - apiVersion: v2 appVersion: 0.1.2 - created: "2021-05-29T18:01:47.008993+01:00" + created: "2021-06-20T21:15:53.963897+01:00" description: sops secrets operator digest: 1fd5eed318627f5ed0656f4e8ce4a25729568a1626ae313bcbe21050f5f26240 maintainers: @@ -549,7 +569,7 @@ entries: version: 0.4.3 - apiVersion: v2 appVersion: 0.1.2 - created: "2021-05-29T18:01:47.007968+01:00" + created: "2021-06-20T21:15:53.962778+01:00" description: sops secrets operator digest: 1f4f9869c75f0922e83ba5d530e101bd4252d5c1c31365800cc9d1425680cf18 maintainers: @@ -564,7 +584,7 @@ entries: version: 0.4.2 - apiVersion: v2 appVersion: 0.1.1 - created: "2021-05-29T18:01:47.007034+01:00" + created: "2021-06-20T21:15:53.961906+01:00" description: sops secrets operator digest: 6b054a4e9f261eea3cb84ee2e70b87b24780f1703e2c218ea5f69b7f82d1876f maintainers: @@ -579,7 +599,7 @@ entries: version: 0.4.1 - apiVersion: v2 appVersion: 0.1.0 - created: "2021-05-29T18:01:47.00575+01:00" + created: "2021-06-20T21:15:53.959739+01:00" description: sops secrets operator digest: 78b62ab37eac1b45f0a68a9752a3615c5d3f1c960bb4057e665923ce104931cf maintainers: @@ -594,7 +614,7 @@ entries: version: 0.4.0 - apiVersion: v1 appVersion: 0.1.5 - created: "2021-05-29T18:01:47.003405+01:00" + created: "2021-06-20T21:15:53.95895+01:00" description: sops secrets operator digest: 41baa3c580cb9d8951c18513a4f04c4dbbfad99de9c62f53de2450c0c7b76725 maintainers: @@ -608,7 +628,7 @@ entries: version: 0.3.7 - apiVersion: v1 appVersion: 0.1.5 - created: "2021-05-29T18:01:47.001835+01:00" + created: "2021-06-20T21:15:53.958026+01:00" description: sops secrets operator digest: 1103b1f7bf7af3f400c172227cd5a3659f3a03e5e8158b19ba0b25f7ed45208b maintainers: @@ -622,7 +642,7 @@ entries: version: 0.3.6 - apiVersion: v1 appVersion: 0.1.5 - created: "2021-05-29T18:01:47.000715+01:00" + created: "2021-06-20T21:15:53.957111+01:00" description: sops secrets operator digest: 15c72ba7fb09d0e980ec32fd94f56893c439c05c435281a9ab9c8bc94bd20063 maintainers: @@ -636,7 +656,7 @@ entries: version: 0.3.5 - apiVersion: v1 appVersion: 0.1.4 - created: "2021-05-29T18:01:46.999698+01:00" + created: "2021-06-20T21:15:53.956272+01:00" description: sops secrets operator digest: 025a6a6381b75286756ef55105ace6e911e5a5818b495ede6356cc8ec572aeac maintainers: @@ -650,7 +670,7 @@ entries: version: 0.3.4 - apiVersion: v1 appVersion: 0.1.3 - created: "2021-05-29T18:01:46.998541+01:00" + created: "2021-06-20T21:15:53.955449+01:00" description: sops secrets operator digest: f61b070b640169439cf4ab500047c1e356748a85871f7aeefde46d63d87d453a maintainers: @@ -664,7 +684,7 @@ entries: version: 0.3.3 - apiVersion: v1 appVersion: 0.1.2 - created: "2021-05-29T18:01:46.997197+01:00" + created: "2021-06-20T21:15:53.954613+01:00" description: sops secrets operator digest: 2b37dc4e545e8a9540f6b7693079b98bf161ec5a68899defcfc9420bdcbb33e3 maintainers: @@ -678,7 +698,7 @@ entries: version: 0.3.2 - apiVersion: v1 appVersion: 0.1.1 - created: "2021-05-29T18:01:46.995734+01:00" + created: "2021-06-20T21:15:53.953724+01:00" description: sops secrets operator digest: 2e2762b8f9d66aab0caacde225955fec8bfd5a4cc10dc6943a1de3809dda4091 maintainers: @@ -692,7 +712,7 @@ entries: version: 0.3.1 - apiVersion: v1 appVersion: 0.1.0 - created: "2021-05-29T18:01:46.994769+01:00" + created: "2021-06-20T21:15:53.952917+01:00" description: sops secrets operator digest: ce84f5b64402a582c7689cb842ba03fb10f968c38b57dc9e05f588493128019a maintainers: @@ -706,7 +726,7 @@ entries: version: 0.3.0 - apiVersion: v2 appVersion: 0.0.10 - created: "2021-05-29T18:01:46.993412+01:00" + created: "2021-06-20T21:15:53.952019+01:00" description: sops secrets operator digest: 5e4c8bc37ea2c819c55b288c0a5e76ff8c9c02be591bd53776606666af45581c maintainers: @@ -721,7 +741,7 @@ entries: version: 0.2.1 - apiVersion: v1 appVersion: 0.0.10 - created: "2021-05-29T18:01:46.992358+01:00" + created: "2021-06-20T21:15:53.951234+01:00" description: sops secrets operator digest: 50b8ebab19008dfc43de1eaee8b0f6287f7a55134585dc6ae88df2520d779f8f maintainers: @@ -733,4 +753,4 @@ entries: urls: - https://isindir.github.io/sops-secrets-operator/sops-secrets-operator-0.1.10.tgz version: 0.1.10 -generated: "2021-05-29T18:01:46.990787+01:00" +generated: "2021-06-20T21:15:53.94994+01:00" diff --git a/docs/sops-secrets-operator-0.9.1.tgz b/docs/sops-secrets-operator-0.9.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..848ff908d65685a82a9182df3e111c5001de29a1 GIT binary patch literal 10398 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 Uz9V&yF>i8NOV-MDX6b&n? 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 IuK?r#0Mc)ExBvhE literal 0 HcmV?d00001