From 5d6648aff32a27bbd1640954eb1f56bfe76aa6da Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Mon, 4 Dec 2023 18:29:06 -0700 Subject: [PATCH 01/11] feat(reconciler): Refactor source and Helm chart to use K8s reconciler (from informer model) --- .DS_Store | Bin 0 -> 6148 bytes .../keyfactor-extension-generate-readme.yml | 27 - .gitignore | 372 +++++ Dockerfile | 38 +- Makefile | 106 ++ README.md | 77 +- README.md.tpl | 65 - app.go | 23 - charts/.helmignore | 23 - charts/Chart.yaml | 6 - charts/ca.proto | 50 - charts/templates/NOTES.txt | 38 - charts/templates/clusterrolebinding.yaml | 31 - charts/templates/deployment.yaml | 102 -- charts/templates/hpa.yaml | 32 - charts/templates/ingress.yaml | 41 - charts/templates/metadata-configmap.yaml | 10 - charts/templates/role.yaml | 12 - charts/templates/service.yaml | 15 - charts/templates/tests/test-connection.yaml | 15 - charts/values.yaml | 90 - cmd/root.go | 79 - cmd/start.go | 110 -- command-signer-config.yaml | 26 + config/config.example | 14 - credentials/credentials.yaml | 23 - deploy/charts/k8s-csr-signer/Chart.yaml | 12 + .../charts/k8s-csr-signer/templates/NOTES.txt | 5 + .../k8s-csr-signer}/templates/_helpers.tpl | 35 +- .../templates/clusterrole.yaml | 37 +- .../templates/clusterrolebinding.yaml | 14 + .../k8s-csr-signer/templates/deployment.yaml | 66 + .../charts/k8s-csr-signer/templates/role.yaml | 26 + .../k8s-csr-signer/templates/rolebinding.yaml | 14 + .../templates/serviceaccount.yaml | 6 +- .../templates/tests/test-connection.yaml | 15 + .../charts/k8s-csr-signer/values.schema.json | 98 ++ deploy/charts/k8s-csr-signer/values.yaml | 72 + docs/annotations.markdown | 49 + docs/getting-started.markdown | 155 ++ docs/istio-deployment.markdown | 213 +++ docs/testing.markdown | 15 + go.mod | 93 +- go.sum | 1450 ++--------------- .../certificatesigningrequest_controller.go | 171 ++ ...rtificatesigningrequest_controller_test.go | 261 +++ internal/controllers/fake_signer_test.go | 72 + internal/gateway/certificate-enroll.go | 43 - internal/gateway/istio-secret.go | 93 -- internal/gateway/service.go | 125 -- internal/gateway/tls.go | 191 --- internal/health/service.go | 48 - internal/signer/controller.go | 239 --- internal/signer/handler.go | 116 -- internal/signer/handler_test.go | 119 -- internal/signer/signer.go | 377 +++++ internal/signer/signer_test.go | 591 +++++++ main.go | 161 ++ node_modules/.yarn-integrity | 10 - pkg/config/config.go | 100 -- pkg/env/env.go | 44 - pkg/k8s/client.go | 69 - pkg/k8s/jwt.go | 154 -- pkg/keyfactor/client.go | 228 --- pkg/keyfactor/credential.go | 87 - pkg/keyfactor/metadata.go | 142 -- pkg/keyfactor/mock/client.go | 39 - pkg/logger/logger.go | 29 - pkg/util/generate_csr.go | 112 -- pkg/util/k8s.go | 21 - pkg/util/san.go | 203 --- pkg/util/spiffe.go | 43 - pkg/util/util.go | 102 ++ pkg/util/util_test.go | 82 + sample/sample.yaml | 16 + sample/test-csr.yaml | 17 - 76 files changed, 3407 insertions(+), 4498 deletions(-) create mode 100644 .DS_Store delete mode 100644 .github/workflows/keyfactor-extension-generate-readme.yml create mode 100644 .gitignore create mode 100644 Makefile delete mode 100644 README.md.tpl delete mode 100644 app.go delete mode 100644 charts/.helmignore delete mode 100644 charts/Chart.yaml delete mode 100644 charts/ca.proto delete mode 100644 charts/templates/NOTES.txt delete mode 100644 charts/templates/clusterrolebinding.yaml delete mode 100644 charts/templates/deployment.yaml delete mode 100644 charts/templates/hpa.yaml delete mode 100644 charts/templates/ingress.yaml delete mode 100644 charts/templates/metadata-configmap.yaml delete mode 100644 charts/templates/role.yaml delete mode 100644 charts/templates/service.yaml delete mode 100644 charts/templates/tests/test-connection.yaml delete mode 100644 charts/values.yaml delete mode 100644 cmd/root.go delete mode 100644 cmd/start.go create mode 100644 command-signer-config.yaml delete mode 100644 config/config.example delete mode 100644 credentials/credentials.yaml create mode 100644 deploy/charts/k8s-csr-signer/Chart.yaml create mode 100644 deploy/charts/k8s-csr-signer/templates/NOTES.txt rename {charts => deploy/charts/k8s-csr-signer}/templates/_helpers.tpl (64%) rename {charts => deploy/charts/k8s-csr-signer}/templates/clusterrole.yaml (55%) create mode 100644 deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml create mode 100644 deploy/charts/k8s-csr-signer/templates/deployment.yaml create mode 100644 deploy/charts/k8s-csr-signer/templates/role.yaml create mode 100644 deploy/charts/k8s-csr-signer/templates/rolebinding.yaml rename {charts => deploy/charts/k8s-csr-signer}/templates/serviceaccount.yaml (61%) create mode 100644 deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml create mode 100644 deploy/charts/k8s-csr-signer/values.schema.json create mode 100644 deploy/charts/k8s-csr-signer/values.yaml create mode 100644 docs/annotations.markdown create mode 100644 docs/getting-started.markdown create mode 100644 docs/istio-deployment.markdown create mode 100644 docs/testing.markdown create mode 100644 internal/controllers/certificatesigningrequest_controller.go create mode 100644 internal/controllers/certificatesigningrequest_controller_test.go create mode 100644 internal/controllers/fake_signer_test.go delete mode 100644 internal/gateway/certificate-enroll.go delete mode 100644 internal/gateway/istio-secret.go delete mode 100644 internal/gateway/service.go delete mode 100644 internal/gateway/tls.go delete mode 100644 internal/health/service.go delete mode 100644 internal/signer/controller.go delete mode 100644 internal/signer/handler.go delete mode 100644 internal/signer/handler_test.go create mode 100644 internal/signer/signer.go create mode 100644 internal/signer/signer_test.go create mode 100644 main.go delete mode 100644 node_modules/.yarn-integrity delete mode 100644 pkg/config/config.go delete mode 100644 pkg/env/env.go delete mode 100644 pkg/k8s/client.go delete mode 100644 pkg/k8s/jwt.go delete mode 100644 pkg/keyfactor/client.go delete mode 100644 pkg/keyfactor/credential.go delete mode 100644 pkg/keyfactor/metadata.go delete mode 100644 pkg/keyfactor/mock/client.go delete mode 100644 pkg/logger/logger.go delete mode 100644 pkg/util/generate_csr.go delete mode 100644 pkg/util/k8s.go delete mode 100644 pkg/util/san.go delete mode 100644 pkg/util/spiffe.go create mode 100644 pkg/util/util.go create mode 100644 pkg/util/util_test.go create mode 100644 sample/sample.yaml delete mode 100644 sample/test-csr.yaml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..af284a001acffc5e71386f5ff8959593769431ff GIT binary patch literal 6148 zcmeHK%}N774E|DAEC|x09)x`X3%0|;xMz70DU;v=crYNd(h)#E!ddWE>ipH3%>&1Ls&pFnBe?j!@8HQM) z!glZcSF{a3Ozcw*=~rQid(7INa=zzzM!yZV^jzT?GinuT&RpP$qYXz7w7qbCh({uA z?TEbB>+*M(I(XG=}*jcf>LQsE)FxkC!a=!1b^pv}O64+on6Oa7HQi~RPI7zG2tz&~R^vhj30 z;zy<2`s4F7*XC?@Y%1#4s6wG`shDV~m;=2>PR;1{Ty@%ugpH#{(Ro`=jE8^`5?wIx H3k-Y$u_i=4 literal 0 HcmV?d00001 diff --git a/.github/workflows/keyfactor-extension-generate-readme.yml b/.github/workflows/keyfactor-extension-generate-readme.yml deleted file mode 100644 index 8b82c7e..0000000 --- a/.github/workflows/keyfactor-extension-generate-readme.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Update README -on: [push, workflow_dispatch] - -jobs: - update_readme: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@master - - - uses: cuchi/jinja2-action@v1.2.0 - with: - template: README.md.tpl - output_file: README.md - data_file: integration-manifest.json - env: - GITHUB_TOKEN: ${{ secrets.SDK_SYNC_PAT }} - - - uses: stefanzweifel/git-auto-commit-action@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - push_options: '--force' - commit_message: Update generated README - commit_user_name: Keyfactor - commit_user_email: keyfactor@keyfactor.github.io - commit_author: Keyfactor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f71207d --- /dev/null +++ b/.gitignore @@ -0,0 +1,372 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +.idea/ +*.tgz +*.pem +*.crt +*.key +credentials.yaml + +vendor \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1531ce8..de13ed5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,33 @@ -# Start by building the application. -FROM golang:1.14-buster as build +# Build the manager binary +FROM golang:1.20 as builder +ARG TARGETOS +ARG TARGETARCH -WORKDIR /go/src/app -ADD . /go/src/app +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download -RUN go get -d -v ./... +# Copy the go source +COPY main.go main.go +COPY pkg/ pkg/ +COPY internal/ internal/ -RUN go build -o /go/bin/app +# Build +# the GOARCH has not a default value to allow the binary be built according to the host where the command +# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, +# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go -FROM gcr.io/distroless/base-debian10 -COPY --from=build /go/bin/app / -CMD ["/app", "start"] \ No newline at end of file +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..10bfad8 --- /dev/null +++ b/Makefile @@ -0,0 +1,106 @@ +# The version which will be reported by the --version argument of each binary +# and which will be used as the Docker image tag +VERSION ?= latest +# The Docker repository name, overridden in CI. +DOCKER_REGISTRY ?= "" +DOCKER_IMAGE_NAME ?= "" +# Image URL to use all building/pushing image targets +IMG ?= ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${VERSION} + +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.26.0 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: fmt vet envtest ## Run tests. + +##@ Build + +.PHONY: regcheck +regcheck: ## Check if the docker registry is set. + @test -n "$(DOCKER_REGISTRY)" || (echo "DOCKER_REGISTRY is not set" && exit 1) + @test -n "$(DOCKER_IMAGE_NAME)" || (echo "DOCKER_IMAGE_NAME is not set" && exit 1) + +.PHONY: build +build: fmt vet ## Build manager binary. + go build -o bin/manager main.go + +.PHONY: run +run: fmt vet ## Run a controller from your host. + go run ./main.go + +# If you wish built the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: regcheck ## Build docker image with the manager. + docker build -t ${IMG} . + +.PHONY: docker-push regcheck +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ +# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> then the export will fail) +# To properly provided solutions that supports more than one platform you should use this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: regcheck ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - docker buildx create --name project-v3-builder + docker buildx use project-v3-builder + - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - docker buildx rm project-v3-builder + rm Dockerfile.cross + +##@ Build Dependencies + +.PHONY: envtest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest diff --git a/README.md b/README.md index 872da42..3eb9f7f 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,32 @@ -# k8s-csr-signer -## api-client + + Kubernetes logo + -Signer for Kubernetes CSR signing API that passes certificate requests to the Keyfactor Web API for signing with a trusted enterprise CA + + Helm logo + - -*** +# Command Certificate Signing Request Proxy for K8s -## Use Cases +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/k8s-csr-signer)](https://goreportcard.com/report/github.com/Keyfactor/k8s-csr-signer) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/keyfactor/k8s-csr-signer?label=release)](https://github.com/keyfactor/k8s-csr-signer/releases) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![license](https://img.shields.io/github/license/keyfactor/k8s-csr-signer.svg)]() -This signer operates within the [kubernetes certificate signing request API](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) and listens for approved CSRs designated for the signer (by default, it matches CSRs with "keyfactor.com/*"). This allows workloads within the cluster or Istio service mesh to obtain trusted identity certificates from an enterprise PKI while providing InfoSec and OpSec teams with insight into the certificates being issued and control over the certificate issuance requirements and content. +The Command Certificate Signing Request Proxy for K8s forwards certificate signing requests generated by Kubernetes to [Keyfactor Command](https://www.keyfactor.com/products/command/) for signing by a trusted enterprise certificate authority. The signer operates within the [K8s CertificateSigningRequests API](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) and implements a Controller that uses the the V1 CertificateSigningRequests informer to handle associated resources. CSRs are only enrolled if they are approved using an [approver](https://github.com/kubernetes/kubernetes/tree/master/pkg/controller/certificates/approver). -## Configuration +## Community supported +We welcome contributions. -1. Configure your Keyfactor environment with an account, API application, and certificate template for enrollment. Information can be found in the Keyfactor reference guide. +The cert-manager external issuer for Keyfactor command is open source and community supported, meaning that there is **no SLA** applicable for these tools. -2. Create the following string metadata fields in your Keyfactor instance: -- Cluster -- Service -- PodName -- PodIP -- PodNamespace -- TrustDomain +###### To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, see the [contribution guidelines](https://github.com/Keyfactor/command-k8s-csr-signer/blob/main/CONTRIBUTING.md) and use the **[Pull requests](../../pulls)** tab. -3. Clone this repository or download and unzip the binary release to a suitable location in your cluster control plane. +## Migration from Command CSR Signer v1.0 to v2.0 -4. Install kubectl, helm, and their dependencies if not already present. +The Command CSR Signer v2.0 has breaking changes from v1.0. To migrate from v1.0 to v2.0, uninstall the v1.0 deployment and install the v2.0 deployment. The v2.0 deployment uses the same configuration as v1.0, but the configuration is now stored in a Kubernetes ConfigMap. See the [Getting Started](docs/getting-started.markdown) to install the v2.0 deployment. -5. Open credentials/credentials.yaml and enter the following information: -\# Endpoint of Keyfactor Platform -endPoint: "http://192.168.0.24" -\# Name of certificate authority for enrollment -caName: "Keyfactor.thedemodrive.com\\Keyfactor Test Drive CA 2 " -\# Basic auth credentials for authentication header: "Basic ...." -authToken: "Basic RE9NQUlOXFVzZXI6UGFzc3dvcmQ=" -\# API path to enroll new certificate from Keyfactor -enrollPath: "/KeyfactorAPI/Enrollment/CSR" -\# Certificate Template for Istio certificate enrollment -caTemplate: "KubernetesNode" -\# ApiKey from Api Setting, to enroll certificates for Istio -appKey: "uYl+FKUbuFpRWg==" -\# ApiKey for auto provisioning TLS server / client certificates -provisioningAppKey: "uYl+FKUbuFpRWg==" -\# CA Template for auto provisioning TLS server / client certificates -provisioningTemplate: "KubernetesNode" - -6. Create the keyfactor namespace with these credentials as a secret: -kubectl create namespace keyfactor -kubectl create secret generic keyfactor-credentials -n keyfactor --from-file credentials/credentials.yaml - -7. Install Keyfactor signer with helm -helm package charts -helm install keyfactor-k8s -n keyfactor ./keyfactor-kubernetes-0.0.1.tgz -f charts/values.yaml - -8. When the pod in the keyfactor namespace is up, you can test the configuration with the provided sample CSR. Note that depending on your selected template and Keyfactor configuration, this may not represent a valid request. -kubectl apply -f sample/test-csr.yaml -kubectl approve TestABCDEFNAME - -After a few seconds, you should be able to see two certificates issued in your Keyfactor instance: one for the pod created in the keyfactor namespace to communicate via mTLS within the cluster, and one from the sample CSR (if the CSR issuance failed, your Keyfactor instance will reflect that instead). - - -*** - -### License -[Apache](https://apache.org/licenses/LICENSE-2.0) +## Documentation +* [Getting Started](docs/getting-started.markdown) +* Usage + * [Demo usage with Istio](docs/istio-deployment.markdown) + * [Runtime Customization](docs/annotations.markdown) +* [Testing](docs/testing.markdown) +* [License](LICENSE) \ No newline at end of file diff --git a/README.md.tpl b/README.md.tpl deleted file mode 100644 index f012530..0000000 --- a/README.md.tpl +++ /dev/null @@ -1,65 +0,0 @@ -# {{ name }} -## {{ integration_type }} - -{{ description }} - - -*** - -## Use Cases - -This signer operates within the [kubernetes certificate signing request API](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) and listens for approved CSRs designated for the signer (by default, it matches CSRs with "keyfactor.com/*"). This allows workloads within the cluster or Istio service mesh to obtain trusted identity certificates from an enterprise PKI while providing InfoSec and OpSec teams with insight into the certificates being issued and control over the certificate issuance requirements and content. - -## Configuration - -1. Configure your Keyfactor environment with an account, API application, and certificate template for enrollment. Information can be found in the Keyfactor reference guide. - -2. Create the following string metadata fields in your Keyfactor instance: -- Cluster -- Service -- PodName -- PodIP -- PodNamespace -- TrustDomain - -3. Clone this repository or download and unzip the binary release to a suitable location in your cluster control plane. - -4. Install kubectl, helm, and their dependencies if not already present. - -5. Open credentials/credentials.yaml and enter the following information: -\# Endpoint of Keyfactor Platform -endPoint: "http://192.168.0.24" -\# Name of certificate authority for enrollment -caName: "Keyfactor.thedemodrive.com\\Keyfactor Test Drive CA 2 " -\# Basic auth credentials for authentication header: "Basic ...." -authToken: "Basic RE9NQUlOXFVzZXI6UGFzc3dvcmQ=" -\# API path to enroll new certificate from Keyfactor -enrollPath: "/KeyfactorAPI/Enrollment/CSR" -\# Certificate Template for Istio certificate enrollment -caTemplate: "KubernetesNode" -\# ApiKey from Api Setting, to enroll certificates for Istio -appKey: "uYl+FKUbuFpRWg==" -\# ApiKey for auto provisioning TLS server / client certificates -provisioningAppKey: "uYl+FKUbuFpRWg==" -\# CA Template for auto provisioning TLS server / client certificates -provisioningTemplate: "KubernetesNode" - -6. Create the keyfactor namespace with these credentials as a secret: -kubectl create namespace keyfactor -kubectl create secret generic keyfactor-credentials -n keyfactor --from-file credentials/credentials.yaml - -7. Install Keyfactor signer with helm -helm package charts -helm install keyfactor-k8s -n keyfactor ./keyfactor-kubernetes-0.0.1.tgz -f charts/values.yaml - -8. When the pod in the keyfactor namespace is up, you can test the configuration with the provided sample CSR. Note that depending on your selected template and Keyfactor configuration, this may not represent a valid request. -kubectl apply -f sample/test-csr.yaml -kubectl approve TestABCDEFNAME - -After a few seconds, you should be able to see two certificates issued in your Keyfactor instance: one for the pod created in the keyfactor namespace to communicate via mTLS within the cluster, and one from the sample CSR (if the CSR issuance failed, your Keyfactor instance will reflect that instead). - - -*** - -### License -[Apache](https://apache.org/licenses/LICENSE-2.0) diff --git a/app.go b/app.go deleted file mode 100644 index 68cafda..0000000 --- a/app.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package main - -import ( - "github.com/Keyfactor/k8s-proxy/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/charts/.helmignore b/charts/.helmignore deleted file mode 100644 index 0e8a0eb..0000000 --- a/charts/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# 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/ diff --git a/charts/Chart.yaml b/charts/Chart.yaml deleted file mode 100644 index 842489c..0000000 --- a/charts/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v2 -name: keyfactor-kubernetes -description: A Helm chart for the Keyfactor Kubernetes CSR API Signer -type: application -version: 1.0.0 -appVersion: 1.16.0 diff --git a/charts/ca.proto b/charts/ca.proto deleted file mode 100644 index a31e596..0000000 --- a/charts/ca.proto +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright Istio Authors -// -// 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. - -syntax = "proto3"; - -// Keep this package for backward compatibility. -package istio.v1.auth; - -option go_package="istio.io/api/security/v1alpha1"; - -// Certificate request message. The authentication should be based on: -// 1. Bearer tokens carried in the side channel; -// 2. Client-side certificate via Mutual TLS handshake. -// Note: the service implementation is REQUIRED to verify the authenticated caller is authorize to -// all SANs in the CSR. The server side may overwrite any requested certificate field based on its -// policies. -message IstioCertificateRequest { - // PEM-encoded certificate request. - // The public key in the CSR is used to generate the certificate, - // and other fields in the generated certificate may be overwritten by the CA. - string csr = 1; - // Optional: requested certificate validity period, in seconds. - int64 validity_duration = 3; -} - -// Certificate response message. -message IstioCertificateResponse { - // PEM-encoded certificate chain. - // The leaf cert is the first element, and the root cert is the last element. - repeated string cert_chain = 1; -} - -// Service for managing certificates issued by the CA. -service IstioCertificateService { - // Using provided CSR, returns a signed certificate. - rpc CreateCertificate(IstioCertificateRequest) - returns (IstioCertificateResponse) { - } -} diff --git a/charts/templates/NOTES.txt b/charts/templates/NOTES.txt deleted file mode 100644 index 90471b9..0000000 --- a/charts/templates/NOTES.txt +++ /dev/null @@ -1,38 +0,0 @@ -Thank you for installing Keyfactor Kubernetes Proxy -Your release is named {{ .Release.Name }}. - -- : {{ include "charts.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local -- : {{ .Values.service.port }} -- : {{ .Values.keyfactor.istioSecretName }} - -------------- [istio-config.yaml] ---------------- - -apiVersion: install.istio.io/v1alpha1 -kind: IstioOperator -metadata: - namespace: {{ .Values.keyfactor.istioNamespace }} -spec: - hub: thedemodrive - tag: 1.8-keyfactor - installPackagePath: "charts" - profile: "demo" - values: - pilot: - secretVolumes: - - name: {{ .Values.keyfactor.istioSecretName }} - secretName: {{ .Values.keyfactor.istioSecretName }} - mountPath: /etc/istio/{{ .Values.keyfactor.istioSecretName }} - meshConfig: - ca: - istiodSide: true - address: "{{ include "charts.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}" - requestTimeout: 30s - tlsSettings: - mode: MUTUAL - clientCertificate: "/etc/istio/{{ .Values.keyfactor.istioSecretName }}/client-cert.pem" - privateKey: "/etc/istio/{{ .Values.keyfactor.istioSecretName }}/client-key.pem" - caCertificates: "/etc/istio/{{ .Values.keyfactor.istioSecretName }}/cacert.pem" - sni: "{{ include "charts.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local" - subjectAltNames: [] - -------------------- END ---------------- \ No newline at end of file diff --git a/charts/templates/clusterrolebinding.yaml b/charts/templates/clusterrolebinding.yaml deleted file mode 100644 index 2c3b2ea..0000000 --- a/charts/templates/clusterrolebinding.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ include "charts.clusterRole" . }}-binding - labels: - {{- include "charts.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ include "charts.clusterRole" . }} -subjects: - - kind: ServiceAccount - name: {{ include "charts.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "charts.role" . }}-binding - namespace: {{ .Release.Namespace }} - labels: - {{- include "charts.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "charts.role" . }} -subjects: - - kind: ServiceAccount - name: {{ include "charts.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} ---- \ No newline at end of file diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml deleted file mode 100644 index ca3408e..0000000 --- a/charts/templates/deployment.yaml +++ /dev/null @@ -1,102 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "charts.fullname" . }} - labels: - {{- include "charts.labels" . | nindent 4 }} -spec: -{{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} -{{- end }} - selector: - matchLabels: - {{- include "charts.selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "charts.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "charts.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: gprc - containerPort: {{ .Values.service.port }} - protocol: TCP - - name: http - containerPort: {{ .Values.service.healthcheckPort }} - protocol: TCP - readinessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.healthcheckPort }} - initialDelaySeconds: 10 - livenessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.healthcheckPort }} - initialDelaySeconds: 30 - env: - - name: SERVICE_NAME - value: {{ include "charts.fullname" . }} - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - resources: - {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: keyfactor-credentials - mountPath: /credentials - - name: keyfactor-config - mountPath: /config - {{- if .Values.keyfactor.preProvisioningCertSecretName }} - - name: keyfactor-pre-provisioning-certs - mountPath: /certs - {{- end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - volumes: - - name: keyfactor-credentials - secret: - secretName: {{ .Values.keyfactor.secretName }} - - name: keyfactor-config - configMap: - name: keyfactor-config - {{- if .Values.keyfactor.preProvisioningCertSecretName }} - - name: keyfactor-pre-provisioning-certs - secret: - secretName: {{ .Values.keyfactor.preProvisioningCertSecretName }} - {{- end }} diff --git a/charts/templates/hpa.yaml b/charts/templates/hpa.yaml deleted file mode 100644 index ed7811e..0000000 --- a/charts/templates/hpa.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include "charts.fullname" . }} - labels: - {{- include "charts.labels" . | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include "charts.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} - metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - target: - type: Utilization - averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - target: - type: Utilization - averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} diff --git a/charts/templates/ingress.yaml b/charts/templates/ingress.yaml deleted file mode 100644 index cb1a91f..0000000 --- a/charts/templates/ingress.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "charts.fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "charts.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ . }} - backend: - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} diff --git a/charts/templates/metadata-configmap.yaml b/charts/templates/metadata-configmap.yaml deleted file mode 100644 index 65c9aa2..0000000 --- a/charts/templates/metadata-configmap.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: keyfactor-config - namespace: {{ .Release.Namespace }} -data: - config.yaml: | - {{- toYaml .Values.keyfactor | nindent 4}} - gRPCPort: {{ .Values.service.port }} - healthcheckPort: {{ .Values.service.healthcheckPort }} \ No newline at end of file diff --git a/charts/templates/role.yaml b/charts/templates/role.yaml deleted file mode 100644 index 9101c69..0000000 --- a/charts/templates/role.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "charts.role" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "charts.labels" . | nindent 4 }} -rules: -# For storing TLS CA secret -- apiGroups: [""] - resources: ["secrets"] - verbs: ["create", "get", "watch", "list", "update", "delete"] \ No newline at end of file diff --git a/charts/templates/service.yaml b/charts/templates/service.yaml deleted file mode 100644 index 6215870..0000000 --- a/charts/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "charts.fullname" . }} - labels: - {{- include "charts.labels" . | nindent 4 }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: {{ .Values.service.port }} - protocol: TCP - name: grpc - selector: - {{- include "charts.selectorLabels" . | nindent 4 }} diff --git a/charts/templates/tests/test-connection.yaml b/charts/templates/tests/test-connection.yaml deleted file mode 100644 index e69fe06..0000000 --- a/charts/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "charts.fullname" . }}-test-connection" - labels: - {{- include "charts.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test-success -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include "charts.fullname" . }}:{{ .Values.keyfactor.healthcheckPort }}'] - restartPolicy: Never diff --git a/charts/values.yaml b/charts/values.yaml deleted file mode 100644 index 0f5f54b..0000000 --- a/charts/values.yaml +++ /dev/null @@ -1,90 +0,0 @@ -# Default values for charts. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -image: - repository: thedemodrive/keyfactor-proxy - tag: 1.0.1.0 - pullPolicy: IfNotPresent - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -keyfactor: - secretName: keyfactor-credentials - environment: Production - istioNamespace: istio-system - istioSecretName: custom-ca-tls - disableMTLS: false - preProvisioningCertSecretName: "" - enableAutoProvisioningIstioCert: true - metadataMapping: - ClusterID: Cluster - ServiceName: Service - PodName: PodName - PodIP: PodIP - PodNamespace: PodNamespace - TrustDomain: TrustDomain - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "keyfactor-k8s" - -podAnnotations: {} - -podSecurityContext: {} - -securityContext: {} - -service: - type: ClusterIP - port: 3243 - healthcheckPort: 5354 - -ingress: - enabled: false - annotations: - {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: [] - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -resources: - {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -autoscaling: - enabled: true - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -nodeSelector: {} - -tolerations: [] - -affinity: {} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 317db5e..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package cmd - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/Keyfactor/k8s-proxy/pkg/config" - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "github.com/spf13/cobra" -) - -var ( - // Used for flags. - cfgFile string - credentialFile string - kubeconfig string - - cf *config.ServerConfig - keyfactorCredential *keyfactor.ClientCredential - - rootCmd = &cobra.Command{ - Use: "keyfactor", - Short: "Keyfactor Kubernetes CLI", - } - rootLog = klogger.Register("RootCMD") -) - -func init() { - cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "configFile", "./config/config.yaml", - "config file (default is ./config/config.yaml)") - rootCmd.PersistentFlags().StringVar(&credentialFile, "credentialFile", - "./credentials/credentials.yaml", "keyfactor credentials file (default is ./credentials/credentials.yaml)") - rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", filepath.Join(homeDir(), ".kube", "config"), - "(optional) absolute path to the kubeconfig file") - rootCmd.AddCommand(startCMD) -} - -func er(msg interface{}) { - fmt.Println("Error:", msg) - os.Exit(1) -} - -func initConfig() { - var err error - cf = config.LoadConfig(cfgFile) - keyfactorCredential, err = keyfactor.LoadCredential(credentialFile) - if err != nil { - rootLog.Errorf("load keyfactor credentials failed: %v. \nPlease check your kubernetes secret", err) - } -} - -// Execute executes the root command. -func Execute() error { - return rootCmd.Execute() -} - -func homeDir() string { - if h := os.Getenv("HOME"); h != "" { - return h - } - return os.Getenv("USERPROFILE") // windows -} diff --git a/cmd/start.go b/cmd/start.go deleted file mode 100644 index 79d3916..0000000 --- a/cmd/start.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package cmd - -import ( - "fmt" - "net" - - "github.com/Keyfactor/k8s-proxy/internal/gateway" - "github.com/Keyfactor/k8s-proxy/internal/health" - "github.com/Keyfactor/k8s-proxy/internal/signer" - "github.com/Keyfactor/k8s-proxy/pkg/k8s" - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "github.com/spf13/cobra" - "k8s.io/client-go/kubernetes" -) - -var ( - startCMD = &cobra.Command{ - Use: "start", - Short: "Start Keyfactor Gateway for integrating with Istio", - Run: func(cmd *cobra.Command, args []string) { - startKeyfactorGatewayServer() - }, - } - cmdLog = klogger.Register("startCMD") -) - -func startKeyfactorGatewayServer() error { - - var k8sClient *kubernetes.Clientset - lis, err := net.Listen("tcp", fmt.Sprintf(":%v", cf.GRPCPort)) - if err != nil { - return fmt.Errorf("cannot start net listener for server: %v", err) - } - cmdLog.Infof("create net listener at: %s", lis.Addr()) - kl, err := keyfactor.New(keyfactorCredential, cf.MetadataMapping) - if err != nil { - return fmt.Errorf("cannot create keyfactor client: %v", err) - } - cmdLog.Infof("created KeyfactorClient of endpoint: %s", keyfactorCredential.Endpoint) - - if cf.Environment != "Development" { - cmdLog.Info("creating in cluster kubernetes client...") - k8sClient, err = k8s.NewInClusterClient() - if err != nil { - cmdLog.Errorf("cannot create new Kuberenetes Client: %v", err) - return fmt.Errorf("cannot create new Kuberenetes Client: %v", err) - } - } else { - cmdLog.Info("creating out of cluster kubernetes client for dev environment...") - k8sClient, err = k8s.NewTestClient(kubeconfig) - if err != nil { - cmdLog.Errorf("cannot create new Kuberenetes Client: %v", err) - return fmt.Errorf("cannot create new Kuberenetes Client: %v", err) - } - } - - keyfactorGateway, err := gateway.NewKeyfactorGateway(kl, cf, keyfactorCredential, k8sClient) - - if err != nil { - return fmt.Errorf("cannot create Keyfactor Gateway: %v", err) - } - stopChan := make(chan struct{}) - errChan := make(chan error) - defer close(stopChan) - - go func() { - - err := keyfactorGateway.GPRC.Serve(lis) - if err != nil { - cmdLog.Errorf("Keyfactor gatewaye got err: %v", err) - } - errChan <- err - }() - - hService := &health.ServiceHealthCheck{ - Addr: cf.HealthCheckPort, - } - - go func() { - err := hService.Serve() - if err != nil { - cmdLog.Errorf("cannot start health check service: %v", err) - } - errChan <- err - }() - - certificateController := signer.NewCertificateController(k8sClient, kl) - - go certificateController.RunWorker(3, stopChan) - - cmdLog.Infof("started Keyfactor GRPC Gateway at: %v", lis.Addr().String()) - - <-errChan - return fmt.Errorf("keyfactor gateway closed: %v", keyfactorGateway.GPRC.GetServiceInfo()) -} diff --git a/command-signer-config.yaml b/command-signer-config.yaml new file mode 100644 index 0000000..8281c43 --- /dev/null +++ b/command-signer-config.yaml @@ -0,0 +1,26 @@ +# This configuration file contains default values that configure how certificates are signed by Command, +# and how signed certificates are stored back into Kubernetes. +kind: ConfigMap +apiVersion: v1 +metadata: + name: command-signer-config +data: + # Hostname of the Command instance + commandHostname: "" + + # Default certificate template to use for enrollment + defaultCertificateTemplate: "" + + # Default certificate authority logical name to use for enrollment + defaultCertificateAuthorityLogicalName: "" + + # Default certificate authority hostname to reference for enrollment + defaultCertificateAuthorityHostname: "" + + # The length of the certificate chain included with the leaf certificate. + # chainDepth = 0 => whole chain + # chainDepth = 1 => just the leaf + # chainDepth = 2 => leaf + issuer + # chainDepth = 3 => leaf + issuer + issuer + # etc + chainDepth: "0" \ No newline at end of file diff --git a/config/config.example b/config/config.example deleted file mode 100644 index d5b1ca9..0000000 --- a/config/config.example +++ /dev/null @@ -1,14 +0,0 @@ -gRPCPort: 8932 -healthcheckPort: 8832 -environment: Development -istioNamespace: istio-system -istioSecretName: custom-ca-tls -enableAutoProvisioningIstioCert: true -disableMTLS: false -metadataMapping: - ClusterID: Cluster - ServiceName: Service - PodName: PodName - PodIP: PodIP - PodNamespace: PodNamespace - TrustDomain: TrustDomain diff --git a/credentials/credentials.yaml b/credentials/credentials.yaml deleted file mode 100644 index ea2a843..0000000 --- a/credentials/credentials.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Endpoint of Keyfactor Platform -endPoint: "" - -# Name of certificate authority for enrollment -caName: "" - -# Basic auth credentials for authentication header -authToken: "Basic ..." - -# API path to enroll new certificate from Keyfactor -enrollPath: "KeyfactorAPI/Enrollment/CSR" - -# Certificate Template for Istio certificate enrollment -caTemplate: "" - -# ApiKey from Api Setting, to enroll certificates for Istio -appKey: "" - -# ApiKey for auto provisioning TLS server / client certificates -provisioningAppKey: "" - -# CA Template for auto provisioning TLS server / client certificates -provisioningTemplate: "K8SProxy" diff --git a/deploy/charts/k8s-csr-signer/Chart.yaml b/deploy/charts/k8s-csr-signer/Chart.yaml new file mode 100644 index 0000000..56db2a8 --- /dev/null +++ b/deploy/charts/k8s-csr-signer/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: k8s-csr-signer +description: A Helm chart for the Command Kubernetes CSR Signer +type: application +version: 2.0.0 +appVersion: "2.0.0" + +maintainers: + - name: Hayden Roszell + email: hayden.roszell@keyfactor.com + - name: JD Kilgallin + email: jd.kilgallin@keyfactor.com diff --git a/deploy/charts/k8s-csr-signer/templates/NOTES.txt b/deploy/charts/k8s-csr-signer/templates/NOTES.txt new file mode 100644 index 0000000..290f42d --- /dev/null +++ b/deploy/charts/k8s-csr-signer/templates/NOTES.txt @@ -0,0 +1,5 @@ +Create and approve a K8s CertificateSigningRequest with one of the following signerNames: +{{ range $i, $signerName := .Values.ejbca.signerNames }} - {{ $signerName }} +{{ end }} +To see the controller logs, run: +kubectl logs -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "ejbca-csr-signer.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -f \ No newline at end of file diff --git a/charts/templates/_helpers.tpl b/deploy/charts/k8s-csr-signer/templates/_helpers.tpl similarity index 64% rename from charts/templates/_helpers.tpl rename to deploy/charts/k8s-csr-signer/templates/_helpers.tpl index 0108cfd..3d396b2 100644 --- a/charts/templates/_helpers.tpl +++ b/deploy/charts/k8s-csr-signer/templates/_helpers.tpl @@ -1,8 +1,7 @@ -{{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} -{{- define "charts.name" -}} +{{- define "ejbca-csr-signer.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} @@ -11,7 +10,7 @@ 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 "charts.fullname" -}} +{{- define "ejbca-csr-signer.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -27,16 +26,16 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "charts.chart" -}} +{{- define "ejbca-csr-signer.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} -{{- define "charts.labels" -}} -helm.sh/chart: {{ include "charts.chart" . }} -{{ include "charts.selectorLabels" . }} +{{- define "ejbca-csr-signer.labels" -}} +helm.sh/chart: {{ include "ejbca-csr-signer.chart" . }} +{{ include "ejbca-csr-signer.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -46,32 +45,18 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Selector labels */}} -{{- define "charts.selectorLabels" -}} -app.kubernetes.io/name: {{ include "charts.name" . }} +{{- define "ejbca-csr-signer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ejbca-csr-signer.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} -{{- define "charts.serviceAccountName" -}} +{{- define "ejbca-csr-signer.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} -{{- default (include "charts.fullname" .) .Values.serviceAccount.name }} +{{- default (include "ejbca-csr-signer.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} -{{- end }} - -{{/* -Create the name of the cluster role binding -*/}} -{{- define "charts.clusterRole" -}} -{{- .Values.serviceAccount.name }}-cluster-role -{{- end }} - -{{/* -Create the name of the role binding -*/}} -{{- define "charts.role" -}} -{{- .Values.serviceAccount.name }}-role {{- end }} \ No newline at end of file diff --git a/charts/templates/clusterrole.yaml b/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml similarity index 55% rename from charts/templates/clusterrole.yaml rename to deploy/charts/k8s-csr-signer/templates/clusterrole.yaml index 712af47..4f55c0b 100644 --- a/charts/templates/clusterrole.yaml +++ b/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml @@ -1,15 +1,30 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: {{ include "charts.clusterRole" . }} + name: {{ include "ejbca-csr-signer.name" . }}-controller-role namespace: {{ .Release.Namespace }} labels: - {{- include "charts.labels" . | nindent 4 }} + {{- include "ejbca-csr-signer.labels" . | nindent 4 }} rules: - # For storing TLS CA secret - - apiGroups: [""] - resources: ["secrets", "namespaces"] - verbs: ["create", "get", "watch", "list", "update", "delete"] + # The controller needs to be able to read secrets and configmaps to get authentication information + # and to configure itself + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + # configuration validation webhook controller - apiGroups: ["admissionregistration.k8s.io"] resources: ["validatingwebhookconfigurations"] @@ -21,14 +36,12 @@ rules: - "certificatesigningrequests/approval" - "certificatesigningrequests/status" verbs: ["list", "update", "create", "get", "delete", "watch"] + - apiGroups: ["certificates.k8s.io"] resources: - "signers" + {{- with .Values.ejbca.signerNames }} resourceNames: - - "keyfactor.com/*" + {{- toYaml . | nindent 6 }} + {{- end }} verbs: ["approve", "sign"] - - # To review JWT token from Istio - - apiGroups: ["authentication.k8s.io"] - resources: ["tokenreviews"] - verbs: ["create"] diff --git a/deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml b/deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml new file mode 100644 index 0000000..0d2b375 --- /dev/null +++ b/deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "ejbca-csr-signer.name" . }}-controller-rolebinding + labels: + {{- include "ejbca-csr-signer.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "ejbca-csr-signer.name" . }}-controller-role +subjects: + - kind: ServiceAccount + name: {{ include "ejbca-csr-signer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/deployment.yaml b/deploy/charts/k8s-csr-signer/templates/deployment.yaml new file mode 100644 index 0000000..d141c0b --- /dev/null +++ b/deploy/charts/k8s-csr-signer/templates/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ejbca-csr-signer.fullname" . }} + labels: + {{- include "ejbca-csr-signer.labels" . | nindent 4 }} +spec: +{{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} +{{- end }} + selector: + matchLabels: + {{- include "ejbca-csr-signer.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "ejbca-csr-signer.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "ejbca-csr-signer.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + - --credential-secret-name={{ required "credsSecretName is required" .Values.ejbca.credsSecretName }} + - --configmap-name={{ required "configMapName is required" .Values.ejbca.configMapName }} + {{- if .Values.ejbca.caCertConfigmapName }} + - --ca-cert-configmap-name={{ .Values.ejbca.caCertConfigmapName }} + {{- end }} + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/role.yaml b/deploy/charts/k8s-csr-signer/templates/role.yaml new file mode 100644 index 0000000..6e1dc08 --- /dev/null +++ b/deploy/charts/k8s-csr-signer/templates/role.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + name: {{ include "ejbca-csr-signer.name" . }}-leader-election-role +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/rolebinding.yaml b/deploy/charts/k8s-csr-signer/templates/rolebinding.yaml new file mode 100644 index 0000000..48372d4 --- /dev/null +++ b/deploy/charts/k8s-csr-signer/templates/rolebinding.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + name: {{ include "ejbca-csr-signer.name" . }}-leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "ejbca-csr-signer.name" . }}-leader-election-role +subjects: + - kind: ServiceAccount + name: {{ include "ejbca-csr-signer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/charts/templates/serviceaccount.yaml b/deploy/charts/k8s-csr-signer/templates/serviceaccount.yaml similarity index 61% rename from charts/templates/serviceaccount.yaml rename to deploy/charts/k8s-csr-signer/templates/serviceaccount.yaml index 9397ca7..f48ec69 100644 --- a/charts/templates/serviceaccount.yaml +++ b/deploy/charts/k8s-csr-signer/templates/serviceaccount.yaml @@ -2,11 +2,11 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: {{ include "charts.serviceAccountName" . }} + name: {{ include "ejbca-csr-signer.serviceAccountName" . }} labels: - {{- include "charts.labels" . | nindent 4 }} + {{- include "ejbca-csr-signer.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml b/deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml new file mode 100644 index 0000000..35fa4b5 --- /dev/null +++ b/deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "ejbca-csr-signer.fullname" . }}-test-connection" + labels: + {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "ejbca-csr-signer.fullname" . }}:{{ .Values.ejbca.port }}'] + restartPolicy: Never diff --git a/deploy/charts/k8s-csr-signer/values.schema.json b/deploy/charts/k8s-csr-signer/values.schema.json new file mode 100644 index 0000000..ac0d3f7 --- /dev/null +++ b/deploy/charts/k8s-csr-signer/values.schema.json @@ -0,0 +1,98 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "replicaCount": { + "type": "number" + }, + "imagePullSecrets": { + "type": "array", + "items": { + "type": "string" + } + }, + "nameOverride": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "image": { + "type": "object", + "properties": { + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "pullPolicy": { + "type": "string", + "enum": ["IfNotPresent", "Always", "Never"] + } + }, + "required": ["repository", "tag", "pullPolicy"] + }, + "command": { + "type": "object", + "properties": { + "credsSecretName": { + "type": "string" + }, + "configMapName": { + "type": "string" + }, + "caCertConfigmapName": { + "type": "string" + }, + "signerNames": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["credsSecretName", "configMapName", "caCertConfigmapName", "signerNames"] + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "type": "string" + } + }, + "required": ["create", "annotations", "name"] + }, + "podSecurityContext": { + "type": "object" + }, + "securityContext": { + "type": "object" + }, + "resources": { + "type": "object" + }, + "nodeSelector": { + "type": "object" + }, + "tolerations": { + "type": "array", + "items": { + "type": "object" + } + }, + "affinity": { + "type": "object" + } + }, + "required": ["replicaCount", "image", "command", "serviceAccount"] +} \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/values.yaml b/deploy/charts/k8s-csr-signer/values.yaml new file mode 100644 index 0000000..a3396a1 --- /dev/null +++ b/deploy/charts/k8s-csr-signer/values.yaml @@ -0,0 +1,72 @@ +# Default values for k8s-csr-signer. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +image: + repository: "" + tag: "" + pullPolicy: IfNotPresent + +command: + # Name of the secret containing Command credentials + # If this secret is of type kubernetes.io/basic-auth, the signer will use BasicAuth for authentication to Command. + # Future Auth methods to Command will be supported by adding additional secret types. + credsSecretName: "" + + # Name of the configmap containing Command configuration. + configMapName: "" + + # Name of the configmap containing the Command API CA certificate. + # If this name is blank, the signer assumes that the Command API + # is using a certificate signed by a trusted CA. + caCertConfigmapName: "" + + # Signer names that this signer will respond to. The ClusterRole will set each signer + # as resource names with verb "sign". + # Signer names must be absolute and not contain wildcards or the signer will interpret + # them as foreign in scope. + signerNames: [] + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "command-k8s" + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/docs/annotations.markdown b/docs/annotations.markdown new file mode 100644 index 0000000..b2f5fb2 --- /dev/null +++ b/docs/annotations.markdown @@ -0,0 +1,49 @@ +# Annotation Overrides for the Command K8s CSR Signer + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/k8s-csr-signer)](https://goreportcard.com/report/github.com/Keyfactor/k8s-csr-signer) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/keyfactor/k8s-csr-signer?label=release)](https://github.com/keyfactor/k8s-csr-signer/releases) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![license](https://img.shields.io/github/license/keyfactor/k8s-csr-signer.svg)]() + +The Command K8s CSR Signer allows you to customize the certificate signing process by using annotations. Annotations can be used to override the default configuration of the signer. The following annotations are supported: + +### Supported Annotations +Here are the supported annotations that can override the default values: + +- **`k8s-csr-signer.keyfactor.com/certificateTemplate`**: Overrides the `defaultCertificateTemplate` field from the Command Configuration. + + ```yaml + k8s-csr-signer.keyfactor.com/certificateTemplate: "istioAuth-3d" + ``` + +- **`k8s-csr-signer.keyfactor.com/certificateAuthorityHostname`**: Specifies the Certificate Authority (CA) hostname name to use, overriding the default CA hostname specified by the `defaultCertificateAuthorityHostname` field from the Command Configuration. + + ```yaml + k8s-csr-signer.keyfactor.com/certificateAuthorityHostname: "DC-CA.Command.local" + ``` + +- **`k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName`**: Specifies the Certificate Authority (CA) logical name to use, overriding the default CA logical name specified by the `defaultCertificateAuthorityLogicalName` field from the Command Configuration. + + ```yaml + k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: "CommandCA1" + ``` + +- **`k8s-csr-signer.keyfactor.com/chainDepth`**: Specifies the chain depth to use, overriding the default chain depth specified by the `chainDepth` field from the Command Configuration. + + ```yaml + k8s-csr-signer.keyfactor.com/chainDepth: 3 + ``` + +### How to Apply Annotations + +To apply these annotations, include them in the metadata section of your CertificateSigningRequest resource: + +```yaml +apiVersion: certificates.k8s.io/v1 +kind: CertificateSigningRequest +metadata: + annotations: + k8s-csr-signer.keyfactor.com/certificateTemplate: istioAuth-3d + k8s-csr-signer.keyfactor.com/certificateAuthorityHostname: DC-CA.Command.local + k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: CommandCA1 + # ... other annotations +spec: +# ... rest of the spec +``` \ No newline at end of file diff --git a/docs/getting-started.markdown b/docs/getting-started.markdown new file mode 100644 index 0000000..6d1d2ba --- /dev/null +++ b/docs/getting-started.markdown @@ -0,0 +1,155 @@ +# Getting Started with the Command Certificate Signing Request Proxy for K8s + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/k8s-csr-signer)](https://goreportcard.com/report/github.com/Keyfactor/k8s-csr-signer) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/keyfactor/k8s-csr-signer?label=release)](https://github.com/keyfactor/k8s-csr-signer/releases) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![license](https://img.shields.io/github/license/keyfactor/k8s-csr-signer.svg)]() + +## Requirements +* Keyfactor Command + * [Command](https://www.keyfactor.com/products/command/) (v10.4 +) +* Docker (to build the container) + * [Docker Engine](https://docs.docker.com/engine/install/) or [Docker Desktop](https://docs.docker.com/desktop/) +* [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) (v1.11.3 +) +* Kubernetes (v1.19 +) + * [Kubernetes](https://kubernetes.io/docs/tasks/tools/) + * [Minikube](https://minikube.sigs.k8s.io/docs/start/) + * [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) + * [Docker Desktop](https://docs.docker.com/desktop/kubernetes/) + * [Azure Kubernetes](https://azure.microsoft.com/en-us/products/kubernetes-service) + * [Amazon EKS](https://aws.amazon.com/eks/) + * [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine) +* Helm (to deploy to Kubernetes) + * [Helm](https://helm.sh/docs/intro/install/) (v3.1 +) + +## Getting Started +Install required software and their dependencies if not already present. Additionally, verify that at least one Kubernetes node is running by running the following command: + +```shell +kubectl get nodes +``` + +### 1. Building the Container Image + +The Command K8s CSR Signer is distributed as source code, and the container must be built manually. The container image can be built using the following command: +```shell +make docker-build DOCKER_REGISTRY= DOCKER_IMAGE_NAME=keyfactor/k8s-csr-signer +``` + +###### :pushpin: The container image can be built using Docker Buildx by running `make docker-buildx`. This will build the image for all supported platforms. + +### 2. Prepare Credentials and Configuration + +1. Create a new namespace for the CSR proxy. + ```shell + kubectl create namespace command-signer-system + ``` + +2. The Command K8s CSR Signer uses the Command REST API to enroll certificates. Authentication to the Command API is handled using HTTP Basic Authentication. + + * If you want to configure the signer to authenticate to Command using HTTP Basic Auth, create a `kubernetes.io/basic-auth` secret. + + Create a `kubernetes.io/basic-auth` secret containing the username and password. The secret must be created in the same namespace as the Helm chart. + + ```shell + kubectl -n command-signer-system create secret generic --type=kubernetes.io/basic-auth command-credentials \ + --from-literal=username= \ + --from-literal=password= + ``` + +3. The Command K8s CSR Signer uses a K8s ConfigMap to configure how certificates are signed by Command, and how signed certificates are stored back into Kubernetes. A [sample](../command-signer-config.yaml) ConfigMap is provided as a reference. + + The following fields are required: + * `commandHostname`: The hostname of the Command instance. + * `chainDepth`: The length of the certificate chain included with the leaf certificate. For example, a value of `0` will include the whole chain up to the root CA, and a value of `2` will include the leaf certificate and one intermediate CA certificate. + + * The following fields can be configured in the ConfigMap and are optional if annotations are used to override the values at runtime: + * `defaultCertificateTemplate`: The default name of the certificate template to use when enrolling certificates in Command. + * `defaultCertificateAuthorityLogicalName`: The default name of the certificate authority to use when enrolling certificates. + * `defaultCertificateAuthorityHostname`: The default hostname of the certificate authority to use when enrolling certificates. + + Create a new ConfigMap resource using the following command: + ```shell + kubectl -n command-signer-system apply --from-file=config.yaml + ``` + + As with the Command secret, the Command ConfigMap must be deployed in the same namespace as the Helm chart. All fields in the ConfigMap can be overridden using annotations from the CSR at runtime. See the [Annotation Overrides for the Command K8s CSR Signer](annotations.markdown) guide for more information. + +4. If the Command API is configured to use a self-signed certificate or with a certificate signed by an untrusted root, the CA certificate must be provided as a Kubernetes configmap. + + ```shell + kubectl -n command-signer-system create configmap command-ca-cert --from-file=ca.crt + ``` + +### 3. Installation from Helm Chart + +The Command K8s CSR Signer is installed using a Helm chart. The chart is available in the [Command K8s CSR Signer Helm repository](https://keyfactor.github.io/k8s-csr-signer/). + +1. Add the Helm repository: + + ```bash + helm repo add command-k8s https://keyfactor.github.io/k8s-csr-signer + helm repo update + ``` + +2. Then, install the chart: + + ```bash + helm install k8s-csr-signer command-k8s/k8s-csr-signer \ + --namespace command-signer-system \ + --set image.repository=/keyfactor/k8s-csr-signer \ + --set image.tag= \ + # --set image.pullPolicy=Never # Only required if using a local image \ + --set image.pullPolicy=Never \ + --set command.credsSecretName=command-credentials \ + --set command.configMapName=command-signer-config \ + # --set command.caCertConfigmapName=command-ca-cert # Only required if Command API serves an untrusted certificate + ``` + + 1. Modifications can be made by overriding the default values in the `values.yaml` file with the `--set` flag. For example, to add an authorized signer name to the ClusterRole, run the following command: + + ```shell + helm install k8s-csr-signer command-k8s/k8s-csr-signer \ + --namespace command-signer-system \ + --set image.repository=/keyfactor/k8s-csr-signer \ + --set image.tag= \ + --set command.signerNames[0]=internalsigner.com + ``` + + 2. Modifications can also be made by modifying the `values.yaml` file directly. For example, to override the + `signerNames` value, modify the `signerNames` value in the `values.yaml` file: + + ```yaml + cat < override.yaml + image: + repository: /keyfactor/k8s-csr-signer + pullPolicy: Never + tag: "latest" + command: + credsSecretName: command-credentials + configMapName: command-signer-config + caCertConfigmapName: command-ca-cert + signerNames: + - internalsigner.com/cluster + EOF + ``` + + Then, use the `-f` flag to specify the `values.yaml` file: + + ```yaml + helm install k8s-csr-signer command-k8s/k8s-csr-signer \ + -n command-signer-system \ + -f override.yaml + ``` + +###### :pushpin: Wildcards are **NOT** supported in the `signerNames` field. If you want to allow all signers, do not specify any signer names. + +###### :pushpin: The Command K8s CSR signer uses the `SelfSubjectAccessReview` API to determine if the user has permission to sign the CSR. If the user does not have permission, the signer will ignore the CSR. + +### 4. Create a new CertificateSigningRequest resource with the provided sample +A [sample CSR object file](../sample/sample.yaml) is provided to getting started. Create a new CSR resource using the following command. The `request` field contains a Base64 encoded PKCS#10 PEM encoded certificate. +```shell +kubectl apply -f sample/sample.yaml +kubectl get csr +``` +To enroll the CSR, it must be approved. +```shell +kubectl certificate approve commandCsrTest +``` \ No newline at end of file diff --git a/docs/istio-deployment.markdown b/docs/istio-deployment.markdown new file mode 100644 index 0000000..a175ee4 --- /dev/null +++ b/docs/istio-deployment.markdown @@ -0,0 +1,213 @@ +# Deployment with Istio + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/k8s-csr-signer)](https://goreportcard.com/report/github.com/Keyfactor/k8s-csr-signer) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/keyfactor/k8s-csr-signer?label=release)](https://github.com/keyfactor/k8s-csr-signer/releases) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![license](https://img.shields.io/github/license/keyfactor/k8s-csr-signer.svg)]() + +## Requirements +* Keyfactor Command + * [Command](https://www.keyfactor.com/products/command/) (v10.4 +) +* Docker (to build the container) + * [Docker Engine](https://docs.docker.com/engine/install/) or [Docker Desktop](https://docs.docker.com/desktop/) +* [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) (v1.11.3 +) +* Kubernetes (v1.19 +) + * [Kubernetes](https://kubernetes.io/docs/tasks/tools/) + * [Minikube](https://minikube.sigs.k8s.io/docs/start/) + * [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) + * [Docker Desktop](https://docs.docker.com/desktop/kubernetes/) + * [Azure Kubernetes](https://azure.microsoft.com/en-us/products/kubernetes-service) + * [Amazon EKS](https://aws.amazon.com/eks/) + * [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine) +* Helm (to deploy to Kubernetes) + * [Helm](https://helm.sh/docs/intro/install/) (v3.1 +) + +This guide will walk through configuring Istio to use the Command K8s CSR Signer as an external certificate signing service. Once configured, Istio will provision workload certificates using a custom CA in Command via the Command K8s CSR Signer. + +For this tutorial, it's recommended that a distribution of Linux is used as the host operating system. + +## 1. Deploy the Command K8s CSR Signer + +Follow the steps in the [Getting Started](getting-started.markdown) guide to build the container image and prepare the credentials and configuration. + +1. Install Istioctl + + Install the Istio CLI, `istioctl`, by running the following commands: + + ```shell + curl -L https://istio.io/downloadIstio | sh - + cd istio- + export PATH=$PWD/bin:$PATH + ``` + + Or navigate to the Istio [release page](https://github.com/istio/istio/releases/) and download the latest release for your host OS. + +2. Download and store the CA Chain of the Command CA represented by the default CA logical name used in the getting started guide. + + 1. Navigate to the Command GUI and log in. + 2. Navigate to the Certificate Search page and search for the CA in any of the following ways: + * Search by CA DN. + * Search by CertState [is equal to] CertificateAuthority (6). + * Others... + 3. Click the Download button with the following options: + * Include Chain: True + * Chain Order: End Entity First + * Format: PEM + + Assign the root certificates of the Command CA to an environment variable. + + ```shell + export COMMAND_ROOT_CERTS=$(cat | sed 's/^/ /') + ``` + +3. Deploy Istio with the `keyfactor.com/bookinfo` and `keyfactor.com/istio-system` signers. + + The signer names can be modified according do your cluster's needs, but you _must_ ensure that the signer names match the signer names configured in the `command.signerNames` in the Command K8s CSR Signer Helm chart. By default, no signer names are configured in the Command K8s CSR Signer; all signer names are in scope. + + ```yaml + cat < ./ejbca-istio.yaml + apiVersion: install.istio.io/v1alpha1 + kind: IstioOperator + spec: + values: + pilot: + env: + EXTERNAL_CA: ISTIOD_RA_KUBERNETES_API + meshConfig: + defaultConfig: + proxyMetadata: + ISTIO_META_CERT_SIGNER: istio-system + caCertificates: + - pem: | + $COMMAND_ROOT_CERTS + certSigners: + - keyfactor.com/istio-system + - pem: | + $COMMAND_ROOT_CERTS + certSigners: + - keyfactor.com/bookinfo + components: + pilot: + k8s: + env: + - name: CERT_SIGNER_DOMAIN + value: keyfactor.com + - name: PILOT_CERT_PROVIDER + value: k8s.io/keyfactor.com/istio-system + overlays: + - apiVersion: apps/v1 + kind: Deployment + name: istiod + patches: + - path: spec.template.spec.containers.[name:discovery].args + value: + - "discovery" + - "--log_output_level=default:debug" + - kind: ClusterRole + name: istiod-clusterrole-istio-system + patches: + - path: rules[-1] + value: | + apiGroups: + - certificates.k8s.io + resourceNames: + - keyfactor.com/bookinfo + - keyfactor.com/istio-system + resources: + - signers + verbs: + - approve + EOF + istioctl install --skip-confirmation -f ./ejbca-istio.yaml + ``` + +## 3. Deploy the Bookinfo demo application + +1. Create a namespace for the Bookinfo demo application. + ```shell + kubectl create ns bookinfo + ``` + + Label the namespace with the `istio-injection=enabled` label to enable automatic sidecar injection. + ```shell + kubectl label namespace bookinfo istio-injection=enabled + ``` + +2. Enforce strict mTLS for the `bookinfo` namespace. + + ```yaml + cat <.*" + echo "http://${GATEWAY_URL}/productpage" + ``` + + + diff --git a/docs/testing.markdown b/docs/testing.markdown new file mode 100644 index 0000000..2d849df --- /dev/null +++ b/docs/testing.markdown @@ -0,0 +1,15 @@ +# Testing the Command CSR Signer Source Code + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/k8s-csr-signer)](https://goreportcard.com/report/github.com/Keyfactor/k8s-csr-signer) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/keyfactor/k8s-csr-signer?label=release)](https://github.com/keyfactor/k8s-csr-signer/releases) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![license](https://img.shields.io/github/license/keyfactor/k8s-csr-signer.svg)]() + +The test cases for the controller require a set of environment variables to be set. These variables are used to +authenticate to a Command API server and to enroll a certificate. The test cases are run using the `make test` command. + +The following environment variables must be exported before testing the controller: +- **COMMAND_HOSTNAME** - The hostname of the Command instance. +- **COMMAND_USERNAME** - The username to authenticate to Command. +- **COMMAND_PASSWORD** - The password to authenticate to Command. +- **COMMAND_CA_CERT_PATH** - The path to the CA certificate of the Command API server in PEM format. +- **COMMAND_CERTIFICATE_TEMPLATE** - The certificate template to use when enrolling a certificate. +- **COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME** - The hostname of the Certificate Authority (CA) to use when enrolling a certificate. +- **COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME** - The logical name of the Certificate Authority (CA) to use when enrolling a certificate. diff --git a/go.mod b/go.mod index a852cb4..f376efa 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,72 @@ -module github.com/Keyfactor/k8s-proxy +module github.com/Keyfactor/k8s-csr-signer -go 1.14 +go 1.20 require ( - github.com/gogo/protobuf v1.3.1 - github.com/klauspost/compress v1.10.11 // indirect - github.com/mitchellh/mapstructure v1.3.3 // indirect - github.com/onsi/gomega v1.10.2 // indirect - github.com/pelletier/go-toml v1.8.0 // indirect - github.com/sirupsen/logrus v1.6.0 - github.com/spf13/afero v1.4.0 // indirect - github.com/spf13/cobra v1.0.0 - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.6.1 - github.com/valyala/fasthttp v1.16.0 - golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e - google.golang.org/genproto v0.0.0-20200831141814-d751682dd103 // indirect - google.golang.org/grpc v1.32.0 - gopkg.in/ini.v1 v1.57.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - istio.io/api v0.0.0-20200828230038-701568332f0f - istio.io/istio v0.0.0-20200831162021-362e3d808028 - istio.io/pkg v0.0.0-20200819194923-e8c102c633f0 // indirect - k8s.io/api v0.19.2 - k8s.io/apimachinery v0.19.2 - k8s.io/client-go v0.19.0 + github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 + github.com/go-logr/logr v1.2.4 + github.com/stretchr/testify v1.8.2 + k8s.io/api v0.28.4 + k8s.io/apimachinery v0.28.4 + k8s.io/client-go v0.28.4 + k8s.io/utils v0.0.0-20231127182322-b307cd553661 + sigs.k8s.io/controller-runtime v0.16.3 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/zapr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.25.0 // indirect + golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.28.3 // indirect + k8s.io/component-base v0.28.3 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 0e345c2..696b616 100644 --- a/go.sum +++ b/go.sum @@ -1,1350 +1,218 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -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= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fortio.org/fortio v1.6.3/go.mod h1:pvYR4QBINE3QC9LxpsSZIDMdNZUgmOJ0WRCrcRPV/vQ= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -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/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= -github.com/Masterminds/squirrel v1.2.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= -github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/ahmetb/gen-crd-api-reference-docs v0.2.0/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= -github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= -github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.33.11/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 h1:caLlzFCz2L4Dth/9wh+VlypFATmOMmCSQkCPKOKMxw8= +github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2/go.mod h1:Z5pSk8YFGXHbKeQ1wTzVN8A4P/fZmtAwqu3NgBHbDOs= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containernetworking/cni v0.7.0-alpha1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.7.3/go.mod h1:dagHaAhNjXjT9QYOklkKJDGaQPTg4pf//FrUcJeb7FU= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= -github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= -github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7-0.20200811182123-112a4904c4b0/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= -github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.2.1-0.20200730175230-ee2de8da5be6 h1:ZPVluSmhtMIHlqUDMZu70FgMpRzbQfl4h9oKCAXOVDE= -github.com/go-logr/logr v0.2.1-0.20200730175230-ee2de8da5be6/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-resty/resty v1.12.0 h1:L1P5qymrXL5H/doXe2pKUr1wxovAI5ilm2LdVLbwThc= -github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= -github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= -github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= -github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= -github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= -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/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:YCHYtYb9c8Q7XgYVYjmJBPtFPKx5QvOcPxHZWjldabE= -github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= -github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= -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/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -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/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= -github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -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/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.0.3/go.mod h1:aXl6A3Tfh50BoCNJd3oM3IK+bj2m/vVQGu8blfCvEsY= -github.com/hashicorp/vault/sdk v0.1.12/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= -github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= -github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.10.11 h1:K9z59aO18Aywg2b/WSgBaUX99mHy2BES18Cr5lBKZHk= -github.com/klauspost/compress v1.10.11/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -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/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -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/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= -github.com/lestrrat-go/jwx v1.0.3/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0= -github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 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-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/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= -github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= -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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= -github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/openshift/api v0.0.0-20200713203337-b2494ecb17dd/go.mod h1:vWmWTm4y7XR3wkLR+bDDjRbvkBfx2yP7yve6kfb7+Ts= -github.com/openshift/build-machinery-go v0.0.0-20200713135615-1f43d26dccc7/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= -github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/prom2json v1.1.0/go.mod h1:v7OY1795b9fEUZgq4UU2+15YjRv0LfpxKejIQCy3L7o= -github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.4.0 h1:jsLTaI1zwYO3vjrzHalkVcIHXTNmdQFepW4OI8H3+x8= -github.com/spf13/afero v1.4.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.16.0 h1:9zAqOYLl8Tuy3E5R6ckzGDJ1g8+pw15oQp2iL9Jl6gQ= -github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yl2chen/cidranger v1.0.0/go.mod h1:L7Msw4X7EQK7zMVjOtv7o8xMyjv1rJcNlYlMgGwP7ko= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -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/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -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-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/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/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20180823144017-11551d06cbcc/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-20180905080454-ebe1bf3edb33/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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -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/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190213192042-740235f6c0d8/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200806022845-90696ccdc692 h1:fsn47thVa7Ar/TMyXYlZgOoT7M4+kRpb+KpSAqRQx1w= -golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -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/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= -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-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200619004808-3e7fca5c55db/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200831141814-d751682dd103 h1:z46CEPU+LlO0kGGwrH8h5epkkJhRZbAHYWOWD9JhLPI= -google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k= -google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc/examples v0.0.0-20200825162801-44d73dff99bf/go.mod h1:Lh55/1hxmVHEkOvSIQ2uj0P12QyOCUNyRwnUlSS13hw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -helm.sh/helm/v3 v3.2.4/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -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= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -istio.io/api v0.0.0-20190515205759-982e5c3888c6/go.mod h1:hhLFQmpHia8zgaM37vb2ml9iS5NfNfqZGRt1pS9aVEo= -istio.io/api v0.0.0-20200812202721-24be265d41c3/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64= -istio.io/api v0.0.0-20200827131210-bd18678dabfe/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64= -istio.io/api v0.0.0-20200828230038-701568332f0f h1:gSIDlys0LPrr6dli/xNFTCvkzkZ9YH21M/KWdqUb/is= -istio.io/api v0.0.0-20200828230038-701568332f0f/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64= -istio.io/client-go v0.0.0-20200812230733-f5504d568313/go.mod h1:SO65MWt7I45dvUwuDowoiB0SVcGpfWZfUTlopvYpbZc= -istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs= -istio.io/gogo-genproto v0.0.0-20200720193312-b523a30fe746/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs= -istio.io/istio v0.0.0-20200831162021-362e3d808028 h1:T1d4fo8Xw6DOz7WTkNHvdEHVS3D6Yc6V+q74lrrrBgo= -istio.io/istio v0.0.0-20200831162021-362e3d808028/go.mod h1:csYD2h5L9VsG0EE4KcE7R/WiL7hwxmXbylA+vSX+UyU= -istio.io/pkg v0.0.0-20200721143030-6b837ddaf2ab/go.mod h1:EwvmercDF5DLCg5qUQlkM40xHwCxGoY1H/2LhI1p2YU= -istio.io/pkg v0.0.0-20200819194923-e8c102c633f0 h1:J8I7LXllwqcGqGPEbV8Io5O+wFPjIbf5rgwOlfpW3X8= -istio.io/pkg v0.0.0-20200819194923-e8c102c633f0/go.mod h1:EwvmercDF5DLCg5qUQlkM40xHwCxGoY1H/2LhI1p2YU= -k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= -k8s.io/api v0.18.1/go.mod h1:3My4jorQWzSs5a+l7Ge6JBbIxChLnY8HnuT58ZWolss= -k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= -k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= -k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= -k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc= -k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= -k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= -k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= -k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= -k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= -k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= -k8s.io/apiextensions-apiserver v0.19.0/go.mod h1:znfQxNpjqz/ZehvbfMg5N6fvBJW5Lqu5HVLTJQdP4Fs= -k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.1/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= -k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= -k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= -k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= -k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= -k8s.io/apiserver v0.19.0/go.mod h1:XvzqavYj73931x7FLtyagh8WibHpePJ1QwWrSJs2CLk= -k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= -k8s.io/cli-runtime v0.19.0/go.mod h1:tun9l0eUklT8IHIM0jors17KmUjcrAxn0myoBYwuNuo= -k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= -k8s.io/client-go v0.18.1/go.mod h1:iCikYRiXOj/yRRFE/aWqrpPtDt4P2JVWhtHkmESTcfY= -k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= -k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= -k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= -k8s.io/client-go v0.19.0 h1:1+0E0zfWFIWeyRhQYWzimJOyAk2UT7TiARaLNwJCf7k= -k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= -k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= -k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= -k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= -k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= -k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= -k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= -k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= -k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= -k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.2.0/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/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= -k8s.io/kubectl v0.19.0/go.mod h1:gPCjjsmE6unJzgaUNXIFGZGafiUp5jh0If3F/x7/rRg= -k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= -k8s.io/metrics v0.19.0/go.mod h1:WykpW8B60OeAJx1imdwUgyOID2kDljr/Q+1zrPJ98Wo= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7 h1:uuHDyjllyzRyCIvvn0OBjiRB0SgBZGqHNYAmjR7fO50= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= -sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= -sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A= -sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= -sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= -sigs.k8s.io/service-apis v0.0.0-20200731055707-56154e7bfde5/go.mod h1:s/e4e7RMwEcieVQ33+YMaKbSeaQyatebjinhC/kIJ3U= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= +k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= +k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= +k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= +k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= +k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= +k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= +k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= +k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= +sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/controllers/certificatesigningrequest_controller.go b/internal/controllers/certificatesigningrequest_controller.go new file mode 100644 index 0000000..fcb5145 --- /dev/null +++ b/internal/controllers/certificatesigningrequest_controller.go @@ -0,0 +1,171 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package controllers + +import ( + "context" + "fmt" + "github.com/Keyfactor/k8s-csr-signer/internal/signer" + "github.com/Keyfactor/k8s-csr-signer/pkg/util" + v1 "k8s.io/api/authorization/v1" + certificates "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/utils/clock" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type CertificateSigningRequestReconciler struct { + client.Client + Scheme *runtime.Scheme + SignerBuilder signer.Builder + ClusterResourceNamespace string + Clock clock.Clock + CheckApprovedCondition, CheckServiceAccountScope bool + CredsSecret, ConfigMap, CaCertConfigmap types.NamespacedName +} + +func (c *CertificateSigningRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { + reconcileLog := ctrl.LoggerFrom(ctx) + + c.SignerBuilder.Reset() + + // Get the CertificateSigningRequest + var certificateSigningRequest certificates.CertificateSigningRequest + if err = c.Get(ctx, req.NamespacedName, &certificateSigningRequest); err != nil { + if err = client.IgnoreNotFound(err); err != nil { + return ctrl.Result{}, fmt.Errorf("unexpected get error: %v", err) + } + reconcileLog.Info("Not found. Ignoring.") + return ctrl.Result{}, nil + } + + if c.CheckServiceAccountScope { + // Verify that the signerName is available within the scope of the controller's service account + scopeStatus, err := c.IsIssuerInScope(ctx, certificateSigningRequest.Spec.SignerName) + if err != nil { + return ctrl.Result{}, err + } + + if !scopeStatus.Allowed { + reconcileLog.Info(fmt.Sprintf("SignerName %q is not in scope of the controller's service account. Ignoring.", certificateSigningRequest.Spec.SignerName)) + return ctrl.Result{}, nil + } + } + + // Ignore CertificateSigningRequests that are not approved yet + if c.CheckApprovedCondition && !util.IsCertificateRequestApproved(certificateSigningRequest) { + reconcileLog.Info("CertificateSigningRequest has not been approved yet. Ignoring.") + return ctrl.Result{}, nil + } + + // Ignore CertificateSigningRequests that have already been signed + if certificateSigningRequest.Status.Certificate != nil { + reconcileLog.Info("CertificateSigningRequest has already been signed. Ignoring.") + return ctrl.Result{}, nil + } + + // Always attempt to update the Ready condition + defer func() { + reconcileLog.Info(fmt.Sprintf("Updating CertificateSigningRequest called %q", certificateSigningRequest.GetName())) + + if updateErr := c.Status().Update(ctx, &certificateSigningRequest); updateErr != nil { + err = utilerrors.NewAggregate([]error{err, updateErr}) + result = ctrl.Result{} + } + }() + + reconcileLog.Info(fmt.Sprintf("Preparing to sign CSR called %q", certificateSigningRequest.GetName())) + + // Get the credentials secret + var creds corev1.Secret + if err = c.Get(ctx, c.CredsSecret, &creds); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get Secret containing Signer credentials, secret name: %s, reason: %v", c.CredsSecret.Name, err) + } + + // Get the signer configuration + var config corev1.ConfigMap + if err = c.Get(ctx, c.ConfigMap, &config); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get ConfigMap containing Signer configuration, configmap name: %s, reason: %v", c.ConfigMap.Name, err) + } + + // Get the CA certificate + var root corev1.ConfigMap + if c.CaCertConfigmap.Name != "" { + // If the CA secret name is not specified, we will not attempt to retrieve it + err = c.Get(ctx, c.CaCertConfigmap, &root) + if err != nil { + return ctrl.Result{}, fmt.Errorf("caSecretName was provided, but failed to get ConfigMap containing CA certificate, configmap name: %q, reason: %v", c.CaCertConfigmap, err) + } + } + + // Apply the configuration to the signer builder + c.SignerBuilder. + WithContext(ctx). + WithCredsSecret(creds). + WithConfigMap(config). + WithCACertConfigMap(root) + + // Validate that there were no issues with the configuration + err = c.SignerBuilder.PreFlight() + if err != nil { + return ctrl.Result{}, err + } + + // Sign the certificate + leafAndChain, err := c.SignerBuilder.Build().Sign(certificateSigningRequest) + if err != nil { + return ctrl.Result{}, err + } + + // Update the certificate status + certificateSigningRequest.Status.Certificate = leafAndChain + + return ctrl.Result{}, nil +} + +func (c *CertificateSigningRequestReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&certificates.CertificateSigningRequest{}). + Complete(c) +} + +func (c *CertificateSigningRequestReconciler) IsIssuerInScope(ctx context.Context, issuerName string) (v1.SubjectAccessReviewStatus, error) { + scopeLog := ctrl.LoggerFrom(ctx) + + ssar := v1.SelfSubjectAccessReview{ + Spec: v1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &v1.ResourceAttributes{ + Group: "certificates.k8s.io", + Resource: "signers", + Name: issuerName, + Verb: "sign", // Check for "sign" verb for the given issuer name + }, + }, + } + + err := c.Create(ctx, &ssar) + if err != nil { + scopeLog.Error(err, "Failed to create SelfSubjectAccessReview") + return v1.SubjectAccessReviewStatus{}, err + } + + return ssar.Status, nil +} diff --git a/internal/controllers/certificatesigningrequest_controller_test.go b/internal/controllers/certificatesigningrequest_controller_test.go new file mode 100644 index 0000000..a9b3575 --- /dev/null +++ b/internal/controllers/certificatesigningrequest_controller_test.go @@ -0,0 +1,261 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package controllers + +import ( + "context" + "fmt" + "github.com/Keyfactor/k8s-csr-signer/internal/signer" + logrtesting "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + certificates "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/clock" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "testing" +) + +var ( + fixedClock = clock.RealClock{} +) + +func CreateCertificateSigningRequest(name types.NamespacedName, status certificates.RequestConditionType, csr, certificate []byte) *certificates.CertificateSigningRequest { + return &certificates.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: name.Name, + Namespace: name.Namespace, + }, + Spec: certificates.CertificateSigningRequestSpec{ + SignerName: fakeSignerName, + Request: csr, + }, + Status: certificates.CertificateSigningRequestStatus{ + Conditions: []certificates.CertificateSigningRequestCondition{ + { + Type: status, + }, + }, + Certificate: certificate, + }, + } +} + +func CreateFakeCreds(name types.NamespacedName) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name.Name, + Namespace: name.Namespace, + }, + Type: corev1.SecretTypeTLS, + Data: map[string][]byte{ + "tls.crt": []byte("public key"), + "tls.key": []byte("private key"), + }, + } +} + +func CreateFakeConfig(name types.NamespacedName) *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name.Name, + Namespace: name.Namespace, + }, + Data: map[string]string{ + "commandHostname": "fake-hostname.command.com", + "defaultCertificateTemplate": "FakeCertTemplate", + "defaultCertificateAuthorityLogicalName": "FakeCALogicalName", + "defaultCertificateAuthorityHostname": "fake-ca.command.com", + "chainDepth": "2", + }, + } +} + +func TestCertificateSigningRequestReconciler_Reconcile(t *testing.T) { + type testCase struct { + name types.NamespacedName + objects []client.Object + clusterResourceNamespace string + expectedResult ctrl.Result + expectedError error + expectedCertificate []byte + credsSecret, configMap, caCertConfigmap types.NamespacedName + signerBuilder signer.Builder + checkScope bool + } + + namespacedCsrName := types.NamespacedName{Namespace: "ns1", Name: "csr1"} + namespacedCredsName := types.NamespacedName{Namespace: "ns1", Name: "creds1"} + namespacedCaCertConfigmapName := types.NamespacedName{Namespace: "ns1", Name: "caCertConfigmap1"} + + tests := map[string]testCase{ + "not-found": { + name: namespacedCsrName, + signerBuilder: &FakeSignerBuilder{}, + }, + "not-approved": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateDenied, nil, nil), + }, + signerBuilder: &FakeSignerBuilder{}, + }, + "already-signed": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateApproved, nil, fakeSuccessCertificate), + }, + expectedCertificate: fakeSuccessCertificate, + signerBuilder: &FakeSignerBuilder{}, + }, + "no-creds": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateApproved, nil, nil), + }, + expectedError: fmt.Errorf("failed to get Secret containing Signer credentials, secret name: , reason: secrets \"\" not found"), + signerBuilder: &FakeSignerBuilder{}, + }, + "no-configmap": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateApproved, nil, nil), + CreateFakeCreds(namespacedCredsName), + }, + credsSecret: namespacedCredsName, + expectedError: fmt.Errorf("failed to get ConfigMap containing Signer configuration, configmap name: , reason: configmaps \"\" not found"), + signerBuilder: &FakeSignerBuilder{}, + }, + "no-ca-cert-configmap": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateApproved, nil, nil), + CreateFakeCreds(namespacedCredsName), + CreateFakeConfig(namespacedCredsName), + }, + credsSecret: namespacedCredsName, + configMap: namespacedCredsName, + caCertConfigmap: namespacedCaCertConfigmapName, + expectedError: fmt.Errorf("caSecretName was provided, but failed to get ConfigMap containing CA certificate, configmap name: %q, reason: configmaps %q not found", namespacedCaCertConfigmapName, namespacedCaCertConfigmapName.Name), + signerBuilder: &FakeSignerBuilder{}, + }, + "sign-error": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateApproved, fakeCsr, nil), + CreateFakeCreds(namespacedCredsName), + CreateFakeConfig(namespacedCredsName), + }, + credsSecret: namespacedCredsName, + configMap: namespacedCredsName, + signerBuilder: &FakeSignerBuilder{ + errSign: fmt.Errorf("sign error"), + }, + expectedError: fmt.Errorf("sign error"), + }, + "success": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateApproved, fakeCsr, nil), + CreateFakeCreds(namespacedCredsName), + CreateFakeConfig(namespacedCredsName), + }, + credsSecret: namespacedCredsName, + configMap: namespacedCredsName, + signerBuilder: &FakeSignerBuilder{}, + expectedCertificate: fakeSuccessCertificate, + }, + "denied": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateDenied, fakeCsr, nil), + CreateFakeCreds(namespacedCredsName), + CreateFakeConfig(namespacedCredsName), + }, + credsSecret: namespacedCredsName, + configMap: namespacedCredsName, + signerBuilder: &FakeSignerBuilder{}, + }, + "check-scope": { + name: namespacedCsrName, + objects: []client.Object{ + CreateCertificateSigningRequest(namespacedCsrName, certificates.CertificateApproved, fakeCsr, nil), + }, + signerBuilder: &FakeSignerBuilder{}, + checkScope: true, + // The fake client does not have a fake SelfSubjectAccessReview API, so we expect an error + expectedError: fmt.Errorf(" \"\" is invalid: metadata.name: Required value: name is required"), + }, + } + + scheme := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(scheme)) + require.NoError(t, corev1.AddToScheme(scheme)) + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(tc.objects...). + Build() + controller := CertificateSigningRequestReconciler{ + Client: fakeClient, + Scheme: scheme, + ClusterResourceNamespace: tc.clusterResourceNamespace, + SignerBuilder: tc.signerBuilder, + CheckApprovedCondition: true, + Clock: fixedClock, + CredsSecret: tc.credsSecret, + ConfigMap: tc.configMap, + CaCertConfigmap: tc.caCertConfigmap, + CheckServiceAccountScope: tc.checkScope, + } + result, err := controller.Reconcile( + ctrl.LoggerInto(context.TODO(), logrtesting.New(t)), + reconcile.Request{NamespacedName: tc.name}, + ) + if tc.expectedError != nil { + assertErrorIs(t, tc.expectedError, err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tc.expectedResult, result, "Unexpected result") + + var csr certificates.CertificateSigningRequest + err = fakeClient.Get(context.TODO(), tc.name, &csr) + require.NoError(t, client.IgnoreNotFound(err), "unexpected error from fake client") + if err == nil { + assert.Equal(t, tc.expectedCertificate, csr.Status.Certificate) + } + }) + } +} + +func assertErrorIs(t *testing.T, expectedError, actualError error) { + if !assert.Error(t, actualError) { + return + } + assert.Equal(t, expectedError.Error(), actualError.Error(), "unexpected error type. expected: %v, got: %v", expectedError, actualError) +} diff --git a/internal/controllers/fake_signer_test.go b/internal/controllers/fake_signer_test.go new file mode 100644 index 0000000..6e7f348 --- /dev/null +++ b/internal/controllers/fake_signer_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package controllers + +import ( + "context" + "github.com/Keyfactor/k8s-csr-signer/internal/signer" + certificates "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" +) + +var _ signer.Builder = &FakeSignerBuilder{} +var _ signer.Signer = &FakeSignerBuilder{} + +type FakeSignerBuilder struct { + errSign error +} + +func (f *FakeSignerBuilder) Reset() signer.Builder { + return f +} + +func (f *FakeSignerBuilder) WithContext(ctx context.Context) signer.Builder { + return f +} + +func (f *FakeSignerBuilder) WithCredsSecret(secret corev1.Secret) signer.Builder { + return f +} + +func (f *FakeSignerBuilder) WithConfigMap(configMap corev1.ConfigMap) signer.Builder { + return f +} + +func (f *FakeSignerBuilder) WithCACertConfigMap(configMap corev1.ConfigMap) signer.Builder { + return f +} + +func (f *FakeSignerBuilder) PreFlight() error { + return nil +} + +func (f *FakeSignerBuilder) Build() signer.Signer { + return f +} + +func (f *FakeSignerBuilder) Sign(csr certificates.CertificateSigningRequest) ([]byte, error) { + return fakeSuccessCertificate, f.errSign +} + +var ( + fakeSuccessCertificate = []byte("fake signed certificate") + fakeCsr = []byte("fake csr") +) + +const ( + fakeSignerName = "fakesigner.com" +) diff --git a/internal/gateway/certificate-enroll.go b/internal/gateway/certificate-enroll.go deleted file mode 100644 index 393c158..0000000 --- a/internal/gateway/certificate-enroll.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package gateway - -import ( - "context" - - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "istio.io/api/security/v1alpha1" -) - -// CreateCertificate response cert chain -func (k *KeyfactorGateway) CreateCertificate(ctx context.Context, - req *v1alpha1.IstioCertificateRequest) (*v1alpha1.IstioCertificateResponse, error) { - metadata, err := keyfactor.ExtractMetadataFromCSR(ctx, req) - if err != nil { - sLog.Errorf("cannot extract metadata from request: %v", err) - return nil, status.Errorf(codes.InvalidArgument, "cannot extract metadata from request: %v", err) - } - - resp, err := k.keyfactorClient.CSRSign(ctx, req.GetCsr(), metadata, false) - if err != nil { - sLog.Errorf("cannot request sign CSR to keyfactor api: %v", err) - return nil, status.Errorf(codes.InvalidArgument, "cannot request sign CSR to keyfactor api: %v", err) - } - return &v1alpha1.IstioCertificateResponse{ - CertChain: resp.CertificateInformation.Certificates, - }, nil -} diff --git a/internal/gateway/istio-secret.go b/internal/gateway/istio-secret.go deleted file mode 100644 index 9de865b..0000000 --- a/internal/gateway/istio-secret.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package gateway - -import ( - "context" - "fmt" - "io/ioutil" - - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var ( - istioLog = klogger.Register("IstioSecret") -) - -func (k *KeyfactorGateway) createIstioTLSSecretIfMissing(ctx context.Context) error { - - if !k.serverConfig.EnableAutoProvisioningIstioCert { - return nil - } - istioLog.Infof("Provisioning TLS Client Secret for Istio ...") - istioLog.Infof("Namespace = '%v', SecretName = '%v'", k.serverConfig.IstioNamespace, k.serverConfig.IstioSecretName) - existedSecret, err := k.k8sClient.CoreV1().Secrets(k.serverConfig.IstioNamespace).Get(ctx, k.serverConfig.IstioSecretName, v1.GetOptions{}) - - if existedSecret.Name != "" { - istioLog.Infof("Istio TLS Client secret is existed: '%v'", existedSecret.Name) - return nil - } - - if err != nil { - clientPrivate, err := ioutil.ReadFile(clientPrivateKey) - if err != nil { - return fmt.Errorf("read client private key (%v) failed: %v", clientPrivateKey, err) - } - clientCert, err := ioutil.ReadFile(clientCertificate) - if err != nil { - return fmt.Errorf("read client cert (%v) failed: %v", clientCertificate, err) - } - caCert, err := ioutil.ReadFile(rootCAFile) - if err != nil { - return fmt.Errorf("read ca cert (%v) failed: %v", rootCAFile, err) - } - - namespace := &corev1.Namespace{ - ObjectMeta: v1.ObjectMeta{ - Name: k.serverConfig.IstioNamespace, - }, - } - if _, err = k.k8sClient.CoreV1().Namespaces().Create(ctx, namespace, v1.CreateOptions{}); err != nil { - istioLog.Infof("Namespace (%v) is existed: %v", k.serverConfig.IstioNamespace, err) - } - - secret := &corev1.Secret{ - ObjectMeta: v1.ObjectMeta{ - Name: k.serverConfig.IstioSecretName, - Labels: map[string]string{ - "app": k.serverConfig.ServiceName, - "env": k.serverConfig.Environment, - }, - Namespace: k.serverConfig.IstioNamespace, - }, - StringData: map[string]string{ - "client-key.pem": string(clientPrivate), - "client-cert.pem": string(clientCert), - "cacert.pem": string(caCert), - }, - } - - s, err := k.k8sClient.CoreV1().Secrets(k.serverConfig.IstioNamespace).Create(ctx, secret, v1.CreateOptions{}) - if err != nil { - istioLog.Errorf("create TLS CLIENT secret for Istio failed: %v", err) - return fmt.Errorf("create TLS CLIENT secret for Istio failed: %v", err) - } - log.Infof("create TLS CLIENT secret for Istio successful: %v", s.Name) - } - - return nil -} diff --git a/internal/gateway/service.go b/internal/gateway/service.go deleted file mode 100644 index 5fe6a66..0000000 --- a/internal/gateway/service.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package gateway - -import ( - "context" - "crypto/tls" - "fmt" - - "github.com/Keyfactor/k8s-proxy/pkg/config" - "github.com/Keyfactor/k8s-proxy/pkg/k8s" - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "k8s.io/client-go/kubernetes" - - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/status" - "istio.io/api/security/v1alpha1" -) - -var ( - sLog = klogger.Register("GatewayService") -) - -// KeyfactorGateway the Proxy service -type KeyfactorGateway struct { - v1alpha1.UnimplementedIstioCertificateServiceServer - // GRPC the server implement based istio signing api - GPRC *grpc.Server - - keyfactorClient keyfactor.SigningClientInterface - serverConfig *config.ServerConfig - credentials *keyfactor.ClientCredential - k8sClient *kubernetes.Clientset - authenticator *k8s.KubeJWTAuthenticator -} - -// NewKeyfactorGateway create new KeyfactorGateway service -func NewKeyfactorGateway(keyfactorClient keyfactor.SigningClientInterface, serverConfig *config.ServerConfig, - credentials *keyfactor.ClientCredential, k8sClient *kubernetes.Clientset) (*KeyfactorGateway, error) { - k := &KeyfactorGateway{ - keyfactorClient: keyfactorClient, - serverConfig: serverConfig, - credentials: credentials, - k8sClient: k8sClient, - } - var err error - - k.authenticator = k8s.NewKubeJWTAuthenticator(k.k8sClient, serverConfig) - - if serverConfig.DisableMTLS { - err = k.initGRPCServerWithInsecure() - if err != nil { - return nil, fmt.Errorf("cannot create new Insecure Keyfactor Gateway: %v", err) - } - } else { - err = k.initGRPCServerWithTLS() - if err != nil { - return nil, fmt.Errorf("cannot create new TLS Keyfactor Gateway: %v", err) - } - } - v1alpha1.RegisterIstioCertificateServiceServer(k.GPRC, k) - return k, nil -} - -func (k *KeyfactorGateway) initGRPCServerWithTLS() error { - sLog.Info("initGRPCServerWithTLS...") - certificate, rootCAs, err := k.retrieveServerTLS() - sLog.Info("retrieveServerTLS...") - if err != nil { - sLog.Errorf("cannot retrive TLS server: %v", err) - return fmt.Errorf("cannot retrive TLS server: %v", err) - } - - err = k.createIstioTLSSecretIfMissing(context.TODO()) - if err != nil { - return fmt.Errorf("create Istio TLS Secret failed: %v", err) - } - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{*certificate}, - ClientCAs: rootCAs, - ClientAuth: tls.RequireAndVerifyClientCert, - } - - sLog.Info("tlsConfig...") - - k.GPRC = grpc.NewServer( - grpc.UnaryInterceptor(k.authenticate), - grpc.Creds(credentials.NewTLS(tlsConfig)), - ) - - return nil -} - -func (k *KeyfactorGateway) initGRPCServerWithInsecure() error { - sLog.Info("initGRPCServerWithInsecure...") - k.GPRC = grpc.NewServer() - return nil -} - -func (k *KeyfactorGateway) authenticate(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { - // mTLS verification - sLog.Info("Checking peer certificate...") - err = k.verifyPeer(ctx) - if err != nil { - sLog.Errorf("Verify peer got err: %v", err) - return nil, status.Error(codes.Unauthenticated, "Client TLS certificate is missing or invalid") - } - return handler(ctx, req) -} diff --git a/internal/gateway/tls.go b/internal/gateway/tls.go deleted file mode 100644 index 67fc602..0000000 --- a/internal/gateway/tls.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package gateway - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "io/ioutil" - "os" - "time" - - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "github.com/Keyfactor/k8s-proxy/pkg/util" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/status" -) - -var ( - hostName = "keyfactor-k8s.svc.keyfactor.cluster.local" - serverCertificate = "./certs/server.crt" - serverPrivateKey = "./certs/server.key" - rootCAFile = "./certs/root-cert.pem" - clientCertificate = "./certs/client.crt" - clientPrivateKey = "./certs/client.key" - log = klogger.Register("retrieveServerTLS") -) - -func (k *KeyfactorGateway) retrieveServerTLS() (*tls.Certificate, *x509.CertPool, error) { - - // If missing, generate new one - if _, err := os.Stat(serverCertificate); err != nil { - log.Infof("starting gen new Server tls certificates") - if err = k.genTLSCertificateFromKeyfactor(serverPrivateKey, serverCertificate, rootCAFile); err != nil { - log.Errorf("gen certificate for server got error: %v", err) - return nil, nil, fmt.Errorf("gen certificate for server got error: %v", err) - } - if err = k.genTLSCertificateFromKeyfactor(clientPrivateKey, clientCertificate, rootCAFile); err != nil { - log.Errorf("gen certificate for client got error: %v", err) - return nil, nil, fmt.Errorf("gen certificate for client got error: %v", err) - } - } else { - log.Infof("server tls certificate existed.") - } - - log.Infof("loading tls certificate") - certificate, err := tls.LoadX509KeyPair(serverCertificate, serverPrivateKey) - if err != nil { - return nil, nil, fmt.Errorf("load TLS certificate failed: %v", err) - } - - log.Infof("loading root CA at: %v", rootCAFile) - rootCertBytes, err := ioutil.ReadFile(rootCAFile) - if err != nil { - return nil, nil, fmt.Errorf("load root-cert failed: %v", err) - } - - log.Infof("create rootCAPool..") - rootCAPool := x509.NewCertPool() - if ok := rootCAPool.AppendCertsFromPEM(rootCertBytes); !ok { - return nil, nil, fmt.Errorf("append certs from root PEM failed: %v", string(rootCertBytes)) - } - return &certificate, rootCAPool, nil -} - -func (k *KeyfactorGateway) genTLSCertificateFromKeyfactor(keyFile, certFile, caFile string) error { - if _, err := os.Stat("./certs"); err != nil { - os.MkdirAll("./certs", 0777) - } - - privKey, err := rsa.GenerateKey(rand.Reader, 2048) - hostNames := []string{k.serverConfig.Hostname, "0.0.0.0", "127.0.0.1"} - sanIds, err := util.BuildSubjectAltNameExtension(hostNames...) - if err != nil { - return fmt.Errorf("cannot generate private key: %v", err) - } - template := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: k.serverConfig.Hostname, - Organization: []string{"Keyfactor Inc"}, - }, - DNSNames: hostNames, - ExtraExtensions: []pkix.Extension{*sanIds}, - } - csr, err := x509.CreateCertificateRequest(rand.Reader, template, privKey) - blockCSR := &pem.Block{ - Type: "CERTIFICATE REQUEST", - Bytes: csr, - } - - csrStr := string(pem.EncodeToMemory(blockCSR)) - - log.Infof("CSR for enroll server TLS certficate: %v", csrStr) - if err != nil { - return fmt.Errorf("cannot generate CSR: %v", err) - } - - cxt, cancel := context.WithTimeout(context.TODO(), 30*time.Second) - defer cancel() - - metadata := &keyfactor.CSRMetadata{ - TrustDomain: k.serverConfig.ClusterDomain, - ClusterID: "Kubernetes", - PodName: k.serverConfig.PodName, - PodNamespace: k.serverConfig.Namespace, - ServiceName: k.serverConfig.ServiceName, - PodIP: k.serverConfig.PodIP, - } - - res, err := k.keyfactorClient.CSRSign(cxt, csrStr, metadata, true) - certChain := res.CertificateInformation.Certificates - block, _ := pem.Decode([]byte(certChain[0])) - err = ioutil.WriteFile(certFile, pem.EncodeToMemory(block), 0777) - if err != nil { - return fmt.Errorf("save server cert failed: %v", err) - } - err = savePEMKey(keyFile, privKey) - if err != nil { - return fmt.Errorf("save private key failed: %v", err) - } - - rootCABlock, _ := pem.Decode([]byte(certChain[len(certChain)-1])) - err = ioutil.WriteFile(caFile, pem.EncodeToMemory(rootCABlock), 0777) - if err != nil { - return fmt.Errorf("save rootCA cert failed: %v", err) - } - return nil -} - -func savePEMKey(fileName string, key *rsa.PrivateKey) error { - outFile, err := os.Create(fileName) - if err != nil { - return fmt.Errorf("cannot create file %v - err: %v", fileName, err) - } - defer outFile.Close() - - var privateKey = &pem.Block{ - Type: "PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(key), - } - - err = pem.Encode(outFile, privateKey) - if err != nil { - return fmt.Errorf("cannot writefile file %v - err: %v", fileName, err) - } - return nil -} - -func (k *KeyfactorGateway) verifyPeer(ctx context.Context) error { - p, ok := peer.FromContext(ctx) - if !ok { - return status.Error(codes.Unauthenticated, "no peer found") - } - - tlsAuth, ok := p.AuthInfo.(credentials.TLSInfo) - if !ok { - return status.Error(codes.Unauthenticated, "unexpected peer transport credentials") - } - - if len(tlsAuth.State.VerifiedChains) == 0 || len(tlsAuth.State.VerifiedChains[0]) == 0 { - return status.Error(codes.Unauthenticated, "could not verify peer certificate") - } - - sLog.Infof("Verify peer mTLS: %+v", tlsAuth.State) - // Check subject common name against configured Keyfactor's CaName - if tlsAuth.State.VerifiedChains[0][0].Subject.CommonName != k.serverConfig.Hostname { - return status.Error(codes.Unauthenticated, "invalid subject common name") - } - - return nil -} diff --git a/internal/health/service.go b/internal/health/service.go deleted file mode 100644 index cd1559c..0000000 --- a/internal/health/service.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package health - -import ( - "fmt" - - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "github.com/valyala/fasthttp" -) - -var ( - log = klogger.Register("HealthCheckLog") -) - -// ServiceHealthCheck create a health check service -type ServiceHealthCheck struct { - Addr string -} - -// Serve start listen health check -func (s *ServiceHealthCheck) Serve() error { - address := fmt.Sprintf("[::]:%s", s.Addr) - log.Infof("start health check at: %v", address) - if err := fasthttp.ListenAndServe(address, fasthttp.CompressHandler(s.requestHandler)); err != nil { - log.Fatalf("Error in ListenAndServe: %s", err) - return fmt.Errorf("Error in ListenAndServe: %s", err) - } - return nil -} - -func (s *ServiceHealthCheck) requestHandler(ctx *fasthttp.RequestCtx) { - ctx.SetStatusCode(fasthttp.StatusOK) - fmt.Fprintf(ctx, "OK!") - ctx.SetContentType("text/plain; charset=utf8") -} diff --git a/internal/signer/controller.go b/internal/signer/controller.go deleted file mode 100644 index c597cf1..0000000 --- a/internal/signer/controller.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package signer - -import ( - "fmt" - "strings" - - "time" - - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "golang.org/x/time/rate" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/workqueue" - - certificates "k8s.io/api/certificates/v1beta1" - "k8s.io/apimachinery/pkg/api/errors" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - certificateslisters "k8s.io/client-go/listers/certificates/v1beta1" -) - -var ( - signerLogger = klogger.Register("CertificateSigner") -) - -const ( - // CertificateControllerName name - CertificateControllerName = "keyfactor-certificate-signer" - // KeyfactorSignerNameScope default certificate signerName - KeyfactorSignerNameScope = "keyfactor.com" -) - -// CertificateController contains config for Certificate Siging Request Controller -type CertificateController struct { - kubeClient clientset.Interface - csrLister certificateslisters.CertificateSigningRequestLister - csrInformer cache.SharedIndexInformer - handler func(*certificates.CertificateSigningRequest) error - workqueue workqueue.RateLimitingInterface - keyfactorClient keyfactor.SigningClientInterface -} - -// NewCertificateController create new Kubernetes Controller to watching Certificate Signing Request -func NewCertificateController(kubeClient clientset.Interface, keyfactorClient keyfactor.SigningClientInterface) *CertificateController { - // Send events to the apiserver - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartLogging(signerLogger.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) - - informerFactory := informers.NewSharedInformerFactory(kubeClient, 0) - csrInformer := informerFactory.Certificates().V1beta1().CertificateSigningRequests() - - cc := &CertificateController{ - kubeClient: kubeClient, - workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewMaxOfRateLimiter( - workqueue.NewItemExponentialFailureRateLimiter(200*time.Millisecond, 1000*time.Second), - // 10 qps, 100 bucket size. This is only for retry speed and its only the overall factor (not per item) - &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}, - ), CertificateControllerName), - keyfactorClient: keyfactorClient, - csrInformer: csrInformer.Informer(), - csrLister: csrInformer.Lister(), - } - - // Manage the addition/update of certificate requests - csrInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - csr := obj.(*certificates.CertificateSigningRequest) - if !strings.Contains(*csr.Spec.SignerName, KeyfactorSignerNameScope) { - signerLogger.Warnf("ADD NEW CSR: Out of scope of Keyfactor Signer - %s", *csr.Spec.SignerName) - return - } - - if IsCertificateRequestApproved(csr) { - return - } - signerLogger.Infof("Adding CSR name '%s', signer '%s'. Waiting for approval...", csr.Name, *csr.Spec.SignerName) - }, - UpdateFunc: func(old, new interface{}) { - newCSR := new.(*certificates.CertificateSigningRequest) - oldCSR := old.(*certificates.CertificateSigningRequest) - signerLogger.Infof("Updating certificate request: NEW - %#v", newCSR.Name) - if !strings.Contains(*newCSR.Spec.SignerName, KeyfactorSignerNameScope) { - signerLogger.Warnf("Out of scope of Keyfactor Signer - %s", *newCSR.Spec.SignerName) - return - } - - if IsCertificateRequestApproved(oldCSR) || !IsCertificateRequestApproved(newCSR) { - return - } - - signerLogger.Infof("Certificate approved: %s. Waiting for signing ...", newCSR.Name) - cc.workqueue.Add(newCSR.Name) - }, - DeleteFunc: func(obj interface{}) { - csr, ok := obj.(*certificates.CertificateSigningRequest) - if !strings.Contains(*csr.Spec.SignerName, KeyfactorSignerNameScope) { - signerLogger.Warnf("Deleting CSR: Out of scope of Keyfactor Signer - %s", *csr.Spec.SignerName) - return - } - if !ok { - tombstone, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - signerLogger.Infof("Couldn't get object from tombstone %#v", obj) - return - } - csr, ok = tombstone.Obj.(*certificates.CertificateSigningRequest) - if !ok { - signerLogger.Infof("Tombstone contained object that is not a CSR: %#v", obj) - return - } - } - signerLogger.Infof("Deleting certificate request %s", csr.Name) - }, - }) - - cc.handler = cc.handleCSR - return cc -} - -// RunWorker the main goroutine responsible for watching and syncing jobs. -func (cc *CertificateController) RunWorker(workers int, stopCh <-chan struct{}) { - defer utilruntime.HandleCrash() - defer cc.workqueue.ShutDown() - - go cc.csrInformer.Run(stopCh) - - // Handle timeout for syncing. - timeout := time.NewTimer(time.Second * 30) - timeoutCh := make(chan struct{}) - - go func() { - <-timeout.C - timeoutCh <- struct{}{} - }() - - signerLogger.Infoln("Waiting cache to be synced. Timeout: 30s") - if ok := cache.WaitForCacheSync(timeoutCh, cc.csrInformer.HasSynced); !ok { - signerLogger.Fatalln("Timeout expired during waiting for caches to sync.") - } - - // Launch workers to process Certificates resources - for i := 0; i < workers; i++ { - go wait.Until(cc.worker, time.Second, stopCh) - } - - signerLogger.Infoln("Starting custom controller.") - <-stopCh -} - -// worker runs a thread that dequeues CSRs, handles them, and marks them done. -func (cc *CertificateController) worker() { - for cc.processNextWorkItem() { - } -} - -// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. -func (cc *CertificateController) processNextWorkItem() bool { - cKey, quit := cc.workqueue.Get() - if quit { - return false - } - defer cc.workqueue.Done(cKey) - - if err := cc.syncFunc(cKey.(string)); err != nil { - cc.workqueue.AddRateLimited(cKey) - if _, ignorable := err.(IgnorableErrorType); !ignorable { - utilruntime.HandleError(fmt.Errorf("Sync %v failed with : %v", cKey, err)) - } else { - signerLogger.Infof("Sync %v failed with : %v", cKey, err) - } - return true - } - - cc.workqueue.Forget(cKey) - return true - -} - -// maybeSignCertificate will inspect the certificate request and, if it has -// been approved and meets policy expectations, generate an X509 cert using the -// cluster CA assets. If successful it will update the CSR approve subresource -// with the signed certificate. -func (cc *CertificateController) syncFunc(key string) error { - startTime := time.Now() - defer func() { - signerLogger.Infof("Finished syncing certificate request %q (%v)", key, time.Since(startTime)) - }() - csr, err := cc.csrLister.Get(key) - if errors.IsNotFound(err) { - signerLogger.Infof("csr has been deleted: %v", key) - return nil - } - if err != nil { - return err - } - - if csr.Status.Certificate != nil { - // no need to do anything because it already has a cert - return nil - } - - // need to operate on a copy so we don't mutate the csr in the shared cache - csr = csr.DeepCopy() - - return cc.handler(csr) -} - -// IgnorableError returns an error that we shouldn't handle (i.e. log) because -// it's spammy and usually user error. Instead we will log these errors at a -// higher log level. We still need to throw these errors to signal that the -// sync should be retried. -func IgnorableError(s string, args ...interface{}) IgnorableErrorType { - return IgnorableErrorType(fmt.Sprintf(s, args...)) -} - -type IgnorableErrorType string - -func (e IgnorableErrorType) Error() string { - return string(e) -} diff --git a/internal/signer/handler.go b/internal/signer/handler.go deleted file mode 100644 index 308e9b8..0000000 --- a/internal/signer/handler.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package signer - -import ( - "context" - "fmt" - "strings" - "time" - - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - capi "k8s.io/api/certificates/v1beta1" - certificates "k8s.io/api/certificates/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var ( - hanlderLog = klogger.Register("CertificateSigner-Handler") -) - -func (c *CertificateController) handleCSR(csr *capi.CertificateSigningRequest) error { - if !IsCertificateRequestApproved(csr) { - return nil - } - hanlderLog.Infof("Request Certificate - signerName: %s", *csr.Spec.SignerName) - if !strings.Contains(*csr.Spec.SignerName, KeyfactorSignerNameScope) { - hanlderLog.Errorf("Request Certificate - out of signer name scope: %s", *csr.Spec.SignerName) - return fmt.Errorf("Invalid certificate SignerName: %s", *csr.Spec.SignerName) - } - - var usages []string - - for _, usage := range csr.Spec.Usages { - usages = append(usages, string(usage)) - } - - hanlderLog.Infof("Request Certificate - usages: %v", usages) - - timeoutContext, cancel := context.WithTimeout(context.TODO(), 15*time.Second) - defer cancel() - - csrMetadata := extractMetadataFromK8SCSRAPI(csr.GetObjectMeta().GetAnnotations()) - hanlderLog.Infof("Request Certificate - extra metadata: %#v", csrMetadata) - res, err := c.keyfactorClient.CSRSign(timeoutContext, string(csr.Spec.Request), csrMetadata, false) - - if err != nil { - hanlderLog.Errorf("cannot signing certificate from K8S CSR API: %v", err) - return fmt.Errorf("cannot signing certificate from K8S CSR API: %v", err) - } - certChain := res.CertificateInformation.Certificates - csr.Status.Certificate = []byte(strings.Join(certChain, "")) - - _, err = c.kubeClient.CertificatesV1beta1().CertificateSigningRequests().UpdateStatus(context.TODO(), csr, v1.UpdateOptions{}) - - if err != nil { - hanlderLog.Errorf("error updating signature for csr: %v", err) - return fmt.Errorf("error updating signature for csr: %v", err) - } - return nil -} - -func extractMetadataFromK8SCSRAPI(extra map[string]string) *keyfactor.CSRMetadata { - meta := &keyfactor.CSRMetadata{} - - for key, value := range extra { - hanlderLog.Infof("Meta: %s - %v", key, value) - switch key { - case "ClusterID": - meta.ClusterID = value - case "ServiceName": - meta.ServiceName = value - case "PodIP": - meta.PodIP = value - case "PodName": - meta.PodName = value - case "PodNamespace": - meta.PodNamespace = value - case "TrustDomain": - meta.TrustDomain = value - } - } - - return meta -} - -// IsCertificateRequestApproved returns true if a certificate request has the -// "Approved" condition and no "Denied" conditions; false otherwise. -func IsCertificateRequestApproved(csr *certificates.CertificateSigningRequest) bool { - approved, denied := getCertApprovalCondition(&csr.Status) - return approved && !denied -} - -func getCertApprovalCondition(status *certificates.CertificateSigningRequestStatus) (approved bool, denied bool) { - for _, c := range status.Conditions { - if c.Type == certificates.CertificateApproved { - approved = true - } - if c.Type == certificates.CertificateDenied { - denied = true - } - } - return -} diff --git a/internal/signer/handler_test.go b/internal/signer/handler_test.go deleted file mode 100644 index cada490..0000000 --- a/internal/signer/handler_test.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package signer - -import ( - "context" - "testing" - - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - keyfactorMock "github.com/Keyfactor/k8s-proxy/pkg/keyfactor/mock" - - "github.com/stretchr/testify/assert" - "k8s.io/api/certificates/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8sFake "k8s.io/client-go/kubernetes/fake" -) - -func TestHandleCSR(t *testing.T) { - invalidSignerName := "scope-hostname.io/unmatch-name" - validSignerName := "keyfactor.com/certificate-name" - - testcases := map[string]struct { - csr *v1beta1.CertificateSigningRequest - expectError string - expectCSRMetadata *keyfactor.CSRMetadata - expectCallKeyfactor bool - }{ - "UnApproved CSR": { - csr: &v1beta1.CertificateSigningRequest{ - Spec: v1beta1.CertificateSigningRequestSpec{ - SignerName: &validSignerName, - Request: []byte("FAKE_CSR"), - }, - }, - expectCallKeyfactor: false, - }, - "UnMatched signerName": { - csr: &v1beta1.CertificateSigningRequest{ - Spec: v1beta1.CertificateSigningRequestSpec{ - SignerName: &invalidSignerName, - Request: []byte("FAKE_CSR"), - }, - Status: v1beta1.CertificateSigningRequestStatus{ - Conditions: []v1beta1.CertificateSigningRequestCondition{ - { - Type: v1beta1.CertificateApproved, - Reason: "AutoApproved", - Message: "TestApproved", - }, - }, - }, - }, - expectError: "Invalid certificate SignerName: scope-hostname.io/unmatch-name", - }, - "Should run successful": { - csr: &v1beta1.CertificateSigningRequest{ - Spec: v1beta1.CertificateSigningRequestSpec{ - SignerName: &validSignerName, - Request: []byte("FAKE_CSR"), - Extra: map[string]v1beta1.ExtraValue{ - "ServiceName": []string{"KHOA"}, - "OutOfScope": []string{"SHOULD_IGNORE"}, - }, - }, - Status: v1beta1.CertificateSigningRequestStatus{ - Conditions: []v1beta1.CertificateSigningRequestCondition{ - { - Type: v1beta1.CertificateApproved, - Reason: "AutoApproved", - Message: "TestApproved", - }, - }, - }, - }, - expectCSRMetadata: &keyfactor.CSRMetadata{ - ServiceName: "KHOA", - }, - expectCallKeyfactor: true, - }, - } - - for id, tc := range testcases { - t.Run(id, func(tsub *testing.T) { - as := assert.New(t) - mockClient := keyfactorMock.NewKeyfactorClientMock() - - if tc.expectCallKeyfactor { - mockClient.On("CSRSign", string(tc.csr.Spec.Request), tc.expectCSRMetadata).Times(1) - } - - controller := &CertificateController{ - keyfactorClient: mockClient, - kubeClient: k8sFake.NewSimpleClientset(), - } - csrKube, err := controller.kubeClient.CertificatesV1beta1().CertificateSigningRequests().Create(context.TODO(), tc.csr, v1.CreateOptions{}) - as.Equal(err, nil) - - err = controller.handleCSR(csrKube) - - if tc.expectError != "" { - as.Error(err, tc.expectError) - return - } - as.Equal(err, nil) - }) - } -} diff --git a/internal/signer/signer.go b/internal/signer/signer.go new file mode 100644 index 0000000..d791d8b --- /dev/null +++ b/internal/signer/signer.go @@ -0,0 +1,377 @@ +package signer + +import ( + "context" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "github.com/Keyfactor/k8s-csr-signer/pkg/util" + "github.com/Keyfactor/keyfactor-go-client-sdk/api/keyfactor" + "github.com/go-logr/logr" + certificates "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "sigs.k8s.io/controller-runtime/pkg/log" + "strconv" + "strings" + "time" +) + +// commandSigner implements both Signer and Builder interfaces +var _ Builder = &commandSigner{} +var _ Signer = &commandSigner{} + +const ( + enrollmentPEMFormat = "PEM" + annotationPrefix = "k8s-csr-signer.keyfactor.com/" +) + +type Builder interface { + Reset() Builder + WithContext(ctx context.Context) Builder + WithCredsSecret(corev1.Secret) Builder + WithConfigMap(corev1.ConfigMap) Builder + WithCACertConfigMap(corev1.ConfigMap) Builder + PreFlight() error + Build() Signer +} + +type Signer interface { + Sign(csr certificates.CertificateSigningRequest) ([]byte, error) +} + +type commandSigner struct { + ctx context.Context + logger logr.Logger + creds corev1.Secret + + // Given from config + hostname string + defaultCertificateTemplate string + defaultCertificateAuthorityLogicalName string + defaultCertificateAuthorityHostname string + chainDepth int + + // Computed + errs []error + caChain []*x509.Certificate + preflightComplete bool + + basicAuthRestClient *keyfactor.APIClient +} + +func NewCommandSignerBuilder() Builder { + return &commandSigner{} +} + +func (s *commandSigner) Reset() Builder { + s.errs = make([]error, 0) + s.preflightComplete = false + return s +} + +func (s *commandSigner) WithContext(ctx context.Context) Builder { + s.ctx = ctx + s.logger = log.FromContext(ctx) + return s +} + +func (s *commandSigner) WithCredsSecret(secret corev1.Secret) Builder { + if secret.Type == corev1.SecretTypeBasicAuth { + s.logger.Info("Found BasicAuth secret. Using BasicAuth authentication") + + _, ok := secret.Data["username"] + if !ok { + s.errs = append(s.errs, errors.New("username not found in secret data")) + } + + _, ok = secret.Data["password"] + if !ok { + s.errs = append(s.errs, errors.New("password not found in secret data")) + } + } else { + s.errs = append(s.errs, errors.New("secret type is not TLS or BasicAuth")) + } + + s.creds = secret + return s +} + +func (s *commandSigner) WithConfigMap(config corev1.ConfigMap) Builder { + if host, ok := config.Data["commandHostname"]; ok && host != "" { + s.hostname = config.Data["commandHostname"] + } else { + s.errs = append(s.errs, errors.New("commandHostname not found in config map data")) + } + + if defaultCertificateTemplate, ok := config.Data["defaultCertificateTemplate"]; ok && defaultCertificateTemplate != "" { + s.defaultCertificateTemplate = defaultCertificateTemplate + } + + if defaultCertificateAuthorityLogicalName, ok := config.Data["defaultCertificateAuthorityLogicalName"]; ok && defaultCertificateAuthorityLogicalName != "" { + s.defaultCertificateAuthorityLogicalName = defaultCertificateAuthorityLogicalName + } + + if defaultCertificateAuthorityHostname, ok := config.Data["defaultCertificateAuthorityHostname"]; ok && defaultCertificateAuthorityHostname != "" { + s.defaultCertificateAuthorityHostname = defaultCertificateAuthorityHostname + } + + if chainDepth, ok := config.Data["chainDepth"]; ok && chainDepth != "" { + var err error + s.chainDepth, err = strconv.Atoi(chainDepth) + if err != nil { + s.errs = append(s.errs, errors.New("chainDepth is not an integer")) + } + } + + return s +} + +func (s *commandSigner) WithCACertConfigMap(config corev1.ConfigMap) Builder { + if len(config.Data) == 0 { + return s + } + + // There is no requirement that the CA certificate is stored under a specific key in the secret, so we can just + // iterate over the map and effectively set the caCertBytes to the last value in the map + var caCertBytes string + for _, caCertBytes = range config.Data { + } + + // Try to decode caCertBytes as a PEM formatted block + caChainBlocks, _ := util.DecodePEMBytes([]byte(caCertBytes)) + if len(caChainBlocks) > 0 { + var caChain []*x509.Certificate + for _, block := range caChainBlocks { + // Parse the PEM block into an x509 certificate + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + s.errs = append(s.errs, err) + return s + } + + caChain = append(caChain, cert) + } + + s.caChain = caChain + } + + s.logger.Info(fmt.Sprintf("Found %d CA certificates in the CA certificate config map", len(s.caChain))) + + return s +} + +func (s *commandSigner) PreFlight() error { + var err error + + s.basicAuthRestClient, err = s.newBasicAuthClient() + if err != nil { + s.errs = append(s.errs, err) + } + + s.logger.Info("Preflight complete") + s.preflightComplete = true + return utilerrors.NewAggregate(s.errs) +} + +func (s *commandSigner) newBasicAuthClient() (*keyfactor.APIClient, error) { + // Create Command API Client + commandConfig := keyfactor.NewConfiguration(make(map[string]string)) + + if commandConfig.Host == "" { + commandConfig.Host = s.hostname + } + + username, ok := s.creds.Data["username"] + if !ok || len(username) == 0 { + return nil, errors.New("username not found in secret data") + } + + password, ok := s.creds.Data["password"] + if !ok || len(password) == 0 { + return nil, errors.New("password not found in secret data") + } + + commandConfig.BasicAuth.UserName = string(username) + commandConfig.BasicAuth.Password = string(password) + + // If the CA certificate is provided, add it to the Command configuration + commandConfig.SetCaCertificates(s.caChain) + + s.logger.Info("Creating Command REST API client with basic auth credentials") + + // Create Command API Client + client := keyfactor.NewAPIClient(commandConfig) + if client == nil { + return nil, fmt.Errorf("failed to create Command REST API client") + } + + return client, nil +} + +func (s *commandSigner) Build() Signer { + if !s.preflightComplete { + s.logger.Error(fmt.Errorf("preflight not complete"), "preflight must be completed before building signer") + return nil + } + + return s +} + +func (s *commandSigner) Sign(csr certificates.CertificateSigningRequest) ([]byte, error) { + annotations := csr.GetAnnotations() + + parsedCsr, err := parseCSR(csr.Spec.Request) + if err != nil { + return nil, err + } + + // Log the common metadata of the CSR + s.logger.Info(fmt.Sprintf("Found CSR wtih DN %q and %d DNS SANs, %d IP SANs, and %d URI SANs", parsedCsr.Subject, len(parsedCsr.DNSNames), len(parsedCsr.IPAddresses), len(parsedCsr.URIs))) + + // Create a Command CSR enrollment request for initialization + enroll := keyfactor.ModelsEnrollmentCSREnrollmentRequest{} + enroll.SetTimestamp(time.Now()) + enroll.SetIncludeChain(true) + enroll.SetCSR(string(csr.Spec.Request)) + + // Set default fields from config map - Will be overwritten by annotations if present + if s.defaultCertificateTemplate != "" { + enroll.SetTemplate(s.defaultCertificateTemplate) + } + certificateAuthorityHostname := s.defaultCertificateAuthorityHostname + certificateAuthorityLogicalName := s.defaultCertificateAuthorityLogicalName + + // Set overrides from annotations + certificateTemplate, ok := annotations[annotationPrefix+"certificateTemplate"] + if ok && certificateTemplate != "" { + s.logger.Info(fmt.Sprintf("Using the %q certificate template from CSR annotations", certificateTemplate)) + enroll.SetTemplate(certificateTemplate) + } + + overrideCertificateAuthorityHostname, ok := annotations[annotationPrefix+"certificateAuthorityHostname"] + if ok && overrideCertificateAuthorityHostname != "" { + s.logger.Info(fmt.Sprintf("Using the %q certificate authority hostname from CSR annotations", overrideCertificateAuthorityHostname)) + certificateAuthorityHostname = overrideCertificateAuthorityHostname + } + + overrideCertificateAuthorityLogicalName, ok := annotations[annotationPrefix+"certificateAuthorityLogicalName"] + if ok && overrideCertificateAuthorityLogicalName != "" { + s.logger.Info(fmt.Sprintf("Using the %q certificate authority logical name from CSR annotations", overrideCertificateAuthorityLogicalName)) + certificateAuthorityLogicalName = overrideCertificateAuthorityLogicalName + } + + chainDepthStr, ok := annotations[annotationPrefix+"chainDepth"] + if ok { + chainDepth, err := strconv.Atoi(chainDepthStr) + if err == nil { + s.logger.Info(fmt.Sprintf("Using \"%d\" as chain depth from annotation", chainDepth)) + s.chainDepth = chainDepth + } + } + + // Construct CA name from hostname and logical name + var caBuilder strings.Builder + if certificateAuthorityHostname != "" { + caBuilder.WriteString(certificateAuthorityHostname) + caBuilder.WriteString("\\") + } + caBuilder.WriteString(certificateAuthorityLogicalName) + enroll.SetCertificateAuthority(caBuilder.String()) + + // Final preflight check + if enroll.GetTemplate() == "" { + return nil, errors.New("certificate template was not found - either set the defaultCertificateTemplate in the config map or specify the certificateTemplate annotation") + } + if enroll.GetCertificateAuthority() == "" { + return nil, errors.New("certificate authority was not found - either set the defaultCertificateAuthorityLogicalName and defaultCertificateAuthorityHostname in the config map or specify the certificateAuthorityLogicalName and certificateAuthorityHostname annotations") + } + + s.logger.Info(fmt.Sprintf("Enrolling CSR with Command using the %q certificate template and the %q CA", enroll.GetTemplate(), enroll.GetCertificateAuthority())) + + // Enroll certificate + certificateObject, _, err := s.basicAuthRestClient.EnrollmentApi.EnrollmentPostCSREnroll(context.Background()).Request(enroll).XCertificateformat(enrollmentPEMFormat).Execute() + if err != nil { + detail := "error enrolling certificate with Command. verify that the certificate template, certificate authority, and credentials are correct" + + var bodyError *keyfactor.GenericOpenAPIError + ok = errors.As(err, &bodyError) + if ok { + detail += fmt.Sprintf(" - %s", string(bodyError.Body())) + } + + s.logger.Error(err, detail) + + return nil, fmt.Errorf(detail) + } + + leafAndChain, err := getCertificatesFromCertificateInformation(certificateObject.CertificateInformation) + if err != nil { + s.logger.Error(err, fmt.Sprintf("error getting certificate from Command response: %s", err.Error())) + return nil, err + } + + // Then, construct the PEM list according to chainDepth + + /* + chainDepth = 0 => whole chain + chainDepth = 1 => just the leaf + chainDepth = 2 => leaf + issuer + chainDepth = 3 => leaf + issuer + issuer + etc + */ + + // The two scenarios where we want the whole chain are when chainDepth is 0 or greater than the length of the whole chain + var pemChain []byte + if s.chainDepth == 0 || s.chainDepth > len(leafAndChain) { + s.chainDepth = len(leafAndChain) + } + for i := 0; i < s.chainDepth; i++ { + pemChain = append(pemChain, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafAndChain[i].Raw})...) + } + + s.logger.Info(fmt.Sprintf("Successfully enrolled certificate with Command and built leaf and chain to depth %d", s.chainDepth)) + + // Return the certificate and chain in PEM format + return pemChain, nil +} + +func (s *commandSigner) deprecatedAnnotationGetter(annotations map[string]string, annotation string) string { + annotationValue, ok := annotations[annotation] + if ok { + s.logger.Info(fmt.Sprintf("Annotations specified without the %q prefix is deprecated and will be removed in the future. Using %q as %q", annotationPrefix, annotationValue, annotation)) + return annotationValue + } + + return "" +} + +func getCertificatesFromCertificateInformation(commandResp *keyfactor.ModelsPkcs10CertificateResponse) ([]*x509.Certificate, error) { + var certBytes []byte + + for _, cert := range commandResp.Certificates { + block, _ := pem.Decode([]byte(cert)) + if block == nil { + return nil, errors.New("failed to parse certificate PEM") + } + + certBytes = append(certBytes, block.Bytes...) + } + + certs, err := x509.ParseCertificates(certBytes) + if err != nil { + return nil, err + } + + return certs, nil +} + +func parseCSR(pemBytes []byte) (*x509.CertificateRequest, error) { + // extract PEM from request object + block, _ := pem.Decode(pemBytes) + if block == nil || block.Type != "CERTIFICATE REQUEST" { + return nil, errors.New("PEM block type must be CERTIFICATE REQUEST") + } + return x509.ParseCertificateRequest(block.Bytes) +} diff --git a/internal/signer/signer_test.go b/internal/signer/signer_test.go new file mode 100644 index 0000000..d385129 --- /dev/null +++ b/internal/signer/signer_test.go @@ -0,0 +1,591 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package signer + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "errors" + "fmt" + "github.com/Keyfactor/k8s-csr-signer/pkg/util" + logrtesting "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/assert" + certificates "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "math/big" + mathrand "math/rand" + "net" + "net/url" + "os" + ctrl "sigs.k8s.io/controller-runtime" + "strings" + "testing" + "time" +) + +func TestNewCommandSignerBuilder(t *testing.T) { + signer := NewCommandSignerBuilder() + if signer == nil { + t.Error("NewCommandSignerBuilder() should not return nil") + } +} + +func TestCommandSignerBuilder(t *testing.T) { + signer := &commandSigner{} + + t.Run("WithContext", func(t *testing.T) { + ctx := ctrl.LoggerInto(context.TODO(), logrtesting.New(t)) + signer.WithContext(ctx) + + if signer.ctx != ctx { + t.Error("WithContext() should set the context") + } + + if !signer.logger.Enabled() { + t.Error("Expected logger to be enabled") + } + }) + + t.Run("WithCredsSecret", func(t *testing.T) { + t.Run("BasicAuth", func(t *testing.T) { + secret := corev1.Secret{ + Type: corev1.SecretTypeBasicAuth, + } + + signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + secret.Data = map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), + } + + signer.WithCredsSecret(secret) + + if len(signer.errs) != 0 { + t.Error("Expected no errors since secret is not empty") + } + }) + }) + + t.Run("WithConfigMap", func(t *testing.T) { + config := corev1.ConfigMap{} + + t.Run("Fail", func(t *testing.T) { + signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + signer.WithConfigMap(config) + + if len(signer.errs) == 0 { + t.Error("Expected errors since config is empty") + } + }) + + // Clear errors and config + signer.Reset() + + t.Run("chainDepth_not_digit", func(t *testing.T) { + signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + config.Data = map[string]string{ + "chainDepth": "not a digit", + } + + signer.WithConfigMap(config) + + if len(signer.errs) == 0 { + t.Error("Expected errors since chainDepth is not a digit") + } + }) + + // Clear errors and config + signer.Reset() + + t.Run("Success", func(t *testing.T) { + signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + config.Data = map[string]string{ + "commandHostname": "fake-hostname.command.com", + "defaultCertificateTemplate": "FakeCertTemplate", + "defaultCertificateAuthorityLogicalName": "FakeCALogicalName", + "defaultCertificateAuthorityHostname": "fake-ca.command.com", + "chainDepth": "2", + } + + signer.WithConfigMap(config) + + if len(signer.errs) != 0 { + t.Error("Expected no errors since config is not empty") + } + + assert.Equal(t, "fake-hostname.command.com", signer.hostname) + assert.Equal(t, "FakeCertTemplate", signer.defaultCertificateTemplate) + assert.Equal(t, "FakeCALogicalName", signer.defaultCertificateAuthorityLogicalName) + assert.Equal(t, "fake-ca.command.com", signer.defaultCertificateAuthorityHostname) + assert.Equal(t, 2, signer.chainDepth) + }) + }) + + t.Run("WithCACertConfigMap", func(t *testing.T) { + caConfig := corev1.ConfigMap{} + + t.Run("InvalidCert", func(t *testing.T) { + signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + caConfig.Data = map[string]string{ + "caCert.crt": "invalid cert", + } + + signer.WithCACertConfigMap(caConfig) + + if len(signer.caChain) != 0 { + t.Error("Expected no CA chain since cert is invalid") + } + }) + + // Clear errors and config + signer.Reset() + + t.Run("Success", func(t *testing.T) { + signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + certificate, err := generateSelfSignedCertificate() + if err != nil { + t.Fatalf("Failed to generate self-signed certificate: %v", err) + } + certBytes, err := util.CompileCertificatesToPemBytes([]*x509.Certificate{certificate}) + if err != nil { + t.Fatalf("Failed to compile certificate to PEM bytes: %v", err) + } + + caConfig.Data = map[string]string{ + "caCert.crt": string(certBytes), + } + + signer.WithCACertConfigMap(caConfig) + + if len(signer.caChain) != 1 { + t.Error("Expected CA chain to have one certificate") + } + + if len(signer.errs) != 0 { + t.Error("Expected no errors since config is not empty") + } + }) + }) +} + +func TestCommandSigner(t *testing.T) { + commandConfig := CommandTestConfig{} + err := commandConfig.Get(t) + if err != nil { + t.Fatal(err) + } + + signerConfig := corev1.ConfigMap{ + Data: map[string]string{ + "commandHostname": commandConfig.hostname, + "defaultCertificateTemplate": commandConfig.commandCertificateTemplate, + "defaultCertificateAuthorityLogicalName": commandConfig.commandCertificateAuthorityLogicalName, + "defaultCertificateAuthorityHostname": commandConfig.commandCertificateAuthorityHostname, + "chainDepth": "0", + }, + } + + caConfig := corev1.ConfigMap{ + Data: map[string]string{ + "caCert.crt": string(commandConfig.caCertBytes), + }, + } + + dn, err := parseSubjectDN(commandConfig.commandCsrDn, false) + if err != nil { + return + } + + t.Run("BasicAuth", func(t *testing.T) { + creds := corev1.Secret{ + Type: corev1.SecretTypeBasicAuth, + Data: map[string][]byte{ + "username": []byte(commandConfig.username), + "password": []byte(commandConfig.password), + }, + } + + // Build the signer + builder := &commandSigner{} + builder. + WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))). + WithCredsSecret(creds). + WithConfigMap(signerConfig). + WithCACertConfigMap(caConfig) + + err = builder.PreFlight() + if err != nil { + t.Fatalf("Failed to preflight signer: %v", err) + } + + signer := builder.Build() + + // Generate a CSR + csr, _, err := generateCSR(dn.String(), []string{dn.CommonName}, []string{}, []string{}) + if err != nil { + t.Fatalf("Failed to generate CSR: %v", err) + } + request := certificates.CertificateSigningRequest{ + Spec: certificates.CertificateSigningRequestSpec{ + Request: csr, + }, + } + + signedCertBytes, err := signer.Sign(request) + if err != nil { + t.Fatalf("Failed to sign CSR: %v", err) + } + + // Verify the signed certificate + certBlock, _ := util.DecodePEMBytes(signedCertBytes) + if len(certBlock) == 0 { + t.Fatalf("Failed to decode signed certificate") + } + + cert, err := x509.ParseCertificate(certBlock[0].Bytes) + if err != nil { + t.Fatalf("Failed to parse signed certificate: %v", err) + } + + if cert.Subject.String() != commandConfig.commandCsrDn { + t.Error("Signed certificate subject does not match CSR subject") + } + }) + + // Create supported annotations + supportedAnnotations := map[string]string{ + "k8s-csr-signer.keyfactor.com/certificateTemplate": commandConfig.commandCertificateTemplate, + "k8s-csr-signer.keyfactor.com/certificateAuthorityHostname": commandConfig.commandCertificateAuthorityHostname, + "k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName": commandConfig.commandCertificateAuthorityLogicalName, + "k8s-csr-signer.keyfactor.com/chainDepth": "5", + } + + t.Run("BasicAuthWithAnnotations", func(t *testing.T) { + estCreds := corev1.Secret{ + Type: corev1.SecretTypeBasicAuth, + Data: map[string][]byte{ + "username": []byte(commandConfig.username), + "password": []byte(commandConfig.password), + }, + } + + // Clear out existing config for annotation override + signerConfig = corev1.ConfigMap{ + Data: map[string]string{ + "commandHostname": commandConfig.hostname, + }, + } + + // Build the signer + builder := &commandSigner{} + builder. + WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))). + WithCredsSecret(estCreds). + WithConfigMap(signerConfig). + WithCACertConfigMap(caConfig) + + err = builder.PreFlight() + if err != nil { + t.Fatalf("Failed to preflight signer: %v", err) + } + + signer := builder.Build() + + // Generate a CSR + csr, _, err := generateCSR(dn.String(), []string{dn.CommonName}, []string{}, []string{}) + if err != nil { + t.Fatalf("Failed to generate CSR: %v", err) + } + request := certificates.CertificateSigningRequest{ + Spec: certificates.CertificateSigningRequestSpec{ + Request: csr, + }, + } + + request.SetAnnotations(supportedAnnotations) + + signedCertBytes, err := signer.Sign(request) + if err != nil { + t.Fatalf("Failed to sign CSR: %v", err) + } + + // Verify the signed certificate + certBlock, _ := util.DecodePEMBytes(signedCertBytes) + if len(certBlock) == 0 { + t.Fatalf("Failed to decode signed certificate") + } + + cert, err := x509.ParseCertificate(certBlock[0].Bytes) + if err != nil { + t.Fatalf("Failed to parse signed certificate: %v", err) + } + + if cert.Subject.String() != commandConfig.commandCsrDn { + t.Error("Signed certificate subject does not match CSR subject") + } + }) +} + +type CommandTestConfig struct { + hostname string + username string + password string + + commandCertificateTemplate string + commandCertificateAuthorityLogicalName string + commandCertificateAuthorityHostname string + + caCertBytes []byte + + commandCsrDn string +} + +func (c *CommandTestConfig) Get(t *testing.T) error { + var errs []error + + // Paths + pathToCaCert := os.Getenv("COMMAND_CA_CERT_PATH") + + // Command Config + c.hostname = os.Getenv("COMMAND_HOSTNAME") + c.username = os.Getenv("COMMAND_USERNAME") + c.password = os.Getenv("COMMAND_PASSWORD") + + c.commandCertificateTemplate = os.Getenv("COMMAND_CERTIFICATE_TEMPLATE") + c.commandCertificateAuthorityLogicalName = os.Getenv("COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME") + c.commandCertificateAuthorityHostname = os.Getenv("COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME") + + // CSR Config + c.commandCsrDn = "CN=k8s-csr-signer-test.com" + + if pathToCaCert == "" { + err := errors.New("COMMAND_CA_CERT_PATH environment variable is not set") + t.Error(err) + errs = append(errs, err) + } + + if c.hostname == "" { + err := errors.New("COMMAND_HOSTNAME environment variable is not set") + t.Error(err) + errs = append(errs, err) + } + + if c.username == "" { + err := errors.New("COMMAND_USERNAME environment variable is not set") + t.Error(err) + errs = append(errs, err) + } + + if c.password == "" { + err := errors.New("COMMAND_PASSWORD environment variable is not set") + t.Error(err) + errs = append(errs, err) + } + + if c.commandCertificateTemplate == "" { + err := errors.New("COMMAND_CERTIFICATE_TEMPLATE environment variable is not set") + t.Error(err) + errs = append(errs, err) + } + + if c.commandCertificateAuthorityLogicalName == "" { + err := errors.New("COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME environment variable is not set") + t.Error(err) + errs = append(errs, err) + } + + if c.commandCertificateAuthorityHostname == "" { + err := errors.New("COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME environment variable is not set") + t.Error(err) + errs = append(errs, err) + } + + // Read the CA cert from the file system. + caCertBytes, err := os.ReadFile(pathToCaCert) + if err != nil { + t.Errorf("Failed to read CA cert from file system: %v", err) + errs = append(errs, err) + } + c.caCertBytes = caCertBytes + + return utilerrors.NewAggregate(errs) +} + +func generateSelfSignedCertificate() (*x509.Certificate, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "test"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, err + } + + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, err + } + + return cert, nil +} + +func generateCSR(subject string, dnsNames []string, uris []string, ipAddresses []string) ([]byte, *x509.CertificateRequest, error) { + keyBytes, _ := rsa.GenerateKey(rand.Reader, 2048) + + subj, err := parseSubjectDN(subject, false) + if err != nil { + return nil, nil, err + } + + template := x509.CertificateRequest{ + Subject: subj, + SignatureAlgorithm: x509.SHA256WithRSA, + } + + if len(dnsNames) > 0 { + template.DNSNames = dnsNames + } + + // Parse and add URIs + var uriPointers []*url.URL + for _, u := range uris { + if u == "" { + continue + } + uriPointer, err := url.Parse(u) + if err != nil { + return nil, nil, err + } + uriPointers = append(uriPointers, uriPointer) + } + template.URIs = uriPointers + + // Parse and add IPAddresses + var ipAddrs []net.IP + for _, ipStr := range ipAddresses { + if ipStr == "" { + continue + } + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, nil, fmt.Errorf("invalid IP address: %s", ipStr) + } + ipAddrs = append(ipAddrs, ip) + } + template.IPAddresses = ipAddrs + + // Generate the CSR + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, keyBytes) + if err != nil { + return nil, nil, err + } + + var csrBuf bytes.Buffer + err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) + if err != nil { + return nil, nil, err + } + + parsedCSR, err := x509.ParseCertificateRequest(csrBytes) + if err != nil { + return nil, nil, err + } + + return csrBuf.Bytes(), parsedCSR, nil +} + +// Function that turns subject string into pkix.Name +// EG "C=US,ST=California,L=San Francisco,O=HashiCorp,OU=Engineering,CN=example.com" +func parseSubjectDN(subject string, randomizeCn bool) (pkix.Name, error) { + var name pkix.Name + + if subject == "" { + return name, nil + } + + // Split the subject into its individual parts + parts := strings.Split(subject, ",") + + for _, part := range parts { + // Split the part into key and value + keyValue := strings.SplitN(part, "=", 2) + + if len(keyValue) != 2 { + return pkix.Name{}, asn1.SyntaxError{Msg: "malformed subject DN"} + } + + key := strings.TrimSpace(keyValue[0]) + value := strings.TrimSpace(keyValue[1]) + + // Map the key to the appropriate field in the pkix.Name struct + switch key { + case "C": + name.Country = []string{value} + case "ST": + name.Province = []string{value} + case "L": + name.Locality = []string{value} + case "O": + name.Organization = []string{value} + case "OU": + name.OrganizationalUnit = []string{value} + case "CN": + if randomizeCn { + name.CommonName = fmt.Sprintf("%s-%s", value, generateRandomString(5)) + } else { + name.CommonName = value + } + default: + // Ignore any unknown keys + } + } + + return name, nil +} + +func generateRandomString(length int) string { + mathrand.New(mathrand.NewSource(time.Now().UnixNano())) + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, length) + for i := range b { + b[i] = letters[mathrand.Intn(len(letters))] + } + return string(b) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..0f949f1 --- /dev/null +++ b/main.go @@ -0,0 +1,161 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package main + +import ( + "errors" + "flag" + "github.com/Keyfactor/k8s-csr-signer/internal/controllers" + "github.com/Keyfactor/k8s-csr-signer/internal/signer" + "github.com/Keyfactor/k8s-csr-signer/pkg/util" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/clock" + "os" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + var clusterResourceNamespace string + var printVersion bool + var disableApprovedCheck bool + + var credsSecretName, configMapName, caCertConfigmapName string + + flag.StringVar(&credsSecretName, "credential-secret-name", "", "The name of the secret containing the Command credentials") + flag.StringVar(&configMapName, "configmap-name", "", "The name of the configmap containing the signer configuration") + flag.StringVar(&caCertConfigmapName, "ca-cert-configmap-name", "", "The name of the configmap containing the root CAs of the Command API server") + + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.StringVar(&clusterResourceNamespace, "cluster-resource-namespace", "", "The namespace for secrets in which cluster-scoped resources are found.") + flag.BoolVar(&printVersion, "version", false, "Print version to stdout and exit") + flag.BoolVar(&disableApprovedCheck, "disable-approved-check", false, + "Disables waiting for CertificateRequests to have an approved condition before signing.") + + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + if clusterResourceNamespace == "" { + var err error + clusterResourceNamespace, err = util.GetInClusterNamespace() + if err != nil { + if errors.Is(err, errors.New("not running in-cluster")) { + setupLog.Error(err, "please supply --cluster-resource-namespace") + } else { + setupLog.Error(err, "unexpected error while getting in-cluster Namespace") + } + os.Exit(1) + } + } + + if credsSecretName == "" { + setupLog.Error(errors.New("please supply --credential-secret-name"), "") + os.Exit(1) + } + + if configMapName == "" { + setupLog.Error(errors.New("please supply --configmap-name"), "") + os.Exit(1) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "b68cef23.keyfactor.com", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + credsSecret := types.NamespacedName{ + Namespace: clusterResourceNamespace, + Name: credsSecretName, + } + configMap := types.NamespacedName{ + Namespace: clusterResourceNamespace, + Name: configMapName, + } + caCertConfigmap := types.NamespacedName{ + Namespace: clusterResourceNamespace, + Name: caCertConfigmapName, + } + + commandSignerBuilder := signer.NewCommandSignerBuilder() + + if err = (&controllers.CertificateSigningRequestReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + SignerBuilder: commandSignerBuilder, + ClusterResourceNamespace: clusterResourceNamespace, + Clock: clock.RealClock{}, + CheckApprovedCondition: !disableApprovedCheck, + CheckServiceAccountScope: true, + CredsSecret: credsSecret, + ConfigMap: configMap, + CaCertConfigmap: caCertConfigmap, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "CertificateSigningRequest") + os.Exit(1) + } + + if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity deleted file mode 100644 index 5e070b6..0000000 --- a/node_modules/.yarn-integrity +++ /dev/null @@ -1,10 +0,0 @@ -{ - "systemParams": "darwin-x64-72", - "modulesFolders": [], - "flags": [], - "linkedModules": [], - "topLevelPatterns": [], - "lockfileEntries": {}, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go deleted file mode 100644 index c9b6c3c..0000000 --- a/pkg/config/config.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package config - -import ( - "fmt" - - "github.com/Keyfactor/k8s-proxy/pkg/env" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "github.com/Keyfactor/k8s-proxy/pkg/util" - "github.com/spf13/viper" -) - -var ( - cLog = klogger.Register("ServerConfig") - ServiceNameENV = env.RegisterString("SERVICE_NAME", "k8s-proxy-service") - NamespaceENV = env.RegisterString("NAMESPACE", "keyfactor") - ClusterDomainENV = env.RegisterString("CLUSTER_DOMAIN", "cluster.local") - PodNameENV = env.RegisterString("POD_NAME", "k8s-proxy-pod") - PodIPENV = env.RegisterString("POD_IP", "k8s-proxy-pod") -) - -// ServerConfig contains all configuration of server -type ServerConfig struct { - // Port server listener port for GPRC Server and Webhook Server - GRPCPort string - HealthCheckPort string - Environment string - IstioNamespace string - IstioSecretName string - EnableAutoProvisioningIstioCert bool - Hostname string - ServiceName string - Namespace string - PodName string - ClusterDomain string - PodIP string - MetadataMapping map[string]string - DisableMTLS bool -} - -//LoadConfig load config from file -func LoadConfig(cfgFile string) *ServerConfig { - sc := DefaultConfig() - - if cfgFile != "" { - cLog.Infof("start load config from file: %s", cfgFile) - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - viper.AddConfigPath("./config") - viper.SetConfigName("config") - viper.SetConfigType("yaml") - cLog.Infof("config file is empty. Load config from default path: %v", viper.ConfigFileUsed()) - } - - viper.AutomaticEnv() - - if err := viper.ReadInConfig(); err != nil { - fmt.Println(fmt.Errorf("load keyfactor config from file (%s) got error:%v", viper.ConfigFileUsed(), err)) - } - - err := viper.Unmarshal(sc) - - if err != nil { - cLog.Errorf("cannot Unmarshal config from file: %v - %v", viper.ConfigFileUsed(), err) - } - - sc.ClusterDomain = ClusterDomainENV.Get() - sc.ServiceName = ServiceNameENV.Get() - sc.Namespace = NamespaceENV.Get() - sc.PodName = PodNameENV.Get() - sc.Hostname = util.GetHostName(ServiceNameENV.Get(), NamespaceENV.Get(), ClusterDomainENV.Get()) - sc.PodIP = PodIPENV.Get() - - cLog.Infof("load config successful: \n%#v\n", sc) - return sc -} - -// DefaultConfig get default of ServerConfig -func DefaultConfig() *ServerConfig { - return &ServerConfig{ - GRPCPort: "8090", - IstioNamespace: "istio-system", - EnableAutoProvisioningIstioCert: true, - } - -} diff --git a/pkg/env/env.go b/pkg/env/env.go deleted file mode 100644 index 8567db9..0000000 --- a/pkg/env/env.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package env - -import ( - "strings" - - "github.com/spf13/viper" -) - -// E struct contains environment value -type E struct { - name string - defaultValue string -} - -// Register new env -func RegisterString(name string, defaultValue string) *E { - viper.BindEnv(name) - return &E{ - name: name, - defaultValue: defaultValue, - } -} - -func (e *E) Get() string { - envValue := viper.GetString(e.name) - if envValue != "" { - return strings.TrimSpace(envValue) - } - return e.defaultValue -} diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go deleted file mode 100644 index 9d8afd3..0000000 --- a/pkg/k8s/client.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package k8s - -import ( - "fmt" - "os" - - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" -) - -var ( - k8sLogger = klogger.Register("K8S_Client") -) - -// NewInClusterClient create new kubernetes client -func NewInClusterClient() (*kubernetes.Clientset, error) { - conf, err := rest.InClusterConfig() - if err != nil { - return nil, fmt.Errorf("create config failed: %v", err) - } - k8sLogger.Infof("get kubernetes config in cluster: %v", conf) - - client, err := kubernetes.NewForConfig(conf) - if err != nil { - return nil, fmt.Errorf("create kubernetes client failed: %v", err) - } - return client, nil -} - -// NewTestClient create a Kubernetes Client out of Cluster -func NewTestClient(kubeconfig string) (*kubernetes.Clientset, error) { - k8sLogger.Infof("NewTestClient: load kubeconfig from: %v", kubeconfig) - // use the current context in kubeconfig - config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - k8sLogger.Errorf("load kubeconfig from: %v failed: %v", kubeconfig, err) - return nil, fmt.Errorf("load kubeconfig from: %v failed: %v", kubeconfig, err) - } - // create the clientset - client, err := kubernetes.NewForConfig(config) - if err != nil { - k8sLogger.Errorf("create kubernetes client failed: %v", err) - return nil, fmt.Errorf("create kubernetes client failed: %v", err) - } - return client, nil -} - -func homeDir() string { - if h := os.Getenv("HOME"); h != "" { - return h - } - return os.Getenv("USERPROFILE") // windows -} diff --git a/pkg/k8s/jwt.go b/pkg/k8s/jwt.go deleted file mode 100644 index 6fb48ef..0000000 --- a/pkg/k8s/jwt.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package k8s - -import ( - "context" - "fmt" - "strings" - - "github.com/Keyfactor/k8s-proxy/pkg/config" - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "google.golang.org/grpc/metadata" - k8sauth "k8s.io/api/authentication/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -const ( - bearerTokenPrefix = "Bearer " - authorizationMeta = "authorization" - clusterIDMeta = "clusterid" -) - -var ( - aLog = klogger.Register("Kubernetes Authenticate") -) - -// KubeJWTAuthenticator authenticates K8s JWTs. -type KubeJWTAuthenticator struct { - trustDomain string - - // Primary cluster kube client - kube *kubernetes.Clientset - serverConfig *config.ServerConfig -} - -// NewKubeJWTAuthenticator creates a new kubeJWTAuthenticator. -func NewKubeJWTAuthenticator(client *kubernetes.Clientset, serverConfig *config.ServerConfig) *KubeJWTAuthenticator { - return &KubeJWTAuthenticator{ - serverConfig: serverConfig, - kube: client, - } -} - -// Authenticate authenticates the call using the K8s JWT from the context. -// Return {, } in the targetToken when the validation passes. -// Otherwise, return the error. -// targetToken: the JWT of the K8s service account to be reviewed -// jwtPolicy: the policy for validating JWT. -func (a *KubeJWTAuthenticator) Authenticate(ctx context.Context) ([]string, error) { - targetJWT, err := extractBearerToken(ctx) - if err != nil { - return nil, fmt.Errorf("target JWT extraction error: %v", err) - } - aLog.Infof("received K8S JWT from request: %v", targetJWT) - tokenReview := &k8sauth.TokenReview{ - Spec: k8sauth.TokenReviewSpec{ - Token: targetJWT, - Audiences: []string{}, - }, - } - reviewRes, err := a.kube.AuthenticationV1().TokenReviews().Create(ctx, tokenReview, metav1.CreateOptions{}) - if err != nil { - aLog.Errorf("call kubernetes TokenReview failed: %v", err) - return nil, fmt.Errorf("call kubernetes TokenReview failed: %v", err) - } - aLog.Infof("Kubernetes token review response aud: %v", reviewRes.Spec.Audiences) - return getTokenReviewResult(reviewRes) -} - -func extractBearerToken(ctx context.Context) (string, error) { - md, ok := metadata.FromIncomingContext(ctx) - aLog.Infof("metadata.FromIncomingContext: %#v", md) - if !ok { - return "", fmt.Errorf("no metadata is attached") - } - - authHeader, exists := md[authorizationMeta] - if !exists { - return "", fmt.Errorf("no HTTP authorization header exists") - } - - for _, value := range authHeader { - if strings.HasPrefix(value, bearerTokenPrefix) { - return strings.TrimPrefix(value, bearerTokenPrefix), nil - } - } - - return "", fmt.Errorf("no bearer token exists in HTTP authorization header") -} - -func getTokenReviewResult(tokenReview *k8sauth.TokenReview) ([]string, error) { - if tokenReview.Status.Error != "" { - return nil, fmt.Errorf("the service account authentication returns an error: %v", - tokenReview.Status.Error) - } - // An example SA token: - // {"alg":"RS256","typ":"JWT"} - // {"iss":"kubernetes/serviceaccount", - // "kubernetes.io/serviceaccount/namespace":"default", - // "kubernetes.io/serviceaccount/secret.name":"example-pod-sa-token-h4jqx", - // "kubernetes.io/serviceaccount/service-account.name":"example-pod-sa", - // "kubernetes.io/serviceaccount/service-account.uid":"ff578a9e-65d3-11e8-aad2-42010a8a001d", - // "sub":"system:serviceaccount:default:example-pod-sa" - // } - - // An example token review status - // "status":{ - // "authenticated":true, - // "user":{ - // "username":"system:serviceaccount:default:example-pod-sa", - // "uid":"ff578a9e-65d3-11e8-aad2-42010a8a001d", - // "groups":["system:serviceaccounts","system:serviceaccounts:default","system:authenticated"] - // } - // } - - if !tokenReview.Status.Authenticated { - return nil, fmt.Errorf("the token is not authenticated") - } - - inServiceAccountGroup := false - for _, group := range tokenReview.Status.User.Groups { - if group == "system:serviceaccounts" { - inServiceAccountGroup = true - break - } - } - - if !inServiceAccountGroup { - return nil, fmt.Errorf("the token is not a service account") - } - - // "username" is in the form of system:serviceaccount:{namespace}:{service account name}", - // e.g., "username":"system:serviceaccount:default:example-pod-sa" - subStrings := strings.Split(tokenReview.Status.User.Username, ":") - if len(subStrings) != 4 { - return nil, fmt.Errorf("invalid username field in the token review result") - } - namespace := subStrings[2] - saName := subStrings[3] - return []string{namespace, saName}, nil -} diff --git a/pkg/keyfactor/client.go b/pkg/keyfactor/client.go deleted file mode 100644 index 26d19ae..0000000 --- a/pkg/keyfactor/client.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package keyfactor - -import ( - "bytes" - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "net/http" - "net/url" - "path" - "time" - - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" -) - -var ( - clientLog = klogger.Register("Client") -) - -// SigningClientInterface interface client of keyfactor -type SigningClientInterface interface { - CSRSign(ctx context.Context, csrPEM string, metadata *CSRMetadata, isServerTLS bool) (*EnrollResponse, error) -} - -// CaClient struct to define http client for KeyfactorCA -type CaClient struct { - Client *http.Client - credentials *ClientCredential - metadataConfiguration map[string]string -} - -type keyfactorRequestPayload struct { - CSR string `json:"CSR"` - CertificateAuthority string `json:"CertificateAuthority"` - IncludeChain bool `json:"IncludeChain"` - TimeStamp string `json:"TimeStamp"` - Template string `json:"Template"` - Metadata map[string]string `json:"Metadata"` -} - -// EnrollResponse response structure for keyfactor server -type EnrollResponse struct { - CertificateInformation struct { - SerialNumber string `json:"SerialNumber"` - IssuerDN string `json:"IssuerDN"` - Thumbprint string `json:"Thumbprint"` - KeyfactorID int `json:"KeyfactorID"` - KeyfactorRequestID int `json:"KeyfactorRequestId"` - Certificates []string `json:"Certificates"` - RequestDisposition string `json:"RequestDisposition"` - DispositionMessage string `json:"DispositionMessage"` - EnrollmentContext interface{} `json:"EnrollmentContext"` - } `json:"CertificateInformation"` - Metadata struct { - ClusterID string `json:"ClusterID"` - Service string `json:"Service"` - PodNamespace string `json:"PodNamespace"` - PodName string `json:"PodName"` - PodIP string `json:"PodIP"` - TrustDomain string `json:"TrustDomain"` - TrustDomainData string `json:"TrustDomainData"` - Cluster string `json:"Cluster"` - KMSTestMetadata string `json:"KMSTestMetadata"` - } `json:"Metadata"` -} - -// New create a CA client for KeyFactor CA. -func New(credentials *ClientCredential, metadataConfiguration map[string]string) (SigningClientInterface, error) { - c := &CaClient{ - credentials: credentials, - metadataConfiguration: metadataConfiguration, - } - - err := c.createTLSClient() - if err != nil { - return nil, fmt.Errorf("cannot create TLS client for keyfactor: %v", err) - } - - return c, nil -} - -func (cl *CaClient) createTLSClient() error { - // Load the system default root certificates. - pool, err := x509.SystemCertPool() - if err != nil { - clientLog.Errorf("could not get SystemCertPool: %v", err) - return fmt.Errorf("could not get SystemCertPool: %v", err) - } - - if pool == nil { - clientLog.Info("System cert pool is nil, create a new cert pool") - pool = x509.NewCertPool() - } - - tlsConfig := &tls.Config{ - RootCAs: pool, - } - transport := &http.Transport{TLSClientConfig: tlsConfig} - cl.Client = &http.Client{ - Transport: transport, - Timeout: 30 * time.Second, - } - return nil -} - -// CSRSign calls KeyFactor CA to sign a CSR. -func (cl *CaClient) CSRSign(ctx context.Context, csrPEM string, - metadata *CSRMetadata, isServerTLS bool) (*EnrollResponse, error) { - - payload := &keyfactorRequestPayload{ - CSR: csrPEM, - CertificateAuthority: cl.credentials.CaName, - IncludeChain: true, - Template: cl.credentials.CaTemplate, - TimeStamp: time.Now().UTC().Format(time.RFC3339), - Metadata: metadata.MappingCustomMetadata(cl.metadataConfiguration), - } - - if isServerTLS { - payload.Template = cl.credentials.ProvisioningTemplate - } - - bytesRepresentation, err := json.Marshal(payload) - clientLog.Infof("payload body: %v", string(bytesRepresentation)) - if err != nil { - clientLog.Errorf("error encode json data: %v", err) - return nil, fmt.Errorf("error encode json data: %v", err) - } - - u, err := url.Parse(cl.credentials.Endpoint) - - if err != nil { - clientLog.Errorf("invalid caAddress: %v (%v)", cl.credentials.Endpoint, err) - return nil, fmt.Errorf("invalid caAddress: %v (%v)", cl.credentials.Endpoint, err) - } - - u.Path = path.Join(u.Path, cl.credentials.EnrollPath) - enrollCSRPath := u.String() - - clientLog.Infof("start sign Keyfactor CSR request to: %v", enrollCSRPath) - requestCSR, err := http.NewRequest("POST", enrollCSRPath, bytes.NewBuffer(bytesRepresentation)) - - if err != nil { - return nil, fmt.Errorf("cannot create request with url: %v", enrollCSRPath) - } - - requestCSR.Header.Set("authorization", cl.credentials.AuthToken) - requestCSR.Header.Set("x-keyfactor-requested-with", "APIClient") - - if isServerTLS { - requestCSR.Header.Set("x-Keyfactor-appKey", cl.credentials.ProvisioningAppKey) - } else { - requestCSR.Header.Set("x-Keyfactor-appKey", cl.credentials.AppKey) - } - requestCSR.Header.Set("x-certificateformat", "PEM") - requestCSR.Header.Set("Content-Type", "application/json") - - res, err := cl.Client.Do(requestCSR) - if err != nil { - clientLog.Errorf("could not request to KeyfactorCA server: %v %v", cl.credentials.Endpoint, err) - return nil, fmt.Errorf("could not request to KeyfactorCA server: %v %v", cl.credentials.Endpoint, err) - } - defer res.Body.Close() - status := res.StatusCode - - if status == http.StatusOK { - jsonResponse := &EnrollResponse{} - err := json.NewDecoder(res.Body).Decode(&jsonResponse) - - if err != nil { - clientLog.Errorf("could not decode response data from KeyfactorCA: %v", err) - return nil, fmt.Errorf("could not decode response data from KeyfactorCA: %v", err) - } - jsonResponse.CertificateInformation.Certificates = getCertFromResponse(jsonResponse) - return jsonResponse, nil - } - - var errorMessage interface{} - err = json.NewDecoder(res.Body).Decode(&errorMessage) - if err != nil { - clientLog.Errorf("cannot decode error message from keyfactorCA: %v", err) - } - clientLog.Errorf("request failed with status: %v, message: %v", status, errorMessage) - return nil, fmt.Errorf("request failed with status: %v, message: %v", status, errorMessage) -} - -func getCertFromResponse(jsonResponse *EnrollResponse) []string { - - template := "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n" - var certChains []string - for _, certStr := range jsonResponse.CertificateInformation.Certificates { - - block, _ := pem.Decode([]byte(fmt.Sprintf(template, certStr))) - certChains = append(certChains, string(pem.EncodeToMemory(block))) - } - - clientLog.Infof("keyfactor response %v certificates in certchain.", len(certChains)) - - return certChains -} - -func isotimestring(t time.Time) string { - var tz string - zName, zOffset := t.Zone() - if zName == "UTC" { - tz = "Z" - } else { - tz = fmt.Sprintf("%03d00", zOffset/3600) - } - return fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), tz) -} diff --git a/pkg/keyfactor/credential.go b/pkg/keyfactor/credential.go deleted file mode 100644 index 73f2a3b..0000000 --- a/pkg/keyfactor/credential.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package keyfactor - -import ( - "fmt" - - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "github.com/spf13/viper" -) - -var ( - creLog = klogger.Register("KeyfactorCredential") -) - -// ClientCredential config meta for KeyfactorCA client -type ClientCredential struct { - // Endpoint address of certificate authorization - Endpoint string - - // CaName Name of certificate authorization - CaName string - - // Using for authentication header - AuthToken string - - // CaTemplate Certificate Template for enroll the new one Default is Istio - CaTemplate string - - // AppKey ApiKey from Api Setting - AppKey string - - // EnrollPath api path to Enroll CSR Request - EnrollPath string - - ProvisioningAppKey string - ProvisioningTemplate string -} - -// LoadCredential create and response default config -func LoadCredential(creFilePath string) (*ClientCredential, error) { - - c := &ClientCredential{ - EnrollPath: "/KeyfactorAPI/Enrollment/CSR", - CaTemplate: "Istio", - ProvisioningTemplate: "K8SProxy", - } - - if creFilePath != "" { - creLog.Infof("start load credential from file: %s", creFilePath) - // Use config file from the flag. - viper.SetConfigFile(creFilePath) - } else { - viper.AddConfigPath("./credentials") - viper.SetConfigName("credential") - viper.SetConfigType("yaml") - creLog.Infof("keyfactor credential file is empty. Load credential from default path: %v", viper.ConfigFileUsed()) - } - - viper.AutomaticEnv() - - if err := viper.ReadInConfig(); err != nil { - creLog.Errorf("load keyfactor credential from file (%s) got error:%v", viper.ConfigFileUsed(), err) - return nil, fmt.Errorf("load keyfactor credential from file (%s) got error:%v", viper.ConfigFileUsed(), err) - } - - err := viper.Unmarshal(c) - - if err != nil { - creLog.Errorf("cannot Unmarshal credential from file: %v - %v", viper.ConfigFileUsed(), err) - return nil, fmt.Errorf("cannot Unmarshal credential from file: %v - %v", viper.ConfigFileUsed(), err) - } - - return c, nil -} diff --git a/pkg/keyfactor/metadata.go b/pkg/keyfactor/metadata.go deleted file mode 100644 index fd7f4b9..0000000 --- a/pkg/keyfactor/metadata.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package keyfactor - -import ( - "context" - "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "strings" - - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" - "github.com/Keyfactor/k8s-proxy/pkg/util" - "github.com/gogo/protobuf/jsonpb" - "github.com/gogo/protobuf/types" - pb "istio.io/api/security/v1alpha1" -) - -var ( - log = klogger.Register("Extract Metadata from CSR") -) - -// CSRMetadata Metadata extracted from Istio's CSR -type CSRMetadata struct { - ClusterID string - TrustDomain string - PodName string - PodNamespace string - PodIP string - ServiceName string -} - -// IstioMetadata the certificate singing metadata carry from xds node -type IstioMetadata struct { - WorkloadName string `json:"WorkloadName"` - ClusterID string `json:"ClusterID"` - WorkloadIPs []string `json:"WorkloadIPs"` -} - -// MappingCustomMetadata create dynnamic metadata based on configure -func (metadata *CSRMetadata) MappingCustomMetadata(metadataConfig map[string]string) map[string]string { - if metadata.ServiceName == "" && metadata.PodName != "" { - podSplitted := strings.Split(metadata.PodName, "-") - metadata.ServiceName = strings.Join(podSplitted[:len(podSplitted)-2], "-") - } - metadataPayload := make(map[string]string) - for key, fieldName := range metadataConfig { - switch key { - case "clusterid": - metadataPayload[fieldName] = metadata.ClusterID - case "service": - metadataPayload[fieldName] = metadata.ServiceName - case "podname": - metadataPayload[fieldName] = metadata.PodName - case "podip": - metadataPayload[fieldName] = metadata.PodIP - case "podnamespace": - metadataPayload[fieldName] = metadata.PodNamespace - case "trustdomain": - metadataPayload[fieldName] = metadata.TrustDomain - } - } - return metadataPayload -} - -// ParseMetadataProtoStruct return Metadata struct from proto message -func ParseMetadataProtoStruct(proto *types.Struct) (*IstioMetadata, error) { - m := &IstioMetadata{} - marshaler := &jsonpb.Marshaler{ - EmitDefaults: true, - } - str, err := marshaler.MarshalToString(proto) - if err != nil { - log.Errorf("parse proto Struct to Json failed: %v", err) - return nil, fmt.Errorf("parse proto Struct to Json failed: %v", err) - } - err = json.Unmarshal([]byte(str), m) - if err != nil { - log.Errorf("unmarshal json to Metadata Struct failed: %v", err) - return nil, fmt.Errorf("unmarshal json to Metadata Struct failed: %v", err) - } - return m, nil -} - -// ExtractMetadataFromCSR extract metadata from request CSR context and proto message -func ExtractMetadataFromCSR(ctx context.Context, request *pb.IstioCertificateRequest) (*CSRMetadata, error) { - meta := &CSRMetadata{} - - istioMetadata, err := ParseMetadataProtoStruct(request.Metadata) - if err == nil { - meta.ClusterID = istioMetadata.ClusterID - meta.PodName = istioMetadata.WorkloadName - if len(istioMetadata.WorkloadIPs) > 0 { - meta.PodIP = istioMetadata.WorkloadIPs[0] - } - log.Infof("Retrived PodName, ClusterID, PodIP from CSR request: %#v", meta) - } else { - log.Warningf("cannot parse istio metadata from proto.struct: %v", err) - } - - pemBlock, _ := pem.Decode([]byte(request.GetCsr())) - if pemBlock == nil { - return nil, fmt.Errorf("csr request is invalid: %v", request.GetCsr()) - } - - cert, err := x509.ParseCertificateRequest(pemBlock.Bytes) - if err != nil { - return nil, fmt.Errorf("cannot parse CSR from request: %v", err) - } - meta = parseFromCertificateRequestExt(meta, cert) - return meta, nil -} - -func parseFromCertificateRequestExt(meta *CSRMetadata, cert *x509.CertificateRequest) *CSRMetadata { - ids, err := util.ExtractIDs(cert.Extensions) - - if err == nil && len(ids) != 0 { - if spiffeData, err := util.ExtractSPIFFE(ids[0]); err == nil { - meta.TrustDomain = spiffeData.TrustDomain - meta.PodNamespace = spiffeData.Namespace - meta.ServiceName = strings.Replace(spiffeData.ServiceAccount, "-service-account", "", 1) - log.Infof("Retrive TrustDomain, PodNamespace, ServiceName from SPIFFE URI: %#v", meta) - return meta - } - } - log.Warningf("cannot extract SAN URI from request: %v -\n %+v", err, cert) - log.Warningf("CSR raw: \n%#v", cert) - return meta -} diff --git a/pkg/keyfactor/mock/client.go b/pkg/keyfactor/mock/client.go deleted file mode 100644 index fa285f6..0000000 --- a/pkg/keyfactor/mock/client.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package mock - -import ( - "context" - - "github.com/Keyfactor/k8s-proxy/pkg/keyfactor" - "github.com/stretchr/testify/mock" -) - -// KeyfactorClientMock is a mocked object that implements an interface keyfactor Client -type KeyfactorClientMock struct { - mock.Mock -} - -// NewKeyfactorClientMock create mocking keyfactor client -func NewKeyfactorClientMock() *KeyfactorClientMock { - return new(KeyfactorClientMock) -} - -// CSRSign is a method on KeyfactorClientMock that implements some interface -// and just records the activity, and returns what the Mock object tells it to. -func (m *KeyfactorClientMock) CSRSign(ctx context.Context, csrPEM string, metadata *keyfactor.CSRMetadata, isServerTLS bool) (*keyfactor.EnrollResponse, error) { - m.Called(csrPEM, metadata) - return nil, nil -} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go deleted file mode 100644 index c51a954..0000000 --- a/pkg/logger/logger.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package klogger - -import ( - "github.com/sirupsen/logrus" -) - -// Register create keyfactor logger -func Register(name string) *logrus.Entry { - log := logrus.New() - log.SetFormatter(&logrus.TextFormatter{ - FullTimestamp: true, - ForceColors: true, - }) - return log.WithField("scope", name) -} diff --git a/pkg/util/generate_csr.go b/pkg/util/generate_csr.go deleted file mode 100644 index af13cf4..0000000 --- a/pkg/util/generate_csr.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package util - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "io/ioutil" - "strings" - - klogger "github.com/Keyfactor/k8s-proxy/pkg/logger" -) - -var ( - csrLog = klogger.Register("CSR Gen") -) - -// CertOptions contains options for generating a new certificate. -type CertOptions struct { - // Comma-separated hostnames and IPs to generate a certificate for. - // This can also be set to the identity running the workload, - // like kubernetes service account. - Host string - - // Organization for this certificate. - Org string -} - -const keySizeOfRSA = 2048 - -// GenCSR generates a X.509 certificate sign request and private key with the given options. -func GenCSR(options CertOptions) ([]byte, []byte, error) { - - priv, err := rsa.GenerateKey(rand.Reader, keySizeOfRSA) - if err != nil { - return nil, nil, fmt.Errorf("RSA key generation failed (%v)", err) - } - template, err := GenCSRTemplate(options) - if err != nil { - return nil, nil, fmt.Errorf("CSR template creation failed (%v)", err) - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, template, crypto.PrivateKey(priv)) - if err != nil { - return nil, nil, fmt.Errorf("CSR creation failed (%v)", err) - } - - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - encodedKey := x509.MarshalPKCS1PrivateKey(priv) - privPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: encodedKey}) - return csr, privPem, err -} - -// GenCSRTemplate generates a certificateRequest template with the given options. -func GenCSRTemplate(options CertOptions) (*x509.CertificateRequest, error) { - template := &x509.CertificateRequest{ - Subject: pkix.Name{ - Organization: []string{options.Org}, - }, - } - - if h := options.Host; len(h) > 0 { - s, err := BuildSubjectAltNameExtension(h) - if err != nil { - return nil, err - } - template.ExtraExtensions = []pkix.Extension{*s} - } - - return template, nil -} - -// AppendRootCerts appends root certificates in RootCertFile to the input certificate. -func AppendRootCerts(pemCert []byte, rootCertFile string) ([]byte, error) { - var rootCerts []byte - if len(pemCert) > 0 { - // Copy the input certificate - rootCerts = make([]byte, len(pemCert)) - copy(rootCerts, pemCert) - } - if len(rootCertFile) > 0 { - csrLog.Infof("append root certificates from %v", rootCertFile) - certBytes, err := ioutil.ReadFile(rootCertFile) - if err != nil { - return rootCerts, fmt.Errorf("failed to read root certificates (%v)", err) - } - csrLog.Infof("The root certificates to be appended is: %v", rootCertFile) - if len(rootCerts) > 0 { - // Append a newline after the last cert - rootCerts = []byte(strings.TrimSuffix(string(rootCerts), "\n") + "\n") - } - rootCerts = append(rootCerts, certBytes...) - } - return rootCerts, nil -} diff --git a/pkg/util/k8s.go b/pkg/util/k8s.go deleted file mode 100644 index 6ac79c7..0000000 --- a/pkg/util/k8s.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package util - -import "fmt" - -func GetHostName(serviceName, namespace, clusterDomain string) string { - return fmt.Sprintf("%s.%s.svc.%s", serviceName, namespace, clusterDomain) -} diff --git a/pkg/util/san.go b/pkg/util/san.go deleted file mode 100644 index 5588cc3..0000000 --- a/pkg/util/san.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package util - -import ( - "crypto/x509/pkix" - "encoding/asn1" - "fmt" - "net" - "strings" - - "istio.io/istio/pkg/spiffe" -) - -// IdentityType represents type of an identity. This is used to properly encode -// an identity into a SAN extension. -type IdentityType int - -const ( - // TypeDNS represents a DNS name. - TypeDNS IdentityType = iota - // TypeIP represents an IP address. - TypeIP - // TypeURI represents a universal resource identifier. - TypeURI -) - -var ( - // Mapping from the type of an identity to the OID tag value for the X.509 - // SAN field (see https://tools.ietf.org/html/rfc5280#appendix-A.2) - // - // SubjectAltName ::= GeneralNames - // - // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName - // - // GeneralName ::= CHOICE { - // dNSName [2] IA5String, - // uniformResourceIdentifier [6] IA5String, - // iPAddress [7] OCTET STRING, - // } - oidTagMap = map[IdentityType]int{ - TypeDNS: 2, - TypeURI: 6, - TypeIP: 7, - } - - // A reversed map that maps from an OID tag to the corresponding identity - // type. - identityTypeMap = generateReversedMap(oidTagMap) - - // The OID for the SAN extension (See - // http://www.alvestrand.no/objectid/2.5.29.17.html). - oidSubjectAlternativeName = asn1.ObjectIdentifier{2, 5, 29, 17} -) - -// Identity is an object holding both the encoded identifier bytes as well as -// the type of the identity. -type Identity struct { - Type IdentityType - Value []byte -} - -// BuildSubjectAltNameExtension builds the SAN extension for the certificate. -func BuildSubjectAltNameExtension(hosts ...string) (*pkix.Extension, error) { - ids := []Identity{} - for _, host := range hosts { - if ip := net.ParseIP(host); ip != nil { - // Use the 4-byte representation of the IP address when possible. - if eip := ip.To4(); eip != nil { - ip = eip - } - ids = append(ids, Identity{Type: TypeIP, Value: ip}) - } else if strings.HasPrefix(host, spiffe.URIPrefix) { - ids = append(ids, Identity{Type: TypeURI, Value: []byte(host)}) - } else { - ids = append(ids, Identity{Type: TypeDNS, Value: []byte(host)}) - } - } - - san, err := BuildSANExtension(ids) - if err != nil { - return nil, fmt.Errorf("SAN extension building failure (%v)", err) - } - - return san, nil -} - -// BuildSANExtension builds a `pkix.Extension` of type "Subject -// Alternative Name" based on the given identities. -func BuildSANExtension(identites []Identity) (*pkix.Extension, error) { - rawValues := []asn1.RawValue{} - for _, i := range identites { - tag, ok := oidTagMap[i.Type] - if !ok { - return nil, fmt.Errorf("unsupported identity type: %v", i.Type) - } - - rawValues = append(rawValues, asn1.RawValue{ - Bytes: i.Value, - Class: asn1.ClassContextSpecific, - Tag: tag, - }) - } - - bs, err := asn1.Marshal(rawValues) - if err != nil { - return nil, fmt.Errorf("failed to marshal the raw values for SAN field (err: %s)", err) - } - - // SAN is Critical because the subject is empty. This is compliant with X.509 and SPIFFE standards. - return &pkix.Extension{Id: oidSubjectAlternativeName, Critical: true, Value: bs}, nil -} - -// ExtractIDsFromSAN takes a SAN extension and extracts the identities. -// The logic is mostly borrowed from -// https://github.com/golang/go/blob/master/src/crypto/x509/x509.go, with the -// addition of supporting extracting URIs. -func ExtractIDsFromSAN(sanExt *pkix.Extension) ([]Identity, error) { - if !sanExt.Id.Equal(oidSubjectAlternativeName) { - return nil, fmt.Errorf("the input is not a SAN extension") - } - - var sequence asn1.RawValue - if rest, err := asn1.Unmarshal(sanExt.Value, &sequence); err != nil { - return nil, err - } else if len(rest) != 0 { - return nil, fmt.Errorf("the SAN extension is incorrectly encoded") - } - - // Check the rawValue is a sequence. - if !sequence.IsCompound || sequence.Tag != asn1.TagSequence || sequence.Class != asn1.ClassUniversal { - return nil, fmt.Errorf("the SAN extension is incorrectly encoded") - } - - ids := []Identity{} - for bytes := sequence.Bytes; len(bytes) > 0; { - var rawValue asn1.RawValue - var err error - - bytes, err = asn1.Unmarshal(bytes, &rawValue) - if err != nil { - return nil, err - } - ids = append(ids, Identity{Type: identityTypeMap[rawValue.Tag], Value: rawValue.Bytes}) - } - - return ids, nil -} - -// ExtractSANExtension extracts the "Subject Alternative Name" externsion from -// the given PKIX extension set. -func ExtractSANExtension(exts []pkix.Extension) *pkix.Extension { - for _, ext := range exts { - if ext.Id.Equal(oidSubjectAlternativeName) { - // We don't need to examine other extensions anymore since a certificate - // must not include more than one instance of a particular extension. See - // https://tools.ietf.org/html/rfc5280#section-4.2. - return &ext - } - } - return nil -} - -// ExtractIDs first finds the SAN extension from the given extension set, then -// extract identities from the SAN extension. -func ExtractIDs(exts []pkix.Extension) ([]string, error) { - sanExt := ExtractSANExtension(exts) - if sanExt == nil { - return nil, fmt.Errorf("the SAN extension does not exist") - } - - idsWithType, err := ExtractIDsFromSAN(sanExt) - if err != nil { - return nil, fmt.Errorf("failed to extract identities from SAN extension (error %v)", err) - } - - ids := []string{} - for _, id := range idsWithType { - ids = append(ids, string(id.Value)) - } - - return ids, nil -} - -func generateReversedMap(m map[IdentityType]int) map[int]IdentityType { - reversed := make(map[int]IdentityType) - for key, value := range m { - reversed[value] = key - } - return reversed -} diff --git a/pkg/util/spiffe.go b/pkg/util/spiffe.go deleted file mode 100644 index 29ccd1c..0000000 --- a/pkg/util/spiffe.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 Keyfactor -// -// 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. - -package util - -import ( - "fmt" - "net/url" - "strings" -) - -type SpiffeStruct struct { - TrustDomain string - ServiceAccount string - Namespace string -} - -func ExtractSPIFFE(peerID string) (*SpiffeStruct, error) { - - s := &SpiffeStruct{} - - u, err := url.Parse(peerID) - if err != nil { - return nil, fmt.Errorf("cannot extract SPIFFE ID: %v", err) - } - - s.TrustDomain = u.Hostname() - splitedPath := strings.Split(u.Path, "/") - s.Namespace = splitedPath[2] - s.ServiceAccount = splitedPath[4] - return s, nil -} diff --git a/pkg/util/util.go b/pkg/util/util.go new file mode 100644 index 0000000..2490441 --- /dev/null +++ b/pkg/util/util.go @@ -0,0 +1,102 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package util + +import ( + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + certificates "k8s.io/api/certificates/v1" + "os" + "strings" +) + +const inClusterNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + +// GetInClusterNamespace Copied from controller-runtime/pkg/leaderelection +func GetInClusterNamespace() (string, error) { + // Check whether the namespace file exists. + // If not, we are not running in cluster so can't guess the namespace. + _, err := os.Stat(inClusterNamespacePath) + if os.IsNotExist(err) { + return "", errors.New("not running in-cluster") + } else if err != nil { + return "", fmt.Errorf("error checking namespace file: %w", err) + } + + // Load the namespace file and return its content + namespace, err := os.ReadFile(inClusterNamespacePath) + if err != nil { + return "", fmt.Errorf("error reading namespace file: %w", err) + } + return string(namespace), nil +} + +// IsCertificateRequestApproved returns true if a certificate request has the +// "Approved" condition and no "Denied" conditions; false otherwise. +func IsCertificateRequestApproved(csr certificates.CertificateSigningRequest) bool { + approved, denied := getCertApprovalCondition(csr.Status) + return approved && !denied +} + +func getCertApprovalCondition(status certificates.CertificateSigningRequestStatus) (approved bool, denied bool) { + for _, c := range status.Conditions { + if c.Type == certificates.CertificateApproved { + approved = true + } + if c.Type == certificates.CertificateDenied { + denied = true + } + } + return +} + +// CompileCertificatesToPemBytes takes a slice of x509 certificates and returns a string containing the certificates in PEM format +// If an error occurred, the function logs the error and continues to parse the remaining objects. +func CompileCertificatesToPemBytes(certificates []*x509.Certificate) ([]byte, error) { + var leafAndChain strings.Builder + + for _, certificate := range certificates { + err := pem.Encode(&leafAndChain, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certificate.Raw, + }) + if err != nil { + return make([]byte, 0), err + } + } + + return []byte(leafAndChain.String()), nil +} + +func DecodePEMBytes(buf []byte) ([]*pem.Block, *pem.Block) { + var privKey *pem.Block + var certs []*pem.Block + var block *pem.Block + for { + block, buf = pem.Decode(buf) + if block == nil { + break + } else if strings.Contains(block.Type, "PRIVATE KEY") { + privKey = block + } else { + certs = append(certs, block) + } + } + return certs, privKey +} diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go new file mode 100644 index 0000000..0cb9309 --- /dev/null +++ b/pkg/util/util_test.go @@ -0,0 +1,82 @@ +package util + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "testing" + "time" +) + +func TestCompileCertificatesToPemBytes(t *testing.T) { + // Generate two certificates for testing + cert1, err := generateSelfSignedCertificate() + if err != nil { + t.Fatalf("failed to generate mock certificate: %v", err) + } + cert2, err := generateSelfSignedCertificate() + if err != nil { + t.Fatalf("failed to generate mock certificate: %v", err) + } + + tests := []struct { + name string + certificates []*x509.Certificate + expectedError bool + }{ + { + name: "No certificates", + certificates: []*x509.Certificate{}, + expectedError: false, + }, + { + name: "Single certificate", + certificates: []*x509.Certificate{cert1}, + expectedError: false, + }, + { + name: "Multiple certificates", + certificates: []*x509.Certificate{cert1, cert2}, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err = CompileCertificatesToPemBytes(tt.certificates) + if (err != nil) != tt.expectedError { + t.Errorf("expected error = %v, got %v", tt.expectedError, err) + } + }) + } +} + +func generateSelfSignedCertificate() (*x509.Certificate, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "test"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, err + } + + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, err + } + + return cert, nil +} diff --git a/sample/sample.yaml b/sample/sample.yaml new file mode 100644 index 0000000..93dcbfd --- /dev/null +++ b/sample/sample.yaml @@ -0,0 +1,16 @@ +apiVersion: certificates.k8s.io/v1 +kind: CertificateSigningRequest +metadata: + name: commandCsrTest + annotations: + k8s-csr-signer.keyfactor.com/certificateTemplate: 2YearTestWebServer + k8s-csr-signer.keyfactor.com/certificateAuthorityHostname: DC-CA.Command.local + k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: 2YearTestWebServer + + k8s-csr-signer.keyfactor.com/chainDepth: "0" +spec: + request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ3VEQ0NBYUFDQVFBd0FEQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU0waApZOTdtam1BT09ic0ZhSUJKMktSdFBFMGxkN3YwT1JIa3dnVCtodFVkd1ZyYzdiZm9zVnJ3UGdGV05oZVl2cTJsCktHdTU2Vk1tWXQ1aXFWOGhFck9rbXhpM1BSK2srSFhqdVJyakpJdnMvYWg2Qm5DZ0ExTmE2U1pRT2hGazUxa0kKaVpuOTQwWFRoaTJ5Q1dLaWlkUEJ0dlNabFE2VDlQdXhOSTJjekVVNDF0QVRUUXZOV0JMMzRXeWJENkpsbHVtWApVb0Foa0x4cElvaU45Ykk4NEgyVWg0bU14ZzUzZEVZMVZmV2EvVWk3czhGUzl1bHNkZmM3MFhvWTJFMHFsbW83ClV6c2NxbklBd0czblNhb3U1anFzbzVoZ2REZFo1NXZhOUdLS0RWUzFGSFJWaUZTMlNWUmRPMTNnWTF1VWc5Y2MKakpwUzc3S2hUbXBHelFjNHRxRUNBd0VBQWFCek1IRUdDU3FHU0liM0RRRUpEakZrTUdJd1lBWURWUjBSQkZrdwpWNElYYVhOMGFXOWtMbWx6ZEdsdkxYTjVjM1JsYlM1emRtT0NIbWx6ZEdsdlpDMXlaVzF2ZEdVdWFYTjBhVzh0CmMzbHpkR1Z0TG5OMlk0SWNhWE4wYVc4dGNHbHNiM1F1YVhOMGFXOHRjM2x6ZEdWdExuTjJZekFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQVNvZGZ3dDdJd2FQNVhRVkpSYXF3QUdTTTlwUDZHMnNsMDlqTUtibHhBWTZlSVJ0MAp0OXJ5dkVQOCttYmJFSkJucEZSc3dmL2NIS3pNVDBlNHc2TDNuL3pOYlRTWDdGUWZWNEdqdjIzM3NQS0tvZjloCk5HWXhpOWFHZmR3eitRbFhTS0RUNzJaaE5xUElFdStNNjlXUVRXclhUWjhpeWVmT2JnbzFpdlhxclkxT0UxYTUKWUNJMWVYL0UwRGtMWlkvSVNweUtiUnVTZTVmNUZCZmRuVjNWamM5dGluWmdOTVNLWm1LZHpnS0Z3NVdLY3NPWgpZT2VYS3hueUlxcUhNQS9odWIvYnRrV2ovSExGWlhWb0xCbzIxWnFCKzBQbTRZUFkzMWM2UmoySmtCUjVqWm9qCnMxWm5aNkFoN1Q5V1ZUVE9HVy9YVjhtRStkUS94ZGdxdzgrZnhnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg== + usages: + - client auth + - server auth + signerName: "keyfactor.com/kubernetes-integration" \ No newline at end of file diff --git a/sample/test-csr.yaml b/sample/test-csr.yaml deleted file mode 100644 index 7a75273..0000000 --- a/sample/test-csr.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: certificates.k8s.io/v1beta1 -kind: CertificateSigningRequest -metadata: - name: TestABCDEFNAME - annotations: - ServiceName: "ServiceName1234" - ClusterID: "ClusterID123" - TrustDomain: "cluster.local" - PodName: "ABC/XYZ" - PodNamespace: "Namespace" - PodIP: "192.168.3.2" -spec: - request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZqQ0NBVDRDQVFBd0VURVBNQTBHQTFVRUF3d0dZVzVuWld4aE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRgpBQU9DQVE4QU1JSUJDZ0tDQVFFQTByczhJTHRHdTYxakx2dHhWTTJSVlRWMDNHWlJTWWw0dWluVWo4RElaWjBOCnR2MUZtRVFSd3VoaUZsOFEzcWl0Qm0wMUFSMkNJVXBGd2ZzSjZ4MXF3ckJzVkhZbGlBNVhwRVpZM3ExcGswSDQKM3Z3aGJlK1o2MVNrVHF5SVBYUUwrTWM5T1Nsbm0xb0R2N0NtSkZNMUlMRVI3QTVGZnZKOEdFRjJ6dHBoaUlFMwpub1dtdHNZb3JuT2wzc2lHQ2ZGZzR4Zmd4eW8ybmlneFNVekl1bXNnVm9PM2ttT0x1RVF6cXpkakJ3TFJXbWlECklmMXBMWnoyalVnald4UkhCM1gyWnVVV1d1T09PZnpXM01LaE8ybHEvZi9DdS8wYk83c0x0MCt3U2ZMSU91TFcKcW90blZtRmxMMytqTy82WDNDKzBERHk5aUtwbXJjVDBnWGZLemE1dHJRSURBUUFCb0FBd0RRWUpLb1pJaHZjTgpBUUVMQlFBRGdnRUJBR05WdmVIOGR4ZzNvK21VeVRkbmFjVmQ1N24zSkExdnZEU1JWREkyQTZ1eXN3ZFp1L1BVCkkwZXpZWFV0RVNnSk1IRmQycVVNMjNuNVJsSXJ3R0xuUXFISUh5VStWWHhsdnZsRnpNOVpEWllSTmU3QlJvYXgKQVlEdUI5STZXT3FYbkFvczFqRmxNUG5NbFpqdU5kSGxpT1BjTU1oNndLaTZzZFhpVStHYTJ2RUVLY01jSVUyRgpvU2djUWdMYTk0aEpacGk3ZnNMdm1OQUxoT045UHdNMGM1dVJVejV4T0dGMUtCbWRSeEgvbUNOS2JKYjFRQm1HCkkwYitEUEdaTktXTU0xMzhIQXdoV0tkNjVoVHdYOWl4V3ZHMkh4TG1WQzg0L1BHT0tWQW9FNkpsYWFHdTlQVmkKdjlOSjVaZlZrcXdCd0hKbzZXdk9xVlA3SVFjZmg3d0drWm89Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo= - usages: - - client auth - - server auth - signerName: "keyfactor.com/kubernetes-integration" From 78e5e3dd3b59807d25dc83d1344e19ed66cf6046 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Mon, 4 Dec 2023 18:30:28 -0700 Subject: [PATCH 02/11] chore(helm): Update variable names to reflect command signer --- .../charts/k8s-csr-signer/templates/NOTES.txt | 4 ++-- .../k8s-csr-signer/templates/_helpers.tpl | 20 +++++++++---------- .../k8s-csr-signer/templates/clusterrole.yaml | 6 +++--- .../templates/clusterrolebinding.yaml | 8 ++++---- .../k8s-csr-signer/templates/deployment.yaml | 18 ++++++++--------- .../charts/k8s-csr-signer/templates/role.yaml | 4 ++-- .../k8s-csr-signer/templates/rolebinding.yaml | 8 ++++---- .../templates/serviceaccount.yaml | 4 ++-- .../templates/tests/test-connection.yaml | 6 +++--- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/deploy/charts/k8s-csr-signer/templates/NOTES.txt b/deploy/charts/k8s-csr-signer/templates/NOTES.txt index 290f42d..ae53e47 100644 --- a/deploy/charts/k8s-csr-signer/templates/NOTES.txt +++ b/deploy/charts/k8s-csr-signer/templates/NOTES.txt @@ -1,5 +1,5 @@ Create and approve a K8s CertificateSigningRequest with one of the following signerNames: -{{ range $i, $signerName := .Values.ejbca.signerNames }} - {{ $signerName }} +{{ range $i, $signerName := .Values.command.signerNames }} - {{ $signerName }} {{ end }} To see the controller logs, run: -kubectl logs -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "ejbca-csr-signer.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -f \ No newline at end of file +kubectl logs -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "command-csr-signer.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -f \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/_helpers.tpl b/deploy/charts/k8s-csr-signer/templates/_helpers.tpl index 3d396b2..f3267ac 100644 --- a/deploy/charts/k8s-csr-signer/templates/_helpers.tpl +++ b/deploy/charts/k8s-csr-signer/templates/_helpers.tpl @@ -1,7 +1,7 @@ {{/* Expand the name of the chart. */}} -{{- define "ejbca-csr-signer.name" -}} +{{- define "command-csr-signer.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} @@ -10,7 +10,7 @@ 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 "ejbca-csr-signer.fullname" -}} +{{- define "command-csr-signer.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -26,16 +26,16 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "ejbca-csr-signer.chart" -}} +{{- define "command-csr-signer.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} -{{- define "ejbca-csr-signer.labels" -}} -helm.sh/chart: {{ include "ejbca-csr-signer.chart" . }} -{{ include "ejbca-csr-signer.selectorLabels" . }} +{{- define "command-csr-signer.labels" -}} +helm.sh/chart: {{ include "command-csr-signer.chart" . }} +{{ include "command-csr-signer.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -45,17 +45,17 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Selector labels */}} -{{- define "ejbca-csr-signer.selectorLabels" -}} -app.kubernetes.io/name: {{ include "ejbca-csr-signer.name" . }} +{{- define "command-csr-signer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "command-csr-signer.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} -{{- define "ejbca-csr-signer.serviceAccountName" -}} +{{- define "command-csr-signer.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} -{{- default (include "ejbca-csr-signer.fullname" .) .Values.serviceAccount.name }} +{{- default (include "command-csr-signer.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} diff --git a/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml b/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml index 4f55c0b..51d945a 100644 --- a/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml +++ b/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml @@ -1,10 +1,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: {{ include "ejbca-csr-signer.name" . }}-controller-role + name: {{ include "command-csr-signer.name" . }}-controller-role namespace: {{ .Release.Namespace }} labels: - {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + {{- include "command-csr-signer.labels" . | nindent 4 }} rules: # The controller needs to be able to read secrets and configmaps to get authentication information # and to configure itself @@ -40,7 +40,7 @@ rules: - apiGroups: ["certificates.k8s.io"] resources: - "signers" - {{- with .Values.ejbca.signerNames }} + {{- with .Values.command.signerNames }} resourceNames: {{- toYaml . | nindent 6 }} {{- end }} diff --git a/deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml b/deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml index 0d2b375..0a245e2 100644 --- a/deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml +++ b/deploy/charts/k8s-csr-signer/templates/clusterrolebinding.yaml @@ -1,14 +1,14 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: {{ include "ejbca-csr-signer.name" . }}-controller-rolebinding + name: {{ include "command-csr-signer.name" . }}-controller-rolebinding labels: - {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + {{- include "command-csr-signer.labels" . | nindent 4 }} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: {{ include "ejbca-csr-signer.name" . }}-controller-role + name: {{ include "command-csr-signer.name" . }}-controller-role subjects: - kind: ServiceAccount - name: {{ include "ejbca-csr-signer.serviceAccountName" . }} + name: {{ include "command-csr-signer.serviceAccountName" . }} namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/deployment.yaml b/deploy/charts/k8s-csr-signer/templates/deployment.yaml index d141c0b..2210799 100644 --- a/deploy/charts/k8s-csr-signer/templates/deployment.yaml +++ b/deploy/charts/k8s-csr-signer/templates/deployment.yaml @@ -1,26 +1,26 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "ejbca-csr-signer.fullname" . }} + name: {{ include "command-csr-signer.fullname" . }} labels: - {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + {{- include "command-csr-signer.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: - {{- include "ejbca-csr-signer.selectorLabels" . | nindent 6 }} + {{- include "command-csr-signer.selectorLabels" . | nindent 6 }} template: metadata: labels: - {{- include "ejbca-csr-signer.selectorLabels" . | nindent 8 }} + {{- include "command-csr-signer.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} - serviceAccountName: {{ include "ejbca-csr-signer.serviceAccountName" . }} + serviceAccountName: {{ include "command-csr-signer.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: @@ -33,10 +33,10 @@ spec: - --health-probe-bind-address=:8081 - --metrics-bind-address=127.0.0.1:8080 - --leader-elect - - --credential-secret-name={{ required "credsSecretName is required" .Values.ejbca.credsSecretName }} - - --configmap-name={{ required "configMapName is required" .Values.ejbca.configMapName }} - {{- if .Values.ejbca.caCertConfigmapName }} - - --ca-cert-configmap-name={{ .Values.ejbca.caCertConfigmapName }} + - --credential-secret-name={{ required "credsSecretName is required" .Values.command.credsSecretName }} + - --configmap-name={{ required "configMapName is required" .Values.command.configMapName }} + {{- if .Values.command.caCertConfigmapName }} + - --ca-cert-configmap-name={{ .Values.command.caCertConfigmapName }} {{- end }} livenessProbe: httpGet: diff --git a/deploy/charts/k8s-csr-signer/templates/role.yaml b/deploy/charts/k8s-csr-signer/templates/role.yaml index 6e1dc08..8d65da9 100644 --- a/deploy/charts/k8s-csr-signer/templates/role.yaml +++ b/deploy/charts/k8s-csr-signer/templates/role.yaml @@ -2,8 +2,8 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: - {{- include "ejbca-csr-signer.labels" . | nindent 4 }} - name: {{ include "ejbca-csr-signer.name" . }}-leader-election-role + {{- include "command-csr-signer.labels" . | nindent 4 }} + name: {{ include "command-csr-signer.name" . }}-leader-election-role rules: - apiGroups: - coordination.k8s.io diff --git a/deploy/charts/k8s-csr-signer/templates/rolebinding.yaml b/deploy/charts/k8s-csr-signer/templates/rolebinding.yaml index 48372d4..1498f2d 100644 --- a/deploy/charts/k8s-csr-signer/templates/rolebinding.yaml +++ b/deploy/charts/k8s-csr-signer/templates/rolebinding.yaml @@ -2,13 +2,13 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: - {{- include "ejbca-csr-signer.labels" . | nindent 4 }} - name: {{ include "ejbca-csr-signer.name" . }}-leader-election-rolebinding + {{- include "command-csr-signer.labels" . | nindent 4 }} + name: {{ include "command-csr-signer.name" . }}-leader-election-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: {{ include "ejbca-csr-signer.name" . }}-leader-election-role + name: {{ include "command-csr-signer.name" . }}-leader-election-role subjects: - kind: ServiceAccount - name: {{ include "ejbca-csr-signer.serviceAccountName" . }} + name: {{ include "command-csr-signer.serviceAccountName" . }} namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/serviceaccount.yaml b/deploy/charts/k8s-csr-signer/templates/serviceaccount.yaml index f48ec69..40fe5d0 100644 --- a/deploy/charts/k8s-csr-signer/templates/serviceaccount.yaml +++ b/deploy/charts/k8s-csr-signer/templates/serviceaccount.yaml @@ -2,9 +2,9 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: {{ include "ejbca-csr-signer.serviceAccountName" . }} + name: {{ include "command-csr-signer.serviceAccountName" . }} labels: - {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + {{- include "command-csr-signer.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} annotations: {{- toYaml . | nindent 4 }} diff --git a/deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml b/deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml index 35fa4b5..c7f7a0a 100644 --- a/deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml +++ b/deploy/charts/k8s-csr-signer/templates/tests/test-connection.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "ejbca-csr-signer.fullname" . }}-test-connection" + name: "{{ include "command-csr-signer.fullname" . }}-test-connection" labels: - {{- include "ejbca-csr-signer.labels" . | nindent 4 }} + {{- include "command-csr-signer.labels" . | nindent 4 }} annotations: "helm.sh/hook": test spec: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "ejbca-csr-signer.fullname" . }}:{{ .Values.ejbca.port }}'] + args: ['{{ include "command-csr-signer.fullname" . }}:{{ .Values.command.port }}'] restartPolicy: Never From 4bf97c5477c82d0db2cc5f639656daca656e8c44 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Mon, 4 Dec 2023 21:17:51 -0700 Subject: [PATCH 03/11] chore(signer): Refactor docs, testing --- .github/workflows/helm_release.yml | 93 +++++++++++ .github/workflows/release.yml | 158 ++++++++++++++++++ .github/workflows/test.yml | 71 ++++++++ CHANGELOG.md | 22 +++ .../k8s-csr-signer/templates/deployment.yaml | 2 - docs/getting-started.markdown | 2 + docs/istio-deployment.markdown | 4 +- internal/signer/signer.go | 10 -- sample/sample.yaml | 4 +- 9 files changed, 350 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/helm_release.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 CHANGELOG.md diff --git a/.github/workflows/helm_release.yml b/.github/workflows/helm_release.yml new file mode 100644 index 0000000..ef3444f --- /dev/null +++ b/.github/workflows/helm_release.yml @@ -0,0 +1,93 @@ +name: Package and Release Helm Chart +on: + pull_request: + branches: + - 'v*' + types: + # action should run when the pull request is closed + # (regardless of whether it was merged or just closed) + - closed + +jobs: + helm: + name: Package and Release Helm Chart + runs-on: ubuntu-latest + + # Restrict to only run if the PR is merged + if: github.event.pull_request.merged == true + + steps: + # Set the IMAGE_NAME environment variable to the repository name + # Use parameter expansion to convert to lowercase + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + + # Set the CHART_NAME environment variable to the repository name + # IMAGE_NAME is in the format owner/repo, so use parameter expansion to get the repo name + - name: Set CHART_NAME + run: | + echo "CHART_NAME=${IMAGE_NAME##*/}" >>${GITHUB_ENV} + + # Checkout code + # https://github.com/actions/checkout + - name: Checkout code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + # Extract metadata (tags, labels) to use in Helm chart + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.IMAGE_NAME }} + + # Set version from DOCKER_METADATA_OUTPUT_VERSION as environment variable + # This workflow is triggered when PRs with semver tags are closed, so + # DOCKER_METADATA_OUTPUT_VERSION will be in the format `v1.2(.3)` + - name: Set Version + run: | + echo "VERSION=${DOCKER_METADATA_OUTPUT_VERSION:1}" >> $GITHUB_ENV + + # Change version and appVersion in Chart.yaml to the tag in the closed PR + - name: Update Helm App/Chart Version + shell: bash + run: | + sed -i "s/^version: .*/version: ${{ env.VERSION }}/g" deploy/charts/${{ env.CHART_NAME }}/Chart.yaml + sed -i "s/^appVersion: .*/appVersion: \"${{ env.DOCKER_METADATA_OUTPUT_VERSION }}\"/g" deploy/charts/${{ env.CHART_NAME }}/Chart.yaml + + # Setup Helm + # https://github.com/Azure/setup-helm + - name: Install Helm + uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3.5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + # Helm requires an ident name to be set for chart-releaser to work + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + # Build and release Helm chart to GitHub Pages + # https://github.com/helm/chart-releaser-action + - name: Run chart-releaser + uses: helm/chart-releaser-action@be16258da8010256c6e82849661221415f031968 # v1.5.0 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + charts_dir: deploy/charts + + # Create GitHub tag with Container version to kick off container release workflow + # https://github.com/actions/github-script + - name: Create new tag to kick off container release + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + github-token: ${{ secrets.TOKEN }} + script: | + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/tags/' + process.env.DOCKER_METADATA_OUTPUT_VERSION, + sha: context.sha + }) \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c261a05 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,158 @@ +name: Build and Release Container +on: + push: + branches-ignore: + - 'v*' + tags: + - 'v*' + +env: + REGISTRY: ghcr.io + +jobs: + build: + name: Build Containers + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - linux/arm64 + - linux/amd64 + - linux/s390x + - linux/ppc64le + + permissions: + contents: read + packages: write + + steps: + + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + + # Checkout code + # https://github.com/actions/checkout + - name: Checkout code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + # Extract metadata (tags, labels) for Docker + # If the workflow was triggered from a push, the edge tag will be included. + # If the workflow was triggered from a release, the latest tag will be included. + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern=v{{version}} + type=sha + type=edge + + # Set up QEMU + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login to Docker registry + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Build and push Docker image with Buildx + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + platforms: ${{ matrix.platform }} + labels: ${{ env.DOCKER_METADATA_OUTPUT_LABELS }} + push: true + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true + + # Export digest + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + # Upload digest + - name: Upload digest + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: digests + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Merge Container Manifests + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + + # Download digests + # https://github.com/actions/download-artifact + - name: Download digests + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: digests + path: /tmp/digests + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Extract metadata (tags, labels) for Docker + # If the pull request is not merged, do not include the edge tag and only include the sha tag. + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern=v{{version}} + type=sha + type=edge + + # Login to Docker registry + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Create manifest list and push + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + # Create a manifest list with the selected tag(s) and push + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DOCKER_METADATA_OUTPUT_VERSION }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d26c7ac --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,71 @@ +name: test +on: [workflow_dispatch, push, pull_request] +jobs: + build: + name: Build and Lint + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + # Checkout code + # https://github.com/actions/checkout + - name: Checkout code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + # Setup GoLang build environment + # https://github.com/actions/setup-go + - name: Set up Go 1.x + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + + # Download dependencies + - run: go mod download + + # Build Go binary + - run: go build -v . + + # Run Go linters + # https://github.com/golangci/golangci-lint-action + - name: Run linters + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 + with: + version: latest + + test: + name: Go Test + needs: build + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + # Checkout code + # https://github.com/actions/checkout + - name: Checkout code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + # Setup GoLang build environment + # https://github.com/actions/setup-go + - name: Set up Go 1.x + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + + # Download dependencies + - run: go mod download + + # Place the contents of ${{ secrets.COMMAND_CA_CERT_PATH }} into a file at /tmp/certs/ejbca-ca.pem + - run: mkdir -p /tmp/certs && echo "${{ secrets.COMMAND_CA_CERT }}" > /tmp/certs/command-ca.pem + + # Run Go tests + - name: Run go test + run: go test -v ./... + env: + COMMAND_HOSTNAME: ${{ secrets.COMMAND_HOSTNAME }} + COMMAND_PASSWORD: ${{ secrets.COMMAND_PASSWORD }} + COMMAND_USERNAME: ${{ secrets.COMMAND_USERNAME }} + COMMAND_CA_CERT_PATH: /tmp/certs/command-ca.pem + + COMMAND_CERTIFICATE_TEMPLATE: ${{ vars.COMMAND_CERTIFICATE_TEMPLATE }} + COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME: ${{ vars.COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME }} + COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME: ${{ vars.COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..79a8c01 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# v2.0.0 +## Features + +### Reconciler Controller +- Refactored K8s `CertificateSigningRequest` controller to use a Reconciler pattern using [controller-runtime](https://pkg.go.dev/sigs.k8s.io/controller-runtime) +- Changed retrieval of authentication, configuration, and CA root certificate to use the Kubernetes API instead of reading from a file +- Added support for out-of-cluster deployments using the Kubernetes API + +### Runtime Customization +- Added support for customizing the certificate signing process using annotations + +### Documentation +- Added updated documentation for deploying the Command CSR Signer v2.0 +- Added updated documentation for using the Command CSR Signer v2.0 with Istio + +### Testing +- Added unit tests for the Reconciler controller +- Added unit tests for the CSR Signer + +### Actions +- Added GitHub Actions for building and testing the Command CSR Signer +- Added GitHub Actions for releasing the Command CSR Signer \ No newline at end of file diff --git a/deploy/charts/k8s-csr-signer/templates/deployment.yaml b/deploy/charts/k8s-csr-signer/templates/deployment.yaml index 2210799..52d0435 100644 --- a/deploy/charts/k8s-csr-signer/templates/deployment.yaml +++ b/deploy/charts/k8s-csr-signer/templates/deployment.yaml @@ -5,9 +5,7 @@ metadata: labels: {{- include "command-csr-signer.labels" . | nindent 4 }} spec: -{{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} -{{- end }} selector: matchLabels: {{- include "command-csr-signer.selectorLabels" . | nindent 6 }} diff --git a/docs/getting-started.markdown b/docs/getting-started.markdown index 6d1d2ba..8535468 100644 --- a/docs/getting-started.markdown +++ b/docs/getting-started.markdown @@ -110,6 +110,8 @@ The Command K8s CSR Signer is installed using a Helm chart. The chart is availab --namespace command-signer-system \ --set image.repository=/keyfactor/k8s-csr-signer \ --set image.tag= \ + --set command.credsSecretName=command-credentials \ + --set command.configMapName=command-signer-config \ --set command.signerNames[0]=internalsigner.com ``` diff --git a/docs/istio-deployment.markdown b/docs/istio-deployment.markdown index a175ee4..3994bd6 100644 --- a/docs/istio-deployment.markdown +++ b/docs/istio-deployment.markdown @@ -62,7 +62,7 @@ Follow the steps in the [Getting Started](getting-started.markdown) guide to bui The signer names can be modified according do your cluster's needs, but you _must_ ensure that the signer names match the signer names configured in the `command.signerNames` in the Command K8s CSR Signer Helm chart. By default, no signer names are configured in the Command K8s CSR Signer; all signer names are in scope. ```yaml - cat < ./ejbca-istio.yaml + cat < ./command-istio.yaml apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: @@ -115,7 +115,7 @@ Follow the steps in the [Getting Started](getting-started.markdown) guide to bui verbs: - approve EOF - istioctl install --skip-confirmation -f ./ejbca-istio.yaml + istioctl install --skip-confirmation -f ./command-istio.yaml ``` ## 3. Deploy the Bookinfo demo application diff --git a/internal/signer/signer.go b/internal/signer/signer.go index d791d8b..cddf1c9 100644 --- a/internal/signer/signer.go +++ b/internal/signer/signer.go @@ -337,16 +337,6 @@ func (s *commandSigner) Sign(csr certificates.CertificateSigningRequest) ([]byte return pemChain, nil } -func (s *commandSigner) deprecatedAnnotationGetter(annotations map[string]string, annotation string) string { - annotationValue, ok := annotations[annotation] - if ok { - s.logger.Info(fmt.Sprintf("Annotations specified without the %q prefix is deprecated and will be removed in the future. Using %q as %q", annotationPrefix, annotationValue, annotation)) - return annotationValue - } - - return "" -} - func getCertificatesFromCertificateInformation(commandResp *keyfactor.ModelsPkcs10CertificateResponse) ([]*x509.Certificate, error) { var certBytes []byte diff --git a/sample/sample.yaml b/sample/sample.yaml index 93dcbfd..c973bd5 100644 --- a/sample/sample.yaml +++ b/sample/sample.yaml @@ -5,11 +5,11 @@ metadata: annotations: k8s-csr-signer.keyfactor.com/certificateTemplate: 2YearTestWebServer k8s-csr-signer.keyfactor.com/certificateAuthorityHostname: DC-CA.Command.local - k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: 2YearTestWebServer + k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: CommandCA1 k8s-csr-signer.keyfactor.com/chainDepth: "0" spec: - request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ3VEQ0NBYUFDQVFBd0FEQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU0waApZOTdtam1BT09ic0ZhSUJKMktSdFBFMGxkN3YwT1JIa3dnVCtodFVkd1ZyYzdiZm9zVnJ3UGdGV05oZVl2cTJsCktHdTU2Vk1tWXQ1aXFWOGhFck9rbXhpM1BSK2srSFhqdVJyakpJdnMvYWg2Qm5DZ0ExTmE2U1pRT2hGazUxa0kKaVpuOTQwWFRoaTJ5Q1dLaWlkUEJ0dlNabFE2VDlQdXhOSTJjekVVNDF0QVRUUXZOV0JMMzRXeWJENkpsbHVtWApVb0Foa0x4cElvaU45Ykk4NEgyVWg0bU14ZzUzZEVZMVZmV2EvVWk3czhGUzl1bHNkZmM3MFhvWTJFMHFsbW83ClV6c2NxbklBd0czblNhb3U1anFzbzVoZ2REZFo1NXZhOUdLS0RWUzFGSFJWaUZTMlNWUmRPMTNnWTF1VWc5Y2MKakpwUzc3S2hUbXBHelFjNHRxRUNBd0VBQWFCek1IRUdDU3FHU0liM0RRRUpEakZrTUdJd1lBWURWUjBSQkZrdwpWNElYYVhOMGFXOWtMbWx6ZEdsdkxYTjVjM1JsYlM1emRtT0NIbWx6ZEdsdlpDMXlaVzF2ZEdVdWFYTjBhVzh0CmMzbHpkR1Z0TG5OMlk0SWNhWE4wYVc4dGNHbHNiM1F1YVhOMGFXOHRjM2x6ZEdWdExuTjJZekFOQmdrcWhraUcKOXcwQkFRc0ZBQU9DQVFFQVNvZGZ3dDdJd2FQNVhRVkpSYXF3QUdTTTlwUDZHMnNsMDlqTUtibHhBWTZlSVJ0MAp0OXJ5dkVQOCttYmJFSkJucEZSc3dmL2NIS3pNVDBlNHc2TDNuL3pOYlRTWDdGUWZWNEdqdjIzM3NQS0tvZjloCk5HWXhpOWFHZmR3eitRbFhTS0RUNzJaaE5xUElFdStNNjlXUVRXclhUWjhpeWVmT2JnbzFpdlhxclkxT0UxYTUKWUNJMWVYL0UwRGtMWlkvSVNweUtiUnVTZTVmNUZCZmRuVjNWamM5dGluWmdOTVNLWm1LZHpnS0Z3NVdLY3NPWgpZT2VYS3hueUlxcUhNQS9odWIvYnRrV2ovSExGWlhWb0xCbzIxWnFCKzBQbTRZUFkzMWM2UmoySmtCUjVqWm9qCnMxWm5aNkFoN1Q5V1ZUVE9HVy9YVjhtRStkUS94ZGdxdzgrZnhnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg== + request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ25EQ0NBWVFDQVFBd0lqRWdNQjRHQTFVRUF4TVhhemh6TFdOemNpMXphV2R1WlhJdGRHVnpkQzVqYjIwdwpnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFESmcydDI0MjBERzVCZStsM3FSRGE4Cm1rOFdJRDVzaXJJRVh1aEQzMWQ5R3ZqSVJ4bjRqT01SL2Y0ck93MmM2aWNDQVFkUmRNbG5pSFYzM20vb09FekIKTkVLQWpoSWpTTUVERXpsa1d2MGVGcTFnK25RN2c3bUZpVHB0RDhhck9LZlhBM2t3eDU3bURhWkJHTDNPaXlqUAo5Y2Y5NWl2VldDa2JNZk9JV0ZlTVIyRnY3Um1PcEFiaW1JUnBnSWlnd3dROHVzaVRhN3FNcUJBa2R0b2w2MjJKCkxaWUloSGs2WVB1b253MnBoRHNZL1pkY2dLSi9CaGlaTC9ibng3NlJ0WnRWRVF6OSsySmdWTjA2eHJNUVIyUnoKVUdMbGRKb2dIWDQwTUhQYWRMNXdSZkJiaTVQa0VxeTF4RzRwYUFUY29lNVk2YTlRREQ2cFZXeDhjanVpTXJKOQpBZ01CQUFHZ05UQXpCZ2txaGtpRzl3MEJDUTR4SmpBa01DSUdBMVVkRVFRYk1CbUNGMnM0Y3kxamMzSXRjMmxuCmJtVnlMWFJsYzNRdVkyOXRNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJWUnV0dGk3V0lpY3VMdGxFT2UzZzYKYjdQUzI0Q1hadEhxQ2tDVENZWmtkR1NNaXRPcUljajduenFnWTlndWdjYURSVERWcExkTVo4SWtoZGhuR3BZRgo2R25ma0poQmRQNVg0U3JsdGlRUml0YjNQZXJQTm8rU29xbFFXNTl2S3JselRHVng4MldNbjNYc2JLY0VOam9vCktXUjJKTHVvVVNlOGJqdit1T0E4SVlqQW9WZEtGMkNJVCtIZmVZNzVOcnQ1RVlqZ2tzL0JTVmsva05lRXJKM3kKSEthN3JzeUVFQXpxcmY1QmNXM1B5UlU3UitIWmkxZlpudnlzRTA5Nnd3QlVBb01OYmZ5OFFRUlpXV2l1NTJpMwphSko4VUR4SlcyTys0UGF1Q3haVm0yeUQ1UjdzekYrZjMyeXFjT0xOYVNDWndmNmNrYUNEZmphTjRlUzJnM1drCi0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo= usages: - client auth - server auth From d858387dc3b0153121568b16d7ad08053a3bf82d Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 5 Dec 2023 10:41:48 -0700 Subject: [PATCH 04/11] chore(docs): Expand on Command configuration --- deploy/charts/k8s-csr-signer/README.md | 105 +++++++++++++++++++++++++ docs/istio-deployment.markdown | 15 ++++ 2 files changed, 120 insertions(+) create mode 100644 deploy/charts/k8s-csr-signer/README.md diff --git a/deploy/charts/k8s-csr-signer/README.md b/deploy/charts/k8s-csr-signer/README.md new file mode 100644 index 0000000..2d352ea --- /dev/null +++ b/deploy/charts/k8s-csr-signer/README.md @@ -0,0 +1,105 @@ + + Kubernetes logo + + + + Helm logo + + +# Helm chart for the Command K8s Certificate Signing Request Proxy + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/k8s-csr-signer)](https://goreportcard.com/report/github.com/Keyfactor/k8s-csr-signer) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/keyfactor/k8s-csr-signer?label=release)](https://github.com/keyfactor/k8s-csr-signer/releases) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![license](https://img.shields.io/github/license/keyfactor/k8s-csr-signer.svg)]() + +The Command Certificate Signing Request Proxy for K8s forwards certificate signing requests generated by Kubernetes to [Keyfactor Command](https://www.keyfactor.com/products/command/) for signing by a trusted enterprise certificate authority. The signer operates within the [K8s CertificateSigningRequests API](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) and implements a Controller that uses the the V1 CertificateSigningRequests API to handle associated resources. CSRs are only enrolled if they are approved using an [approver](https://github.com/kubernetes/kubernetes/tree/master/pkg/controller/certificates/approver). + +## Installation + +### Add Helm Repository + +```bash +helm repo add command-k8s https://keyfactor.github.io/k8s-csr-signer +helm repo update +``` + +### Install Chart + +```bash +helm install k8s-csr-signer command-issuer/k8s-csr-signer \ + --namespace command-signer-system \ + --set image.repository=/keyfactor/k8s-csr-signer \ + --set image.tag= \ + # --set image.pullPolicy=Never # Only required if using a local image \ + --set image.pullPolicy=Never \ + --set command.credsSecretName=command-credentials \ + --set command.configMapName=command-signer-config \ + # --set command.caCertConfigmapName=command-ca-cert # Only required if Command API serves an untrusted certificate +``` + +Modifications can be made by overriding the default values in the `values.yaml` file with the `--set` flag. For example, to override the `replicaCount` value, run the following command: +```shell +helm install k8s-csr-signer command-k8s/k8s-csr-signer \ + --namespace command-signer-system \ + --set image.repository=/keyfactor/k8s-csr-signer \ + --set image.tag= \ + --set command.credsSecretName=command-credentials \ + --set command.configMapName=command-signer-config \ + --set command.signerNames[0]=internalsigner.com +``` + +Modifications can also be made by modifying the `values.yaml` file directly. For example, to override the +`signerNames` value, modify the `signerNames` value in the `values.yaml` file: + +```yaml +cat < override.yaml +image: + repository: /keyfactor/k8s-csr-signer + pullPolicy: Never + tag: "latest" +command: + credsSecretName: command-credentials + configMapName: command-signer-config + caCertConfigmapName: command-ca-cert + signerNames: + - internalsigner.com/cluster +EOF +``` + +Then, use the `-f` flag to specify the `values.yaml` file: + +```yaml +helm install k8s-csr-signer command-k8s/k8s-csr-signer \ + -n command-signer-system \ + -f override.yaml +``` + +###### :pushpin: Wildcards are **NOT** supported in the `signerNames` field. If you want to allow all signers, do not specify any signer names. + +###### :pushpin: The Command K8s CSR signer uses the `SelfSubjectAccessReview` API to determine if the user has permission to sign the CSR. If the user does not have permission, the signer will ignore the CSR. + +## Configuration + +The following table lists the configurable parameters of the `k8s-csr-signer` chart and their default values. + +| Configuration Item | Default Value | Description | +|-------------------------------|-----------------|-----------------------------------------------------------------| +| `replicaCount` | `1` | Number of replicas | +| `imagePullSecrets` | `[]` | List of secrets used to pull images | +| `nameOverride` | `""` | Override for the app name | +| `fullnameOverride` | `""` | Override for the app's full name | +| `image.repository` | `""` | Image repository | +| `image.tag` | `""` | Image tag | +| `image.pullPolicy` | `IfNotPresent` | Image pull policy | +| `command.credsSecretName` | `""` | Name of the secret containing Command credentials | +| `command.configMapName` | `""` | Name of the configmap containing Command configuration | +| `command.caCertConfigmapName` | `""` | Name of the configmap containing the Command API CA certificate | +| `command.signerNames` | `[]` | Signer names that this signer will respond to | +| `serviceAccount.create` | `true` | Specifies whether a service account should be created | +| `serviceAccount.annotations` | `{}` | Annotations to add to the service account | +| `serviceAccount.name` | `"command-k8s"` | The name of the service account to use | +| `podSecurityContext` | `{}` | Pod security context settings | +| `securityContext` | `{}` | Security context settings for the pod | +| `resources` | `{}` | CPU/memory resource requests/limits (commented out by default) | +| `nodeSelector` | `{}` | Node labels for pod assignment | +| `tolerations` | `[]` | Tolerations for pod assignment | +| `affinity` | `{}` | Affinity settings for pod assignment | + diff --git a/docs/istio-deployment.markdown b/docs/istio-deployment.markdown index 3994bd6..68e2116 100644 --- a/docs/istio-deployment.markdown +++ b/docs/istio-deployment.markdown @@ -23,6 +23,21 @@ This guide will walk through configuring Istio to use the Command K8s CSR Signer For this tutorial, it's recommended that a distribution of Linux is used as the host operating system. +## Prepare Keyfactor Command + +Keyfactor Command must be configured with an active CA and a certificate template that can support signing of Istio workload certificates. Creating certificate templates will vary depending on your CA choice, but the template must support the following: + +| Element | Requirement | +|--------------------------|-------------------------------------------------------------------------------------| +| Public Key Algorithm | Must support RSA | +| Key Size | Must support 2048 | +| Signature Algorithm | Must support SHA256 with RSA | +| Subject DN | None, Istio workload certificates will not have a subject DN. | +| Subject Alternative Name | Must support DNS Names, and the DNS Names must be allowed to not match the subject. | +| Key Usage | Must support Digital Signature and Key Encipherment. | +| Extended Key Usage | Must support Server and Client Authentication. | + + ## 1. Deploy the Command K8s CSR Signer Follow the steps in the [Getting Started](getting-started.markdown) guide to build the container image and prepare the credentials and configuration. From 8f76d22481082f05645c411b0ca6bd5d613e9ed5 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 5 Dec 2023 11:10:16 -0700 Subject: [PATCH 05/11] chore(manifest): Update integration manifest --- integration-manifest.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration-manifest.json b/integration-manifest.json index b5d48fc..cedba83 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -4,5 +4,7 @@ "name": "k8s-csr-signer", "status": "pilot", "link_github":true, - "description": "Signer for Kubernetes CSR signing API that passes certificate requests to the Keyfactor Web API for signing with a trusted enterprise CA" + "description": "Signer for Kubernetes CSR signing API that passes certificate requests to the Keyfactor Command API for signing with a trusted enterprise CA", + "support_level": "kf-community", + "release_dir": "" } From 69bdc7bb0014c0a2d3cfc0bad82e1c4fa82ff923 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 5 Dec 2023 12:03:24 -0700 Subject: [PATCH 06/11] chore(docs): Refactor getting started guide --- docs/getting-started.markdown | 2 +- sample/sample.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting-started.markdown b/docs/getting-started.markdown index 8535468..9c5003d 100644 --- a/docs/getting-started.markdown +++ b/docs/getting-started.markdown @@ -46,7 +46,7 @@ make docker-build DOCKER_REGISTRY= DOCKER_IMAGE_NAME=ke * If you want to configure the signer to authenticate to Command using HTTP Basic Auth, create a `kubernetes.io/basic-auth` secret. - Create a `kubernetes.io/basic-auth` secret containing the username and password. The secret must be created in the same namespace as the Helm chart. + Create a `kubernetes.io/basic-auth` secret containing the username and password. If the domain is required by the Command auth method, the username should be in the form `domain\username`. The secret must be created in the same namespace as the Helm chart. ```shell kubectl -n command-signer-system create secret generic --type=kubernetes.io/basic-auth command-credentials \ diff --git a/sample/sample.yaml b/sample/sample.yaml index c973bd5..b029722 100644 --- a/sample/sample.yaml +++ b/sample/sample.yaml @@ -3,7 +3,7 @@ kind: CertificateSigningRequest metadata: name: commandCsrTest annotations: - k8s-csr-signer.keyfactor.com/certificateTemplate: 2YearTestWebServer + k8s-csr-signer.keyfactor.com/certificateTemplate: ClientAuth k8s-csr-signer.keyfactor.com/certificateAuthorityHostname: DC-CA.Command.local k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: CommandCA1 From b58d286fcf09a87fc7821bcdfc0618ae4fff9a03 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 5 Dec 2023 13:40:31 -0700 Subject: [PATCH 07/11] feat(metadata): Signer recognizes annotation prefix for Command metadata --- .../k8s-csr-signer/templates/clusterrole.yaml | 21 +------ .../k8s-csr-signer/templates/secretrole.yaml | 38 +++++++++++++ docs/annotations.markdown | 9 +++ docs/getting-started.markdown | 55 +++++++++++++++++++ .../certificatesigningrequest_controller.go | 13 ++++- internal/controllers/fake_signer_test.go | 4 ++ internal/signer/signer.go | 49 ++++++++++++++++- internal/signer/signer_test.go | 39 +++++++++++-- sample/sample.yaml | 1 + 9 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 deploy/charts/k8s-csr-signer/templates/secretrole.yaml diff --git a/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml b/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml index 51d945a..59095cc 100644 --- a/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml +++ b/deploy/charts/k8s-csr-signer/templates/clusterrole.yaml @@ -6,25 +6,6 @@ metadata: labels: {{- include "command-csr-signer.labels" . | nindent 4 }} rules: - # The controller needs to be able to read secrets and configmaps to get authentication information - # and to configure itself - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - # configuration validation webhook controller - apiGroups: ["admissionregistration.k8s.io"] resources: ["validatingwebhookconfigurations"] @@ -44,4 +25,4 @@ rules: resourceNames: {{- toYaml . | nindent 6 }} {{- end }} - verbs: ["approve", "sign"] + verbs: ["sign"] diff --git a/deploy/charts/k8s-csr-signer/templates/secretrole.yaml b/deploy/charts/k8s-csr-signer/templates/secretrole.yaml new file mode 100644 index 0000000..897843d --- /dev/null +++ b/deploy/charts/k8s-csr-signer/templates/secretrole.yaml @@ -0,0 +1,38 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + {{- include "command-csr-signer.labels" . | nindent 4 }} + name: {{ include "command-csr-signer.name" . }}-secret-reader-role +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + {{- include "command-csr-signer.labels" . | nindent 4 }} + name: {{ include "command-csr-signer.name" . }}-secret-reader-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "command-csr-signer.name" . }}-secret-reader-role +subjects: + - kind: ServiceAccount + name: {{ include "command-csr-signer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/docs/annotations.markdown b/docs/annotations.markdown index b2f5fb2..822ea34 100644 --- a/docs/annotations.markdown +++ b/docs/annotations.markdown @@ -31,6 +31,15 @@ Here are the supported annotations that can override the default values: k8s-csr-signer.keyfactor.com/chainDepth: 3 ``` +### Metadata Annotations + +The Keyfactor Command K8s CSR Signer also allows you to specify Command Metadata through the use of annotations. Metadata attached to a certificate request will be stored in Command and can be used for reporting and auditing purposes. The syntax for specifying metadata is as follows: +```yaml +metadata.k8s-csr-signer.keyfactor.com/: +``` + +Ensure that the metadata specified by the `metadata-field-name` matches a name of a metadata field in Command exactly. If the metadata field name does not match, the CSR enrollment will fail. + ### How to Apply Annotations To apply these annotations, include them in the metadata section of your CertificateSigningRequest resource: diff --git a/docs/getting-started.markdown b/docs/getting-started.markdown index 9c5003d..cc7631a 100644 --- a/docs/getting-started.markdown +++ b/docs/getting-started.markdown @@ -19,6 +19,61 @@ * Helm (to deploy to Kubernetes) * [Helm](https://helm.sh/docs/intro/install/) (v3.1 +) +### Keyfactor Command Configuration +The Command K8s CSR Signer populates metadata fields on issued certificates in Command pertaining to the K8s cluster and CertificateSigningRequest reconcile loops. Before deploying any CSRs in the cluster, these metadata fields must be created in Command. To easily create these metadata fields, use the `kfutil` Keyfactor command line tool that offers convenient and powerful command line access to the Keyfactor platform. Before proceeding, ensure that `kfutil` is installed and configured by following the instructions here: [https://github.com/Keyfactor/kfutil](https://github.com/Keyfactor/kfutil). + +Then, use the `import` command to import the metadata fields into Command: +```shell +cat <> metadata.json +{ + "Collections": [], + "MetadataFields": [ + { + "AllowAPI": true, + "DataType": 1, + "Description": "The reconcile ID of the reconcile loop that signed the certificate.", + "Name": "Controller-Reconcile-Id" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The namespace that the controller reconciler is running in.", + "Name": "Controller-Namespace" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The name of the resource that a K8s controller is reconciling.", + "Name": "Controller-Kind" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The group name of the resource that a K8s controller is reconciling.", + "ExplicitUpdate": false, + "Name": "Controller-Resource-Group-Name" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The name of the resource being reconciled in Kubernetes", + "Name": "Controller-Resource-Name", + } + ], + "ExpirationAlerts": [], + "IssuedCertAlerts": [], + "DeniedCertAlerts": [], + "PendingCertAlerts": [], + "Networks": [], + "WorkflowDefinitions": [], + "BuiltInReports": [], + "CustomReports": [], + "SecurityRoles": [] +} +EOF +kfutil import --metadata --file metadata.json +``` + ## Getting Started Install required software and their dependencies if not already present. Additionally, verify that at least one Kubernetes node is running by running the following command: diff --git a/internal/controllers/certificatesigningrequest_controller.go b/internal/controllers/certificatesigningrequest_controller.go index fcb5145..e3f6222 100644 --- a/internal/controllers/certificatesigningrequest_controller.go +++ b/internal/controllers/certificatesigningrequest_controller.go @@ -30,6 +30,7 @@ import ( "k8s.io/utils/clock" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" ) type CertificateSigningRequestReconciler struct { @@ -45,6 +46,8 @@ type CertificateSigningRequestReconciler struct { func (c *CertificateSigningRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { reconcileLog := ctrl.LoggerFrom(ctx) + meta := signer.K8sMetadata{} + c.SignerBuilder.Reset() // Get the CertificateSigningRequest @@ -116,12 +119,20 @@ func (c *CertificateSigningRequestReconciler) Reconcile(ctx context.Context, req } } + // Populate metadata + meta.ControllerNamespace = c.ClusterResourceNamespace + meta.ControllerKind = "CertificateSigningRequest" + meta.ControllerResourceGroupName = "certificatesigningrequests.certificates.k8s.io" + meta.ControllerReconcileId = string(controller.ReconcileIDFromContext(ctx)) + meta.ControllerResourceName = certificateSigningRequest.GetName() + // Apply the configuration to the signer builder c.SignerBuilder. WithContext(ctx). WithCredsSecret(creds). WithConfigMap(config). - WithCACertConfigMap(root) + WithCACertConfigMap(root). + WithMetadata(meta) // Validate that there were no issues with the configuration err = c.SignerBuilder.PreFlight() diff --git a/internal/controllers/fake_signer_test.go b/internal/controllers/fake_signer_test.go index 6e7f348..285581d 100644 --- a/internal/controllers/fake_signer_test.go +++ b/internal/controllers/fake_signer_test.go @@ -50,6 +50,10 @@ func (f *FakeSignerBuilder) WithCACertConfigMap(configMap corev1.ConfigMap) sign return f } +func (f *FakeSignerBuilder) WithMetadata(meta signer.K8sMetadata) signer.Builder { + return f +} + func (f *FakeSignerBuilder) PreFlight() error { return nil } diff --git a/internal/signer/signer.go b/internal/signer/signer.go index cddf1c9..0cc0f88 100644 --- a/internal/signer/signer.go +++ b/internal/signer/signer.go @@ -23,8 +23,9 @@ var _ Builder = &commandSigner{} var _ Signer = &commandSigner{} const ( - enrollmentPEMFormat = "PEM" - annotationPrefix = "k8s-csr-signer.keyfactor.com/" + enrollmentPEMFormat = "PEM" + annotationPrefix = "k8s-csr-signer.keyfactor.com/" + commandMetadataAnnotationPrefix = "metadata.k8s-csr-signer.keyfactor.com/" ) type Builder interface { @@ -33,6 +34,7 @@ type Builder interface { WithCredsSecret(corev1.Secret) Builder WithConfigMap(corev1.ConfigMap) Builder WithCACertConfigMap(corev1.ConfigMap) Builder + WithMetadata(meta K8sMetadata) Builder PreFlight() error Build() Signer } @@ -41,11 +43,30 @@ type Signer interface { Sign(csr certificates.CertificateSigningRequest) ([]byte, error) } +type K8sMetadata struct { + ControllerNamespace string + ControllerKind string + ControllerResourceGroupName string + ControllerReconcileId string + ControllerResourceName string +} + +const ( + CommandMetaControllerNamespace = "Controller-Namespace" + CommandMetaControllerKind = "Controller-Kind" + CommandMetaControllerResourceGroupName = "Controller-Resource-Group-Name" + CommandMetaControllerReconcileId = "Controller-Reconcile-Id" + CommandMetaControllerResourceName = "Controller-Resource-Name" +) + type commandSigner struct { ctx context.Context logger logr.Logger creds corev1.Secret + // Meta + meta K8sMetadata + // Given from config hostname string defaultCertificateTemplate string @@ -162,6 +183,11 @@ func (s *commandSigner) WithCACertConfigMap(config corev1.ConfigMap) Builder { return s } +func (s *commandSigner) WithMetadata(meta K8sMetadata) Builder { + s.meta = meta + return s +} + func (s *commandSigner) PreFlight() error { var err error @@ -271,6 +297,25 @@ func (s *commandSigner) Sign(csr certificates.CertificateSigningRequest) ([]byte } } + // Construct metadata map + meta := map[string]interface{}{ + CommandMetaControllerNamespace: s.meta.ControllerNamespace, + CommandMetaControllerKind: s.meta.ControllerKind, + CommandMetaControllerResourceGroupName: s.meta.ControllerResourceGroupName, + CommandMetaControllerReconcileId: s.meta.ControllerReconcileId, + CommandMetaControllerResourceName: s.meta.ControllerResourceName, + } + + // Set custom metadata from annotations + for key, value := range annotations { + if strings.HasPrefix(key, commandMetadataAnnotationPrefix) { + meta[strings.TrimPrefix(key, commandMetadataAnnotationPrefix)] = value + } + } + + // Set metadata on enrollment request + enroll.SetMetadata(meta) + // Construct CA name from hostname and logical name var caBuilder strings.Builder if certificateAuthorityHostname != "" { diff --git a/internal/signer/signer_test.go b/internal/signer/signer_test.go index d385129..91c21e7 100644 --- a/internal/signer/signer_test.go +++ b/internal/signer/signer_test.go @@ -193,6 +193,30 @@ func TestCommandSignerBuilder(t *testing.T) { } }) }) + + t.Run("WithMetadata", func(t *testing.T) { + meta := GetFakeMetadata() + + signer.WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + signer.WithMetadata(meta) + + assert.Equal(t, meta.ControllerNamespace, signer.meta.ControllerNamespace) + assert.Equal(t, meta.ControllerKind, signer.meta.ControllerKind) + assert.Equal(t, meta.ControllerResourceGroupName, signer.meta.ControllerResourceGroupName) + assert.Equal(t, meta.ControllerReconcileId, signer.meta.ControllerReconcileId) + assert.Equal(t, meta.ControllerResourceName, signer.meta.ControllerResourceName) + }) +} + +func GetFakeMetadata() K8sMetadata { + return K8sMetadata{ + ControllerNamespace: "fake-namespace", + ControllerKind: "fake-kind", + ControllerResourceGroupName: "fake-resource-group", + ControllerReconcileId: "fake-reconcile-id", + ControllerResourceName: "fake-resource-name", + } } func TestCommandSigner(t *testing.T) { @@ -238,7 +262,8 @@ func TestCommandSigner(t *testing.T) { WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))). WithCredsSecret(creds). WithConfigMap(signerConfig). - WithCACertConfigMap(caConfig) + WithCACertConfigMap(caConfig). + WithMetadata(GetFakeMetadata()) err = builder.PreFlight() if err != nil { @@ -281,10 +306,11 @@ func TestCommandSigner(t *testing.T) { // Create supported annotations supportedAnnotations := map[string]string{ - "k8s-csr-signer.keyfactor.com/certificateTemplate": commandConfig.commandCertificateTemplate, - "k8s-csr-signer.keyfactor.com/certificateAuthorityHostname": commandConfig.commandCertificateAuthorityHostname, - "k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName": commandConfig.commandCertificateAuthorityLogicalName, - "k8s-csr-signer.keyfactor.com/chainDepth": "5", + annotationPrefix + "certificateTemplate": commandConfig.commandCertificateTemplate, + annotationPrefix + "certificateAuthorityHostname": commandConfig.commandCertificateAuthorityHostname, + annotationPrefix + "certificateAuthorityLogicalName": commandConfig.commandCertificateAuthorityLogicalName, + annotationPrefix + "chainDepth": "5", + commandMetadataAnnotationPrefix + "Email-Contact": "k8s-csr-signer@keyfactor.com", } t.Run("BasicAuthWithAnnotations", func(t *testing.T) { @@ -309,7 +335,8 @@ func TestCommandSigner(t *testing.T) { WithContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))). WithCredsSecret(estCreds). WithConfigMap(signerConfig). - WithCACertConfigMap(caConfig) + WithCACertConfigMap(caConfig). + WithMetadata(GetFakeMetadata()) err = builder.PreFlight() if err != nil { diff --git a/sample/sample.yaml b/sample/sample.yaml index b029722..eb96a89 100644 --- a/sample/sample.yaml +++ b/sample/sample.yaml @@ -8,6 +8,7 @@ metadata: k8s-csr-signer.keyfactor.com/certificateAuthorityLogicalName: CommandCA1 k8s-csr-signer.keyfactor.com/chainDepth: "0" + metadata.k8s-csr-signer.keyfactor.com/Email-Contact: "demo-command-signer@command.com" spec: request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ25EQ0NBWVFDQVFBd0lqRWdNQjRHQTFVRUF4TVhhemh6TFdOemNpMXphV2R1WlhJdGRHVnpkQzVqYjIwdwpnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFESmcydDI0MjBERzVCZStsM3FSRGE4Cm1rOFdJRDVzaXJJRVh1aEQzMWQ5R3ZqSVJ4bjRqT01SL2Y0ck93MmM2aWNDQVFkUmRNbG5pSFYzM20vb09FekIKTkVLQWpoSWpTTUVERXpsa1d2MGVGcTFnK25RN2c3bUZpVHB0RDhhck9LZlhBM2t3eDU3bURhWkJHTDNPaXlqUAo5Y2Y5NWl2VldDa2JNZk9JV0ZlTVIyRnY3Um1PcEFiaW1JUnBnSWlnd3dROHVzaVRhN3FNcUJBa2R0b2w2MjJKCkxaWUloSGs2WVB1b253MnBoRHNZL1pkY2dLSi9CaGlaTC9ibng3NlJ0WnRWRVF6OSsySmdWTjA2eHJNUVIyUnoKVUdMbGRKb2dIWDQwTUhQYWRMNXdSZkJiaTVQa0VxeTF4RzRwYUFUY29lNVk2YTlRREQ2cFZXeDhjanVpTXJKOQpBZ01CQUFHZ05UQXpCZ2txaGtpRzl3MEJDUTR4SmpBa01DSUdBMVVkRVFRYk1CbUNGMnM0Y3kxamMzSXRjMmxuCmJtVnlMWFJsYzNRdVkyOXRNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJWUnV0dGk3V0lpY3VMdGxFT2UzZzYKYjdQUzI0Q1hadEhxQ2tDVENZWmtkR1NNaXRPcUljajduenFnWTlndWdjYURSVERWcExkTVo4SWtoZGhuR3BZRgo2R25ma0poQmRQNVg0U3JsdGlRUml0YjNQZXJQTm8rU29xbFFXNTl2S3JselRHVng4MldNbjNYc2JLY0VOam9vCktXUjJKTHVvVVNlOGJqdit1T0E4SVlqQW9WZEtGMkNJVCtIZmVZNzVOcnQ1RVlqZ2tzL0JTVmsva05lRXJKM3kKSEthN3JzeUVFQXpxcmY1QmNXM1B5UlU3UitIWmkxZlpudnlzRTA5Nnd3QlVBb01OYmZ5OFFRUlpXV2l1NTJpMwphSko4VUR4SlcyTys0UGF1Q3haVm0yeUQ1UjdzekYrZjMyeXFjT0xOYVNDWndmNmNrYUNEZmphTjRlUzJnM1drCi0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo= usages: From 5f0e521f9ed4cd1bcd0679b09c33e081fb4e92b3 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 6 Dec 2023 15:58:52 -0700 Subject: [PATCH 08/11] fix(config): Implement K8s client-go in secondary API client for secret/config retrieval --- go.mod | 60 +++---- go.sum | 160 ++++++++---------- .../certificatesigningrequest_controller.go | 12 +- ...rtificatesigningrequest_controller_test.go | 1 + .../controllers/fake_configclient_test.go | 57 +++++++ main.go | 8 + pkg/util/configclient.go | 152 +++++++++++++++++ pkg/util/configclient_test.go | 88 ++++++++++ 8 files changed, 419 insertions(+), 119 deletions(-) create mode 100644 internal/controllers/fake_configclient_test.go create mode 100644 pkg/util/configclient.go create mode 100644 pkg/util/configclient_test.go diff --git a/go.mod b/go.mod index f376efa..88eaf07 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.20 require ( github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 - github.com/go-logr/logr v1.2.4 - github.com/stretchr/testify v1.8.2 + github.com/go-logr/logr v1.3.0 + github.com/stretchr/testify v1.8.4 k8s.io/api v0.28.4 k8s.io/apimachinery v0.28.4 k8s.io/client-go v0.28.4 + k8s.io/klog/v2 v2.110.1 k8s.io/utils v0.0.0-20231127182322-b307cd553661 sigs.k8s.io/controller-runtime v0.16.3 ) @@ -19,54 +20,53 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/zapr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/swag v0.22.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.25.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.28.3 // indirect - k8s.io/component-base v0.28.3 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/apiextensions-apiserver v0.28.4 // indirect + k8s.io/component-base v0.28.4 // indirect + k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 696b616..15760a8 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 h1:caLlzFCz2L4Dth/9wh+VlypFATmOMmCSQkCPKOKMxw8= github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2/go.mod h1:Z5pSk8YFGXHbKeQ1wTzVN8A4P/fZmtAwqu3NgBHbDOs= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -14,52 +12,51 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= +github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -69,7 +66,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 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= @@ -79,19 +77,18 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -99,89 +96,82 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -194,25 +184,25 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= -k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU= +k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM= k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= -k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= -k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo= +k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 h1:vzKzxN5uyJZLY8HL1/OovW7BJefnsBIWt8T7Gjh2boQ= +k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/controllers/certificatesigningrequest_controller.go b/internal/controllers/certificatesigningrequest_controller.go index e3f6222..1bf0867 100644 --- a/internal/controllers/certificatesigningrequest_controller.go +++ b/internal/controllers/certificatesigningrequest_controller.go @@ -35,6 +35,7 @@ import ( type CertificateSigningRequestReconciler struct { client.Client + ConfigClient util.ConfigClient Scheme *runtime.Scheme SignerBuilder signer.Builder ClusterResourceNamespace string @@ -97,23 +98,26 @@ func (c *CertificateSigningRequestReconciler) Reconcile(ctx context.Context, req reconcileLog.Info(fmt.Sprintf("Preparing to sign CSR called %q", certificateSigningRequest.GetName())) + // Set the context on the config client + c.ConfigClient.SetContext(ctx) + // Get the credentials secret var creds corev1.Secret - if err = c.Get(ctx, c.CredsSecret, &creds); err != nil { + if err = c.ConfigClient.GetSecret(c.CredsSecret, &creds); err != nil { return ctrl.Result{}, fmt.Errorf("failed to get Secret containing Signer credentials, secret name: %s, reason: %v", c.CredsSecret.Name, err) } // Get the signer configuration var config corev1.ConfigMap - if err = c.Get(ctx, c.ConfigMap, &config); err != nil { + if err = c.ConfigClient.GetConfigMap(c.ConfigMap, &config); err != nil { return ctrl.Result{}, fmt.Errorf("failed to get ConfigMap containing Signer configuration, configmap name: %s, reason: %v", c.ConfigMap.Name, err) } // Get the CA certificate var root corev1.ConfigMap + // If the CA secret name is not specified, we will not attempt to retrieve it if c.CaCertConfigmap.Name != "" { - // If the CA secret name is not specified, we will not attempt to retrieve it - err = c.Get(ctx, c.CaCertConfigmap, &root) + err = c.ConfigClient.GetConfigMap(c.CaCertConfigmap, &root) if err != nil { return ctrl.Result{}, fmt.Errorf("caSecretName was provided, but failed to get ConfigMap containing CA certificate, configmap name: %q, reason: %v", c.CaCertConfigmap, err) } diff --git a/internal/controllers/certificatesigningrequest_controller_test.go b/internal/controllers/certificatesigningrequest_controller_test.go index a9b3575..f7dd358 100644 --- a/internal/controllers/certificatesigningrequest_controller_test.go +++ b/internal/controllers/certificatesigningrequest_controller_test.go @@ -221,6 +221,7 @@ func TestCertificateSigningRequestReconciler_Reconcile(t *testing.T) { Build() controller := CertificateSigningRequestReconciler{ Client: fakeClient, + ConfigClient: NewFakeConfigClient(fakeClient), Scheme: scheme, ClusterResourceNamespace: tc.clusterResourceNamespace, SignerBuilder: tc.signerBuilder, diff --git a/internal/controllers/fake_configclient_test.go b/internal/controllers/fake_configclient_test.go new file mode 100644 index 0000000..70e810e --- /dev/null +++ b/internal/controllers/fake_configclient_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package controllers + +import ( + "context" + "github.com/Keyfactor/k8s-csr-signer/pkg/util" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// FakeConfigClient is a fake implementation of the util.ConfigClient interface +// It forwards requests destined for the Kubernetes API server implemented by +// the util.ConfigClient interface to a fake Kubernetes API server implemented +// by the client.Client interface. + +// Force the compiler to check that FakeConfigClient implements the util.ConfigClient interface +var _ util.ConfigClient = &FakeConfigClient{} + +type FakeConfigClient struct { + client client.Client + ctx context.Context +} + +// NewFakeConfigClient uses the +func NewFakeConfigClient(fakeControllerRuntimeClient client.Client) util.ConfigClient { + return &FakeConfigClient{ + client: fakeControllerRuntimeClient, + } +} + +func (f FakeConfigClient) SetContext(ctx context.Context) { + f.ctx = ctx +} + +func (f FakeConfigClient) GetConfigMap(name types.NamespacedName, out *corev1.ConfigMap) error { + return f.client.Get(f.ctx, name, out) +} + +func (f FakeConfigClient) GetSecret(name types.NamespacedName, out *corev1.Secret) error { + return f.client.Get(f.ctx, name, out) +} diff --git a/main.go b/main.go index 0f949f1..6bf48ed 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "errors" "flag" "github.com/Keyfactor/k8s-csr-signer/internal/controllers" @@ -97,6 +98,12 @@ func main() { os.Exit(1) } + ctx := context.Background() + configClient, err := util.NewConfigClient(ctx) + if err != nil { + setupLog.Error(err, "error creating config client") + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, HealthProbeBindAddress: probeAddr, @@ -130,6 +137,7 @@ func main() { if err = (&controllers.CertificateSigningRequestReconciler{ Client: mgr.GetClient(), + ConfigClient: configClient, Scheme: mgr.GetScheme(), SignerBuilder: commandSignerBuilder, ClusterResourceNamespace: clusterResourceNamespace, diff --git a/pkg/util/configclient.go b/pkg/util/configclient.go new file mode 100644 index 0000000..f8f9944 --- /dev/null +++ b/pkg/util/configclient.go @@ -0,0 +1,152 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package util + +import ( + "context" + "fmt" + authv1 "k8s.io/api/authorization/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" +) + +type ConfigClient interface { + SetContext(ctx context.Context) + GetConfigMap(name types.NamespacedName, out *corev1.ConfigMap) error + GetSecret(name types.NamespacedName, out *corev1.Secret) error +} + +type configClient struct { + ctx context.Context + logger klog.Logger + client kubernetes.Interface + accessCache map[string]bool + + verifyAccessFunc func(apiResource string, resource types.NamespacedName) error +} + +func NewConfigClient(ctx context.Context) (ConfigClient, error) { + config := ctrl.GetConfigOrDie() + + // Create the clientset + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to create clientset: %w", err) + } + + client := &configClient{ + client: clientset, + accessCache: make(map[string]bool), + ctx: ctx, + logger: klog.NewKlogr(), + } + + client.verifyAccessFunc = client.verifyAccessToResource + + return client, nil +} + +func (c *configClient) SetContext(ctx context.Context) { + c.ctx = ctx + c.logger = klog.FromContext(ctx) +} + +func (c *configClient) verifyAccessToResource(apiResource string, resource types.NamespacedName) error { + verbs := []string{"get", "list", "watch"} + + for _, verb := range verbs { + ssar := &authv1.SelfSubjectAccessReview{ + Spec: authv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authv1.ResourceAttributes{ + Name: resource.Name, + Namespace: resource.Namespace, + + Group: "", + Resource: apiResource, + Verb: verb, + }, + }, + } + + ssar, err := c.client.AuthorizationV1().SelfSubjectAccessReviews().Create(c.ctx, ssar, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create SelfSubjectAccessReview to check access to %s for verb %q: %w", apiResource, verb, err) + } + + if !ssar.Status.Allowed { + return fmt.Errorf("client does not have access to %s called %q for verb %q, reason: %v", apiResource, resource.String(), verb, ssar.Status.String()) + } + } + + c.logger.Info(fmt.Sprintf("Client has access to %s called %q", apiResource, resource.String())) + + return nil +} + +func (c *configClient) GetConfigMap(name types.NamespacedName, out *corev1.ConfigMap) error { + if c == nil { + return fmt.Errorf("config client is nil") + } + + // Check if the client has access to the configmap resource + if ok, _ := c.accessCache[name.String()]; !ok { + err := c.verifyAccessFunc("configmaps", name) + if err != nil { + return err + } + c.accessCache[name.String()] = true + } + + // Get the configmap + configmap, err := c.client.CoreV1().ConfigMaps(name.Namespace).Get(c.ctx, name.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + // Copy the configmap into the out parameter + configmap.DeepCopyInto(out) + return nil +} + +func (c *configClient) GetSecret(name types.NamespacedName, out *corev1.Secret) error { + if c == nil { + return fmt.Errorf("config client is nil") + } + + // Check if the client has access to the secret resource + if ok, _ := c.accessCache[name.String()]; !ok { + err := c.verifyAccessFunc("secrets", name) + if err != nil { + return err + } + c.accessCache[name.String()] = true + } + + // Get the secret + secret, err := c.client.CoreV1().Secrets(name.Namespace).Get(c.ctx, name.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + // Copy the secret into the out parameter + secret.DeepCopyInto(out) + return nil +} diff --git a/pkg/util/configclient_test.go b/pkg/util/configclient_test.go new file mode 100644 index 0000000..e57f922 --- /dev/null +++ b/pkg/util/configclient_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2023 The Keyfactor Command Authors. + +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. +*/ + +package util + +import ( + "context" + logrtesting "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/fake" + ctrl "sigs.k8s.io/controller-runtime" + "testing" +) + +func TestConfigClient(t *testing.T) { + var err error + + // Define namespaced names for test objects + configMapName := types.NamespacedName{Name: "test-configmap", Namespace: "default"} + secretName := types.NamespacedName{Name: "test-secret", Namespace: "default"} + + // Create and inject fake ConfigMap + testConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: configMapName.Name, Namespace: configMapName.Namespace}, + } + + // Create and inject fake Secret + testSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: secretName.Name, Namespace: secretName.Namespace}, + } + + // Create a fake clientset with the test objects + clientset := fake.NewSimpleClientset([]runtime.Object{ + testConfigMap, + testSecret, + }...) + + // We can't test NewConfigClient unless we can mock ctrl.GetConfigOrDie() and kubernetes.NewForConfig() + // So we'll just test the methods that use the clientset + + // Create a ConfigClient + client := &configClient{ + client: clientset, + accessCache: make(map[string]bool), + } + + // The fake client doesn't implement authorization.k8s.io/v1 SelfSubjectAccessReview + // So we'll mock the verifyAccessFunc + client.verifyAccessFunc = func(apiResource string, resource types.NamespacedName) error { + return nil + } + + // Setup logging for test environment by setting the context + client.SetContext(ctrl.LoggerInto(context.TODO(), logrtesting.New(t))) + + t.Run("GetConfigMap", func(t *testing.T) { + // Test GetConfigMap + var out corev1.ConfigMap + err = client.GetConfigMap(configMapName, &out) + assert.NoError(t, err) + assert.Equal(t, testConfigMap, &out) + }) + + t.Run("GetSecret", func(t *testing.T) { + // Test GetSecret + var out corev1.Secret + err = client.GetSecret(secretName, &out) + assert.NoError(t, err) + assert.Equal(t, testSecret, &out) + }) +} From 9674170b0d0de3b0eaa934e05ecdc7693a1e8165 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 6 Dec 2023 16:08:00 -0700 Subject: [PATCH 09/11] fix(lint): Run linters --- internal/controllers/fake_configclient_test.go | 6 +++--- pkg/util/configclient.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/controllers/fake_configclient_test.go b/internal/controllers/fake_configclient_test.go index 70e810e..3479b6f 100644 --- a/internal/controllers/fake_configclient_test.go +++ b/internal/controllers/fake_configclient_test.go @@ -44,14 +44,14 @@ func NewFakeConfigClient(fakeControllerRuntimeClient client.Client) util.ConfigC } } -func (f FakeConfigClient) SetContext(ctx context.Context) { +func (f *FakeConfigClient) SetContext(ctx context.Context) { f.ctx = ctx } -func (f FakeConfigClient) GetConfigMap(name types.NamespacedName, out *corev1.ConfigMap) error { +func (f *FakeConfigClient) GetConfigMap(name types.NamespacedName, out *corev1.ConfigMap) error { return f.client.Get(f.ctx, name, out) } -func (f FakeConfigClient) GetSecret(name types.NamespacedName, out *corev1.Secret) error { +func (f *FakeConfigClient) GetSecret(name types.NamespacedName, out *corev1.Secret) error { return f.client.Get(f.ctx, name, out) } diff --git a/pkg/util/configclient.go b/pkg/util/configclient.go index f8f9944..990e9a1 100644 --- a/pkg/util/configclient.go +++ b/pkg/util/configclient.go @@ -107,7 +107,7 @@ func (c *configClient) GetConfigMap(name types.NamespacedName, out *corev1.Confi } // Check if the client has access to the configmap resource - if ok, _ := c.accessCache[name.String()]; !ok { + if _, ok := c.accessCache[name.String()]; !ok { err := c.verifyAccessFunc("configmaps", name) if err != nil { return err @@ -132,7 +132,7 @@ func (c *configClient) GetSecret(name types.NamespacedName, out *corev1.Secret) } // Check if the client has access to the secret resource - if ok, _ := c.accessCache[name.String()]; !ok { + if _, ok := c.accessCache[name.String()]; !ok { err := c.verifyAccessFunc("secrets", name) if err != nil { return err From 9dad741ee3af4f54ec61ef3935e965db06fb42f0 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Thu, 14 Dec 2023 10:51:38 -0700 Subject: [PATCH 10/11] chore(comments): Write function comments and update license header --- .DS_Store | Bin 6148 -> 8196 bytes .../certificatesigningrequest_controller.go | 8 +++- ...rtificatesigningrequest_controller_test.go | 2 +- .../controllers/fake_configclient_test.go | 2 +- internal/controllers/fake_signer_test.go | 2 +- internal/signer/signer.go | 41 ++++++++++++++++++ internal/signer/signer_test.go | 2 +- main.go | 2 +- pkg/util/configclient.go | 2 +- pkg/util/configclient_test.go | 2 +- pkg/util/util.go | 2 +- 11 files changed, 56 insertions(+), 9 deletions(-) diff --git a/.DS_Store b/.DS_Store index af284a001acffc5e71386f5ff8959593769431ff..5c90b4be8f0478c5de3a9ccee1ff420438646c7b 100644 GIT binary patch literal 8196 zcmeHMO=}ZD7=9*+ZKPOwtcVa1rHF=BdTS4BO@)G3q5gm-X-ra+Y?`)$7g<5DC$$%Q z5PQ*!c<>*1D0uVW*@IrZdiEfw&wMnQ$!2>~0-b@GcX!^eXP(*au^Uuv}_{CEicOto?*%z%$?(@CfoSC0Ll!fRpYh#=}(!H6O@6Gk(Qic6WWzhy9!fc z2va!-?M{7Q>?19;JA}&2I3}|&6^by0hsY^&h(Jp}dImfLaRy}VoNFLsP(SSXlp0NGzZmZx4m|0v^heVgIhk@|Mciao9}j2_}c z4cjO=*>U+HZTc*144LVZmGx%|L(Hm&B-zTrqhCfRh^LM49d=vFM>4Y~dnZWde*Slx;K+;(?1`=TyJm#xOs7p|-49b7I)Lmt^DM z5YrMzhB!i$vFT!1y<_X_{NML3efX;LcYW>LgY-^bL^AJb?o$g}+^H%$DDtXK94%Zy zlXorN-KB1m^LeaOQs?eys>&$)*55$38?i(GA{y?B4(4eev$0opX7BaxdA+g$cD+45 z%FbD-J`hK76HB;`Mcl#)7Ts0gJrBxpYZYmv_Jd8Ans~=IOAFe z9d0uypTkXZ)nwKNDsI-%jefnI`^~#oSHB%ThgJ;xSxB;Raj4$~Be>7~ZmFlSW8+Bu z919c Date: Thu, 14 Dec 2023 10:59:29 -0700 Subject: [PATCH 11/11] chore(comments): Comments for SSAR tasks --- pkg/util/configclient.go | 10 ++++++++++ pkg/util/util.go | 2 ++ 2 files changed, 12 insertions(+) diff --git a/pkg/util/configclient.go b/pkg/util/configclient.go index 2aa3582..e6782f7 100644 --- a/pkg/util/configclient.go +++ b/pkg/util/configclient.go @@ -28,6 +28,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ) +// ConfigClient is an interface for a K8s REST client. type ConfigClient interface { SetContext(ctx context.Context) GetConfigMap(name types.NamespacedName, out *corev1.ConfigMap) error @@ -43,6 +44,7 @@ type configClient struct { verifyAccessFunc func(apiResource string, resource types.NamespacedName) error } +// NewConfigClient creates a new K8s REST client using the configuration from the controller-runtime. func NewConfigClient(ctx context.Context) (ConfigClient, error) { config := ctrl.GetConfigOrDie() @@ -64,11 +66,15 @@ func NewConfigClient(ctx context.Context) (ConfigClient, error) { return client, nil } +// SetContext sets the context for the client. func (c *configClient) SetContext(ctx context.Context) { c.ctx = ctx c.logger = klog.FromContext(ctx) } +// verifyAccessToResource verifies that the client has access to a given resource in a given namespace +// by creating a SelfSubjectAccessReview. This is done to avoid errors when the client does not have +// access to the resource. func (c *configClient) verifyAccessToResource(apiResource string, resource types.NamespacedName) error { verbs := []string{"get", "list", "watch"} @@ -101,6 +107,7 @@ func (c *configClient) verifyAccessToResource(apiResource string, resource types return nil } +// GetConfigMap gets the configmap with the given name and namespace and copies it into the out parameter. func (c *configClient) GetConfigMap(name types.NamespacedName, out *corev1.ConfigMap) error { if c == nil { return fmt.Errorf("config client is nil") @@ -108,6 +115,8 @@ func (c *configClient) GetConfigMap(name types.NamespacedName, out *corev1.Confi // Check if the client has access to the configmap resource if _, ok := c.accessCache[name.String()]; !ok { + // If this is the first time the client is accessing the resource and it does have + // permission, add it to the access cache so that it does not need to be checked again. err := c.verifyAccessFunc("configmaps", name) if err != nil { return err @@ -126,6 +135,7 @@ func (c *configClient) GetConfigMap(name types.NamespacedName, out *corev1.Confi return nil } +// GetSecret gets the secret with the given name and namespace and copies it into the out parameter. func (c *configClient) GetSecret(name types.NamespacedName, out *corev1.Secret) error { if c == nil { return fmt.Errorf("config client is nil") diff --git a/pkg/util/util.go b/pkg/util/util.go index c5f050f..b0c12e2 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -54,6 +54,7 @@ func IsCertificateRequestApproved(csr certificates.CertificateSigningRequest) bo return approved && !denied } +// getCertApprovalCondition is a helper function that determines if a certificate request has been approved or denied func getCertApprovalCondition(status certificates.CertificateSigningRequestStatus) (approved bool, denied bool) { for _, c := range status.Conditions { if c.Type == certificates.CertificateApproved { @@ -84,6 +85,7 @@ func CompileCertificatesToPemBytes(certificates []*x509.Certificate) ([]byte, er return []byte(leafAndChain.String()), nil } +// DecodePEMBytes takes a byte array containing PEM encoded data and returns a slice of PEM blocks and a private key PEM block func DecodePEMBytes(buf []byte) ([]*pem.Block, *pem.Block) { var privKey *pem.Block var certs []*pem.Block