mirror of
https://github.com/ysoftdevs/secret-duplicator.git
synced 2026-01-18 16:06:58 +01: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