mirror of
https://github.com/ysoftdevs/secret-duplicator.git
synced 2026-04-05 08:57:12 +02:00
Initial commit
This commit is contained in:
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
vendor/*
|
||||||
|
|
||||||
|
build/_output
|
||||||
|
|
||||||
|
.go
|
||||||
|
.idea
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
vendor/*
|
||||||
|
|
||||||
|
build/_output
|
||||||
|
|
||||||
|
.go
|
||||||
|
.idea
|
||||||
|
|
||||||
|
TODO.md
|
||||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
113
Makefile
Normal file
113
Makefile
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Image URL to use all building/pushing image targets;
|
||||||
|
# Use your own docker registry and image name for dev/test by overridding the
|
||||||
|
# IMAGE_REPO, IMAGE_NAME and IMAGE_TAG environment variable.
|
||||||
|
REPOSITORY_BASE ?= ghcr.io
|
||||||
|
IMAGE_REPO ?= $(REPOSITORY_BASE)/ysoftdevs/secret-duplicator
|
||||||
|
IMAGE_NAME ?= secret-duplicator
|
||||||
|
GENERATOR_IMAGE_NAME ?= webhook-cert-generator
|
||||||
|
|
||||||
|
# Github host to use for checking the source tree;
|
||||||
|
GIT_HOST ?= github.com/ysoftdevs
|
||||||
|
|
||||||
|
PWD := $(shell pwd)
|
||||||
|
BASE_DIR := $(shell basename $(PWD))
|
||||||
|
REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
|
# Keep an existing GOPATH, make a private one if it is undefined
|
||||||
|
GOPATH_DEFAULT := $(PWD)/.go
|
||||||
|
export GOPATH ?= $(GOPATH_DEFAULT)
|
||||||
|
TESTARGS_DEFAULT := "-v"
|
||||||
|
export TESTARGS ?= $(TESTARGS_DEFAULT)
|
||||||
|
DEST := $(GOPATH)/src/$(GIT_HOST)/$(BASE_DIR)
|
||||||
|
IMAGE_TAG ?= $(shell cat "$(REPO_ROOT)/VERSION")
|
||||||
|
|
||||||
|
|
||||||
|
LOCAL_OS := $(shell uname)
|
||||||
|
ifeq ($(LOCAL_OS),Linux)
|
||||||
|
TARGET_OS ?= linux
|
||||||
|
XARGS_FLAGS="-r"
|
||||||
|
else ifeq ($(LOCAL_OS),Darwin)
|
||||||
|
TARGET_OS ?= darwin
|
||||||
|
XARGS_FLAGS=
|
||||||
|
else
|
||||||
|
$(error "This system's OS $(LOCAL_OS) isn't recognized/supported")
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: fmt lint test build image
|
||||||
|
|
||||||
|
ifeq (,$(wildcard go.mod))
|
||||||
|
ifneq ("$(realpath $(DEST))", "$(realpath $(PWD))")
|
||||||
|
$(error Please run 'make' from $(DEST). Current directory is $(PWD))
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# format section
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@go fmt ./cmd/...
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# lint section
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@echo "Runing the golangci-lint..."
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# test section
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
test:
|
||||||
|
@echo "Running the tests for $(IMAGE_NAME)..."
|
||||||
|
@go test $(TESTARGS) ./...
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# build section
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo "Building the $(IMAGE_NAME) binary..."
|
||||||
|
@CGO_ENABLED=0 go build -o build/_output/bin/$(IMAGE_NAME) ./cmd/
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
@echo "Building the $(IMAGE_NAME) binary for Docker (linux)..."
|
||||||
|
@GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o build/_output/linux/bin/$(IMAGE_NAME) ./cmd/
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# image section
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
image: docker-login build-image push-image
|
||||||
|
|
||||||
|
docker-login:
|
||||||
|
@echo "$(DOCKER_TOKEN)" | docker login -u "$(DOCKER_USER)" --password-stdin "$(REPOSITORY_BASE)"
|
||||||
|
|
||||||
|
docker-logout:
|
||||||
|
@docker logout
|
||||||
|
|
||||||
|
build-image:
|
||||||
|
@echo "Building the docker image: $(IMAGE_REPO)/$(IMAGE_NAME):$(IMAGE_TAG)..."
|
||||||
|
@docker build -t $(IMAGE_REPO)/$(IMAGE_NAME):$(IMAGE_TAG) -f build/Dockerfile .
|
||||||
|
@echo "Building the docker image: $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):$(IMAGE_TAG)..."
|
||||||
|
@docker build -t $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):$(IMAGE_TAG) -f build/Dockerfile.cert-generator .
|
||||||
|
|
||||||
|
push-image: build-image
|
||||||
|
@echo "Pushing the docker image for $(IMAGE_REPO)/$(IMAGE_NAME):$(IMAGE_TAG) and $(IMAGE_REPO)/$(IMAGE_NAME):latest..."
|
||||||
|
@docker tag $(IMAGE_REPO)/$(IMAGE_NAME):$(IMAGE_TAG) $(IMAGE_REPO)/$(IMAGE_NAME):latest
|
||||||
|
@docker push $(IMAGE_REPO)/$(IMAGE_NAME):$(IMAGE_TAG)
|
||||||
|
@docker push $(IMAGE_REPO)/$(IMAGE_NAME):latest
|
||||||
|
@echo "Pushing the docker image for $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):$(IMAGE_TAG) and $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):latest..."
|
||||||
|
@docker tag $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):$(IMAGE_TAG) $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):latest
|
||||||
|
@docker push $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):$(IMAGE_TAG)
|
||||||
|
@docker push $(IMAGE_REPO)/$(GENERATOR_IMAGE_NAME):latest
|
||||||
|
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# clean section
|
||||||
|
############################################################
|
||||||
|
clean:
|
||||||
|
@rm -rf build/_output
|
||||||
|
|
||||||
|
.PHONY: all fmt lint check test build image clean
|
||||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Kubernetes Mutating Webhook for ImagePullSecret injection in ServiceAccounts
|
||||||
|
|
||||||
|
The responsibility of this webhook is to patch all newly created/updated service account and make sure they all contained proper imagepullsecret configuration.
|
||||||
|
|
||||||
|
This repo produces one helm chart available via helm repository https://ysoftdevs.github.io/imagepullsecret-injector. There are also 2 docker images:
|
||||||
|
- `ghcr.io/ysoftdevs/imagepullsecret-injector/imagepullsecret-injector` - the image containing the webhook itself
|
||||||
|
- `ghcr.io/ysoftdevs/imagepullsecret-injector/webhook-cert-generator` - helper image responsible for (re)generating the certificates
|
||||||
|
|
||||||
|
## Helm description
|
||||||
|
The helm chart consists of 2 parts: the certificate generator and the webhook configuration itself.
|
||||||
|
|
||||||
|
Certificate generation part periodically generates certificates signed by kubernetes' CA and passes them to the webhook where they are used as server-side certificates. The flow works roughly like this:
|
||||||
|
1. We generate a CSR using openssl and tie the certificate to the webhook's service DNS.
|
||||||
|
1. We create a k8s CertificateSigningRequest from the openssl CSR.
|
||||||
|
1. We approve this request using our special ServiceAccount with approve permissions. This makes kubernetes issue the certificate
|
||||||
|
1. We fetch the certificate from the k8s CSR (at `.status.certificate`) and create a secret from it
|
||||||
|
1. We also create a CronJob that does this periodically as k8s only issues certificates for 1 year
|
||||||
|
|
||||||
|
The main part is the deployment and the web hook configuration. The flow is as follows
|
||||||
|
1. The MutatingWebhookConfiguration we create instructs k8s to pass all requests for creating/updating all ServiceAccounts to our webhook before finishing the request
|
||||||
|
1. We check whether the SA has the correctly defined imagepullsecret configuration. if not, we create a patch for the resource
|
||||||
|
1. We also check whether we have the secret we are using in the imagepullsecret in the SA's namespace. If not, we create it based on our source secret
|
||||||
|
1. We return the patch to k8s, which applies the changes
|
||||||
|
|
||||||
|
Of note is also a fact that the chart runs a lookup to the connected cluster to fetch the CA bundle for the MutatingWebhook. This means `helm template` won't work.
|
||||||
|
|
||||||
|
## Running locally
|
||||||
|
1. Create the prerequisite resources:
|
||||||
|
```bash
|
||||||
|
kubectl create ns imagepullsecret-injector
|
||||||
|
|
||||||
|
kubectl create secret -n imagepullsecret-injector \
|
||||||
|
generic acr-dockerconfigjson-source \
|
||||||
|
--type=kubernetes.io/dockerconfigjson \
|
||||||
|
--from-literal=.dockerconfigjson='<your .dockerconfigjson configuration file>'
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Build the images and run the chart
|
||||||
|
``` bash
|
||||||
|
make build-image
|
||||||
|
helm upgrade -i imagepullsecret-injector \
|
||||||
|
-n imagepullsecret-injector \
|
||||||
|
charts/imagepullsecret-injector
|
||||||
|
```
|
||||||
|
Alternatively, you can use the pre-built, publicly available helm chart and docker images:
|
||||||
|
```bash
|
||||||
|
helm repo add imagepullsecret-injector https://ysoftdevs.github.io/imagepullsecret-injector
|
||||||
|
helm repo update
|
||||||
|
helm upgrade -i imagepullsecret-injector \
|
||||||
|
-n imagepullsecret-injector \
|
||||||
|
magepullsecret-injector/imagepullsecret-injector
|
||||||
|
```
|
||||||
|
|
||||||
|
1. To test whether everything works, you can run
|
||||||
|
```bash
|
||||||
|
kubectl create ns yolo
|
||||||
|
kubectl get sa -n yolo default -ojsonpath='{.imagePullSecrets}'
|
||||||
|
```
|
||||||
|
The `get` command should display _some_ non-empty result.
|
||||||
|
|
||||||
|
## Releasing locally
|
||||||
|
To authenticate to the docker registry to push the images manually, you will need your own Github Personal Access Token. For more information follow this guide https://docs.github.com/en/packages/guides/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry
|
||||||
10
build/Dockerfile
Normal file
10
build/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM golang:1.15 AS builder
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/ysoftdevs/imagepullsecret-injector
|
||||||
|
COPY . .
|
||||||
|
RUN make build
|
||||||
|
|
||||||
|
FROM alpine:3.13.4 as base
|
||||||
|
COPY --from=builder /go/src/github.com/ysoftdevs/imagepullsecret-injector/build/_output/bin/imagepullsecret-injector /usr/local/bin/imagepullsecret-injector
|
||||||
|
|
||||||
|
ENTRYPOINT ["imagepullsecret-injector"]
|
||||||
6
build/Dockerfile.cert-generator
Normal file
6
build/Dockerfile.cert-generator
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM alpine:3.13.4
|
||||||
|
|
||||||
|
RUN apk add bash curl openssl \
|
||||||
|
&& curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
|
||||||
|
&& chmod 755 ./kubectl \
|
||||||
|
&& mv ./kubectl /usr/bin/kubectl
|
||||||
23
charts/secret-duplicator/.helmignore
Normal file
23
charts/secret-duplicator/.helmignore
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
23
charts/secret-duplicator/Chart.yaml
Normal file
23
charts/secret-duplicator/Chart.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: secret-duplicator
|
||||||
|
description: A Helm chart for Kubernetes
|
||||||
|
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.0.1
|
||||||
|
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
appVersion: 0.0.1
|
||||||
142
charts/secret-duplicator/scripts/create-signed-cert.sh
Normal file
142
charts/secret-duplicator/scripts/create-signed-cert.sh
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Generate certificate suitable for use with an secret-duplicator webhook service.
|
||||||
|
|
||||||
|
This script uses k8s' CertificateSigningRequest API to a generate a
|
||||||
|
certificate signed by k8s CA suitable for use with secret-duplicator webhook
|
||||||
|
services. This requires permissions to create and approve CSR. See
|
||||||
|
https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster for
|
||||||
|
detailed explanation and additional instructions.
|
||||||
|
|
||||||
|
The server key/cert k8s CA cert are stored in a k8s secret.
|
||||||
|
|
||||||
|
usage: ${0} [OPTIONS]
|
||||||
|
|
||||||
|
The following flags are required.
|
||||||
|
|
||||||
|
--service Service name of webhook.
|
||||||
|
--namespace Namespace where webhook service and secret reside.
|
||||||
|
--secret Secret name for CA certificate and server certificate/key pair.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case ${1} in
|
||||||
|
--service)
|
||||||
|
service="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--secret)
|
||||||
|
secret="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--namespace)
|
||||||
|
namespace="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -z "${service}" ] && service=ips-injector-svc
|
||||||
|
[ -z "${secret}" ] && secret=descret-duplicator-webhook-certs
|
||||||
|
[ -z "${namespace}" ] && namespace=secret-duplicator
|
||||||
|
|
||||||
|
if [ ! -x "$(command -v openssl)" ]; then
|
||||||
|
echo "openssl not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
csrName=${service}.${namespace}
|
||||||
|
tmpdir=$(mktemp -d)
|
||||||
|
echo "Creating certs in tmpdir ${tmpdir}"
|
||||||
|
|
||||||
|
cat <<EOF >> "${tmpdir}"/csr.conf
|
||||||
|
[req]
|
||||||
|
req_extensions = v3_req
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
[req_distinguished_name]
|
||||||
|
[ v3_req ]
|
||||||
|
basicConstraints = CA:FALSE
|
||||||
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = ${service}
|
||||||
|
DNS.2 = ${service}.${namespace}
|
||||||
|
DNS.3 = ${service}.${namespace}.svc
|
||||||
|
EOF
|
||||||
|
|
||||||
|
openssl genrsa -out "${tmpdir}"/server-key.pem 2048
|
||||||
|
openssl req -new -key "${tmpdir}"/server-key.pem -subj "/O=system:nodes/CN=system:node:${service}.${namespace}.svc" -out "${tmpdir}"/server.csr -config "${tmpdir}"/csr.conf
|
||||||
|
|
||||||
|
# clean-up any previously created CSR for our service. Ignore errors if not present.
|
||||||
|
echo "Deleting old CertificateSigningRequests"
|
||||||
|
kubectl delete csr ${csrName} 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Creating new CertificateSigningRequests"
|
||||||
|
# create server cert/key CSR and send to k8s API
|
||||||
|
jq -n --arg request "$(< "${tmpdir}"/server.csr base64 -w0)" \
|
||||||
|
--arg namespace "$namespace" \
|
||||||
|
--arg csrName "$csrName" '{
|
||||||
|
apiVersion: "certificates.k8s.io/v1beta1",
|
||||||
|
kind: "CertificateSigningRequest",
|
||||||
|
metadata: {
|
||||||
|
name: $csrName,
|
||||||
|
namespace: $namespace
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
signerName: "kubernetes.io/kubelet-serving",
|
||||||
|
groups: ["system:authenticated"],
|
||||||
|
request: $request,
|
||||||
|
usages: [
|
||||||
|
"digital signature",
|
||||||
|
"key encipherment",
|
||||||
|
"server auth"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}' | kubectl create -f -
|
||||||
|
|
||||||
|
# verify CSR has been created
|
||||||
|
while true; do
|
||||||
|
if kubectl get csr ${csrName}; then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Approving CertificateSigningRequests"
|
||||||
|
# approve and fetch the signed certificate
|
||||||
|
kubectl certificate approve ${csrName}
|
||||||
|
|
||||||
|
echo "Fetching certificate from approved CertificateSigningRequests"
|
||||||
|
# verify certificate has been signed
|
||||||
|
for _ in $(seq 10); do
|
||||||
|
serverCert=$(kubectl get csr ${csrName} -o jsonpath='{.status.certificate}')
|
||||||
|
if [[ ${serverCert} != '' ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [[ ${serverCert} == '' ]]; then
|
||||||
|
echo "ERROR: After approving csr ${csrName}, the signed certificate did not appear on the resource. Giving up after 10 attempts." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "${serverCert}" | openssl base64 -d -A -out "${tmpdir}"/server-cert.pem
|
||||||
|
|
||||||
|
echo "Creating secret $secret based on the retrieved certificate"
|
||||||
|
# create the secret with CA cert and server cert/key
|
||||||
|
kubectl create secret generic ${secret} \
|
||||||
|
--from-file=key.pem="${tmpdir}"/server-key.pem \
|
||||||
|
--from-file=cert.pem="${tmpdir}"/server-cert.pem \
|
||||||
|
--dry-run=client -o yaml |
|
||||||
|
kubectl -n ${namespace} apply -f -
|
||||||
81
charts/secret-duplicator/templates/_helpers.tpl
Normal file
81
charts/secret-duplicator/templates/_helpers.tpl
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{{/* vim: set filetype=mustache: */}}
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "secret-duplicator.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "secret-duplicator.serviceName" -}}
|
||||||
|
ips-injector-svc
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "secret-duplicator.certificateSecretName" -}}
|
||||||
|
{{ include "secret-duplicator.name" . }}-webhook-certs
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "secret-duplicator.lookupCaBundle" -}}
|
||||||
|
{{- /* Find the name of the secret corresponding to the default SA in the default namespace */ -}}
|
||||||
|
{{- /* Equivalent to `kubectl get sa -n default default -ojsonpath='{.secrets[0].name}'` */ -}}
|
||||||
|
{{- $defaultSecretName := ((lookup "v1" "ServiceAccount" "default" "default").secrets | first).name -}}
|
||||||
|
{{- /* Fetch the ca.crt from the default secret (still base64-encoded)*/ -}}
|
||||||
|
{{- /* Equivalent to `kubectl get secret -n default $defaultSecretName -ojsonpath='{.data.ca\.crt}'` */ -}}
|
||||||
|
{{- $caBundle := get (lookup "v1" "Secret" "default" $defaultSecretName ).data "ca.crt" -}}
|
||||||
|
{{- $caBundle -}}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "secret-duplicator.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "secret-duplicator.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "secret-duplicator.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "secret-duplicator.chart" . }}
|
||||||
|
{{ include "secret-duplicator.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "secret-duplicator.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "secret-duplicator.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "secret-duplicator.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "secret-duplicator.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: secret-duplicator-cert-gen-entrypoint
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
entrypoint.sh: | {{ .Files.Get "scripts/create-signed-cert.sh" | nindent 4 }}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
apiVersion: batch/v1beta1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-cert-gen-cron-job"
|
||||||
|
labels:
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
schedule: '* * * * 0'
|
||||||
|
jobTemplate:
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}"
|
||||||
|
labels:
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 30
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: secret-duplicator-cert-gen
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: pre-install-job
|
||||||
|
image: "{{ .Values.certificateGeneratorImage.registry }}/{{ .Values.certificateGeneratorImage.repository }}:{{ .Values.certificateGeneratorImage.tag | default .Chart.AppVersion }}"
|
||||||
|
command: ["/entrypoint/entrypoint.sh"]
|
||||||
|
args:
|
||||||
|
- --service
|
||||||
|
- "{{ include "secret-duplicator.serviceName" . }}"
|
||||||
|
- --namespace
|
||||||
|
- "{{ .Release.Namespace }}"
|
||||||
|
- --secret
|
||||||
|
- "{{ include "secret-duplicator.certificateSecretName" . }}"
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: "/entrypoint"
|
||||||
|
name: entrypoint
|
||||||
|
volumes:
|
||||||
|
- name: entrypoint
|
||||||
|
configMap:
|
||||||
|
name: secret-duplicator-cert-gen-entrypoint
|
||||||
|
items:
|
||||||
|
- key: entrypoint.sh
|
||||||
|
path: entrypoint.sh
|
||||||
|
mode: 0755
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-cert-gen-job"
|
||||||
|
labels:
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 30
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: secret-duplicator-cert-gen
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: pre-install-job
|
||||||
|
image: "{{ .Values.certificateGeneratorImage.registry }}/{{ .Values.certificateGeneratorImage.repository }}:{{ .Values.certificateGeneratorImage.tag | default .Chart.AppVersion }}"
|
||||||
|
command: ["/entrypoint/entrypoint.sh"]
|
||||||
|
args:
|
||||||
|
- --service
|
||||||
|
- "{{ include "secret-duplicator.serviceName" . }}"
|
||||||
|
- --namespace
|
||||||
|
- "{{ .Release.Namespace }}"
|
||||||
|
- --secret
|
||||||
|
- "{{ include "secret-duplicator.certificateSecretName" . }}"
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: "/entrypoint"
|
||||||
|
name: entrypoint
|
||||||
|
volumes:
|
||||||
|
- name: entrypoint
|
||||||
|
configMap:
|
||||||
|
name: secret-duplicator-cert-gen-entrypoint
|
||||||
|
items:
|
||||||
|
- key: entrypoint.sh
|
||||||
|
path: entrypoint.sh
|
||||||
|
mode: 0755
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: secret-duplicator-cert-gen
|
||||||
|
namespace : {{ .Release.Namespace }}
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: secret-duplicator-cert-gen
|
||||||
|
name: secret-duplicator-cert-gen
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
- serviceaccounts
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- create
|
||||||
|
- get
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- certificates.k8s.io
|
||||||
|
resources:
|
||||||
|
- certificatesigningrequests
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- list
|
||||||
|
- get
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- certificates.k8s.io
|
||||||
|
resources:
|
||||||
|
- certificatesigningrequests/approval
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- certificates.k8s.io
|
||||||
|
resources:
|
||||||
|
- signers
|
||||||
|
resourceNames:
|
||||||
|
- kubernetes.io/kubelet-serving
|
||||||
|
verbs:
|
||||||
|
- approve
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: secret-duplicator-cert-gen
|
||||||
|
labels:
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: secret-duplicator-cert-gen
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: secret-duplicator-cert-gen
|
||||||
|
namespace : {{ .Release.Namespace }}
|
||||||
7
charts/secret-duplicator/templates/cm.yaml
Normal file
7
charts/secret-duplicator/templates/cm.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: tmp
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
data:
|
||||||
|
caBundle: {{ include "secret-duplicator.lookupCaBundle" . | quote }}
|
||||||
48
charts/secret-duplicator/templates/deployment.yaml
Normal file
48
charts/secret-duplicator/templates/deployment.yaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: secret-duplicator-webhook-deployment
|
||||||
|
namespace : {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
app: secret-duplicator
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: secret-duplicator
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: secret-duplicator
|
||||||
|
spec:
|
||||||
|
serviceAccountName: secret-duplicator
|
||||||
|
containers:
|
||||||
|
- name: secret-duplicator
|
||||||
|
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- -alsologtostderr
|
||||||
|
- -v=4
|
||||||
|
- 2>&1
|
||||||
|
env:
|
||||||
|
- name: CONFIG_PORT
|
||||||
|
value: "8443"
|
||||||
|
- name: CONFIG_CERT_PATH
|
||||||
|
value: "/etc/webhook/certs/cert.pem"
|
||||||
|
- name: CONFIG_KEY_PATH
|
||||||
|
value: "/etc/webhook/certs/key.pem"
|
||||||
|
- name: CONFIG_EXCLUDE_NAMESPACES
|
||||||
|
value: {{ join "," .Values.secretDuplicator.excludeNamespaces | quote }}
|
||||||
|
- name: CONFIG_TARGET_SECRET_NAME
|
||||||
|
value: {{ .Values.secretDuplicator.targetSecretName | quote }}
|
||||||
|
- name: CONFIG_TARGET_SECRET_ANNOTATION
|
||||||
|
value: {{ .Values.secretDuplicator.targetSecretAnnotation | quote }}
|
||||||
|
volumeMounts:
|
||||||
|
- name: webhook-certs
|
||||||
|
mountPath: /etc/webhook/certs
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: webhook-certs
|
||||||
|
secret:
|
||||||
|
secretName: {{ include "secret-duplicator.certificateSecretName" . }}
|
||||||
25
charts/secret-duplicator/templates/mutatingwebhook.yaml
Normal file
25
charts/secret-duplicator/templates/mutatingwebhook.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
kind: MutatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: secret-duplicator-webhook-cfg
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
app: secret-duplicator
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
webhooks:
|
||||||
|
- name: secret-duplicator.ysoftdevs.github.com
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: {{ include "secret-duplicator.serviceName" . }}
|
||||||
|
namespace : {{ .Release.Namespace }}
|
||||||
|
path: "/mutate"
|
||||||
|
caBundle: {{ include "secret-duplicator.lookupCaBundle" . }}
|
||||||
|
rules:
|
||||||
|
- operations: ["CREATE", "UPDATE"]
|
||||||
|
apiGroups: [""]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
resources: ["serviceaccounts"]
|
||||||
|
admissionReviewVersions: ["v1", "v1beta1"]
|
||||||
|
sideEffects: None
|
||||||
|
# The default "Fail" option prevents Gardener cluster to be hibernated
|
||||||
|
failurePolicy: Ignore
|
||||||
72
charts/secret-duplicator/templates/rbac.yaml
Normal file
72
charts/secret-duplicator/templates/rbac.yaml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: secret-duplicator
|
||||||
|
namespace : {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: secret-duplicator
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
name: secret-duplicator
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
- serviceaccounts
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- create
|
||||||
|
- get
|
||||||
|
- delete
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- "certificates.k8s.io/v1"
|
||||||
|
resources:
|
||||||
|
- certificatesigningrequests
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- list
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- "certificates.k8s.io/v1"
|
||||||
|
resources:
|
||||||
|
- certificatesigningrequests/approval
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- certificates.k8s.io
|
||||||
|
resources:
|
||||||
|
- signers
|
||||||
|
resourceNames:
|
||||||
|
- kubernetes.io/kubelet-serving
|
||||||
|
verbs:
|
||||||
|
- approve
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: secret-duplicator
|
||||||
|
labels:
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: secret-duplicator
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: secret-duplicator
|
||||||
|
namespace : {{ .Release.Namespace }}
|
||||||
13
charts/secret-duplicator/templates/service.yaml
Normal file
13
charts/secret-duplicator/templates/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secret-duplicator.serviceName" . }}
|
||||||
|
namespace : {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
app: secret-duplicator
|
||||||
|
{{- include "secret-duplicator.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
targetPort: 8443
|
||||||
|
selector: {{ include "secret-duplicator.selectorLabels" . | nindent 4 }}
|
||||||
19
charts/secret-duplicator/values.yaml
Normal file
19
charts/secret-duplicator/values.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
image:
|
||||||
|
registry: ghcr.io/ysoftdevs/secret-duplicator
|
||||||
|
repository: secret-duplicator
|
||||||
|
pullPolicy: Always
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: ""
|
||||||
|
|
||||||
|
certificateGeneratorImage:
|
||||||
|
registry: ghcr.io/ysoftdevs/secret-duplicator
|
||||||
|
repository: webhook-cert-generator
|
||||||
|
tag: ""
|
||||||
|
|
||||||
|
secretDuplicator:
|
||||||
|
targetSecretName: "dashboard-terminal-kube-apiserver-tls"
|
||||||
|
targetSecretAnnotation: "reflector.v1.k8s.emberstack.com/reflects=cert-manager/default-cert"
|
||||||
|
excludeNamespaces:
|
||||||
|
- kube-system
|
||||||
|
- traefik
|
||||||
|
- datadog
|
||||||
69
cmd/main.go
Normal file
69
cmd/main.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
parameters := DefaultParametersObject()
|
||||||
|
|
||||||
|
// get command line parameters
|
||||||
|
flag.IntVar(¶meters.port, "port", LookupIntEnv("CONFIG_PORT", parameters.port), "Webhook server port.")
|
||||||
|
flag.StringVar(¶meters.certFile, "tlsCertFile", LookupStringEnv("CONFIG_CERT_PATH", parameters.certFile), "File containing the x509 Certificate for HTTPS.")
|
||||||
|
flag.StringVar(¶meters.keyFile, "tlsKeyFile", LookupStringEnv("CONFIG_KEY_PATH", parameters.keyFile), "File containing the x509 private key to --tlsCertFile.")
|
||||||
|
flag.StringVar(¶meters.excludeNamespaces, "excludeNamespaces", LookupStringEnv("CONFIG_EXCLUDE_NAMESPACES", parameters.excludeNamespaces), "Comma-separated namespace names to ignore.")
|
||||||
|
|
||||||
|
flag.StringVar(¶meters.targetSecretAnnotation, "targetSecretAnnotation", LookupStringEnv("CONFIG_TARGET_SECRET_ANNOTATION", parameters.targetSecretAnnotation), "Annotation of the targetSecret secret we will create in the namespace")
|
||||||
|
flag.StringVar(¶meters.targetSecretName, "targetSecretName", LookupStringEnv("CONFIG_TARGET_SECRET_NAME", parameters.targetSecretName), "Name of the targetSecret secret we will create in the namespace")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
glog.Infof("Running with config: %+v", parameters)
|
||||||
|
|
||||||
|
whsvr, err := NewWebhookServer(
|
||||||
|
¶meters,
|
||||||
|
&http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%v", parameters.port),
|
||||||
|
// This is quite inefficient as it loads file contents on every TLS ClientHello, but ¯\_(ツ)_/¯
|
||||||
|
TLSConfig: &tls.Config{GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
glog.Infof("Loading certificates")
|
||||||
|
cert, err := tls.LoadX509KeyPair(parameters.certFile, parameters.keyFile)
|
||||||
|
return &cert, err
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
glog.Exitf("Could not create the Webhook server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// define http server and server handler
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/mutate", whsvr.serve)
|
||||||
|
whsvr.server.Handler = mux
|
||||||
|
|
||||||
|
// start webhook server in new rountine
|
||||||
|
go func() {
|
||||||
|
if err := whsvr.server.ListenAndServeTLS(parameters.certFile, parameters.keyFile); err != nil {
|
||||||
|
glog.Errorf("Failed to listen and serve webhook server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// listening OS shutdown singal
|
||||||
|
signalChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-signalChan
|
||||||
|
|
||||||
|
glog.Infof("Got OS shutdown signal, shutting down webhook server gracefully...")
|
||||||
|
if err := whsvr.server.Shutdown(context.Background()); err != nil {
|
||||||
|
glog.Errorf("Error while shutting down: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
cmd/utils.go
Normal file
55
cmd/utils.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LookupStringEnv either returns the the value of the env variable, or the provided default value, if the env doesn't exist
|
||||||
|
func LookupStringEnv(envName string, defVal string) string {
|
||||||
|
if envVal, exists := os.LookupEnv(envName); exists {
|
||||||
|
return envVal
|
||||||
|
}
|
||||||
|
|
||||||
|
return defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupBoolEnv either returns the the value of the env variable, or the provided default value, if the env doesn't exist
|
||||||
|
func LookupBoolEnv(envName string, defVal bool) bool {
|
||||||
|
if envVal, exists := os.LookupEnv(envName); exists {
|
||||||
|
if boolVal, err := strconv.ParseBool(envVal); err == nil {
|
||||||
|
return boolVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIntEnv either returns the the value of the env variable, or the provided default value, if the env doesn't exist
|
||||||
|
func LookupIntEnv(envName string, defVal int) int {
|
||||||
|
if envVal, exists := os.LookupEnv(envName); exists {
|
||||||
|
if intVal, err := strconv.Atoi(envVal); err == nil {
|
||||||
|
return intVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentNamespace() string {
|
||||||
|
// Check whether we have overridden the namespace
|
||||||
|
if ns, ok := os.LookupEnv("POD_NAMESPACE"); ok {
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the namespace associated with the service account token, if available (this should exist if running in a K8S pod)
|
||||||
|
if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
|
||||||
|
if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
317
cmd/webhook.go
Normal file
317
cmd/webhook.go
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/api/admission/v1beta1"
|
||||||
|
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
runtimeScheme = runtime.NewScheme()
|
||||||
|
codecs = serializer.NewCodecFactory(runtimeScheme)
|
||||||
|
deserializer = codecs.UniversalDeserializer()
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebhookServer struct {
|
||||||
|
server *http.Server
|
||||||
|
config *WhSvrParameters
|
||||||
|
client *kubernetes.Clientset
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhSvrParameters represents all configuration options available though cmd parameters or env variables
|
||||||
|
type WhSvrParameters struct {
|
||||||
|
port int
|
||||||
|
certFile string
|
||||||
|
keyFile string
|
||||||
|
excludeNamespaces string
|
||||||
|
targetSecretName string
|
||||||
|
targetSecretAnnotation string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultIgnoredNamespaces = []string{
|
||||||
|
metav1.NamespaceSystem,
|
||||||
|
metav1.NamespacePublic,
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultServiceAccounts = []string{
|
||||||
|
"default",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWebhookServer constructor for WebhookServer
|
||||||
|
func NewWebhookServer(parameters *WhSvrParameters, server *http.Server) (*WebhookServer, error) {
|
||||||
|
config, err := rest.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not create k8s client: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientset, err := kubernetes.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not create k8s clientset: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WebhookServer{
|
||||||
|
config: parameters,
|
||||||
|
server: server,
|
||||||
|
client: clientset,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultParametersObject returns a parameters object with the default values
|
||||||
|
func DefaultParametersObject() WhSvrParameters {
|
||||||
|
return WhSvrParameters{
|
||||||
|
port: 8443,
|
||||||
|
certFile: "/etc/webhook/certs/cert.pem",
|
||||||
|
keyFile: "/etc/webhook/certs/key.pem",
|
||||||
|
excludeNamespaces: strings.Join(defaultIgnoredNamespaces, ","),
|
||||||
|
targetSecretName: "dashboard-terminal-kube-apiserver-tls",
|
||||||
|
targetSecretAnnotation: "reflector.v1.k8s.emberstack.com/reflects=cert-manager/default-cert",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type patchOperation struct {
|
||||||
|
Op string `json:"op"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Value interface{} `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_ = corev1.AddToScheme(runtimeScheme)
|
||||||
|
_ = admissionregistrationv1beta1.AddToScheme(runtimeScheme)
|
||||||
|
// defaulting with webhooks:
|
||||||
|
// https://github.com/kubernetes/kubernetes/issues/57982
|
||||||
|
_ = v1.AddToScheme(runtimeScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addImagePullSecret(target, added []corev1.LocalObjectReference, basePath string) (patch []patchOperation) {
|
||||||
|
first := len(target) == 0
|
||||||
|
var value interface{}
|
||||||
|
for _, add := range added {
|
||||||
|
value = add
|
||||||
|
path := basePath
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
value = []corev1.LocalObjectReference{add}
|
||||||
|
} else {
|
||||||
|
path = path + "/-"
|
||||||
|
}
|
||||||
|
patch = append(patch, patchOperation{
|
||||||
|
Op: "add",
|
||||||
|
Path: path,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return patch
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureSecrets looks up the target secret and makes sure the target secret exists and contains annotations
|
||||||
|
func (whsvr *WebhookServer) ensureSecrets(ar *v1beta1.AdmissionReview) error {
|
||||||
|
glog.Infof("Ensuring existing secrets")
|
||||||
|
targetNamespace := ar.Request.Namespace
|
||||||
|
|
||||||
|
glog.Infof("Looking for the existing target secret")
|
||||||
|
secret, err := whsvr.client.CoreV1().Secrets(targetNamespace).Get(whsvr.config.targetSecretName, metav1.GetOptions{})
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
glog.Errorf("Could not fetch secret %s in namespace %s: %v", whsvr.config.targetSecretName, targetNamespace, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && errors.IsNotFound(err) {
|
||||||
|
glog.Infof("Target secret not found, creating a new one")
|
||||||
|
if _, createErr := whsvr.client.CoreV1().Secrets(targetNamespace).Create(&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: whsvr.config.targetSecretName,
|
||||||
|
Namespace: targetNamespace,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
Type: corev1.SecretTypeOpaque,
|
||||||
|
}); createErr != nil {
|
||||||
|
glog.Errorf("Could not create secret %s in namespace %s: %v", whsvr.config.targetSecretName, targetNamespace, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
glog.Infof("Target secret created successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("Target secret found, updating")
|
||||||
|
annotationData := strings.Split(whsvr.config.targetSecretAnnotation, "=")
|
||||||
|
secret.Annotations[annotationData[0]] = annotationData[1]
|
||||||
|
if _, err := whsvr.client.CoreV1().Secrets(targetNamespace).Update(secret); err != nil {
|
||||||
|
glog.Errorf("Could not update secret %s in namespace %s: %v", whsvr.config.targetSecretName, targetNamespace, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
glog.Infof("Target secret updated successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldMutate goes through all filters and determines whether the incoming NS matches them
|
||||||
|
func (whsvr *WebhookServer) shouldMutate(ns corev1.Namespace) bool {
|
||||||
|
for _, excludedNamespace := range strings.Split(whsvr.config.excludeNamespaces, ",") {
|
||||||
|
if ns.Name == excludedNamespace {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutateNamespace contains the whole mutation logic
|
||||||
|
func (whsvr *WebhookServer) mutateNamespace(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||||
|
req := ar.Request
|
||||||
|
glog.Infof("Unmarshalling the Namespace object from request")
|
||||||
|
var ns corev1.Namespace
|
||||||
|
if err := json.Unmarshal(req.Object.Raw, &ns); err != nil {
|
||||||
|
glog.Errorf("Could not unmarshal raw object: %v", err)
|
||||||
|
return &v1beta1.AdmissionResponse{
|
||||||
|
Result: &metav1.Status{
|
||||||
|
Message: err.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v",
|
||||||
|
req.Kind, req.Namespace, req.Name, ns.Name, req.UID, req.Operation, req.UserInfo)
|
||||||
|
|
||||||
|
if !whsvr.shouldMutate(ns) {
|
||||||
|
glog.Infof("Conditions for mutation not met, skipping")
|
||||||
|
return &v1beta1.AdmissionResponse{
|
||||||
|
Allowed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secretList, err := whsvr.client.CoreV1().Secrets(ns.Name).List(metav1.ListOptions{
|
||||||
|
Watch: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not get secret from namespace: %v", err)
|
||||||
|
return &v1beta1.AdmissionResponse{
|
||||||
|
Result: &metav1.Status{
|
||||||
|
Message: err.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether we already have configured secret with annotation present
|
||||||
|
if secretList != nil {
|
||||||
|
for _, item := range secretList.Items {
|
||||||
|
if item.Name == whsvr.config.targetSecretName {
|
||||||
|
annotationToCheck := strings.Split(whsvr.config.targetSecretAnnotation, "=")
|
||||||
|
if val, ok := item.Annotations[annotationToCheck[0]]; ok {
|
||||||
|
glog.Infof("Namespace is already in the correct state and contains secret %s with value %s=%s, skipping", whsvr.config.targetSecretName, annotationToCheck ,val)
|
||||||
|
return &v1beta1.AdmissionResponse{
|
||||||
|
Allowed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := whsvr.ensureSecrets(ar); err != nil {
|
||||||
|
glog.Errorf("Could not ensure existence of the secret")
|
||||||
|
glog.Errorf("Failing the mutation process")
|
||||||
|
return &v1beta1.AdmissionResponse{
|
||||||
|
Result: &metav1.Status{
|
||||||
|
Message: err.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1beta1.AdmissionResponse{
|
||||||
|
Allowed: true,
|
||||||
|
Patch: nil,
|
||||||
|
PatchType: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIncomingRequest(r *http.Request) (v1beta1.AdmissionReview, *errors.StatusError) {
|
||||||
|
var ar v1beta1.AdmissionReview
|
||||||
|
var body []byte
|
||||||
|
if r.Body != nil {
|
||||||
|
if data, err := ioutil.ReadAll(r.Body); err == nil {
|
||||||
|
body = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(body) == 0 {
|
||||||
|
glog.Error("Empty body")
|
||||||
|
return ar, errors.NewBadRequest("Empty body")
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify the content type is accurate
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType != "application/json" {
|
||||||
|
glog.Errorf("Content-Type=%s, expect application/json", contentType)
|
||||||
|
err := &errors.StatusError{ErrStatus: metav1.Status{
|
||||||
|
Status: metav1.StatusFailure,
|
||||||
|
Message: fmt.Sprintf("Content-Type=%s, expect application/json", contentType),
|
||||||
|
Reason: metav1.StatusReasonUnsupportedMediaType,
|
||||||
|
Code: http.StatusUnsupportedMediaType,
|
||||||
|
}}
|
||||||
|
return ar, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
|
||||||
|
glog.Error("Could not parse the request body")
|
||||||
|
return ar, errors.NewBadRequest(fmt.Sprintf("Could not parse the request body: %+v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (whsvr *WebhookServer) sendResponse(w http.ResponseWriter, admissionReview v1beta1.AdmissionReview) error {
|
||||||
|
resp, err := json.Marshal(admissionReview)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Can't encode response: %v", err)
|
||||||
|
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
glog.Infof("Writing response")
|
||||||
|
if _, err := w.Write(resp); err != nil {
|
||||||
|
glog.Errorf("Can't write response: %v", err)
|
||||||
|
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serve parses the raw incoming request, calls the mutation logic and sends the proper response
|
||||||
|
func (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
||||||
|
admissionReviewIn, statusErr := parseIncomingRequest(r)
|
||||||
|
if statusErr != nil {
|
||||||
|
http.Error(w, statusErr.ErrStatus.Message, int(statusErr.ErrStatus.Code))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
admissionResponse := whsvr.mutateNamespace(&admissionReviewIn)
|
||||||
|
|
||||||
|
admissionReviewOut := v1beta1.AdmissionReview{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "AdmissionReview", APIVersion: "admission.k8s.io/v1"},
|
||||||
|
}
|
||||||
|
if admissionResponse != nil {
|
||||||
|
admissionReviewOut.Response = admissionResponse
|
||||||
|
if admissionReviewIn.Request != nil {
|
||||||
|
admissionReviewOut.Response.UID = admissionReviewIn.Request.UID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := whsvr.sendResponse(w, admissionReviewOut); err != nil {
|
||||||
|
glog.Errorf("Could not send response %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
go.mod
Normal file
10
go.mod
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module github.com/ysoftdevs/imagepullsecret-injector
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||||
|
k8s.io/api v0.17.0
|
||||||
|
k8s.io/apimachinery v0.17.0
|
||||||
|
k8s.io/client-go v0.17.0
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.15
|
||||||
189
go.sum
Normal file
189
go.sum
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||||
|
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
|
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
|
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
|
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||||
|
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||||
|
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||||
|
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||||
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||||
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
|
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
|
||||||
|
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||||
|
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||||
|
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
|
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
|
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||||
|
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
|
||||||
|
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||||
|
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
|
||||||
|
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||||
|
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
|
||||||
|
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||||
|
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||||
|
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
|
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
|
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||||
|
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||||
|
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||||
|
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||||
|
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||||
|
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||||
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
Reference in New Issue
Block a user