├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── admission-controller ├── BUILD ├── Dockerfile ├── config.go ├── convert.go ├── convert_test.go ├── go.mod ├── go.sum ├── main.go ├── patch_test.go ├── pods.go ├── scheme.go └── secret-inject │ ├── Chart.yaml │ ├── python │ ├── Dockerfile │ ├── README.md │ └── admission_controller.py │ ├── templates │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── service.yaml │ └── webhook.yaml │ └── values.yaml ├── cmd └── aws-secrets-manager │ ├── main.go │ └── main_test.go ├── ecs-task-def └── task-def.json ├── go.mod ├── go.sum ├── kubernetes-manifests └── webserver.yaml └── secret-operator ├── Dockerfile ├── Makefile ├── PROJECT ├── README.md ├── api └── v1 │ ├── groupversion_info.go │ ├── secretsrotationmapping_types.go │ └── zz_generated.deepcopy.go ├── bin └── manager ├── blog3.jpg ├── cfn.yaml ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── awssecretsoperator.secretoperator_secretsrotationmappings.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_secretsrotationmappings.yaml │ │ └── webhook_in_secretsrotationmappings.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ ├── kustomization_bkup.yaml │ ├── manager.yaml │ └── manager_bkup.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── secretsrotationmapping_editor_role.yaml │ └── secretsrotationmapping_viewer_role.yaml ├── samples │ ├── awssecretsoperator_v1_secretsrotationmapping.1.yaml │ ├── awssecretsoperator_v1_secretsrotationmapping.yaml │ └── deployment.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── service.yaml ├── controllers ├── secretsrotationmapping_controller.go └── suite_test.go ├── cover.out ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt └── main.go /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux AS build 2 | RUN yum -y update && yum -y install tar gzip 3 | RUN curl -o go1.14.3.linux-amd64.tar.gz https://dl.google.com/go/go1.14.3.linux-amd64.tar.gz -s 4 | RUN tar -C /usr/local -xzf go1.14.3.linux-amd64.tar.gz 5 | ENV PATH="/usr/local/go/bin:${PATH}" 6 | WORKDIR /src/aws-secrets-manager 7 | COPY ./go.mod ./go.sum ./ 8 | RUN go mod download 9 | COPY . ./ 10 | RUN go build -o /app -v ./cmd/aws-secrets-manager 11 | 12 | FROM amazonlinux:latest 13 | RUN yum -y update && yum install -y ca-certificates && rm -rf /var/cache/yum/* 14 | COPY --from=build /app /. 15 | ENTRYPOINT ["/app"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is now archived. Please use the [CSI Secret Store driver for AWS Secrets Manager](https://github.com/aws/secrets-store-csi-driver-provider-aws) instead. 2 | 3 | # AWS Secret Sidecar Injector 4 | 5 | The _aws-secret-sidecar-injector_ is a proof-of-concept(PoC) that allows your containerized applications to consume secrets from AWS Secrets Manager. The solution makes use of a Kubernetes dynamic admission controller that injects an _init_ container, aws-secrets-manager-secret-sidecar, upon creation/update of your pod. The init container relies on [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) to retrieve the secret from AWS Secrets Manager. The Kubernetes dynamic admission controller also creates an in-memory Kubernetes volume (with name `secret-vol` and `emptyDirectory.medium` as `Memory`) associated with the pod to access the secret. 6 | 7 | ## Announcing the AWS Secrets and Config Provider (ASCP) 8 | 9 | As of 4/22/21, you use the CSI Secret Store driver with AWS Secrets Manager and Parameter Store. ASCP is similar to this project in that it mounts secrets as volumes, however there are several key differences that are worth highlighting. First, it works with both Secrets Manager **and** Parameter store. Second, ASCP can mount multiple secrets whereas the sidecar injector only supports 1. Third, ASCP can synchronize secrets from Secrets Manager to Kubernetes Secrets which is similar to GoDaddy's [ExternalSecrets](https://github.com/external-secrets/kubernetes-external-secrets) project. Copying secrets from Secrets Manager to Kubernetes Secrets allows you to map secrets to environment variables instead of mounting them as volumes. Fourth, ASCP can rotate secrets, however, unlike the sidecar injector, ASCP uses a polling mechanism rather than an event to trigger the rotation. When we were thinking of how to handle the rotation of secrets we decided to use an event rather than polling to a) limit the resources required to continuously run the sidecar b) avoid getting throttled; currently 5,000 OPS c) keep costs low or as close to zero as possible; Secrets Manager charges $0.05 per 10,000 API calls. Fifth, with ASCP you have to create a secret provider class for each secret you want to reference in your pod. Finally, the CSI Secret Store driver gives you a vendor agnostic and standard way to fetch secrets from an external secret store. 10 | 11 | > You can continue using Michael Hausenblas's [NASE](https://github.com/mhausenblas/nase) project to create secrets in Secrets Manager. 12 | 13 | We will continue supporting this project, but we also encourage you to give ASCP a try. Thank you to all of those who provided feedback and helped make this project what it is today. For additional information about ASCP see: 14 | 15 | + [How to use AWS Secrets Configuration Provider with Kubernetes Secret Store CSI Driver](https://aws.amazon.com/blogs/security/how-to-use-aws-secrets-configuration-provider-with-kubernetes-secrets-store-csi-driver/) 16 | + [Secret Store CSI Driver Provider AWS](https://github.com/aws/secrets-store-csi-driver-provider-aws) 17 | + [Integrating CSI Driver](https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_csi_driver.html) 18 | 19 | ## Prerequsites 20 | - An IRSA ServiceAccount that has permission to access and retrive the secret from AWS Secrets Manager 21 | - Helm to install the mutating admission webhook 22 | 23 | ## Installation 24 | 25 | ### Deploying mutating webhook to inject the init container 26 | 27 | - Add the Helm repository which contains the Helm chart for the mutating admission webhook 28 | 29 | ```helm repo add secret-inject https://aws-samples.github.io/aws-secret-sidecar-injector/``` 30 | 31 | - Update the Helm repository 32 | 33 | ```helm repo update``` 34 | 35 | - Deploy the mutating webhook admission controller 36 | 37 | ```helm install secret-inject secret-inject/secret-inject``` 38 | 39 | ## Accessing the secret 40 | 41 | Add the following annotations to your podSpec to mount the secret in your pod 42 | 43 | ```secrets.k8s.aws/secret-arn: ``` 44 | 45 | By default, the decrypted secret is written to a volume named `secret-vol` and the filename of the secret is `secret`. The Kubernetes dynamic admission controller also creates corresponding mountPath `/tmp/secret` for containers within the pod to access the secret. 46 | 47 | You can optionally mount the `secret-vol` volume for containers within the pod at a specific path using the following optional annotation 48 | 49 | ```secrets.k8s.aws/mount-path: ``` 50 | 51 | Note that,the path should be an absolute path such as "/my-path" 52 | 53 | You can optionally customize the filename / subfolders within the mounted path where the secret is written by using hte following optional annotation 54 | 55 | ```secrets.k8s.aws/secret-filename: ``` 56 | 57 | This repository contains a sample Kubernetes deployment [manifest](https://github.com/aws-samples/aws-secret-sidecar-injector/blob/master/kubernetes-manifests/webserver.yaml) which uses this project to access AWS Secrets Manager secret. 58 | 59 | ## Creating Secrets 60 | 61 | AWS Secrets Manager secrets can be created and managed natively in Kubernetes using [Native Secrets(NASE)](https://github.com/mhausenblas/nase). The NASE project is a serverless mutating webhook, which "intercepts" the calls to create and update native Kubernetes Secrets and writes the secret in the secret manifest to AWS Secrets Manager and returns the ARN of the secret to Kubernetes which stores it as a secret. 62 | 63 | ## Rotating Secrets 64 | 65 | Support for restarting pods when the secret they reference is rotated, is now available. For additional information, see the [README](https://github.com/aws-samples/aws-secret-sidecar-injector/blob/master/secret-operator/README.md) in the secret-operator folder. 66 | 67 | ## License 68 | 69 | This library is licensed under the MIT-0 License. See the LICENSE file. 70 | 71 | -------------------------------------------------------------------------------- /admission-controller/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "go_default_library", 5 | srcs = [ 6 | "addlabel.go", 7 | "alwaysallow.go", 8 | "alwaysdeny.go", 9 | "config.go", 10 | "configmap.go", 11 | "convert.go", 12 | "crd.go", 13 | "customresource.go", 14 | "main.go", 15 | "pods.go", 16 | "scheme.go", 17 | ], 18 | importpath = "k8s.io/kubernetes/test/images/agnhost/webhook", 19 | visibility = ["//visibility:public"], 20 | deps = [ 21 | "//staging/src/k8s.io/api/admission/v1:go_default_library", 22 | "//staging/src/k8s.io/api/admission/v1beta1:go_default_library", 23 | "//staging/src/k8s.io/api/admissionregistration/v1:go_default_library", 24 | "//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library", 25 | "//staging/src/k8s.io/api/core/v1:go_default_library", 26 | "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library", 27 | "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", 28 | "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", 29 | "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", 30 | "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", 31 | "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", 32 | "//vendor/github.com/spf13/cobra:go_default_library", 33 | "//vendor/k8s.io/klog:go_default_library", 34 | ], 35 | ) 36 | 37 | filegroup( 38 | name = "package-srcs", 39 | srcs = glob(["**"]), 40 | tags = ["automanaged"], 41 | visibility = ["//visibility:private"], 42 | ) 43 | 44 | filegroup( 45 | name = "all-srcs", 46 | srcs = [":package-srcs"], 47 | tags = ["automanaged"], 48 | visibility = ["//visibility:public"], 49 | ) 50 | 51 | go_test( 52 | name = "go_default_test", 53 | srcs = [ 54 | "addlabel_test.go", 55 | "convert_test.go", 56 | "patch_test.go", 57 | ], 58 | embed = [":go_default_library"], 59 | deps = [ 60 | "//pkg/apis/admission/fuzzer:go_default_library", 61 | "//staging/src/k8s.io/api/admission/v1:go_default_library", 62 | "//staging/src/k8s.io/api/admission/v1beta1:go_default_library", 63 | "//staging/src/k8s.io/api/core/v1:go_default_library", 64 | "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library", 65 | "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", 66 | "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", 67 | "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", 68 | "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", 69 | "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", 70 | "//vendor/github.com/evanphx/json-patch:go_default_library", 71 | "//vendor/github.com/google/gofuzz:go_default_library", 72 | ], 73 | ) 74 | -------------------------------------------------------------------------------- /admission-controller/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux 2 | COPY ./adm-controller /adm-controller 3 | ENTRYPOINT ["/adm-controller"] 4 | -------------------------------------------------------------------------------- /admission-controller/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/tls" 21 | 22 | "k8s.io/klog" 23 | ) 24 | 25 | // Config contains the server (the webhook) cert and key. 26 | type Config struct { 27 | CertFile string 28 | KeyFile string 29 | } 30 | 31 | func configTLS(config Config) *tls.Config { 32 | sCert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile) 33 | if err != nil { 34 | klog.Fatal(err) 35 | } 36 | return &tls.Config{ 37 | Certificates: []tls.Certificate{sCert}, 38 | // TODO: uses mutual tls after we agree on what cert the apiserver should use. 39 | // ClientAuth: tls.RequireAndVerifyClientCert, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /admission-controller/convert.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | v1 "k8s.io/api/admission/v1" 21 | "k8s.io/api/admission/v1beta1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | ) 24 | 25 | func convertAdmissionRequestToV1(r *v1beta1.AdmissionRequest) *v1.AdmissionRequest { 26 | return &v1.AdmissionRequest{ 27 | Kind: r.Kind, 28 | Namespace: r.Namespace, 29 | Name: r.Name, 30 | Object: r.Object, 31 | Resource: r.Resource, 32 | Operation: v1.Operation(r.Operation), 33 | UID: r.UID, 34 | DryRun: r.DryRun, 35 | OldObject: r.OldObject, 36 | Options: r.Options, 37 | RequestKind: r.RequestKind, 38 | RequestResource: r.RequestResource, 39 | RequestSubResource: r.RequestSubResource, 40 | SubResource: r.SubResource, 41 | UserInfo: r.UserInfo, 42 | } 43 | } 44 | 45 | func convertAdmissionRequestToV1beta1(r *v1.AdmissionRequest) *v1beta1.AdmissionRequest { 46 | return &v1beta1.AdmissionRequest{ 47 | Kind: r.Kind, 48 | Namespace: r.Namespace, 49 | Name: r.Name, 50 | Object: r.Object, 51 | Resource: r.Resource, 52 | Operation: v1beta1.Operation(r.Operation), 53 | UID: r.UID, 54 | DryRun: r.DryRun, 55 | OldObject: r.OldObject, 56 | Options: r.Options, 57 | RequestKind: r.RequestKind, 58 | RequestResource: r.RequestResource, 59 | RequestSubResource: r.RequestSubResource, 60 | SubResource: r.SubResource, 61 | UserInfo: r.UserInfo, 62 | } 63 | } 64 | 65 | func convertAdmissionResponseToV1(r *v1beta1.AdmissionResponse) *v1.AdmissionResponse { 66 | var pt *v1.PatchType 67 | if r.PatchType != nil { 68 | t := v1.PatchType(*r.PatchType) 69 | pt = &t 70 | } 71 | return &v1.AdmissionResponse{ 72 | UID: r.UID, 73 | Allowed: r.Allowed, 74 | AuditAnnotations: r.AuditAnnotations, 75 | Patch: r.Patch, 76 | PatchType: pt, 77 | Result: r.Result, 78 | } 79 | } 80 | 81 | func convertAdmissionResponseToV1beta1(r *v1.AdmissionResponse) *v1beta1.AdmissionResponse { 82 | var pt *v1beta1.PatchType 83 | if r.PatchType != nil { 84 | t := v1beta1.PatchType(*r.PatchType) 85 | pt = &t 86 | } 87 | return &v1beta1.AdmissionResponse{ 88 | UID: r.UID, 89 | Allowed: r.Allowed, 90 | AuditAnnotations: r.AuditAnnotations, 91 | Patch: r.Patch, 92 | PatchType: pt, 93 | Result: r.Result, 94 | } 95 | } 96 | 97 | func toV1AdmissionResponse(err error) *v1.AdmissionResponse { 98 | return &v1.AdmissionResponse{ 99 | Result: &metav1.Status{ 100 | Message: err.Error(), 101 | }, 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /admission-controller/convert_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "math/rand" 22 | "reflect" 23 | "testing" 24 | 25 | fuzz "github.com/google/gofuzz" 26 | 27 | v1 "k8s.io/api/admission/v1" 28 | "k8s.io/api/admission/v1beta1" 29 | "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" 30 | "k8s.io/apimachinery/pkg/runtime" 31 | "k8s.io/apimachinery/pkg/runtime/serializer" 32 | "k8s.io/apimachinery/pkg/util/diff" 33 | admissionfuzzer "k8s.io/kubernetes/pkg/apis/admission/fuzzer" 34 | ) 35 | 36 | func TestConvertAdmissionRequestToV1(t *testing.T) { 37 | f := fuzzer.FuzzerFor(admissionfuzzer.Funcs, rand.NewSource(rand.Int63()), serializer.NewCodecFactory(runtime.NewScheme())) 38 | for i := 0; i < 100; i++ { 39 | t.Run(fmt.Sprintf("Run %d/100", i), func(t *testing.T) { 40 | orig := &v1beta1.AdmissionRequest{} 41 | f.Fuzz(orig) 42 | converted := convertAdmissionRequestToV1(orig) 43 | rt := convertAdmissionRequestToV1beta1(converted) 44 | if !reflect.DeepEqual(orig, rt) { 45 | t.Errorf("expected all request fields to be in converted object but found unaccounted for differences, diff:\n%s", diff.ObjectReflectDiff(orig, converted)) 46 | } 47 | }) 48 | } 49 | } 50 | 51 | func TestConvertAdmissionResponseToV1beta1(t *testing.T) { 52 | f := fuzz.New() 53 | for i := 0; i < 100; i++ { 54 | t.Run(fmt.Sprintf("Run %d/100", i), func(t *testing.T) { 55 | orig := &v1.AdmissionResponse{} 56 | f.Fuzz(orig) 57 | converted := convertAdmissionResponseToV1beta1(orig) 58 | rt := convertAdmissionResponseToV1(converted) 59 | if !reflect.DeepEqual(orig, rt) { 60 | t.Errorf("expected all fields to be in converted object but found unaccounted for differences, diff:\n%s", diff.ObjectReflectDiff(orig, converted)) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /admission-controller/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/amit0701/adm-controller 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/google/ko v0.4.0 7 | k8s.io/api v0.18.0 8 | k8s.io/apiextensions-apiserver v0.18.0 9 | k8s.io/apimachinery v0.18.0 10 | k8s.io/klog v1.0.0 11 | ) 12 | -------------------------------------------------------------------------------- /admission-controller/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "flag" 22 | "fmt" 23 | "io/ioutil" 24 | "net/http" 25 | 26 | v1 "k8s.io/api/admission/v1" 27 | "k8s.io/api/admission/v1beta1" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "k8s.io/klog" 30 | // TODO: try this library to see if it generates correct json patch 31 | // https://github.com/mattbaird/jsonpatch 32 | ) 33 | 34 | var ( 35 | certFile string 36 | keyFile string 37 | port int 38 | sidecarImage string 39 | ) 40 | 41 | func init() { 42 | flag.StringVar(&certFile, "tls-cert-file", "", 43 | "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert).") 44 | flag.StringVar(&keyFile, "tls-private-key-file", "", 45 | "File containing the default x509 private key matching --tls-cert-file.") 46 | flag.IntVar(&port, "port", 443, 47 | "Secure port that the webhook listens on") 48 | flag.StringVar(&sidecarImage, "sidecar-image", "", 49 | "Image to be used as the injected sidecar") 50 | 51 | } 52 | 53 | // admitv1beta1Func handles a v1beta1 admission 54 | type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse 55 | 56 | // admitv1beta1Func handles a v1 admission 57 | type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse 58 | 59 | // admitHandler is a handler, for both validators and mutators, that supports multiple admission review versions 60 | type admitHandler struct { 61 | v1beta1 admitv1beta1Func 62 | v1 admitv1Func 63 | } 64 | 65 | func newDelegateToV1AdmitHandler(f admitv1Func) admitHandler { 66 | return admitHandler{ 67 | v1beta1: delegateV1beta1AdmitToV1(f), 68 | v1: f, 69 | } 70 | } 71 | 72 | func delegateV1beta1AdmitToV1(f admitv1Func) admitv1beta1Func { 73 | return func(review v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { 74 | in := v1.AdmissionReview{Request: convertAdmissionRequestToV1(review.Request)} 75 | out := f(in) 76 | return convertAdmissionResponseToV1beta1(out) 77 | } 78 | } 79 | 80 | // serve handles the http portion of a request prior to handing to an admit 81 | // function 82 | func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) { 83 | var body []byte 84 | if r.Body != nil { 85 | if data, err := ioutil.ReadAll(r.Body); err == nil { 86 | body = data 87 | } 88 | } 89 | 90 | // verify the content type is accurate 91 | contentType := r.Header.Get("Content-Type") 92 | if contentType != "application/json" { 93 | klog.Errorf("contentType=%s, expect application/json", contentType) 94 | return 95 | } 96 | 97 | klog.Info(fmt.Sprintf("handling request: %s", body)) 98 | 99 | deserializer := codecs.UniversalDeserializer() 100 | obj, gvk, err := deserializer.Decode(body, nil, nil) 101 | if err != nil { 102 | msg := fmt.Sprintf("Request could not be decoded: %v", err) 103 | klog.Error(msg) 104 | http.Error(w, msg, http.StatusBadRequest) 105 | return 106 | } 107 | 108 | var responseObj runtime.Object 109 | switch *gvk { 110 | case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"): 111 | requestedAdmissionReview, ok := obj.(*v1beta1.AdmissionReview) 112 | if !ok { 113 | klog.Errorf("Expected v1beta1.AdmissionReview but got: %T", obj) 114 | return 115 | } 116 | responseAdmissionReview := &v1beta1.AdmissionReview{} 117 | responseAdmissionReview.SetGroupVersionKind(*gvk) 118 | responseAdmissionReview.Response = admit.v1beta1(*requestedAdmissionReview) 119 | responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID 120 | responseObj = responseAdmissionReview 121 | case v1.SchemeGroupVersion.WithKind("AdmissionReview"): 122 | requestedAdmissionReview, ok := obj.(*v1.AdmissionReview) 123 | if !ok { 124 | klog.Errorf("Expected v1.AdmissionReview but got: %T", obj) 125 | return 126 | } 127 | responseAdmissionReview := &v1.AdmissionReview{} 128 | responseAdmissionReview.SetGroupVersionKind(*gvk) 129 | responseAdmissionReview.Response = admit.v1(*requestedAdmissionReview) 130 | responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID 131 | responseObj = responseAdmissionReview 132 | default: 133 | msg := fmt.Sprintf("Unsupported group version kind: %v", gvk) 134 | klog.Error(msg) 135 | http.Error(w, msg, http.StatusBadRequest) 136 | return 137 | } 138 | 139 | klog.V(2).Info(fmt.Sprintf("sending response: %v", responseObj)) 140 | respBytes, err := json.Marshal(responseObj) 141 | if err != nil { 142 | klog.Error(err) 143 | http.Error(w, err.Error(), http.StatusInternalServerError) 144 | return 145 | } 146 | w.Header().Set("Content-Type", "application/json") 147 | if _, err := w.Write(respBytes); err != nil { 148 | klog.Error(err) 149 | } 150 | } 151 | 152 | 153 | func serveMutatePods(w http.ResponseWriter, r *http.Request) { 154 | serve(w, r, newDelegateToV1AdmitHandler(mutatePods)) 155 | } 156 | 157 | func serveMutatePodsSidecar(w http.ResponseWriter, r *http.Request) { 158 | serve(w, r, newDelegateToV1AdmitHandler(mutatePodsSidecar)) 159 | } 160 | 161 | 162 | func main() { 163 | 164 | loggingFlags := &flag.FlagSet{} 165 | klog.InitFlags(loggingFlags) 166 | flag.Parse() 167 | 168 | config := Config{ 169 | CertFile: certFile, 170 | KeyFile: keyFile, 171 | } 172 | 173 | http.HandleFunc("/mutating-pods", serveMutatePods) 174 | http.HandleFunc("/mutating-pods-sidecar", serveMutatePodsSidecar) 175 | http.HandleFunc("/readyz", func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("ok")) }) 176 | server := &http.Server{ 177 | Addr: fmt.Sprintf(":%d", port), 178 | TLSConfig: configTLS(config), 179 | } 180 | err := server.ListenAndServeTLS("", "") 181 | if err != nil { 182 | panic(err) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /admission-controller/patch_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "reflect" 23 | "testing" 24 | 25 | jsonpatch "github.com/evanphx/json-patch" 26 | corev1 "k8s.io/api/core/v1" 27 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 | ) 29 | 30 | func TestPatches(t *testing.T) { 31 | sidecarImage = "test-image" 32 | testCases := []struct { 33 | patch string 34 | initial interface{} 35 | expected interface{} 36 | toTest interface{} 37 | }{ 38 | { 39 | patch: configMapPatch1, 40 | initial: corev1.ConfigMap{ 41 | Data: map[string]string{ 42 | "mutation-start": "yes", 43 | }, 44 | }, 45 | expected: &corev1.ConfigMap{ 46 | Data: map[string]string{ 47 | "mutation-start": "yes", 48 | "mutation-stage-1": "yes", 49 | }, 50 | }, 51 | }, 52 | { 53 | patch: configMapPatch2, 54 | initial: corev1.ConfigMap{ 55 | Data: map[string]string{ 56 | "mutation-start": "yes", 57 | }, 58 | }, 59 | expected: &corev1.ConfigMap{ 60 | Data: map[string]string{ 61 | "mutation-start": "yes", 62 | "mutation-stage-2": "yes", 63 | }, 64 | }, 65 | }, 66 | 67 | { 68 | patch: podsInitContainerPatch, 69 | initial: corev1.Pod{ 70 | Spec: corev1.PodSpec{ 71 | InitContainers: []corev1.Container{}, 72 | }, 73 | }, 74 | expected: &corev1.Pod{ 75 | Spec: corev1.PodSpec{ 76 | InitContainers: []corev1.Container{ 77 | { 78 | Image: "webhook-added-image", 79 | Name: "webhook-added-init-container", 80 | Resources: corev1.ResourceRequirements{}, 81 | }, 82 | }, 83 | }, 84 | }, 85 | }, 86 | { 87 | patch: fmt.Sprintf(podsSidecarPatch, sidecarImage), 88 | initial: corev1.Pod{ 89 | Spec: corev1.PodSpec{ 90 | Containers: []corev1.Container{ 91 | { 92 | Image: "image1", 93 | Name: "container1", 94 | Resources: corev1.ResourceRequirements{}, 95 | }, 96 | }, 97 | }, 98 | }, 99 | expected: &corev1.Pod{ 100 | Spec: corev1.PodSpec{ 101 | Containers: []corev1.Container{ 102 | { 103 | Image: "image1", 104 | Name: "container1", 105 | Resources: corev1.ResourceRequirements{}, 106 | }, 107 | { 108 | Image: sidecarImage, 109 | Name: "webhook-added-sidecar", 110 | Resources: corev1.ResourceRequirements{}, 111 | }, 112 | }, 113 | }, 114 | }, 115 | }, 116 | } 117 | for _, testcase := range testCases { 118 | objJS, err := json.Marshal(testcase.initial) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | patchObj, err := jsonpatch.DecodePatch([]byte(testcase.patch)) 123 | if err != nil { 124 | t.Fatal(err) 125 | } 126 | 127 | patchedJS, err := patchObj.Apply(objJS) 128 | if err != nil { 129 | t.Fatal(err) 130 | } 131 | objType := reflect.TypeOf(testcase.initial) 132 | objTest := reflect.New(objType).Interface() 133 | err = json.Unmarshal(patchedJS, objTest) 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | if !reflect.DeepEqual(objTest, testcase.expected) { 138 | t.Errorf("\nexpected %#v\n, got %#v", testcase.expected, objTest) 139 | } 140 | } 141 | 142 | } 143 | 144 | func TestJSONPatchForUnstructured(t *testing.T) { 145 | cr := &unstructured.Unstructured{ 146 | Object: map[string]interface{}{ 147 | "kind": "Something", 148 | "apiVersion": "somegroup/v1", 149 | "data": map[string]interface{}{ 150 | "mutation-start": "yes", 151 | }, 152 | }, 153 | } 154 | crJS, err := json.Marshal(cr) 155 | if err != nil { 156 | t.Fatal(err) 157 | } 158 | 159 | patchObj, err := jsonpatch.DecodePatch([]byte(configMapPatch1)) 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | patchedJS, err := patchObj.Apply(crJS) 164 | if err != nil { 165 | t.Fatal(err) 166 | } 167 | patchedObj := unstructured.Unstructured{} 168 | err = json.Unmarshal(patchedJS, &patchedObj) 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | expectedData := map[string]interface{}{ 173 | "mutation-start": "yes", 174 | "mutation-stage-1": "yes", 175 | } 176 | 177 | if !reflect.DeepEqual(patchedObj.Object["data"], expectedData) { 178 | t.Errorf("\nexpected %#v\n, got %#v", expectedData, patchedObj.Object["data"]) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /admission-controller/pods.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | "strconv" 23 | 24 | "k8s.io/api/admission/v1" 25 | corev1 "k8s.io/api/core/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/klog" 28 | ) 29 | 30 | const ( 31 | podsSidecarPatch string = `[ 32 | {"op":"add", "path":"/spec/containers/-","value":{"image":"%v","name":"webhook-added-sidecar","volumeMounts":[{"name":"vol","mountPath":"/tmp"}],"resources":{}}} 33 | ]` 34 | ) 35 | 36 | var podsInitContainerPatch string = `[ 37 | {"op":"add","path":"/spec/initContainers/0","value":{"image":"%v","name":"secrets-init-container","imagePullPolicy": "Always","volumeMounts":[{"name":"secret-vol","mountPath":"/tmp"}],"env":[{"name": "SECRET_ARN","valueFrom": {"fieldRef": {"fieldPath": "metadata.annotations['secrets.k8s.aws/secret-arn']"}}}` 38 | 39 | func admitPods(ar v1.AdmissionReview) *v1.AdmissionResponse { 40 | klog.V(2).Info("admitting pods") 41 | podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} 42 | if ar.Request.Resource != podResource { 43 | err := fmt.Errorf("expect resource to be %s", podResource) 44 | klog.Error(err) 45 | return toV1AdmissionResponse(err) 46 | } 47 | 48 | raw := ar.Request.Object.Raw 49 | pod := corev1.Pod{} 50 | deserializer := codecs.UniversalDeserializer() 51 | if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil { 52 | klog.Error(err) 53 | return toV1AdmissionResponse(err) 54 | } 55 | reviewResponse := v1.AdmissionResponse{} 56 | reviewResponse.Allowed = true 57 | 58 | var msg string 59 | if v, ok := pod.Labels["webhook-e2e-test"]; ok { 60 | if v == "webhook-disallow" { 61 | reviewResponse.Allowed = false 62 | msg = msg + "the pod contains unwanted label; " 63 | } 64 | if v == "wait-forever" { 65 | reviewResponse.Allowed = false 66 | msg = msg + "the pod response should not be sent; " 67 | <-make(chan int) // Sleep forever - no one sends to this channel 68 | } 69 | } 70 | for _, container := range pod.Spec.Containers { 71 | if strings.Contains(container.Name, "webhook-disallow") { 72 | reviewResponse.Allowed = false 73 | msg = msg + "the pod contains unwanted container name; " 74 | } 75 | } 76 | if !reviewResponse.Allowed { 77 | reviewResponse.Result = &metav1.Status{Message: strings.TrimSpace(msg)} 78 | } 79 | return &reviewResponse 80 | } 81 | 82 | func mutatePods(ar v1.AdmissionReview) *v1.AdmissionResponse { 83 | shouldPatchPod := func(pod *corev1.Pod) bool { 84 | _, arn_ok := pod.ObjectMeta.Annotations["secrets.k8s.aws/secret-arn"] 85 | if arn_ok == false { 86 | return false 87 | } 88 | 89 | if len(pod.Spec.InitContainers) == 0 { 90 | podsInitContainerPatch = `[ 91 | {"op":"add","path":"/spec/initContainers","value":[{"image":"%v","name":"secrets-init-container","imagePullPolicy": "Always","volumeMounts":[{"name":"secret-vol","mountPath":"/tmp"}],"env":[{"name": "SECRET_ARN","valueFrom": {"fieldRef": {"fieldPath": "metadata.annotations['secrets.k8s.aws/secret-arn']"}}}` 92 | } 93 | return !hasContainer(pod.Spec.InitContainers, "secrets-init-container") 94 | } 95 | return applyPodPatch(ar, shouldPatchPod, fmt.Sprintf(podsInitContainerPatch, sidecarImage)) 96 | } 97 | 98 | func mutatePodsSidecar(ar v1.AdmissionReview) *v1.AdmissionResponse { 99 | if sidecarImage == "" { 100 | return &v1.AdmissionResponse{ 101 | Allowed: false, 102 | Result: &metav1.Status{ 103 | Status: "Failure", 104 | Message: "No image specified by the sidecar-image parameter", 105 | Code: 500, 106 | }, 107 | } 108 | } 109 | shouldPatchPod := func(pod *corev1.Pod) bool { 110 | return !hasContainer(pod.Spec.Containers, "webhook-added-sidecar") 111 | } 112 | return applyPodPatch(ar, shouldPatchPod, fmt.Sprintf(podsSidecarPatch, sidecarImage)) 113 | } 114 | 115 | func hasContainer(containers []corev1.Container, containerName string) bool { 116 | for _, container := range containers { 117 | if container.Name == containerName { 118 | return true 119 | } 120 | } 121 | return false 122 | } 123 | 124 | 125 | func applyPodPatch(ar v1.AdmissionReview, shouldPatchPod func(*corev1.Pod) bool, patch string) *v1.AdmissionResponse { 126 | klog.V(2).Info("mutating pods") 127 | podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} 128 | if ar.Request.Resource != podResource { 129 | klog.Errorf("expect resource to be %s", podResource) 130 | return nil 131 | } 132 | raw := ar.Request.Object.Raw 133 | pod := corev1.Pod{} 134 | deserializer := codecs.UniversalDeserializer() 135 | if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil { 136 | klog.Error(err) 137 | return toV1AdmissionResponse(err) 138 | } 139 | reviewResponse := v1.AdmissionResponse{} 140 | reviewResponse.Allowed = true 141 | if shouldPatchPod(&pod) { 142 | mount_path ,mount_path_ok := pod.ObjectMeta.Annotations["secrets.k8s.aws/mount-path"] 143 | secret_filename ,secret_filename_ok := pod.ObjectMeta.Annotations["secrets.k8s.aws/secret-filename"] 144 | var path = "{\"op\": \"add\",\"path\": \"/spec/containers/" 145 | var value = "/volumeMounts/-\",\"value\": {\"mountPath\": \"/tmp/\",\"name\": \"secret-vol\"}}" 146 | if mount_path_ok == true { 147 | value = "/volumeMounts/-\",\"value\": {\"mountPath\":" + "\"" + mount_path +"\""+ ",\"name\": \"secret-vol\"}}" 148 | } 149 | var vol_mounts = "" 150 | for i, _ := range pod.Spec.Containers { 151 | if i == 0 { 152 | vol_mounts = path + strconv.Itoa(i) + value 153 | } else { 154 | vol_mounts = vol_mounts + "," + path + strconv.Itoa(i) + value 155 | } 156 | } 157 | if secret_filename_ok == true { 158 | patch = patch + ",{\"name\":\"SECRET_FILENAME\",\"value\":"+ "\"" + secret_filename + "\"}" 159 | } 160 | if len(pod.Spec.InitContainers) == 0 { 161 | patch = patch + `],"resources":{}}]},{"op":"add","path":"/spec/volumes/-","value":{"emptyDir": {"medium": "Memory"},"name": "secret-vol"}}` + "," + vol_mounts + "]" 162 | } else { 163 | patch = patch + `],"resources":{}}},{"op":"add","path":"/spec/volumes/-","value":{"emptyDir": {"medium": "Memory"},"name": "secret-vol"}}` + "," + vol_mounts + "]" 164 | } 165 | reviewResponse.Patch = []byte(patch) 166 | pt := v1.PatchTypeJSONPatch 167 | reviewResponse.PatchType = &pt 168 | klog.Info(patch) 169 | } 170 | // klog.Info(&reviewResponse) 171 | return &reviewResponse 172 | } 173 | 174 | // denySpecificAttachment denies `kubectl attach to-be-attached-pod -i -c=container1" 175 | // or equivalent client requests. 176 | func denySpecificAttachment(ar v1.AdmissionReview) *v1.AdmissionResponse { 177 | klog.V(2).Info("handling attaching pods") 178 | if ar.Request.Name != "to-be-attached-pod" { 179 | return &v1.AdmissionResponse{Allowed: true} 180 | } 181 | podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} 182 | if e, a := podResource, ar.Request.Resource; e != a { 183 | err := fmt.Errorf("expect resource to be %s, got %s", e, a) 184 | klog.Error(err) 185 | return toV1AdmissionResponse(err) 186 | } 187 | if e, a := "attach", ar.Request.SubResource; e != a { 188 | err := fmt.Errorf("expect subresource to be %s, got %s", e, a) 189 | klog.Error(err) 190 | return toV1AdmissionResponse(err) 191 | } 192 | 193 | raw := ar.Request.Object.Raw 194 | podAttachOptions := corev1.PodAttachOptions{} 195 | deserializer := codecs.UniversalDeserializer() 196 | if _, _, err := deserializer.Decode(raw, nil, &podAttachOptions); err != nil { 197 | klog.Error(err) 198 | return toV1AdmissionResponse(err) 199 | } 200 | klog.V(2).Info(fmt.Sprintf("podAttachOptions=%#v\n", podAttachOptions)) 201 | if !podAttachOptions.Stdin || podAttachOptions.Container != "container1" { 202 | return &v1.AdmissionResponse{Allowed: true} 203 | } 204 | return &v1.AdmissionResponse{ 205 | Allowed: false, 206 | Result: &metav1.Status{ 207 | Message: "attaching to pod 'to-be-attached-pod' is not allowed", 208 | }, 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /admission-controller/scheme.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | admissionv1 "k8s.io/api/admission/v1" 21 | admissionv1beta1 "k8s.io/api/admission/v1beta1" 22 | admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 23 | admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/runtime/serializer" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | ) 29 | 30 | var scheme = runtime.NewScheme() 31 | var codecs = serializer.NewCodecFactory(scheme) 32 | 33 | func init() { 34 | addToScheme(scheme) 35 | } 36 | 37 | func addToScheme(scheme *runtime.Scheme) { 38 | utilruntime.Must(corev1.AddToScheme(scheme)) 39 | utilruntime.Must(admissionv1beta1.AddToScheme(scheme)) 40 | utilruntime.Must(admissionregistrationv1beta1.AddToScheme(scheme)) 41 | utilruntime.Must(admissionv1.AddToScheme(scheme)) 42 | utilruntime.Must(admissionregistrationv1.AddToScheme(scheme)) 43 | } 44 | -------------------------------------------------------------------------------- /admission-controller/secret-inject/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: secret-inject 3 | description: A Helm chart for installing AWS Secret Controller webhook 4 | version: 0.1.1 5 | appVersion: 1.0 6 | 7 | -------------------------------------------------------------------------------- /admission-controller/secret-inject/python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | ADD . / 4 | 5 | RUN apt-get update -y && \ 6 | apt-get install -y python-pip python-dev 7 | 8 | RUN pip install --upgrade pip && pip install -r requirements.txt 9 | 10 | ENTRYPOINT [ "python" ] 11 | 12 | CMD [ "admission_controller.py" ] -------------------------------------------------------------------------------- /admission-controller/secret-inject/python/README.md: -------------------------------------------------------------------------------- 1 | # Admission controller in python 2 | 3 | This python code provides the basic example of writing an admission controller running as a flask based container. 4 | 5 | The directory contains the sample python code and a `Dockerfile` to containerize the code into a flask application. 6 | 7 | -------------------------------------------------------------------------------- /admission-controller/secret-inject/python/admission_controller.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import os 4 | import boto3 5 | from flask import jsonify, Flask, request 6 | 7 | app = Flask(__name__) 8 | 9 | @app.route("/mutating-pods", methods=["POST"]) 10 | def mutation(): 11 | review = request.get_json() 12 | #app.logger.info("Mutating AdmissionReview request: %s", json.dumps(review, indent=4)) 13 | 14 | annotations = review['request']['object']['metadata']['annotations'] 15 | app.logger.info("Annotations on the pod are: %s",annotations) 16 | 17 | response = {} 18 | 19 | # Only allow if there are valid annotationn 20 | if 'secrets.k8s.aws/sidecarInjectorWebhook' and 'secrets.k8s.aws/secret-arn' not in list(annotations): 21 | app.logger.info("Nothing to do because of missing annotations ...") 22 | else: 23 | app.logger.info("Annotations present ...") 24 | app.logger.info("Injecting init container to the pod definition ...") 25 | response = secrets_initcont_patch(annotations,response) 26 | 27 | response['allowed'] = True 28 | review['response'] = response 29 | #app.logger.info("Mutating AdmissionReview request: %s", review) 30 | return jsonify(review), 200 31 | 32 | # Prepare the patch and compose the response json 33 | def secrets_initcont_patch(annotations,response): 34 | 35 | patch = [ 36 | { 37 | "op": "add", 38 | "path": "/spec/initContainers", 39 | "value": [ 40 | { 41 | "image": "%v", 42 | "name": "secrets-init-container", 43 | "volumeMounts": [ 44 | { 45 | "name": "secret-vol", 46 | "mountPath": "/tmp" 47 | } 48 | ], 49 | "env": [ 50 | { 51 | "name": "SECRET_ARN", 52 | "valueFrom": { 53 | "fieldRef": { 54 | "fieldPath": "metadata.annotations['secrets.k8s.aws/secret-arn']" 55 | } 56 | } 57 | } 58 | ], 59 | "resources": {} 60 | } 61 | ] 62 | }, 63 | { 64 | "op": "add", 65 | "path": "/spec/volumes/-", 66 | "value": 67 | { 68 | "emptyDir": 69 | { 70 | "medium": "Memory" 71 | }, 72 | "name": "secret-vol" 73 | } 74 | } 75 | ] 76 | 77 | response['patch'] = base64.b64encode(json.dumps(patch)) 78 | response['patchType'] = 'application/json-patch+json' 79 | 80 | return response 81 | 82 | # Enabling TLS for the flask application 83 | context = ( 84 | os.environ.get("WEBHOOK_CERT", "/tls/tls.crt"), 85 | os.environ.get("WEBHOOK_KEY", "/tls/tls.key"), 86 | ) 87 | app.run(host='0.0.0.0', port='443', debug=False, ssl_context=context) -------------------------------------------------------------------------------- /admission-controller/secret-inject/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "secret-inject.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 6 | {{- end -}} 7 | 8 | 9 | {{/* 10 | Generate certificates for AWS Secrets Controller webhook 11 | */}} 12 | {{- define "secret-inject.gen-certs" -}} 13 | {{- $altNames := "secret-inject.default.svc" -}} 14 | {{- $ca := genCA "secret-inject-ca" 3650 -}} 15 | {{- $cert := genSignedCert (printf "secret-inject.%s.svc" .Release.Namespace) nil nil 3650 $ca -}} 16 | caCert: {{ $ca.Cert | b64enc }} 17 | clientCert: {{ $cert.Cert | b64enc }} 18 | clientKey: {{ $cert.Key | b64enc }} 19 | {{- end -}} 20 | 21 | -------------------------------------------------------------------------------- /admission-controller/secret-inject/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: "secret-inject" 6 | name: "secret-inject" 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | run: "secret-inject" 12 | template: 13 | metadata: 14 | labels: 15 | run : "secret-inject" 16 | spec: 17 | volumes: 18 | - name: certs 19 | secret: 20 | secretName: "secret-inject-tls" 21 | containers: 22 | - name: "secret-inject-init" 23 | image: "public.ecr.aws/aws-containers/aws-secrets-manager-secret-adm-controller:v0.1.5" 24 | volumeMounts: 25 | - name: certs 26 | mountPath: /tls 27 | readOnly: true 28 | args: 29 | - "--tls-cert-file=/tls/tls.crt" 30 | - "--tls-private-key-file=/tls/tls.key" 31 | - "--sidecar-image=public.ecr.aws/aws-containers/aws-secrets-manager-secret-sidecar:v0.1.4" 32 | ports: 33 | - containerPort: 443 34 | imagePullPolicy: Always 35 | -------------------------------------------------------------------------------- /admission-controller/secret-inject/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: "secret-inject" 5 | spec: 6 | type: ClusterIP 7 | ports: 8 | - port: 443 9 | protocol: TCP 10 | targetPort: 443 11 | selector: 12 | run: "secret-inject" 13 | -------------------------------------------------------------------------------- /admission-controller/secret-inject/templates/webhook.yaml: -------------------------------------------------------------------------------- 1 | {{ $tls := fromYaml ( include "secret-inject.gen-certs" . ) }} 2 | --- 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: aws-secret-inject 7 | webhooks: 8 | - name: aws-secret-inject.aws.amazon.com 9 | clientConfig: 10 | service: 11 | name: "secret-inject" 12 | namespace: {{ .Release.Namespace }} 13 | path: "/mutating-pods" 14 | caBundle: {{ $tls.caCert }} 15 | rules: 16 | - operations: ["CREATE","UPDATE"] 17 | apiGroups: [""] 18 | apiVersions: ["v1"] 19 | resources: ["pods"] 20 | failurePolicy: Ignore 21 | admissionReviewVersions: ["v1beta1"] 22 | timeoutSeconds: 5 23 | --- 24 | apiVersion: v1 25 | kind: Secret 26 | metadata: 27 | name: "secret-inject-tls" 28 | type: kubernetes.io/tls 29 | data: 30 | tls.crt: {{ $tls.clientCert }} 31 | tls.key: {{ $tls.clientKey }} 32 | -------------------------------------------------------------------------------- /admission-controller/secret-inject/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for secrets-inject 2 | # This is a YAML formatted file 3 | # Declare variables to be passed into your templates 4 | 5 | replicaCount: 1 6 | nameOveride: "" 7 | -------------------------------------------------------------------------------- /cmd/aws-secrets-manager/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/aws/arn" 12 | "github.com/aws/aws-sdk-go/aws/awserr" 13 | "github.com/aws/aws-sdk-go/aws/session" 14 | "github.com/aws/aws-sdk-go/service/secretsmanager" 15 | ) 16 | 17 | func main() { 18 | secretArn := os.Getenv("SECRET_ARN") 19 | secretFilename := os.Getenv("SECRET_FILENAME") 20 | var AWSRegion string 21 | 22 | if arn.IsARN(secretArn) { 23 | arnobj, _ := arn.Parse(secretArn) 24 | AWSRegion = arnobj.Region 25 | } else { 26 | log.Println("Not a valid ARN") 27 | os.Exit(1) 28 | } 29 | 30 | sess, err := session.NewSession() 31 | if err != nil { 32 | log.Panic(err) 33 | } 34 | svc := secretsmanager.New(sess, &aws.Config{ 35 | Region: aws.String(AWSRegion), 36 | }) 37 | 38 | input := &secretsmanager.GetSecretValueInput{ 39 | SecretId: aws.String(secretArn), 40 | VersionStage: aws.String("AWSCURRENT"), 41 | } 42 | 43 | result, err := svc.GetSecretValue(input) 44 | if err != nil { 45 | if aerr, ok := err.(awserr.Error); ok { 46 | switch aerr.Code() { 47 | case secretsmanager.ErrCodeResourceNotFoundException: 48 | fmt.Println(secretsmanager.ErrCodeResourceNotFoundException, aerr.Error()) 49 | case secretsmanager.ErrCodeInvalidParameterException: 50 | fmt.Println(secretsmanager.ErrCodeInvalidParameterException, aerr.Error()) 51 | case secretsmanager.ErrCodeInvalidRequestException: 52 | fmt.Println(secretsmanager.ErrCodeInvalidRequestException, aerr.Error()) 53 | case secretsmanager.ErrCodeDecryptionFailure: 54 | fmt.Println(secretsmanager.ErrCodeDecryptionFailure, aerr.Error()) 55 | case secretsmanager.ErrCodeInternalServiceError: 56 | fmt.Println(secretsmanager.ErrCodeInternalServiceError, aerr.Error()) 57 | default: 58 | fmt.Println(aerr.Error()) 59 | } 60 | } else { 61 | // Print the error, cast err to awserr.Error to get the Code and 62 | // Message from an error. 63 | log.Println(err.Error()) 64 | } 65 | return 66 | } 67 | // Decrypts secret using the associated KMS CMK. 68 | // Depending on whether the secret is a string or binary, one of these fields will be populated. 69 | var secretString, decodedBinarySecret string 70 | if result.SecretString != nil { 71 | secretString = *result.SecretString 72 | writeOutput(secretString, secretFilename) 73 | } else { 74 | decodedBinarySecretBytes := make([]byte, base64.StdEncoding.DecodedLen(len(result.SecretBinary))) 75 | len, err := base64.StdEncoding.Decode(decodedBinarySecretBytes, result.SecretBinary) 76 | if err != nil { 77 | log.Println("Base64 Decode Error:", err) 78 | return 79 | } 80 | decodedBinarySecret = string(decodedBinarySecretBytes[:len]) 81 | writeOutput(decodedBinarySecret, secretFilename) 82 | } 83 | } 84 | func writeOutput(output string, name string) error { 85 | mountPoint := "/tmp" 86 | dir, file := filepath.Split(name) 87 | if file == "" { 88 | file = "secret" 89 | } 90 | err := os.MkdirAll(mountPoint + dir, os.ModePerm) 91 | if err != nil { 92 | return fmt.Errorf("error creating directory, %w", err) 93 | } 94 | if filepath.IsAbs(filepath.Join(mountPoint + dir, file)) { 95 | f, err := os.Create(filepath.Join(mountPoint + dir, file)) 96 | defer f.Close() 97 | if err != nil { 98 | return fmt.Errorf("error creating file, %w", err) 99 | } 100 | f.WriteString(output) 101 | return nil 102 | } 103 | return fmt.Errorf("not a valid file path") 104 | } -------------------------------------------------------------------------------- /cmd/aws-secrets-manager/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestWriteOutput(t *testing.T) { 6 | err := writeOutput("super secret secret", "") 7 | if err != nil { 8 | t.Errorf("an error occurred: %v", err) 9 | } 10 | err = writeOutput("another secret", "/secrets/aaaaa") 11 | if err != nil { 12 | t.Errorf("an error occurred: %v", err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ecs-task-def/task-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "ipcMode": null, 3 | "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole", 4 | "containerDefinitions": [ 5 | { 6 | "dnsSearchDomains": null, 7 | "logConfiguration": { 8 | "logDriver": "awslogs", 9 | "secretOptions": null, 10 | "options": { 11 | "awslogs-group": "/ecs/secret-sidecar", 12 | "awslogs-region": "us-east-1", 13 | "awslogs-stream-prefix": "ecs" 14 | } 15 | }, 16 | "entryPoint": null, 17 | "portMappings": [], 18 | "command": null, 19 | "linuxParameters": null, 20 | "cpu": 0, 21 | "environment": [ 22 | { 23 | "name": "SECRET_ARN", 24 | "value": "arn:aws:secretsmanager:us-west-2:123456789012:secret:catsndogs/unicorn-8VPgCY" 25 | } 26 | ], 27 | "resourceRequirements": null, 28 | "ulimits": null, 29 | "dnsServers": null, 30 | "mountPoints": [ 31 | { 32 | "readOnly": null, 33 | "containerPath": "/tmp", 34 | "sourceVolume": "secret-vol" 35 | } 36 | ], 37 | "workingDirectory": null, 38 | "secrets": null, 39 | "dockerSecurityOptions": null, 40 | "memory": 128, 41 | "memoryReservation": null, 42 | "volumesFrom": [], 43 | "stopTimeout": null, 44 | "image": "public.ecr.aws/aws-containers/aws-secrets-manager-secret-sidecar:v0.1.4", 45 | "startTimeout": null, 46 | "firelensConfiguration": null, 47 | "dependsOn": null, 48 | "disableNetworking": null, 49 | "interactive": null, 50 | "healthCheck": null, 51 | "essential": false, 52 | "links": null, 53 | "hostname": null, 54 | "extraHosts": null, 55 | "pseudoTerminal": null, 56 | "user": null, 57 | "readonlyRootFilesystem": null, 58 | "dockerLabels": null, 59 | "systemControls": null, 60 | "privileged": null, 61 | "name": "secret-sidecar" 62 | }, 63 | { 64 | "dnsSearchDomains": null, 65 | "logConfiguration": { 66 | "logDriver": "awslogs", 67 | "secretOptions": null, 68 | "options": { 69 | "awslogs-group": "/ecs/secret-sidecar", 70 | "awslogs-region": "us-east-1", 71 | "awslogs-stream-prefix": "ecs" 72 | } 73 | }, 74 | "entryPoint": null, 75 | "portMappings": [], 76 | "command": [ 77 | "sh", 78 | "-c", 79 | "echo $(cat /tmp/secret) && sleep 3600" 80 | ], 81 | "linuxParameters": null, 82 | "cpu": 0, 83 | "environment": [], 84 | "resourceRequirements": null, 85 | "ulimits": null, 86 | "dnsServers": null, 87 | "mountPoints": [ 88 | { 89 | "readOnly": null, 90 | "containerPath": "/tmp", 91 | "sourceVolume": "secret-vol" 92 | } 93 | ], 94 | "workingDirectory": null, 95 | "secrets": null, 96 | "dockerSecurityOptions": null, 97 | "memory": 128, 98 | "memoryReservation": null, 99 | "volumesFrom": [], 100 | "stopTimeout": null, 101 | "image": "busybox:1.28", 102 | "startTimeout": null, 103 | "firelensConfiguration": null, 104 | "dependsOn": [ 105 | { 106 | "containerName": "secret-sidecar", 107 | "condition": "COMPLETE" 108 | } 109 | ], 110 | "disableNetworking": null, 111 | "interactive": null, 112 | "healthCheck": null, 113 | "essential": true, 114 | "links": null, 115 | "hostname": null, 116 | "extraHosts": null, 117 | "pseudoTerminal": null, 118 | "user": null, 119 | "readonlyRootFilesystem": null, 120 | "dockerLabels": null, 121 | "systemControls": null, 122 | "privileged": null, 123 | "name": "busybox" 124 | } 125 | ], 126 | "placementConstraints": [], 127 | "memory": null, 128 | "taskRoleArn": "arn:aws:iam::123456789012:role/TaskSecretRole", 129 | "compatibilities": [ 130 | "EC2" 131 | ], 132 | "taskDefinitionArn": "arn:aws:ecs:us-east-1:123456789012:task-definition/secret-sidecar:", 133 | "family": "secret-sidecar", 134 | "pidMode": null, 135 | "requiresCompatibilities": [ 136 | "EC2" 137 | ], 138 | "networkMode": "bridge", 139 | "cpu": null, 140 | "revision": 6, 141 | "status": "ACTIVE", 142 | "inferenceAccelerators": null, 143 | "proxyConfiguration": null, 144 | "volumes": [ 145 | { 146 | "name": "secret-vol", 147 | "host": null, 148 | "dockerVolumeConfiguration": { 149 | "autoprovision": null, 150 | "labels": null, 151 | "scope": "task", 152 | "driver": "local", 153 | "driverOpts": null 154 | } 155 | } 156 | ] 157 | } 158 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.30.27 7 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK0= 2 | github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 6 | github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= 7 | github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 8 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 13 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 14 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 15 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 16 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= 17 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 18 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 19 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 21 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 23 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 24 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 25 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 26 | -------------------------------------------------------------------------------- /kubernetes-manifests/webserver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | run: webserver 7 | name: webserver 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | run: webserver 13 | template: 14 | metadata: 15 | annotations: 16 | secrets.k8s.aws/secret-arn: arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF 17 | ## secrets.k8s.aws/mount-path: /my-secret-path (Optional) 18 | ## secrets.k8s.aws/secret-filename: my-secret-filename (Optional) 19 | labels: 20 | run: webserver 21 | spec: 22 | serviceAccountName: webserver-service-account 23 | containers: 24 | - image: busybox:1.28 25 | name: webserver 26 | command: ['sh', '-c', 'echo $(cat /tmp/secret) && sleep 3600'] 27 | -------------------------------------------------------------------------------- /secret-operator/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM amazonlinux as builder 3 | RUN yum update -y && yum install go -y 4 | 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY main.go main.go 16 | COPY api/ api/ 17 | COPY controllers/ controllers/ 18 | 19 | # Build 20 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go 21 | 22 | # Use distroless as minimal base image to package the manager binary 23 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 24 | FROM gcr.io/distroless/static:nonroot 25 | WORKDIR / 26 | COPY --from=builder /workspace/manager . 27 | USER nonroot:nonroot 28 | 29 | ENTRYPOINT ["/manager"] 30 | -------------------------------------------------------------------------------- /secret-operator/Makefile: -------------------------------------------------------------------------------- 1 | # Image URL to use all building/pushing image targets 2 | IMG ?= controller:latest 3 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 4 | CRD_OPTIONS ?= "crd:trivialVersions=true" 5 | OIDC_PROVIDER=$(shell aws eks describe-cluster --name $$(kubectl config current-context | cut -d "/" -f 2) --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///") 6 | 7 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 8 | ifeq (,$(shell go env GOBIN)) 9 | GOBIN=$(shell go env GOPATH)/bin 10 | else 11 | GOBIN=$(shell go env GOBIN) 12 | endif 13 | 14 | all: manager 15 | 16 | test1: 17 | @echo ${IMG} 18 | 19 | init: 20 | cp config/manager/kustomization_bkup.yaml config/manager/kustomization.yaml 21 | cp config/manager/manager_bkup.yaml config/manager/manager.yaml 22 | rm config/manager/*.bak 23 | 24 | install: 25 | echo "Creating the AWS resources..." 26 | aws cloudformation create-stack --stack-name EKS-Secrets-Operator-Stack --template-body file://cfn.yaml --parameters ParameterKey=OIDCPROVIDER,ParameterValue=${OIDC_PROVIDER} --capabilities CAPABILITY_NAMED_IAM 27 | aws cloudformation wait stack-create-complete --stack-name EKS-Secrets-Operator-Stack 28 | echo "Stack creation complete, the output values are..." 29 | aws cloudformation describe-stacks --stack-name EKS-Secrets-Operator-Stack --query "Stacks[0].Outputs" 30 | make set_aws_parameters 31 | 32 | set_aws_parameters: 33 | echo "Preparing the controller manager manifest files..." 34 | $(info OPERATOR_REGION=$(shell aws cloudformation describe-stacks --stack-name EKS-Secrets-Operator-Stack --query "Stacks[0].Outputs[?OutputKey=='Region'].OutputValue" --output text)) 35 | $(info SQS_URL=$(shell aws cloudformation describe-stacks --stack-name EKS-Secrets-Operator-Stack --query "Stacks[0].Outputs[?OutputKey=='QueueURL'].OutputValue" --output text)) 36 | $(info IAM_ARN=$(shell aws cloudformation describe-stacks --stack-name EKS-Secrets-Operator-Stack --query "Stacks[0].Outputs[?OutputKey=='IAMRole'].OutputValue" --output text)) 37 | sed -i .bak "s,OPERATOR_REGION,${OPERATOR_REGION},g" config/manager/manager.yaml 38 | sed -i .bak "s,SQS_URL,${SQS_URL},g" config/manager/manager.yaml 39 | sed -i .bak "s,IAM_ARN,${IAM_ARN},g" config/manager/manager.yaml 40 | make install_k8s 41 | 42 | # Install CRDs into a cluster 43 | install_k8s: manifests 44 | echo "Creating the required k8s resources..." 45 | kustomize build config/crd | kubectl apply -f - 46 | make deploy IMG=public.ecr.aws/aws-containers/aws-secrets-manager-secret-rotator:v1 47 | 48 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 49 | deploy: manifests 50 | echo "Deploying the controller using the pre-built registry image..." 51 | cd config/manager && kustomize edit set image controller=${IMG} 52 | kustomize build config/default | kubectl apply -f - 53 | 54 | # Provisions a sample deployment and SecretsRotationMapping 55 | test_operator: 56 | echo "Creating test deployment - operatortest..." 57 | kubectl create -f config/samples/awssecretsoperator_v1_secretsrotationmapping.yaml 58 | kubectl create -f config/samples/deployment.yaml 59 | 60 | # Delete the k8s resources and the cloudformation stack resources 61 | delete: 62 | echo "Cleaning up the k8s and aws resources..." 63 | kubectl delete -f config/samples/deployment.yaml 64 | kubectl delete -f config/samples/awssecretsoperator_v1_secretsrotationmapping.yaml 65 | kustomize build config/default | kubectl delete -f - 66 | aws cloudformation delete-stack --stack-name EKS-Secrets-Operator-Stack 67 | aws cloudformation wait stack-delete-complete --stack-name EKS-Secrets-Operator-Stack 68 | make init 69 | 70 | # Uninstall CRDs from a cluster 71 | uninstall: manifests 72 | kustomize build config/crd | kubectl delete -f - 73 | 74 | # Run tests 75 | test: generate fmt vet manifests 76 | go test ./... -coverprofile cover.out 77 | 78 | # Build manager binary 79 | manager: generate fmt vet 80 | go build -o bin/manager main.go 81 | 82 | # Run against the configured Kubernetes cluster in ~/.kube/config 83 | run: generate fmt vet manifests 84 | go run ./main.go 85 | 86 | # Generate manifests e.g. CRD, RBAC etc. 87 | manifests: controller-gen 88 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 89 | 90 | # Run go fmt against code 91 | fmt: 92 | go fmt ./... 93 | 94 | # Run go vet against code 95 | vet: 96 | go vet ./... 97 | 98 | # Generate code 99 | generate: controller-gen 100 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 101 | 102 | # Build the docker image 103 | docker-build: test 104 | docker build . -t ${IMG} 105 | 106 | # Push the docker image 107 | docker-push: 108 | docker push ${IMG} 109 | 110 | # find or download controller-gen 111 | # download controller-gen if necessary 112 | controller-gen: 113 | ifeq (, $(shell which controller-gen)) 114 | @{ \ 115 | set -e ;\ 116 | CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ 117 | cd $$CONTROLLER_GEN_TMP_DIR ;\ 118 | go mod init tmp ;\ 119 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\ 120 | rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ 121 | } 122 | CONTROLLER_GEN=$(GOBIN)/controller-gen 123 | else 124 | CONTROLLER_GEN=$(shell which controller-gen) 125 | endif 126 | -------------------------------------------------------------------------------- /secret-operator/PROJECT: -------------------------------------------------------------------------------- 1 | domain: secretoperator 2 | repo: secretoperator 3 | resources: 4 | - group: awssecretsoperator 5 | kind: SecretsRotationMapping 6 | version: v1 7 | version: "2" 8 | -------------------------------------------------------------------------------- /secret-operator/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This project helps users to automatically redeploy the pods running on Amazon EKS cluster when the secrets in AWS Secerets Manager is rotated. When the pods are restarted, webhook in our previous [blog](https://aws.amazon.com/blogs/containers/aws-secrets-controller-poc/) will retrive the latest secret and mount it onto the pods. 3 | 4 | ![GitHub Logo](blog3.jpg) 5 | 6 | ## Pre-requisites 7 | 8 | * Install Kubebuilder from the [official documentation](https://book.kubebuilder.io/quick-start.html#installation) 9 | * The controller components are written in Go language. Ensure you have [go installed](https://golang.org/doc/install) in your system 10 | * [Kustomize](https://kubernetes-sigs.github.io/kustomize/installation/) 11 | * [AWS Account](https://aws.amazon.com/) with sufficient access privileges to create resources such as Amazon SQS queue, EventBridge rule 12 | 13 | ## Deploying the operator along with the CRD 14 | 15 | EKS Operator for automatic secrets rotation can be deployed following the series of steps outlined below: 16 | 17 | 1. Clone the github repo into the project path: 18 | 19 | ``` 20 | git clone https://github.com/aws-samples/aws-secret-sidecar-injector && cd secretoperator 21 | ``` 22 | 23 | 2. We need an AWS EventBridge Rule which captures the AWS Secret Manager secrets update API calls and publishes them to an Amazon SQS queue. The secrets controller reads these events and responds by restarting the corresponding Deployment. We have included a CloudFormation template in the repo which creates all of these AWS resources for us. 24 | 25 | Once the resources are created, we create the CRD resources which maps the Kubernetes deployment names to the AWS Secrets Manager secret name. 26 | 27 | Run the following make command to complete this setup. 28 | 29 | ``` 30 | make install 31 | ``` 32 | 33 | This setup also deploys the controller on the cluster using the registry image - public.ecr.aws/aws-containers/aws-secrets-manager-secret-rotator. 34 | 35 | ## Testing the solution 36 | 37 | Now that we have deployed all the building blocks required, let’s test the operator. 38 | 39 | 1. Create a sample CRD and deployment in the default namespace. 40 | 41 | In this example we’re going to create a CRD which maps the secret eks-controller-test-secret to a deployment named nginx. 42 | 43 | ``` 44 | make test_operator 45 | ``` 46 | 47 | When the controller detects any update calls on this secret id, it will scan for all the deployments with the matching labels and restart the deployment pods which will allow them to pick up the new secret value. In this case, the nginx pods with the label environment: operatortest will be restarted 48 | 49 | 2. In order to see the controller re-deploy the deployment pods, create a PutSecretValue event either by changing the secret sqssecret value in console or with the below CLI call 50 | 51 | ``` 52 | aws secretsmanager put-secret-value --secret-id eks-controller-test-secret --secret-string testsqssec:newsecret 53 | ``` 54 | 55 | We can verify from the pod logs that the new secret is successfully updated by running the following command. 56 | 57 | ``` 58 | kubectl logs -n secretoperator-system $(kubectl get po -n secretoperator-system -o=jsonpath='{.items[*].metadata.name}') -c manager -f 59 | ``` 60 | 61 | Sample output. 62 | 63 | ``` 64 | Secret ID rotated eks-controller-test-secret 65 | Rotating deployment operatortest 66 | DeleteMessageBatchList output: { 67 | Successful: [{ 68 | Id: "22211be0-aaaa-bbbb-cccc-733a1ff7bd17" 69 | }] 70 | } 71 | 2021-03-08T20:43:25.715Z DEBUG controller-runtime.controller Successfully Reconciled {"controller": "secretsrotationmapping", "request": "default/secretsrotationmapping-sample"} 72 | ``` 73 | 74 | Alternatively, you can pass the watch option to list the sample deployment pods and observe the restart. 75 | 76 | ``` 77 | kubectl get pods --selector environment=operatortest --watch 78 | ``` 79 | 80 | Sample output. 81 | 82 | ``` 83 | NAME READY STATUS RESTARTS AGE 84 | operatortest-8569978555-cl76n 1/1 Running 0 5s 85 | operatortest-54cd7d5795-6r72c 0/1 Pending 0 0s 86 | operatortest-54cd7d5795-6r72c 0/1 Pending 0 0s 87 | operatortest-54cd7d5795-6r72c 0/1 ContainerCreating 0 0s 88 | operatortest-54cd7d5795-6r72c 1/1 Running 0 2s 89 | operatortest-8569978555-cl76n 1/1 Terminating 0 37s 90 | operatortest-8569978555-cl76n 0/1 Terminating 0 38s 91 | operatortest-8569978555-cl76n 0/1 Terminating 0 39s 92 | operatortest-8569978555-cl76n 0/1 Terminating 0 39s 93 | ``` 94 | 95 | ## Cleaning up 96 | 97 | To clean up all the provisioned resources to test this solution, run the below command. 98 | 99 | ``` 100 | make delete 101 | ``` 102 | 103 | The output should look like - 104 | ``` 105 | echo "Cleaning up the k8s and aws resources..." 106 | Cleaning up the k8s and aws resources... 107 | kubectl delete -f config/samples/deployment.yaml 108 | deployment.apps "operatortest" deleted 109 | kubectl delete -f config/samples/awssecretsoperator_v1_secretsrotationmapping.yaml 110 | secretsrotationmapping.awssecretsoperator.secretoperator "secretsrotationmapping-sample" deleted 111 | kustomize build config/default | kubectl delete -f - 112 | namespace "secretoperator-system" deleted 113 | customresourcedefinition.apiextensions.k8s.io "secretsrotationmappings.awssecretsoperator.secretoperator" deleted 114 | serviceaccount "secretoperator-operator-serviceaccount" deleted 115 | role.rbac.authorization.k8s.io "secretoperator-leader-election-role" deleted 116 | clusterrole.rbac.authorization.k8s.io "secretoperator-manager-role" deleted 117 | clusterrole.rbac.authorization.k8s.io "secretoperator-proxy-role" deleted 118 | clusterrole.rbac.authorization.k8s.io "secretoperator-metrics-reader" deleted 119 | rolebinding.rbac.authorization.k8s.io "secretoperator-leader-election-rolebinding" deleted 120 | clusterrolebinding.rbac.authorization.k8s.io "secretoperator-manager-rolebinding" deleted 121 | clusterrolebinding.rbac.authorization.k8s.io "secretoperator-proxy-rolebinding" deleted 122 | service "secretoperator-controller-manager-metrics-service" deleted 123 | deployment.apps "secretoperator-controller-manager" deleted 124 | aws cloudformation delete-stack --stack-name EKS-Secrets-Operator-Stack 125 | aws cloudformation wait stack-delete-complete --stack-name EKS-Secrets-Operator-Stack 126 | make init 127 | cp config/manager/kustomization_bkup.yaml config/manager/kustomization.yaml 128 | cp config/manager/manager_bkup.yaml config/manager/manager.yaml 129 | rm config/manager/*.bak 130 | ``` 131 | 132 | ## Troubleshooting steps 133 | 134 | 1. How to check if the secretoperator is running 135 | 136 | Check if there is a deployment `secretoperator-controller-manager` in the namespace `secretoperator-system` and make sure the pod in the deployment is ready. 137 | 138 | ``` 139 | kubectl get pods -n secretoperator-system 140 | ``` 141 | 142 | 2. How to check the controller logs 143 | 144 | ``` 145 | kubectl logs -c manager -l control-plane=controller-manager -n secretoperator-system 146 | ``` 147 | 148 | 3. How to fix the issue if the secretoperator pod logs are showing Access Denied errors 149 | 150 | * Make sure the OIDC identity provider is created as per the [IRSA guidelines.](https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/) 151 | 152 | * Make sure the OperatorRole created by CloudFormation stack in step 4 has the correct trust policy . 153 | 154 | ``` 155 | { 156 | "Version": "2012-10-17", 157 | "Statement": [ 158 | { 159 | "Effect": "Allow", 160 | "Principal": { 161 | "Federated": "arn:aws:iam::XXXXXXX:oidc-provider/" 162 | }, 163 | "Action": "sts:AssumeRoleWithWebIdentity", 164 | "Condition": { 165 | "StringEquals": { 166 | ":sub": "system:serviceaccount:secretoperator-system:secretoperator-operator-serviceaccount" 167 | } 168 | } 169 | } 170 | ] 171 | } 172 | ``` 173 | 174 | * Make sure the OIDC ID in the trust policy is for the EKS cluster on which the operator is running 175 | 176 | 4. What to check if the pods in the deployment / daemonset / statefulset are not getting restarted after secret is updated? 177 | 178 | * Check the CRD resource and make sure the secret you are updating the secret ID in the resource is same - 179 | 180 | ``` 181 | kubectl get SecretsRotationMapping secretsrotationmapping-sample -o yaml 182 | ``` 183 | 184 | * Make sure the label in the CRD resource and label in the Deployements/Daemonsets/Statefulset mathc. Labels are case sensitive. 185 | 186 | ## Result - 187 | The secrets-nginx deployment should restart the pods 188 | 189 | -------------------------------------------------------------------------------- /secret-operator/api/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the awssecretsoperator v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=awssecretsoperator.secretoperator 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "awssecretsoperator.secretoperator", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /secret-operator/api/v1/secretsrotationmapping_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // SecretsRotationMappingSpec defines the desired state of SecretsRotationMapping 27 | type SecretsRotationMappingSpec struct { 28 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 29 | // Important: Run "make" to regenerate code after modifying this file 30 | 31 | SecretID string `json:"SecretID,omitempty"` 32 | Labels map[string]string `json:"Labels,omitempty"` 33 | } 34 | 35 | // SecretsRotationMappingStatus defines the observed state of SecretsRotationMapping 36 | type SecretsRotationMappingStatus struct { 37 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 38 | // Important: Run "make" to regenerate code after modifying this file 39 | } 40 | 41 | // +kubebuilder:object:root=true 42 | 43 | // SecretsRotationMapping is the Schema for the secretsrotationmappings API 44 | type SecretsRotationMapping struct { 45 | metav1.TypeMeta `json:",inline"` 46 | metav1.ObjectMeta `json:"metadata,omitempty"` 47 | 48 | Spec SecretsRotationMappingSpec `json:"spec,omitempty"` 49 | Status SecretsRotationMappingStatus `json:"status,omitempty"` 50 | } 51 | 52 | // +kubebuilder:object:root=true 53 | 54 | // SecretsRotationMappingList contains a list of SecretsRotationMapping 55 | type SecretsRotationMappingList struct { 56 | metav1.TypeMeta `json:",inline"` 57 | metav1.ListMeta `json:"metadata,omitempty"` 58 | Items []SecretsRotationMapping `json:"items"` 59 | } 60 | 61 | func init() { 62 | SchemeBuilder.Register(&SecretsRotationMapping{}, &SecretsRotationMappingList{}) 63 | } 64 | -------------------------------------------------------------------------------- /secret-operator/api/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *SecretsRotationMapping) DeepCopyInto(out *SecretsRotationMapping) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | in.Spec.DeepCopyInto(&out.Spec) 33 | out.Status = in.Status 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsRotationMapping. 37 | func (in *SecretsRotationMapping) DeepCopy() *SecretsRotationMapping { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(SecretsRotationMapping) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *SecretsRotationMapping) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *SecretsRotationMappingList) DeepCopyInto(out *SecretsRotationMappingList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | in.ListMeta.DeepCopyInto(&out.ListMeta) 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]SecretsRotationMapping, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | } 67 | 68 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsRotationMappingList. 69 | func (in *SecretsRotationMappingList) DeepCopy() *SecretsRotationMappingList { 70 | if in == nil { 71 | return nil 72 | } 73 | out := new(SecretsRotationMappingList) 74 | in.DeepCopyInto(out) 75 | return out 76 | } 77 | 78 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 79 | func (in *SecretsRotationMappingList) DeepCopyObject() runtime.Object { 80 | if c := in.DeepCopy(); c != nil { 81 | return c 82 | } 83 | return nil 84 | } 85 | 86 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 87 | func (in *SecretsRotationMappingSpec) DeepCopyInto(out *SecretsRotationMappingSpec) { 88 | *out = *in 89 | if in.Labels != nil { 90 | in, out := &in.Labels, &out.Labels 91 | *out = make(map[string]string, len(*in)) 92 | for key, val := range *in { 93 | (*out)[key] = val 94 | } 95 | } 96 | } 97 | 98 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsRotationMappingSpec. 99 | func (in *SecretsRotationMappingSpec) DeepCopy() *SecretsRotationMappingSpec { 100 | if in == nil { 101 | return nil 102 | } 103 | out := new(SecretsRotationMappingSpec) 104 | in.DeepCopyInto(out) 105 | return out 106 | } 107 | 108 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 109 | func (in *SecretsRotationMappingStatus) DeepCopyInto(out *SecretsRotationMappingStatus) { 110 | *out = *in 111 | } 112 | 113 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsRotationMappingStatus. 114 | func (in *SecretsRotationMappingStatus) DeepCopy() *SecretsRotationMappingStatus { 115 | if in == nil { 116 | return nil 117 | } 118 | out := new(SecretsRotationMappingStatus) 119 | in.DeepCopyInto(out) 120 | return out 121 | } 122 | -------------------------------------------------------------------------------- /secret-operator/bin/manager: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-secret-sidecar-injector/eba716dfb2fb3cca2b4d7cd55fd5d1a36dcf63aa/secret-operator/bin/manager -------------------------------------------------------------------------------- /secret-operator/blog3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-secret-sidecar-injector/eba716dfb2fb3cca2b4d7cd55fd5d1a36dcf63aa/secret-operator/blog3.jpg -------------------------------------------------------------------------------- /secret-operator/cfn.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Provision eventbridge, SQS and a test secret required for EKS controller setup 3 | Metadata: 4 | License: >- 5 | Any code, applications, scripts, templates, proofs of concept, documentation 6 | and other items provided by AWS under this SOW are "AWS Content," as defined 7 | in the Agreement, and are provided for illustration purposes only. All such 8 | AWS Content is provided solely at the option of AWS, and is subject to the 9 | terms of the Addendum and the Agreement. Customer is solely responsible for 10 | using, deploying, testing, and supporting any code and applications provided 11 | by AWS under this SOW. 12 | Parameters: 13 | OIDCPROVIDER: 14 | Type: String 15 | Description: Provide the OIDC identity provider 16 | 17 | Resources: 18 | 19 | TestSecret: 20 | Type: AWS::SecretsManager::Secret 21 | Properties: 22 | Name: eks-controller-test-secret 23 | Description: This secret has a hardcoded password in SecretString 24 | SecretString: '{"username":"MasterUsername","password":"secret-password"}' 25 | Tags: 26 | - 27 | Key: AppName 28 | Value: eks-controller 29 | 30 | ControllerListenerSqs: 31 | Type: AWS::SQS::Queue 32 | Properties: 33 | QueueName: eks-controller-sqs 34 | Tags: 35 | - 36 | Key: AppName 37 | Value: eks-controller 38 | 39 | ControllerListenerSqsPolicy: 40 | Type: AWS::SQS::QueuePolicy 41 | Properties: 42 | Queues: 43 | - !Ref ControllerListenerSqs 44 | PolicyDocument: 45 | Statement: 46 | - 47 | Action: 48 | - "SQS:SendMessage" 49 | Effect: "Allow" 50 | Resource: !GetAtt ControllerListenerSqs.Arn 51 | Principal: 52 | Service: 53 | - "events.amazonaws.com" 54 | - "sqs.amazonaws.com" 55 | 56 | EventbridgeRule: 57 | Type: AWS::Events::Rule 58 | Properties: 59 | Name: eks-controller-events-rule 60 | Description: Cloudwatch rule to trigger the SQS queue which listens on secret change events 61 | EventPattern: { "source": [ "aws.secretsmanager" ], "detail-type": [ "AWS API Call via CloudTrail" ], "detail": { "eventSource": [ "secretsmanager.amazonaws.com" ], "eventName": [ "PutSecretValue" ] } } 62 | State: "ENABLED" 63 | Targets: 64 | - 65 | Arn: !GetAtt ControllerListenerSqs.Arn 66 | Id: "SQStrigger" 67 | 68 | IAMRole: 69 | Type: AWS::IAM::Role 70 | Properties: 71 | RoleName: OperatorRole 72 | AssumeRolePolicyDocument: !Sub | 73 | { 74 | "Version": "2012-10-17", 75 | "Statement": [ 76 | { 77 | "Effect": "Allow", 78 | "Principal": { 79 | "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/${OIDCPROVIDER}" 80 | }, 81 | "Action": "sts:AssumeRoleWithWebIdentity", 82 | "Condition": { 83 | "StringEquals": { 84 | "${OIDCPROVIDER}:sub": "system:serviceaccount:secretoperator-system:secretoperator-operator-serviceaccount" 85 | } 86 | } 87 | } 88 | ] 89 | } 90 | Policies: 91 | - PolicyName: root 92 | PolicyDocument: 93 | Version: 2012-10-17 94 | Statement: 95 | - Sid: VisualEditor0 96 | Effect: Allow 97 | Action: 98 | - 'sqs:DeleteMessage' 99 | - 'sqs:DeleteMessageBatch' 100 | - 'sqs:ReceiveMessage' 101 | Resource: '*' 102 | Outputs: 103 | QueueURL: 104 | Description: "URL of source queue" 105 | Value: !Ref ControllerListenerSqs 106 | 107 | Region: 108 | Description: "AWS region" 109 | Value: !Ref "AWS::Region" 110 | 111 | IAMRole: 112 | Description: "IRSA role arn" 113 | Value: !GetAtt IAMRole.Arn 114 | -------------------------------------------------------------------------------- /secret-operator/config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for 4 | # breaking changes 5 | apiVersion: cert-manager.io/v1alpha2 6 | kind: Issuer 7 | metadata: 8 | name: selfsigned-issuer 9 | namespace: system 10 | spec: 11 | selfSigned: {} 12 | --- 13 | apiVersion: cert-manager.io/v1alpha2 14 | kind: Certificate 15 | metadata: 16 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 17 | namespace: system 18 | spec: 19 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 20 | dnsNames: 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 22 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 23 | issuerRef: 24 | kind: Issuer 25 | name: selfsigned-issuer 26 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 27 | -------------------------------------------------------------------------------- /secret-operator/config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /secret-operator/config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /secret-operator/config/crd/bases/awssecretsoperator.secretoperator_secretsrotationmappings.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.5 8 | creationTimestamp: null 9 | name: secretsrotationmappings.awssecretsoperator.secretoperator 10 | spec: 11 | group: awssecretsoperator.secretoperator 12 | names: 13 | kind: SecretsRotationMapping 14 | listKind: SecretsRotationMappingList 15 | plural: secretsrotationmappings 16 | singular: secretsrotationmapping 17 | scope: Namespaced 18 | validation: 19 | openAPIV3Schema: 20 | description: SecretsRotationMapping is the Schema for the secretsrotationmappings 21 | API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation 25 | of an object. Servers should convert recognized schemas to the latest 26 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 27 | type: string 28 | kind: 29 | description: 'Kind is a string value representing the REST resource this 30 | object represents. Servers may infer this from the endpoint the client 31 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | description: SecretsRotationMappingSpec defines the desired state of SecretsRotationMapping 37 | properties: 38 | Labels: 39 | additionalProperties: 40 | type: string 41 | type: object 42 | SecretID: 43 | type: string 44 | type: object 45 | status: 46 | description: SecretsRotationMappingStatus defines the observed state of 47 | SecretsRotationMapping 48 | type: object 49 | type: object 50 | version: v1 51 | versions: 52 | - name: v1 53 | served: true 54 | storage: true 55 | status: 56 | acceptedNames: 57 | kind: "" 58 | plural: "" 59 | conditions: [] 60 | storedVersions: [] 61 | -------------------------------------------------------------------------------- /secret-operator/config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/awssecretsoperator.secretoperator_secretsrotationmappings.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_secretsrotationmappings.yaml 12 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_secretsrotationmappings.yaml 17 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /secret-operator/config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /secret-operator/config/crd/patches/cainjection_in_secretsrotationmappings.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: secretsrotationmappings.awssecretsoperator.secretoperator 9 | -------------------------------------------------------------------------------- /secret-operator/config/crd/patches/webhook_in_secretsrotationmappings.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: secretsrotationmappings.awssecretsoperator.secretoperator 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /secret-operator/config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: secretoperator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: secretoperator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 34 | # crd/kustomization.yaml 35 | #- manager_webhook_patch.yaml 36 | 37 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 38 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 39 | # 'CERTMANAGER' needs to be enabled to use ca injection 40 | #- webhookcainjection_patch.yaml 41 | 42 | # the following config is for teaching kustomize how to do var substitution 43 | vars: 44 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 45 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 46 | # objref: 47 | # kind: Certificate 48 | # group: cert-manager.io 49 | # version: v1alpha2 50 | # name: serving-cert # this name should match the one in certificate.yaml 51 | # fieldref: 52 | # fieldpath: metadata.namespace 53 | #- name: CERTIFICATE_NAME 54 | # objref: 55 | # kind: Certificate 56 | # group: cert-manager.io 57 | # version: v1alpha2 58 | # name: serving-cert # this name should match the one in certificate.yaml 59 | #- name: SERVICE_NAMESPACE # namespace of the service 60 | # objref: 61 | # kind: Service 62 | # version: v1 63 | # name: webhook-service 64 | # fieldref: 65 | # fieldpath: metadata.namespace 66 | #- name: SERVICE_NAME 67 | # objref: 68 | # kind: Service 69 | # version: v1 70 | # name: webhook-service 71 | -------------------------------------------------------------------------------- /secret-operator/config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /secret-operator/config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /secret-operator/config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /secret-operator/config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: controller 8 | newTag: operator -------------------------------------------------------------------------------- /secret-operator/config/manager/kustomization_bkup.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: controller 8 | newTag: operator -------------------------------------------------------------------------------- /secret-operator/config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: v1 9 | kind: ServiceAccount 10 | metadata: 11 | annotations: 12 | eks.amazonaws.com/role-arn: IAM_ARN 13 | name: operator-serviceaccount 14 | namespace: system 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: controller-manager 20 | namespace: system 21 | labels: 22 | control-plane: controller-manager 23 | spec: 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | replicas: 1 28 | template: 29 | metadata: 30 | labels: 31 | control-plane: controller-manager 32 | spec: 33 | containers: 34 | - command: 35 | - /manager 36 | args: 37 | - --enable-leader-election 38 | image: controller:latest 39 | name: manager 40 | env: 41 | - name: SECRETS_SQS_QUEUE_URL 42 | value: SQS_URL 43 | - name: AWS_DEFAULT_REGION 44 | value: OPERATOR_REGION 45 | resources: 46 | limits: 47 | cpu: 100m 48 | memory: 30Mi 49 | requests: 50 | cpu: 100m 51 | memory: 20Mi 52 | terminationGracePeriodSeconds: 10 53 | serviceAccountName: operator-serviceaccount 54 | securityContext: 55 | fsGroup: 65534 -------------------------------------------------------------------------------- /secret-operator/config/manager/manager_bkup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: v1 9 | kind: ServiceAccount 10 | metadata: 11 | annotations: 12 | eks.amazonaws.com/role-arn: IAM_ARN 13 | name: operator-serviceaccount 14 | namespace: system 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: controller-manager 20 | namespace: system 21 | labels: 22 | control-plane: controller-manager 23 | spec: 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | replicas: 1 28 | template: 29 | metadata: 30 | labels: 31 | control-plane: controller-manager 32 | spec: 33 | containers: 34 | - command: 35 | - /manager 36 | args: 37 | - --enable-leader-election 38 | image: controller:latest 39 | name: manager 40 | env: 41 | - name: SECRETS_SQS_QUEUE_URL 42 | value: SQS_URL 43 | - name: AWS_DEFAULT_REGION 44 | value: OPERATOR_REGION 45 | resources: 46 | limits: 47 | cpu: 100m 48 | memory: 30Mi 49 | requests: 50 | cpu: 100m 51 | memory: 20Mi 52 | terminationGracePeriodSeconds: 10 53 | serviceAccountName: operator-serviceaccount 54 | securityContext: 55 | fsGroup: 65534 -------------------------------------------------------------------------------- /secret-operator/config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /secret-operator/config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | selector: 15 | matchLabels: 16 | control-plane: controller-manager 17 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 4 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | - auth_proxy_client_clusterrole.yaml 13 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: operator-serviceaccount 12 | namespace: system 13 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - apps 11 | resources: 12 | - daemonsets 13 | verbs: 14 | - create 15 | - delete 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - apps 22 | resources: 23 | - deployments 24 | verbs: 25 | - create 26 | - delete 27 | - list 28 | - patch 29 | - update 30 | - watch 31 | - apiGroups: 32 | - apps 33 | resources: 34 | - statefulsets 35 | verbs: 36 | - create 37 | - delete 38 | - list 39 | - patch 40 | - update 41 | - watch 42 | - apiGroups: 43 | - awssecretsoperator.secretoperator 44 | resources: 45 | - secretsrotationmappings 46 | verbs: 47 | - create 48 | - delete 49 | - get 50 | - list 51 | - patch 52 | - update 53 | - watch 54 | - apiGroups: 55 | - awssecretsoperator.secretoperator 56 | resources: 57 | - secretsrotationmappings/status 58 | verbs: 59 | - get 60 | - patch 61 | - update 62 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: operator-serviceaccount 12 | namespace: system 13 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/secretsrotationmapping_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit secretsrotationmappings. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: secretsrotationmapping-editor-role 6 | rules: 7 | - apiGroups: 8 | - awssecretsoperator.secretoperator 9 | resources: 10 | - secretsrotationmappings 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - awssecretsoperator.secretoperator 21 | resources: 22 | - secretsrotationmappings/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /secret-operator/config/rbac/secretsrotationmapping_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view secretsrotationmappings. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: secretsrotationmapping-viewer-role 6 | rules: 7 | - apiGroups: 8 | - awssecretsoperator.secretoperator 9 | resources: 10 | - secretsrotationmappings 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - awssecretsoperator.secretoperator 17 | resources: 18 | - secretsrotationmappings/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /secret-operator/config/samples/awssecretsoperator_v1_secretsrotationmapping.1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: awssecretsoperator.secretoperator/v1 2 | kind: SecretsRotationMapping 3 | metadata: 4 | name: secretsrotationmapping-2 5 | spec: 6 | SecretID: "eks-controller-test-secret" 7 | Labels: 8 | environment: operatortest -------------------------------------------------------------------------------- /secret-operator/config/samples/awssecretsoperator_v1_secretsrotationmapping.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: awssecretsoperator.secretoperator/v1 2 | kind: SecretsRotationMapping 3 | metadata: 4 | name: secretsrotationmapping-sample 5 | spec: 6 | SecretID: "eks-controller-test-secret" 7 | Labels: 8 | environment: operatortest -------------------------------------------------------------------------------- /secret-operator/config/samples/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | environment: operatortest 6 | name: operatortest 7 | namespace: default 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | environment: operatortest 13 | template: 14 | metadata: 15 | labels: 16 | environment: operatortest 17 | spec: 18 | containers: 19 | - image: nginx 20 | imagePullPolicy: Always 21 | name: secrets-nginx 22 | -------------------------------------------------------------------------------- /secret-operator/config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /secret-operator/config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /secret-operator/config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /secret-operator/controllers/secretsrotationmapping_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "github.com/aws/aws-sdk-go/aws" 24 | "github.com/aws/aws-sdk-go/aws/session" 25 | "github.com/aws/aws-sdk-go/service/sqs" 26 | "github.com/go-logr/logr" 27 | "k8s.io/api/apps/v1" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "k8s.io/apimachinery/pkg/types" 30 | awssecretsoperatorv1 "secretoperator/api/v1" 31 | ctrl "sigs.k8s.io/controller-runtime" 32 | "sigs.k8s.io/controller-runtime/pkg/client" 33 | "time" 34 | ) 35 | 36 | // SecretsRotationMappingReconciler reconciles a SecretsRotationMapping object 37 | type SecretsRotationMappingReconciler struct { 38 | client.Client 39 | Log logr.Logger 40 | Scheme *runtime.Scheme 41 | RequeueAfter time.Duration 42 | QueueUrl string 43 | Region string 44 | } 45 | 46 | // +kubebuilder:rbac:groups=awssecretsoperator.secretoperator,resources=secretsrotationmappings,verbs=get;list;watch;create;update;patch;delete 47 | // +kubebuilder:rbac:groups=awssecretsoperator.secretoperator,resources=secretsrotationmappings/status,verbs=get;update;patch 48 | // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;create;update;patch;delete 49 | // +kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=list;watch;create;update;patch;delete 50 | // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=list;watch;create;update;patch;delete 51 | 52 | func (r *SecretsRotationMappingReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 53 | ctx := context.Background() 54 | 55 | var DeleteMessageBatchList []*sqs.DeleteMessageBatchRequestEntry 56 | var SecretsRotationMapping awssecretsoperatorv1.SecretsRotationMapping 57 | var result map[string]interface{} 58 | 59 | if err := r.Get(ctx, req.NamespacedName, &SecretsRotationMapping); err != nil { 60 | return ctrl.Result{}, client.IgnoreNotFound(err) 61 | } 62 | 63 | //fmt.Println(SecretsRotationMapping.Spec.Labels) 64 | sess, err := session.NewSession(&aws.Config{ 65 | Region: aws.String(r.Region)}, 66 | ) 67 | svc := sqs.New(sess) 68 | 69 | //read message from SQS 70 | message, err := svc.ReceiveMessage(&sqs.ReceiveMessageInput{ 71 | QueueUrl: &r.QueueUrl, 72 | MaxNumberOfMessages: aws.Int64(10), 73 | VisibilityTimeout: aws.Int64(2), 74 | WaitTimeSeconds: aws.Int64(0), 75 | }) 76 | 77 | if err != nil { 78 | fmt.Println("Error", err) 79 | return ctrl.Result{RequeueAfter: time.Second * r.RequeueAfter}, nil 80 | } 81 | 82 | //fmt.Println("SQS messages:", message.Messages) 83 | //loop through all the messages retrived from SQS 84 | for _, element := range message.Messages { 85 | err := json.Unmarshal([]byte(*element.Body), &result) 86 | if err != nil { 87 | fmt.Println("Error", err) 88 | } 89 | 90 | detail := result["detail"].(map[string]interface{}) 91 | eventName := detail["eventName"] 92 | 93 | // continue only if the event type is PutSecretValue 94 | if eventName == "PutSecretValue" { 95 | requestParameters := detail["requestParameters"].(map[string]interface{}) 96 | secretID := requestParameters["secretId"] 97 | fmt.Println("Secret ID rotated", secretID) 98 | 99 | //if the secretID in SQS message is not same as the secret in CRD, continue with next message 100 | if secretID != SecretsRotationMapping.Spec.SecretID { 101 | fmt.Println("continuing to next loop") 102 | continue 103 | } 104 | 105 | //get the deployment using labesl specified in the crd SecretsRotationMapping 106 | var deploy v1.DeploymentList 107 | //MatchingLabels := SecretsRotationMapping.Spec.Labels 108 | r.List(ctx, &deploy, client.MatchingLabels(SecretsRotationMapping.Spec.Labels)) 109 | // fmt.Println("List deployments by Label:", deploy) 110 | 111 | for _, deployment := range deploy.Items { 112 | // Patch the Deployment with new label containing redeployed timestamp, to force redeploy 113 | fmt.Println("Rotating deployment", deployment.ObjectMeta.Name) 114 | patch := []byte(fmt.Sprintf(`{"spec":{"template":{"metadata":{"labels":{"aws-secrets-controller-redeloyed":"%v"}}}}}`, time.Now().Unix())) 115 | if err := r.Patch(ctx, &deployment, client.RawPatch(types.StrategicMergePatchType, patch)); err != nil { 116 | fmt.Println("Patch deployment err:", err) 117 | return ctrl.Result{RequeueAfter: time.Second * r.RequeueAfter}, nil 118 | } 119 | } 120 | 121 | //get the DaemonSet using labesl specified in the crd SecretsRotationMapping 122 | var DaemonSetList v1.DaemonSetList 123 | r.List(ctx, &DaemonSetList, client.MatchingLabels(SecretsRotationMapping.Spec.Labels)) 124 | // fmt.Println("List DaemonSetList by Label:", DaemonSetList) 125 | 126 | for _, DaemonSet := range DaemonSetList.Items { 127 | // Patch the DaemonSet with new label containing redeployed timestamp, to force redeploy 128 | fmt.Println("Rotating DaemonSet", DaemonSet.ObjectMeta.Name) 129 | patch := []byte(fmt.Sprintf(`{"spec":{"template":{"metadata":{"labels":{"aws-secrets-operator-redeloyed":"%v"}}}}}`, time.Now().Unix())) 130 | if err := r.Patch(ctx, &DaemonSet, client.RawPatch(types.StrategicMergePatchType, patch)); err != nil { 131 | fmt.Println("Patch DaemonSet err:", err) 132 | return ctrl.Result{RequeueAfter: time.Second * r.RequeueAfter}, nil 133 | } 134 | } 135 | 136 | //get the SatefulSet using labesl specified in the crd SecretsRotationMapping 137 | var StatefulSetList v1.StatefulSetList 138 | r.List(ctx, &StatefulSetList, client.MatchingLabels(SecretsRotationMapping.Spec.Labels)) 139 | 140 | for _, StatefulSet := range StatefulSetList.Items { 141 | // Patch the StatefulSet with new label containing redeployed timestamp, to force redeploy 142 | fmt.Println("Rotating StatefulSet", StatefulSet.ObjectMeta.Name) 143 | patch := []byte(fmt.Sprintf(`{"spec":{"template":{"metadata":{"labels":{"aws-secrets-operator-redeloyed":"%v"}}}}}`, time.Now().Unix())) 144 | if err := r.Patch(ctx, &StatefulSet, client.RawPatch(types.StrategicMergePatchType, patch)); err != nil { 145 | fmt.Println("Patch StatefulSet err:", err) 146 | return ctrl.Result{RequeueAfter: time.Second * r.RequeueAfter}, nil 147 | } 148 | } 149 | } 150 | 151 | deleteMessage := sqs.DeleteMessageBatchRequestEntry{Id: element.MessageId, ReceiptHandle: element.ReceiptHandle} 152 | DeleteMessageBatchList = append(DeleteMessageBatchList, &deleteMessage) 153 | } 154 | 155 | //DeleteMessageBatch 156 | //fmt.Println("DeleteMessageBatchList:", DeleteMessageBatchList) 157 | if len(DeleteMessageBatchList) > 0 { 158 | DeleteMessageBatchInput := &sqs.DeleteMessageBatchInput{Entries: DeleteMessageBatchList, QueueUrl: &r.QueueUrl} 159 | DeleteMessageBatchOutput, err := svc.DeleteMessageBatch(DeleteMessageBatchInput) 160 | if err != nil { 161 | fmt.Println("DeleteMessageBatchList error:", err) 162 | } 163 | fmt.Println("DeleteMessageBatchList output:", DeleteMessageBatchOutput) 164 | 165 | } 166 | return ctrl.Result{RequeueAfter: time.Second * r.RequeueAfter}, nil 167 | } 168 | 169 | func (r *SecretsRotationMappingReconciler) SetupWithManager(mgr ctrl.Manager) error { 170 | return ctrl.NewControllerManagedBy(mgr). 171 | For(&awssecretsoperatorv1.SecretsRotationMapping{}). 172 | Complete(r) 173 | } 174 | -------------------------------------------------------------------------------- /secret-operator/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "k8s.io/client-go/rest" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | 33 | awssecretsoperatorv1 "secretoperator/api/v1" 34 | // +kubebuilder:scaffold:imports 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var cfg *rest.Config 41 | var k8sClient client.Client 42 | var testEnv *envtest.Environment 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecsWithDefaultAndCustomReporters(t, 48 | "Controller Suite", 49 | []Reporter{printer.NewlineReporter{}}) 50 | } 51 | 52 | var _ = BeforeSuite(func(done Done) { 53 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) 54 | 55 | By("bootstrapping test environment") 56 | testEnv = &envtest.Environment{ 57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 58 | } 59 | 60 | var err error 61 | cfg, err = testEnv.Start() 62 | Expect(err).ToNot(HaveOccurred()) 63 | Expect(cfg).ToNot(BeNil()) 64 | 65 | err = awssecretsoperatorv1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | // +kubebuilder:scaffold:scheme 69 | 70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 71 | Expect(err).ToNot(HaveOccurred()) 72 | Expect(k8sClient).ToNot(BeNil()) 73 | 74 | close(done) 75 | }, 60) 76 | 77 | var _ = AfterSuite(func() { 78 | By("tearing down the test environment") 79 | err := testEnv.Stop() 80 | Expect(err).ToNot(HaveOccurred()) 81 | }) 82 | -------------------------------------------------------------------------------- /secret-operator/cover.out: -------------------------------------------------------------------------------- 1 | mode: set 2 | secretoperator/controllers/secretsrotationmapping_controller.go:52.93,59.80 5 0 3 | secretoperator/controllers/secretsrotationmapping_controller.go:64.2,77.16 4 0 4 | secretoperator/controllers/secretsrotationmapping_controller.go:84.2,84.43 1 0 5 | secretoperator/controllers/secretsrotationmapping_controller.go:157.2,157.37 1 0 6 | secretoperator/controllers/secretsrotationmapping_controller.go:166.2,166.69 1 0 7 | secretoperator/controllers/secretsrotationmapping_controller.go:59.80,61.3 1 0 8 | secretoperator/controllers/secretsrotationmapping_controller.go:77.16,80.3 2 0 9 | secretoperator/controllers/secretsrotationmapping_controller.go:84.43,86.17 2 0 10 | secretoperator/controllers/secretsrotationmapping_controller.go:90.3,94.36 3 0 11 | secretoperator/controllers/secretsrotationmapping_controller.go:151.3,152.74 2 0 12 | secretoperator/controllers/secretsrotationmapping_controller.go:86.17,88.4 1 0 13 | secretoperator/controllers/secretsrotationmapping_controller.go:94.36,100.56 4 0 14 | secretoperator/controllers/secretsrotationmapping_controller.go:106.4,111.44 3 0 15 | secretoperator/controllers/secretsrotationmapping_controller.go:122.4,126.50 3 0 16 | secretoperator/controllers/secretsrotationmapping_controller.go:137.4,140.54 3 0 17 | secretoperator/controllers/secretsrotationmapping_controller.go:100.56,102.13 2 0 18 | secretoperator/controllers/secretsrotationmapping_controller.go:111.44,115.108 3 0 19 | secretoperator/controllers/secretsrotationmapping_controller.go:115.108,118.6 2 0 20 | secretoperator/controllers/secretsrotationmapping_controller.go:126.50,130.107 3 0 21 | secretoperator/controllers/secretsrotationmapping_controller.go:130.107,133.6 2 0 22 | secretoperator/controllers/secretsrotationmapping_controller.go:140.54,144.109 3 0 23 | secretoperator/controllers/secretsrotationmapping_controller.go:144.109,147.6 2 0 24 | secretoperator/controllers/secretsrotationmapping_controller.go:157.37,160.17 3 0 25 | secretoperator/controllers/secretsrotationmapping_controller.go:163.3,163.74 1 0 26 | secretoperator/controllers/secretsrotationmapping_controller.go:160.17,162.4 1 0 27 | secretoperator/controllers/secretsrotationmapping_controller.go:169.85,173.2 1 0 28 | -------------------------------------------------------------------------------- /secret-operator/go.mod: -------------------------------------------------------------------------------- 1 | module secretoperator 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.34.9 7 | github.com/go-logr/logr v0.1.0 8 | github.com/onsi/ginkgo v1.11.0 9 | github.com/onsi/gomega v1.8.1 10 | k8s.io/api v0.17.2 11 | k8s.io/apimachinery v0.17.2 12 | k8s.io/client-go v0.17.2 13 | sigs.k8s.io/controller-runtime v0.5.0 14 | ) 15 | -------------------------------------------------------------------------------- /secret-operator/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= 4 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 5 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 6 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 7 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 8 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 9 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 10 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 11 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 12 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 13 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 14 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 15 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 16 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 17 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 18 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 19 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 20 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 21 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= 22 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 23 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 24 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 25 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 26 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 27 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 28 | github.com/aws/aws-sdk-go v1.34.9 h1:cUGBW9CVdi0mS7K1hDzxIqTpfeWhpoQiguq81M1tjK0= 29 | github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= 30 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 31 | github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= 32 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 33 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 34 | github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 35 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 36 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 37 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 38 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 39 | github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 40 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 41 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 42 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 43 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 44 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 45 | github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 46 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 47 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 48 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 49 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 50 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 51 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 52 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 53 | github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 54 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 55 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 56 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 57 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 58 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 59 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 60 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 61 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 62 | github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 63 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 64 | github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= 65 | github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 66 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 67 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 68 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 69 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 70 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 71 | github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 72 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 73 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 74 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 75 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 76 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 77 | github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= 78 | github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 79 | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= 80 | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 81 | github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 82 | github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= 83 | github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= 84 | github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 85 | github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 86 | github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 87 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 88 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 89 | github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 90 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 91 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 92 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 93 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 94 | github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 95 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 96 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 97 | github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 98 | github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 99 | github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 100 | github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= 101 | github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= 102 | github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= 103 | github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= 104 | github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= 105 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 106 | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 107 | github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 108 | github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= 109 | github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 110 | github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 111 | github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 112 | github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= 113 | github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= 114 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 115 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 116 | github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 117 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 118 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 119 | github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 120 | github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= 121 | github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= 122 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 123 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 124 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 125 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 126 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= 127 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 128 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 129 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 130 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE= 131 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 132 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 133 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 134 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 135 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 136 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 137 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 138 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 139 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 140 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 141 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 142 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 143 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 144 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 145 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 146 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 147 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 148 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 149 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 150 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 151 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 152 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 153 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 154 | github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= 155 | github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= 156 | github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 157 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 158 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 159 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 160 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 161 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 162 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 163 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 164 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 165 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 166 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 167 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 168 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 169 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 170 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 171 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 172 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 173 | github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= 174 | github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 175 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 176 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 177 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 178 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 179 | github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= 180 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 181 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 182 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 183 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 184 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 185 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 186 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 187 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 188 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 189 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 190 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 191 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 192 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 193 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 194 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 195 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 196 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 197 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 198 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 199 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 200 | github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 201 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 202 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 203 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 204 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 205 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 206 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 207 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 208 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 209 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 210 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 211 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 212 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 213 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 214 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 215 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 216 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 217 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 218 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 219 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 220 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 221 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 222 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 223 | github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= 224 | github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 225 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 226 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 227 | github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= 228 | github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= 229 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 230 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 231 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 232 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 233 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 234 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 235 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 236 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 237 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 238 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 239 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 240 | github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 241 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 242 | github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= 243 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 244 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 245 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 246 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 247 | github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= 248 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 249 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 250 | github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= 251 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 252 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 253 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 254 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 255 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 256 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 257 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 258 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 259 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 260 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 261 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 262 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 263 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 264 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 265 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 266 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 267 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 268 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 269 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 270 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 271 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 272 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 273 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 274 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 275 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 276 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 277 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 278 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 279 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 280 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 281 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 282 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 283 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 284 | github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= 285 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 286 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 287 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 288 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= 289 | go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 290 | go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 291 | go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 292 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 293 | go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= 294 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 295 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 296 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 297 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 298 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 299 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 300 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 301 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 302 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 303 | golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 304 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 305 | golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 306 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= 307 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 308 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 309 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 310 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 311 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 312 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 313 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 314 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 315 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 316 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 317 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 318 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 319 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 320 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 321 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 322 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 323 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 324 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 325 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 326 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 327 | golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 328 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 329 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 330 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 331 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 332 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 333 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= 334 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 335 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 336 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 337 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 338 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 339 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 340 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 341 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 342 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 343 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 344 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 345 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 346 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 347 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 348 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 349 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 350 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 351 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 352 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 353 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 354 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 355 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 356 | golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 357 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 358 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 359 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 360 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= 361 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 362 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 363 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 364 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 365 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 366 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 367 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 368 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 369 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 370 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 371 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 372 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 373 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 374 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 375 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 376 | golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 377 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 378 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 379 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 380 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 381 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 382 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 383 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 384 | golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 385 | golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 386 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= 387 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 388 | gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= 389 | gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 390 | gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= 391 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 392 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= 393 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 394 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 395 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 396 | google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= 397 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 398 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 399 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 400 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 401 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 402 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 403 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 404 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 405 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 406 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 407 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 408 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 409 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 410 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 411 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 412 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 413 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 414 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 415 | gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 416 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 417 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 418 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 419 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 420 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 421 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 422 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 423 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 424 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 425 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 426 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 427 | k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc= 428 | k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= 429 | k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss= 430 | k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= 431 | k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4= 432 | k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= 433 | k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= 434 | k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc= 435 | k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= 436 | k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= 437 | k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= 438 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 439 | k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 440 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 441 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 442 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 443 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 444 | k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= 445 | k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 446 | k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= 447 | k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 448 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 449 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 450 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 451 | modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 452 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 453 | sigs.k8s.io/controller-runtime v0.5.0 h1:CbqIy5fbUX+4E9bpnBFd204YAzRYlM9SWW77BbrcDQo= 454 | sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8= 455 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 456 | sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= 457 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 458 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 459 | -------------------------------------------------------------------------------- /secret-operator/hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /secret-operator/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | "strconv" 23 | "time" 24 | 25 | "k8s.io/apimachinery/pkg/runtime" 26 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 27 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 30 | 31 | awssecretsoperatorv1 "secretoperator/api/v1" 32 | "secretoperator/controllers" 33 | // +kubebuilder:scaffold:imports 34 | ) 35 | 36 | var ( 37 | scheme = runtime.NewScheme() 38 | setupLog = ctrl.Log.WithName("setup") 39 | ) 40 | 41 | func init() { 42 | _ = clientgoscheme.AddToScheme(scheme) 43 | 44 | _ = awssecretsoperatorv1.AddToScheme(scheme) 45 | // +kubebuilder:scaffold:scheme 46 | } 47 | 48 | func main() { 49 | var metricsAddr string 50 | var enableLeaderElection bool 51 | var RequeueAfter time.Duration 52 | 53 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 54 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 55 | "Enable leader election for controller manager. "+ 56 | "Enabling this will ensure there is only one active controller manager.") 57 | flag.Parse() 58 | 59 | ctrl.SetLogger(zap.New(zap.UseDevMode(true))) 60 | 61 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 62 | Scheme: scheme, 63 | MetricsBindAddress: metricsAddr, 64 | Port: 9443, 65 | LeaderElection: enableLeaderElection, 66 | LeaderElectionID: "0949fff9.secretoperator", 67 | }) 68 | if err != nil { 69 | setupLog.Error(err, "unable to start manager") 70 | os.Exit(1) 71 | } 72 | 73 | ///////////////////////////////////////// 74 | 75 | //Read the requestAfter vaule from environment variable 76 | if secret_rotate_after := os.Getenv("SECRETS_ROTATE_AFTER"); secret_rotate_after == "" { 77 | setupLog.Info("setting default reconciler request after time") 78 | RequeueAfter = time.Duration(5) 79 | } else { 80 | setupLog.Info("setting user specified reconciler request after time") 81 | temp, _ := strconv.Atoi(secret_rotate_after) 82 | RequeueAfter = time.Duration(temp) 83 | } 84 | 85 | //Read the SQS queue name vaule from environment variable 86 | secret_sqs_queue := os.Getenv("SECRETS_SQS_QUEUE_URL") 87 | if secret_sqs_queue == "" { 88 | setupLog.Error(err, "please set the SQS queue URL in environment variable SECRETS_SQS_QUEUE_URL") 89 | os.Exit(1) 90 | } 91 | 92 | //Read region vaule from environment variable 93 | region := os.Getenv("AWS_DEFAULT_REGION") 94 | if region == "" { 95 | setupLog.Error(err, "please set region in environment variable AWS_DEFAULT_REGION") 96 | os.Exit(1) 97 | } 98 | ////////////////////////////////////////// 99 | 100 | if err = (&controllers.SecretsRotationMappingReconciler{ 101 | Client: mgr.GetClient(), 102 | Log: ctrl.Log.WithName("controllers").WithName("SecretsRotationMapping"), 103 | Scheme: mgr.GetScheme(), 104 | RequeueAfter: RequeueAfter, 105 | QueueUrl: secret_sqs_queue, 106 | Region: region, 107 | }).SetupWithManager(mgr); err != nil { 108 | setupLog.Error(err, "unable to create controller", "controller", "SecretsRotationMapping") 109 | os.Exit(1) 110 | } 111 | // +kubebuilder:scaffold:builder 112 | 113 | setupLog.Info("starting manager") 114 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 115 | setupLog.Error(err, "problem running manager") 116 | os.Exit(1) 117 | } 118 | } 119 | --------------------------------------------------------------------------------