From be718e2b6186294e2a84d7cd4789f096e44fc545 Mon Sep 17 00:00:00 2001 From: jannfis Date: Mon, 22 Jun 2020 18:21:53 +0200 Subject: [PATCH] feat: GPG commit signature verification (#2492) (#3242) * Add initial primitives and tests for GPG related operations * More tests and test documentation * Move gpg primitives to own module * Add initial primitives for running git verify-commit and tests * Improve and better comment test * Implement VerifyCommitSignature() primitive for metrics wrapper * More commentary * Make reposerver verify gpg signatures when generating manifests * Make signature validation optional * Forbid use of local manifests when signature verification is enabled * Introduce new signatureKeys field in project CRD * Initial support for only syncing against signed revisions * Updates to GnuPG primitives and more test cases * Move signature verification to correct place and add tests * Add signature verification result to revision metadata and display it in UI * Add more primitives and move out some stuff to common module * Add more testdata * Add key management primitives to ArgoDB * Move type GnuPGPublicKey to appsv1 package * Add const ArgoCDGPGKeysConfigMapName * Handle key operations with appsv1.GnuPGPublicKey * Add initial API for managing GPG keys * Remove deprecated code * Add primitives for adding public keys to configuration * Change semantics of ValidateGPGKeys to return more key information * Add key import functionality to public key API * Fix code quirks reported by linter * More code quirks fixes * Fix test * Add primitives for deleting keys from configuration * Add delete key operation to API and CLI * Cosmetics * Implement logic to sync configuration to keyring in repo-server * Add IsGPGEnabled() primitive and also update trustdb on ownertrust changes * Use gpg.IsGPGEnabled() instead of custom test * Remove all keyring manipulating methods from DB * Cosmetics/comments * Require grpc methods from argoproj pkg * Enable setting config path via ARGOCD_GPG_DATA_PATH * Allow "no" and any cases in ARGOCD_GPG_ENABLED * Enable GPG feature on start and start-e2e and set required environment * Cosmetics/comments * Cosmetics and commentary * Update API documentation * Fix comment * Only run GPG related operations if GPG is enabled * Allow setting ARGOCD_GPG_ENABLE from the environment * Create GPG ConfigMap resource during installation * Use function instead of constant to get the watcher path * Re-watch source path in case it gets recreated. Also, error on finish * Add End-to-End tests for GPG commit verification * Introduce SignatureKey type for AppProject CRD * Fix merge error from previous commit * Adapt test for additional manifest (argocd-gpg-keys-cm.yaml) * Fix linter issues * Adapt CircleCI configuration to enable running tests * Add wrapper scripts for git and gpg * Sigh. * Display gpg version in CircleCI * Install gnupg2 and link it to gpg in CI * Try to install gnupg2 in CircleCI image * More CircleCI tweaks * # This is a combination of 10 commits. # This is the 1st commit message: Containerize tests - test cycle # This is the commit message #2: adapt working directory # This is the commit message #3: Build before running tests (so we might have a cache) # This is the commit message #4: Test limiting parallelism # This is the commit message #5: Remove unbound variable # This is the commit message #6: Decrease parallelism to find out limit # This is the commit message #7: Use correct flag # This is the commit message #8: Update Docker image # This is the commit message #9: Remove build phase and increase parallelism # This is the commit message #10: Further increase parallelism * Dockerize toolchain * Add new targets to Makefile * Codegen * Properly handle permissions for E2E tests * Remove gnupg2 installation from CircleCI configuration * Limit parallelism of build * Fix Yarn lint * Retrigger CI for possible flaky test * Codegen * Remove duplicate target in Makefile * Pull in pager from dep ensure -v * Adapt to gitops-engine changes and codegen * Use new health package for health status constants * Add GPG methods to ArgoDB mock module * Fix possible nil pointer dereference * Fix linter issue in imports * Introduce RBAC resource type 'gpgkeys' and adapt policies * Use ARGOCD_GNUPGHOME instead of GNUPGHOME for subsystem configuration Also remove some deprecated unit tests. * Also register GPG keys API with gRPC-GW * Update from codegen * Update GPG key API * Add web UI to manage GPG keys * Lint updates * Change wording * Add some plausibility checks for supplied data on key creation * Update from codegen * Re-allow binary keys and move check for ASCII armoured to UI * Make yarn lint happy * Add editing signature keys for projects in UI * Add ability to configure signature keys for project in CLI * Change default value to use for GNUPGHOME * Do not include data section in default gpg keys CM * Adapt Docker image for GnuPG feature * Add required configuration to installation manifests * Add add-signature-key and remove-signature-key commands to project CLI * Fix typo * Add initial user documentation for GnuPG verification * Fix role name - oops * Mention required RBAC roles in docs * Support GPG verification of git annotated tags as well * Ensure CLI can build succesfully * Better support verification on tags * Print key type in upper case * Update user documentation * Correctly disable GnuPG verification if ARGOCD_GPG_ENABLE=false * Clarify that this feature is only available with Git repositories * codegen * Move verification code to own function * Remove deprecated check * Make things more developer friendly when running locally * Enable GPG feature by default, and don't require ARGOCD_GNUPGHOME to be set * Revert changes to manifests to reflect default enable state * Codegen --- Dockerfile | 8 +- Makefile | 19 +- Procfile | 8 +- assets/builtin-policy.csv | 3 + assets/swagger.json | 212 +- cmd/argocd-repo-server/main.go | 25 +- cmd/argocd/commands/gpg.go | 162 ++ cmd/argocd/commands/project.go | 120 +- cmd/argocd/commands/root.go | 1 + common/common.go | 14 + controller/state.go | 78 +- controller/state_test.go | 279 +++ docs/user-guide/gpg-verification.md | 305 +++ go.mod | 1 + hack/dev-mounter/main.go | 28 +- hack/generate-proto.sh | 2 +- hack/git-verify-wrapper.sh | 46 + hack/gpg-wrapper.sh | 19 + manifests/base/config/argocd-gpg-keys-cm.yaml | 7 + manifests/base/config/kustomization.yaml | 1 + .../argocd-repo-server-deployment.yaml | 5 + manifests/crds/appproject-crd.yaml | 14 + manifests/ha/install.yaml | 27 + manifests/ha/namespace-install.yaml | 27 + manifests/install.yaml | 27 + manifests/namespace-install.yaml | 27 + mkdocs.yml | 1 + pkg/apiclient/apiclient.go | 20 + pkg/apiclient/gpgkey/gpgkey.pb.go | 1180 +++++++++++ pkg/apiclient/gpgkey/gpgkey.pb.gw.go | 288 +++ pkg/apis/api-rules/violation_exceptions.list | 2 + pkg/apis/application/v1alpha1/generated.pb.go | 1838 ++++++++++++----- pkg/apis/application/v1alpha1/generated.proto | 41 + .../application/v1alpha1/openapi_generated.go | 135 +- pkg/apis/application/v1alpha1/types.go | 33 + .../v1alpha1/zz_generated.deepcopy.go | 59 + reposerver/apiclient/repository.pb.go | 282 ++- reposerver/gpgwatcher.go | 71 + reposerver/metrics/gitwrapper.go | 4 + reposerver/repository/repository.go | 44 +- reposerver/repository/repository.proto | 4 + reposerver/repository/repository_test.go | 72 +- server/application/application.go | 6 + server/gpgkey/gpgkey.go | 120 ++ server/gpgkey/gpgkey.proto | 62 + server/rbacpolicy/rbacpolicy.go | 1 + server/server.go | 6 + test/container/Procfile | 2 +- test/e2e/app_management_test.go | 46 + test/e2e/fixture/app/actions.go | 6 + test/e2e/fixture/app/context.go | 16 + test/e2e/fixture/fixture.go | 38 + test/e2e/fixture/gpgkeys/gpgkeys.go | 32 + test/fixture/gpg/D56C4FCA57A46444 | 31 + test/fixture/gpg/signingkey.asc | Bin 0 -> 2560 bytes test/testutil.go | 10 + .../revision-metadata-rows.tsx | 4 + .../revision-metadata-panel.tsx | 4 +- .../components/gpgkeys-list/gpgkeys-list.scss | 44 + .../components/gpgkeys-list/gpgkeys-list.tsx | 196 ++ .../project-details/project-details.tsx | 23 + .../project-edit-panel/project-edit-panel.tsx | 26 + .../components/settings-container.tsx | 2 + .../settings-overview/settings-overview.tsx | 5 + ui/src/app/shared/models.ts | 16 + ui/src/app/shared/services/gpgkey-service.ts | 26 + ui/src/app/shared/services/index.ts | 5 +- .../app/shared/services/projects-service.ts | 2 + util/db/db.go | 7 + util/db/gpgkeys.go | 142 ++ util/db/gpgkeys_test.go | 304 +++ util/db/mocks/ArgoDB.go | 71 +- util/git/client.go | 17 + util/git/git_test.go | 39 + util/git/mocks/Client.go | 28 +- util/gpg/gpg.go | 682 ++++++ util/gpg/gpg_test.go | 580 ++++++ util/gpg/testdata/bad_signature_bad.txt | 3 + util/gpg/testdata/bad_signature_badkeyid.txt | 3 + .../gpg/testdata/bad_signature_malformed1.txt | 3 + .../gpg/testdata/bad_signature_malformed2.txt | 3 + .../gpg/testdata/bad_signature_malformed3.txt | 3 + .../testdata/bad_signature_manipulated.txt | 6 + util/gpg/testdata/bad_signature_nodata.txt | 3 + util/gpg/testdata/bad_signature_preeof1.txt | 2 + util/gpg/testdata/bad_signature_preeof2.txt | 1 + util/gpg/testdata/garbage.asc | 16 + util/gpg/testdata/github.asc | 17 + util/gpg/testdata/good_signature.txt | 3 + util/gpg/testdata/janedoe.asc | 30 + util/gpg/testdata/johndoe.asc | 30 + util/gpg/testdata/multi.asc | 56 + util/gpg/testdata/multi2.asc | 73 + util/gpg/testdata/unknown_signature.txt | 3 + util/settings/settings.go | 21 + 95 files changed, 7781 insertions(+), 633 deletions(-) create mode 100644 cmd/argocd/commands/gpg.go create mode 100644 docs/user-guide/gpg-verification.md create mode 100755 hack/git-verify-wrapper.sh create mode 100755 hack/gpg-wrapper.sh create mode 100644 manifests/base/config/argocd-gpg-keys-cm.yaml create mode 100644 pkg/apiclient/gpgkey/gpgkey.pb.go create mode 100644 pkg/apiclient/gpgkey/gpgkey.pb.gw.go create mode 100644 reposerver/gpgwatcher.go create mode 100644 server/gpgkey/gpgkey.go create mode 100644 server/gpgkey/gpgkey.proto create mode 100644 test/e2e/fixture/gpgkeys/gpgkeys.go create mode 100644 test/fixture/gpg/D56C4FCA57A46444 create mode 100644 test/fixture/gpg/signingkey.asc create mode 100644 ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.scss create mode 100644 ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.tsx create mode 100644 ui/src/app/shared/services/gpgkey-service.ts create mode 100644 util/db/gpgkeys.go create mode 100644 util/db/gpgkeys_test.go create mode 100644 util/gpg/gpg.go create mode 100644 util/gpg/gpg_test.go create mode 100644 util/gpg/testdata/bad_signature_bad.txt create mode 100644 util/gpg/testdata/bad_signature_badkeyid.txt create mode 100644 util/gpg/testdata/bad_signature_malformed1.txt create mode 100644 util/gpg/testdata/bad_signature_malformed2.txt create mode 100644 util/gpg/testdata/bad_signature_malformed3.txt create mode 100644 util/gpg/testdata/bad_signature_manipulated.txt create mode 100644 util/gpg/testdata/bad_signature_nodata.txt create mode 100644 util/gpg/testdata/bad_signature_preeof1.txt create mode 100644 util/gpg/testdata/bad_signature_preeof2.txt create mode 100644 util/gpg/testdata/garbage.asc create mode 100644 util/gpg/testdata/github.asc create mode 100644 util/gpg/testdata/good_signature.txt create mode 100644 util/gpg/testdata/janedoe.asc create mode 100644 util/gpg/testdata/johndoe.asc create mode 100644 util/gpg/testdata/multi.asc create mode 100644 util/gpg/testdata/multi2.asc create mode 100644 util/gpg/testdata/unknown_signature.txt diff --git a/Dockerfile b/Dockerfile index 13f371848b6d7..f31808a62c3da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,12 +50,14 @@ RUN groupadd -g 999 argocd && \ chmod g=u /home/argocd && \ chmod g=u /etc/passwd && \ apt-get update && \ - apt-get install -y git git-lfs python3-pip tini && \ + apt-get install -y git git-lfs python3-pip tini gpg && \ apt-get clean && \ pip3 install awscli==1.18.80 && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* COPY hack/git-ask-pass.sh /usr/local/bin/git-ask-pass.sh +COPY hack/gpg-wrapper.sh /usr/local/bin/gpg-wrapper.sh +COPY hack/git-verify-wrapper.sh /usr/local/bin/git-verify-wrapper.sh COPY --from=builder /usr/local/bin/ks /usr/local/bin/ks COPY --from=builder /usr/local/bin/helm2 /usr/local/bin/helm2 COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm @@ -71,6 +73,10 @@ RUN mkdir -p /app/config/ssh && \ ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts RUN mkdir -p /app/config/tls +RUN mkdir -p /app/config/gpg/source && \ + mkdir -p /app/config/gpg/keys && \ + chown argocd /app/config/gpg/keys && \ + chmod 0700 /app/config/gpg/keys # workaround ksonnet issue https://github.com/ksonnet/ksonnet/issues/298 ENV USER=argocd diff --git a/Makefile b/Makefile index ed89f15bb9d9c..cf207829ca32b 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,8 @@ IMAGE_NAMESPACE?= STATIC_BUILD?=true # build development images DEV_IMAGE?=false +ARGOCD_GPG_ENABLED?=true +ARGOCD_E2E_APISERVER_PORT?=8080 override LDFLAGS += \ -X ${PACKAGE}.version=${VERSION} \ @@ -159,6 +161,8 @@ codegen: .PHONY: cli cli: clean-debug + rm -f ${DIST_DIR}/${CLI_NAME} + mkdir -p ${DIST_DIR} CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd .PHONY: cli-docker @@ -331,7 +335,7 @@ test-e2e: test-e2e-local: cli # NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system export GO111MODULE=off - NO_PROXY=* ./hack/test.sh -timeout 15m -v ./test/e2e + ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout 15m -v ./test/e2e # Spawns a shell in the test server container for debugging purposes debug-test-server: @@ -354,9 +358,17 @@ start-e2e-local: kubectl create ns argocd-e2e || true kubectl config set-context --current --namespace=argocd-e2e kustomize build test/manifests/base | kubectl apply -f - + # Create GPG keys and source directories + if test -d /tmp/argo-e2e/app/config/gpg; then rm -rf /tmp/argo-e2e/app/config/gpg/*; fi + mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys + mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source + if test "$(USER_ID)" != ""; then chown -R "$(USER_ID)" /tmp/argo-e2e; fi # set paths for locally managed ssh known hosts and tls certs data ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \ ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \ + ARGOCD_GPG_DATA_PATH=/tmp/argo-e2e/app/config/gpg/source \ + ARGOCD_GNUPGHOME=/tmp/argo-e2e/app/config/gpg/keys \ + ARGOCD_GPG_ENABLED=true \ ARGOCD_E2E_DISABLE_AUTH=false \ ARGOCD_ZJWT_FEATURE_FLAG=always \ ARGOCD_IN_CI=$(ARGOCD_IN_CI) \ @@ -383,8 +395,13 @@ start-local: mod-vendor-local # check we can connect to Docker to start Redis killall goreman || true kubectl create ns argocd || true + rm -rf /tmp/argocd-local + mkdir -p /tmp/argocd-local + mkdir -p /tmp/argocd-local/gpg/keys && chmod 0700 /tmp/argocd-local/gpg/keys + mkdir -p /tmp/argocd-local/gpg/source ARGOCD_ZJWT_FEATURE_FLAG=always \ ARGOCD_IN_CI=false \ + ARGOCD_GPG_ENABLED=true \ ARGOCD_E2E_TEST=false \ goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START} diff --git a/Procfile b/Procfile index 2c96e1ebc634c..09a1adc1cb2fb 100644 --- a/Procfile +++ b/Procfile @@ -1,8 +1,8 @@ -controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:=/tmp/argocd/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:=/tmp/argocd/ssh} go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}" -api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:=/tmp/argocd/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:=/tmp/argocd/ssh} go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --staticassets ui/dist/app" +controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}" +api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --staticassets ui/dist/app" dex: sh -c "go run github.com/argoproj/argo-cd/cmd/argocd-util gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.22.0 serve /dex.yaml" redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.8-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379} -repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:=/tmp/argocd/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:=/tmp/argocd/ssh} go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}" +repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}" ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start' git-server: test/fixture/testrepos/start-git.sh -dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:=/tmp/argocd/tls} \ No newline at end of file +dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} diff --git a/assets/builtin-policy.csv b/assets/builtin-policy.csv index ba9b77ab04500..89f6efd249d34 100644 --- a/assets/builtin-policy.csv +++ b/assets/builtin-policy.csv @@ -12,6 +12,7 @@ p, role:readonly, clusters, get, *, allow p, role:readonly, repositories, get, *, allow p, role:readonly, projects, get, *, allow p, role:readonly, accounts, get, *, allow +p, role:readonly, gpgkeys, get, *, allow p, role:admin, applications, create, */*, allow p, role:admin, applications, update, */*, allow @@ -32,6 +33,8 @@ p, role:admin, projects, create, *, allow p, role:admin, projects, update, *, allow p, role:admin, projects, delete, *, allow p, role:admin, accounts, update, *, allow +p, role:admin, gpgkeys, create, *, allow +p, role:admin, gpgkeys, delete, *, allow g, role:admin, role:readonly g, admin, role:admin diff --git a/assets/swagger.json b/assets/swagger.json index 4e7d37aca5c73..36997ff4a2e26 100644 --- a/assets/swagger.json +++ b/assets/swagger.json @@ -124,7 +124,7 @@ "tags": [ "AccountService" ], - "operationId": "CreateTokenMixin9", + "operationId": "CreateTokenMixin10", "parameters": [ { "type": "string", @@ -156,7 +156,7 @@ "tags": [ "AccountService" ], - "operationId": "DeleteTokenMixin9", + "operationId": "DeleteTokenMixin10", "parameters": [ { "type": "string", @@ -187,7 +187,7 @@ "ApplicationService" ], "summary": "List returns list of applications", - "operationId": "ListMixin8", + "operationId": "List", "parameters": [ { "type": "string", @@ -237,7 +237,7 @@ "ApplicationService" ], "summary": "Create creates an application", - "operationId": "CreateMixin8", + "operationId": "Create", "parameters": [ { "name": "body", @@ -264,7 +264,7 @@ "ApplicationService" ], "summary": "Update updates an application", - "operationId": "UpdateMixin8", + "operationId": "Update", "parameters": [ { "type": "string", @@ -395,7 +395,7 @@ "ApplicationService" ], "summary": "Get returns an application by name", - "operationId": "GetMixin8", + "operationId": "GetMixin1", "parameters": [ { "type": "string", @@ -445,7 +445,7 @@ "ApplicationService" ], "summary": "Delete deletes an application", - "operationId": "DeleteMixin8", + "operationId": "Delete", "parameters": [ { "type": "string", @@ -1084,7 +1084,7 @@ "ClusterService" ], "summary": "List returns list of clusters", - "operationId": "List", + "operationId": "ListMixin5", "parameters": [ { "type": "string", @@ -1111,7 +1111,7 @@ "ClusterService" ], "summary": "Create creates a cluster", - "operationId": "Create", + "operationId": "CreateMixin5", "parameters": [ { "name": "body", @@ -1138,7 +1138,7 @@ "ClusterService" ], "summary": "Update updates a cluster", - "operationId": "Update", + "operationId": "UpdateMixin5", "parameters": [ { "type": "string", @@ -1171,7 +1171,7 @@ "ClusterService" ], "summary": "Get returns a cluster by server address", - "operationId": "GetMixin2", + "operationId": "GetMixin5", "parameters": [ { "type": "string", @@ -1199,7 +1199,7 @@ "ClusterService" ], "summary": "Delete deletes a cluster", - "operationId": "Delete", + "operationId": "DeleteMixin5", "parameters": [ { "type": "string", @@ -1243,6 +1243,96 @@ } } }, + "/api/v1/gpgkeys": { + "get": { + "tags": [ + "GPGKeyService" + ], + "summary": "List all available repository certificates", + "operationId": "ListMixin2", + "parameters": [ + { + "type": "string", + "description": "The GPG key ID to query for.", + "name": "keyID", + "in": "query" + } + ], + "responses": { + "200": { + "description": "(empty)", + "schema": { + "$ref": "#/definitions/v1alpha1GnuPGPublicKeyList" + } + } + } + }, + "post": { + "tags": [ + "GPGKeyService" + ], + "summary": "Create one or more GPG public keys in the server's configuration", + "operationId": "CreateMixin2", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1alpha1GnuPGPublicKey" + } + } + ], + "responses": { + "200": { + "description": "(empty)", + "schema": { + "$ref": "#/definitions/gpgkeyGnuPGPublicKeyCreateResponse" + } + } + } + }, + "delete": { + "tags": [ + "GPGKeyService" + ], + "summary": "Delete specified GPG public key from the server's configuration", + "operationId": "DeleteMixin2", + "responses": { + "200": { + "description": "(empty)", + "schema": { + "$ref": "#/definitions/gpgkeyGnuPGPublicKeyResponse" + } + } + } + } + }, + "/api/v1/gpgkeys/{keyID}": { + "get": { + "tags": [ + "GPGKeyService" + ], + "summary": "Get information about specified GPG public key from the server", + "operationId": "GetMixin2", + "parameters": [ + { + "type": "string", + "name": "keyID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "(empty)", + "schema": { + "$ref": "#/definitions/v1alpha1GnuPGPublicKey" + } + } + } + } + }, "/api/v1/projects": { "get": { "tags": [ @@ -1704,7 +1794,7 @@ "RepositoryService" ], "summary": "Get returns a repository or its credentials", - "operationId": "GetMixin3", + "operationId": "Get", "parameters": [ { "type": "string", @@ -1886,7 +1976,7 @@ "SessionService" ], "summary": "Create a new JWT for authentication and set a cookie if using HTTP.", - "operationId": "CreateMixin10", + "operationId": "CreateMixin11", "parameters": [ { "name": "body", @@ -1911,7 +2001,7 @@ "SessionService" ], "summary": "Delete an existing JWT cookie if using HTTP.", - "operationId": "DeleteMixin10", + "operationId": "DeleteMixin11", "responses": { "200": { "description": "(empty)", @@ -1945,7 +2035,7 @@ "SettingsService" ], "summary": "Get returns Argo CD settings", - "operationId": "Get", + "operationId": "GetMixin8", "responses": { "200": { "description": "(empty)", @@ -2440,6 +2530,26 @@ } } }, + "gpgkeyGnuPGPublicKeyCreateResponse": { + "type": "object", + "title": "Response to a public key creation request", + "properties": { + "created": { + "$ref": "#/definitions/v1alpha1GnuPGPublicKeyList" + }, + "skipped": { + "type": "array", + "title": "List of key IDs that haven been skipped because they already exist on the server", + "items": { + "type": "string" + } + } + } + }, + "gpgkeyGnuPGPublicKeyResponse": { + "type": "object", + "title": "Generic (empty) response for GPG public key CRUD requests" + }, "oidcClaim": { "type": "object", "properties": { @@ -2687,6 +2797,10 @@ }, "sourceType": { "type": "string" + }, + "verifyResult": { + "type": "string", + "title": "Raw response of git verify-commit operation (always the empty string for Helm)" } } }, @@ -3247,6 +3361,13 @@ "$ref": "#/definitions/v1alpha1ProjectRole" } }, + "signatureKeys": { + "type": "array", + "title": "List of PGP key IDs that commits to be synced to must be signed with", + "items": { + "$ref": "#/definitions/v1alpha1SignatureKey" + } + }, "sourceRepos": { "type": "array", "title": "SourceRepos contains list of repository URLs which can be used for deployment", @@ -3775,6 +3896,51 @@ } } }, + "v1alpha1GnuPGPublicKey": { + "type": "object", + "title": "GnuPGPublicKey is a representation of a GnuPG public key", + "properties": { + "fingerprint": { + "type": "string", + "title": "Fingerprint of the key" + }, + "keyData": { + "type": "string", + "title": "Key data" + }, + "keyID": { + "type": "string", + "title": "KeyID in hexadecimal string format" + }, + "owner": { + "type": "string", + "title": "Owner identification" + }, + "subType": { + "type": "string", + "title": "Key sub type (e.g. rsa4096)" + }, + "trust": { + "type": "string", + "title": "Trust level" + } + } + }, + "v1alpha1GnuPGPublicKeyList": { + "type": "object", + "title": "GnuPGPublicKeyList is a collection of GnuPGPublicKey objects", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/v1alpha1GnuPGPublicKey" + } + }, + "metadata": { + "$ref": "#/definitions/v1ListMeta" + } + } + }, "v1alpha1HealthStatus": { "type": "object", "properties": { @@ -4521,6 +4687,10 @@ "type": "string", "title": "the message associated with the revision,\nprobably the commit message,\nthis is truncated to the first newline or 64 characters (which ever comes first)" }, + "signatureInfo": { + "type": "string", + "title": "If revision was signed with GPG, and signature verification is enabled,\nthis contains a hint on the signer" + }, "tags": { "type": "array", "title": "tags on the revision,\nnote - tags can move from one revision to another", @@ -4530,6 +4700,16 @@ } } }, + "v1alpha1SignatureKey": { + "type": "object", + "title": "SignatureKey is the specification of a key required to verify commit signatures with", + "properties": { + "keyID": { + "type": "string", + "title": "The ID of the key in hexadecimal notation" + } + } + }, "v1alpha1SyncOperation": { "description": "SyncOperation contains sync operation details.", "type": "object", diff --git a/cmd/argocd-repo-server/main.go b/cmd/argocd-repo-server/main.go index 94874cd5235b2..bc3ce2d4a3537 100644 --- a/cmd/argocd-repo-server/main.go +++ b/cmd/argocd-repo-server/main.go @@ -19,14 +19,24 @@ import ( "github.com/argoproj/argo-cd/reposerver/metrics" cacheutil "github.com/argoproj/argo-cd/util/cache" "github.com/argoproj/argo-cd/util/cli" + "github.com/argoproj/argo-cd/util/gpg" "github.com/argoproj/argo-cd/util/tls" ) const ( // CLIName is the name of the CLI - cliName = "argocd-repo-server" + cliName = "argocd-repo-server" + gnuPGSourcePath = "/app/config/gpg/source" ) +func getGnuPGSourcePath() string { + if path := os.Getenv("ARGOCD_GPG_DATA_PATH"); path != "" { + return path + } else { + return gnuPGSourcePath + } +} + func newCommand() *cobra.Command { var ( logFormat string @@ -63,6 +73,19 @@ func newCommand() *cobra.Command { http.Handle("/metrics", metricsServer.GetHandler()) go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", metricsPort), nil)) }() + if gpg.IsGPGEnabled() { + log.Infof("Initializing GnuPG keyring at %s", common.GetGnuPGHomePath()) + err = gpg.InitializeGnuPG() + errors.CheckError(err) + + log.Infof("Populating GnuPG keyring with keys from %s", getGnuPGSourcePath()) + added, removed, err := gpg.SyncKeyRingFromDirectory(getGnuPGSourcePath()) + errors.CheckError(err) + log.Infof("Loaded %d (and removed %d) keys from keyring", len(added), len(removed)) + + go func() { errors.CheckError(reposerver.StartGPGWatcher(getGnuPGSourcePath())) }() + } + log.Infof("argocd-repo-server %s serving on %s", common.GetVersion(), listener.Addr()) stats.RegisterStackDumper() stats.StartStatsTicker(10 * time.Minute) diff --git a/cmd/argocd/commands/gpg.go b/cmd/argocd/commands/gpg.go new file mode 100644 index 0000000000000..d96a934186384 --- /dev/null +++ b/cmd/argocd/commands/gpg.go @@ -0,0 +1,162 @@ +package commands + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "strings" + "text/tabwriter" + + "github.com/argoproj/gitops-engine/pkg/utils/errors" + argoio "github.com/argoproj/gitops-engine/pkg/utils/io" + "github.com/spf13/cobra" + + argocdclient "github.com/argoproj/argo-cd/pkg/apiclient" + gpgkeypkg "github.com/argoproj/argo-cd/pkg/apiclient/gpgkey" + appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" +) + +// NewGPGCommand returns a new instance of an `argocd repo` command +func NewGPGCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "gpg", + Short: "Manage GPG keys used for signature verification", + Run: func(c *cobra.Command, args []string) { + c.HelpFunc()(c, args) + os.Exit(1) + }, + Example: ``, + } + command.AddCommand(NewGPGListCommand(clientOpts)) + command.AddCommand(NewGPGGetCommand(clientOpts)) + command.AddCommand(NewGPGAddCommand(clientOpts)) + command.AddCommand(NewGPGDeleteCommand(clientOpts)) + return command +} + +// NewGPGListCommand lists all configured public keys from the server +func NewGPGListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + output string + ) + var command = &cobra.Command{ + Use: "list", + Short: "List configured GPG public keys", + Run: func(c *cobra.Command, args []string) { + conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie() + defer argoio.Close(conn) + keys, err := gpgIf.List(context.Background(), &gpgkeypkg.GnuPGPublicKeyQuery{}) + errors.CheckError(err) + switch output { + case "yaml", "json": + err := PrintResourceList(keys.Items, output, false) + errors.CheckError(err) + case "wide", "": + printKeyTable(keys.Items) + default: + errors.CheckError(fmt.Errorf("unknown output format: %s", output)) + } + }, + } + command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide") + return command +} + +// NewGPGGetCommand retrieves a single public key from the server +func NewGPGGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + output string + ) + var command = &cobra.Command{ + Use: "get KEYID", + Short: "Get the GPG public key with ID from the server", + Run: func(c *cobra.Command, args []string) { + if len(args) != 1 { + errors.CheckError(fmt.Errorf("Missing KEYID argument")) + } + conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie() + defer argoio.Close(conn) + key, err := gpgIf.Get(context.Background(), &gpgkeypkg.GnuPGPublicKeyQuery{KeyID: args[0]}) + errors.CheckError(err) + switch output { + case "yaml", "json": + err := PrintResourceList(key, output, false) + errors.CheckError(err) + case "wide", "": + fmt.Printf("Key ID: %s\n", key.KeyID) + fmt.Printf("Key fingerprint: %s\n", key.Fingerprint) + fmt.Printf("Key subtype: %s\n", strings.ToUpper(key.SubType)) + fmt.Printf("Key owner: %s\n", key.Owner) + fmt.Printf("Key data follows until EOF:\n%s\n", key.KeyData) + default: + errors.CheckError(fmt.Errorf("unknown output format: %s", output)) + } + }, + } + command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide") + return command +} + +// NewGPGAddCommand adds a public key to the server's configuration +func NewGPGAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + fromFile string + ) + var command = &cobra.Command{ + Use: "add", + Short: "Adds a GPG public key to the server's keyring", + Run: func(c *cobra.Command, args []string) { + if fromFile == "" { + errors.CheckError(fmt.Errorf("--from is mandatory")) + } + keyData, err := ioutil.ReadFile(fromFile) + if err != nil { + errors.CheckError(err) + } + conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie() + defer argoio.Close(conn) + resp, err := gpgIf.Create(context.Background(), &gpgkeypkg.GnuPGPublicKeyCreateRequest{Publickey: &appsv1.GnuPGPublicKey{KeyData: string(keyData)}}) + errors.CheckError(err) + fmt.Printf("Created %d key(s) from input file", len(resp.Created.Items)) + if len(resp.Skipped) > 0 { + fmt.Printf(", and %d key(s) were skipped because they exist already", len(resp.Skipped)) + } + fmt.Printf(".\n") + }, + } + command.Flags().StringVarP(&fromFile, "from", "f", "", "Path to the file that contains the GPG public key to import") + return command + +} + +// NewGPGDeleteCommand removes a key from the server's keyring +func NewGPGDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "rm KEYID", + Short: "Removes a GPG public key from the server's keyring", + Run: func(c *cobra.Command, args []string) { + if len(args) != 1 { + errors.CheckError(fmt.Errorf("Missing KEYID argument")) + } + conn, gpgIf := argocdclient.NewClientOrDie(clientOpts).NewGPGKeyClientOrDie() + defer argoio.Close(conn) + _, err := gpgIf.Delete(context.Background(), &gpgkeypkg.GnuPGPublicKeyQuery{KeyID: args[0]}) + errors.CheckError(err) + fmt.Printf("Deleted key with key ID %s\n", args[0]) + }, + } + return command + +} + +// Print table of certificate info +func printKeyTable(keys []appsv1.GnuPGPublicKey) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "KEYID\tTYPE\tIDENTITY\n") + + for _, k := range keys { + fmt.Fprintf(w, "%s\t%s\t%s\n", k.KeyID, strings.ToUpper(k.SubType), k.Owner) + } + _ = w.Flush() +} diff --git a/cmd/argocd/commands/project.go b/cmd/argocd/commands/project.go index f0e4fac922ecc..43a0109f0fd43 100644 --- a/cmd/argocd/commands/project.go +++ b/cmd/argocd/commands/project.go @@ -28,12 +28,14 @@ import ( "github.com/argoproj/argo-cd/util/cli" "github.com/argoproj/argo-cd/util/config" "github.com/argoproj/argo-cd/util/git" + "github.com/argoproj/argo-cd/util/gpg" ) type projectOpts struct { description string destinations []string sources []string + signatureKeys []string orphanedResourcesEnabled bool orphanedResourcesWarn bool } @@ -60,6 +62,18 @@ func (opts *projectOpts) GetDestinations() []v1alpha1.ApplicationDestination { return destinations } +// TODO: Get configured keys and emit warning when a key is specified that is not configured +func (opts *projectOpts) GetSignatureKeys() []v1alpha1.SignatureKey { + signatureKeys := make([]v1alpha1.SignatureKey, 0) + for _, keyStr := range opts.signatureKeys { + if !gpg.IsShortKeyID(keyStr) && !gpg.IsLongKeyID(keyStr) { + log.Fatalf("'%s' is not a valid GnuPG key ID", keyStr) + } + signatureKeys = append(signatureKeys, v1alpha1.SignatureKey{KeyID: gpg.KeyID(keyStr)}) + } + return signatureKeys +} + // NewProjectCommand returns a new instance of an `argocd proj` command func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var command = &cobra.Command{ @@ -77,6 +91,8 @@ func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { command.AddCommand(NewProjectListCommand(clientOpts)) command.AddCommand(NewProjectSetCommand(clientOpts)) command.AddCommand(NewProjectEditCommand(clientOpts)) + command.AddCommand(NewProjectAddSignatureKeyCommand(clientOpts)) + command.AddCommand(NewProjectRemoveSignatureKeyCommand(clientOpts)) command.AddCommand(NewProjectAddDestinationCommand(clientOpts)) command.AddCommand(NewProjectRemoveDestinationCommand(clientOpts)) command.AddCommand(NewProjectAddSourceCommand(clientOpts)) @@ -94,6 +110,7 @@ func addProjFlags(command *cobra.Command, opts *projectOpts) { command.Flags().StringArrayVarP(&opts.destinations, "dest", "d", []string{}, "Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)") command.Flags().StringArrayVarP(&opts.sources, "src", "s", []string{}, "Permitted source repository URL") + command.Flags().StringSliceVar(&opts.signatureKeys, "signature-keys", []string{}, "GnuPG public key IDs for commit signature verification") command.Flags().BoolVar(&opts.orphanedResourcesEnabled, "orphaned-resources", false, "Enables orphaned resources monitoring") command.Flags().BoolVar(&opts.orphanedResourcesWarn, "orphaned-resources-warn", false, "Specifies if applications should be a warning condition when orphaned resources detected") } @@ -133,6 +150,7 @@ func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm Short: "Create a project", Run: func(c *cobra.Command, args []string) { var proj v1alpha1.AppProject + fmt.Printf("EE: %d/%v\n", len(opts.signatureKeys), opts.signatureKeys) if fileURL == "-" { // read stdin reader := bufio.NewReader(os.Stdin) @@ -165,6 +183,7 @@ func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm Description: opts.description, Destinations: opts.GetDestinations(), SourceRepos: opts.sources, + SignatureKeys: opts.GetSignatureKeys(), OrphanedResources: getOrphanedResourcesSettings(c, opts), }, } @@ -215,6 +234,8 @@ func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command proj.Spec.Destinations = opts.GetDestinations() case "src": proj.Spec.SourceRepos = opts.sources + case "signature-keys": + proj.Spec.SignatureKeys = opts.GetSignatureKeys() case "orphaned-resources", "orphaned-resources-warn": proj.Spec.OrphanedResources = getOrphanedResourcesSettings(c, opts) } @@ -233,6 +254,81 @@ func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command return command } +// NewProjectAddSignatureKeyCommand returns a new instance of an `argocd proj add-destination` command +func NewProjectAddSignatureKeyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "add-signature-key PROJECT KEY-ID", + Short: "Add GnuPG signature key to project", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + signatureKey := args[1] + + if !gpg.IsShortKeyID(signatureKey) && !gpg.IsLongKeyID(signatureKey) { + log.Fatalf("%s is not a valid GnuPG key ID", signatureKey) + } + + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer argoio.Close(conn) + + proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName}) + errors.CheckError(err) + + for _, key := range proj.Spec.SignatureKeys { + if key.KeyID == signatureKey { + log.Fatal("Specified signature key is already defined in project") + } + } + proj.Spec.SignatureKeys = append(proj.Spec.SignatureKeys, v1alpha1.SignatureKey{KeyID: signatureKey}) + _, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj}) + errors.CheckError(err) + }, + } + return command +} + +// NewProjectRemoveDestinationCommand returns a new instance of an `argocd proj remove-destination` command +func NewProjectRemoveSignatureKeyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "remove-signature-key PROJECT KEY-ID", + Short: "Remove GnuPG signature key from project", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + signatureKey := args[1] + + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer argoio.Close(conn) + + proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName}) + errors.CheckError(err) + + index := -1 + for i, key := range proj.Spec.SignatureKeys { + if key.KeyID == signatureKey { + index = i + break + } + } + if index == -1 { + log.Fatal("Specified signature key is not configured for project") + } else { + proj.Spec.SignatureKeys = append(proj.Spec.SignatureKeys[:index], proj.Spec.SignatureKeys[index+1:]...) + _, err = projIf.Update(context.Background(), &projectpkg.ProjectUpdateRequest{Project: proj}) + errors.CheckError(err) + } + }, + } + + return command +} + // NewProjectAddDestinationCommand returns a new instance of an `argocd proj add-destination` command func NewProjectAddDestinationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var command = &cobra.Command{ @@ -571,7 +667,7 @@ func printProjectNames(projects []v1alpha1.AppProject) { // Print table of project info func printProjectTable(projects []v1alpha1.AppProject) { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\tSOURCES\tCLUSTER-RESOURCE-WHITELIST\tNAMESPACE-RESOURCE-BLACKLIST\tORPHANED-RESOURCES\n") + fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\tSOURCES\tCLUSTER-RESOURCE-WHITELIST\tNAMESPACE-RESOURCE-BLACKLIST\tSIGNATURE-KEYS\tORPHANED-RESOURCES\n") for _, p := range projects { printProjectLine(w, &p) } @@ -616,7 +712,7 @@ func formatOrphanedResources(p *v1alpha1.AppProject) string { } func printProjectLine(w io.Writer, p *v1alpha1.AppProject) { - var destinations, sourceRepos, clusterWhitelist, namespaceBlacklist string + var destinations, sourceRepos, clusterWhitelist, namespaceBlacklist, signatureKeys string switch len(p.Spec.Destinations) { case 0: destinations = "" @@ -647,7 +743,13 @@ func printProjectLine(w io.Writer, p *v1alpha1.AppProject) { default: namespaceBlacklist = fmt.Sprintf("%d resources", len(p.Spec.NamespaceResourceBlacklist)) } - fmt.Fprintf(w, "%s\t%s\t%v\t%v\t%v\t%v\t%v\n", p.Name, p.Spec.Description, destinations, sourceRepos, clusterWhitelist, namespaceBlacklist, formatOrphanedResources(p)) + switch len(p.Spec.SignatureKeys) { + case 0: + signatureKeys = "" + default: + signatureKeys = fmt.Sprintf("%d key(s)", len(p.Spec.SignatureKeys)) + } + fmt.Fprintf(w, "%s\t%s\t%v\t%v\t%v\t%v\t%v\t%v\n", p.Name, p.Spec.Description, destinations, sourceRepos, clusterWhitelist, namespaceBlacklist, signatureKeys, formatOrphanedResources(p)) } func printProject(p *v1alpha1.AppProject) { @@ -695,6 +797,18 @@ func printProject(p *v1alpha1.AppProject) { for i := 1; i < len(p.Spec.NamespaceResourceBlacklist); i++ { fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[i].Group, p.Spec.NamespaceResourceBlacklist[i].Kind)) } + + // Print required signature keys + signatureKeysStr := "" + if len(p.Spec.SignatureKeys) > 0 { + kids := make([]string, 0) + for _, key := range p.Spec.SignatureKeys { + kids = append(kids, key.KeyID) + } + signatureKeysStr = strings.Join(kids, ", ") + } + fmt.Printf(printProjFmtStr, "Signature keys:", signatureKeysStr) + fmt.Printf(printProjFmtStr, "Orphaned Resources:", formatOrphanedResources(p)) } diff --git a/cmd/argocd/commands/root.go b/cmd/argocd/commands/root.go index fb74fb3828f6f..cb41353d597ee 100644 --- a/cmd/argocd/commands/root.go +++ b/cmd/argocd/commands/root.go @@ -53,6 +53,7 @@ func NewCommand() *cobra.Command { command.AddCommand(NewAccountCommand(&clientOpts)) command.AddCommand(NewLogoutCommand(&clientOpts)) command.AddCommand(NewCertCommand(&clientOpts)) + command.AddCommand(NewGPGCommand(&clientOpts)) defaultLocalConfigPath, err := localconfig.DefaultLocalConfigPath() errors.CheckError(err) diff --git a/common/common.go b/common/common.go index d93ec3ffd6195..60b992e053ee5 100644 --- a/common/common.go +++ b/common/common.go @@ -25,6 +25,7 @@ const ( ArgoCDKnownHostsConfigMapName = "argocd-ssh-known-hosts-cm" // Contains TLS certificate data for connecting repositories. Will get mounted as volume to pods ArgoCDTLSCertsConfigMapName = "argocd-tls-certs-cm" + ArgoCDGPGKeysConfigMapName = "argocd-gpg-keys-cm" ) // Some default configurables @@ -50,6 +51,8 @@ const ( DefaultPathSSHConfig = "/app/config/ssh" // Default name for the SSH known hosts file DefaultSSHKnownHostsName = "ssh_known_hosts" + // Default path to GnuPG home directory + DefaultGnuPgHomePath = "/app/config/gpg/keys" ) // Argo CD application related constants @@ -148,6 +151,8 @@ const ( EnvK8sClientBurst = "ARGOCD_K8S_CLIENT_BURST" // EnvK8sClientMaxIdleConnections is the number of max idle connections in K8s REST client HTTP transport (default: 500) EnvK8sClientMaxIdleConnections = "ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS" + // EnvGnuPGHome is the path to ArgoCD's GnuPG keyring for signature verification + EnvGnuPGHome = "ARGOCD_GNUPGHOME" ) const ( @@ -160,6 +165,15 @@ const ( CacheVersion = "1.0.0" ) +// GetGnuPGHomePath retrieves the path to use for GnuPG home directory, which is either taken from GNUPGHOME environment or a default value +func GetGnuPGHomePath() string { + if gnuPgHome := os.Getenv(EnvGnuPGHome); gnuPgHome == "" { + return DefaultGnuPgHomePath + } else { + return gnuPgHome + } +} + var ( // K8sClientConfigQPS controls the QPS to be used in K8s REST client configs K8sClientConfigQPS float32 = 50 diff --git a/controller/state.go b/controller/state.go index c6fa60b7a3718..3d02394fe6e8b 100644 --- a/controller/state.go +++ b/controller/state.go @@ -30,6 +30,7 @@ import ( "github.com/argoproj/argo-cd/reposerver/apiclient" "github.com/argoproj/argo-cd/util/argo" "github.com/argoproj/argo-cd/util/db" + "github.com/argoproj/argo-cd/util/gpg" argohealth "github.com/argoproj/argo-cd/util/health" "github.com/argoproj/argo-cd/util/settings" "github.com/argoproj/argo-cd/util/stats" @@ -93,7 +94,7 @@ type appStateManager struct { namespace string } -func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1.ApplicationSource, appLabelKey, revision string, noCache bool) ([]*unstructured.Unstructured, *apiclient.ManifestResponse, error) { +func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1.ApplicationSource, appLabelKey, revision string, noCache, verifySignature bool) ([]*unstructured.Unstructured, *apiclient.ManifestResponse, error) { ts := stats.NewTimingStats() helmRepos, err := m.db.ListHelmRepositories(context.Background()) if err != nil { @@ -152,6 +153,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1 KustomizeOptions: kustomizeOptions, KubeVersion: serverVersion, ApiVersions: argo.APIGroupsToVersions(apiGroups), + VerifySignature: verifySignature, }) if err != nil { return nil, nil, err @@ -242,6 +244,50 @@ func (m *appStateManager) getComparisonSettings(app *appv1.Application) (string, return appLabelKey, resourceOverrides, diffNormalizer, resFilter, nil } +// verifyGnuPGSignature verifies the result of a GnuPG operation for a given git +// revision. +func verifyGnuPGSignature(revision string, project *appv1.AppProject, manifestInfo *apiclient.ManifestResponse) []appv1.ApplicationCondition { + now := metav1.Now() + conditions := make([]appv1.ApplicationCondition, 0) + // We need to have some data in the verificatin result to parse, otherwise there was no signature + if manifestInfo.VerifyResult != "" { + verifyResult, err := gpg.ParseGitCommitVerification(manifestInfo.VerifyResult) + if err != nil { + conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now}) + log.Errorf("Error while verifying git commit for revision %s: %s", revision, err.Error()) + } else { + switch verifyResult.Result { + case gpg.VerifyResultGood: + // This is the only case we allow to sync to, but we need to make sure signing key is allowed + validKey := false + for _, k := range project.Spec.SignatureKeys { + if gpg.KeyID(k.KeyID) == gpg.KeyID(verifyResult.KeyID) && gpg.KeyID(k.KeyID) != "" { + validKey = true + break + } + } + if !validKey { + msg := fmt.Sprintf("Found good signature made with %s key %s, but this key is not allowed in AppProject", + verifyResult.Cipher, verifyResult.KeyID) + conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now}) + } + case gpg.VerifyResultInvalid: + msg := fmt.Sprintf("Found signature made with %s key %s, but verification result was invalid: '%s'", + verifyResult.Cipher, verifyResult.KeyID, verifyResult.Message) + conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now}) + default: + msg := fmt.Sprintf("Could not verify commit signature on revision '%s', check logs for more information.", revision) + conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now}) + } + } + } else { + msg := fmt.Sprintf("Target revision %s in Git is not signed, but a signature is required", revision) + conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now}) + } + + return conditions +} + // CompareAppState compares application git state to the live app state, using the specified // revision and supplied source. If revision or overrides are empty, then compares against // revision and overrides in the app spec. @@ -261,6 +307,12 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap } } + // When signature keys are defined in the project spec, we need to verify the signature on the Git revision + verifySignature := false + if project.Spec.SignatureKeys != nil && len(project.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled() { + verifySignature = true + } + // do best effort loading live and target state to present as much information about app state as possible failedToLoadObjs := false conditions := make([]v1alpha1.ApplicationCondition, 0) @@ -273,18 +325,27 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap now := metav1.Now() if len(localManifests) == 0 { - targetObjs, manifestInfo, err = m.getRepoObjs(app, source, appLabelKey, revision, noCache) + targetObjs, manifestInfo, err = m.getRepoObjs(app, source, appLabelKey, revision, noCache, verifySignature) if err != nil { targetObjs = make([]*unstructured.Unstructured, 0) conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now}) failedToLoadObjs = true } } else { - targetObjs, err = unmarshalManifests(localManifests) - if err != nil { + // Prevent applying local manifests for now when signature verification is enabled + // This is also enforced on API level, but as a last resort, we also enforce it here + if gpg.IsGPGEnabled() && verifySignature { + msg := "Cannot use local manifests when signature verification is required" targetObjs = make([]*unstructured.Unstructured, 0) - conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now}) + conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now}) failedToLoadObjs = true + } else { + targetObjs, err = unmarshalManifests(localManifests) + if err != nil { + targetObjs = make([]*unstructured.Unstructured, 0) + conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now}) + failedToLoadObjs = true + } } manifestInfo = nil } @@ -454,6 +515,13 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now}) } + // Git has already performed the signature verification via its GPG interface, and the result is available + // in the manifest info received from the repository server. We now need to form our oppinion about the result + // and stop processing if we do not agree about the outcome. + if gpg.IsGPGEnabled() && verifySignature && manifestInfo != nil { + conditions = append(conditions, verifyGnuPGSignature(revision, project, manifestInfo)...) + } + compRes := comparisonResult{ syncStatus: &syncStatus, healthStatus: healthStatus, diff --git a/controller/state_test.go b/controller/state_test.go index 8a9e81c5aff98..0e39fe1ecb277 100644 --- a/controller/state_test.go +++ b/controller/state_test.go @@ -2,6 +2,8 @@ package controller import ( "encoding/json" + "io/ioutil" + "os" "testing" "time" @@ -440,3 +442,280 @@ func Test_appStateManager_persistRevisionHistory(t *testing.T) { assert.NoError(t, err) assert.Equal(t, app.Status.History.LastRevisionHistory().DeployStartedAt, &metav1NowTime) } + +// helper function to read contents of a file to string +// panics on error +func mustReadFile(path string) string { + b, err := ioutil.ReadFile(path) + if err != nil { + panic(err.Error()) + } + return string(b) +} + +var signedProj = argoappv1.AppProject{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: test.FakeArgoCDNamespace, + }, + Spec: argoappv1.AppProjectSpec{ + SourceRepos: []string{"*"}, + Destinations: []argoappv1.ApplicationDestination{ + { + Server: "*", + Namespace: "*", + }, + }, + SignatureKeys: []argoappv1.SignatureKey{ + { + KeyID: "4AEE18F83AFDEB23", + }, + }, + }, +} + +func TestSignedResponseNoSignatureRequired(t *testing.T) { + oldval := os.Getenv("ARGOCD_GPG_ENABLED") + os.Setenv("ARGOCD_GPG_ENABLED", "true") + defer os.Setenv("ARGOCD_GPG_ENABLED", oldval) + // We have a good signature response, but project does not require signed commits + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: mustReadFile("../util/gpg/testdata/good_signature.txt"), + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 0) + } + // We have a bad signature response, but project does not require signed commits + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_bad.txt"), + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 0) + } +} + +func TestSignedResponseSignatureRequired(t *testing.T) { + oldval := os.Getenv("ARGOCD_GPG_ENABLED") + os.Setenv("ARGOCD_GPG_ENABLED", "true") + defer os.Setenv("ARGOCD_GPG_ENABLED", oldval) + + // We have a good signature response, valid key, and signing is required - sync! + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: mustReadFile("../util/gpg/testdata/good_signature.txt"), + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &signedProj, "", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 0) + } + // We have a bad signature response and signing is required - do not sync + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_bad.txt"), + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &signedProj, "abc123", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 1) + } + // We have a malformed signature response and signing is required - do not sync + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_malformed1.txt"), + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &signedProj, "abc123", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 1) + } + // We have no signature response (no signature made) and signing is required - do not sync + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: "", + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &signedProj, "abc123", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 1) + } + + // We have a good signature and signing is required, but key is not allowed - do not sync + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: mustReadFile("../util/gpg/testdata/good_signature.txt"), + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + testProj := signedProj + testProj.Spec.SignatureKeys[0].KeyID = "4AEE18F83AFDEB24" + compRes := ctrl.appStateManager.CompareAppState(app, &testProj, "abc123", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 1) + assert.Contains(t, app.Status.Conditions[0].Message, "key is not allowed") + } + // Signature required and local manifests supplied - do not sync + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: "", + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + // it doesn't matter for our test whether local manifests are valid + localManifests := []string{"foobar"} + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &signedProj, "abc123", app.Spec.Source, false, localManifests) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeUnknown, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 1) + assert.Contains(t, app.Status.Conditions[0].Message, "Cannot use local manifests") + } + + os.Setenv("ARGOCD_GPG_ENABLED", "false") + // We have a bad signature response and signing would be required, but GPG subsystem is disabled - sync + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_bad.txt"), + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &signedProj, "abc123", app.Spec.Source, false, nil) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 0) + } + + // Signature required and local manifests supplied and GPG subystem is disabled - sync + { + app := newFakeApp() + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + VerifyResult: "", + }, + managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), + } + // it doesn't matter for our test whether local manifests are valid + localManifests := []string{""} + ctrl := newFakeController(&data) + compRes := ctrl.appStateManager.CompareAppState(app, &signedProj, "abc123", app.Spec.Source, false, localManifests) + assert.NotNil(t, compRes) + assert.NotNil(t, compRes.syncStatus) + assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) + assert.Len(t, compRes.resources, 0) + assert.Len(t, compRes.managedResources, 0) + assert.Len(t, app.Status.Conditions, 0) + } + +} diff --git a/docs/user-guide/gpg-verification.md b/docs/user-guide/gpg-verification.md new file mode 100644 index 0000000000000..9884658970814 --- /dev/null +++ b/docs/user-guide/gpg-verification.md @@ -0,0 +1,305 @@ +# GnuPG signature verifcation + +## Overview + +As of v1.7 it is possible to configure ArgoCD to only sync against commits +that are signed in Git using GnuPG. Signature verification is configured on +project level. + +If a project is configured to enforce signature verification, all applications +associated with this project must have the commits in the source repositories +signed with a GnuPG public key known to ArgoCD. ArgoCD will refuse to sync to +any revision that does not have a valid signature made by one of the configured +keys. The controller will emit a `ResourceComparison` error if it tries to sync +to a revision that is either not signed, or is signed by an unknown or not +allowed public key. + +By default, signature verification is enabled but not enforced. If you wish to +completely disable the GnuPG functionality in ArgoCD, you have to set the +environment variable `ARGOCD_GPG_ENABLED` to `"false"` in the pod templates of +the `argocd-server`, `argocd-repo-server` and `argocd-application-controller` +deployment manifests. + +Verification of GnuPG signatures is only supported with Git repositories. It is +not possible using Helm repositories. + +!!!note "A few words about trust" + ArgoCD uses a very simple trust model for the keys you import: Once the key + is imported, ArgoCD will trust it. ArgoCD does not support more complex + trust models, and it is not necessary (nor possible) to sign the public keys + you are going to import into ArgoCD. + +## Signature verification targets + +If signature verification is enforced, ArgoCD will verify the signature using +following strategy: + +* If `target revision` is a pointer to a commit object (i.e. a branch name, the + name of a reference such as `HEAD` or a commit SHA), ArgoCD will perform the + signature verification on the commit object the name points to, i.e. a commit. + +* If `target revision` resolves to a tag and the tag is a lightweight tag, the + behaviour is same as if `target revision` would be a pointer to a commit + object. However, if the tag is annotated, the target revision will point to + a *tag* object and thus, the signature verification is performed on the tag + object, i.e. the tag itself must be signed (using `git tag -s`). + +## Enforcing signature verification + +To configure enforcing of signature verification, the following steps must be +performed: + +* Import the GnuPG public key(s) used for signing commits in ArgoCD +* Configure a project to enforce signature verification for given keys + +Once you have configured one or more keys to be required for verification for +a given project, enforcement is active for all applications associated with +this project. + +!!!warning + If signature verification is enforced, you will not be able to sync from + local sources (i.e. `argocd app sync --local`) anymore. + +## Importing GnuPG public keys + +You can configure the GnuPG public keys that ArgoCD will use for verification +of commit signatures using either the CLI, the web UI or configuring it using +declarative setup. + +!!!note + After you have imported a GnuPG key, it may take a while until the key is + propagated within the cluster, even if listed as configured. If you still + cannot sync to commits signed by the already imported key, please see the + troubleshooting section below. + +Users wanting to manage the GnuPG public key configuration require the RBAC +permissions for `gpgkeys` resources. + +### Manage public keys using the CLI + +To configure GnuPG public keys using the CLI, use the `argocd gpg` command. + +#### Listing all configured keys + +To list all configured keys known to ArgoCD, use the `argocd gpg list` +sub-command: + +```bash +argocd gpg list +``` + +#### Show information about a certain key + +To get information about a specific key, use the `argocd gpg get` sub-command: + +```bash +argocd gpg get +``` + +#### Importing a key + +To import a new key to ArgoCD, use the `argocd gpg add` sub-command: + +```bash +argocd gpg add --from +``` + +The key to be imported can be either in binary or ASCII-armored format. + +#### Removing a key from configuration + +To remove a previously configured key from the configuration, use the +`argocd gpg rm` sub-command: + +```bash +argocd gpg rm +``` + +### Manage public keys using the Web UI + +Basic key management functionality for listing, importing and removing GnuPG +public keys is implemented in the Web UI. You can find the configuration +module from the **Settings** page in the **GnuPG keys** module. + +Please note that when you configure keys using the Web UI, the key must be +imported in ASCII armored format for now. + +### Manage public keys in declarative setup + +ArgoCD stores public keys internally in the `argocd-gpg-keys-cm` ConfigMap +resource, with the public GnuPG key's ID as its name and the ASCII armored +key data as string value, i.e. the entry for the GitHub's web-flow signing +key would look like follows: + +```yaml +4AEE18F83AFDEB23: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQENBFmUaEEBCACzXTDt6ZnyaVtueZASBzgnAmK13q9Urgch+sKYeIhdymjuMQta + x15OklctmrZtqre5kwPUosG3/B2/ikuPYElcHgGPL4uL5Em6S5C/oozfkYzhwRrT + SQzvYjsE4I34To4UdE9KA97wrQjGoz2Bx72WDLyWwctD3DKQtYeHXswXXtXwKfjQ + 7Fy4+Bf5IPh76dA8NJ6UtjjLIDlKqdxLW4atHe6xWFaJ+XdLUtsAroZcXBeWDCPa + buXCDscJcLJRKZVc62gOZXXtPfoHqvUPp3nuLA4YjH9bphbrMWMf810Wxz9JTd3v + yWgGqNY0zbBqeZoGv+TuExlRHT8ASGFS9SVDABEBAAG0NUdpdEh1YiAod2ViLWZs + b3cgY29tbWl0IHNpZ25pbmcpIDxub3JlcGx5QGdpdGh1Yi5jb20+iQEiBBMBCAAW + BQJZlGhBCRBK7hj4Ov3rIwIbAwIZAQAAmQEH/iATWFmi2oxlBh3wAsySNCNV4IPf + DDMeh6j80WT7cgoX7V7xqJOxrfrqPEthQ3hgHIm7b5MPQlUr2q+UPL22t/I+ESF6 + 9b0QWLFSMJbMSk+BXkvSjH9q8jAO0986/pShPV5DU2sMxnx4LfLfHNhTzjXKokws + +8ptJ8uhMNIDXfXuzkZHIxoXk3rNcjDN5c5X+sK8UBRH092BIJWCOfaQt7v7wig5 + 4Ra28pM9GbHKXVNxmdLpCFyzvyMuCmINYYADsC848QQFFwnd4EQnupo6QvhEVx1O + j7wDwvuH5dCrLuLwtwXaQh0onG4583p0LGms2Mf5F+Ick6o/4peOlBoZz48= + =Bvzs + -----END PGP PUBLIC KEY BLOCK----- +``` + +## Configuring a project to enforce signature verification + +Once you have imported the GnuPG keys to ArgoCD, you must now configure the +project to enforce the verification of commit signatures with the imported +keys. + +### Configuring using the CLI + +#### Adding a key ID to list of allowed keys + +To add a key ID to the list of allowed GnuPG keys for a project, you can use +the `argocd proj add-signature-key` command, i.e. the following command would +add the key ID `4AEE18F83AFDEB23` to the project named `myproj`: + +```bash +argocd proj add-signature-key myproj 4AEE18F83AFDEB23 +``` + +#### Removing a key ID from the list of allowed keys + +Similarily, you can remove a key ID from the list of allowed GnuPG keys for a +project using the `argocd proj remove-signature-key` command, i.e. to remove +the key added above from project `myproj`, use the command: + +```bash +argocd proj remove-signature-key myproj 4AEE18F83AFDEB23 +``` + +#### Showing allowed key IDs for a project + +To see which key IDs are allowed for a given project, you can inspect the +output of the `argocd proj get` command, i.e for a project named `gpg`: + +```bash +$ argocd proj get gpg +Name: gpg +Description: GnuPG verification +Destinations: *,* +Repositories: * +Whitelisted Cluster Resources: */* +Blacklisted Namespaced Resources: +Signature keys: 4AEE18F83AFDEB23, 07E34825A909B250 +Orphaned Resources: disabled +``` + +#### Override list of key IDs + +You can also explicitly set the currently allowed keys with one or more new keys +using the `argocd proj set` command in combination with the `--signature-keys` +flag, which you can use to specify a comma separated list of allowed key IDs: + +```bash +argocd proj set myproj --signature-keys 4AEE18F83AFDEB23,07E34825A909B250 +``` + +The `--signature-keys` flag can also be used on project creation, i.e. the +`argocd proj create` command. + +### Configure using the Web UI + +You can configure the GnuPG key IDs required for signature verification using +the web UI, in the Project configuration. Navigate to the **Settings** page +and select the **Projects** module, then click on the project you want to +configure. + +From the project's details page, click **Edit** and find the +**Required signature keys** section, where you can add or remove the key IDs +for signature verification. After you have modified your project, click +**Update** to save the changes. + +### Configure using declarative setup + +You can specify the key IDs required for signature verification in the project +manifest within the `signatureKeys` section, i.e: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: gpg + namespace: argocd +spec: + clusterResourceWhitelist: + - group: '*' + kind: '*' + description: GnuPG verification + destinations: + - namespace: '*' + server: '*' + namespaceResourceWhitelist: + - group: '*' + kind: '*' + signatureKeys: + - keyID: 4AEE18F83AFDEB23 + sourceRepos: + - '*' +``` + +`signatureKeys` is an array of `SignatureKey` objects, whose only property is +`keyID` at the moment. + +## Troubleshooting + +### Disabling the feature + +The GnuPG feature can be completely disabled if desired. In order to disable it, +set the environment variable `ARGOCD_GPG_ENABLED` to `false` for the pod +templates of the `argocd-server`, `argocd-repo-server` and + `argocd-application-controller` deployments. + +After the pods have been restarted, the GnuPG feature is disabled. + +### GnuPG key ring + +The GnuPG key ring used for signature verification is maintained within the +pods of `argocd-repo-server`. The keys in the keyring are synchronized to the +configuration stored in the `argocd-gpg-keys-cm` ConfigMap resource, which is +volume-mounted to the `argocd-repo-server` pods. + +!!!note + The GnuPG key ring in the pods is transient and gets recreated from the + configuration on each restart of the pods. You should never add or remove + keys manually to the key ring, because your changes will be lost. Also, + any of the private keys found in the key ring are transient and will be + regenerated upon each restart. The private key is only used to build the + trust DB for the running pod. + +To check whether the keys are actually in sync, you can `kubectl exec` into the +repository server's pods and inspect the key ring, which is located at path +`/app/config/gpg/keys` + +```bash +$ kubectl exec -it argocd-repo-server-7d6bdfdf6d-hzqkg bash +argocd@argocd-repo-server-7d6bdfdf6d-hzqkg:~$ GNUPGHOME=/app/config/gpg/keys gpg --list-keys +/app/config/gpg/keys/pubring.kbx +-------------------------------- +pub rsa2048 2020-06-15 [SC] [expires: 2020-12-12] + D48F075D818A813C436914BC9324F0D2144753B1 +uid [ultimate] Anon Ymous (ArgoCD key signing key) + +pub rsa2048 2017-08-16 [SC] + 5DE3E0509C47EA3CF04A42D34AEE18F83AFDEB23 +uid [ultimate] GitHub (web-flow commit signing) + +argocd@argocd-repo-server-7d6bdfdf6d-hzqkg:~$ +``` + +If the key ring stays out of sync with your configuration after you have added +or removed keys for a longer period of time, you might want to restart your +`argocd-repo-server` pods. If such a problem persists, please consider raising +a bug report. diff --git a/go.mod b/go.mod index 6085ef8d1ad40..5faf95ef096d4 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/dustin/go-humanize v1.0.0 github.com/evanphx/json-patch v4.5.0+incompatible + github.com/fsnotify/fsnotify v1.4.7 github.com/ghodss/yaml v1.0.0 github.com/go-openapi/loads v0.19.2 github.com/go-openapi/runtime v0.19.0 diff --git a/hack/dev-mounter/main.go b/hack/dev-mounter/main.go index cba02d6c23496..008111c52f4e3 100644 --- a/hack/dev-mounter/main.go +++ b/hack/dev-mounter/main.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "strings" "time" @@ -61,10 +62,33 @@ func newCommand() *cobra.Command { log.Warnf("Failed to create directory: %v", err) return } + // Remove files that do not exist in ConfigMap anymore + err = filepath.Walk(destPath, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + if err != nil { + log.Warnf("Error walking path %s: %v", path, err) + } + p := filepath.Base(path) + if _, ok := cm.Data[p]; !ok { + log.Infof("Removing file '%s'", path) + err := os.Remove(path) + if err != nil { + log.Warnf("Failed to remove file %s: %v", path, err) + } + } + return nil + }) + if err != nil { + log.Fatalf("Error: %v", err) + } + // Create or update files that are specified in ConfigMap for name, data := range cm.Data { - err := ioutil.WriteFile(path.Join(destPath, name), []byte(data), 0644) + p := path.Join(destPath, name) + err := ioutil.WriteFile(p, []byte(data), 0644) if err != nil { - log.Warnf("Failed to create file: %v", err) + log.Warnf("Failed to create file %s: %v", p, err) } } } diff --git a/hack/generate-proto.sh b/hack/generate-proto.sh index e64cd7211a984..20cf4fe013de1 100755 --- a/hack/generate-proto.sh +++ b/hack/generate-proto.sh @@ -121,7 +121,7 @@ clean_swagger() { } echo "If additional types are added, the number of expected collisions may need to be increased" -EXPECTED_COLLISION_COUNT=32 +EXPECTED_COLLISION_COUNT=33 collect_swagger server ${EXPECTED_COLLISION_COUNT} clean_swagger server clean_swagger reposerver diff --git a/hack/git-verify-wrapper.sh b/hack/git-verify-wrapper.sh new file mode 100755 index 0000000000000..7f6d3ffb44e92 --- /dev/null +++ b/hack/git-verify-wrapper.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# Wrapper script to perform GPG signature validation on git commit SHAs and +# annotated tags. +# +# We capture stderr to stdout, so we can have the output in the logs. Also, +# we ignore error codes that are emitted if signature verification failed. +# +if test "$1" = ""; then + echo "Wrong usage of git-verify-wrapper.sh" >&2 + exit 1 +fi + +REVISION="$1" +TYPE= + +# Figure out we have an annotated tag or a commit SHA +if git describe --exact-match "${REVISION}" >/dev/null 2>&1; then + IFS='' + TYPE=tag + OUTPUT=$(git verify-tag "$REVISION" 2>&1) + RET=$? +else + IFS='' + TYPE=commit + OUTPUT=$(git verify-commit "$REVISION" 2>&1) + RET=$? +fi + +case "$RET" in +0) + echo "$OUTPUT" + ;; +1) + # git verify-tag emits error messages if no signature is found on tag, + # which we don't want in the output. + if test "$TYPE" = "tag" -a "${OUTPUT%%:*}" = "error"; then + OUTPUT="" + fi + echo "$OUTPUT" + RET=0 + ;; +*) + echo "$OUTPUT" >&2 + ;; +esac +exit $RET diff --git a/hack/gpg-wrapper.sh b/hack/gpg-wrapper.sh new file mode 100755 index 0000000000000..38112284813ef --- /dev/null +++ b/hack/gpg-wrapper.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Simple wrapper around gpg to prevent exit code != 0 +ARGS=$* +OUTPUT=$(gpg $ARGS 2>&1) +IFS='' +RET=$? +case "$RET" in +0) + echo $OUTPUT + ;; +1) + echo $OUTPUT + RET=0 + ;; +*) + echo $OUTPUT >&2 + ;; +esac +exit $RET diff --git a/manifests/base/config/argocd-gpg-keys-cm.yaml b/manifests/base/config/argocd-gpg-keys-cm.yaml new file mode 100644 index 0000000000000..d5a61cc6cb332 --- /dev/null +++ b/manifests/base/config/argocd-gpg-keys-cm.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-gpg-keys-cm + app.kubernetes.io/part-of: argocd + name: argocd-gpg-keys-cm diff --git a/manifests/base/config/kustomization.yaml b/manifests/base/config/kustomization.yaml index 124f2d00120e4..f3678dce8e150 100644 --- a/manifests/base/config/kustomization.yaml +++ b/manifests/base/config/kustomization.yaml @@ -7,3 +7,4 @@ resources: - argocd-rbac-cm.yaml - argocd-ssh-known-hosts-cm.yaml - argocd-tls-certs-cm.yaml +- argocd-gpg-keys-cm.yaml diff --git a/manifests/base/repo-server/argocd-repo-server-deployment.yaml b/manifests/base/repo-server/argocd-repo-server-deployment.yaml index 9e483aeb6f411..bc5dcc8eed798 100644 --- a/manifests/base/repo-server/argocd-repo-server-deployment.yaml +++ b/manifests/base/repo-server/argocd-repo-server-deployment.yaml @@ -43,6 +43,8 @@ spec: mountPath: /app/config/ssh - name: tls-certs mountPath: /app/config/tls + - name: gpg-keys + mountPath: /app/config/gpg/source volumes: - name: ssh-known-hosts configMap: @@ -50,3 +52,6 @@ spec: - name: tls-certs configMap: name: argocd-tls-certs-cm + - name: gpg-keys + configMap: + name: argocd-gpg-keys-cm diff --git a/manifests/crds/appproject-crd.yaml b/manifests/crds/appproject-crd.yaml index f59e0d5b5e1c0..d31122c191d11 100644 --- a/manifests/crds/appproject-crd.yaml +++ b/manifests/crds/appproject-crd.yaml @@ -170,6 +170,20 @@ spec: - name type: object type: array + signatureKeys: + description: List of PGP key IDs that commits to be synced to must be + signed with + items: + description: SignatureKey is the specification of a key required to + verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array sourceRepos: description: SourceRepos contains list of repository URLs which can be used for deployment diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml index a1b6153a7ef99..b45e5c9ba5e06 100644 --- a/manifests/ha/install.yaml +++ b/manifests/ha/install.yaml @@ -1904,6 +1904,20 @@ spec: - name type: object type: array + signatureKeys: + description: List of PGP key IDs that commits to be synced to must be + signed with + items: + description: SignatureKey is the specification of a key required to + verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array sourceRepos: description: SourceRepos contains list of repository URLs which can be used for deployment @@ -2539,6 +2553,14 @@ metadata: --- apiVersion: v1 kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-gpg-keys-cm + app.kubernetes.io/part-of: argocd + name: argocd-gpg-keys-cm +--- +apiVersion: v1 +kind: ConfigMap metadata: labels: app.kubernetes.io/name: argocd-rbac-cm @@ -3045,6 +3067,8 @@ spec: name: ssh-known-hosts - mountPath: /app/config/tls name: tls-certs + - mountPath: /app/config/gpg/source + name: gpg-keys volumes: - configMap: name: argocd-ssh-known-hosts-cm @@ -3052,6 +3076,9 @@ spec: - configMap: name: argocd-tls-certs-cm name: tls-certs + - configMap: + name: argocd-gpg-keys-cm + name: gpg-keys --- apiVersion: apps/v1 kind: Deployment diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml index e74f2d31dc3c1..8ace52024e532 100644 --- a/manifests/ha/namespace-install.yaml +++ b/manifests/ha/namespace-install.yaml @@ -1904,6 +1904,20 @@ spec: - name type: object type: array + signatureKeys: + description: List of PGP key IDs that commits to be synced to must be + signed with + items: + description: SignatureKey is the specification of a key required to + verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array sourceRepos: description: SourceRepos contains list of repository URLs which can be used for deployment @@ -2454,6 +2468,14 @@ metadata: --- apiVersion: v1 kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-gpg-keys-cm + app.kubernetes.io/part-of: argocd + name: argocd-gpg-keys-cm +--- +apiVersion: v1 +kind: ConfigMap metadata: labels: app.kubernetes.io/name: argocd-rbac-cm @@ -2960,6 +2982,8 @@ spec: name: ssh-known-hosts - mountPath: /app/config/tls name: tls-certs + - mountPath: /app/config/gpg/source + name: gpg-keys volumes: - configMap: name: argocd-ssh-known-hosts-cm @@ -2967,6 +2991,9 @@ spec: - configMap: name: argocd-tls-certs-cm name: tls-certs + - configMap: + name: argocd-gpg-keys-cm + name: gpg-keys --- apiVersion: apps/v1 kind: Deployment diff --git a/manifests/install.yaml b/manifests/install.yaml index e7c7dd1f86117..4947204f22d8c 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -1904,6 +1904,20 @@ spec: - name type: object type: array + signatureKeys: + description: List of PGP key IDs that commits to be synced to must be + signed with + items: + description: SignatureKey is the specification of a key required to + verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array sourceRepos: description: SourceRepos contains list of repository URLs which can be used for deployment @@ -2234,6 +2248,14 @@ metadata: --- apiVersion: v1 kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-gpg-keys-cm + app.kubernetes.io/part-of: argocd + name: argocd-gpg-keys-cm +--- +apiVersion: v1 +kind: ConfigMap metadata: labels: app.kubernetes.io/name: argocd-rbac-cm @@ -2559,6 +2581,8 @@ spec: name: ssh-known-hosts - mountPath: /app/config/tls name: tls-certs + - mountPath: /app/config/gpg/source + name: gpg-keys volumes: - configMap: name: argocd-ssh-known-hosts-cm @@ -2566,6 +2590,9 @@ spec: - configMap: name: argocd-tls-certs-cm name: tls-certs + - configMap: + name: argocd-gpg-keys-cm + name: gpg-keys --- apiVersion: apps/v1 kind: Deployment diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 8ad3936017085..27f2b364f4f22 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -1904,6 +1904,20 @@ spec: - name type: object type: array + signatureKeys: + description: List of PGP key IDs that commits to be synced to must be + signed with + items: + description: SignatureKey is the specification of a key required to + verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array sourceRepos: description: SourceRepos contains list of repository URLs which can be used for deployment @@ -2149,6 +2163,14 @@ metadata: --- apiVersion: v1 kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: argocd-gpg-keys-cm + app.kubernetes.io/part-of: argocd + name: argocd-gpg-keys-cm +--- +apiVersion: v1 +kind: ConfigMap metadata: labels: app.kubernetes.io/name: argocd-rbac-cm @@ -2474,6 +2496,8 @@ spec: name: ssh-known-hosts - mountPath: /app/config/tls name: tls-certs + - mountPath: /app/config/gpg/source + name: gpg-keys volumes: - configMap: name: argocd-ssh-known-hosts-cm @@ -2481,6 +2505,9 @@ spec: - configMap: name: argocd-tls-certs-cm name: tls-certs + - configMap: + name: argocd-gpg-keys-cm + name: gpg-keys --- apiVersion: apps/v1 kind: Deployment diff --git a/mkdocs.yml b/mkdocs.yml index 2ca04e823b389..59d09b8aac874 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,6 +65,7 @@ nav: - user-guide/tool_detection.md - user-guide/projects.md - user-guide/private-repositories.md + - GnuPG verification: user-guide/gpg-verification.md - user-guide/auto_sync.md - user-guide/diffing.md - user-guide/orphaned-resources.md diff --git a/pkg/apiclient/apiclient.go b/pkg/apiclient/apiclient.go index 4956eec268e28..54a2307d4c212 100644 --- a/pkg/apiclient/apiclient.go +++ b/pkg/apiclient/apiclient.go @@ -31,6 +31,7 @@ import ( applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application" certificatepkg "github.com/argoproj/argo-cd/pkg/apiclient/certificate" clusterpkg "github.com/argoproj/argo-cd/pkg/apiclient/cluster" + gpgkeypkg "github.com/argoproj/argo-cd/pkg/apiclient/gpgkey" projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project" repocredspkg "github.com/argoproj/argo-cd/pkg/apiclient/repocreds" repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository" @@ -68,6 +69,8 @@ type Client interface { NewCertClientOrDie() (io.Closer, certificatepkg.CertificateServiceClient) NewClusterClient() (io.Closer, clusterpkg.ClusterServiceClient, error) NewClusterClientOrDie() (io.Closer, clusterpkg.ClusterServiceClient) + NewGPGKeyClient() (io.Closer, gpgkeypkg.GPGKeyServiceClient, error) + NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient) NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error) @@ -559,6 +562,23 @@ func (c *client) NewClusterClientOrDie() (io.Closer, clusterpkg.ClusterServiceCl return conn, clusterIf } +func (c *client) NewGPGKeyClient() (io.Closer, gpgkeypkg.GPGKeyServiceClient, error) { + conn, closer, err := c.newConn() + if err != nil { + return nil, nil, err + } + gpgkeyIf := gpgkeypkg.NewGPGKeyServiceClient(conn) + return closer, gpgkeyIf, nil +} + +func (c *client) NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient) { + conn, gpgkeyIf, err := c.NewGPGKeyClient() + if err != nil { + log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err) + } + return conn, gpgkeyIf +} + func (c *client) NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error) { conn, closer, err := c.newConn() if err != nil { diff --git a/pkg/apiclient/gpgkey/gpgkey.pb.go b/pkg/apiclient/gpgkey/gpgkey.pb.go new file mode 100644 index 0000000000000..af8630380e09f --- /dev/null +++ b/pkg/apiclient/gpgkey/gpgkey.pb.go @@ -0,0 +1,1180 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: server/gpgkey/gpgkey.proto + +// GPG public key service +// +// GPG public key API performs CRUD actions against GnuPGPublicKey resources + +package gpgkey + +import ( + context "context" + fmt "fmt" + v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Message to query the server for configured GPG public keys +type GnuPGPublicKeyQuery struct { + // The GPG key ID to query for + KeyID string `protobuf:"bytes,1,opt,name=keyID,proto3" json:"keyID,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GnuPGPublicKeyQuery) Reset() { *m = GnuPGPublicKeyQuery{} } +func (m *GnuPGPublicKeyQuery) String() string { return proto.CompactTextString(m) } +func (*GnuPGPublicKeyQuery) ProtoMessage() {} +func (*GnuPGPublicKeyQuery) Descriptor() ([]byte, []int) { + return fileDescriptor_8ba55a5eb76dc6fd, []int{0} +} +func (m *GnuPGPublicKeyQuery) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GnuPGPublicKeyQuery) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GnuPGPublicKeyQuery.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GnuPGPublicKeyQuery) XXX_Merge(src proto.Message) { + xxx_messageInfo_GnuPGPublicKeyQuery.Merge(m, src) +} +func (m *GnuPGPublicKeyQuery) XXX_Size() int { + return m.Size() +} +func (m *GnuPGPublicKeyQuery) XXX_DiscardUnknown() { + xxx_messageInfo_GnuPGPublicKeyQuery.DiscardUnknown(m) +} + +var xxx_messageInfo_GnuPGPublicKeyQuery proto.InternalMessageInfo + +func (m *GnuPGPublicKeyQuery) GetKeyID() string { + if m != nil { + return m.KeyID + } + return "" +} + +// Request to create one or more public keys on the server +type GnuPGPublicKeyCreateRequest struct { + // Raw key data of the GPG key(s) to create + Publickey *v1alpha1.GnuPGPublicKey `protobuf:"bytes,1,opt,name=publickey,proto3" json:"publickey,omitempty"` + // Whether to upsert already existing public keys + Upsert bool `protobuf:"varint,2,opt,name=upsert,proto3" json:"upsert,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GnuPGPublicKeyCreateRequest) Reset() { *m = GnuPGPublicKeyCreateRequest{} } +func (m *GnuPGPublicKeyCreateRequest) String() string { return proto.CompactTextString(m) } +func (*GnuPGPublicKeyCreateRequest) ProtoMessage() {} +func (*GnuPGPublicKeyCreateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8ba55a5eb76dc6fd, []int{1} +} +func (m *GnuPGPublicKeyCreateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GnuPGPublicKeyCreateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GnuPGPublicKeyCreateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GnuPGPublicKeyCreateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GnuPGPublicKeyCreateRequest.Merge(m, src) +} +func (m *GnuPGPublicKeyCreateRequest) XXX_Size() int { + return m.Size() +} +func (m *GnuPGPublicKeyCreateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GnuPGPublicKeyCreateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GnuPGPublicKeyCreateRequest proto.InternalMessageInfo + +func (m *GnuPGPublicKeyCreateRequest) GetPublickey() *v1alpha1.GnuPGPublicKey { + if m != nil { + return m.Publickey + } + return nil +} + +func (m *GnuPGPublicKeyCreateRequest) GetUpsert() bool { + if m != nil { + return m.Upsert + } + return false +} + +// Response to a public key creation request +type GnuPGPublicKeyCreateResponse struct { + // List of GPG public keys that have been created + Created *v1alpha1.GnuPGPublicKeyList `protobuf:"bytes,1,opt,name=created,proto3" json:"created,omitempty"` + // List of key IDs that haven been skipped because they already exist on the server + Skipped []string `protobuf:"bytes,2,rep,name=skipped,proto3" json:"skipped,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GnuPGPublicKeyCreateResponse) Reset() { *m = GnuPGPublicKeyCreateResponse{} } +func (m *GnuPGPublicKeyCreateResponse) String() string { return proto.CompactTextString(m) } +func (*GnuPGPublicKeyCreateResponse) ProtoMessage() {} +func (*GnuPGPublicKeyCreateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8ba55a5eb76dc6fd, []int{2} +} +func (m *GnuPGPublicKeyCreateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GnuPGPublicKeyCreateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GnuPGPublicKeyCreateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GnuPGPublicKeyCreateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GnuPGPublicKeyCreateResponse.Merge(m, src) +} +func (m *GnuPGPublicKeyCreateResponse) XXX_Size() int { + return m.Size() +} +func (m *GnuPGPublicKeyCreateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GnuPGPublicKeyCreateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GnuPGPublicKeyCreateResponse proto.InternalMessageInfo + +func (m *GnuPGPublicKeyCreateResponse) GetCreated() *v1alpha1.GnuPGPublicKeyList { + if m != nil { + return m.Created + } + return nil +} + +func (m *GnuPGPublicKeyCreateResponse) GetSkipped() []string { + if m != nil { + return m.Skipped + } + return nil +} + +// Generic (empty) response for GPG public key CRUD requests +type GnuPGPublicKeyResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GnuPGPublicKeyResponse) Reset() { *m = GnuPGPublicKeyResponse{} } +func (m *GnuPGPublicKeyResponse) String() string { return proto.CompactTextString(m) } +func (*GnuPGPublicKeyResponse) ProtoMessage() {} +func (*GnuPGPublicKeyResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8ba55a5eb76dc6fd, []int{3} +} +func (m *GnuPGPublicKeyResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GnuPGPublicKeyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GnuPGPublicKeyResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GnuPGPublicKeyResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GnuPGPublicKeyResponse.Merge(m, src) +} +func (m *GnuPGPublicKeyResponse) XXX_Size() int { + return m.Size() +} +func (m *GnuPGPublicKeyResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GnuPGPublicKeyResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GnuPGPublicKeyResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*GnuPGPublicKeyQuery)(nil), "gpgkey.GnuPGPublicKeyQuery") + proto.RegisterType((*GnuPGPublicKeyCreateRequest)(nil), "gpgkey.GnuPGPublicKeyCreateRequest") + proto.RegisterType((*GnuPGPublicKeyCreateResponse)(nil), "gpgkey.GnuPGPublicKeyCreateResponse") + proto.RegisterType((*GnuPGPublicKeyResponse)(nil), "gpgkey.GnuPGPublicKeyResponse") +} + +func init() { proto.RegisterFile("server/gpgkey/gpgkey.proto", fileDescriptor_8ba55a5eb76dc6fd) } + +var fileDescriptor_8ba55a5eb76dc6fd = []byte{ + // 491 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x41, 0x8b, 0xd3, 0x40, + 0x14, 0xc7, 0x99, 0xee, 0x9a, 0xb5, 0x23, 0x22, 0x8e, 0xcb, 0x6e, 0xcc, 0x96, 0x5a, 0xa2, 0x87, + 0xa2, 0x38, 0x63, 0xd7, 0x9b, 0x07, 0x0f, 0xba, 0x10, 0xca, 0x2a, 0xd4, 0x78, 0xf3, 0xa0, 0xa4, + 0xc9, 0x63, 0x36, 0x26, 0x66, 0xc6, 0xcc, 0xa4, 0x12, 0xc4, 0x8b, 0x17, 0x0f, 0x82, 0x17, 0xef, + 0x82, 0xdf, 0xc6, 0xa3, 0xe0, 0x17, 0x90, 0xe2, 0x07, 0x91, 0x4e, 0xa6, 0xee, 0xb6, 0x94, 0xba, + 0x87, 0x9e, 0xf2, 0x5e, 0xde, 0xbc, 0xf7, 0x7e, 0x6f, 0xde, 0x7f, 0xb0, 0xa7, 0xa0, 0x9c, 0x40, + 0xc9, 0xb8, 0xe4, 0x19, 0xd4, 0xf6, 0x43, 0x65, 0x29, 0xb4, 0x20, 0x4e, 0xe3, 0x79, 0xbb, 0x5c, + 0x70, 0x61, 0x7e, 0xb1, 0x99, 0xd5, 0x44, 0xbd, 0x0e, 0x17, 0x82, 0xe7, 0xc0, 0x22, 0x99, 0xb2, + 0xa8, 0x28, 0x84, 0x8e, 0x74, 0x2a, 0x0a, 0x65, 0xa3, 0x43, 0x9e, 0xea, 0x93, 0x6a, 0x4c, 0x63, + 0xf1, 0x86, 0x45, 0xa5, 0x49, 0x7f, 0x6d, 0x8c, 0xbb, 0x71, 0xc2, 0x64, 0xc6, 0x67, 0x69, 0x8a, + 0x45, 0x52, 0xe6, 0x69, 0x6c, 0x12, 0xd9, 0x64, 0x10, 0xe5, 0xf2, 0x24, 0x1a, 0x30, 0x0e, 0x05, + 0x94, 0x91, 0x86, 0xa4, 0x29, 0xe5, 0xdf, 0xc1, 0xd7, 0x82, 0xa2, 0x1a, 0x05, 0xa3, 0x6a, 0x9c, + 0xa7, 0xf1, 0x31, 0xd4, 0xcf, 0x2a, 0x28, 0x6b, 0xb2, 0x8b, 0x2f, 0x64, 0x50, 0x0f, 0x8f, 0x5c, + 0xd4, 0x43, 0xfd, 0x76, 0xd8, 0x38, 0xfe, 0x37, 0x84, 0x0f, 0x16, 0x4f, 0x3f, 0x2e, 0x21, 0xd2, + 0x10, 0xc2, 0xdb, 0x0a, 0x94, 0x26, 0x1c, 0xb7, 0xa5, 0x89, 0x64, 0x50, 0x9b, 0xcc, 0x4b, 0x87, + 0x43, 0x7a, 0xca, 0x4a, 0xe7, 0xac, 0xc6, 0x78, 0x15, 0x27, 0x54, 0x66, 0x9c, 0xce, 0x58, 0xe9, + 0x19, 0x56, 0x3a, 0x67, 0xa5, 0x8b, 0xad, 0xc2, 0xd3, 0xda, 0x64, 0x0f, 0x3b, 0x95, 0x54, 0x50, + 0x6a, 0xb7, 0xd5, 0x43, 0xfd, 0x8b, 0xa1, 0xf5, 0xfc, 0xef, 0x08, 0x77, 0x56, 0x03, 0x2a, 0x29, + 0x0a, 0x05, 0x84, 0xe3, 0x9d, 0xd8, 0xfc, 0x49, 0x2c, 0xdf, 0xd3, 0x8d, 0xf1, 0x3d, 0x49, 0x95, + 0x0e, 0xe7, 0xd5, 0x89, 0x8b, 0x77, 0x54, 0x96, 0x4a, 0x09, 0x89, 0xdb, 0xea, 0x6d, 0xf5, 0xdb, + 0xe1, 0xdc, 0xf5, 0x5d, 0xbc, 0xb7, 0x34, 0x98, 0x85, 0x3b, 0xfc, 0xb4, 0x8d, 0x2f, 0x07, 0xa3, + 0xe0, 0x18, 0xea, 0xe7, 0x50, 0x4e, 0xd2, 0x18, 0xc8, 0x67, 0x84, 0xb7, 0x67, 0x75, 0xc9, 0x01, + 0xb5, 0xe2, 0x59, 0xb1, 0x2c, 0x6f, 0xb3, 0x33, 0xf8, 0xfb, 0x1f, 0x7f, 0xfd, 0xf9, 0xda, 0xba, + 0x4a, 0xae, 0x18, 0xf5, 0x4d, 0x06, 0x56, 0xb7, 0x8a, 0x7c, 0x41, 0x78, 0x2b, 0x80, 0xff, 0xc0, + 0x6c, 0x6e, 0xe1, 0xfe, 0x0d, 0x03, 0x72, 0x9d, 0xec, 0x2f, 0x81, 0xb0, 0xf7, 0x46, 0x8e, 0x1f, + 0xc8, 0x3b, 0xec, 0x34, 0xfb, 0x25, 0x37, 0x57, 0x23, 0x2d, 0xc8, 0xd3, 0xbb, 0xb5, 0xfe, 0x50, + 0xb3, 0x05, 0xdf, 0x37, 0x5d, 0x3b, 0xfe, 0xf2, 0xf8, 0x0f, 0xce, 0xe8, 0xef, 0x25, 0x76, 0x8e, + 0x20, 0x07, 0x0d, 0xeb, 0xef, 0xa2, 0xbb, 0x3a, 0xf8, 0xaf, 0x95, 0xbd, 0xe9, 0xdb, 0xcb, 0xad, + 0x1e, 0x3d, 0xfc, 0x31, 0xed, 0xa2, 0x9f, 0xd3, 0x2e, 0xfa, 0x3d, 0xed, 0xa2, 0x17, 0xf7, 0xce, + 0xf1, 0xdc, 0xe3, 0x3c, 0x85, 0x42, 0xdb, 0x02, 0x63, 0xc7, 0x3c, 0xee, 0xfb, 0x7f, 0x03, 0x00, + 0x00, 0xff, 0xff, 0x98, 0x67, 0x12, 0xff, 0x81, 0x04, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GPGKeyServiceClient is the client API for GPGKeyService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GPGKeyServiceClient interface { + // List all available repository certificates + List(ctx context.Context, in *GnuPGPublicKeyQuery, opts ...grpc.CallOption) (*v1alpha1.GnuPGPublicKeyList, error) + // Get information about specified GPG public key from the server + Get(ctx context.Context, in *GnuPGPublicKeyQuery, opts ...grpc.CallOption) (*v1alpha1.GnuPGPublicKey, error) + // Create one or more GPG public keys in the server's configuration + Create(ctx context.Context, in *GnuPGPublicKeyCreateRequest, opts ...grpc.CallOption) (*GnuPGPublicKeyCreateResponse, error) + // Delete specified GPG public key from the server's configuration + Delete(ctx context.Context, in *GnuPGPublicKeyQuery, opts ...grpc.CallOption) (*GnuPGPublicKeyResponse, error) +} + +type gPGKeyServiceClient struct { + cc *grpc.ClientConn +} + +func NewGPGKeyServiceClient(cc *grpc.ClientConn) GPGKeyServiceClient { + return &gPGKeyServiceClient{cc} +} + +func (c *gPGKeyServiceClient) List(ctx context.Context, in *GnuPGPublicKeyQuery, opts ...grpc.CallOption) (*v1alpha1.GnuPGPublicKeyList, error) { + out := new(v1alpha1.GnuPGPublicKeyList) + err := c.cc.Invoke(ctx, "/gpgkey.GPGKeyService/List", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gPGKeyServiceClient) Get(ctx context.Context, in *GnuPGPublicKeyQuery, opts ...grpc.CallOption) (*v1alpha1.GnuPGPublicKey, error) { + out := new(v1alpha1.GnuPGPublicKey) + err := c.cc.Invoke(ctx, "/gpgkey.GPGKeyService/Get", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gPGKeyServiceClient) Create(ctx context.Context, in *GnuPGPublicKeyCreateRequest, opts ...grpc.CallOption) (*GnuPGPublicKeyCreateResponse, error) { + out := new(GnuPGPublicKeyCreateResponse) + err := c.cc.Invoke(ctx, "/gpgkey.GPGKeyService/Create", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gPGKeyServiceClient) Delete(ctx context.Context, in *GnuPGPublicKeyQuery, opts ...grpc.CallOption) (*GnuPGPublicKeyResponse, error) { + out := new(GnuPGPublicKeyResponse) + err := c.cc.Invoke(ctx, "/gpgkey.GPGKeyService/Delete", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GPGKeyServiceServer is the server API for GPGKeyService service. +type GPGKeyServiceServer interface { + // List all available repository certificates + List(context.Context, *GnuPGPublicKeyQuery) (*v1alpha1.GnuPGPublicKeyList, error) + // Get information about specified GPG public key from the server + Get(context.Context, *GnuPGPublicKeyQuery) (*v1alpha1.GnuPGPublicKey, error) + // Create one or more GPG public keys in the server's configuration + Create(context.Context, *GnuPGPublicKeyCreateRequest) (*GnuPGPublicKeyCreateResponse, error) + // Delete specified GPG public key from the server's configuration + Delete(context.Context, *GnuPGPublicKeyQuery) (*GnuPGPublicKeyResponse, error) +} + +// UnimplementedGPGKeyServiceServer can be embedded to have forward compatible implementations. +type UnimplementedGPGKeyServiceServer struct { +} + +func (*UnimplementedGPGKeyServiceServer) List(ctx context.Context, req *GnuPGPublicKeyQuery) (*v1alpha1.GnuPGPublicKeyList, error) { + return nil, status.Errorf(codes.Unimplemented, "method List not implemented") +} +func (*UnimplementedGPGKeyServiceServer) Get(ctx context.Context, req *GnuPGPublicKeyQuery) (*v1alpha1.GnuPGPublicKey, error) { + return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") +} +func (*UnimplementedGPGKeyServiceServer) Create(ctx context.Context, req *GnuPGPublicKeyCreateRequest) (*GnuPGPublicKeyCreateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") +} +func (*UnimplementedGPGKeyServiceServer) Delete(ctx context.Context, req *GnuPGPublicKeyQuery) (*GnuPGPublicKeyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} + +func RegisterGPGKeyServiceServer(s *grpc.Server, srv GPGKeyServiceServer) { + s.RegisterService(&_GPGKeyService_serviceDesc, srv) +} + +func _GPGKeyService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GnuPGPublicKeyQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GPGKeyServiceServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gpgkey.GPGKeyService/List", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GPGKeyServiceServer).List(ctx, req.(*GnuPGPublicKeyQuery)) + } + return interceptor(ctx, in, info, handler) +} + +func _GPGKeyService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GnuPGPublicKeyQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GPGKeyServiceServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gpgkey.GPGKeyService/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GPGKeyServiceServer).Get(ctx, req.(*GnuPGPublicKeyQuery)) + } + return interceptor(ctx, in, info, handler) +} + +func _GPGKeyService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GnuPGPublicKeyCreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GPGKeyServiceServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gpgkey.GPGKeyService/Create", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GPGKeyServiceServer).Create(ctx, req.(*GnuPGPublicKeyCreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GPGKeyService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GnuPGPublicKeyQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GPGKeyServiceServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gpgkey.GPGKeyService/Delete", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GPGKeyServiceServer).Delete(ctx, req.(*GnuPGPublicKeyQuery)) + } + return interceptor(ctx, in, info, handler) +} + +var _GPGKeyService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "gpgkey.GPGKeyService", + HandlerType: (*GPGKeyServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "List", + Handler: _GPGKeyService_List_Handler, + }, + { + MethodName: "Get", + Handler: _GPGKeyService_Get_Handler, + }, + { + MethodName: "Create", + Handler: _GPGKeyService_Create_Handler, + }, + { + MethodName: "Delete", + Handler: _GPGKeyService_Delete_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "server/gpgkey/gpgkey.proto", +} + +func (m *GnuPGPublicKeyQuery) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GnuPGPublicKeyQuery) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GnuPGPublicKeyQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.KeyID) > 0 { + i -= len(m.KeyID) + copy(dAtA[i:], m.KeyID) + i = encodeVarintGpgkey(dAtA, i, uint64(len(m.KeyID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GnuPGPublicKeyCreateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GnuPGPublicKeyCreateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GnuPGPublicKeyCreateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Upsert { + i-- + if m.Upsert { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.Publickey != nil { + { + size, err := m.Publickey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGpgkey(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GnuPGPublicKeyCreateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GnuPGPublicKeyCreateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GnuPGPublicKeyCreateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Skipped) > 0 { + for iNdEx := len(m.Skipped) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Skipped[iNdEx]) + copy(dAtA[i:], m.Skipped[iNdEx]) + i = encodeVarintGpgkey(dAtA, i, uint64(len(m.Skipped[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Created != nil { + { + size, err := m.Created.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGpgkey(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GnuPGPublicKeyResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GnuPGPublicKeyResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GnuPGPublicKeyResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + return len(dAtA) - i, nil +} + +func encodeVarintGpgkey(dAtA []byte, offset int, v uint64) int { + offset -= sovGpgkey(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GnuPGPublicKeyQuery) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.KeyID) + if l > 0 { + n += 1 + l + sovGpgkey(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *GnuPGPublicKeyCreateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Publickey != nil { + l = m.Publickey.Size() + n += 1 + l + sovGpgkey(uint64(l)) + } + if m.Upsert { + n += 2 + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *GnuPGPublicKeyCreateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Created != nil { + l = m.Created.Size() + n += 1 + l + sovGpgkey(uint64(l)) + } + if len(m.Skipped) > 0 { + for _, s := range m.Skipped { + l = len(s) + n += 1 + l + sovGpgkey(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *GnuPGPublicKeyResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovGpgkey(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGpgkey(x uint64) (n int) { + return sovGpgkey(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GnuPGPublicKeyQuery) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GnuPGPublicKeyQuery: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GnuPGPublicKeyQuery: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KeyID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGpgkey + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGpgkey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.KeyID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGpgkey(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GnuPGPublicKeyCreateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GnuPGPublicKeyCreateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GnuPGPublicKeyCreateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Publickey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGpgkey + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGpgkey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Publickey == nil { + m.Publickey = &v1alpha1.GnuPGPublicKey{} + } + if err := m.Publickey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Upsert", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Upsert = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipGpgkey(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GnuPGPublicKeyCreateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GnuPGPublicKeyCreateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GnuPGPublicKeyCreateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Created", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGpgkey + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGpgkey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Created == nil { + m.Created = &v1alpha1.GnuPGPublicKeyList{} + } + if err := m.Created.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Skipped", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGpgkey + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGpgkey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Skipped = append(m.Skipped, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGpgkey(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GnuPGPublicKeyResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGpgkey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GnuPGPublicKeyResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GnuPGPublicKeyResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipGpgkey(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGpgkey + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGpgkey(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGpgkey + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGpgkey + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGpgkey + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGpgkey + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGpgkey + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGpgkey + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGpgkey = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGpgkey = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGpgkey = fmt.Errorf("proto: unexpected end of group") +) diff --git a/pkg/apiclient/gpgkey/gpgkey.pb.gw.go b/pkg/apiclient/gpgkey/gpgkey.pb.gw.go new file mode 100644 index 0000000000000..10c863b921096 --- /dev/null +++ b/pkg/apiclient/gpgkey/gpgkey.pb.gw.go @@ -0,0 +1,288 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: server/gpgkey/gpgkey.proto + +/* +Package gpgkey is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package gpgkey + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray + +var ( + filter_GPGKeyService_List_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GPGKeyService_List_0(ctx context.Context, marshaler runtime.Marshaler, client GPGKeyServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GnuPGPublicKeyQuery + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GPGKeyService_List_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.List(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_GPGKeyService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client GPGKeyServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GnuPGPublicKeyQuery + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["keyID"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "keyID") + } + + protoReq.KeyID, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "keyID", err) + } + + msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_GPGKeyService_Create_0 = &utilities.DoubleArray{Encoding: map[string]int{"publickey": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_GPGKeyService_Create_0(ctx context.Context, marshaler runtime.Marshaler, client GPGKeyServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GnuPGPublicKeyCreateRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.Publickey); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GPGKeyService_Create_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Create(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_GPGKeyService_Delete_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GPGKeyService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, client GPGKeyServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GnuPGPublicKeyQuery + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GPGKeyService_Delete_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Delete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterGPGKeyServiceHandlerFromEndpoint is same as RegisterGPGKeyServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterGPGKeyServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterGPGKeyServiceHandler(ctx, mux, conn) +} + +// RegisterGPGKeyServiceHandler registers the http handlers for service GPGKeyService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterGPGKeyServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterGPGKeyServiceHandlerClient(ctx, mux, NewGPGKeyServiceClient(conn)) +} + +// RegisterGPGKeyServiceHandler registers the http handlers for service GPGKeyService to "mux". +// The handlers forward requests to the grpc endpoint over the given implementation of "GPGKeyServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "GPGKeyServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "GPGKeyServiceClient" to call the correct interceptors. +func RegisterGPGKeyServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client GPGKeyServiceClient) error { + + mux.Handle("GET", pattern_GPGKeyService_List_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GPGKeyService_List_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GPGKeyService_List_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GPGKeyService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GPGKeyService_Get_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GPGKeyService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_GPGKeyService_Create_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GPGKeyService_Create_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GPGKeyService_Create_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_GPGKeyService_Delete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GPGKeyService_Delete_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GPGKeyService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_GPGKeyService_List_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "gpgkeys"}, "")) + + pattern_GPGKeyService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "gpgkeys", "keyID"}, "")) + + pattern_GPGKeyService_Create_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "gpgkeys"}, "")) + + pattern_GPGKeyService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "gpgkeys"}, "")) +) + +var ( + forward_GPGKeyService_List_0 = runtime.ForwardResponseMessage + + forward_GPGKeyService_Get_0 = runtime.ForwardResponseMessage + + forward_GPGKeyService_Create_0 = runtime.ForwardResponseMessage + + forward_GPGKeyService_Delete_0 = runtime.ForwardResponseMessage +) diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index 6432fafcc2634..b4c13e7bf4d24 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -4,6 +4,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/appli API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,AppProjectSpec,NamespaceResourceBlacklist API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,AppProjectSpec,NamespaceResourceWhitelist API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,AppProjectSpec,Roles +API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,AppProjectSpec,SignatureKeys API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,AppProjectSpec,SourceRepos API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,ApplicationList,Items API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,ApplicationSourceHelm,FileParameters @@ -24,6 +25,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/appli API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,ClusterList,Items API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,Command,Args API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,Command,Command +API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,GnuPGPublicKeyList,Items API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,Operation,Info API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,ProjectRole,Groups API rule violation: list_type_missing,github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1,ProjectRole,JWTTokens diff --git a/pkg/apis/application/v1alpha1/generated.pb.go b/pkg/apis/application/v1alpha1/generated.pb.go index f04b72467b250..bac2a512ac90b 100644 --- a/pkg/apis/application/v1alpha1/generated.pb.go +++ b/pkg/apis/application/v1alpha1/generated.pb.go @@ -847,10 +847,66 @@ func (m *EnvEntry) XXX_DiscardUnknown() { var xxx_messageInfo_EnvEntry proto.InternalMessageInfo +func (m *GnuPGPublicKey) Reset() { *m = GnuPGPublicKey{} } +func (*GnuPGPublicKey) ProtoMessage() {} +func (*GnuPGPublicKey) Descriptor() ([]byte, []int) { + return fileDescriptor_e7dc23c2911a1a00, []int{29} +} +func (m *GnuPGPublicKey) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GnuPGPublicKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *GnuPGPublicKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_GnuPGPublicKey.Merge(m, src) +} +func (m *GnuPGPublicKey) XXX_Size() int { + return m.Size() +} +func (m *GnuPGPublicKey) XXX_DiscardUnknown() { + xxx_messageInfo_GnuPGPublicKey.DiscardUnknown(m) +} + +var xxx_messageInfo_GnuPGPublicKey proto.InternalMessageInfo + +func (m *GnuPGPublicKeyList) Reset() { *m = GnuPGPublicKeyList{} } +func (*GnuPGPublicKeyList) ProtoMessage() {} +func (*GnuPGPublicKeyList) Descriptor() ([]byte, []int) { + return fileDescriptor_e7dc23c2911a1a00, []int{30} +} +func (m *GnuPGPublicKeyList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GnuPGPublicKeyList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *GnuPGPublicKeyList) XXX_Merge(src proto.Message) { + xxx_messageInfo_GnuPGPublicKeyList.Merge(m, src) +} +func (m *GnuPGPublicKeyList) XXX_Size() int { + return m.Size() +} +func (m *GnuPGPublicKeyList) XXX_DiscardUnknown() { + xxx_messageInfo_GnuPGPublicKeyList.DiscardUnknown(m) +} + +var xxx_messageInfo_GnuPGPublicKeyList proto.InternalMessageInfo + func (m *HealthStatus) Reset() { *m = HealthStatus{} } func (*HealthStatus) ProtoMessage() {} func (*HealthStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{29} + return fileDescriptor_e7dc23c2911a1a00, []int{31} } func (m *HealthStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -878,7 +934,7 @@ var xxx_messageInfo_HealthStatus proto.InternalMessageInfo func (m *HelmFileParameter) Reset() { *m = HelmFileParameter{} } func (*HelmFileParameter) ProtoMessage() {} func (*HelmFileParameter) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{30} + return fileDescriptor_e7dc23c2911a1a00, []int{32} } func (m *HelmFileParameter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -906,7 +962,7 @@ var xxx_messageInfo_HelmFileParameter proto.InternalMessageInfo func (m *HelmParameter) Reset() { *m = HelmParameter{} } func (*HelmParameter) ProtoMessage() {} func (*HelmParameter) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{31} + return fileDescriptor_e7dc23c2911a1a00, []int{33} } func (m *HelmParameter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -934,7 +990,7 @@ var xxx_messageInfo_HelmParameter proto.InternalMessageInfo func (m *Info) Reset() { *m = Info{} } func (*Info) ProtoMessage() {} func (*Info) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{32} + return fileDescriptor_e7dc23c2911a1a00, []int{34} } func (m *Info) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -962,7 +1018,7 @@ var xxx_messageInfo_Info proto.InternalMessageInfo func (m *InfoItem) Reset() { *m = InfoItem{} } func (*InfoItem) ProtoMessage() {} func (*InfoItem) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{33} + return fileDescriptor_e7dc23c2911a1a00, []int{35} } func (m *InfoItem) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -990,7 +1046,7 @@ var xxx_messageInfo_InfoItem proto.InternalMessageInfo func (m *JWTToken) Reset() { *m = JWTToken{} } func (*JWTToken) ProtoMessage() {} func (*JWTToken) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{34} + return fileDescriptor_e7dc23c2911a1a00, []int{36} } func (m *JWTToken) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1018,7 +1074,7 @@ var xxx_messageInfo_JWTToken proto.InternalMessageInfo func (m *JsonnetVar) Reset() { *m = JsonnetVar{} } func (*JsonnetVar) ProtoMessage() {} func (*JsonnetVar) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{35} + return fileDescriptor_e7dc23c2911a1a00, []int{37} } func (m *JsonnetVar) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1046,7 +1102,7 @@ var xxx_messageInfo_JsonnetVar proto.InternalMessageInfo func (m *KnownTypeField) Reset() { *m = KnownTypeField{} } func (*KnownTypeField) ProtoMessage() {} func (*KnownTypeField) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{36} + return fileDescriptor_e7dc23c2911a1a00, []int{38} } func (m *KnownTypeField) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1074,7 +1130,7 @@ var xxx_messageInfo_KnownTypeField proto.InternalMessageInfo func (m *KsonnetParameter) Reset() { *m = KsonnetParameter{} } func (*KsonnetParameter) ProtoMessage() {} func (*KsonnetParameter) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{37} + return fileDescriptor_e7dc23c2911a1a00, []int{39} } func (m *KsonnetParameter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1102,7 +1158,7 @@ var xxx_messageInfo_KsonnetParameter proto.InternalMessageInfo func (m *KustomizeOptions) Reset() { *m = KustomizeOptions{} } func (*KustomizeOptions) ProtoMessage() {} func (*KustomizeOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{38} + return fileDescriptor_e7dc23c2911a1a00, []int{40} } func (m *KustomizeOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1130,7 +1186,7 @@ var xxx_messageInfo_KustomizeOptions proto.InternalMessageInfo func (m *Operation) Reset() { *m = Operation{} } func (*Operation) ProtoMessage() {} func (*Operation) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{39} + return fileDescriptor_e7dc23c2911a1a00, []int{41} } func (m *Operation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1158,7 +1214,7 @@ var xxx_messageInfo_Operation proto.InternalMessageInfo func (m *OperationInitiator) Reset() { *m = OperationInitiator{} } func (*OperationInitiator) ProtoMessage() {} func (*OperationInitiator) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{40} + return fileDescriptor_e7dc23c2911a1a00, []int{42} } func (m *OperationInitiator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1186,7 +1242,7 @@ var xxx_messageInfo_OperationInitiator proto.InternalMessageInfo func (m *OperationState) Reset() { *m = OperationState{} } func (*OperationState) ProtoMessage() {} func (*OperationState) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{41} + return fileDescriptor_e7dc23c2911a1a00, []int{43} } func (m *OperationState) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1214,7 +1270,7 @@ var xxx_messageInfo_OperationState proto.InternalMessageInfo func (m *OrphanedResourcesMonitorSettings) Reset() { *m = OrphanedResourcesMonitorSettings{} } func (*OrphanedResourcesMonitorSettings) ProtoMessage() {} func (*OrphanedResourcesMonitorSettings) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{42} + return fileDescriptor_e7dc23c2911a1a00, []int{44} } func (m *OrphanedResourcesMonitorSettings) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1242,7 +1298,7 @@ var xxx_messageInfo_OrphanedResourcesMonitorSettings proto.InternalMessageInfo func (m *ProjectRole) Reset() { *m = ProjectRole{} } func (*ProjectRole) ProtoMessage() {} func (*ProjectRole) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{43} + return fileDescriptor_e7dc23c2911a1a00, []int{45} } func (m *ProjectRole) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1270,7 +1326,7 @@ var xxx_messageInfo_ProjectRole proto.InternalMessageInfo func (m *RepoCreds) Reset() { *m = RepoCreds{} } func (*RepoCreds) ProtoMessage() {} func (*RepoCreds) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{44} + return fileDescriptor_e7dc23c2911a1a00, []int{46} } func (m *RepoCreds) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1298,7 +1354,7 @@ var xxx_messageInfo_RepoCreds proto.InternalMessageInfo func (m *RepoCredsList) Reset() { *m = RepoCredsList{} } func (*RepoCredsList) ProtoMessage() {} func (*RepoCredsList) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{45} + return fileDescriptor_e7dc23c2911a1a00, []int{47} } func (m *RepoCredsList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1326,7 +1382,7 @@ var xxx_messageInfo_RepoCredsList proto.InternalMessageInfo func (m *Repository) Reset() { *m = Repository{} } func (*Repository) ProtoMessage() {} func (*Repository) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{46} + return fileDescriptor_e7dc23c2911a1a00, []int{48} } func (m *Repository) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1354,7 +1410,7 @@ var xxx_messageInfo_Repository proto.InternalMessageInfo func (m *RepositoryCertificate) Reset() { *m = RepositoryCertificate{} } func (*RepositoryCertificate) ProtoMessage() {} func (*RepositoryCertificate) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{47} + return fileDescriptor_e7dc23c2911a1a00, []int{49} } func (m *RepositoryCertificate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1382,7 +1438,7 @@ var xxx_messageInfo_RepositoryCertificate proto.InternalMessageInfo func (m *RepositoryCertificateList) Reset() { *m = RepositoryCertificateList{} } func (*RepositoryCertificateList) ProtoMessage() {} func (*RepositoryCertificateList) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{48} + return fileDescriptor_e7dc23c2911a1a00, []int{50} } func (m *RepositoryCertificateList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1410,7 +1466,7 @@ var xxx_messageInfo_RepositoryCertificateList proto.InternalMessageInfo func (m *RepositoryList) Reset() { *m = RepositoryList{} } func (*RepositoryList) ProtoMessage() {} func (*RepositoryList) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{49} + return fileDescriptor_e7dc23c2911a1a00, []int{51} } func (m *RepositoryList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1438,7 +1494,7 @@ var xxx_messageInfo_RepositoryList proto.InternalMessageInfo func (m *ResourceAction) Reset() { *m = ResourceAction{} } func (*ResourceAction) ProtoMessage() {} func (*ResourceAction) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{50} + return fileDescriptor_e7dc23c2911a1a00, []int{52} } func (m *ResourceAction) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1466,7 +1522,7 @@ var xxx_messageInfo_ResourceAction proto.InternalMessageInfo func (m *ResourceActionDefinition) Reset() { *m = ResourceActionDefinition{} } func (*ResourceActionDefinition) ProtoMessage() {} func (*ResourceActionDefinition) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{51} + return fileDescriptor_e7dc23c2911a1a00, []int{53} } func (m *ResourceActionDefinition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1494,7 +1550,7 @@ var xxx_messageInfo_ResourceActionDefinition proto.InternalMessageInfo func (m *ResourceActionParam) Reset() { *m = ResourceActionParam{} } func (*ResourceActionParam) ProtoMessage() {} func (*ResourceActionParam) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{52} + return fileDescriptor_e7dc23c2911a1a00, []int{54} } func (m *ResourceActionParam) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1522,7 +1578,7 @@ var xxx_messageInfo_ResourceActionParam proto.InternalMessageInfo func (m *ResourceActions) Reset() { *m = ResourceActions{} } func (*ResourceActions) ProtoMessage() {} func (*ResourceActions) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{53} + return fileDescriptor_e7dc23c2911a1a00, []int{55} } func (m *ResourceActions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1550,7 +1606,7 @@ var xxx_messageInfo_ResourceActions proto.InternalMessageInfo func (m *ResourceDiff) Reset() { *m = ResourceDiff{} } func (*ResourceDiff) ProtoMessage() {} func (*ResourceDiff) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{54} + return fileDescriptor_e7dc23c2911a1a00, []int{56} } func (m *ResourceDiff) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1578,7 +1634,7 @@ var xxx_messageInfo_ResourceDiff proto.InternalMessageInfo func (m *ResourceIgnoreDifferences) Reset() { *m = ResourceIgnoreDifferences{} } func (*ResourceIgnoreDifferences) ProtoMessage() {} func (*ResourceIgnoreDifferences) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{55} + return fileDescriptor_e7dc23c2911a1a00, []int{57} } func (m *ResourceIgnoreDifferences) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1606,7 +1662,7 @@ var xxx_messageInfo_ResourceIgnoreDifferences proto.InternalMessageInfo func (m *ResourceNetworkingInfo) Reset() { *m = ResourceNetworkingInfo{} } func (*ResourceNetworkingInfo) ProtoMessage() {} func (*ResourceNetworkingInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{56} + return fileDescriptor_e7dc23c2911a1a00, []int{58} } func (m *ResourceNetworkingInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1634,7 +1690,7 @@ var xxx_messageInfo_ResourceNetworkingInfo proto.InternalMessageInfo func (m *ResourceNode) Reset() { *m = ResourceNode{} } func (*ResourceNode) ProtoMessage() {} func (*ResourceNode) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{57} + return fileDescriptor_e7dc23c2911a1a00, []int{59} } func (m *ResourceNode) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1662,7 +1718,7 @@ var xxx_messageInfo_ResourceNode proto.InternalMessageInfo func (m *ResourceOverride) Reset() { *m = ResourceOverride{} } func (*ResourceOverride) ProtoMessage() {} func (*ResourceOverride) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{58} + return fileDescriptor_e7dc23c2911a1a00, []int{60} } func (m *ResourceOverride) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1690,7 +1746,7 @@ var xxx_messageInfo_ResourceOverride proto.InternalMessageInfo func (m *ResourceRef) Reset() { *m = ResourceRef{} } func (*ResourceRef) ProtoMessage() {} func (*ResourceRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{59} + return fileDescriptor_e7dc23c2911a1a00, []int{61} } func (m *ResourceRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1718,7 +1774,7 @@ var xxx_messageInfo_ResourceRef proto.InternalMessageInfo func (m *ResourceResult) Reset() { *m = ResourceResult{} } func (*ResourceResult) ProtoMessage() {} func (*ResourceResult) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{60} + return fileDescriptor_e7dc23c2911a1a00, []int{62} } func (m *ResourceResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1746,7 +1802,7 @@ var xxx_messageInfo_ResourceResult proto.InternalMessageInfo func (m *ResourceStatus) Reset() { *m = ResourceStatus{} } func (*ResourceStatus) ProtoMessage() {} func (*ResourceStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{61} + return fileDescriptor_e7dc23c2911a1a00, []int{63} } func (m *ResourceStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1774,7 +1830,7 @@ var xxx_messageInfo_ResourceStatus proto.InternalMessageInfo func (m *RevisionHistory) Reset() { *m = RevisionHistory{} } func (*RevisionHistory) ProtoMessage() {} func (*RevisionHistory) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{62} + return fileDescriptor_e7dc23c2911a1a00, []int{64} } func (m *RevisionHistory) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1802,7 +1858,7 @@ var xxx_messageInfo_RevisionHistory proto.InternalMessageInfo func (m *RevisionMetadata) Reset() { *m = RevisionMetadata{} } func (*RevisionMetadata) ProtoMessage() {} func (*RevisionMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{63} + return fileDescriptor_e7dc23c2911a1a00, []int{65} } func (m *RevisionMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1827,10 +1883,38 @@ func (m *RevisionMetadata) XXX_DiscardUnknown() { var xxx_messageInfo_RevisionMetadata proto.InternalMessageInfo +func (m *SignatureKey) Reset() { *m = SignatureKey{} } +func (*SignatureKey) ProtoMessage() {} +func (*SignatureKey) Descriptor() ([]byte, []int) { + return fileDescriptor_e7dc23c2911a1a00, []int{66} +} +func (m *SignatureKey) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SignatureKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *SignatureKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignatureKey.Merge(m, src) +} +func (m *SignatureKey) XXX_Size() int { + return m.Size() +} +func (m *SignatureKey) XXX_DiscardUnknown() { + xxx_messageInfo_SignatureKey.DiscardUnknown(m) +} + +var xxx_messageInfo_SignatureKey proto.InternalMessageInfo + func (m *SyncOperation) Reset() { *m = SyncOperation{} } func (*SyncOperation) ProtoMessage() {} func (*SyncOperation) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{64} + return fileDescriptor_e7dc23c2911a1a00, []int{67} } func (m *SyncOperation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1858,7 +1942,7 @@ var xxx_messageInfo_SyncOperation proto.InternalMessageInfo func (m *SyncOperationResource) Reset() { *m = SyncOperationResource{} } func (*SyncOperationResource) ProtoMessage() {} func (*SyncOperationResource) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{65} + return fileDescriptor_e7dc23c2911a1a00, []int{68} } func (m *SyncOperationResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1886,7 +1970,7 @@ var xxx_messageInfo_SyncOperationResource proto.InternalMessageInfo func (m *SyncOperationResult) Reset() { *m = SyncOperationResult{} } func (*SyncOperationResult) ProtoMessage() {} func (*SyncOperationResult) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{66} + return fileDescriptor_e7dc23c2911a1a00, []int{69} } func (m *SyncOperationResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1914,7 +1998,7 @@ var xxx_messageInfo_SyncOperationResult proto.InternalMessageInfo func (m *SyncPolicy) Reset() { *m = SyncPolicy{} } func (*SyncPolicy) ProtoMessage() {} func (*SyncPolicy) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{67} + return fileDescriptor_e7dc23c2911a1a00, []int{70} } func (m *SyncPolicy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1942,7 +2026,7 @@ var xxx_messageInfo_SyncPolicy proto.InternalMessageInfo func (m *SyncPolicyAutomated) Reset() { *m = SyncPolicyAutomated{} } func (*SyncPolicyAutomated) ProtoMessage() {} func (*SyncPolicyAutomated) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{68} + return fileDescriptor_e7dc23c2911a1a00, []int{71} } func (m *SyncPolicyAutomated) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1970,7 +2054,7 @@ var xxx_messageInfo_SyncPolicyAutomated proto.InternalMessageInfo func (m *SyncStatus) Reset() { *m = SyncStatus{} } func (*SyncStatus) ProtoMessage() {} func (*SyncStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{69} + return fileDescriptor_e7dc23c2911a1a00, []int{72} } func (m *SyncStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1998,7 +2082,7 @@ var xxx_messageInfo_SyncStatus proto.InternalMessageInfo func (m *SyncStrategy) Reset() { *m = SyncStrategy{} } func (*SyncStrategy) ProtoMessage() {} func (*SyncStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{70} + return fileDescriptor_e7dc23c2911a1a00, []int{73} } func (m *SyncStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2026,7 +2110,7 @@ var xxx_messageInfo_SyncStrategy proto.InternalMessageInfo func (m *SyncStrategyApply) Reset() { *m = SyncStrategyApply{} } func (*SyncStrategyApply) ProtoMessage() {} func (*SyncStrategyApply) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{71} + return fileDescriptor_e7dc23c2911a1a00, []int{74} } func (m *SyncStrategyApply) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2054,7 +2138,7 @@ var xxx_messageInfo_SyncStrategyApply proto.InternalMessageInfo func (m *SyncStrategyHook) Reset() { *m = SyncStrategyHook{} } func (*SyncStrategyHook) ProtoMessage() {} func (*SyncStrategyHook) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{72} + return fileDescriptor_e7dc23c2911a1a00, []int{75} } func (m *SyncStrategyHook) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2082,7 +2166,7 @@ var xxx_messageInfo_SyncStrategyHook proto.InternalMessageInfo func (m *SyncWindow) Reset() { *m = SyncWindow{} } func (*SyncWindow) ProtoMessage() {} func (*SyncWindow) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{73} + return fileDescriptor_e7dc23c2911a1a00, []int{76} } func (m *SyncWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2110,7 +2194,7 @@ var xxx_messageInfo_SyncWindow proto.InternalMessageInfo func (m *TLSClientConfig) Reset() { *m = TLSClientConfig{} } func (*TLSClientConfig) ProtoMessage() {} func (*TLSClientConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_e7dc23c2911a1a00, []int{74} + return fileDescriptor_e7dc23c2911a1a00, []int{77} } func (m *TLSClientConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2166,6 +2250,8 @@ func init() { proto.RegisterType((*ConfigManagementPlugin)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ConfigManagementPlugin") proto.RegisterType((*ConnectionState)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ConnectionState") proto.RegisterType((*EnvEntry)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.EnvEntry") + proto.RegisterType((*GnuPGPublicKey)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.GnuPGPublicKey") + proto.RegisterType((*GnuPGPublicKeyList)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.GnuPGPublicKeyList") proto.RegisterType((*HealthStatus)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.HealthStatus") proto.RegisterType((*HelmFileParameter)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.HelmFileParameter") proto.RegisterType((*HelmParameter)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.HelmParameter") @@ -2203,6 +2289,7 @@ func init() { proto.RegisterType((*ResourceStatus)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ResourceStatus") proto.RegisterType((*RevisionHistory)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.RevisionHistory") proto.RegisterType((*RevisionMetadata)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.RevisionMetadata") + proto.RegisterType((*SignatureKey)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.SignatureKey") proto.RegisterType((*SyncOperation)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.SyncOperation") proto.RegisterType((*SyncOperationResource)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.SyncOperationResource") proto.RegisterType((*SyncOperationResult)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.SyncOperationResult") @@ -2221,333 +2308,344 @@ func init() { } var fileDescriptor_e7dc23c2911a1a00 = []byte{ - // 5212 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x3c, 0x5b, 0x8c, 0x1c, 0xc7, - 0x71, 0x9a, 0xdd, 0xbb, 0xbd, 0xdd, 0xba, 0x07, 0x79, 0x4d, 0x51, 0x3e, 0x33, 0x36, 0x8f, 0x18, - 0xc1, 0xb6, 0x12, 0x4b, 0x7b, 0x11, 0xa1, 0x24, 0xb4, 0x95, 0x58, 0xbe, 0xbd, 0xe3, 0xe3, 0xc8, - 0xbb, 0xe3, 0xa9, 0xf7, 0x28, 0x02, 0xf2, 0x23, 0x1a, 0xce, 0xf4, 0xee, 0x0e, 0x6f, 0x77, 0x66, - 0x34, 0x33, 0x7b, 0xe4, 0x29, 0xb1, 0x63, 0x27, 0x56, 0x60, 0x38, 0x52, 0x10, 0xc0, 0xc8, 0x57, - 0xe2, 0x20, 0xc9, 0x5f, 0xfc, 0x67, 0x18, 0x08, 0x10, 0x20, 0x5f, 0xfa, 0x70, 0x84, 0x7c, 0x18, - 0x8e, 0x21, 0x24, 0xca, 0x03, 0x97, 0x88, 0xfe, 0x09, 0x92, 0x0f, 0x27, 0x08, 0xf2, 0xc3, 0xaf, - 0xa0, 0xdf, 0x3d, 0xb3, 0xbb, 0xbc, 0x3d, 0xee, 0x90, 0x36, 0x9c, 0xbf, 0x9d, 0xaa, 0xea, 0xaa, - 0xea, 0xea, 0xea, 0xee, 0xea, 0xea, 0xea, 0x85, 0x8d, 0xb6, 0x9f, 0x76, 0xfa, 0xb7, 0xea, 0x6e, - 0xd8, 0x5b, 0x71, 0xe2, 0x76, 0x18, 0xc5, 0xe1, 0x6d, 0xf6, 0xe3, 0x39, 0xd7, 0x5b, 0x89, 0xf6, - 0xda, 0x2b, 0x4e, 0xe4, 0x27, 0x2b, 0x4e, 0x14, 0x75, 0x7d, 0xd7, 0x49, 0xfd, 0x30, 0x58, 0xd9, - 0x7f, 0xde, 0xe9, 0x46, 0x1d, 0xe7, 0xf9, 0x95, 0x36, 0x09, 0x48, 0xec, 0xa4, 0xc4, 0xab, 0x47, - 0x71, 0x98, 0x86, 0xe8, 0x53, 0x9a, 0x55, 0x5d, 0xb2, 0x62, 0x3f, 0x7e, 0xdd, 0xf5, 0xea, 0xd1, - 0x5e, 0xbb, 0x4e, 0x59, 0xd5, 0x0d, 0x56, 0x75, 0xc9, 0xea, 0xcc, 0x73, 0x86, 0x16, 0xed, 0xb0, - 0x1d, 0xae, 0x30, 0x8e, 0xb7, 0xfa, 0x2d, 0xf6, 0xc5, 0x3e, 0xd8, 0x2f, 0x2e, 0xe9, 0x8c, 0xbd, - 0x77, 0x21, 0xa9, 0xfb, 0x21, 0xd5, 0x6d, 0xc5, 0x0d, 0x63, 0xb2, 0xb2, 0x3f, 0xa0, 0xcd, 0x99, - 0x17, 0x34, 0x4d, 0xcf, 0x71, 0x3b, 0x7e, 0x40, 0xe2, 0x03, 0xdd, 0xa1, 0x1e, 0x49, 0x9d, 0x61, - 0xad, 0x56, 0x46, 0xb5, 0x8a, 0xfb, 0x41, 0xea, 0xf7, 0xc8, 0x40, 0x83, 0x5f, 0x3e, 0xaa, 0x41, - 0xe2, 0x76, 0x48, 0xcf, 0xc9, 0xb7, 0xb3, 0x5f, 0x87, 0xf9, 0xd5, 0x9b, 0xcd, 0xd5, 0x7e, 0xda, - 0x59, 0x0b, 0x83, 0x96, 0xdf, 0x46, 0xbf, 0x04, 0xb3, 0x6e, 0xb7, 0x9f, 0xa4, 0x24, 0xde, 0x76, - 0x7a, 0x64, 0xc9, 0x3a, 0x67, 0x3d, 0x53, 0x6b, 0x9c, 0x7a, 0xf7, 0x70, 0xf9, 0x89, 0x7b, 0x87, - 0xcb, 0xb3, 0x6b, 0x1a, 0x85, 0x4d, 0x3a, 0xf4, 0xf3, 0x30, 0x13, 0x87, 0x5d, 0xb2, 0x8a, 0xb7, - 0x97, 0x4a, 0xac, 0xc9, 0x09, 0xd1, 0x64, 0x06, 0x73, 0x30, 0x96, 0x78, 0xfb, 0x9f, 0x2d, 0x80, - 0xd5, 0x28, 0xda, 0x89, 0xc3, 0xdb, 0xc4, 0x4d, 0xd1, 0x6b, 0x50, 0xa5, 0x56, 0xf0, 0x9c, 0xd4, - 0x61, 0xd2, 0x66, 0xcf, 0xff, 0x62, 0x9d, 0x77, 0xa6, 0x6e, 0x76, 0x46, 0x8f, 0x1c, 0xa5, 0xae, - 0xef, 0x3f, 0x5f, 0xbf, 0x7e, 0x8b, 0xb6, 0xdf, 0x22, 0xa9, 0xd3, 0x40, 0x42, 0x18, 0x68, 0x18, - 0x56, 0x5c, 0xd1, 0x1e, 0x4c, 0x25, 0x11, 0x71, 0x99, 0x62, 0xb3, 0xe7, 0x37, 0xea, 0x0f, 0xed, - 0x1f, 0x75, 0xad, 0x76, 0x33, 0x22, 0x6e, 0x63, 0x4e, 0x88, 0x9d, 0xa2, 0x5f, 0x98, 0x09, 0xb1, - 0xff, 0xc9, 0x82, 0x05, 0x4d, 0xb6, 0xe9, 0x27, 0x29, 0xfa, 0xfc, 0x40, 0x0f, 0xeb, 0xe3, 0xf5, - 0x90, 0xb6, 0x66, 0xfd, 0x3b, 0x29, 0x04, 0x55, 0x25, 0xc4, 0xe8, 0xdd, 0x6d, 0x98, 0xf6, 0x53, - 0xd2, 0x4b, 0x96, 0x4a, 0xe7, 0xca, 0xcf, 0xcc, 0x9e, 0xbf, 0x58, 0x48, 0xf7, 0x1a, 0xf3, 0x42, - 0xe2, 0xf4, 0x06, 0xe5, 0x8d, 0xb9, 0x08, 0xfb, 0xfb, 0x55, 0xb3, 0x73, 0xb4, 0xd7, 0xe8, 0x79, - 0x98, 0x4d, 0xc2, 0x7e, 0xec, 0x12, 0x4c, 0xa2, 0x30, 0x59, 0xb2, 0xce, 0x95, 0xe9, 0xe0, 0x53, - 0x5f, 0x69, 0x6a, 0x30, 0x36, 0x69, 0xd0, 0xef, 0x59, 0x30, 0xe7, 0x91, 0x24, 0xf5, 0x03, 0x26, - 0x5f, 0x6a, 0xfe, 0xf2, 0x64, 0x9a, 0x4b, 0xe0, 0xba, 0xe6, 0xdc, 0x78, 0x52, 0xf4, 0x62, 0xce, - 0x00, 0x26, 0x38, 0x23, 0x9c, 0x3a, 0xbc, 0x47, 0x12, 0x37, 0xf6, 0x23, 0xfa, 0xbd, 0x54, 0xce, - 0x3a, 0xfc, 0xba, 0x46, 0x61, 0x93, 0x0e, 0xed, 0xc1, 0x34, 0x75, 0xe8, 0x64, 0x69, 0x8a, 0x29, - 0x7f, 0x69, 0x02, 0xe5, 0x85, 0x39, 0xe9, 0x44, 0xd1, 0x76, 0xa7, 0x5f, 0x09, 0xe6, 0x32, 0xd0, - 0xdb, 0x16, 0x2c, 0x89, 0xd9, 0x86, 0x09, 0x37, 0xe5, 0xcd, 0x8e, 0x9f, 0x92, 0xae, 0x9f, 0xa4, - 0x4b, 0xd3, 0x4c, 0x81, 0x95, 0xf1, 0x5c, 0xea, 0x72, 0x1c, 0xf6, 0xa3, 0x6b, 0x7e, 0xe0, 0x35, - 0xce, 0x09, 0x49, 0x4b, 0x6b, 0x23, 0x18, 0xe3, 0x91, 0x22, 0xd1, 0x37, 0x2d, 0x38, 0x13, 0x38, - 0x3d, 0x92, 0x44, 0x0e, 0x1d, 0x54, 0x8e, 0x6e, 0x74, 0x1d, 0x77, 0x8f, 0x69, 0x54, 0x79, 0x38, - 0x8d, 0x6c, 0xa1, 0xd1, 0x99, 0xed, 0x91, 0xac, 0xf1, 0x03, 0xc4, 0xa2, 0x3f, 0xb5, 0x60, 0x31, - 0x8c, 0xa3, 0x8e, 0x13, 0x10, 0x4f, 0x62, 0x93, 0xa5, 0x19, 0x36, 0xe3, 0x3e, 0x37, 0xc1, 0xf8, - 0x5c, 0xcf, 0xf3, 0xdc, 0x0a, 0x03, 0x3f, 0x0d, 0xe3, 0x26, 0x49, 0x53, 0x3f, 0x68, 0x27, 0x8d, - 0xd3, 0xf7, 0x0e, 0x97, 0x17, 0x07, 0xa8, 0xf0, 0xa0, 0x32, 0xe8, 0x2e, 0xcc, 0x26, 0x07, 0x81, - 0x7b, 0xd3, 0x0f, 0xbc, 0xf0, 0x4e, 0xb2, 0x54, 0x9d, 0x78, 0xca, 0x36, 0x15, 0x37, 0x31, 0xe9, - 0x34, 0x77, 0x6c, 0x8a, 0x1a, 0x3e, 0x64, 0xda, 0x89, 0x6a, 0x45, 0x0f, 0x99, 0x76, 0xa3, 0x07, - 0x88, 0xb5, 0xbf, 0x57, 0x86, 0x59, 0x63, 0xee, 0x3e, 0x86, 0xcd, 0xa0, 0x9b, 0xd9, 0x0c, 0xae, - 0x16, 0xb3, 0xe6, 0x8c, 0xda, 0x0d, 0x50, 0x0a, 0x95, 0x24, 0x75, 0xd2, 0x7e, 0xc2, 0xd6, 0x95, - 0xd9, 0xf3, 0x9b, 0x05, 0xc9, 0x63, 0x3c, 0x1b, 0x0b, 0x42, 0x62, 0x85, 0x7f, 0x63, 0x21, 0x0b, - 0xbd, 0x0e, 0xb5, 0x30, 0xa2, 0xdb, 0x3c, 0x5d, 0xd0, 0xa6, 0x98, 0xe0, 0xf5, 0x49, 0xfc, 0x5f, - 0xf2, 0x6a, 0xcc, 0xdf, 0x3b, 0x5c, 0xae, 0xa9, 0x4f, 0xac, 0xa5, 0xd8, 0xff, 0x60, 0xc1, 0x93, - 0x86, 0x82, 0x6b, 0x61, 0xe0, 0xf9, 0x6c, 0x44, 0xcf, 0xc1, 0x54, 0x7a, 0x10, 0xc9, 0x40, 0x42, - 0xd9, 0x68, 0xf7, 0x20, 0x22, 0x98, 0x61, 0x68, 0xe8, 0xd0, 0x23, 0x49, 0xe2, 0xb4, 0x49, 0x3e, - 0x74, 0xd8, 0xe2, 0x60, 0x2c, 0xf1, 0x28, 0x06, 0xd4, 0x75, 0x92, 0x74, 0x37, 0x76, 0x82, 0x84, - 0xb1, 0xdf, 0xf5, 0x7b, 0x44, 0x98, 0xf6, 0x17, 0xc6, 0x73, 0x14, 0xda, 0xa2, 0xf1, 0xd4, 0xbd, - 0xc3, 0x65, 0xb4, 0x39, 0xc0, 0x09, 0x0f, 0xe1, 0x6e, 0x7f, 0xd3, 0x82, 0xa7, 0x86, 0x6f, 0x2f, - 0xe8, 0xe3, 0x50, 0x49, 0x48, 0xbc, 0x4f, 0x62, 0xd1, 0x3b, 0x3d, 0x1e, 0x0c, 0x8a, 0x05, 0x16, - 0xad, 0x40, 0x4d, 0xcd, 0x01, 0xd1, 0xc7, 0x45, 0x41, 0x5a, 0xd3, 0x13, 0x47, 0xd3, 0x50, 0xa3, - 0xd1, 0x0f, 0xb1, 0x19, 0x29, 0xa3, 0xb1, 0xb0, 0x8b, 0x61, 0xec, 0x7f, 0xb1, 0xe0, 0x84, 0xa1, - 0xd5, 0x63, 0x88, 0x33, 0xf6, 0xb2, 0x71, 0xc6, 0xa5, 0x62, 0x3c, 0x79, 0x44, 0xa0, 0xf1, 0xdd, - 0x0a, 0x2c, 0x9a, 0xfe, 0xce, 0x96, 0x0d, 0x16, 0x64, 0x92, 0x28, 0xbc, 0x81, 0x37, 0x85, 0xc1, - 0x75, 0x90, 0xc9, 0xc1, 0x58, 0xe2, 0xa9, 0x05, 0x23, 0x27, 0xed, 0x08, 0x6b, 0x2b, 0x0b, 0xee, - 0x38, 0x69, 0x07, 0x33, 0x0c, 0xfa, 0x0c, 0x2c, 0xa4, 0x4e, 0xdc, 0x26, 0x29, 0x26, 0xfb, 0x7e, - 0x22, 0x67, 0x4a, 0xad, 0xf1, 0x94, 0xa0, 0x5d, 0xd8, 0xcd, 0x60, 0x71, 0x8e, 0x1a, 0x05, 0x30, - 0xd5, 0x21, 0xdd, 0x9e, 0xd8, 0x5f, 0x76, 0x0a, 0x9a, 0xd8, 0xac, 0xa3, 0x57, 0x48, 0xb7, 0xd7, - 0xa8, 0x52, 0x7d, 0xe9, 0x2f, 0xcc, 0xe4, 0xa0, 0xdf, 0xb6, 0xa0, 0xb6, 0xd7, 0x4f, 0xd2, 0xb0, - 0xe7, 0xbf, 0x41, 0x96, 0xaa, 0x4c, 0xea, 0x8d, 0x22, 0xa5, 0x5e, 0x93, 0xcc, 0xf9, 0x34, 0x57, - 0x9f, 0x58, 0x8b, 0x45, 0x6f, 0xc0, 0xcc, 0x5e, 0x12, 0x06, 0x01, 0xa1, 0x3b, 0x06, 0xd5, 0xa0, - 0x59, 0xa8, 0x06, 0x9c, 0x75, 0x63, 0x96, 0x0e, 0xa9, 0xf8, 0xc0, 0x52, 0x20, 0x33, 0x80, 0xe7, - 0xc7, 0xc4, 0x4d, 0xc3, 0xf8, 0x60, 0x09, 0x8a, 0x37, 0xc0, 0xba, 0x64, 0xce, 0x0d, 0xa0, 0x3e, - 0xb1, 0x16, 0x8b, 0xf6, 0xa1, 0x12, 0x75, 0xfb, 0x6d, 0x3f, 0x58, 0x9a, 0x65, 0x0a, 0xe0, 0x22, - 0x15, 0xd8, 0x61, 0x9c, 0x1b, 0x40, 0x97, 0x10, 0xfe, 0x1b, 0x0b, 0x69, 0xe8, 0x69, 0x98, 0x76, - 0x3b, 0x4e, 0x9c, 0x2e, 0xcd, 0x31, 0x27, 0x55, 0xb3, 0x66, 0x8d, 0x02, 0x31, 0xc7, 0xd9, 0x7f, - 0x63, 0xc1, 0x99, 0xd1, 0xbd, 0xe2, 0xd3, 0xc7, 0xed, 0xc7, 0x09, 0x5f, 0x8d, 0xab, 0xe6, 0xf4, - 0x61, 0x60, 0x2c, 0xf1, 0xe8, 0xcb, 0x30, 0x73, 0x5b, 0x8c, 0x73, 0xa9, 0xf8, 0x71, 0xbe, 0x2a, - 0xc6, 0x59, 0xc9, 0xbf, 0x2a, 0xc7, 0x5a, 0x08, 0xb5, 0xbf, 0x5b, 0x86, 0xd3, 0x43, 0xa7, 0x05, - 0xaa, 0x03, 0xec, 0x3b, 0xdd, 0x3e, 0xb9, 0xe4, 0xd3, 0xe0, 0x9b, 0x1f, 0x37, 0x16, 0xe8, 0x6e, - 0xff, 0x8a, 0x82, 0x62, 0x83, 0x02, 0xfd, 0x26, 0x40, 0xe4, 0xc4, 0x4e, 0x8f, 0xa4, 0x24, 0x96, - 0x6b, 0xd7, 0x95, 0x09, 0x3a, 0x43, 0x95, 0xd8, 0x91, 0x0c, 0x75, 0xac, 0xa1, 0x40, 0x09, 0x36, - 0xe4, 0xd1, 0xc3, 0x45, 0x4c, 0xba, 0xc4, 0x49, 0xc8, 0xb6, 0x5e, 0xcf, 0xd5, 0xe1, 0x02, 0x6b, - 0x14, 0x36, 0xe9, 0xe8, 0xc6, 0xc2, 0xba, 0x90, 0x88, 0x35, 0x49, 0x6d, 0x2c, 0xac, 0x93, 0x09, - 0x16, 0x58, 0xf4, 0x96, 0x05, 0x0b, 0x2d, 0xbf, 0x4b, 0xb4, 0x74, 0x71, 0x1a, 0xd8, 0x9c, 0xb0, - 0x87, 0x97, 0x4c, 0xa6, 0x7a, 0x49, 0xcc, 0x80, 0x13, 0x9c, 0x93, 0x6d, 0xff, 0xaf, 0x05, 0x4b, - 0xa3, 0x06, 0x1b, 0x45, 0x30, 0x43, 0xee, 0xa6, 0xaf, 0x38, 0x31, 0x1f, 0xb5, 0xc9, 0xc2, 0x5e, - 0xc1, 0xf4, 0x15, 0x27, 0xd6, 0x4e, 0x74, 0x91, 0x73, 0xc7, 0x52, 0x0c, 0x6a, 0xc3, 0x54, 0xda, - 0x75, 0x8a, 0x38, 0x18, 0x1b, 0xe2, 0x74, 0x04, 0xb3, 0xb9, 0x9a, 0x60, 0x26, 0xc0, 0xfe, 0xe1, - 0xb0, 0x7e, 0x8b, 0xf5, 0x8b, 0xba, 0x00, 0x09, 0xf6, 0xfd, 0x38, 0x0c, 0x7a, 0x24, 0x48, 0xf3, - 0x09, 0x95, 0x8b, 0x1a, 0x85, 0x4d, 0x3a, 0xf4, 0x5b, 0x43, 0xfc, 0xf6, 0xda, 0x04, 0x5d, 0x10, - 0xea, 0x8c, 0xed, 0xba, 0xf6, 0xdf, 0x96, 0x87, 0x2c, 0x26, 0x6a, 0x53, 0x40, 0xe7, 0x01, 0x68, - 0x20, 0xb2, 0x13, 0x93, 0x96, 0x7f, 0x57, 0xf4, 0x4a, 0xb1, 0xdc, 0x56, 0x18, 0x6c, 0x50, 0xc9, - 0x36, 0xcd, 0x7e, 0x8b, 0xb6, 0x29, 0x0d, 0xb6, 0xe1, 0x18, 0x6c, 0x50, 0xa1, 0x17, 0xa0, 0xe2, - 0xf7, 0x9c, 0x36, 0xa1, 0x11, 0x34, 0x9d, 0xeb, 0x1f, 0xa1, 0xd3, 0x60, 0x83, 0x41, 0xee, 0x1f, - 0x2e, 0x2f, 0x28, 0x85, 0x18, 0x08, 0x0b, 0x5a, 0xf4, 0x67, 0x16, 0xcc, 0xb9, 0x61, 0xaf, 0x17, - 0x06, 0x9b, 0xce, 0x2d, 0xd2, 0x95, 0xa7, 0xf4, 0xf6, 0x23, 0xd9, 0x2f, 0xeb, 0x6b, 0x86, 0xa4, - 0x8b, 0x41, 0x1a, 0x1f, 0xe8, 0xc4, 0x83, 0x89, 0xc2, 0x19, 0x95, 0xe8, 0x72, 0xbc, 0x4f, 0x62, - 0x16, 0x79, 0x4c, 0x67, 0xa3, 0x99, 0x57, 0x38, 0x18, 0x4b, 0xfc, 0x99, 0x97, 0x60, 0x71, 0x40, - 0x06, 0x3a, 0x09, 0xe5, 0x3d, 0x72, 0xc0, 0x4d, 0x8f, 0xe9, 0x4f, 0xf4, 0x24, 0x4c, 0xb3, 0x85, - 0x81, 0x9b, 0x16, 0xf3, 0x8f, 0x4f, 0x97, 0x2e, 0x58, 0xf6, 0x1f, 0x59, 0xf0, 0xa1, 0x11, 0xdb, - 0x8d, 0x0a, 0x36, 0xad, 0x51, 0xc1, 0x26, 0xfa, 0x22, 0x94, 0x49, 0xb0, 0x2f, 0x9c, 0x70, 0x6d, - 0x02, 0x1b, 0x5e, 0x0c, 0xf6, 0xb9, 0x7d, 0x66, 0xee, 0x1d, 0x2e, 0x97, 0x2f, 0x06, 0xfb, 0x98, - 0x32, 0xb6, 0xdf, 0xac, 0x64, 0x82, 0xd9, 0xa6, 0x3c, 0x39, 0x31, 0x2d, 0x45, 0x28, 0xbb, 0x59, - 0xe4, 0xd0, 0x19, 0x91, 0x3a, 0xcf, 0x4b, 0x09, 0x59, 0xe8, 0xeb, 0x16, 0xcb, 0x06, 0xc9, 0x08, - 0x5f, 0x6c, 0x7e, 0x8f, 0x20, 0x33, 0x65, 0x26, 0x98, 0x24, 0x10, 0x9b, 0xa2, 0xa9, 0x7b, 0x44, - 0x3c, 0x31, 0x24, 0xb6, 0x0d, 0xe5, 0x1e, 0x32, 0x5f, 0x24, 0xf1, 0xa8, 0x0f, 0x40, 0x8f, 0xfa, - 0x3b, 0x61, 0xd7, 0x77, 0x0f, 0xc4, 0x81, 0x6f, 0xd2, 0xa4, 0x02, 0x67, 0xc6, 0xb7, 0x56, 0xfd, - 0x8d, 0x0d, 0x41, 0xe8, 0x5b, 0x16, 0x2c, 0xfa, 0xed, 0x20, 0x8c, 0xc9, 0xba, 0xdf, 0x6a, 0x91, - 0x98, 0x04, 0x2e, 0x91, 0x1b, 0xd0, 0xee, 0x04, 0xe2, 0x65, 0x9a, 0x60, 0x23, 0xcf, 0xbb, 0xf1, - 0x61, 0x61, 0x82, 0xc5, 0x01, 0x14, 0x1e, 0xd4, 0x04, 0x39, 0x30, 0xe5, 0x07, 0xad, 0x50, 0xa4, - 0xa3, 0x5e, 0x9a, 0x40, 0xa3, 0x8d, 0xa0, 0x15, 0xea, 0x99, 0x41, 0xbf, 0x30, 0x63, 0x8d, 0x36, - 0xe1, 0xc9, 0x58, 0x1c, 0x08, 0xae, 0xf8, 0x09, 0x8d, 0xb2, 0x36, 0xfd, 0x9e, 0x9f, 0xb2, 0x43, - 0x41, 0xb9, 0xb1, 0x74, 0xef, 0x70, 0xf9, 0x49, 0x3c, 0x04, 0x8f, 0x87, 0xb6, 0xb2, 0xff, 0xa7, - 0x9a, 0x3d, 0xf5, 0xf0, 0xd3, 0xfc, 0x1b, 0x50, 0x8b, 0x55, 0x36, 0x8b, 0x6f, 0x9d, 0x1b, 0x05, - 0x58, 0x57, 0xe4, 0x10, 0xd4, 0x41, 0x54, 0xe7, 0xad, 0xb4, 0x38, 0xba, 0x85, 0xd2, 0x01, 0x17, - 0xf3, 0x60, 0x52, 0x9f, 0x12, 0x22, 0x75, 0xa2, 0xe4, 0x20, 0x70, 0x31, 0x13, 0x80, 0x42, 0xa8, - 0x74, 0x88, 0xd3, 0x4d, 0x3b, 0xe2, 0x34, 0x7f, 0x79, 0xa2, 0x00, 0x86, 0x32, 0xca, 0xe7, 0x48, - 0x38, 0x14, 0x0b, 0x31, 0xa8, 0x0f, 0x33, 0x1d, 0x6e, 0x7b, 0xb1, 0x37, 0x5c, 0x9d, 0xc8, 0xa6, - 0x99, 0xd1, 0xd4, 0x53, 0x55, 0x00, 0xb0, 0x94, 0x85, 0x7e, 0xc7, 0x02, 0x70, 0x65, 0x72, 0x44, - 0x4e, 0x96, 0xeb, 0xc5, 0xac, 0x2f, 0x2a, 0xe9, 0xa2, 0x37, 0x55, 0x05, 0x4a, 0xb0, 0x21, 0x16, - 0xbd, 0x06, 0x73, 0x31, 0x71, 0xc3, 0xc0, 0xf5, 0xbb, 0xc4, 0x5b, 0x4d, 0x97, 0x2a, 0xc7, 0xce, - 0xa0, 0x9c, 0xa4, 0x9b, 0x1b, 0x36, 0x78, 0xe0, 0x0c, 0x47, 0xf4, 0xa6, 0x05, 0x0b, 0x2a, 0x3b, - 0x44, 0x87, 0x82, 0x88, 0x83, 0xf2, 0x46, 0x11, 0x89, 0x28, 0xc6, 0xb0, 0x81, 0x68, 0x48, 0x9a, - 0x85, 0xe1, 0x9c, 0x50, 0xf4, 0x2a, 0x40, 0x78, 0x8b, 0xa5, 0x61, 0x68, 0x3f, 0xab, 0xc7, 0xee, - 0xe7, 0x02, 0x4f, 0x24, 0x4a, 0x0e, 0xd8, 0xe0, 0x86, 0xae, 0x01, 0xf0, 0x79, 0xb2, 0x7b, 0x10, - 0x11, 0x76, 0x1e, 0xae, 0x35, 0x3e, 0x29, 0x2d, 0xdf, 0x54, 0x98, 0xfb, 0x87, 0xcb, 0x83, 0x67, - 0x19, 0x96, 0xff, 0x32, 0x9a, 0xa3, 0xbb, 0x30, 0x93, 0xf4, 0x7b, 0x3d, 0x47, 0x1d, 0x6d, 0xb7, - 0x0a, 0xda, 0xf0, 0x38, 0x53, 0xed, 0x92, 0x02, 0x80, 0xa5, 0x38, 0x3b, 0x00, 0x34, 0x48, 0x8f, - 0x5e, 0x80, 0x39, 0x72, 0x37, 0x25, 0x71, 0xe0, 0x74, 0x6f, 0xe0, 0x4d, 0x79, 0xd2, 0x62, 0xc3, - 0x7e, 0xd1, 0x80, 0xe3, 0x0c, 0x15, 0xb2, 0x55, 0xb4, 0x56, 0x62, 0xf4, 0xa0, 0xa3, 0x35, 0x19, - 0x9b, 0xd9, 0xbf, 0x5b, 0xca, 0xec, 0xf6, 0xbb, 0x31, 0x21, 0xa8, 0x0b, 0xd3, 0x41, 0xe8, 0xa9, - 0xf5, 0xed, 0x72, 0x01, 0xeb, 0xdb, 0x76, 0xe8, 0x19, 0xd7, 0x29, 0xf4, 0x2b, 0xc1, 0x5c, 0x08, - 0xfa, 0x9a, 0x05, 0xf3, 0x32, 0x37, 0xcf, 0x10, 0x22, 0xb4, 0x29, 0x4c, 0xec, 0x69, 0x21, 0x76, - 0xfe, 0xba, 0x29, 0x05, 0x67, 0x85, 0xda, 0x3f, 0xb2, 0x32, 0x87, 0xdc, 0x9b, 0x4e, 0xea, 0x76, - 0x2e, 0xee, 0xd3, 0xe0, 0xff, 0x5a, 0x26, 0x69, 0xfa, 0x2b, 0x66, 0xd2, 0xf4, 0xfe, 0xe1, 0xf2, - 0x27, 0x46, 0xdd, 0xf5, 0xde, 0xa1, 0x1c, 0xea, 0x8c, 0x85, 0x91, 0x5f, 0xfd, 0x12, 0xcc, 0x1a, - 0x1a, 0x8b, 0xa5, 0xbc, 0xa8, 0xf4, 0x9d, 0x8a, 0x63, 0x0c, 0x20, 0x36, 0xe5, 0xd9, 0xef, 0x94, - 0x61, 0x46, 0x5c, 0x31, 0x8d, 0x9d, 0x30, 0x95, 0x21, 0x69, 0x69, 0x64, 0x48, 0x1a, 0x41, 0xc5, - 0x65, 0x17, 0xd6, 0x62, 0xbf, 0x98, 0xe4, 0x48, 0x2f, 0xb4, 0xe3, 0x17, 0xe0, 0x5a, 0x27, 0xfe, - 0x8d, 0x85, 0x1c, 0xf4, 0xb6, 0x05, 0x27, 0x5c, 0x7a, 0x86, 0x72, 0xf5, 0x92, 0x36, 0x35, 0xf1, - 0x25, 0xc2, 0x5a, 0x96, 0x63, 0xe3, 0x43, 0x42, 0xfa, 0x89, 0x1c, 0x02, 0xe7, 0x65, 0xa3, 0x17, - 0x61, 0x9e, 0x5b, 0xeb, 0x95, 0xcc, 0x21, 0x42, 0xb9, 0x5e, 0xd3, 0x44, 0xe2, 0x2c, 0x2d, 0xaa, - 0xf3, 0x93, 0x18, 0xcb, 0x36, 0x27, 0x2c, 0x40, 0x12, 0x59, 0x14, 0x95, 0x8e, 0x4e, 0xb0, 0x41, - 0x61, 0xff, 0x65, 0x19, 0xe6, 0x33, 0x66, 0x42, 0xcf, 0x42, 0xb5, 0x9f, 0xd0, 0x89, 0xaf, 0x4e, - 0x0e, 0x2a, 0x79, 0x7c, 0x43, 0xc0, 0xb1, 0xa2, 0xa0, 0xd4, 0x91, 0x93, 0x24, 0x77, 0xc2, 0xd8, - 0x13, 0x83, 0xaa, 0xa8, 0x77, 0x04, 0x1c, 0x2b, 0x0a, 0x7a, 0x64, 0xbe, 0x45, 0x9c, 0x98, 0xc4, - 0xbb, 0xe1, 0x1e, 0x19, 0xb8, 0x92, 0x6d, 0x68, 0x14, 0x36, 0xe9, 0xd8, 0x08, 0xa5, 0xdd, 0x64, - 0xad, 0xeb, 0x93, 0x20, 0xe5, 0x6a, 0x16, 0x30, 0x42, 0xbb, 0x9b, 0x4d, 0x93, 0xa3, 0x1e, 0xa1, - 0x1c, 0x02, 0xe7, 0x65, 0xa3, 0xaf, 0x5a, 0x30, 0xef, 0xdc, 0x49, 0x74, 0x71, 0x05, 0x1b, 0xa2, - 0xc9, 0x7c, 0x35, 0x53, 0xac, 0xd1, 0x58, 0xa4, 0x03, 0x9d, 0x01, 0xe1, 0xac, 0x44, 0xfb, 0x3d, - 0x0b, 0x64, 0xd1, 0xc6, 0x63, 0xb8, 0x23, 0x68, 0x67, 0xef, 0x08, 0x1a, 0x93, 0x4f, 0xca, 0x11, - 0xf7, 0x03, 0xdb, 0x30, 0x43, 0x0f, 0xc4, 0x4e, 0xe0, 0xa1, 0x8f, 0xc1, 0x8c, 0xcb, 0x7f, 0x8a, - 0x3d, 0x8a, 0x65, 0x8f, 0x05, 0x16, 0x4b, 0x1c, 0xfa, 0x08, 0x4c, 0x39, 0x71, 0x5b, 0xee, 0x4b, - 0x2c, 0xb9, 0xbe, 0x1a, 0xb7, 0x13, 0xcc, 0xa0, 0xf6, 0xdb, 0x25, 0x80, 0xb5, 0xb0, 0x17, 0x39, - 0x31, 0xf1, 0x76, 0xc3, 0xff, 0xf7, 0x87, 0x4f, 0xfb, 0x2d, 0x0b, 0x10, 0xb5, 0x47, 0x18, 0x90, - 0x40, 0xe7, 0x8c, 0xd0, 0x0a, 0xd4, 0x5c, 0x09, 0x15, 0xb3, 0x5e, 0x9d, 0x1f, 0x14, 0x39, 0xd6, - 0x34, 0x63, 0x2c, 0xe4, 0x4f, 0xcb, 0x9c, 0x45, 0x39, 0x9b, 0xd8, 0x66, 0x99, 0x4e, 0x91, 0xc2, - 0xb0, 0x7f, 0xbf, 0x04, 0x4f, 0x71, 0x87, 0xde, 0x72, 0x02, 0xa7, 0x4d, 0x7a, 0x54, 0xab, 0x71, - 0xb3, 0x17, 0xaf, 0xd1, 0x63, 0xa0, 0x2f, 0x13, 0xd9, 0x13, 0xf9, 0x24, 0xf7, 0x25, 0xee, 0x3d, - 0x1b, 0x81, 0x9f, 0x62, 0xc6, 0x19, 0x45, 0x50, 0x95, 0x75, 0x55, 0x62, 0x3b, 0x2a, 0x42, 0x8a, - 0x9a, 0x68, 0x97, 0x05, 0x6f, 0xac, 0xa4, 0xd8, 0xef, 0x58, 0x90, 0xdf, 0x21, 0xd8, 0xe6, 0xca, - 0xef, 0x9a, 0xf3, 0x9b, 0x6b, 0xf6, 0x76, 0xf8, 0x18, 0xf7, 0xad, 0x9f, 0x87, 0x59, 0x27, 0x4d, - 0x49, 0x2f, 0x4a, 0x59, 0xf8, 0x5c, 0x7e, 0xb8, 0xf0, 0x79, 0x2b, 0xf4, 0xfc, 0x96, 0xcf, 0xc2, - 0x67, 0x93, 0x9d, 0xfd, 0x32, 0x54, 0x65, 0x42, 0x68, 0x8c, 0x61, 0x7c, 0x3a, 0x93, 0xdc, 0x1a, - 0xe1, 0x28, 0x7f, 0x6e, 0xc1, 0x9c, 0x79, 0xfc, 0x43, 0xed, 0x9c, 0x51, 0xae, 0x67, 0x8d, 0x72, - 0xff, 0x70, 0xf9, 0xd7, 0x86, 0x55, 0x1e, 0xb6, 0xfd, 0x34, 0x8c, 0x92, 0xe7, 0x48, 0xd0, 0xf6, - 0x03, 0xc2, 0x62, 0x2b, 0x7e, 0x6c, 0xcc, 0x9c, 0x2d, 0xd7, 0x42, 0x8f, 0x3c, 0x84, 0x55, 0xed, - 0x9b, 0xb0, 0x38, 0x90, 0x63, 0x1f, 0xc3, 0x00, 0x47, 0x5e, 0x69, 0xda, 0x6f, 0x5b, 0x30, 0x9f, - 0xb9, 0x9f, 0x28, 0xc8, 0xac, 0x74, 0x43, 0x6e, 0x85, 0x2c, 0x67, 0x10, 0xfb, 0x01, 0x0f, 0xb9, - 0xaa, 0x7a, 0x15, 0xb9, 0xa4, 0x51, 0xd8, 0xa4, 0xb3, 0xb7, 0x80, 0xe5, 0x4a, 0x8a, 0x1a, 0xdc, - 0x97, 0xa1, 0x4a, 0xd9, 0xd1, 0x8d, 0xa0, 0x28, 0x96, 0x21, 0x54, 0xaf, 0xde, 0xdc, 0xe5, 0xe1, - 0x83, 0x0d, 0x65, 0xdf, 0xe1, 0xcb, 0x5a, 0x59, 0x4f, 0xbe, 0x8d, 0x24, 0xe9, 0x33, 0xd7, 0xa5, - 0x48, 0xf4, 0x34, 0x94, 0xc9, 0xdd, 0x88, 0xb1, 0x2c, 0xeb, 0xa5, 0xef, 0xe2, 0xdd, 0xc8, 0x8f, - 0x49, 0x42, 0x89, 0xc8, 0xdd, 0x08, 0x9d, 0x81, 0x92, 0xef, 0x89, 0xf5, 0x0c, 0x04, 0x4d, 0x69, - 0x63, 0x1d, 0x97, 0x7c, 0xcf, 0xee, 0x03, 0xe8, 0xcb, 0x84, 0xa2, 0x86, 0xe7, 0x1c, 0x4c, 0xb9, - 0xa1, 0x47, 0xc4, 0xb8, 0x28, 0x36, 0xcc, 0x3f, 0x19, 0xc6, 0xbe, 0x09, 0x0b, 0xd7, 0x82, 0xf0, - 0x4e, 0x40, 0x8f, 0x05, 0x97, 0x7c, 0xd2, 0xf5, 0x28, 0xe3, 0x16, 0xfd, 0x21, 0x64, 0x2b, 0xc6, - 0x0c, 0x8b, 0x39, 0x4e, 0x15, 0x6f, 0x94, 0x46, 0x15, 0x6f, 0xd8, 0xdf, 0xb0, 0xe0, 0x64, 0xfe, - 0x6a, 0xe1, 0x27, 0xb6, 0x4d, 0x7c, 0x85, 0x2a, 0x23, 0xb3, 0xf2, 0xd7, 0x23, 0x9e, 0xeb, 0xb8, - 0x00, 0x73, 0xb7, 0xfa, 0x7e, 0xd7, 0x13, 0xdf, 0x42, 0x1f, 0x95, 0xa0, 0x6f, 0x18, 0x38, 0x9c, - 0xa1, 0x44, 0xe7, 0x01, 0x6e, 0xf9, 0x81, 0x13, 0x1f, 0xec, 0xe8, 0x69, 0xa7, 0x32, 0x2b, 0x0d, - 0x85, 0xc1, 0x06, 0x95, 0xfd, 0x8f, 0x25, 0xd0, 0x05, 0x32, 0xa8, 0x25, 0xd2, 0x67, 0xd6, 0xc4, - 0x71, 0x5f, 0xf3, 0x20, 0x70, 0x75, 0x1d, 0x4e, 0x35, 0x97, 0x3d, 0xfb, 0x9a, 0x05, 0xb3, 0x74, - 0x27, 0xf2, 0x9d, 0x94, 0x78, 0x8d, 0x03, 0xb1, 0xd5, 0x6d, 0x15, 0x91, 0x6a, 0xd9, 0xe0, 0x6c, - 0xc3, 0x58, 0xcf, 0xf7, 0x0d, 0x2d, 0x09, 0x9b, 0x62, 0xd1, 0x17, 0x44, 0xc2, 0xb5, 0x5c, 0x4c, - 0xc2, 0xb5, 0x9a, 0x4d, 0xb6, 0xda, 0x09, 0xa0, 0x41, 0xb5, 0x8e, 0x79, 0x10, 0x59, 0x81, 0x9a, - 0xd3, 0x4f, 0xc3, 0x1e, 0xd5, 0x98, 0x99, 0xa9, 0xaa, 0x5d, 0x73, 0x55, 0x22, 0xb0, 0xa6, 0xb1, - 0xdf, 0x9b, 0x82, 0x5c, 0x8e, 0x09, 0xf5, 0xcd, 0xf2, 0x2a, 0xab, 0xc0, 0xf2, 0x2a, 0xa5, 0xc9, - 0xb0, 0x12, 0x2b, 0x1a, 0x5c, 0x47, 0x1d, 0x27, 0x91, 0xb3, 0xe4, 0x65, 0x39, 0x05, 0x76, 0x28, - 0xf0, 0xfe, 0xe1, 0xf2, 0x67, 0xc7, 0xdb, 0xc8, 0xa8, 0xc3, 0xac, 0xf0, 0x8b, 0x28, 0x2d, 0x9a, - 0xf1, 0xc0, 0x9c, 0xbf, 0xb9, 0x95, 0x95, 0x8f, 0x08, 0x10, 0xbe, 0xcc, 0x6f, 0x1e, 0x30, 0x49, - 0xfa, 0xdd, 0x54, 0x1c, 0xb6, 0xb6, 0x8b, 0x72, 0x73, 0xce, 0x55, 0x5f, 0x41, 0xf0, 0x6f, 0x6c, - 0x48, 0x44, 0x9f, 0x83, 0x5a, 0x92, 0x3a, 0x71, 0xfa, 0x90, 0x59, 0x4c, 0x65, 0xf0, 0xa6, 0x64, - 0x82, 0x35, 0x3f, 0xf4, 0x2a, 0x40, 0xcb, 0x0f, 0xfc, 0xa4, 0xc3, 0xb8, 0xcf, 0x3c, 0x5c, 0xf0, - 0x73, 0x49, 0x71, 0xc0, 0x06, 0x37, 0xfb, 0xb3, 0x70, 0xee, 0xa8, 0xb2, 0x52, 0x7a, 0x64, 0xb9, - 0xe3, 0xc4, 0x81, 0x28, 0xd6, 0x60, 0xb3, 0xe1, 0xa6, 0x13, 0x07, 0x98, 0x41, 0xed, 0x6f, 0x97, - 0x60, 0xd6, 0xa8, 0x1c, 0x1e, 0x63, 0x2f, 0xc9, 0x55, 0x3a, 0x97, 0xc6, 0xac, 0x74, 0x7e, 0x06, - 0xaa, 0x51, 0xd8, 0xf5, 0x5d, 0x5f, 0xdd, 0xc1, 0xce, 0xb1, 0x73, 0xbb, 0x80, 0x61, 0x85, 0x45, - 0x29, 0xd4, 0x6e, 0xdf, 0x49, 0xd9, 0x6e, 0x2a, 0x6f, 0x5c, 0x27, 0xb9, 0x2d, 0x94, 0x3b, 0xb3, - 0x1e, 0x26, 0x09, 0x49, 0xb0, 0x16, 0x84, 0x6c, 0xa8, 0xb4, 0xe3, 0xb0, 0x1f, 0xf1, 0x6c, 0xba, - 0xc8, 0x39, 0xb2, 0x12, 0xd5, 0x04, 0x0b, 0x8c, 0xfd, 0xc3, 0x12, 0xd4, 0x30, 0x89, 0xc2, 0xb5, - 0x98, 0x78, 0x09, 0xfa, 0x28, 0x94, 0xfb, 0x71, 0x57, 0x58, 0x6a, 0x56, 0x30, 0x2f, 0xdf, 0xc0, - 0x9b, 0x98, 0xc2, 0x33, 0x2b, 0x4a, 0xe9, 0x58, 0xa9, 0x8d, 0xf2, 0x91, 0xa9, 0x8d, 0x17, 0x61, - 0x3e, 0x49, 0x3a, 0x3b, 0xb1, 0xbf, 0xef, 0xa4, 0xe4, 0x1a, 0x39, 0x10, 0x05, 0x1e, 0x3a, 0x6b, - 0xd3, 0xbc, 0xa2, 0x91, 0x38, 0x4b, 0x8b, 0x2e, 0xc3, 0xa2, 0xce, 0x31, 0x90, 0x38, 0x5d, 0xa7, - 0xa7, 0x78, 0x9e, 0xf6, 0x51, 0x37, 0x63, 0x3a, 0x2b, 0x21, 0x08, 0xf0, 0x60, 0x1b, 0xb4, 0x0e, - 0x27, 0x33, 0x40, 0xaa, 0x48, 0x85, 0xf1, 0x59, 0x12, 0x7c, 0x4e, 0x66, 0xf8, 0x50, 0x5d, 0x06, - 0x5a, 0xd8, 0xef, 0x5b, 0x30, 0xaf, 0x8c, 0xfa, 0x18, 0xb2, 0x0b, 0x7e, 0x36, 0xbb, 0xb0, 0x3e, - 0x51, 0xb6, 0x56, 0xa8, 0x3d, 0x22, 0xbf, 0xf0, 0x27, 0x15, 0x00, 0xf6, 0x58, 0xc1, 0x67, 0xb7, - 0x36, 0xe7, 0x60, 0x2a, 0x26, 0x51, 0x98, 0x9f, 0x5b, 0x94, 0x02, 0x33, 0xcc, 0x4f, 0xaf, 0xcf, - 0x0c, 0x4b, 0x5b, 0x4e, 0xff, 0x04, 0xd3, 0x96, 0x4d, 0x38, 0xed, 0x07, 0x09, 0x71, 0xfb, 0xb1, - 0xb8, 0xdf, 0xbd, 0x12, 0x26, 0xca, 0xff, 0xaa, 0x8d, 0x8f, 0x0a, 0x46, 0xa7, 0x37, 0x86, 0x11, - 0xe1, 0xe1, 0x6d, 0xa9, 0x3d, 0x25, 0x82, 0xad, 0xd3, 0x55, 0x23, 0x7e, 0x17, 0x70, 0xac, 0x28, - 0x68, 0x0c, 0x40, 0x02, 0xe7, 0x56, 0x97, 0x6c, 0xb6, 0x12, 0x76, 0x25, 0x64, 0xc4, 0x00, 0x17, - 0x39, 0xe2, 0x52, 0x13, 0x6b, 0x9a, 0xe1, 0xf3, 0xae, 0x56, 0xd0, 0xbc, 0x83, 0xe3, 0xce, 0x3b, - 0x15, 0x95, 0xcf, 0x8e, 0x2c, 0xa9, 0x96, 0x7b, 0xc1, 0xdc, 0xc8, 0xbd, 0xe0, 0x33, 0xb0, 0xe0, - 0x07, 0x1d, 0x12, 0xfb, 0x29, 0xf1, 0xd8, 0x44, 0x58, 0x9a, 0x67, 0x86, 0x50, 0xa5, 0x5e, 0x1b, - 0x19, 0x2c, 0xce, 0x51, 0xdb, 0x5f, 0x2f, 0xc1, 0x69, 0x3d, 0x41, 0xa8, 0x66, 0x7e, 0x8b, 0x7a, - 0x09, 0x2b, 0x0c, 0xe2, 0xb9, 0x66, 0xe3, 0xfd, 0x98, 0x8a, 0x9a, 0x9b, 0x0a, 0x83, 0x0d, 0x2a, - 0x3a, 0x7e, 0x2e, 0x89, 0xd9, 0xa5, 0x45, 0x7e, 0xf6, 0xac, 0x09, 0x38, 0x56, 0x14, 0xec, 0x89, - 0x1a, 0x89, 0xd3, 0x66, 0xff, 0x16, 0x6b, 0x90, 0x4b, 0x0f, 0xaf, 0x69, 0x14, 0x36, 0xe9, 0xe8, - 0x3e, 0xe6, 0xca, 0xc1, 0xa3, 0x33, 0x68, 0x8e, 0xef, 0x63, 0x6a, 0xbc, 0x14, 0x56, 0xaa, 0x43, - 0x43, 0x4f, 0xb1, 0xbc, 0x66, 0xd4, 0x61, 0x21, 0xa9, 0xa2, 0xb0, 0xff, 0xcb, 0x82, 0x0f, 0x0f, - 0x35, 0xc5, 0x63, 0x58, 0x12, 0xfb, 0xd9, 0x25, 0x71, 0x67, 0xc2, 0x25, 0x71, 0xa0, 0x0b, 0x23, - 0x96, 0xc7, 0xbf, 0xb7, 0x60, 0x41, 0xd3, 0x3f, 0x86, 0x7e, 0xb6, 0x8a, 0x7b, 0xe4, 0xa6, 0xf5, - 0x6e, 0xd4, 0x06, 0x3a, 0xf6, 0x3e, 0xeb, 0x18, 0x8f, 0xc7, 0x56, 0x5d, 0xf9, 0x80, 0xe1, 0x88, - 0xb8, 0x6a, 0x1f, 0x2a, 0xac, 0x6e, 0x4e, 0x6a, 0xb7, 0x5d, 0xc0, 0x35, 0x22, 0x17, 0xce, 0x8e, - 0xd4, 0x3a, 0x91, 0xc7, 0x3e, 0x13, 0x2c, 0xa4, 0x51, 0x37, 0xf5, 0xfc, 0x84, 0x2e, 0x52, 0x9e, - 0x38, 0xfa, 0x2b, 0x13, 0xae, 0x0b, 0x38, 0x56, 0x14, 0x76, 0x0f, 0x96, 0xb2, 0xcc, 0xd7, 0x49, - 0x8b, 0x1d, 0xde, 0xc6, 0xea, 0x23, 0x3d, 0x37, 0xb1, 0x56, 0x9b, 0x7d, 0x27, 0xff, 0x84, 0x61, - 0x55, 0x22, 0xb0, 0xa6, 0xb1, 0xff, 0xc2, 0x82, 0x53, 0x43, 0x3a, 0x53, 0x60, 0xca, 0x23, 0xd5, - 0x93, 0x7f, 0xc4, 0xb3, 0x12, 0x8f, 0xb4, 0x1c, 0x79, 0x2e, 0x31, 0x4e, 0x31, 0xeb, 0x1c, 0x8c, - 0x25, 0xde, 0xfe, 0x0f, 0x0b, 0x4e, 0x64, 0x75, 0x4d, 0xd0, 0x55, 0x40, 0xbc, 0x33, 0xeb, 0x7e, - 0xe2, 0x86, 0xfb, 0x24, 0x3e, 0xa0, 0x3d, 0xe7, 0x5a, 0x9f, 0x11, 0x9c, 0xd0, 0xea, 0x00, 0x05, - 0x1e, 0xd2, 0x0a, 0x7d, 0x83, 0x25, 0xf6, 0xa5, 0xb5, 0xa5, 0x9b, 0x34, 0x0b, 0x73, 0x13, 0x3d, - 0x92, 0x66, 0x38, 0xaf, 0xe4, 0x61, 0x53, 0xb8, 0xfd, 0xe3, 0x32, 0xcc, 0xc9, 0xe6, 0xeb, 0x7e, - 0xab, 0x45, 0xed, 0xcd, 0xa2, 0xe4, 0x7c, 0x26, 0x88, 0x85, 0xd0, 0x98, 0xe3, 0xa8, 0xbd, 0xf7, - 0xfc, 0xc0, 0xcb, 0x67, 0x68, 0xae, 0xf9, 0x81, 0x87, 0x19, 0x26, 0xfb, 0xc8, 0xa5, 0x7c, 0x8c, - 0x47, 0x2e, 0x53, 0x0f, 0x3a, 0xb0, 0xf0, 0x47, 0x17, 0x3a, 0x6c, 0x31, 0x16, 0xfa, 0x5d, 0x8d, - 0xc2, 0x26, 0x1d, 0xd5, 0xa4, 0xeb, 0xef, 0x13, 0xde, 0xa8, 0x92, 0xd5, 0x64, 0x53, 0x22, 0xb0, - 0xa6, 0xa1, 0x9a, 0x78, 0x7e, 0xab, 0xc5, 0x42, 0x07, 0x43, 0x13, 0x6a, 0x1d, 0xcc, 0x30, 0x94, - 0xa2, 0x13, 0x86, 0x7b, 0x22, 0x5a, 0x50, 0x14, 0x57, 0xc2, 0x70, 0x0f, 0x33, 0x0c, 0xda, 0x82, - 0x53, 0x41, 0x18, 0xf7, 0x9c, 0xae, 0xff, 0x06, 0xf1, 0x94, 0x14, 0x11, 0x25, 0xfc, 0x9c, 0x68, - 0x70, 0x6a, 0x7b, 0x90, 0x04, 0x0f, 0x6b, 0x47, 0xdd, 0x2f, 0x8a, 0x89, 0xe7, 0xbb, 0xa9, 0xc9, - 0x0d, 0xb2, 0xee, 0xb7, 0x33, 0x40, 0x81, 0x87, 0xb4, 0xb2, 0xff, 0x93, 0x6d, 0x50, 0x23, 0x6a, - 0xea, 0x8a, 0x1a, 0xfe, 0x23, 0x9f, 0x2c, 0x65, 0x1d, 0x64, 0x6a, 0x0c, 0x07, 0x79, 0x01, 0xe6, - 0x6e, 0x27, 0x61, 0xb0, 0x13, 0xfa, 0x81, 0x2a, 0x6d, 0x17, 0x25, 0x28, 0x57, 0x9b, 0xd7, 0xb7, - 0x25, 0x1c, 0x67, 0xa8, 0xec, 0x77, 0xa6, 0xe1, 0x29, 0x55, 0x8c, 0x41, 0xd2, 0x3b, 0x61, 0xbc, - 0xe7, 0x07, 0x6d, 0x96, 0x87, 0xfe, 0x96, 0x05, 0x73, 0xdc, 0x51, 0x44, 0x55, 0x30, 0xaf, 0x36, - 0x71, 0x8b, 0x28, 0xfb, 0xc8, 0x48, 0xaa, 0xef, 0x1a, 0x52, 0x72, 0x15, 0xc1, 0x26, 0x0a, 0x67, - 0xd4, 0x41, 0x6f, 0x00, 0xc8, 0x47, 0x46, 0xad, 0x22, 0xde, 0x59, 0x49, 0xe5, 0x30, 0x69, 0xe9, - 0x10, 0x6c, 0x57, 0x49, 0xc0, 0x86, 0x34, 0xf4, 0xa6, 0x05, 0x95, 0x2e, 0xb7, 0x0a, 0x4f, 0xdf, - 0x7d, 0xa1, 0x78, 0xab, 0x98, 0xf6, 0x50, 0x9b, 0x9a, 0xb0, 0x84, 0x10, 0x8e, 0x30, 0xcc, 0xf8, - 0x41, 0x3b, 0x26, 0x89, 0xcc, 0x20, 0x7c, 0xc2, 0x08, 0x23, 0xea, 0x6e, 0x18, 0x13, 0x16, 0x34, - 0x84, 0x8e, 0xd7, 0x70, 0xba, 0x4e, 0xe0, 0x92, 0x78, 0x83, 0x93, 0xeb, 0xf5, 0x5d, 0x00, 0xb0, - 0x64, 0x34, 0x50, 0xcb, 0x34, 0x3d, 0x4e, 0x2d, 0xd3, 0x99, 0x97, 0x60, 0x71, 0x60, 0x18, 0x8f, - 0x53, 0x74, 0x7d, 0xe6, 0x53, 0x30, 0xfb, 0xb0, 0xf5, 0xda, 0xef, 0x4d, 0xeb, 0x45, 0x7a, 0x3b, - 0xf4, 0x58, 0x11, 0x4f, 0xac, 0x47, 0x53, 0x44, 0x58, 0x45, 0xf9, 0x86, 0xf1, 0x20, 0x45, 0x01, - 0xb1, 0x29, 0x8f, 0x7a, 0x66, 0xe4, 0xc4, 0x24, 0x78, 0xa4, 0x9e, 0xb9, 0xa3, 0x24, 0x60, 0x43, - 0x1a, 0x22, 0x99, 0xac, 0xf2, 0xda, 0x84, 0x59, 0x65, 0x1a, 0xee, 0x0d, 0x2d, 0xe5, 0x7d, 0xdb, - 0x82, 0x85, 0x20, 0xe3, 0xaf, 0x22, 0x9f, 0xf9, 0x72, 0xe1, 0x13, 0x81, 0x57, 0x2e, 0x66, 0x61, - 0x38, 0x27, 0x1c, 0xad, 0xc2, 0x09, 0x39, 0x02, 0xd9, 0x0a, 0x1f, 0x75, 0xd6, 0xc6, 0x59, 0x34, - 0xce, 0xd3, 0x1b, 0xd5, 0x78, 0x95, 0x51, 0xd5, 0x78, 0x68, 0x4f, 0x15, 0xde, 0xce, 0x14, 0x5b, - 0x78, 0x0b, 0x83, 0x45, 0xb7, 0xf6, 0xf7, 0x4a, 0x70, 0x52, 0x6a, 0x7d, 0x7d, 0x9f, 0xc4, 0xb1, - 0xef, 0xb1, 0x7d, 0x81, 0xa3, 0x75, 0x80, 0xa5, 0xf6, 0x85, 0x2b, 0x12, 0x81, 0x35, 0x0d, 0x8d, - 0xec, 0x78, 0x90, 0x95, 0xe4, 0xf3, 0xd3, 0x22, 0x78, 0xc3, 0x12, 0x4f, 0x4f, 0xee, 0x83, 0x15, - 0xea, 0xa5, 0xec, 0xc9, 0x7d, 0xac, 0x5a, 0xf2, 0xb7, 0x2c, 0x38, 0xb1, 0x97, 0xb9, 0x41, 0x93, - 0xeb, 0xd3, 0x24, 0x05, 0xad, 0xd9, 0x3b, 0x39, 0x3d, 0xb2, 0x59, 0x78, 0x82, 0xf3, 0xa2, 0xed, - 0xff, 0xb6, 0xc0, 0x9c, 0xac, 0xe3, 0x6d, 0xe2, 0xc6, 0x83, 0x93, 0xd2, 0x83, 0x1f, 0x9c, 0xa8, - 0xfd, 0xbe, 0x3c, 0x5e, 0xb8, 0x37, 0x75, 0x8c, 0x70, 0x6f, 0x7a, 0x64, 0x80, 0xf0, 0x51, 0x28, - 0xf7, 0x7d, 0x4f, 0x44, 0x6c, 0x3a, 0x2d, 0xbb, 0xb1, 0x8e, 0x29, 0xdc, 0xfe, 0xab, 0x69, 0x7d, - 0x36, 0x13, 0xe9, 0xff, 0x9f, 0x89, 0x6e, 0xb7, 0x54, 0x89, 0x02, 0xef, 0xf9, 0xf6, 0x40, 0x89, - 0xc2, 0xaf, 0x1e, 0xff, 0x66, 0x87, 0x1b, 0x68, 0x54, 0x85, 0xc2, 0xcc, 0x11, 0xd7, 0x3a, 0xb7, - 0xa1, 0x4a, 0x83, 0x5a, 0x96, 0x5e, 0xa9, 0x66, 0x94, 0xaa, 0x5e, 0x11, 0xf0, 0xfb, 0x87, 0xcb, - 0x9f, 0x3e, 0xbe, 0x5a, 0xb2, 0x35, 0x56, 0xfc, 0x51, 0x02, 0x35, 0xfa, 0x9b, 0xdd, 0x40, 0x89, - 0x70, 0xf9, 0x86, 0x9a, 0xfe, 0x12, 0x51, 0xc8, 0xf5, 0x96, 0x96, 0x83, 0x02, 0xa8, 0xb1, 0x87, - 0x2c, 0x4c, 0x28, 0x8f, 0xaa, 0x77, 0xd4, 0x5d, 0x90, 0x44, 0xdc, 0x3f, 0x5c, 0x7e, 0xf1, 0xf8, - 0x42, 0x55, 0x73, 0xac, 0x45, 0xd8, 0x1f, 0x94, 0xb5, 0xef, 0x8a, 0xca, 0x94, 0x9f, 0x09, 0xdf, - 0xbd, 0x90, 0xf3, 0xdd, 0x73, 0x03, 0xbe, 0xbb, 0xa0, 0x1f, 0x7e, 0x64, 0xbc, 0xf1, 0x71, 0xee, - 0x3b, 0x63, 0x1c, 0xdf, 0xd8, 0x6e, 0xfb, 0x7a, 0xdf, 0x8f, 0x49, 0xb2, 0x13, 0xf7, 0x03, 0x3f, - 0x68, 0x33, 0x5f, 0xac, 0x9a, 0xbb, 0x6d, 0x06, 0x8d, 0xf3, 0xf4, 0xf6, 0x1f, 0x97, 0xe1, 0x44, - 0xee, 0x21, 0x08, 0x7a, 0x16, 0xaa, 0xf2, 0xa5, 0x4f, 0x3e, 0xb1, 0xa9, 0xfe, 0x58, 0x40, 0x51, - 0xa0, 0x2f, 0x02, 0x78, 0x24, 0xea, 0x86, 0x07, 0xec, 0xc2, 0x71, 0xea, 0xd8, 0x17, 0x8e, 0x2a, - 0x92, 0x5a, 0x57, 0x5c, 0xb0, 0xc1, 0x51, 0x14, 0xa6, 0x4c, 0xb3, 0xe2, 0x95, 0x5c, 0x61, 0x8a, - 0x51, 0xf2, 0x58, 0x79, 0x8c, 0x25, 0x8f, 0x3e, 0x9c, 0xe0, 0xfa, 0xa9, 0x0b, 0xd8, 0x87, 0xb8, - 0x67, 0x3d, 0x45, 0x87, 0x67, 0x3d, 0xcb, 0x06, 0xe7, 0xf9, 0xda, 0x7f, 0x67, 0xd1, 0xd8, 0x83, - 0x5b, 0x7a, 0x4b, 0xe6, 0x15, 0x3f, 0x0e, 0x15, 0xa7, 0x9f, 0x76, 0xc2, 0x81, 0x82, 0xf4, 0x55, - 0x06, 0xc5, 0x02, 0x8b, 0x36, 0x61, 0xca, 0xa3, 0x07, 0xf0, 0xd2, 0xb1, 0x95, 0xd3, 0xd9, 0x04, - 0x7a, 0x3c, 0x67, 0x5c, 0xd0, 0x47, 0x60, 0x2a, 0x75, 0xda, 0xf2, 0x36, 0x95, 0x5d, 0xec, 0xee, - 0x3a, 0xed, 0x04, 0x33, 0xa8, 0xb9, 0x4e, 0x4f, 0x1d, 0x51, 0x49, 0xf6, 0xaf, 0x53, 0x30, 0x9f, - 0xb9, 0x32, 0xcf, 0x38, 0x9c, 0x75, 0xa4, 0xc3, 0x3d, 0x0d, 0xd3, 0x51, 0xdc, 0x0f, 0x88, 0xa8, - 0x84, 0x50, 0x6b, 0x10, 0x75, 0x69, 0x82, 0x39, 0x8e, 0xda, 0xc8, 0x8b, 0x0f, 0x70, 0x3f, 0x10, - 0x49, 0x46, 0x65, 0xa3, 0x75, 0x06, 0xc5, 0x02, 0x8b, 0xbe, 0x04, 0x73, 0x09, 0x9b, 0xeb, 0xb1, - 0x93, 0x92, 0xb6, 0x7c, 0x87, 0x78, 0x79, 0xe2, 0x37, 0x63, 0x9c, 0x1d, 0x3f, 0xae, 0x99, 0x10, - 0x9c, 0x11, 0x87, 0xbe, 0x6a, 0x99, 0xef, 0xe4, 0x2a, 0x13, 0xe7, 0xc3, 0xf3, 0xa5, 0x08, 0xdc, - 0x91, 0x1f, 0xfc, 0x5c, 0x2e, 0x52, 0x93, 0x68, 0xe6, 0x11, 0x4c, 0x22, 0x18, 0x32, 0x81, 0x3e, - 0x09, 0xb5, 0x9e, 0x13, 0xf8, 0x2d, 0x92, 0xa4, 0xfc, 0xef, 0xa4, 0x6a, 0xfc, 0xcf, 0x2b, 0xb6, - 0x24, 0x10, 0x6b, 0x3c, 0xfb, 0xaf, 0x36, 0xd6, 0x2b, 0x1e, 0x3c, 0xd7, 0x8c, 0xff, 0x6a, 0xd3, - 0x60, 0x6c, 0xd2, 0xd8, 0xdf, 0xb1, 0xe0, 0xf4, 0x50, 0x4b, 0xfc, 0xf4, 0xe6, 0x8d, 0xec, 0xef, - 0x94, 0xe0, 0xd4, 0x90, 0x42, 0x12, 0xb4, 0xff, 0x68, 0x1e, 0x52, 0x8a, 0x32, 0x95, 0xf9, 0x91, - 0x5e, 0x71, 0xbc, 0x4d, 0x40, 0x2f, 0xc4, 0xe5, 0xc7, 0xb7, 0x10, 0xdb, 0x7f, 0x6d, 0x81, 0xf1, - 0xcc, 0x17, 0xfd, 0x86, 0x59, 0x26, 0x65, 0x15, 0x52, 0xd6, 0xc3, 0x39, 0xab, 0x1a, 0x2b, 0x6e, - 0xaf, 0x61, 0x25, 0x57, 0x79, 0x37, 0x2d, 0x8d, 0xe1, 0xa6, 0x1d, 0x3e, 0xe2, 0x39, 0x19, 0x7a, - 0x7d, 0xb3, 0x1e, 0xb0, 0xbe, 0x3d, 0x0b, 0xd5, 0x84, 0x74, 0x5b, 0x34, 0x64, 0x10, 0xeb, 0xa0, - 0x1a, 0x9e, 0xa6, 0x80, 0x63, 0x45, 0x61, 0xff, 0x58, 0x18, 0x4a, 0x44, 0x71, 0x17, 0x72, 0xf5, - 0xc5, 0xe3, 0x07, 0x40, 0x07, 0x00, 0xae, 0x7a, 0x71, 0x50, 0xc0, 0x03, 0x5b, 0xfd, 0x7c, 0xc1, - 0x7c, 0xfe, 0x29, 0x61, 0xd8, 0x10, 0x96, 0x71, 0xc8, 0xf2, 0x51, 0x0e, 0x69, 0xff, 0xbb, 0x05, - 0x99, 0x75, 0x17, 0xf5, 0x60, 0x9a, 0x6a, 0x70, 0x50, 0xc0, 0xe3, 0x08, 0x93, 0x2f, 0x75, 0x56, - 0x71, 0x27, 0xc7, 0x7e, 0x62, 0x2e, 0x05, 0xf9, 0x22, 0x78, 0xe3, 0x26, 0xba, 0x56, 0x90, 0x34, - 0x1a, 0xfb, 0x89, 0xff, 0x58, 0x52, 0x51, 0xa0, 0x7d, 0x01, 0x16, 0x07, 0x34, 0x62, 0x95, 0xb2, - 0xa1, 0x7c, 0x0b, 0x62, 0x38, 0x11, 0x2b, 0x7b, 0xc6, 0x1c, 0x67, 0x7f, 0xdb, 0x82, 0x93, 0x79, - 0xf6, 0xe8, 0x0f, 0x2d, 0x58, 0x4c, 0xf2, 0xfc, 0x1e, 0x89, 0xd5, 0x54, 0x32, 0x63, 0x00, 0x85, - 0x07, 0x35, 0xb0, 0xbf, 0x5f, 0xe2, 0x3e, 0xcc, 0xff, 0x1b, 0x50, 0x2d, 0xd2, 0xd6, 0xc8, 0x45, - 0x9a, 0x4e, 0x11, 0xb7, 0x43, 0xbc, 0x7e, 0x77, 0xe0, 0x7e, 0xbe, 0x29, 0xe0, 0x58, 0x51, 0xb0, - 0x7b, 0xc9, 0xbe, 0x28, 0x8f, 0xcc, 0xb9, 0xd7, 0xba, 0x80, 0x63, 0x45, 0x81, 0x5e, 0x80, 0x39, - 0xa3, 0x93, 0x3c, 0xab, 0x22, 0x92, 0xb3, 0xc6, 0xf2, 0x95, 0xe0, 0x0c, 0x55, 0xee, 0x01, 0xdb, - 0xf4, 0x51, 0x0f, 0xd8, 0xd8, 0xe5, 0x3f, 0x7f, 0x51, 0x24, 0x93, 0x61, 0xfc, 0xf2, 0x5f, 0xc0, - 0xb0, 0xc2, 0xa2, 0xf3, 0x00, 0x3d, 0x27, 0xe8, 0x3b, 0x5d, 0x6a, 0x21, 0x51, 0x4d, 0xa2, 0x26, - 0xd4, 0x96, 0xc2, 0x60, 0x83, 0x8a, 0x4e, 0x91, 0xfc, 0x73, 0xb0, 0x4c, 0x4d, 0x8a, 0x75, 0x64, - 0x4d, 0x4a, 0xb6, 0x6a, 0xa2, 0x34, 0x56, 0xd5, 0x84, 0x59, 0xd0, 0x50, 0x7e, 0x60, 0x41, 0xc3, - 0xc7, 0x60, 0x66, 0x8f, 0x1c, 0x18, 0x95, 0x0f, 0xfc, 0x1f, 0xb6, 0x38, 0x08, 0x4b, 0x1c, 0xb2, - 0xa1, 0xe2, 0x3a, 0xaa, 0xa8, 0x6c, 0x8e, 0x07, 0x1c, 0x6b, 0xab, 0x8c, 0x48, 0x60, 0x1a, 0xf5, - 0x77, 0x3f, 0x38, 0xfb, 0xc4, 0x0f, 0x3e, 0x38, 0xfb, 0xc4, 0xfb, 0x1f, 0x9c, 0x7d, 0xe2, 0x2b, - 0xf7, 0xce, 0x5a, 0xef, 0xde, 0x3b, 0x6b, 0xfd, 0xe0, 0xde, 0x59, 0xeb, 0xfd, 0x7b, 0x67, 0xad, - 0x7f, 0xbb, 0x77, 0xd6, 0xfa, 0x83, 0x1f, 0x9d, 0x7d, 0xe2, 0xd5, 0xaa, 0xf4, 0xd5, 0xff, 0x0b, - 0x00, 0x00, 0xff, 0xff, 0x4b, 0x8e, 0x9f, 0x3b, 0xd9, 0x59, 0x00, 0x00, + // 5390 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x7c, 0x5d, 0x8c, 0x1c, 0xc7, + 0x71, 0xb0, 0x66, 0xf7, 0x6e, 0x6f, 0xb7, 0xee, 0x87, 0xbc, 0xa6, 0x28, 0xaf, 0xf9, 0xd9, 0x3c, + 0x62, 0x04, 0xdb, 0xfa, 0x62, 0x69, 0x2f, 0x62, 0x94, 0x84, 0xb6, 0x12, 0xcb, 0xb7, 0x77, 0xfc, + 0x39, 0xde, 0x0f, 0x4f, 0x7d, 0x47, 0x11, 0x90, 0x7f, 0xa2, 0xe1, 0x6c, 0xef, 0xee, 0xf0, 0x76, + 0x67, 0x46, 0x33, 0xb3, 0x47, 0xae, 0x12, 0x3b, 0x76, 0x62, 0x05, 0x86, 0x23, 0x19, 0x01, 0x8c, + 0x3c, 0x25, 0x0e, 0x92, 0xbc, 0xc5, 0x6f, 0x86, 0x81, 0x00, 0x01, 0xf2, 0xa4, 0x07, 0x47, 0xc8, + 0x43, 0x60, 0x18, 0x4a, 0xa2, 0xfc, 0xe0, 0x12, 0x9d, 0x5f, 0x82, 0xe4, 0xc1, 0x09, 0x82, 0xbc, + 0xf0, 0x29, 0xe8, 0xff, 0x9e, 0xd9, 0x5d, 0xde, 0x1e, 0x77, 0x48, 0x1b, 0xce, 0xdb, 0x4e, 0x55, + 0x75, 0x55, 0x75, 0x77, 0x55, 0x77, 0x75, 0x75, 0xf5, 0xc2, 0x7a, 0xcb, 0x4b, 0xda, 0xbd, 0xdb, + 0x35, 0x37, 0xe8, 0x2e, 0x3b, 0x51, 0x2b, 0x08, 0xa3, 0xe0, 0x0e, 0xfb, 0xf1, 0x9c, 0xdb, 0x58, + 0x0e, 0xf7, 0x5b, 0xcb, 0x4e, 0xe8, 0xc5, 0xcb, 0x4e, 0x18, 0x76, 0x3c, 0xd7, 0x49, 0xbc, 0xc0, + 0x5f, 0x3e, 0x78, 0xde, 0xe9, 0x84, 0x6d, 0xe7, 0xf9, 0xe5, 0x16, 0xf1, 0x49, 0xe4, 0x24, 0xa4, + 0x51, 0x0b, 0xa3, 0x20, 0x09, 0xd0, 0xa7, 0x34, 0xab, 0x9a, 0x64, 0xc5, 0x7e, 0xfc, 0x9a, 0xdb, + 0xa8, 0x85, 0xfb, 0xad, 0x1a, 0x65, 0x55, 0x33, 0x58, 0xd5, 0x24, 0xab, 0x73, 0xcf, 0x19, 0x5a, + 0xb4, 0x82, 0x56, 0xb0, 0xcc, 0x38, 0xde, 0xee, 0x35, 0xd9, 0x17, 0xfb, 0x60, 0xbf, 0xb8, 0xa4, + 0x73, 0xf6, 0xfe, 0xa5, 0xb8, 0xe6, 0x05, 0x54, 0xb7, 0x65, 0x37, 0x88, 0xc8, 0xf2, 0xc1, 0x80, + 0x36, 0xe7, 0x5e, 0xd0, 0x34, 0x5d, 0xc7, 0x6d, 0x7b, 0x3e, 0x89, 0xfa, 0xba, 0x43, 0x5d, 0x92, + 0x38, 0xc3, 0x5a, 0x2d, 0x8f, 0x6a, 0x15, 0xf5, 0xfc, 0xc4, 0xeb, 0x92, 0x81, 0x06, 0xbf, 0x74, + 0x5c, 0x83, 0xd8, 0x6d, 0x93, 0xae, 0x93, 0x6d, 0x67, 0xbf, 0x0e, 0xf3, 0x2b, 0xb7, 0x76, 0x57, + 0x7a, 0x49, 0x7b, 0x35, 0xf0, 0x9b, 0x5e, 0x0b, 0xfd, 0x22, 0xcc, 0xba, 0x9d, 0x5e, 0x9c, 0x90, + 0x68, 0xdb, 0xe9, 0x92, 0xaa, 0x75, 0xc1, 0x7a, 0xa6, 0x52, 0x3f, 0xf3, 0xee, 0xe1, 0xd2, 0x13, + 0x47, 0x87, 0x4b, 0xb3, 0xab, 0x1a, 0x85, 0x4d, 0x3a, 0xf4, 0xff, 0x61, 0x26, 0x0a, 0x3a, 0x64, + 0x05, 0x6f, 0x57, 0x0b, 0xac, 0xc9, 0x29, 0xd1, 0x64, 0x06, 0x73, 0x30, 0x96, 0x78, 0xfb, 0x9f, + 0x2c, 0x80, 0x95, 0x30, 0xdc, 0x89, 0x82, 0x3b, 0xc4, 0x4d, 0xd0, 0x6b, 0x50, 0xa6, 0xa3, 0xd0, + 0x70, 0x12, 0x87, 0x49, 0x9b, 0xbd, 0xf8, 0xf3, 0x35, 0xde, 0x99, 0x9a, 0xd9, 0x19, 0x3d, 0x73, + 0x94, 0xba, 0x76, 0xf0, 0x7c, 0xed, 0xc6, 0x6d, 0xda, 0x7e, 0x8b, 0x24, 0x4e, 0x1d, 0x09, 0x61, + 0xa0, 0x61, 0x58, 0x71, 0x45, 0xfb, 0x30, 0x15, 0x87, 0xc4, 0x65, 0x8a, 0xcd, 0x5e, 0x5c, 0xaf, + 0x3d, 0xb4, 0x7d, 0xd4, 0xb4, 0xda, 0xbb, 0x21, 0x71, 0xeb, 0x73, 0x42, 0xec, 0x14, 0xfd, 0xc2, + 0x4c, 0x88, 0xfd, 0x8f, 0x16, 0x2c, 0x68, 0xb2, 0x4d, 0x2f, 0x4e, 0xd0, 0xe7, 0x07, 0x7a, 0x58, + 0x1b, 0xaf, 0x87, 0xb4, 0x35, 0xeb, 0xdf, 0x69, 0x21, 0xa8, 0x2c, 0x21, 0x46, 0xef, 0xee, 0xc0, + 0xb4, 0x97, 0x90, 0x6e, 0x5c, 0x2d, 0x5c, 0x28, 0x3e, 0x33, 0x7b, 0xf1, 0x72, 0x2e, 0xdd, 0xab, + 0xcf, 0x0b, 0x89, 0xd3, 0xeb, 0x94, 0x37, 0xe6, 0x22, 0xec, 0xbf, 0xad, 0x98, 0x9d, 0xa3, 0xbd, + 0x46, 0xcf, 0xc3, 0x6c, 0x1c, 0xf4, 0x22, 0x97, 0x60, 0x12, 0x06, 0x71, 0xd5, 0xba, 0x50, 0xa4, + 0x93, 0x4f, 0x6d, 0x65, 0x57, 0x83, 0xb1, 0x49, 0x83, 0x7e, 0xd7, 0x82, 0xb9, 0x06, 0x89, 0x13, + 0xcf, 0x67, 0xf2, 0xa5, 0xe6, 0x2f, 0x4f, 0xa6, 0xb9, 0x04, 0xae, 0x69, 0xce, 0xf5, 0x27, 0x45, + 0x2f, 0xe6, 0x0c, 0x60, 0x8c, 0x53, 0xc2, 0xa9, 0xc1, 0x37, 0x48, 0xec, 0x46, 0x5e, 0x48, 0xbf, + 0xab, 0xc5, 0xb4, 0xc1, 0xaf, 0x69, 0x14, 0x36, 0xe9, 0xd0, 0x3e, 0x4c, 0x53, 0x83, 0x8e, 0xab, + 0x53, 0x4c, 0xf9, 0x2b, 0x13, 0x28, 0x2f, 0x86, 0x93, 0x3a, 0x8a, 0x1e, 0x77, 0xfa, 0x15, 0x63, + 0x2e, 0x03, 0xbd, 0x6d, 0x41, 0x55, 0x78, 0x1b, 0x26, 0x7c, 0x28, 0x6f, 0xb5, 0xbd, 0x84, 0x74, + 0xbc, 0x38, 0xa9, 0x4e, 0x33, 0x05, 0x96, 0xc7, 0x33, 0xa9, 0xab, 0x51, 0xd0, 0x0b, 0x37, 0x3c, + 0xbf, 0x51, 0xbf, 0x20, 0x24, 0x55, 0x57, 0x47, 0x30, 0xc6, 0x23, 0x45, 0xa2, 0x6f, 0x59, 0x70, + 0xce, 0x77, 0xba, 0x24, 0x0e, 0x1d, 0x3a, 0xa9, 0x1c, 0x5d, 0xef, 0x38, 0xee, 0x3e, 0xd3, 0xa8, + 0xf4, 0x70, 0x1a, 0xd9, 0x42, 0xa3, 0x73, 0xdb, 0x23, 0x59, 0xe3, 0x07, 0x88, 0x45, 0x7f, 0x6c, + 0xc1, 0x62, 0x10, 0x85, 0x6d, 0xc7, 0x27, 0x0d, 0x89, 0x8d, 0xab, 0x33, 0xcc, 0xe3, 0x3e, 0x37, + 0xc1, 0xfc, 0xdc, 0xc8, 0xf2, 0xdc, 0x0a, 0x7c, 0x2f, 0x09, 0xa2, 0x5d, 0x92, 0x24, 0x9e, 0xdf, + 0x8a, 0xeb, 0x67, 0x8f, 0x0e, 0x97, 0x16, 0x07, 0xa8, 0xf0, 0xa0, 0x32, 0xe8, 0x1e, 0xcc, 0xc6, + 0x7d, 0xdf, 0xbd, 0xe5, 0xf9, 0x8d, 0xe0, 0x6e, 0x5c, 0x2d, 0x4f, 0xec, 0xb2, 0xbb, 0x8a, 0x9b, + 0x70, 0x3a, 0xcd, 0x1d, 0x9b, 0xa2, 0x86, 0x4f, 0x99, 0x36, 0xa2, 0x4a, 0xde, 0x53, 0xa6, 0xcd, + 0xe8, 0x01, 0x62, 0xd1, 0xd7, 0x2c, 0x98, 0x8f, 0xbd, 0x96, 0xef, 0x24, 0xbd, 0x88, 0x6c, 0x90, + 0x7e, 0x5c, 0x05, 0xa6, 0xc8, 0xd5, 0x49, 0x86, 0xc4, 0xe0, 0x57, 0x3f, 0x2b, 0x14, 0x9c, 0x37, + 0xa1, 0x31, 0x4e, 0x0b, 0xb5, 0xbf, 0x5f, 0x84, 0x59, 0x63, 0x09, 0x79, 0x0c, 0x7b, 0x52, 0x27, + 0xb5, 0x27, 0x5d, 0xcf, 0x67, 0xe9, 0x1b, 0xb5, 0x29, 0xa1, 0x04, 0x4a, 0x71, 0xe2, 0x24, 0xbd, + 0x98, 0x2d, 0x6f, 0xb3, 0x17, 0x37, 0x73, 0x92, 0xc7, 0x78, 0xd6, 0x17, 0x84, 0xc4, 0x12, 0xff, + 0xc6, 0x42, 0x16, 0x7a, 0x1d, 0x2a, 0x41, 0x48, 0xa3, 0x0d, 0xba, 0xae, 0x4e, 0x31, 0xc1, 0x6b, + 0x93, 0xb8, 0xa1, 0xe4, 0x55, 0x9f, 0x3f, 0x3a, 0x5c, 0xaa, 0xa8, 0x4f, 0xac, 0xa5, 0xd8, 0x7f, + 0x6f, 0xc1, 0x93, 0x86, 0x82, 0xab, 0x81, 0xdf, 0xf0, 0xd8, 0x8c, 0x5e, 0x80, 0xa9, 0xa4, 0x1f, + 0xca, 0x78, 0x46, 0x8d, 0xd1, 0x5e, 0x3f, 0x24, 0x98, 0x61, 0x68, 0x04, 0xd3, 0x25, 0x71, 0xec, + 0xb4, 0x48, 0x36, 0x82, 0xd9, 0xe2, 0x60, 0x2c, 0xf1, 0x28, 0x02, 0xd4, 0x71, 0xe2, 0x64, 0x2f, + 0x72, 0xfc, 0x98, 0xb1, 0xdf, 0xf3, 0xba, 0x44, 0x0c, 0xed, 0xcf, 0x8d, 0x67, 0x28, 0xb4, 0x45, + 0xfd, 0xa9, 0xa3, 0xc3, 0x25, 0xb4, 0x39, 0xc0, 0x09, 0x0f, 0xe1, 0x6e, 0x7f, 0xcb, 0x82, 0xa7, + 0x86, 0xef, 0x72, 0xe8, 0xe3, 0x50, 0x8a, 0x49, 0x74, 0x40, 0x22, 0xd1, 0x3b, 0x3d, 0x1f, 0x0c, + 0x8a, 0x05, 0x16, 0x2d, 0x43, 0x45, 0xb9, 0xa2, 0xe8, 0xe3, 0xa2, 0x20, 0xad, 0x68, 0xff, 0xd5, + 0x34, 0x74, 0xd0, 0xe8, 0x87, 0xd8, 0x13, 0xd5, 0xa0, 0xb1, 0xe8, 0x8f, 0x61, 0xec, 0x7f, 0xb6, + 0xe0, 0x94, 0xa1, 0xd5, 0x63, 0x08, 0x77, 0xf6, 0xd3, 0xe1, 0xce, 0x95, 0x7c, 0x2c, 0x79, 0x44, + 0xbc, 0xf3, 0xbd, 0x12, 0x2c, 0x9a, 0xf6, 0xce, 0x56, 0x2f, 0x16, 0xeb, 0x92, 0x30, 0xb8, 0x89, + 0x37, 0xc5, 0x80, 0xeb, 0x58, 0x97, 0x83, 0xb1, 0xc4, 0xd3, 0x11, 0x0c, 0x9d, 0xa4, 0x2d, 0x46, + 0x5b, 0x8d, 0xe0, 0x8e, 0x93, 0xb4, 0x31, 0xc3, 0xa0, 0xcf, 0xc0, 0x42, 0xe2, 0x44, 0x2d, 0x92, + 0x60, 0x72, 0xe0, 0xc5, 0xd2, 0x53, 0x2a, 0xf5, 0xa7, 0x04, 0xed, 0xc2, 0x5e, 0x0a, 0x8b, 0x33, + 0xd4, 0xc8, 0x87, 0xa9, 0x36, 0xe9, 0x74, 0xc5, 0x36, 0xb7, 0x93, 0x93, 0x63, 0xb3, 0x8e, 0x5e, + 0x23, 0x9d, 0x6e, 0xbd, 0x4c, 0xf5, 0xa5, 0xbf, 0x30, 0x93, 0x83, 0x7e, 0xcb, 0x82, 0xca, 0x7e, + 0x2f, 0x4e, 0x82, 0xae, 0xf7, 0x06, 0xa9, 0x96, 0x99, 0xd4, 0x9b, 0x79, 0x4a, 0xdd, 0x90, 0xcc, + 0xb9, 0x9b, 0xab, 0x4f, 0xac, 0xc5, 0xa2, 0x37, 0x60, 0x66, 0x3f, 0x0e, 0x7c, 0x9f, 0xd0, 0x8d, + 0x8b, 0x6a, 0xb0, 0x9b, 0xab, 0x06, 0x9c, 0x75, 0x7d, 0x96, 0x4e, 0xa9, 0xf8, 0xc0, 0x52, 0x20, + 0x1b, 0x80, 0x86, 0x17, 0x11, 0x37, 0x09, 0xa2, 0x7e, 0x15, 0xf2, 0x1f, 0x80, 0x35, 0xc9, 0x9c, + 0x0f, 0x80, 0xfa, 0xc4, 0x5a, 0x2c, 0x3a, 0x80, 0x52, 0xd8, 0xe9, 0xb5, 0x3c, 0xbf, 0x3a, 0xcb, + 0x14, 0xc0, 0x79, 0x2a, 0xb0, 0xc3, 0x38, 0xd7, 0x81, 0x2e, 0x21, 0xfc, 0x37, 0x16, 0xd2, 0xd0, + 0xd3, 0x30, 0xed, 0xb6, 0x9d, 0x28, 0xa9, 0xce, 0x31, 0x23, 0x55, 0x5e, 0xb3, 0x4a, 0x81, 0x98, + 0xe3, 0xec, 0xbf, 0xb2, 0xe0, 0xdc, 0xe8, 0x5e, 0x71, 0xf7, 0x71, 0x7b, 0x51, 0xcc, 0x57, 0xe3, + 0xb2, 0xe9, 0x3e, 0x0c, 0x8c, 0x25, 0x1e, 0x7d, 0x19, 0x66, 0xee, 0x88, 0x79, 0x2e, 0xe4, 0x3f, + 0xcf, 0xd7, 0xc5, 0x3c, 0x2b, 0xf9, 0xd7, 0xe5, 0x5c, 0x0b, 0xa1, 0xf6, 0xf7, 0x8a, 0x70, 0x76, + 0xa8, 0x5b, 0xa0, 0x1a, 0xc0, 0x81, 0xd3, 0xe9, 0x91, 0x2b, 0x1e, 0x3d, 0x03, 0xf0, 0x53, 0xcf, + 0x02, 0xdd, 0xed, 0x5f, 0x51, 0x50, 0x6c, 0x50, 0xa0, 0xdf, 0x00, 0x08, 0x9d, 0xc8, 0xe9, 0x92, + 0x84, 0x44, 0x72, 0xed, 0xba, 0x36, 0x41, 0x67, 0xa8, 0x12, 0x3b, 0x92, 0xa1, 0x8e, 0x35, 0x14, + 0x28, 0xc6, 0x86, 0x3c, 0x7a, 0xc6, 0x89, 0x48, 0x87, 0x38, 0x31, 0xd9, 0xd6, 0xeb, 0xb9, 0x3a, + 0xe3, 0x60, 0x8d, 0xc2, 0x26, 0x1d, 0xdd, 0x58, 0x58, 0x17, 0x62, 0xb1, 0x26, 0xa9, 0x8d, 0x85, + 0x75, 0x32, 0xc6, 0x02, 0x8b, 0xde, 0xb2, 0x60, 0xa1, 0xe9, 0x75, 0x88, 0x96, 0x2e, 0x0e, 0x25, + 0x9b, 0x13, 0xf6, 0xf0, 0x8a, 0xc9, 0x54, 0x2f, 0x89, 0x29, 0x70, 0x8c, 0x33, 0xb2, 0xed, 0xff, + 0xb1, 0xa0, 0x3a, 0x6a, 0xb2, 0x51, 0x08, 0x33, 0xe4, 0x5e, 0xf2, 0x8a, 0x13, 0xf1, 0x59, 0x9b, + 0x2c, 0xfa, 0x16, 0x4c, 0x5f, 0x71, 0x22, 0x6d, 0x44, 0x97, 0x39, 0x77, 0x2c, 0xc5, 0xa0, 0x16, + 0x4c, 0x25, 0x1d, 0x27, 0x8f, 0xf3, 0xb9, 0x21, 0x4e, 0x47, 0x30, 0x9b, 0x2b, 0x31, 0x66, 0x02, + 0xec, 0x1f, 0x0e, 0xeb, 0xb7, 0x58, 0xbf, 0xa8, 0x09, 0x10, 0xff, 0xc0, 0x8b, 0x02, 0xbf, 0x4b, + 0xfc, 0x24, 0x9b, 0xd7, 0xb9, 0xac, 0x51, 0xd8, 0xa4, 0x43, 0xbf, 0x39, 0xc4, 0x6e, 0x37, 0x26, + 0xe8, 0x82, 0x50, 0x67, 0x6c, 0xd3, 0xb5, 0xff, 0xba, 0x38, 0x64, 0x31, 0x51, 0x9b, 0x02, 0xba, + 0x08, 0x40, 0x03, 0x91, 0x9d, 0x88, 0x34, 0xbd, 0x7b, 0xa2, 0x57, 0x8a, 0xe5, 0xb6, 0xc2, 0x60, + 0x83, 0x4a, 0xb6, 0xd9, 0xed, 0x35, 0x69, 0x9b, 0xc2, 0x60, 0x1b, 0x8e, 0xc1, 0x06, 0x15, 0x7a, + 0x01, 0x4a, 0x5e, 0xd7, 0x69, 0x11, 0x1a, 0x41, 0x53, 0x5f, 0xff, 0x08, 0x75, 0x83, 0x75, 0x06, + 0xb9, 0x7f, 0xb8, 0xb4, 0xa0, 0x14, 0x62, 0x20, 0x2c, 0x68, 0xd1, 0x9f, 0x58, 0x30, 0xe7, 0x06, + 0xdd, 0x6e, 0xe0, 0x6f, 0x3a, 0xb7, 0x49, 0x47, 0x26, 0x0b, 0x5a, 0x8f, 0x64, 0xbf, 0xac, 0xad, + 0x1a, 0x92, 0x2e, 0xfb, 0x49, 0xd4, 0xd7, 0xf9, 0x0f, 0x13, 0x85, 0x53, 0x2a, 0xd1, 0xe5, 0xf8, + 0x80, 0x44, 0x2c, 0xf2, 0x98, 0x4e, 0x47, 0x33, 0xaf, 0x70, 0x30, 0x96, 0xf8, 0x73, 0x2f, 0xc1, + 0xe2, 0x80, 0x0c, 0x74, 0x1a, 0x8a, 0xfb, 0xa4, 0xcf, 0x87, 0x1e, 0xd3, 0x9f, 0xe8, 0x49, 0x98, + 0x66, 0x0b, 0x03, 0x1f, 0x5a, 0xcc, 0x3f, 0x3e, 0x5d, 0xb8, 0x64, 0xd9, 0x7f, 0x60, 0xc1, 0x87, + 0x46, 0x6c, 0x37, 0x2a, 0xd8, 0xb4, 0x46, 0x05, 0x9b, 0xe8, 0x8b, 0x50, 0x24, 0xfe, 0x81, 0x30, + 0xc2, 0xd5, 0x09, 0xc6, 0xf0, 0xb2, 0x7f, 0xc0, 0xc7, 0x67, 0xe6, 0xe8, 0x70, 0xa9, 0x78, 0xd9, + 0x3f, 0xc0, 0x94, 0xb1, 0xfd, 0x66, 0x29, 0x15, 0xcc, 0xee, 0xca, 0x93, 0x13, 0xd3, 0x52, 0x84, + 0xb2, 0x9b, 0x79, 0x4e, 0x9d, 0x11, 0xa9, 0xf3, 0xf4, 0x98, 0x90, 0x85, 0xbe, 0x6e, 0xb1, 0xa4, + 0x94, 0x8c, 0xf0, 0xc5, 0xe6, 0xf7, 0x08, 0x12, 0x64, 0x66, 0x9e, 0x4b, 0x02, 0xb1, 0x29, 0x9a, + 0x9a, 0x47, 0xc8, 0xf3, 0x53, 0x62, 0xdb, 0x50, 0xe6, 0x21, 0xd3, 0x56, 0x12, 0x8f, 0x7a, 0x00, + 0x71, 0xdf, 0x77, 0x77, 0x82, 0x8e, 0xe7, 0xf6, 0xc5, 0x81, 0x6f, 0xd2, 0xdc, 0x06, 0x67, 0xc6, + 0xb7, 0x56, 0xfd, 0x8d, 0x0d, 0x41, 0xe8, 0xdb, 0x16, 0x2c, 0x7a, 0x2d, 0x3f, 0x88, 0xc8, 0x9a, + 0xd7, 0x6c, 0x92, 0x88, 0xf8, 0x2e, 0x91, 0x1b, 0xd0, 0xde, 0x04, 0xe2, 0x65, 0xb6, 0x62, 0x3d, + 0xcb, 0xbb, 0xfe, 0x61, 0x31, 0x04, 0x8b, 0x03, 0x28, 0x3c, 0xa8, 0x09, 0x72, 0x60, 0xca, 0xf3, + 0x9b, 0x81, 0xc8, 0x8a, 0xbd, 0x34, 0x81, 0x46, 0xeb, 0x7e, 0x33, 0xd0, 0x9e, 0x41, 0xbf, 0x30, + 0x63, 0x8d, 0x36, 0xe1, 0xc9, 0x48, 0x1c, 0x08, 0xae, 0x79, 0x31, 0x8d, 0xb2, 0x36, 0xbd, 0xae, + 0x97, 0xb0, 0x43, 0x41, 0xb1, 0x5e, 0x3d, 0x3a, 0x5c, 0x7a, 0x12, 0x0f, 0xc1, 0xe3, 0xa1, 0xad, + 0xec, 0xff, 0x2e, 0xa7, 0x4f, 0x3d, 0xfc, 0x34, 0xff, 0x06, 0x54, 0x22, 0x95, 0x54, 0xe3, 0x5b, + 0xe7, 0x7a, 0x0e, 0xa3, 0x2b, 0x72, 0x08, 0xea, 0x20, 0xaa, 0xd3, 0x67, 0x5a, 0x1c, 0xdd, 0x42, + 0xe9, 0x84, 0x0b, 0x3f, 0x98, 0xd4, 0xa6, 0x84, 0x48, 0x9d, 0x28, 0xe9, 0xfb, 0x2e, 0x66, 0x02, + 0x50, 0x00, 0xa5, 0x36, 0x71, 0x3a, 0x49, 0x5b, 0x9c, 0xe6, 0xaf, 0x4e, 0x14, 0xc0, 0x50, 0x46, + 0xd9, 0x1c, 0x09, 0x87, 0x62, 0x21, 0x06, 0xf5, 0x60, 0xa6, 0xcd, 0xc7, 0x5e, 0xec, 0x0d, 0xd7, + 0x27, 0x1a, 0xd3, 0xd4, 0x6c, 0x6a, 0x57, 0x15, 0x00, 0x2c, 0x65, 0xa1, 0xdf, 0xb6, 0x00, 0x5c, + 0x99, 0x1c, 0x91, 0xce, 0x72, 0x23, 0x9f, 0xf5, 0x45, 0x25, 0x5d, 0xf4, 0xa6, 0xaa, 0x40, 0x31, + 0x36, 0xc4, 0xa2, 0xd7, 0x60, 0x2e, 0x22, 0x6e, 0xe0, 0xbb, 0x5e, 0x87, 0x34, 0x56, 0x92, 0x6a, + 0xe9, 0xc4, 0x19, 0x94, 0xd3, 0x74, 0x73, 0xc3, 0x06, 0x0f, 0x9c, 0xe2, 0x88, 0xde, 0xb4, 0x60, + 0x41, 0x65, 0x87, 0xe8, 0x54, 0x10, 0x71, 0x50, 0x5e, 0xcf, 0x23, 0x11, 0xc5, 0x18, 0xd6, 0x11, + 0x0d, 0x49, 0xd3, 0x30, 0x9c, 0x11, 0x8a, 0x5e, 0x05, 0x08, 0x6e, 0xb3, 0x34, 0x0c, 0xed, 0x67, + 0xf9, 0xc4, 0xfd, 0x5c, 0xe0, 0x89, 0x44, 0xc9, 0x01, 0x1b, 0xdc, 0xd0, 0x06, 0x00, 0xf7, 0x93, + 0xbd, 0x7e, 0x48, 0xd8, 0x79, 0xb8, 0x52, 0xff, 0xa4, 0x1c, 0xf9, 0x5d, 0x85, 0xb9, 0x7f, 0xb8, + 0x34, 0x78, 0x96, 0x61, 0xf9, 0x2f, 0xa3, 0x39, 0xba, 0x07, 0x33, 0x71, 0xaf, 0xdb, 0x75, 0xd4, + 0xd1, 0x76, 0x2b, 0xa7, 0x0d, 0x8f, 0x33, 0xd5, 0x26, 0x29, 0x00, 0x58, 0x8a, 0xb3, 0x7d, 0x40, + 0x83, 0xf4, 0xe8, 0x05, 0x98, 0x23, 0xf7, 0x12, 0x12, 0xf9, 0x4e, 0xe7, 0x26, 0xde, 0x94, 0x27, + 0x2d, 0x36, 0xed, 0x97, 0x0d, 0x38, 0x4e, 0x51, 0x21, 0x5b, 0x45, 0x6b, 0x05, 0x46, 0x0f, 0x3a, + 0x5a, 0x93, 0xb1, 0x99, 0xfd, 0x3b, 0x85, 0xd4, 0x6e, 0xbf, 0x17, 0x11, 0x82, 0x3a, 0x30, 0xed, + 0x07, 0x0d, 0xb5, 0xbe, 0x5d, 0xcd, 0x61, 0x7d, 0xdb, 0x0e, 0x1a, 0xc6, 0xad, 0x0e, 0xfd, 0x8a, + 0x31, 0x17, 0xc2, 0x92, 0xdf, 0xf2, 0x8a, 0x80, 0x21, 0x44, 0x68, 0x93, 0x9b, 0x58, 0x95, 0xfc, + 0xbe, 0x61, 0x4a, 0xc1, 0x69, 0xa1, 0xf6, 0x8f, 0xac, 0xd4, 0x21, 0xf7, 0x96, 0x93, 0xb8, 0xed, + 0xcb, 0x07, 0x34, 0xf8, 0xdf, 0x48, 0x25, 0x4d, 0x7f, 0xd9, 0x4c, 0x9a, 0xde, 0x3f, 0x5c, 0xfa, + 0xc4, 0xa8, 0x2b, 0xe7, 0xbb, 0x94, 0x43, 0x8d, 0xb1, 0x30, 0xf2, 0xab, 0x5f, 0x82, 0x59, 0x43, + 0x63, 0xb1, 0x94, 0xe7, 0x95, 0xbe, 0x53, 0x71, 0x8c, 0x01, 0xc4, 0xa6, 0x3c, 0xfb, 0x9d, 0x22, + 0xcc, 0x88, 0x9b, 0xae, 0xb1, 0x13, 0xa6, 0x32, 0x24, 0x2d, 0x8c, 0x0c, 0x49, 0x43, 0x28, 0xb9, + 0xec, 0xde, 0x5c, 0xec, 0x17, 0x93, 0x1c, 0xe9, 0x85, 0x76, 0xfc, 0x1e, 0x5e, 0xeb, 0xc4, 0xbf, + 0xb1, 0x90, 0x83, 0xde, 0xb6, 0xe0, 0x94, 0x4b, 0xcf, 0x50, 0xae, 0x5e, 0xd2, 0xa6, 0x26, 0xbe, + 0x44, 0x58, 0x4d, 0x73, 0xac, 0x7f, 0x48, 0x48, 0x3f, 0x95, 0x41, 0xe0, 0xac, 0x6c, 0xf4, 0x22, + 0xcc, 0xf3, 0xd1, 0x7a, 0x25, 0x75, 0x88, 0xd0, 0xf7, 0x2e, 0x26, 0x12, 0xa7, 0x69, 0x51, 0x8d, + 0x9f, 0xc4, 0x58, 0xb6, 0x39, 0x66, 0x01, 0x92, 0xc8, 0xa2, 0xa8, 0x74, 0x74, 0x8c, 0x0d, 0x0a, + 0xfb, 0xcf, 0x8b, 0x30, 0x9f, 0x1a, 0x26, 0xf4, 0x2c, 0x94, 0x7b, 0x31, 0x75, 0x7c, 0x75, 0x72, + 0x50, 0xc9, 0xe3, 0x9b, 0x02, 0x8e, 0x15, 0x05, 0xa5, 0x0e, 0x9d, 0x38, 0xbe, 0x1b, 0x44, 0x0d, + 0x31, 0xa9, 0x8a, 0x7a, 0x47, 0xc0, 0xb1, 0xa2, 0xa0, 0x47, 0xe6, 0xdb, 0xc4, 0x89, 0x48, 0xb4, + 0x17, 0xec, 0x93, 0x81, 0x9b, 0xe1, 0xba, 0x46, 0x61, 0x93, 0x8e, 0xcd, 0x50, 0xd2, 0x89, 0x57, + 0x3b, 0x1e, 0xf1, 0x13, 0xae, 0x66, 0x0e, 0x33, 0xb4, 0xb7, 0xb9, 0x6b, 0x72, 0xd4, 0x33, 0x94, + 0x41, 0xe0, 0xac, 0x6c, 0xf4, 0x55, 0x0b, 0xe6, 0x9d, 0xbb, 0xb1, 0xae, 0xf1, 0x60, 0x53, 0x34, + 0x99, 0xad, 0xa6, 0x6a, 0x46, 0xea, 0x8b, 0x74, 0xa2, 0x53, 0x20, 0x9c, 0x96, 0x68, 0xbf, 0x67, + 0x81, 0xac, 0x1d, 0x79, 0x0c, 0x77, 0x04, 0xad, 0xf4, 0x1d, 0x41, 0x7d, 0x72, 0xa7, 0x1c, 0x71, + 0x3f, 0xb0, 0x0d, 0x33, 0xf4, 0x40, 0xec, 0xf8, 0x0d, 0xf4, 0x31, 0x98, 0x71, 0xf9, 0x4f, 0xb1, + 0x47, 0xb1, 0xec, 0xb1, 0xc0, 0x62, 0x89, 0x43, 0x1f, 0x81, 0x29, 0x27, 0x6a, 0xc9, 0x7d, 0x89, + 0x25, 0xd7, 0x57, 0xa2, 0x56, 0x8c, 0x19, 0xd4, 0x7e, 0xbb, 0x00, 0xb0, 0x1a, 0x74, 0x43, 0x27, + 0x22, 0x8d, 0xbd, 0xe0, 0xff, 0xfc, 0xe1, 0xd3, 0x7e, 0xcb, 0x02, 0x44, 0xc7, 0x23, 0xf0, 0x89, + 0xaf, 0x73, 0x46, 0x68, 0x19, 0x2a, 0xae, 0x84, 0x0a, 0xaf, 0x57, 0xe7, 0x07, 0x45, 0x8e, 0x35, + 0xcd, 0x18, 0x0b, 0xf9, 0xd3, 0x32, 0x67, 0x51, 0x4c, 0x27, 0xb6, 0x59, 0xa6, 0x53, 0xa4, 0x30, + 0xec, 0x6f, 0x16, 0xe0, 0x29, 0x6e, 0xd0, 0x5b, 0x8e, 0xef, 0xb4, 0x48, 0x97, 0x6a, 0x35, 0x6e, + 0xf6, 0xe2, 0x35, 0x7a, 0x0c, 0xf4, 0x64, 0x22, 0x7b, 0x22, 0x9b, 0xe4, 0xb6, 0xc4, 0xad, 0x67, + 0xdd, 0xf7, 0x12, 0xcc, 0x38, 0xa3, 0x10, 0xca, 0xb2, 0xbc, 0x4b, 0x6c, 0x47, 0x79, 0x48, 0x51, + 0x8e, 0x76, 0x55, 0xf0, 0xc6, 0x4a, 0x8a, 0xfd, 0x8e, 0x05, 0xd9, 0x1d, 0x82, 0x6d, 0xae, 0xfc, + 0xae, 0x39, 0xbb, 0xb9, 0xa6, 0x6f, 0x87, 0x4f, 0x70, 0xdf, 0xfa, 0x79, 0x98, 0x75, 0x92, 0x84, + 0x74, 0xc3, 0x84, 0x85, 0xcf, 0xc5, 0x87, 0x0b, 0x9f, 0xb7, 0x82, 0x86, 0xd7, 0xf4, 0x58, 0xf8, + 0x6c, 0xb2, 0xb3, 0x5f, 0x86, 0xb2, 0x4c, 0x08, 0x8d, 0x31, 0x8d, 0x4f, 0xa7, 0x92, 0x5b, 0x23, + 0x0c, 0xe5, 0xad, 0x02, 0x2c, 0x5c, 0xf5, 0x7b, 0x3b, 0x57, 0x77, 0x7a, 0xb7, 0x3b, 0x9e, 0xbb, + 0x41, 0xfa, 0xb4, 0xdd, 0x3e, 0xe9, 0xaf, 0xaf, 0x09, 0xd6, 0xaa, 0xdd, 0x06, 0x05, 0x62, 0x8e, + 0xa3, 0x3b, 0x4e, 0xd3, 0xf3, 0x5b, 0x24, 0x0a, 0x23, 0xcf, 0x4f, 0x84, 0x08, 0xe5, 0x26, 0x57, + 0x34, 0x0a, 0x9b, 0x74, 0x94, 0x77, 0x70, 0xd7, 0x27, 0x51, 0xd6, 0x78, 0x6f, 0x50, 0x20, 0xe6, + 0x38, 0x4a, 0x94, 0x44, 0xbd, 0x38, 0x11, 0xb9, 0x7c, 0x45, 0xb4, 0x47, 0x81, 0x98, 0xe3, 0xe8, + 0xa4, 0xc4, 0xbd, 0xdb, 0xec, 0x20, 0x91, 0x49, 0x06, 0xee, 0x72, 0x30, 0x96, 0x78, 0x4a, 0xba, + 0x4f, 0xfa, 0x6b, 0x74, 0x05, 0x2f, 0xa5, 0x49, 0x37, 0x38, 0x18, 0x4b, 0xbc, 0x7d, 0x64, 0x01, + 0x4a, 0x0f, 0xc7, 0x63, 0xd8, 0x04, 0xfc, 0xf4, 0x26, 0x30, 0xc9, 0x81, 0x2f, 0xad, 0xfb, 0x88, + 0xbd, 0xe0, 0x4f, 0x2d, 0x98, 0x33, 0x8f, 0xfc, 0xa8, 0x95, 0x71, 0x84, 0x1b, 0x69, 0x47, 0xb8, + 0x7f, 0xb8, 0xf4, 0xab, 0xc3, 0x8a, 0x5e, 0x5b, 0x5e, 0x12, 0x84, 0xf1, 0x73, 0xc4, 0x6f, 0x79, + 0x3e, 0x61, 0xf1, 0x34, 0x4f, 0x15, 0xa4, 0xf2, 0x09, 0xab, 0x41, 0x83, 0x3c, 0x84, 0x27, 0xd9, + 0xb7, 0x60, 0x71, 0xe0, 0x5e, 0x65, 0x0c, 0xa3, 0x3f, 0xf6, 0x1a, 0xdb, 0x7e, 0xdb, 0x82, 0xf9, + 0xd4, 0x9d, 0x54, 0x4e, 0xae, 0xc4, 0x5c, 0x22, 0x60, 0x79, 0xa2, 0xc8, 0xf3, 0x79, 0x98, 0x5d, + 0x36, 0x5c, 0x42, 0xa3, 0xb0, 0x49, 0x67, 0x6f, 0x01, 0xcb, 0x8f, 0xe5, 0xe5, 0xd0, 0x2f, 0x43, + 0x99, 0xb2, 0xa3, 0x13, 0x9e, 0x17, 0xcb, 0x00, 0xca, 0xd7, 0x6f, 0xed, 0xf1, 0x90, 0xd1, 0x86, + 0xa2, 0xe7, 0xf0, 0xad, 0xac, 0xa8, 0x8d, 0x7a, 0x3d, 0x8e, 0x7b, 0x6c, 0xb9, 0xa2, 0x48, 0xf4, + 0x34, 0x14, 0xc9, 0xbd, 0x90, 0xb1, 0x2c, 0xea, 0xed, 0xee, 0xf2, 0xbd, 0xd0, 0x8b, 0x48, 0x4c, + 0x89, 0xc8, 0xbd, 0x10, 0x9d, 0x83, 0x82, 0xd7, 0x10, 0xcb, 0x00, 0x08, 0x9a, 0xc2, 0xfa, 0x1a, + 0x2e, 0x78, 0x0d, 0xbb, 0x07, 0xa0, 0x2f, 0x90, 0xf2, 0x9a, 0x9e, 0x0b, 0x30, 0xe5, 0x06, 0x0d, + 0x22, 0xe6, 0x45, 0xb1, 0x61, 0xf6, 0xc9, 0x30, 0xf6, 0x2d, 0x58, 0xd8, 0xf0, 0x83, 0xbb, 0x3e, + 0x5d, 0x34, 0xae, 0x78, 0xa4, 0xd3, 0xa0, 0x8c, 0x9b, 0xf4, 0x47, 0x76, 0x29, 0x64, 0x58, 0xcc, + 0x71, 0xaa, 0x60, 0xa7, 0x30, 0xaa, 0x60, 0xc7, 0xfe, 0x86, 0x05, 0xa7, 0xb3, 0xd7, 0x49, 0x3f, + 0xb1, 0xd0, 0xe0, 0x2b, 0x54, 0x19, 0x79, 0x13, 0x73, 0x23, 0xe4, 0xf9, 0xad, 0x4b, 0x30, 0x77, + 0xbb, 0xe7, 0x75, 0x1a, 0xe2, 0x5b, 0xe8, 0xa3, 0x2e, 0x65, 0xea, 0x06, 0x0e, 0xa7, 0x28, 0xd1, + 0x45, 0x80, 0xdb, 0x9e, 0xef, 0x44, 0xfd, 0x1d, 0xed, 0x76, 0x2a, 0x9b, 0x56, 0x57, 0x18, 0x6c, + 0x50, 0xd9, 0xff, 0x50, 0x00, 0x5d, 0x14, 0x85, 0x9a, 0x22, 0x65, 0x6a, 0x4d, 0x1c, 0xeb, 0xef, + 0xf6, 0x7d, 0x57, 0xd7, 0x5e, 0x95, 0x33, 0x19, 0xd3, 0xaf, 0x59, 0x30, 0x4b, 0xa3, 0x0f, 0xcf, + 0x49, 0x48, 0xa3, 0xde, 0x17, 0xe1, 0xcd, 0x56, 0x1e, 0xe9, 0xb5, 0x75, 0xce, 0x36, 0x88, 0xb4, + 0xbf, 0xaf, 0x6b, 0x49, 0xd8, 0x14, 0x8b, 0xbe, 0x20, 0x92, 0xec, 0xc5, 0x7c, 0x92, 0xec, 0xe5, + 0x74, 0x82, 0xdd, 0x8e, 0x01, 0x0d, 0xaa, 0x75, 0xc2, 0xc3, 0xe7, 0x32, 0x54, 0x9c, 0x5e, 0x12, + 0x74, 0xa9, 0xc6, 0x6c, 0x98, 0xca, 0xda, 0x34, 0x57, 0x24, 0x02, 0x6b, 0x1a, 0xfb, 0xbd, 0x29, + 0xc8, 0xe4, 0x15, 0x51, 0xcf, 0x2c, 0xa9, 0xb3, 0x72, 0x2c, 0xa9, 0x53, 0x9a, 0x0c, 0x2b, 0xab, + 0xa3, 0x07, 0xaa, 0xb0, 0xed, 0xc4, 0xd2, 0x4b, 0x5e, 0x96, 0x2e, 0xb0, 0x43, 0x81, 0xf7, 0x0f, + 0x97, 0x3e, 0x3b, 0xde, 0x46, 0x46, 0x0d, 0x66, 0x99, 0x5f, 0x3e, 0x6a, 0xd1, 0x8c, 0x07, 0xe6, + 0xfc, 0xcd, 0xad, 0xac, 0x78, 0x4c, 0x50, 0xf8, 0x65, 0x7e, 0xdb, 0x84, 0x49, 0xdc, 0xeb, 0x24, + 0xe2, 0x80, 0xbd, 0x9d, 0x97, 0x99, 0x73, 0xae, 0xfa, 0xda, 0x89, 0x7f, 0x63, 0x43, 0x22, 0xfa, + 0x1c, 0x54, 0xe2, 0xc4, 0x89, 0x92, 0x87, 0xcc, 0x5c, 0xab, 0x01, 0xdf, 0x95, 0x4c, 0xb0, 0xe6, + 0x87, 0x5e, 0x05, 0x68, 0x7a, 0xbe, 0x17, 0xb7, 0x19, 0xf7, 0x99, 0x87, 0x0b, 0x78, 0xaf, 0x28, + 0x0e, 0xd8, 0xe0, 0x66, 0x7f, 0x16, 0x2e, 0x1c, 0x57, 0xd1, 0x4c, 0x8f, 0xa9, 0x77, 0x9d, 0xc8, + 0x17, 0x05, 0x3a, 0xcc, 0x1b, 0x6e, 0x39, 0x91, 0x8f, 0x19, 0xd4, 0xfe, 0x4e, 0x01, 0x66, 0x8d, + 0xa2, 0xf5, 0x31, 0xf6, 0x92, 0x4c, 0x91, 0x7d, 0x61, 0xcc, 0x22, 0xfb, 0x67, 0xa0, 0x1c, 0x06, + 0x1d, 0xcf, 0xf5, 0xd4, 0xbd, 0xfb, 0x1c, 0xcb, 0xd5, 0x08, 0x18, 0x56, 0x58, 0x94, 0x40, 0xe5, + 0xce, 0xdd, 0x84, 0xed, 0xa6, 0xf2, 0x96, 0x7d, 0x92, 0x1b, 0x62, 0xb9, 0x33, 0xeb, 0x69, 0x92, + 0x90, 0x18, 0x6b, 0x41, 0xc8, 0x86, 0x52, 0x2b, 0x0a, 0x7a, 0x21, 0xbf, 0x41, 0x11, 0x79, 0x66, + 0x56, 0x1d, 0x1d, 0x63, 0x81, 0xb1, 0x7f, 0x58, 0x80, 0x0a, 0x26, 0x61, 0xb0, 0x1a, 0x91, 0x46, + 0x8c, 0x3e, 0x0a, 0xc5, 0x5e, 0xd4, 0x11, 0x23, 0x35, 0x2b, 0x98, 0x17, 0x6f, 0xe2, 0x4d, 0x4c, + 0xe1, 0xa9, 0x15, 0xa5, 0x70, 0xa2, 0x74, 0x56, 0xf1, 0xd8, 0x74, 0xd6, 0x8b, 0x30, 0x1f, 0xc7, + 0xed, 0x9d, 0xc8, 0x3b, 0x70, 0x12, 0xb2, 0x41, 0xfa, 0xe2, 0x20, 0xa0, 0x33, 0x75, 0xbb, 0xd7, + 0x34, 0x12, 0xa7, 0x69, 0xd1, 0x55, 0x58, 0xd4, 0x79, 0x25, 0x12, 0x25, 0x2c, 0xee, 0xe7, 0x47, + 0x04, 0x75, 0x1b, 0xaa, 0x33, 0x51, 0x82, 0x00, 0x0f, 0xb6, 0x41, 0x6b, 0x70, 0x3a, 0x05, 0xa4, + 0x8a, 0xf0, 0xf3, 0x43, 0x55, 0xf0, 0x39, 0x9d, 0xe2, 0x43, 0x75, 0x19, 0x68, 0x61, 0xbf, 0x6f, + 0xc1, 0xbc, 0x1a, 0xd4, 0xc7, 0x70, 0x98, 0xf0, 0xd2, 0x87, 0x89, 0xb5, 0x89, 0x32, 0xf4, 0x42, + 0xed, 0x11, 0xe7, 0x88, 0x3f, 0x2a, 0x01, 0xb0, 0x77, 0x32, 0x1e, 0xbb, 0xa9, 0xbb, 0x00, 0x53, + 0x11, 0x09, 0x83, 0xac, 0x6f, 0x51, 0x0a, 0xcc, 0x30, 0x3f, 0xbd, 0x36, 0x33, 0x2c, 0x55, 0x3d, + 0xfd, 0x13, 0x4c, 0x55, 0xef, 0xc2, 0x59, 0xcf, 0x8f, 0x89, 0xdb, 0x8b, 0xc4, 0x9d, 0xfe, 0xb5, + 0x20, 0x56, 0xf6, 0x57, 0xae, 0x7f, 0x54, 0x30, 0x3a, 0xbb, 0x3e, 0x8c, 0x08, 0x0f, 0x6f, 0x4b, + 0xc7, 0x53, 0x22, 0xd8, 0x3a, 0x5d, 0x36, 0xe2, 0x77, 0x01, 0xc7, 0x8a, 0x82, 0xc6, 0x00, 0xc4, + 0x77, 0x6e, 0x77, 0xc8, 0x66, 0x33, 0x66, 0xd7, 0x80, 0x46, 0x0c, 0x70, 0x99, 0x23, 0xae, 0xec, + 0x62, 0x4d, 0x33, 0xdc, 0xef, 0x2a, 0x39, 0xf9, 0x1d, 0x9c, 0xd4, 0xef, 0x54, 0x54, 0x3e, 0x3b, + 0xb2, 0x8c, 0x5e, 0xee, 0x05, 0x73, 0x23, 0xf7, 0x82, 0xcf, 0xc0, 0x82, 0xe7, 0xb7, 0x49, 0xe4, + 0x25, 0xa4, 0xc1, 0x1c, 0xa1, 0x3a, 0xcf, 0x06, 0x42, 0x95, 0xf7, 0xad, 0xa7, 0xb0, 0x38, 0x43, + 0x6d, 0x7f, 0xbd, 0x00, 0x67, 0xb5, 0x83, 0x50, 0xcd, 0xbc, 0x26, 0xb5, 0x12, 0x56, 0x0c, 0xc6, + 0xef, 0x17, 0x8c, 0xa7, 0x8b, 0x2a, 0x6a, 0xde, 0x55, 0x18, 0x6c, 0x50, 0xd1, 0xf9, 0x73, 0x49, + 0xc4, 0x2e, 0xaa, 0xb2, 0xde, 0xb3, 0x2a, 0xe0, 0x58, 0x51, 0xb0, 0xd7, 0x91, 0x24, 0x4a, 0x44, + 0x32, 0x24, 0x7b, 0x25, 0xb0, 0xaa, 0x51, 0xd8, 0xa4, 0xa3, 0xfb, 0x98, 0x2b, 0x27, 0x8f, 0x7a, + 0xd0, 0x1c, 0xdf, 0xc7, 0xd4, 0x7c, 0x29, 0xac, 0x54, 0x87, 0x86, 0x9e, 0x62, 0x79, 0x4d, 0xa9, + 0xc3, 0x42, 0x52, 0x45, 0x61, 0xff, 0xa7, 0x05, 0x1f, 0x1e, 0x3a, 0x14, 0x8f, 0x61, 0x49, 0xec, + 0xa5, 0x97, 0xc4, 0x9d, 0x09, 0x97, 0xc4, 0x81, 0x2e, 0x8c, 0x58, 0x1e, 0xff, 0xce, 0x82, 0x05, + 0x4d, 0xff, 0x18, 0xfa, 0xd9, 0xcc, 0xef, 0x7d, 0xa5, 0xd6, 0xbb, 0x5e, 0x19, 0xe8, 0xd8, 0xfb, + 0xac, 0x63, 0x3c, 0x1e, 0x5b, 0x71, 0xe5, 0xa3, 0x95, 0x63, 0xe2, 0xaa, 0x03, 0x28, 0xb1, 0x5a, + 0x49, 0xa9, 0xdd, 0x76, 0x0e, 0x57, 0xc7, 0x5c, 0x38, 0x3b, 0x52, 0xeb, 0xe4, 0x2d, 0xfb, 0x8c, + 0xb1, 0x90, 0x46, 0xcd, 0xb4, 0xe1, 0xc5, 0x74, 0x91, 0x6a, 0x88, 0xa3, 0xbf, 0x1a, 0xc2, 0x35, + 0x01, 0xc7, 0x8a, 0xc2, 0xee, 0x42, 0x35, 0xcd, 0x7c, 0x8d, 0x34, 0xd9, 0xe1, 0x6d, 0xac, 0x3e, + 0xd2, 0x73, 0x13, 0x6b, 0xb5, 0xd9, 0x73, 0xb2, 0xcf, 0x56, 0x56, 0x24, 0x02, 0x6b, 0x1a, 0xfb, + 0xcf, 0x2c, 0x38, 0x33, 0xa4, 0x33, 0x39, 0xa6, 0x3c, 0x12, 0xed, 0xfc, 0x23, 0x9e, 0x12, 0x35, + 0x48, 0xd3, 0x91, 0xe7, 0x12, 0xe3, 0x14, 0xb3, 0xc6, 0xc1, 0x58, 0xe2, 0xed, 0x7f, 0xb7, 0xe0, + 0x54, 0x5a, 0xd7, 0x18, 0x5d, 0x07, 0xc4, 0x3b, 0xb3, 0xe6, 0xc5, 0x6e, 0x70, 0x40, 0xa2, 0x3e, + 0xed, 0x39, 0xd7, 0xfa, 0x9c, 0xe0, 0x84, 0x56, 0x06, 0x28, 0xf0, 0x90, 0x56, 0xe8, 0x1b, 0xec, + 0x32, 0x47, 0x8e, 0xb6, 0x34, 0x93, 0xdd, 0xdc, 0xcc, 0x44, 0xcf, 0xa4, 0x19, 0xce, 0x2b, 0x79, + 0xd8, 0x14, 0x6e, 0xff, 0xb8, 0x08, 0x73, 0xb2, 0xf9, 0x9a, 0xd7, 0x6c, 0xd2, 0xf1, 0x66, 0x51, + 0x72, 0x36, 0x13, 0xc4, 0x42, 0x68, 0xcc, 0x71, 0x74, 0xbc, 0xf7, 0x3d, 0xbf, 0x91, 0xcd, 0xd0, + 0x6c, 0x78, 0x7e, 0x03, 0x33, 0x4c, 0xfa, 0x61, 0x53, 0xf1, 0x04, 0x0f, 0x9b, 0xa6, 0x1e, 0x74, + 0x60, 0xe1, 0x0f, 0x6d, 0x74, 0xd8, 0x62, 0x2c, 0xf4, 0x7b, 0x1a, 0x85, 0x4d, 0x3a, 0xaa, 0x49, + 0xc7, 0x3b, 0x20, 0xbc, 0x51, 0x29, 0xad, 0xc9, 0xa6, 0x44, 0x60, 0x4d, 0x43, 0x35, 0x69, 0x78, + 0xcd, 0x26, 0x0b, 0x1d, 0x0c, 0x4d, 0xe8, 0xe8, 0x60, 0x86, 0xa1, 0x14, 0xed, 0x20, 0xd8, 0x17, + 0xd1, 0x82, 0xa2, 0xb8, 0x16, 0x04, 0xfb, 0x98, 0x61, 0xd0, 0x16, 0x9c, 0xf1, 0x83, 0xa8, 0xeb, + 0x74, 0xbc, 0x37, 0x48, 0x43, 0x49, 0x11, 0x51, 0xc2, 0xff, 0x13, 0x0d, 0xce, 0x6c, 0x0f, 0x92, + 0xe0, 0x61, 0xed, 0xa8, 0xf9, 0x85, 0x11, 0x69, 0x78, 0x6e, 0x62, 0x72, 0x83, 0xb4, 0xf9, 0xed, + 0x0c, 0x50, 0xe0, 0x21, 0xad, 0xec, 0xff, 0x60, 0x1b, 0xd4, 0x88, 0x3a, 0xca, 0xbc, 0xa6, 0xff, + 0xd8, 0x67, 0x6a, 0x69, 0x03, 0x99, 0x1a, 0xc3, 0x40, 0x5e, 0x80, 0xb9, 0x3b, 0x71, 0xe0, 0xef, + 0x04, 0x9e, 0xaf, 0x9e, 0x33, 0x88, 0xb2, 0xa3, 0xeb, 0xbb, 0x37, 0xb6, 0x25, 0x1c, 0xa7, 0xa8, + 0xec, 0x77, 0xa6, 0xe1, 0x29, 0x55, 0x80, 0x43, 0x92, 0xbb, 0x41, 0xb4, 0xef, 0xf9, 0x2d, 0x96, + 0x87, 0xfe, 0xb6, 0x05, 0x73, 0xdc, 0x50, 0x44, 0x25, 0x38, 0xaf, 0x30, 0x72, 0xf3, 0x28, 0xf5, + 0x49, 0x49, 0xaa, 0xed, 0x19, 0x52, 0x32, 0x55, 0xe0, 0x26, 0x0a, 0xa7, 0xd4, 0x41, 0x6f, 0x00, + 0xc8, 0x87, 0x65, 0xcd, 0x3c, 0xde, 0xd6, 0x49, 0xe5, 0x30, 0x69, 0xea, 0x10, 0x6c, 0x4f, 0x49, + 0xc0, 0x86, 0x34, 0xf4, 0xa6, 0x05, 0xa5, 0x0e, 0x1f, 0x15, 0x9e, 0xbe, 0xfb, 0x42, 0xfe, 0xa3, + 0x62, 0x8e, 0x87, 0xda, 0xd4, 0xc4, 0x48, 0x08, 0xe1, 0x08, 0xc3, 0x8c, 0xe7, 0xb7, 0x22, 0x12, + 0xcb, 0x0c, 0xc2, 0x27, 0x8c, 0x30, 0xa2, 0xe6, 0x06, 0x11, 0x61, 0x41, 0x43, 0xe0, 0x34, 0xea, + 0x4e, 0xc7, 0xf1, 0x5d, 0x12, 0xad, 0x73, 0x72, 0xbd, 0xbe, 0x0b, 0x00, 0x96, 0x8c, 0x06, 0xea, + 0xd7, 0xa6, 0xc7, 0xa9, 0x5f, 0x3b, 0xf7, 0x12, 0x2c, 0x0e, 0x4c, 0xe3, 0x49, 0x0a, 0xed, 0xcf, + 0x7d, 0x0a, 0x66, 0x1f, 0xb6, 0x46, 0xff, 0xbd, 0x69, 0xbd, 0x48, 0x6f, 0x07, 0x0d, 0x56, 0xb8, + 0x15, 0xe9, 0xd9, 0x14, 0x11, 0x56, 0x5e, 0xb6, 0x61, 0x3c, 0x42, 0x52, 0x40, 0x6c, 0xca, 0xa3, + 0x96, 0x19, 0x3a, 0x11, 0xf1, 0x1f, 0xa9, 0x65, 0xee, 0x28, 0x09, 0xd8, 0x90, 0x86, 0x48, 0x2a, + 0xab, 0xbc, 0x3a, 0x61, 0x56, 0x99, 0x86, 0x7b, 0x43, 0xcb, 0xb7, 0xdf, 0xb6, 0x60, 0xc1, 0x4f, + 0xd9, 0xab, 0xc8, 0x67, 0xbe, 0x9c, 0xbb, 0x23, 0xf0, 0x6a, 0xd5, 0x34, 0x0c, 0x67, 0x84, 0xa3, + 0x15, 0x38, 0x25, 0x67, 0x20, 0x5d, 0xd5, 0xa5, 0xce, 0xda, 0x38, 0x8d, 0xc6, 0x59, 0x7a, 0xa3, + 0x02, 0xb3, 0x34, 0xaa, 0x02, 0x13, 0xed, 0xab, 0x62, 0xeb, 0x99, 0x7c, 0x8b, 0xad, 0x61, 0xb0, + 0xd0, 0xda, 0xfe, 0x7e, 0x01, 0x4e, 0x4b, 0xad, 0x6f, 0x1c, 0x90, 0x28, 0xf2, 0x1a, 0x6c, 0x5f, + 0xe0, 0x68, 0x1d, 0x60, 0xa9, 0x7d, 0xe1, 0x9a, 0x44, 0x60, 0x4d, 0x43, 0x23, 0x3b, 0x1e, 0x64, + 0xc5, 0xd9, 0xfc, 0xb4, 0x08, 0xde, 0xb0, 0xc4, 0xd3, 0x93, 0xfb, 0xe0, 0xab, 0x84, 0x42, 0xfa, + 0xe4, 0x3e, 0xd6, 0xfb, 0x81, 0xb7, 0x2c, 0x38, 0xb5, 0x9f, 0xba, 0x41, 0x93, 0xeb, 0xd3, 0x24, + 0x77, 0xda, 0xe9, 0x3b, 0x39, 0x3d, 0xb3, 0x69, 0x78, 0x8c, 0xb3, 0xa2, 0xed, 0xff, 0xb2, 0xc0, + 0x74, 0xd6, 0xf1, 0x36, 0x71, 0xe3, 0x91, 0x51, 0xe1, 0xc1, 0x8f, 0x8c, 0xd4, 0x7e, 0x5f, 0x1c, + 0x2f, 0xdc, 0x9b, 0x3a, 0x41, 0xb8, 0x37, 0x3d, 0x32, 0x40, 0xf8, 0x28, 0x14, 0x7b, 0x5e, 0x43, + 0x44, 0x6c, 0x3a, 0x2d, 0xbb, 0xbe, 0x86, 0x29, 0xdc, 0xfe, 0x8b, 0x69, 0x7d, 0x36, 0x13, 0xe9, + 0xff, 0x9f, 0x89, 0x6e, 0x37, 0x55, 0x89, 0x02, 0xef, 0xf9, 0xf6, 0x40, 0x89, 0xc2, 0xaf, 0x9c, + 0xfc, 0x66, 0x87, 0x0f, 0xd0, 0xa8, 0x0a, 0x85, 0x99, 0x63, 0xae, 0x75, 0xee, 0x40, 0x99, 0x06, + 0xb5, 0x2c, 0xbd, 0x52, 0x4e, 0x29, 0x55, 0xbe, 0x26, 0xe0, 0xf7, 0x0f, 0x97, 0x3e, 0x7d, 0x72, + 0xb5, 0x64, 0x6b, 0xac, 0xf8, 0xa3, 0x18, 0x2a, 0xf4, 0x37, 0xbb, 0x81, 0x12, 0xe1, 0xf2, 0x4d, + 0xe5, 0xfe, 0x12, 0x91, 0xcb, 0xf5, 0x96, 0x96, 0x83, 0x7c, 0xa8, 0xb0, 0xc7, 0x4b, 0x4c, 0x28, + 0x8f, 0xaa, 0x77, 0xd4, 0x5d, 0x90, 0x44, 0xdc, 0x3f, 0x5c, 0x7a, 0xf1, 0xe4, 0x42, 0x55, 0x73, + 0xac, 0x45, 0xd8, 0x1f, 0x14, 0xb5, 0xed, 0x8a, 0xca, 0x94, 0x9f, 0x09, 0xdb, 0xbd, 0x94, 0xb1, + 0xdd, 0x0b, 0x03, 0xb6, 0xbb, 0xa0, 0x1f, 0xfb, 0xa4, 0xac, 0xf1, 0x71, 0xee, 0x3b, 0x63, 0x1c, + 0xdf, 0xd8, 0x6e, 0xfb, 0x7a, 0xcf, 0x8b, 0x48, 0xbc, 0x13, 0xf5, 0x7c, 0xcf, 0x6f, 0x31, 0x5b, + 0x2c, 0x9b, 0xbb, 0x6d, 0x0a, 0x8d, 0xb3, 0xf4, 0xf6, 0x1f, 0x16, 0xe1, 0x54, 0xe6, 0xf1, 0x0f, + 0x7a, 0x16, 0xca, 0xf2, 0x75, 0x57, 0x36, 0xb1, 0xa9, 0xfe, 0x4c, 0x42, 0x51, 0xa0, 0x2f, 0x02, + 0x34, 0x48, 0xd8, 0x09, 0xfa, 0xec, 0xc2, 0x71, 0xea, 0xc4, 0x17, 0x8e, 0x2a, 0x92, 0x5a, 0x53, + 0x5c, 0xb0, 0xc1, 0x51, 0x14, 0xa6, 0x4c, 0xb3, 0xe2, 0x95, 0x4c, 0x61, 0x8a, 0x51, 0xe6, 0x5a, + 0x7a, 0x8c, 0x65, 0xae, 0x1e, 0x9c, 0xe2, 0xfa, 0xa9, 0x0b, 0xd8, 0x87, 0xb8, 0x67, 0x3d, 0x43, + 0xa7, 0x67, 0x2d, 0xcd, 0x06, 0x67, 0xf9, 0xda, 0xdf, 0x64, 0xb1, 0x07, 0x1f, 0xe9, 0x2d, 0x99, + 0x57, 0xfc, 0x38, 0x94, 0x9c, 0x5e, 0xd2, 0x0e, 0x06, 0x1e, 0x21, 0xac, 0x30, 0x28, 0x16, 0x58, + 0xb4, 0x09, 0x53, 0x0d, 0x7a, 0x00, 0x2f, 0x9c, 0x58, 0x39, 0x9d, 0x4d, 0xa0, 0xc7, 0x73, 0xc6, + 0x05, 0x7d, 0x04, 0xa6, 0x12, 0xa7, 0x25, 0x6f, 0x53, 0xd9, 0xc5, 0xee, 0x9e, 0xd3, 0x8a, 0x31, + 0x83, 0x9a, 0xeb, 0xf4, 0xd4, 0x31, 0xeb, 0xf4, 0x8b, 0xc6, 0x1f, 0x37, 0x19, 0xd9, 0xea, 0xc1, + 0xff, 0x5b, 0x62, 0x11, 0x66, 0x9a, 0xd6, 0xfe, 0x05, 0x98, 0x33, 0xff, 0x8f, 0x69, 0xac, 0xe2, + 0x48, 0xfb, 0x5f, 0xa6, 0x60, 0x3e, 0x75, 0x49, 0x9f, 0x32, 0x71, 0xeb, 0x58, 0x13, 0x7f, 0x1a, + 0xa6, 0xc3, 0xa8, 0xe7, 0x13, 0x51, 0x7b, 0xa1, 0x84, 0x50, 0x27, 0x22, 0x98, 0xe3, 0xe8, 0xac, + 0x34, 0xa2, 0x3e, 0xee, 0xf9, 0x22, 0xad, 0xa9, 0x66, 0x65, 0x8d, 0x41, 0xb1, 0xc0, 0xa2, 0x2f, + 0xc1, 0x5c, 0xcc, 0x56, 0x97, 0xc8, 0x49, 0x48, 0x4b, 0xbe, 0x76, 0xbd, 0x3a, 0xf1, 0xcb, 0x44, + 0xce, 0x8e, 0x1f, 0x10, 0x4d, 0x08, 0x4e, 0x89, 0x43, 0x5f, 0xb5, 0xcc, 0xd7, 0x98, 0xa5, 0x89, + 0x33, 0xf0, 0xd9, 0xe2, 0x07, 0xee, 0x3a, 0x0f, 0x7e, 0x94, 0x19, 0x2a, 0xb7, 0x9d, 0x79, 0x04, + 0x6e, 0x0b, 0x43, 0x5c, 0xf6, 0x93, 0x50, 0xe9, 0x3a, 0xbe, 0xd7, 0x24, 0x71, 0xc2, 0xff, 0x3b, + 0xad, 0xc2, 0xff, 0x22, 0x65, 0x4b, 0x02, 0xb1, 0xc6, 0xb3, 0x3f, 0x26, 0x64, 0xbd, 0xe2, 0xe1, + 0x7a, 0xc5, 0xf8, 0x63, 0x42, 0x0d, 0xc6, 0x26, 0x8d, 0xfd, 0x5d, 0x0b, 0xce, 0x0e, 0x1d, 0x89, + 0x9f, 0xde, 0x4c, 0x95, 0xfd, 0xdd, 0x02, 0x9c, 0x19, 0x52, 0xba, 0x82, 0x0e, 0x1e, 0xcd, 0x73, + 0x5d, 0x51, 0x18, 0x33, 0x3f, 0xd2, 0x2a, 0x4e, 0xb6, 0xed, 0xe8, 0xa5, 0xbf, 0xf8, 0xf8, 0x96, + 0x7e, 0xfb, 0x2f, 0x2d, 0x30, 0x1e, 0x93, 0xa3, 0x5f, 0x37, 0x0b, 0xb3, 0xac, 0x5c, 0x0a, 0x89, + 0x38, 0x67, 0x55, 0xd5, 0xc5, 0xc7, 0x6b, 0x58, 0x91, 0x57, 0xd6, 0x4c, 0x0b, 0x63, 0x98, 0x69, + 0x9b, 0xcf, 0x78, 0x46, 0x86, 0x5e, 0xdf, 0xac, 0x07, 0xac, 0x6f, 0xcf, 0x42, 0x39, 0x26, 0x9d, + 0x26, 0x0d, 0x52, 0xc4, 0x3a, 0xa8, 0xa6, 0x67, 0x57, 0xc0, 0xb1, 0xa2, 0xb0, 0x7f, 0x2c, 0x06, + 0x4a, 0xc4, 0x8d, 0x97, 0x32, 0x15, 0xcd, 0xe3, 0x87, 0x5c, 0x7d, 0x00, 0x57, 0xbd, 0x6b, 0xc9, + 0xe1, 0x19, 0xb7, 0x7e, 0x24, 0x63, 0x3e, 0x32, 0x96, 0x30, 0x6c, 0x08, 0x4b, 0x19, 0x64, 0xf1, + 0x38, 0x83, 0xb4, 0xff, 0xcd, 0x82, 0xd4, 0xba, 0x8b, 0xba, 0x30, 0x4d, 0x35, 0xe8, 0xe7, 0xf0, + 0x04, 0xc7, 0xe4, 0x4b, 0x8d, 0x55, 0xdc, 0x02, 0xb2, 0x9f, 0x98, 0x4b, 0x41, 0x9e, 0x08, 0x17, + 0xf9, 0x10, 0x6d, 0xe4, 0x24, 0x8d, 0x46, 0x9b, 0xe2, 0x9f, 0xbc, 0x54, 0xdc, 0x69, 0x5f, 0x82, + 0xc5, 0x01, 0x8d, 0x58, 0x6d, 0x6e, 0x20, 0x5f, 0x1c, 0x19, 0x46, 0xc4, 0x0a, 0xad, 0x31, 0xc7, + 0xd9, 0xdf, 0xb1, 0xe0, 0x74, 0x96, 0x3d, 0xfa, 0x7d, 0x0b, 0x16, 0xe3, 0x2c, 0xbf, 0x47, 0x32, + 0x6a, 0x2a, 0x7d, 0x32, 0x80, 0xc2, 0x83, 0x1a, 0xd8, 0x7f, 0x53, 0xe0, 0x36, 0xcc, 0xff, 0x08, + 0x53, 0x2d, 0xd2, 0xd6, 0xc8, 0x45, 0x9a, 0xba, 0x88, 0xdb, 0x26, 0x8d, 0x5e, 0x67, 0xa0, 0x22, + 0x60, 0x57, 0xc0, 0xb1, 0xa2, 0x60, 0x37, 0xa1, 0x3d, 0x51, 0x90, 0x99, 0x31, 0xaf, 0x35, 0x01, + 0xc7, 0x8a, 0x02, 0xbd, 0x00, 0x73, 0x46, 0x27, 0x79, 0x1e, 0x47, 0xa4, 0x83, 0x8d, 0xe5, 0x2b, + 0xc6, 0x29, 0xaa, 0xcc, 0x33, 0xc9, 0xe9, 0xe3, 0x9e, 0x49, 0xb2, 0x72, 0x03, 0xfe, 0x6e, 0x4d, + 0xa6, 0xdf, 0x78, 0xb9, 0x81, 0x80, 0x61, 0x85, 0x45, 0x17, 0x01, 0xba, 0x8e, 0xdf, 0x73, 0x3a, + 0x74, 0x84, 0x44, 0xfd, 0x8a, 0x72, 0xa8, 0x2d, 0x85, 0xc1, 0x06, 0x15, 0x75, 0x91, 0xec, 0xa3, + 0xc3, 0x54, 0x15, 0x8c, 0x75, 0x6c, 0x15, 0x4c, 0xba, 0x4e, 0xa3, 0x30, 0x56, 0x9d, 0x86, 0x59, + 0x42, 0x51, 0x7c, 0x60, 0x09, 0xc5, 0xc7, 0xf4, 0xc3, 0x14, 0x5e, 0x6b, 0x31, 0x3b, 0xec, 0x51, + 0x0a, 0xb2, 0xa1, 0xe4, 0x3a, 0xaa, 0x8c, 0x6d, 0x8e, 0x07, 0x1c, 0xab, 0x2b, 0x8c, 0x48, 0x60, + 0xea, 0xb5, 0x77, 0x3f, 0x38, 0xff, 0xc4, 0x0f, 0x3e, 0x38, 0xff, 0xc4, 0xfb, 0x1f, 0x9c, 0x7f, + 0xe2, 0x2b, 0x47, 0xe7, 0xad, 0x77, 0x8f, 0xce, 0x5b, 0x3f, 0x38, 0x3a, 0x6f, 0xbd, 0x7f, 0x74, + 0xde, 0xfa, 0xd7, 0xa3, 0xf3, 0xd6, 0xef, 0xfd, 0xe8, 0xfc, 0x13, 0xaf, 0x96, 0xa5, 0xad, 0xfe, + 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2b, 0xf3, 0x46, 0xaf, 0xc6, 0x5c, 0x00, 0x00, } func (m *AWSAuthConfig) Marshal() (dAtA []byte, err error) { @@ -2693,6 +2791,20 @@ func (m *AppProjectSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.SignatureKeys) > 0 { + for iNdEx := len(m.SignatureKeys) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SignatureKeys[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 + } + } if len(m.NamespaceResourceWhitelist) > 0 { for iNdEx := len(m.NamespaceResourceWhitelist) - 1; iNdEx >= 0; iNdEx-- { { @@ -4204,6 +4316,106 @@ func (m *EnvEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *GnuPGPublicKey) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GnuPGPublicKey) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GnuPGPublicKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.KeyData) + copy(dAtA[i:], m.KeyData) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.KeyData))) + i-- + dAtA[i] = 0x32 + i -= len(m.SubType) + copy(dAtA[i:], m.SubType) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SubType))) + i-- + dAtA[i] = 0x2a + i -= len(m.Trust) + copy(dAtA[i:], m.Trust) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Trust))) + i-- + dAtA[i] = 0x22 + i -= len(m.Owner) + copy(dAtA[i:], m.Owner) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Owner))) + i-- + dAtA[i] = 0x1a + i -= len(m.Fingerprint) + copy(dAtA[i:], m.Fingerprint) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Fingerprint))) + i-- + dAtA[i] = 0x12 + i -= len(m.KeyID) + copy(dAtA[i:], m.KeyID) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.KeyID))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GnuPGPublicKeyList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GnuPGPublicKeyList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GnuPGPublicKeyList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *HealthStatus) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6020,6 +6232,11 @@ func (m *RevisionMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + i -= len(m.SignatureInfo) + copy(dAtA[i:], m.SignatureInfo) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SignatureInfo))) + i-- + dAtA[i] = 0x2a i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) @@ -6052,6 +6269,34 @@ func (m *RevisionMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *SignatureKey) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignatureKey) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignatureKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.KeyID) + copy(dAtA[i:], m.KeyID) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.KeyID))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *SyncOperation) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6722,6 +6967,12 @@ func (m *AppProjectSpec) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if len(m.SignatureKeys) > 0 { + for _, e := range m.SignatureKeys { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -7252,13 +7503,51 @@ func (m *EnvEntry) Size() (n int) { return n } -func (m *HealthStatus) Size() (n int) { +func (m *GnuPGPublicKey) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Status) + l = len(m.KeyID) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Fingerprint) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Owner) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Trust) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.SubType) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.KeyData) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *GnuPGPublicKeyList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *HealthStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Status) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) @@ -7945,6 +8234,19 @@ func (m *RevisionMetadata) Size() (n int) { } l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) + l = len(m.SignatureInfo) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *SignatureKey) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.KeyID) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -8242,6 +8544,11 @@ func (this *AppProjectSpec) String() string { repeatedStringForNamespaceResourceWhitelist += fmt.Sprintf("%v", f) + "," } repeatedStringForNamespaceResourceWhitelist += "}" + repeatedStringForSignatureKeys := "[]SignatureKey{" + for _, f := range this.SignatureKeys { + repeatedStringForSignatureKeys += strings.Replace(strings.Replace(f.String(), "SignatureKey", "SignatureKey", 1), `&`, ``, 1) + "," + } + repeatedStringForSignatureKeys += "}" s := strings.Join([]string{`&AppProjectSpec{`, `SourceRepos:` + fmt.Sprintf("%v", this.SourceRepos) + `,`, `Destinations:` + repeatedStringForDestinations + `,`, @@ -8252,6 +8559,7 @@ func (this *AppProjectSpec) String() string { `OrphanedResources:` + strings.Replace(this.OrphanedResources.String(), "OrphanedResourcesMonitorSettings", "OrphanedResourcesMonitorSettings", 1) + `,`, `SyncWindows:` + repeatedStringForSyncWindows + `,`, `NamespaceResourceWhitelist:` + repeatedStringForNamespaceResourceWhitelist + `,`, + `SignatureKeys:` + repeatedStringForSignatureKeys + `,`, `}`, }, "") return s @@ -8656,6 +8964,37 @@ func (this *EnvEntry) String() string { }, "") return s } +func (this *GnuPGPublicKey) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&GnuPGPublicKey{`, + `KeyID:` + fmt.Sprintf("%v", this.KeyID) + `,`, + `Fingerprint:` + fmt.Sprintf("%v", this.Fingerprint) + `,`, + `Owner:` + fmt.Sprintf("%v", this.Owner) + `,`, + `Trust:` + fmt.Sprintf("%v", this.Trust) + `,`, + `SubType:` + fmt.Sprintf("%v", this.SubType) + `,`, + `KeyData:` + fmt.Sprintf("%v", this.KeyData) + `,`, + `}`, + }, "") + return s +} +func (this *GnuPGPublicKeyList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]GnuPGPublicKey{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "GnuPGPublicKey", "GnuPGPublicKey", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&GnuPGPublicKeyList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} func (this *HealthStatus) String() string { if this == nil { return "nil" @@ -9194,6 +9533,17 @@ func (this *RevisionMetadata) String() string { `Date:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Date), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, `Tags:` + fmt.Sprintf("%v", this.Tags) + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `SignatureInfo:` + fmt.Sprintf("%v", this.SignatureInfo) + `,`, + `}`, + }, "") + return s +} +func (this *SignatureKey) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&SignatureKey{`, + `KeyID:` + fmt.Sprintf("%v", this.KeyID) + `,`, `}`, }, "") return s @@ -10042,6 +10392,40 @@ func (m *AppProjectSpec) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureKeys", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SignatureKeys = append(m.SignatureKeys, SignatureKey{}) + if err := m.SignatureKeys[len(m.SignatureKeys)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -14045,15 +14429,439 @@ func (m *ConfigManagementPlugin) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ConfigManagementPlugin: wiretype end group for non-group") + return fmt.Errorf("proto: ConfigManagementPlugin: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConfigManagementPlugin: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Init", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Init == nil { + m.Init = &Command{} + } + if err := m.Init.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Generate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Generate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConnectionState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConnectionState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConnectionState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Status = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ModifiedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ModifiedAt == nil { + m.ModifiedAt = &v1.Time{} + } + if err := m.ModifiedAt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EnvEntry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EnvEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EnvEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GnuPGPublicKey) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GnuPGPublicKey: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ConfigManagementPlugin: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: GnuPGPublicKey: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field KeyID", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -14081,13 +14889,13 @@ func (m *ConfigManagementPlugin) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.KeyID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Init", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Fingerprint", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -14097,33 +14905,29 @@ func (m *ConfigManagementPlugin) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Init == nil { - m.Init = &Command{} - } - if err := m.Init.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Fingerprint = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Generate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Owner", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -14133,81 +14937,27 @@ func (m *ConfigManagementPlugin) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Generate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Owner = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ConnectionState) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ConnectionState: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ConnectionState: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Trust", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -14235,11 +14985,11 @@ func (m *ConnectionState) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Status = string(dAtA[iNdEx:postIndex]) + m.Trust = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SubType", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -14267,13 +15017,13 @@ func (m *ConnectionState) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Message = string(dAtA[iNdEx:postIndex]) + m.SubType = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ModifiedAt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field KeyData", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -14283,27 +15033,23 @@ func (m *ConnectionState) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if m.ModifiedAt == nil { - m.ModifiedAt = &v1.Time{} - } - if err := m.ModifiedAt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.KeyData = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -14329,7 +15075,7 @@ func (m *ConnectionState) Unmarshal(dAtA []byte) error { } return nil } -func (m *EnvEntry) Unmarshal(dAtA []byte) error { +func (m *GnuPGPublicKeyList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -14352,17 +15098,17 @@ func (m *EnvEntry) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EnvEntry: wiretype end group for non-group") + return fmt.Errorf("proto: GnuPGPublicKeyList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EnvEntry: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: GnuPGPublicKeyList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -14372,29 +15118,30 @@ func (m *EnvEntry) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -14404,23 +15151,25 @@ func (m *EnvEntry) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Value = string(dAtA[iNdEx:postIndex]) + m.Items = append(m.Items, GnuPGPublicKey{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -21054,6 +21803,123 @@ func (m *RevisionMetadata) Unmarshal(dAtA []byte) error { } m.Message = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureInfo", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SignatureInfo = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SignatureKey) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignatureKey: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignatureKey: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KeyID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.KeyID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/application/v1alpha1/generated.proto b/pkg/apis/application/v1alpha1/generated.proto index be4588cb37e41..6796574bc4b1a 100644 --- a/pkg/apis/application/v1alpha1/generated.proto +++ b/pkg/apis/application/v1alpha1/generated.proto @@ -74,6 +74,9 @@ message AppProjectSpec { // NamespaceResourceWhitelist contains list of whitelisted namespace level resources repeated k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind namespaceResourceWhitelist = 9; + + // List of PGP key IDs that commits to be synced to must be signed with + repeated SignatureKey signatureKeys = 10; } // Application is a definition of Application resource. @@ -400,6 +403,34 @@ message EnvEntry { optional string value = 2; } +// GnuPGPublicKey is a representation of a GnuPG public key +message GnuPGPublicKey { + // KeyID in hexadecimal string format + optional string keyID = 1; + + // Fingerprint of the key + optional string fingerprint = 2; + + // Owner identification + optional string owner = 3; + + // Trust level + optional string trust = 4; + + // Key sub type (e.g. rsa4096) + optional string subType = 5; + + // Key data + optional string keyData = 6; +} + +// GnuPGPublicKeyList is a collection of GnuPGPublicKey objects +message GnuPGPublicKeyList { + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + repeated GnuPGPublicKey items = 2; +} + message HealthStatus { optional string status = 1; @@ -867,6 +898,16 @@ message RevisionMetadata { // probably the commit message, // this is truncated to the first newline or 64 characters (which ever comes first) optional string message = 4; + + // If revision was signed with GPG, and signature verification is enabled, + // this contains a hint on the signer + optional string signatureInfo = 5; +} + +// SignatureKey is the specification of a key required to verify commit signatures with +message SignatureKey { + // The ID of the key in hexadecimal notation + optional string keyID = 1; } // SyncOperation contains sync operation details. diff --git a/pkg/apis/application/v1alpha1/openapi_generated.go b/pkg/apis/application/v1alpha1/openapi_generated.go index 199a1d0baea49..d792e1e608056 100644 --- a/pkg/apis/application/v1alpha1/openapi_generated.go +++ b/pkg/apis/application/v1alpha1/openapi_generated.go @@ -42,6 +42,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ConfigManagementPlugin": schema_pkg_apis_application_v1alpha1_ConfigManagementPlugin(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ConnectionState": schema_pkg_apis_application_v1alpha1_ConnectionState(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.EnvEntry": schema_pkg_apis_application_v1alpha1_EnvEntry(ref), + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.GnuPGPublicKey": schema_pkg_apis_application_v1alpha1_GnuPGPublicKey(ref), + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.GnuPGPublicKeyList": schema_pkg_apis_application_v1alpha1_GnuPGPublicKeyList(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.HealthStatus": schema_pkg_apis_application_v1alpha1_HealthStatus(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.HelmFileParameter": schema_pkg_apis_application_v1alpha1_HelmFileParameter(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.HelmParameter": schema_pkg_apis_application_v1alpha1_HelmParameter(ref), @@ -77,6 +79,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ResourceStatus": schema_pkg_apis_application_v1alpha1_ResourceStatus(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.RevisionHistory": schema_pkg_apis_application_v1alpha1_RevisionHistory(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.RevisionMetadata": schema_pkg_apis_application_v1alpha1_RevisionMetadata(ref), + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SignatureKey": schema_pkg_apis_application_v1alpha1_SignatureKey(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SyncOperation": schema_pkg_apis_application_v1alpha1_SyncOperation(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SyncOperationResource": schema_pkg_apis_application_v1alpha1_SyncOperationResource(ref), "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SyncOperationResult": schema_pkg_apis_application_v1alpha1_SyncOperationResult(ref), @@ -318,11 +321,24 @@ func schema_pkg_apis_application_v1alpha1_AppProjectSpec(ref common.ReferenceCal }, }, }, + "signatureKeys": { + SchemaProps: spec.SchemaProps{ + Description: "List of PGP key IDs that commits to be synced to must be signed with", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SignatureKey"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationDestination", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.OrphanedResourcesMonitorSettings", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ProjectRole", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SyncWindow", "k8s.io/apimachinery/pkg/apis/meta/v1.GroupKind"}, + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ApplicationDestination", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.OrphanedResourcesMonitorSettings", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ProjectRole", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SignatureKey", "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.SyncWindow", "k8s.io/apimachinery/pkg/apis/meta/v1.GroupKind"}, } } @@ -1438,6 +1454,95 @@ func schema_pkg_apis_application_v1alpha1_EnvEntry(ref common.ReferenceCallback) } } +func schema_pkg_apis_application_v1alpha1_GnuPGPublicKey(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GnuPGPublicKey is a representation of a GnuPG public key", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "keyID": { + SchemaProps: spec.SchemaProps{ + Description: "KeyID in hexadecimal string format", + Type: []string{"string"}, + Format: "", + }, + }, + "fingerprint": { + SchemaProps: spec.SchemaProps{ + Description: "Fingerprint of the key", + Type: []string{"string"}, + Format: "", + }, + }, + "owner": { + SchemaProps: spec.SchemaProps{ + Description: "Owner identification", + Type: []string{"string"}, + Format: "", + }, + }, + "trust": { + SchemaProps: spec.SchemaProps{ + Description: "Trust level", + Type: []string{"string"}, + Format: "", + }, + }, + "subType": { + SchemaProps: spec.SchemaProps{ + Description: "Key sub type (e.g. rsa4096)", + Type: []string{"string"}, + Format: "", + }, + }, + "keyData": { + SchemaProps: spec.SchemaProps{ + Description: "Key data", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"keyID"}, + }, + }, + } +} + +func schema_pkg_apis_application_v1alpha1_GnuPGPublicKeyList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GnuPGPublicKeyList is a collection of GnuPGPublicKey objects", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.GnuPGPublicKey"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.GnuPGPublicKey", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + func schema_pkg_apis_application_v1alpha1_HealthStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -3018,6 +3123,13 @@ func schema_pkg_apis_application_v1alpha1_RevisionMetadata(ref common.ReferenceC Format: "", }, }, + "signatureInfo": { + SchemaProps: spec.SchemaProps{ + Description: "If revision was signed with GPG, and signature verification is enabled, this contains a hint on the signer", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"date"}, }, @@ -3027,6 +3139,27 @@ func schema_pkg_apis_application_v1alpha1_RevisionMetadata(ref common.ReferenceC } } +func schema_pkg_apis_application_v1alpha1_SignatureKey(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SignatureKey is the specification of a key required to verify commit signatures with", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "keyID": { + SchemaProps: spec.SchemaProps{ + Description: "The ID of the key in hexadecimal notation", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"keyID"}, + }, + }, + } +} + func schema_pkg_apis_application_v1alpha1_SyncOperation(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/application/v1alpha1/types.go b/pkg/apis/application/v1alpha1/types.go index 4fcb24c2afee1..e7e34d68c09e3 100644 --- a/pkg/apis/application/v1alpha1/types.go +++ b/pkg/apis/application/v1alpha1/types.go @@ -618,6 +618,9 @@ type RevisionMetadata struct { // probably the commit message, // this is truncated to the first newline or 64 characters (which ever comes first) Message string `json:"message,omitempty" protobuf:"bytes,4,opt,name=message"` + // If revision was signed with GPG, and signature verification is enabled, + // this contains a hint on the signer + SignatureInfo string `json:"signatureInfo,omitempty" protobuf:"bytes,5,opt,name=signatureInfo"` } // SyncOperationResult represent result of sync operation @@ -1247,6 +1250,28 @@ type RepositoryCertificateList struct { Items []RepositoryCertificate `json:"items" protobuf:"bytes,2,rep,name=items"` } +// GnuPGPublicKey is a representation of a GnuPG public key +type GnuPGPublicKey struct { + // KeyID in hexadecimal string format + KeyID string `json:"keyID" protobuf:"bytes,1,opt,name=keyID"` + // Fingerprint of the key + Fingerprint string `json:"fingerprint,omitempty" protobuf:"bytes,2,opt,name=fingerprint"` + // Owner identification + Owner string `json:"owner,omitempty" protobuf:"bytes,3,opt,name=owner"` + // Trust level + Trust string `json:"trust,omitempty" protobuf:"bytes,4,opt,name=trust"` + // Key sub type (e.g. rsa4096) + SubType string `json:"subType,omitempty" protobuf:"bytes,5,opt,name=subType"` + // Key data + KeyData string `json:"keyData,omitempty" protobuf:"bytes,6,opt,name=keyData"` +} + +// GnuPGPublicKeyList is a collection of GnuPGPublicKey objects +type GnuPGPublicKeyList struct { + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []GnuPGPublicKey `json:"items" protobuf:"bytes,2,rep,name=items"` +} + // AppProjectList is list of AppProject resources // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type AppProjectList struct { @@ -1540,6 +1565,12 @@ func (s *OrphanedResourcesMonitorSettings) IsWarn() bool { return s.Warn == nil || *s.Warn } +// SignatureKey is the specification of a key required to verify commit signatures with +type SignatureKey struct { + // The ID of the key in hexadecimal notation + KeyID string `json:"keyID" protobuf:"bytes,1,name=keyID"` +} + // AppProjectSpec is the specification of an AppProject type AppProjectSpec struct { // SourceRepos contains list of repository URLs which can be used for deployment @@ -1560,6 +1591,8 @@ type AppProjectSpec struct { SyncWindows SyncWindows `json:"syncWindows,omitempty" protobuf:"bytes,8,opt,name=syncWindows"` // NamespaceResourceWhitelist contains list of whitelisted namespace level resources NamespaceResourceWhitelist []metav1.GroupKind `json:"namespaceResourceWhitelist,omitempty" protobuf:"bytes,9,opt,name=namespaceResourceWhitelist"` + // List of PGP key IDs that commits to be synced to must be signed with + SignatureKeys []SignatureKey `json:"signatureKeys,omitempty" protobuf:"bytes,10,opt,name=signatureKeys"` } // SyncWindows is a collection of sync windows in this project diff --git a/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go index ce920a8350c41..ffe9fb627371d 100644 --- a/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go @@ -137,6 +137,11 @@ func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) { *out = make([]v1.GroupKind, len(*in)) copy(*out, *in) } + if in.SignatureKeys != nil { + in, out := &in.SignatureKeys, &out.SignatureKeys + *out = make([]SignatureKey, len(*in)) + copy(*out, *in) + } return } @@ -822,6 +827,44 @@ func (in *EnvEntry) DeepCopy() *EnvEntry { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GnuPGPublicKey) DeepCopyInto(out *GnuPGPublicKey) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GnuPGPublicKey. +func (in *GnuPGPublicKey) DeepCopy() *GnuPGPublicKey { + if in == nil { + return nil + } + out := new(GnuPGPublicKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GnuPGPublicKeyList) DeepCopyInto(out *GnuPGPublicKeyList) { + *out = *in + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GnuPGPublicKey, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GnuPGPublicKeyList. +func (in *GnuPGPublicKeyList) DeepCopy() *GnuPGPublicKeyList { + if in == nil { + return nil + } + out := new(GnuPGPublicKeyList) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HealthStatus) DeepCopyInto(out *HealthStatus) { *out = *in @@ -1648,6 +1691,22 @@ func (in *RevisionMetadata) DeepCopy() *RevisionMetadata { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SignatureKey) DeepCopyInto(out *SignatureKey) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SignatureKey. +func (in *SignatureKey) DeepCopy() *SignatureKey { + if in == nil { + return nil + } + out := new(SignatureKey) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SyncOperation) DeepCopyInto(out *SyncOperation) { *out = *in diff --git a/reposerver/apiclient/repository.pb.go b/reposerver/apiclient/repository.pb.go index 1f9d36bebdd94..39957f29dbaf7 100644 --- a/reposerver/apiclient/repository.pb.go +++ b/reposerver/apiclient/repository.pb.go @@ -34,20 +34,22 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type ManifestRequest struct { Repo *v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` // revision, potentially un-resolved - Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"` - NoCache bool `protobuf:"varint,3,opt,name=noCache,proto3" json:"noCache,omitempty"` - AppLabelKey string `protobuf:"bytes,4,opt,name=appLabelKey,proto3" json:"appLabelKey,omitempty"` - AppLabelValue string `protobuf:"bytes,5,opt,name=appLabelValue,proto3" json:"appLabelValue,omitempty"` - Namespace string `protobuf:"bytes,8,opt,name=namespace,proto3" json:"namespace,omitempty"` - ApplicationSource *v1alpha1.ApplicationSource `protobuf:"bytes,10,opt,name=applicationSource,proto3" json:"applicationSource,omitempty"` - Repos []*v1alpha1.Repository `protobuf:"bytes,11,rep,name=repos,proto3" json:"repos,omitempty"` - Plugins []*v1alpha1.ConfigManagementPlugin `protobuf:"bytes,12,rep,name=plugins,proto3" json:"plugins,omitempty"` - KustomizeOptions *v1alpha1.KustomizeOptions `protobuf:"bytes,13,opt,name=kustomizeOptions,proto3" json:"kustomizeOptions,omitempty"` - KubeVersion string `protobuf:"bytes,14,opt,name=kubeVersion,proto3" json:"kubeVersion,omitempty"` - ApiVersions []string `protobuf:"bytes,15,rep,name=apiVersions,proto3" json:"apiVersions,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"` + NoCache bool `protobuf:"varint,3,opt,name=noCache,proto3" json:"noCache,omitempty"` + AppLabelKey string `protobuf:"bytes,4,opt,name=appLabelKey,proto3" json:"appLabelKey,omitempty"` + AppLabelValue string `protobuf:"bytes,5,opt,name=appLabelValue,proto3" json:"appLabelValue,omitempty"` + Namespace string `protobuf:"bytes,8,opt,name=namespace,proto3" json:"namespace,omitempty"` + ApplicationSource *v1alpha1.ApplicationSource `protobuf:"bytes,10,opt,name=applicationSource,proto3" json:"applicationSource,omitempty"` + Repos []*v1alpha1.Repository `protobuf:"bytes,11,rep,name=repos,proto3" json:"repos,omitempty"` + Plugins []*v1alpha1.ConfigManagementPlugin `protobuf:"bytes,12,rep,name=plugins,proto3" json:"plugins,omitempty"` + KustomizeOptions *v1alpha1.KustomizeOptions `protobuf:"bytes,13,opt,name=kustomizeOptions,proto3" json:"kustomizeOptions,omitempty"` + KubeVersion string `protobuf:"bytes,14,opt,name=kubeVersion,proto3" json:"kubeVersion,omitempty"` + ApiVersions []string `protobuf:"bytes,15,rep,name=apiVersions,proto3" json:"apiVersions,omitempty"` + // Request to verify the signature when generating the manifests (only for Git repositories) + VerifySignature bool `protobuf:"varint,16,opt,name=verifySignature,proto3" json:"verifySignature,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ManifestRequest) Reset() { *m = ManifestRequest{} } @@ -167,13 +169,22 @@ func (m *ManifestRequest) GetApiVersions() []string { return nil } +func (m *ManifestRequest) GetVerifySignature() bool { + if m != nil { + return m.VerifySignature + } + return false +} + type ManifestResponse struct { Manifests []string `protobuf:"bytes,1,rep,name=manifests,proto3" json:"manifests,omitempty"` Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` Server string `protobuf:"bytes,3,opt,name=server,proto3" json:"server,omitempty"` // resolved revision - Revision string `protobuf:"bytes,4,opt,name=revision,proto3" json:"revision,omitempty"` - SourceType string `protobuf:"bytes,6,opt,name=sourceType,proto3" json:"sourceType,omitempty"` + Revision string `protobuf:"bytes,4,opt,name=revision,proto3" json:"revision,omitempty"` + SourceType string `protobuf:"bytes,6,opt,name=sourceType,proto3" json:"sourceType,omitempty"` + // Raw response of git verify-commit operation (always the empty string for Helm) + VerifyResult string `protobuf:"bytes,7,opt,name=verifyResult,proto3" json:"verifyResult,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -247,6 +258,13 @@ func (m *ManifestResponse) GetSourceType() string { return "" } +func (m *ManifestResponse) GetVerifyResult() string { + if m != nil { + return m.VerifyResult + } + return "" +} + // ListAppsRequest requests a repository directory structure type ListAppsRequest struct { Repo *v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` @@ -1095,82 +1113,84 @@ func init() { } var fileDescriptor_dd8723cfcc820480 = []byte{ - // 1194 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0x5b, 0x6f, 0x1b, 0xc5, - 0x17, 0xcf, 0xda, 0x8e, 0x13, 0x1f, 0xf7, 0xe2, 0x4c, 0xfb, 0xef, 0x7f, 0x31, 0xa9, 0x65, 0x56, - 0x80, 0x02, 0xa5, 0x6b, 0x12, 0x2a, 0x11, 0x15, 0xa9, 0x92, 0x49, 0xd2, 0x14, 0x39, 0x51, 0xd3, - 0x0d, 0x54, 0xe2, 0x22, 0x55, 0x93, 0xf5, 0x64, 0x3d, 0x78, 0xbd, 0x3b, 0xec, 0x8c, 0x8d, 0xd2, - 0x2f, 0x00, 0x12, 0x8f, 0x88, 0x17, 0x1e, 0xf9, 0x08, 0xbc, 0xf1, 0xce, 0x03, 0x8f, 0x7c, 0x04, - 0x94, 0x47, 0x3e, 0x05, 0x9a, 0xd9, 0xdb, 0x78, 0xed, 0xe4, 0xc5, 0xbd, 0xbc, 0xd8, 0x33, 0x67, - 0xce, 0x6d, 0xce, 0xf9, 0x9d, 0x33, 0x67, 0xe1, 0xdd, 0x88, 0xb0, 0x90, 0x93, 0x68, 0x42, 0xa2, - 0x8e, 0x5a, 0x52, 0x11, 0x46, 0x67, 0xda, 0xd2, 0x66, 0x51, 0x28, 0x42, 0x04, 0x39, 0xa5, 0x79, - 0xd3, 0x0b, 0xbd, 0x50, 0x91, 0x3b, 0x72, 0x15, 0x73, 0x34, 0xd7, 0xbd, 0x30, 0xf4, 0x7c, 0xd2, - 0xc1, 0x8c, 0x76, 0x70, 0x10, 0x84, 0x02, 0x0b, 0x1a, 0x06, 0x3c, 0x39, 0xb5, 0x86, 0xdb, 0xdc, - 0xa6, 0xa1, 0x3a, 0x75, 0xc3, 0x88, 0x74, 0x26, 0x9b, 0x1d, 0x8f, 0x04, 0x24, 0xc2, 0x82, 0xf4, - 0x13, 0x9e, 0xcf, 0x3c, 0x2a, 0x06, 0xe3, 0x13, 0xdb, 0x0d, 0x47, 0x1d, 0x1c, 0x29, 0x13, 0xdf, - 0xaa, 0xc5, 0x5d, 0xb7, 0xdf, 0x61, 0x43, 0x4f, 0x0a, 0xf3, 0x0e, 0x66, 0xcc, 0xa7, 0xae, 0x52, - 0xde, 0x99, 0x6c, 0x62, 0x9f, 0x0d, 0xf0, 0x8c, 0x2a, 0xeb, 0xa7, 0x2a, 0x5c, 0x3f, 0xc4, 0x01, - 0x3d, 0x25, 0x5c, 0x38, 0xe4, 0xbb, 0x31, 0xe1, 0x02, 0x7d, 0x09, 0x15, 0x79, 0x09, 0xd3, 0x68, - 0x1b, 0x1b, 0xf5, 0xad, 0x3d, 0x3b, 0xb7, 0x66, 0xa7, 0xd6, 0xd4, 0xe2, 0x99, 0xdb, 0xb7, 0xd9, - 0xd0, 0xb3, 0xa5, 0x35, 0x5b, 0xb3, 0x66, 0xa7, 0xd6, 0x6c, 0x27, 0x8b, 0x85, 0xa3, 0x54, 0xa2, - 0x26, 0xac, 0x46, 0x64, 0x42, 0x39, 0x0d, 0x03, 0xb3, 0xd4, 0x36, 0x36, 0x6a, 0x4e, 0xb6, 0x47, - 0x26, 0xac, 0x04, 0xe1, 0x0e, 0x76, 0x07, 0xc4, 0x2c, 0xb7, 0x8d, 0x8d, 0x55, 0x27, 0xdd, 0xa2, - 0x36, 0xd4, 0x31, 0x63, 0x07, 0xf8, 0x84, 0xf8, 0x3d, 0x72, 0x66, 0x56, 0x94, 0xa0, 0x4e, 0x42, - 0x6f, 0xc3, 0xd5, 0x74, 0xfb, 0x14, 0xfb, 0x63, 0x62, 0x2e, 0x2b, 0x9e, 0x69, 0x22, 0x5a, 0x87, - 0x5a, 0x80, 0x47, 0x84, 0x33, 0xec, 0x12, 0x73, 0x55, 0x71, 0xe4, 0x04, 0xf4, 0x1c, 0xd6, 0xb4, - 0x4b, 0x1c, 0x87, 0xe3, 0xc8, 0x25, 0x26, 0xa8, 0x18, 0x1c, 0x2c, 0x10, 0x83, 0x6e, 0x51, 0xa7, - 0x33, 0x6b, 0x06, 0x7d, 0x0d, 0xcb, 0x0a, 0x37, 0x66, 0xbd, 0x5d, 0x7e, 0x71, 0x31, 0x8f, 0x75, - 0xa2, 0x21, 0xac, 0x30, 0x7f, 0xec, 0xd1, 0x80, 0x9b, 0x57, 0x94, 0xfa, 0x27, 0x0b, 0xa8, 0xdf, - 0x09, 0x83, 0x53, 0xea, 0x1d, 0xe2, 0x00, 0x7b, 0x64, 0x44, 0x02, 0x71, 0xa4, 0x34, 0x3b, 0xa9, - 0x05, 0xf4, 0x3d, 0x34, 0x86, 0x63, 0x2e, 0xc2, 0x11, 0x7d, 0x4e, 0x1e, 0x33, 0x85, 0x6c, 0xf3, - 0xaa, 0x0a, 0x62, 0x6f, 0x01, 0xab, 0xbd, 0x82, 0x4a, 0x67, 0xc6, 0x88, 0x04, 0xc9, 0x70, 0x7c, - 0x42, 0x9e, 0x92, 0x48, 0xa1, 0xeb, 0x5a, 0x0c, 0x12, 0x8d, 0x14, 0xc3, 0x88, 0x26, 0x3b, 0x6e, - 0x5e, 0x6f, 0x97, 0x63, 0x18, 0x65, 0x24, 0xeb, 0x37, 0x03, 0x1a, 0x79, 0x35, 0x70, 0x16, 0x06, - 0x5c, 0xa1, 0x66, 0x94, 0xd0, 0xb8, 0x69, 0x28, 0xa1, 0x9c, 0x30, 0x8d, 0xa9, 0x52, 0x11, 0x53, - 0xb7, 0xa0, 0x1a, 0xf7, 0x0c, 0x05, 0xe9, 0x9a, 0x93, 0xec, 0xa6, 0xea, 0xa0, 0x52, 0xa8, 0x83, - 0x16, 0x00, 0x57, 0xa8, 0xf8, 0xfc, 0x8c, 0x11, 0xb3, 0xaa, 0x4e, 0x35, 0x8a, 0xf5, 0xa3, 0x01, - 0xd7, 0x0f, 0x28, 0x17, 0x5d, 0xc6, 0xf8, 0xeb, 0x2d, 0x59, 0x6b, 0x0c, 0x2b, 0x5d, 0xc6, 0xa4, - 0x33, 0x68, 0x13, 0x2a, 0x98, 0xb1, 0x38, 0x40, 0xf5, 0xad, 0xdb, 0xb6, 0xd6, 0x18, 0x13, 0x16, - 0xf9, 0xcf, 0xf7, 0x02, 0x21, 0x35, 0x4b, 0xd6, 0xe6, 0xc7, 0x50, 0xcb, 0x48, 0xa8, 0x01, 0xe5, - 0x21, 0x39, 0x53, 0x17, 0xa8, 0x39, 0x72, 0x89, 0x6e, 0xc2, 0xf2, 0x44, 0xd5, 0x72, 0x6c, 0x35, - 0xde, 0xdc, 0x2f, 0x6d, 0x1b, 0xd6, 0xef, 0x65, 0x78, 0x43, 0xfa, 0x79, 0xac, 0x82, 0xd9, 0x65, - 0x6c, 0x97, 0x08, 0x4c, 0x7d, 0xfe, 0x64, 0x4c, 0xa2, 0xb3, 0x97, 0x19, 0x8b, 0x3e, 0x54, 0xe3, - 0x44, 0x28, 0x9f, 0x5e, 0x74, 0x5f, 0x48, 0x74, 0xe7, 0xcd, 0xa0, 0xfc, 0x12, 0x9a, 0xc1, 0xbc, - 0xfa, 0xac, 0xbc, 0x82, 0xfa, 0xb4, 0x7e, 0x28, 0xc1, 0x2d, 0xe9, 0x4e, 0x9e, 0xae, 0xac, 0xc2, - 0x10, 0x54, 0x84, 0xc4, 0x7a, 0x9c, 0x7c, 0xb5, 0x46, 0xf7, 0x60, 0x65, 0xc8, 0xc3, 0x20, 0x20, - 0x22, 0x89, 0x75, 0x53, 0x87, 0x54, 0x2f, 0x3e, 0xea, 0x32, 0x76, 0xcc, 0x88, 0xeb, 0xa4, 0xac, - 0xe8, 0x0e, 0x54, 0x06, 0xc4, 0x1f, 0xa9, 0x6a, 0xab, 0x6f, 0xfd, 0x5f, 0x17, 0x79, 0x44, 0xfc, - 0x51, 0xca, 0xaf, 0x98, 0xd0, 0x7d, 0xa8, 0x65, 0x5e, 0x26, 0x31, 0x58, 0x9f, 0x32, 0x92, 0x1e, - 0xa6, 0x62, 0x39, 0xbb, 0x94, 0xed, 0xd3, 0x88, 0xb8, 0x92, 0x51, 0x3d, 0x36, 0x05, 0xd9, 0xdd, - 0xf4, 0x30, 0x93, 0xcd, 0xd8, 0xad, 0x5f, 0x0d, 0x78, 0x2b, 0x87, 0xaf, 0x93, 0x14, 0xd3, 0x21, - 0x11, 0xb8, 0x8f, 0x05, 0x7e, 0xcd, 0x25, 0xfd, 0x67, 0x09, 0xae, 0x4d, 0x47, 0x57, 0xa6, 0x47, - 0x76, 0xb4, 0x34, 0x3d, 0x72, 0x8d, 0x8e, 0xe0, 0x0a, 0x09, 0x26, 0x34, 0x0a, 0x03, 0xf9, 0x08, - 0xa4, 0x50, 0xfd, 0xe0, 0xe2, 0x1c, 0xd9, 0x7b, 0x1a, 0x7b, 0xdc, 0x05, 0xa6, 0x34, 0xa0, 0x21, - 0x00, 0xc3, 0x11, 0x1e, 0x11, 0x41, 0x22, 0x09, 0xc9, 0xf2, 0xa2, 0x90, 0x8c, 0xcd, 0x1f, 0xa5, - 0x3a, 0x1d, 0x4d, 0x7d, 0xf3, 0x19, 0xac, 0xcd, 0xf8, 0x33, 0xa7, 0x05, 0xdd, 0xd3, 0x5b, 0x50, - 0x7d, 0xab, 0x35, 0xe7, 0x7a, 0x9a, 0x1a, 0xbd, 0x45, 0xfd, 0x51, 0x82, 0xba, 0x86, 0xb8, 0xb9, - 0x31, 0x6c, 0x01, 0x28, 0x81, 0x87, 0xd4, 0x27, 0x71, 0x04, 0x6b, 0x8e, 0x46, 0x41, 0x83, 0x39, - 0x11, 0x79, 0xb4, 0x40, 0x44, 0xa4, 0x3f, 0x73, 0xc3, 0x21, 0x9f, 0x29, 0x65, 0x97, 0x27, 0x73, - 0x53, 0xb2, 0x43, 0x02, 0xae, 0x9d, 0x52, 0x9f, 0x1c, 0xe5, 0x5e, 0x54, 0x95, 0x17, 0x07, 0x0b, - 0x7a, 0xf1, 0x50, 0x57, 0xea, 0x14, 0x6c, 0x58, 0xef, 0x43, 0xa3, 0x58, 0x7a, 0xd2, 0x43, 0x3a, - 0xc2, 0x5e, 0x16, 0xa7, 0x64, 0x67, 0xfd, 0x62, 0x00, 0x9a, 0xcd, 0xc4, 0x45, 0xe1, 0x1e, 0x6e, - 0xf3, 0x74, 0x3e, 0x88, 0x71, 0xaf, 0x51, 0x50, 0x0f, 0xea, 0x7d, 0xc2, 0x05, 0x0d, 0x94, 0xc3, - 0x49, 0x43, 0x78, 0xef, 0xf2, 0x94, 0xef, 0xe6, 0x02, 0x8e, 0x2e, 0x6d, 0x7d, 0x01, 0xb7, 0x2f, - 0xe5, 0xd6, 0x26, 0x03, 0x63, 0x6a, 0x32, 0xb8, 0x74, 0x9e, 0xb0, 0x10, 0x34, 0x8a, 0x9d, 0xc5, - 0x0a, 0x60, 0x4d, 0xc6, 0x74, 0x67, 0x80, 0x23, 0xf1, 0x0a, 0x06, 0x02, 0xeb, 0x13, 0xa8, 0x65, - 0xf6, 0xe6, 0x06, 0xba, 0x09, 0xab, 0x93, 0x74, 0xc8, 0x2a, 0xa9, 0x6c, 0x65, 0x7b, 0xab, 0x0b, - 0x48, 0x77, 0x36, 0x79, 0x00, 0xee, 0xc0, 0x32, 0x15, 0x64, 0x94, 0x4e, 0x0f, 0xff, 0x2b, 0xf6, - 0x6d, 0xc5, 0xee, 0xc4, 0x3c, 0x5b, 0xff, 0x96, 0x61, 0x2d, 0x6f, 0x9f, 0xf2, 0x97, 0xba, 0x04, - 0x3d, 0x86, 0xc6, 0x7e, 0xf2, 0x6d, 0x93, 0x4e, 0x70, 0xe8, 0x4d, 0x5d, 0x4f, 0xe1, 0x2b, 0xa7, - 0xb9, 0x3e, 0xff, 0x30, 0xf6, 0xc8, 0x5a, 0x42, 0x0f, 0x60, 0x35, 0x9d, 0xb2, 0xa6, 0x15, 0x15, - 0x66, 0xaf, 0xe6, 0x8d, 0x39, 0xb3, 0x8e, 0xb5, 0x84, 0xbe, 0x81, 0xab, 0xfb, 0xaa, 0xfb, 0x25, - 0xaf, 0x1d, 0x7a, 0x47, 0xe7, 0xbb, 0x70, 0x7c, 0x69, 0x5a, 0x45, 0xb6, 0xd9, 0x07, 0xd3, 0x5a, - 0x42, 0x3f, 0x1b, 0x70, 0x63, 0x9f, 0x88, 0xe2, 0xe3, 0x81, 0xee, 0xce, 0x37, 0x72, 0xc1, 0x23, - 0xd3, 0xec, 0x2d, 0x04, 0x8c, 0x69, 0x9d, 0xd6, 0x12, 0x3a, 0x52, 0x77, 0xce, 0x13, 0x8c, 0x6e, - 0xcf, 0xcd, 0x64, 0x16, 0xba, 0xd6, 0x45, 0xc7, 0xe9, 0x3d, 0x3f, 0x7d, 0xf0, 0xd7, 0x79, 0xcb, - 0xf8, 0xfb, 0xbc, 0x65, 0xfc, 0x73, 0xde, 0x32, 0xbe, 0xfa, 0xf0, 0xb2, 0x0f, 0x5f, 0xed, 0x03, - 0x1d, 0x33, 0xea, 0xfa, 0x94, 0x04, 0xe2, 0xa4, 0xaa, 0x3e, 0x73, 0x3f, 0xfa, 0x2f, 0x00, 0x00, - 0xff, 0xff, 0x61, 0xf3, 0xc7, 0x8e, 0xbf, 0x0f, 0x00, 0x00, + // 1230 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0xdd, 0x6e, 0x1b, 0xc5, + 0x17, 0xcf, 0xda, 0x8e, 0x13, 0x1f, 0xb7, 0x8d, 0x33, 0xed, 0xbf, 0xff, 0xc5, 0xa4, 0x96, 0x59, + 0x01, 0x0a, 0x94, 0xae, 0x69, 0xa8, 0x44, 0x55, 0xa4, 0x4a, 0xa6, 0x49, 0x53, 0xe4, 0x44, 0x4d, + 0x37, 0x50, 0x89, 0x0f, 0xa9, 0x9a, 0xac, 0x27, 0xeb, 0xc1, 0xeb, 0xdd, 0x61, 0x67, 0x6c, 0xe4, + 0xbe, 0x00, 0xdc, 0x23, 0x6e, 0x78, 0x0c, 0x24, 0x2e, 0xb8, 0x47, 0x88, 0x4b, 0x1e, 0x01, 0xe5, + 0x92, 0xa7, 0x40, 0x33, 0xfb, 0x35, 0x5e, 0x3b, 0xb9, 0x71, 0x3f, 0x6e, 0xec, 0x99, 0x33, 0x67, + 0xce, 0x39, 0xf3, 0x9b, 0xdf, 0x39, 0x73, 0x16, 0xde, 0x8d, 0x08, 0x0b, 0x39, 0x89, 0x26, 0x24, + 0xea, 0xa8, 0x21, 0x15, 0x61, 0x34, 0xd5, 0x86, 0x36, 0x8b, 0x42, 0x11, 0x22, 0xc8, 0x25, 0xcd, + 0x6b, 0x5e, 0xe8, 0x85, 0x4a, 0xdc, 0x91, 0xa3, 0x58, 0xa3, 0xb9, 0xe5, 0x85, 0xa1, 0xe7, 0x93, + 0x0e, 0x66, 0xb4, 0x83, 0x83, 0x20, 0x14, 0x58, 0xd0, 0x30, 0xe0, 0xc9, 0xaa, 0x35, 0xbc, 0xcb, + 0x6d, 0x1a, 0xaa, 0x55, 0x37, 0x8c, 0x48, 0x67, 0x72, 0xbb, 0xe3, 0x91, 0x80, 0x44, 0x58, 0x90, + 0x7e, 0xa2, 0xf3, 0x99, 0x47, 0xc5, 0x60, 0x7c, 0x62, 0xbb, 0xe1, 0xa8, 0x83, 0x23, 0xe5, 0xe2, + 0x5b, 0x35, 0xb8, 0xe5, 0xf6, 0x3b, 0x6c, 0xe8, 0xc9, 0xcd, 0xbc, 0x83, 0x19, 0xf3, 0xa9, 0xab, + 0x8c, 0x77, 0x26, 0xb7, 0xb1, 0xcf, 0x06, 0x78, 0xce, 0x94, 0xf5, 0x5b, 0x15, 0x36, 0x0e, 0x71, + 0x40, 0x4f, 0x09, 0x17, 0x0e, 0xf9, 0x6e, 0x4c, 0xb8, 0x40, 0x5f, 0x42, 0x45, 0x1e, 0xc2, 0x34, + 0xda, 0xc6, 0x76, 0x7d, 0x67, 0xcf, 0xce, 0xbd, 0xd9, 0xa9, 0x37, 0x35, 0x78, 0xe6, 0xf6, 0x6d, + 0x36, 0xf4, 0x6c, 0xe9, 0xcd, 0xd6, 0xbc, 0xd9, 0xa9, 0x37, 0xdb, 0xc9, 0xb0, 0x70, 0x94, 0x49, + 0xd4, 0x84, 0xf5, 0x88, 0x4c, 0x28, 0xa7, 0x61, 0x60, 0x96, 0xda, 0xc6, 0x76, 0xcd, 0xc9, 0xe6, + 0xc8, 0x84, 0xb5, 0x20, 0x7c, 0x80, 0xdd, 0x01, 0x31, 0xcb, 0x6d, 0x63, 0x7b, 0xdd, 0x49, 0xa7, + 0xa8, 0x0d, 0x75, 0xcc, 0xd8, 0x01, 0x3e, 0x21, 0x7e, 0x8f, 0x4c, 0xcd, 0x8a, 0xda, 0xa8, 0x8b, + 0xd0, 0xdb, 0x70, 0x39, 0x9d, 0x3e, 0xc5, 0xfe, 0x98, 0x98, 0xab, 0x4a, 0x67, 0x56, 0x88, 0xb6, + 0xa0, 0x16, 0xe0, 0x11, 0xe1, 0x0c, 0xbb, 0xc4, 0x5c, 0x57, 0x1a, 0xb9, 0x00, 0x3d, 0x87, 0x4d, + 0xed, 0x10, 0xc7, 0xe1, 0x38, 0x72, 0x89, 0x09, 0x0a, 0x83, 0x83, 0x25, 0x30, 0xe8, 0x16, 0x6d, + 0x3a, 0xf3, 0x6e, 0xd0, 0xd7, 0xb0, 0xaa, 0x78, 0x63, 0xd6, 0xdb, 0xe5, 0x17, 0x87, 0x79, 0x6c, + 0x13, 0x0d, 0x61, 0x8d, 0xf9, 0x63, 0x8f, 0x06, 0xdc, 0xbc, 0xa4, 0xcc, 0x3f, 0x59, 0xc2, 0xfc, + 0x83, 0x30, 0x38, 0xa5, 0xde, 0x21, 0x0e, 0xb0, 0x47, 0x46, 0x24, 0x10, 0x47, 0xca, 0xb2, 0x93, + 0x7a, 0x40, 0xdf, 0x43, 0x63, 0x38, 0xe6, 0x22, 0x1c, 0xd1, 0xe7, 0xe4, 0x31, 0x53, 0xcc, 0x36, + 0x2f, 0x2b, 0x10, 0x7b, 0x4b, 0x78, 0xed, 0x15, 0x4c, 0x3a, 0x73, 0x4e, 0x24, 0x49, 0x86, 0xe3, + 0x13, 0xf2, 0x94, 0x44, 0x8a, 0x5d, 0x57, 0x62, 0x92, 0x68, 0xa2, 0x98, 0x46, 0x34, 0x99, 0x71, + 0x73, 0xa3, 0x5d, 0x8e, 0x69, 0x94, 0x89, 0xd0, 0x36, 0x6c, 0x4c, 0x48, 0x44, 0x4f, 0xa7, 0xc7, + 0xd4, 0x0b, 0xb0, 0x18, 0x47, 0xc4, 0x6c, 0x28, 0x2a, 0x16, 0xc5, 0xd6, 0x9f, 0x06, 0x34, 0xf2, + 0xbc, 0xe1, 0x2c, 0x0c, 0xb8, 0xe2, 0xd7, 0x28, 0x91, 0x71, 0xd3, 0x50, 0xe6, 0x73, 0xc1, 0x2c, + 0xfb, 0x4a, 0x45, 0xf6, 0x5d, 0x87, 0x6a, 0x5c, 0x5d, 0x14, 0xf9, 0x6b, 0x4e, 0x32, 0x9b, 0xc9, + 0x98, 0x4a, 0x21, 0x63, 0x5a, 0x00, 0x5c, 0xf1, 0xe7, 0xf3, 0x29, 0x23, 0x66, 0x55, 0xad, 0x6a, + 0x12, 0x64, 0xc1, 0xa5, 0x38, 0x6e, 0x87, 0xf0, 0xb1, 0x2f, 0xcc, 0x35, 0xa5, 0x31, 0x23, 0xb3, + 0x7e, 0x34, 0x60, 0xe3, 0x80, 0x72, 0xd1, 0x65, 0x8c, 0xbf, 0xde, 0x02, 0x60, 0x8d, 0x61, 0xad, + 0xcb, 0x98, 0x0c, 0x06, 0xdd, 0x86, 0x0a, 0x66, 0x2c, 0x06, 0xb1, 0xbe, 0x73, 0xc3, 0xd6, 0xca, + 0x6c, 0xa2, 0x22, 0xff, 0xf9, 0x5e, 0x20, 0xa4, 0x65, 0xa9, 0xda, 0xfc, 0x18, 0x6a, 0x99, 0x08, + 0x35, 0xa0, 0x3c, 0x24, 0x53, 0x75, 0x80, 0x9a, 0x23, 0x87, 0xe8, 0x1a, 0xac, 0x4e, 0x54, 0x65, + 0x88, 0xbd, 0xc6, 0x93, 0x7b, 0xa5, 0xbb, 0x86, 0xf5, 0x6b, 0x19, 0xde, 0x90, 0x71, 0x1e, 0x2b, + 0xc0, 0xbb, 0x8c, 0xed, 0x12, 0x81, 0xa9, 0xcf, 0x9f, 0x8c, 0x49, 0x34, 0x7d, 0x99, 0x58, 0xf4, + 0xa1, 0x1a, 0x5f, 0x96, 0x8a, 0xe9, 0x45, 0x57, 0x99, 0xc4, 0x76, 0x5e, 0x5a, 0xca, 0x2f, 0xa1, + 0xb4, 0x2c, 0xca, 0xf6, 0xca, 0x2b, 0xc8, 0x76, 0xeb, 0x87, 0x12, 0x5c, 0x97, 0xe1, 0xe4, 0xd7, + 0x95, 0x65, 0x21, 0x82, 0x8a, 0x90, 0xf9, 0x10, 0x5f, 0xbe, 0x1a, 0xa3, 0x3b, 0xb0, 0x36, 0xe4, + 0x61, 0x10, 0x10, 0x91, 0x60, 0xdd, 0xd4, 0x29, 0xd5, 0x8b, 0x97, 0xba, 0x8c, 0x1d, 0x33, 0xe2, + 0x3a, 0xa9, 0x2a, 0xba, 0x09, 0x95, 0x01, 0xf1, 0x47, 0x2a, 0x23, 0xeb, 0x3b, 0xff, 0xd7, 0xb7, + 0x3c, 0x22, 0xfe, 0x28, 0xd5, 0x57, 0x4a, 0xe8, 0x1e, 0xd4, 0xb2, 0x28, 0x13, 0x0c, 0xb6, 0x66, + 0x9c, 0xa4, 0x8b, 0xe9, 0xb6, 0x5c, 0x5d, 0xee, 0xed, 0xd3, 0x88, 0xb8, 0x52, 0x51, 0x3d, 0x5d, + 0x85, 0xbd, 0xbb, 0xe9, 0x62, 0xb6, 0x37, 0x53, 0xb7, 0x7e, 0x31, 0xe0, 0xad, 0x9c, 0xbe, 0x4e, + 0x92, 0x4c, 0x87, 0x44, 0xe0, 0x3e, 0x16, 0xf8, 0x35, 0xa7, 0xf4, 0x1f, 0x25, 0xb8, 0x32, 0x8b, + 0xae, 0xbc, 0x1e, 0x59, 0xf5, 0xd2, 0xeb, 0x91, 0x63, 0x74, 0x04, 0x97, 0x48, 0x30, 0xa1, 0x51, + 0x18, 0xc8, 0x27, 0x25, 0xa5, 0xea, 0x07, 0xe7, 0xdf, 0x91, 0xbd, 0xa7, 0xa9, 0xc7, 0x55, 0x60, + 0xc6, 0x02, 0x1a, 0x02, 0x30, 0x1c, 0xe1, 0x11, 0x11, 0x24, 0x92, 0x94, 0x2c, 0x2f, 0x4b, 0xc9, + 0xd8, 0xfd, 0x51, 0x6a, 0xd3, 0xd1, 0xcc, 0x37, 0x9f, 0xc1, 0xe6, 0x5c, 0x3c, 0x0b, 0x4a, 0xd0, + 0x1d, 0xbd, 0x04, 0xd5, 0x77, 0x5a, 0x0b, 0x8e, 0xa7, 0x99, 0xd1, 0x4b, 0xd4, 0xef, 0x25, 0xa8, + 0x6b, 0x8c, 0x5b, 0x88, 0x61, 0x0b, 0x40, 0x6d, 0x78, 0x48, 0x7d, 0x12, 0x23, 0x58, 0x73, 0x34, + 0x09, 0x1a, 0x2c, 0x40, 0xe4, 0xd1, 0x12, 0x88, 0xc8, 0x78, 0x16, 0xc2, 0x21, 0x9f, 0x32, 0xe5, + 0x97, 0x27, 0x5d, 0x58, 0x32, 0x43, 0x02, 0xae, 0x9c, 0x52, 0x9f, 0x1c, 0xe5, 0x51, 0x54, 0x55, + 0x14, 0x07, 0x4b, 0x46, 0xf1, 0x50, 0x37, 0xea, 0x14, 0x7c, 0x58, 0xef, 0x43, 0xa3, 0x98, 0x7a, + 0x32, 0x42, 0x3a, 0xc2, 0x5e, 0x86, 0x53, 0x32, 0xb3, 0x7e, 0x36, 0x00, 0xcd, 0xdf, 0xc4, 0x79, + 0x70, 0x0f, 0xef, 0xf2, 0xb4, 0xdb, 0x88, 0x79, 0xaf, 0x49, 0x50, 0x0f, 0xea, 0x7d, 0xc2, 0x05, + 0x0d, 0x54, 0xc0, 0x49, 0x41, 0x78, 0xef, 0xe2, 0x2b, 0xdf, 0xcd, 0x37, 0x38, 0xfa, 0x6e, 0xeb, + 0x0b, 0xb8, 0x71, 0xa1, 0xb6, 0xd6, 0x3d, 0x18, 0x33, 0xdd, 0xc3, 0x85, 0x3d, 0x87, 0x85, 0xa0, + 0x51, 0xac, 0x2c, 0x56, 0x00, 0x9b, 0x12, 0xd3, 0x07, 0x03, 0x1c, 0x89, 0x57, 0xd0, 0x10, 0x58, + 0x9f, 0x40, 0x2d, 0xf3, 0xb7, 0x10, 0xe8, 0x26, 0xac, 0x4f, 0xd2, 0x96, 0xad, 0xa4, 0x6e, 0x2b, + 0x9b, 0x5b, 0x5d, 0x40, 0x7a, 0xb0, 0xc9, 0x03, 0x70, 0x13, 0x56, 0xa9, 0x20, 0xa3, 0xb4, 0x7b, + 0xf8, 0x5f, 0xb1, 0x6e, 0x2b, 0x75, 0x27, 0xd6, 0xd9, 0xf9, 0xb7, 0x0c, 0x9b, 0x79, 0xf9, 0x94, + 0xbf, 0xd4, 0x25, 0xe8, 0x31, 0x34, 0xf6, 0x93, 0x2f, 0xa5, 0xb4, 0xcb, 0x43, 0x6f, 0xea, 0x76, + 0x0a, 0xdf, 0x4c, 0xcd, 0xad, 0xc5, 0x8b, 0x71, 0x44, 0xd6, 0x0a, 0xba, 0x0f, 0xeb, 0x69, 0x97, + 0x35, 0x6b, 0xa8, 0xd0, 0x7b, 0x35, 0xaf, 0x2e, 0xe8, 0x75, 0xac, 0x15, 0xf4, 0x0d, 0x5c, 0xde, + 0x57, 0xd5, 0x2f, 0x79, 0xed, 0xd0, 0x3b, 0xba, 0xde, 0xb9, 0xed, 0x4b, 0xd3, 0x2a, 0xaa, 0xcd, + 0x3f, 0x98, 0xd6, 0x0a, 0xfa, 0xc9, 0x80, 0xab, 0xfb, 0x44, 0x14, 0x1f, 0x0f, 0x74, 0x6b, 0xb1, + 0x93, 0x73, 0x1e, 0x99, 0x66, 0x6f, 0x29, 0x62, 0xcc, 0xda, 0xb4, 0x56, 0xd0, 0x91, 0x3a, 0x73, + 0x7e, 0xc1, 0xe8, 0xc6, 0xc2, 0x9b, 0xcc, 0xa0, 0x6b, 0x9d, 0xb7, 0x9c, 0x9e, 0xf3, 0xd3, 0xfb, + 0x7f, 0x9d, 0xb5, 0x8c, 0xbf, 0xcf, 0x5a, 0xc6, 0x3f, 0x67, 0x2d, 0xe3, 0xab, 0x0f, 0x2f, 0xfa, + 0x8c, 0xd6, 0x3e, 0xf7, 0x31, 0xa3, 0xae, 0x4f, 0x49, 0x20, 0x4e, 0xaa, 0xea, 0xa3, 0xf9, 0xa3, + 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x2d, 0x37, 0xe2, 0x11, 0x0d, 0x10, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1431,6 +1451,18 @@ func (m *ManifestRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.VerifySignature { + i-- + if m.VerifySignature { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x80 + } if len(m.ApiVersions) > 0 { for iNdEx := len(m.ApiVersions) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.ApiVersions[iNdEx]) @@ -1576,6 +1608,13 @@ func (m *ManifestResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.VerifyResult) > 0 { + i -= len(m.VerifyResult) + copy(dAtA[i:], m.VerifyResult) + i = encodeVarintRepository(dAtA, i, uint64(len(m.VerifyResult))) + i-- + dAtA[i] = 0x3a + } if len(m.SourceType) > 0 { i -= len(m.SourceType) copy(dAtA[i:], m.SourceType) @@ -2415,6 +2454,9 @@ func (m *ManifestRequest) Size() (n int) { n += 1 + l + sovRepository(uint64(l)) } } + if m.VerifySignature { + n += 3 + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -2449,6 +2491,10 @@ func (m *ManifestResponse) Size() (n int) { if l > 0 { n += 1 + l + sovRepository(uint64(l)) } + l = len(m.VerifyResult) + if l > 0 { + n += 1 + l + sovRepository(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -3203,6 +3249,26 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error { } m.ApiVersions = append(m.ApiVersions, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 16: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VerifySignature", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRepository + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.VerifySignature = bool(v != 0) default: iNdEx = preIndex skippy, err := skipRepository(dAtA[iNdEx:]) @@ -3417,6 +3483,38 @@ func (m *ManifestResponse) Unmarshal(dAtA []byte) error { } m.SourceType = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VerifyResult", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRepository + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRepository + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRepository + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VerifyResult = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRepository(dAtA[iNdEx:]) diff --git a/reposerver/gpgwatcher.go b/reposerver/gpgwatcher.go new file mode 100644 index 0000000000000..a59b1ec778204 --- /dev/null +++ b/reposerver/gpgwatcher.go @@ -0,0 +1,71 @@ +package reposerver + +import ( + "fmt" + "path" + "time" + + "github.com/fsnotify/fsnotify" + log "github.com/sirupsen/logrus" + + "github.com/argoproj/argo-cd/util/gpg" +) + +// StartGPGWatcher watches a given directory for creation and deletion of files and syncs the GPG keyring +func StartGPGWatcher(sourcePath string) error { + log.Infof("Starting GPG sync watcher on directory '%s'", sourcePath) + forceSync := false + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Remove == fsnotify.Remove { + // In case our watched path is re-created (i.e. during e2e tests), we need to watch again + if event.Name == sourcePath && event.Op&fsnotify.Remove == fsnotify.Remove { + log.Warnf("Re-creating watcher on %s", sourcePath) + time.Sleep(1 * time.Second) + err = watcher.Add(sourcePath) + if err != nil { + log.Errorf("Error re-creating watcher on %s: %v", sourcePath, err) + return + } + // Force sync because we probably missed an event + forceSync = true + } + if gpg.IsShortKeyID(path.Base(event.Name)) || forceSync { + log.Infof("Updating GPG keyring on filesystem event") + added, removed, err := gpg.SyncKeyRingFromDirectory(sourcePath) + if err != nil { + log.Errorf("Could not sync keyring: %s", err.Error()) + } else { + log.Infof("Result of sync operation: keys added: %d, keys removed: %d", len(added), len(removed)) + } + forceSync = false + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Errorf("%v", err) + } + } + }() + + err = watcher.Add(sourcePath) + if err != nil { + return err + } + <-done + return fmt.Errorf("Abnormal termination of GPG watcher, refusing to continue.") +} diff --git a/reposerver/metrics/gitwrapper.go b/reposerver/metrics/gitwrapper.go index be6c0f757b4e2..edfd83b40dca9 100644 --- a/reposerver/metrics/gitwrapper.go +++ b/reposerver/metrics/gitwrapper.go @@ -53,3 +53,7 @@ func (w *gitClientWrapper) Init() error { func (w *gitClientWrapper) RevisionMetadata(revision string) (*git.RevisionMetadata, error) { return w.client.RevisionMetadata(revision) } + +func (w *gitClientWrapper) VerifyCommitSignature(revision string) (string, error) { + return w.client.VerifyCommitSignature(revision) +} diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index 7c12cb52eeab7..5400d5639473d 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -37,6 +37,7 @@ import ( "github.com/argoproj/argo-cd/util/app/discovery" argopath "github.com/argoproj/argo-cd/util/app/path" "github.com/argoproj/argo-cd/util/git" + "github.com/argoproj/argo-cd/util/gpg" "github.com/argoproj/argo-cd/util/helm" "github.com/argoproj/argo-cd/util/ksonnet" argokube "github.com/argoproj/argo-cd/util/kube" @@ -118,13 +119,15 @@ func (s *Service) runRepoOperation( revision string, repo *v1alpha1.Repository, source *v1alpha1.ApplicationSource, + verifyCommit bool, getCached func(revision string) bool, - operation func(appPath, repoRoot, revision string) error, + operation func(appPath, repoRoot, revision, verifyResult string) error, settings operationSettings) error { var gitClient git.Client var helmClient helm.Client var err error + var signature string revision = textutils.FirstNonEmpty(revision, source.TargetRevision) if source.IsHelm() { helmClient, revision, err = s.newHelmClientResolveRevision(repo, revision, source.Chart) @@ -169,7 +172,7 @@ func (s *Service) runRepoOperation( return err } defer io.Close(closer) - return operation(chartPath, chartPath, revision) + return operation(chartPath, chartPath, revision, "") } else { s.repoLock.Lock(gitClient.Root()) defer s.repoLock.Unlock(gitClient.Root()) @@ -181,11 +184,17 @@ func (s *Service) runRepoOperation( if err != nil { return err } + if verifyCommit { + signature, err = gitClient.VerifyCommitSignature(revision) + if err != nil { + return err + } + } appPath, err := argopath.Path(gitClient.Root(), source.Path) if err != nil { return err } - return operation(appPath, gitClient.Root(), revision) + return operation(appPath, gitClient.Root(), revision, signature) } } @@ -205,13 +214,14 @@ func (s *Service) GenerateManifest(ctx context.Context, q *apiclient.ManifestReq } return false } - err := s.runRepoOperation(ctx, q.Revision, q.Repo, q.ApplicationSource, getCached, func(appPath, repoRoot, revision string) error { + err := s.runRepoOperation(ctx, q.Revision, q.Repo, q.ApplicationSource, q.VerifySignature, getCached, func(appPath, repoRoot, revision, verifyResult string) error { var err error res, err = GenerateManifests(appPath, repoRoot, revision, q, false) if err != nil { return err } res.Revision = revision + res.VerifyResult = verifyResult err = s.cache.SetManifests(revision, q.ApplicationSource, q.Namespace, q.AppLabelKey, q.AppLabelValue, &res) if err != nil { log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), revision, err) @@ -672,7 +682,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD return false } - err := s.runRepoOperation(ctx, q.Source.TargetRevision, q.Repo, q.Source, getCached, func(appPath, repoRoot, revision string) error { + err := s.runRepoOperation(ctx, q.Source.TargetRevision, q.Repo, q.Source, false, getCached, func(appPath, repoRoot, revision, verifyResult string) error { appSourceType, err := GetAppSourceType(q.Source, appPath) if err != nil { return err @@ -810,9 +820,31 @@ func (s *Service) GetRevisionMetadata(ctx context.Context, q *apiclient.RepoServ if err != nil { return nil, err } + + // Run gpg verify-commit on the revision + signatureInfo := "" + if gpg.IsGPGEnabled() { + cs, err := gitClient.VerifyCommitSignature(q.Revision) + if err != nil { + log.Debugf("Could not verify commit signature: %v", err) + return nil, err + } + + if cs != "" { + vr, err := gpg.ParseGitCommitVerification(cs) + if err != nil { + log.Debugf("Could not parse commit verification: %v", err) + return nil, err + } + signatureInfo = fmt.Sprintf("%s signature from %s key %s", vr.Result, vr.Cipher, gpg.KeyID(vr.KeyID)) + } else { + signatureInfo = "Revision is not signed." + } + } + // discard anything after the first new line and then truncate to 64 chars message := text.Trunc(strings.SplitN(m.Message, "\n", 2)[0], 64) - metadata = &v1alpha1.RevisionMetadata{Author: m.Author, Date: metav1.Time{Time: m.Date}, Tags: m.Tags, Message: message} + metadata = &v1alpha1.RevisionMetadata{Author: m.Author, Date: metav1.Time{Time: m.Date}, Tags: m.Tags, Message: message, SignatureInfo: signatureInfo} _ = s.cache.SetRevisionMetadata(q.Repo.Repo, q.Revision, metadata) return metadata, nil } diff --git a/reposerver/repository/repository.proto b/reposerver/repository/repository.proto index 906622210291c..503bbdaa045f8 100644 --- a/reposerver/repository/repository.proto +++ b/reposerver/repository/repository.proto @@ -23,6 +23,8 @@ message ManifestRequest { github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.KustomizeOptions kustomizeOptions = 13; string kubeVersion = 14; repeated string apiVersions = 15; + // Request to verify the signature when generating the manifests (only for Git repositories) + bool verifySignature = 16; } message ManifestResponse { @@ -32,6 +34,8 @@ message ManifestResponse { // resolved revision string revision = 4; string sourceType = 6; + // Raw response of git verify-commit operation (always the empty string for Helm) + string verifyResult = 7; } // ListAppsRequest requests a repository directory structure diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go index 65c9fdb15aebc..d391b0e0aabd7 100644 --- a/reposerver/repository/repository_test.go +++ b/reposerver/repository/repository_test.go @@ -29,7 +29,12 @@ import ( helmmocks "github.com/argoproj/argo-cd/util/helm/mocks" ) -func newServiceWithMocks(root string) (*Service, *gitmocks.Client) { +const testSignature = `gpg: Signature made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key 4AEE18F83AFDEB23 +gpg: Good signature from "GitHub (web-flow commit signing) " [ultimate] +` + +func newServiceWithMocks(root string, signed bool) (*Service, *gitmocks.Client) { service := NewService(metrics.NewMetricsServer(), cache.NewCache( cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Minute)), 1*time.Minute, @@ -46,6 +51,11 @@ func newServiceWithMocks(root string) (*Service, *gitmocks.Client) { gitClient.On("LsRemote", mock.Anything).Return(mock.Anything, nil) gitClient.On("CommitSHA").Return(mock.Anything, nil) gitClient.On("Root").Return(root) + if signed { + gitClient.On("VerifyCommitSignature", mock.Anything).Return(testSignature, nil) + } else { + gitClient.On("VerifyCommitSignature", mock.Anything).Return("", nil) + } chart := "my-chart" version := semver.MustParse("1.1.0") @@ -65,7 +75,12 @@ func newServiceWithMocks(root string) (*Service, *gitmocks.Client) { } func newService(root string) *Service { - service, _ := newServiceWithMocks(root) + service, _ := newServiceWithMocks(root, false) + return service +} + +func newServiceWithSignature(root string) *Service { + service, _ := newServiceWithMocks(root, true) return service } @@ -76,7 +91,7 @@ func TestGenerateYamlManifestInDir(t *testing.T) { q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src} // update this value if we add/remove manifests - const countOfManifests = 25 + const countOfManifests = 26 res1, err := service.GenerateManifest(context.Background(), &q) @@ -107,7 +122,7 @@ func TestHelmManifestFromChartRepo(t *testing.T) { } func TestGenerateManifestsUseExactRevision(t *testing.T) { - service, gitClient := newServiceWithMocks(".") + service, gitClient := newServiceWithMocks(".", false) src := argoappv1.ApplicationSource{Path: "./testdata/recurse", Directory: &argoappv1.ApplicationSourceDirectory{Recurse: true}} @@ -568,7 +583,7 @@ func TestGetHelmCharts(t *testing.T) { } func TestGetRevisionMetadata(t *testing.T) { - service, gitClient := newServiceWithMocks("../..") + service, gitClient := newServiceWithMocks("../..", false) now := time.Now() gitClient.On("RevisionMetadata", mock.Anything).Return(&git.RevisionMetadata{ @@ -591,6 +606,53 @@ func TestGetRevisionMetadata(t *testing.T) { } +func TestGetSignatureVerificationResult(t *testing.T) { + // Commit with signature and verification requested + { + service := newServiceWithSignature("../..") + + src := argoappv1.ApplicationSource{Path: "manifests/base"} + q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src, VerifySignature: true} + + res, err := service.GenerateManifest(context.Background(), &q) + assert.NoError(t, err) + assert.Equal(t, testSignature, res.VerifyResult) + } + // Commit with signature and verification not requested + { + service := newServiceWithSignature("../..") + + src := argoappv1.ApplicationSource{Path: "manifests/base"} + q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src} + + res, err := service.GenerateManifest(context.Background(), &q) + assert.NoError(t, err) + assert.Empty(t, res.VerifyResult) + } + // Commit without signature and verification requested + { + service := newService("../..") + + src := argoappv1.ApplicationSource{Path: "manifests/base"} + q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src, VerifySignature: true} + + res, err := service.GenerateManifest(context.Background(), &q) + assert.NoError(t, err) + assert.Empty(t, res.VerifyResult) + } + // Commit without signature and verification not requested + { + service := newService("../..") + + src := argoappv1.ApplicationSource{Path: "manifests/base"} + q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src, VerifySignature: true} + + res, err := service.GenerateManifest(context.Background(), &q) + assert.NoError(t, err) + assert.Empty(t, res.VerifyResult) + } +} + func Test_newEnv(t *testing.T) { assert.Equal(t, &argoappv1.Env{ &argoappv1.EnvEntry{Name: "ARGOCD_APP_NAME", Value: "my-app-name"}, diff --git a/server/application/application.go b/server/application/application.go index 09d52ec8acce7..cdcf7bd11d8bc 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -1070,6 +1070,12 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR if a.Spec.SyncPolicy != nil { syncOptions = a.Spec.SyncPolicy.SyncOptions } + + // We cannot use local manifests if we're only allowed to sync to signed commits + if syncReq.Manifests != nil && len(proj.Spec.SignatureKeys) > 0 { + return nil, status.Errorf(codes.FailedPrecondition, "Cannot use local sync when signature keys are required.") + } + op := appv1.Operation{ Sync: &appv1.SyncOperation{ Revision: revision, diff --git a/server/gpgkey/gpgkey.go b/server/gpgkey/gpgkey.go new file mode 100644 index 0000000000000..c9b7822181ded --- /dev/null +++ b/server/gpgkey/gpgkey.go @@ -0,0 +1,120 @@ +package gpgkey + +import ( + "fmt" + "strings" + + "golang.org/x/net/context" + + gpgkeypkg "github.com/argoproj/argo-cd/pkg/apiclient/gpgkey" + appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/reposerver/apiclient" + "github.com/argoproj/argo-cd/server/rbacpolicy" + "github.com/argoproj/argo-cd/util/db" + "github.com/argoproj/argo-cd/util/gpg" + "github.com/argoproj/argo-cd/util/rbac" +) + +// Server provides a service of type GPGKeyService +type Server struct { + db db.ArgoDB + repoClientset apiclient.Clientset + enf *rbac.Enforcer +} + +// NewServer returns a new instance of the service with type GPGKeyService +func NewServer( + repoClientset apiclient.Clientset, + db db.ArgoDB, + enf *rbac.Enforcer, +) *Server { + return &Server{ + db: db, + repoClientset: repoClientset, + enf: enf, + } +} + +// ListGnuPGPublicKeys returns a list of GnuPG public keys in the configuration +func (s *Server) List(ctx context.Context, q *gpgkeypkg.GnuPGPublicKeyQuery) (*appsv1.GnuPGPublicKeyList, error) { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceGPGKeys, rbacpolicy.ActionGet, ""); err != nil { + return nil, err + } + keys, err := s.db.ListConfiguredGPGPublicKeys(ctx) + if err != nil { + return nil, err + } + keyList := &appsv1.GnuPGPublicKeyList{} + for _, v := range keys { + // Remove key's data from list result to save some bytes + v.KeyData = "" + keyList.Items = append(keyList.Items, *v) + } + return keyList, nil +} + +// GetGnuPGPublicKey retrieves a single GPG public key from the configuration +func (s *Server) Get(ctx context.Context, q *gpgkeypkg.GnuPGPublicKeyQuery) (*appsv1.GnuPGPublicKey, error) { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceGPGKeys, rbacpolicy.ActionGet, ""); err != nil { + return nil, err + } + + keyID := gpg.KeyID(q.KeyID) + if keyID == "" { + return nil, fmt.Errorf("KeyID is malformed or empty") + } + + keys, err := s.db.ListConfiguredGPGPublicKeys(ctx) + if err != nil { + return nil, err + } + + if key, ok := keys[keyID]; ok { + return key, nil + } + + return nil, fmt.Errorf("No such key: %s", keyID) +} + +// CreateGnuPGPublicKey adds one or more GPG public keys to the server's configuration +func (s *Server) Create(ctx context.Context, q *gpgkeypkg.GnuPGPublicKeyCreateRequest) (*gpgkeypkg.GnuPGPublicKeyCreateResponse, error) { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceGPGKeys, rbacpolicy.ActionCreate, ""); err != nil { + return nil, err + } + + keyData := strings.TrimSpace(q.Publickey.KeyData) + if keyData == "" { + return nil, fmt.Errorf("Submitted key data is empty") + } + + added, skipped, err := s.db.AddGPGPublicKey(ctx, q.Publickey.KeyData) + if err != nil { + return nil, err + } + + items := make([]appsv1.GnuPGPublicKey, 0) + for _, k := range added { + items = append(items, *k) + } + + response := &gpgkeypkg.GnuPGPublicKeyCreateResponse{ + Created: &appsv1.GnuPGPublicKeyList{Items: items}, + Skipped: skipped, + } + + return response, nil +} + +// DeleteGnuPGPublicKey removes a single GPG public key from the server's configuration +func (s *Server) Delete(ctx context.Context, q *gpgkeypkg.GnuPGPublicKeyQuery) (*gpgkeypkg.GnuPGPublicKeyResponse, error) { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceGPGKeys, rbacpolicy.ActionDelete, ""); err != nil { + return nil, err + } + + err := s.db.DeleteGPGPublicKey(ctx, q.KeyID) + if err != nil { + return nil, err + } + + return &gpgkeypkg.GnuPGPublicKeyResponse{}, nil +} diff --git a/server/gpgkey/gpgkey.proto b/server/gpgkey/gpgkey.proto new file mode 100644 index 0000000000000..d219e719c4ad3 --- /dev/null +++ b/server/gpgkey/gpgkey.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; +option go_package = "github.com/argoproj/argo-cd/pkg/apiclient/gpgkey"; + +// GPG public key service +// +// GPG public key API performs CRUD actions against GnuPGPublicKey resources +package gpgkey; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1/generated.proto"; + +// Message to query the server for configured GPG public keys +message GnuPGPublicKeyQuery { + // The GPG key ID to query for + string keyID = 1; +} + +// Request to create one or more public keys on the server +message GnuPGPublicKeyCreateRequest { + // Raw key data of the GPG key(s) to create + github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.GnuPGPublicKey publickey = 1; + // Whether to upsert already existing public keys + bool upsert = 2; +} + +// Response to a public key creation request +message GnuPGPublicKeyCreateResponse { + // List of GPG public keys that have been created + github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.GnuPGPublicKeyList created = 1; + // List of key IDs that haven been skipped because they already exist on the server + repeated string skipped = 2; +} + +// Generic (empty) response for GPG public key CRUD requests +message GnuPGPublicKeyResponse {} + +// GPGKeyService implements API for managing GPG public keys on the server +service GPGKeyService { + // List all available repository certificates + rpc List(GnuPGPublicKeyQuery) returns (github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.GnuPGPublicKeyList) { + option (google.api.http).get = "/api/v1/gpgkeys"; + } + + // Get information about specified GPG public key from the server + rpc Get(GnuPGPublicKeyQuery) returns (github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.GnuPGPublicKey) { + option (google.api.http).get = "/api/v1/gpgkeys/{keyID}"; + } + + // Create one or more GPG public keys in the server's configuration + rpc Create(GnuPGPublicKeyCreateRequest) returns (GnuPGPublicKeyCreateResponse) { + option (google.api.http) = { + post: "/api/v1/gpgkeys" + body: "publickey" + }; + } + + // Delete specified GPG public key from the server's configuration + rpc Delete(GnuPGPublicKeyQuery) returns (GnuPGPublicKeyResponse) { + option (google.api.http).delete = "/api/v1/gpgkeys"; + } +} \ No newline at end of file diff --git a/server/rbacpolicy/rbacpolicy.go b/server/rbacpolicy/rbacpolicy.go index dc0ae3cf56722..9fff5fec9871c 100644 --- a/server/rbacpolicy/rbacpolicy.go +++ b/server/rbacpolicy/rbacpolicy.go @@ -20,6 +20,7 @@ const ( ResourceRepositories = "repositories" ResourceCertificates = "certificates" ResourceAccounts = "accounts" + ResourceGPGKeys = "gpgkeys" // please add new items to Actions ActionGet = "get" diff --git a/server/server.go b/server/server.go index ad24b51c8c002..e346adfa1ab5b 100644 --- a/server/server.go +++ b/server/server.go @@ -57,6 +57,7 @@ import ( applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application" certificatepkg "github.com/argoproj/argo-cd/pkg/apiclient/certificate" clusterpkg "github.com/argoproj/argo-cd/pkg/apiclient/cluster" + gpgkeypkg "github.com/argoproj/argo-cd/pkg/apiclient/gpgkey" projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project" repocredspkg "github.com/argoproj/argo-cd/pkg/apiclient/repocreds" repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository" @@ -74,6 +75,7 @@ import ( servercache "github.com/argoproj/argo-cd/server/cache" "github.com/argoproj/argo-cd/server/certificate" "github.com/argoproj/argo-cd/server/cluster" + "github.com/argoproj/argo-cd/server/gpgkey" "github.com/argoproj/argo-cd/server/metrics" "github.com/argoproj/argo-cd/server/project" "github.com/argoproj/argo-cd/server/rbacpolicy" @@ -463,6 +465,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server { "/cluster.ClusterService/Update": true, "/session.SessionService/Create": true, "/account.AccountService/UpdatePassword": true, + "/gpgkey.GPGKeyService/CreateGnuPGPublicKey": true, "/repository.RepositoryService/Create": true, "/repository.RepositoryService/Update": true, "/repository.RepositoryService/CreateRepository": true, @@ -515,6 +518,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server { settingsService := settings.NewServer(a.settingsMgr, a, a.DisableAuth) accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf) certificateService := certificate.NewServer(a.RepoClientset, db, a.enf) + gpgkeyService := gpgkey.NewServer(a.RepoClientset, db, a.enf) versionpkg.RegisterVersionServiceServer(grpcS, &version.Server{}) clusterpkg.RegisterClusterServiceServer(grpcS, clusterService) applicationpkg.RegisterApplicationServiceServer(grpcS, applicationService) @@ -525,6 +529,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server { projectpkg.RegisterProjectServiceServer(grpcS, projectService) accountpkg.RegisterAccountServiceServer(grpcS, accountService) certificatepkg.RegisterCertificateServiceServer(grpcS, certificateService) + gpgkeypkg.RegisterGPGKeyServiceServer(grpcS, gpgkeyService) // Register reflection service on gRPC server. reflection.Register(grpcS) grpc_prometheus.Register(grpcS) @@ -627,6 +632,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl mustRegisterGWHandler(projectpkg.RegisterProjectServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) mustRegisterGWHandler(accountpkg.RegisterAccountServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) mustRegisterGWHandler(certificatepkg.RegisterCertificateServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) + mustRegisterGWHandler(gpgkeypkg.RegisterGPGKeyServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) // Swagger UI swagger.ServeSwaggerUI(mux, assets.SwaggerJSON, "/swagger-ui", a.RootPath) diff --git a/test/container/Procfile b/test/container/Procfile index 9109532f82cfb..b0a327acb54d6 100644 --- a/test/container/Procfile +++ b/test/container/Procfile @@ -6,4 +6,4 @@ repo-server: su --pty -m default -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=t ui: su --pty -m default -c "test \"$ARGOCD_IN_CI\" = \"true\" && exit 0; cd ui && ARGOCD_E2E_YARN_HOST=0.0.0.0 ${ARGOCD_E2E_YARN_CMD:-yarn} start" sshd: test "$ARGOCD_E2E_TEST" = "true" && /usr/sbin/sshd -p 2222 -D -e fcgiwrap: test "$ARGOCD_E2E_TEST" = "true" && (fcgiwrap -s unix:/var/run/fcgiwrap.socket & sleep 1 && chmod 777 /var/run/fcgiwrap.socket && wait) -nginx: test "$ARGOCD_E2E_TEST" = "true" && nginx -g 'daemon off;' -c $(pwd)/test/fixture/testrepos/nginx.conf \ No newline at end of file +nginx: test "$ARGOCD_E2E_TEST" = "true" && nginx -g 'daemon off;' -c $(pwd)/test/fixture/testrepos/nginx.conf diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go index 87282cd2f1b44..b6155063b5fd0 100644 --- a/test/e2e/app_management_test.go +++ b/test/e2e/app_management_test.go @@ -45,6 +45,52 @@ const ( guestbookPathLocal = "./testdata/guestbook_local" ) +func TestSyncToUnsignedCommit(t *testing.T) { + Given(t). + Project("gpg"). + Path(guestbookPath). + When(). + IgnoreErrors(). + Create(). + Sync(). + Then(). + Expect(OperationPhaseIs(OperationError)). + Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). + Expect(HealthIs(health.HealthStatusMissing)) +} + +func TestSyncToSignedCommitWithoutKnownKey(t *testing.T) { + Given(t). + Project("gpg"). + Path(guestbookPath). + When(). + AddSignedFile("test.yaml", "null"). + IgnoreErrors(). + Create(). + Sync(). + Then(). + Expect(OperationPhaseIs(OperationError)). + Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). + Expect(HealthIs(health.HealthStatusMissing)) +} + +func TestSyncToSignedCommitKeyWithKnownKey(t *testing.T) { + Given(t). + Project("gpg"). + Path(guestbookPath). + GPGPublicKeyAdded(). + Sleep(2). + When(). + AddSignedFile("test.yaml", "null"). + IgnoreErrors(). + Create(). + Sync(). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)) +} + func TestAppCreation(t *testing.T) { ctx := Given(t) diff --git a/test/e2e/fixture/app/actions.go b/test/e2e/fixture/app/actions.go index 092d3f7be5792..7d64cb850fdd1 100644 --- a/test/e2e/fixture/app/actions.go +++ b/test/e2e/fixture/app/actions.go @@ -52,6 +52,12 @@ func (a *Actions) AddFile(fileName, fileContents string) *Actions { return a } +func (a *Actions) AddSignedFile(fileName, fileContents string) *Actions { + a.context.t.Helper() + fixture.AddSignedFile(a.context.path+"/"+fileName, fileContents) + return a +} + func (a *Actions) CreateFromFile(handler func(app *Application)) *Actions { a.context.t.Helper() app := &Application{ diff --git a/test/e2e/fixture/app/context.go b/test/e2e/fixture/app/context.go index 019f42671a8fe..44d0dbd1d3225 100644 --- a/test/e2e/fixture/app/context.go +++ b/test/e2e/fixture/app/context.go @@ -8,6 +8,7 @@ import ( "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/test/e2e/fixture" "github.com/argoproj/argo-cd/test/e2e/fixture/certs" + "github.com/argoproj/argo-cd/test/e2e/fixture/gpgkeys" "github.com/argoproj/argo-cd/test/e2e/fixture/repos" "github.com/argoproj/argo-cd/util/settings" ) @@ -42,6 +43,16 @@ func Given(t *testing.T) *Context { return &Context{t: t, destServer: KubernetesInternalAPIServerAddr, repoURLType: fixture.RepoURLTypeFile, name: fixture.Name(), timeout: 10, project: "default", prune: true} } +func (c *Context) GPGPublicKeyAdded() *Context { + gpgkeys.AddGPGPublicKey() + return c +} + +func (c *Context) GPGPublicKeyRemoved() *Context { + gpgkeys.DeleteGPGPublicKey() + return c +} + func (c *Context) CustomCACertAdded() *Context { certs.AddCustomCACert() return c @@ -217,6 +228,11 @@ func (c *Context) When() *Actions { return &Actions{context: c} } +func (c *Context) Sleep(seconds time.Duration) *Context { + time.Sleep(seconds * time.Second) + return c +} + func (c *Context) Prune(prune bool) *Context { c.prune = prune return c diff --git a/test/e2e/fixture/fixture.go b/test/e2e/fixture/fixture.go index 74fbdd5eaa160..96d9e11a34739 100644 --- a/test/e2e/fixture/fixture.go +++ b/test/e2e/fixture/fixture.go @@ -76,6 +76,7 @@ const ( RepoURLTypeHelm = "helm" GitUsername = "admin" GitPassword = "password" + GpgGoodKeyID = "D56C4FCA57A46444" ) // getKubeConfig creates new kubernetes client config using specified config path and config overrides variables @@ -319,6 +320,16 @@ func EnsureCleanState(t *testing.T) { ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}}, }) + // Create seperate project for testing gpg signature verification + FailOnErr(RunCli("proj", "create", "gpg")) + SetProjectSpec("gpg", v1alpha1.AppProjectSpec{ + OrphanedResources: nil, + SourceRepos: []string{"*"}, + Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}}, + ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}}, + SignatureKeys: []v1alpha1.SignatureKey{{KeyID: GpgGoodKeyID}}, + }) + // remove tmp dir CheckError(os.RemoveAll(TmpDir)) @@ -335,6 +346,21 @@ func EnsureCleanState(t *testing.T) { FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/tls")) FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/ssh")) + // For signing during the tests + FailOnErr(Run("", "mkdir", "-p", TmpDir+"/gpg")) + FailOnErr(Run("", "chmod", "0700", TmpDir+"/gpg")) + prevGnuPGHome := os.Getenv("GNUPGHOME") + os.Setenv("GNUPGHOME", TmpDir+"/gpg") + // nolint:errcheck + Run("", "pkill", "-9", "gpg-agent") + FailOnErr(Run("", "gpg", "--import", "../fixture/gpg/signingkey.asc")) + os.Setenv("GNUPGHOME", prevGnuPGHome) + + // recreate GPG directories + FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/source")) + FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/keys")) + FailOnErr(Run("", "chmod", "0700", TmpDir+"/app/config/gpg/keys")) + // set-up tmp repo, must have unique name FailOnErr(Run("", "cp", "-Rf", "testdata", repoDirectory())) FailOnErr(Run(repoDirectory(), "chmod", "777", ".")) @@ -418,6 +444,18 @@ func AddFile(path, contents string) { FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "add file")) } +func AddSignedFile(path, contents string) { + log.WithFields(log.Fields{"path": path}).Info("adding") + + CheckError(ioutil.WriteFile(filepath.Join(repoDirectory(), path), []byte(contents), 0644)) + prevGnuPGHome := os.Getenv("GNUPGHOME") + os.Setenv("GNUPGHOME", TmpDir+"/gpg") + FailOnErr(Run(repoDirectory(), "git", "diff")) + FailOnErr(Run(repoDirectory(), "git", "add", ".")) + FailOnErr(Run(repoDirectory(), "git", "-c", fmt.Sprintf("user.signingkey=%s", GpgGoodKeyID), "commit", "-S", "-am", "add file")) + os.Setenv("GNUPGHOME", prevGnuPGHome) +} + // create the resource by creating using "kubectl apply", with bonus templating func Declarative(filename string, values interface{}) (string, error) { diff --git a/test/e2e/fixture/gpgkeys/gpgkeys.go b/test/e2e/fixture/gpgkeys/gpgkeys.go new file mode 100644 index 0000000000000..0d30e81311311 --- /dev/null +++ b/test/e2e/fixture/gpgkeys/gpgkeys.go @@ -0,0 +1,32 @@ +package gpgkeys + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/argoproj/gitops-engine/pkg/utils/errors" + + "github.com/argoproj/argo-cd/test/e2e/fixture" +) + +// Add GPG public key via API and create appropriate file where the ConfigMap mount would de it as well +func AddGPGPublicKey() { + keyPath, err := filepath.Abs(fmt.Sprintf("../fixture/gpg/%s", fixture.GpgGoodKeyID)) + errors.CheckError(err) + args := []string{"gpg", "add", "--from", keyPath} + errors.FailOnErr(fixture.RunCli(args...)) + + keyData, err := ioutil.ReadFile(keyPath) + errors.CheckError(err) + err = ioutil.WriteFile(fmt.Sprintf("%s/app/config/gpg/source/%s", fixture.TmpDir, fixture.GpgGoodKeyID), keyData, 0644) + errors.CheckError(err) +} + +func DeleteGPGPublicKey() { + args := []string{"gpg", "rm", fixture.GpgGoodKeyID} + errors.FailOnErr(fixture.RunCli(args...)) + + errors.CheckError(os.Remove(fmt.Sprintf("%s/app/config/gpg/source/%s", fixture.TmpDir, fixture.GpgGoodKeyID))) +} diff --git a/test/fixture/gpg/D56C4FCA57A46444 b/test/fixture/gpg/D56C4FCA57A46444 new file mode 100644 index 0000000000000..0bec3bcdc28b8 --- /dev/null +++ b/test/fixture/gpg/D56C4FCA57A46444 @@ -0,0 +1,31 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBF5vlTkBCADFX1JfbbIYZ+dv5QGRAZfiid8jAkcwJSRn0xwd0M3Dld2vDpKV +UAfzul2sHFSpmwqkiXbXpl9QierO8fBBfmT5dZf63JdSzmiH5e8ysmSaC8iOAGBc +nSX/35++8FxM1FtRXiK5/T6kfCQVRZhMKLnjIMREbvse+OMLouGq3JscywIdKvLO +QDM/Ni1aH33grdQ+UbOFpT6Z1Jm0bGxrrzncwBWMLPePrYyVyletYTbXe9wbPx7e +BnxsqVaeb3I6w4exWVjyGJKFMQWpcLOLZ0/7Osbs6Qk+BhI7IYgeVzG/0BeaKsZX +vCTioKcpjAWEw7JRQWWmbl8dqtJr3tXUnt2NABEBAAG0PkFyZ29DRCBFMkUgVGVz +dCBTaWduaW5nIEtleSAoRG8gbm90IHVzZSkgPG5vcmVwbHlAZXhhbXBsZS5jb20+ +iQFOBBMBCAA4FiEE6kWbSVlcvj/R+6MD1WxPylekZEQFAl5vlTkCGwMFCwkIBwIG +FQoJCAsCBBYCAwECHgECF4AACgkQ1WxPylekZESZkAgAm8UZCNO1pKYRPT97agML +vX9G8RJ9oBUG2rUZWcMJ09prpaHf1CpkipxBUDAG7ljjpPgl1Y4gmopEEUESvEgf +g6PlNJfM83WE7gw548qOUTxlbCuLnKyLcUyr+YMo5DNgJG++VDG47MPP3fubO/ZL +zSyACeek+bMYrYEy1e3O/bQ61AJIGJGEjxXhmNSLd8koLPFMrdupckshJ1T8tUGs +P14qHUo4UCjytPCca8BN1KOiRcrykENte0S++Y3KT6Z+qMrrh0gb6JYU76oD8AU2 +tz9S/lb9Yeg/mpefnPB7bEVKslvZdSPrBWGu6vD16MQ3KQRgvmkVp+L22PGZ1N46 +WrkBDQReb5U5AQgAukdIaK1cDe1Q2QXS3e2NLiCNtQgEvJGru2o7Nz58NkWv2vMW +a7Q+JLdFltvciFHaq3HRaw3Xr4ejhKYFYuaHbKBtVu/CbQSP/e6nm2zyfqU6wHgS +nGnFg9toQAcOEE0Sdz5J4plSL4osMJ6LVo3DHqf3wkYX2ajK+cAvKRTj/O9oF1PV +nBUEYOu5GOP0dczpNz6TP5QBMjwU6ORcxxkqX/cY8io/sdaC77PR1Xrmul92NJ4B +kM7fFBd3QCSjhxADYnWGgWogQ6B865V8lpNX4GI6tXVRLjF/XjuapoSiPmax+Al4 +Wt2W5m/K4+/Fk0nO0SZawcpUrGI82yTp3CmGXwARAQABiQE2BBgBCAAgFiEE6kWb +SVlcvj/R+6MD1WxPylekZEQFAl5vlTkCGwwACgkQ1WxPylekZETt9gf+JNcc003S +BEyfr/WYz65ktu5kLoGPtjUF1dBF/6MCFS5SCp/rEaK0y8R09I8wYb90sRVB80lM +vES/Ec0KD0beE1vjVAbURIrGC9fXK7lFo+KoBHmdDSKdkP8t289CrZ9g2n4orr2M +aLVobOh8Q8eXR6xyguDR8OcgSbUaHuY8ZsEyeS9IH+p97GojD/mwnZletu4wleDT +2DdUIsiV6L1d40mxRGdCpNAzPzibFn1Nh6dr5yzH0ihaTQ4He4MqcZZgYJXODZFV +5shN+mHofvXnJ5Wt4sKzRH+A6vg/jMUxXoSU3Gu0a/RGUn8Cz6cX9boe6M1Fi+xd +LZtzetfNy78xfg== +=TpiH +-----END PGP PUBLIC KEY BLOCK----- diff --git a/test/fixture/gpg/signingkey.asc b/test/fixture/gpg/signingkey.asc new file mode 100644 index 0000000000000000000000000000000000000000..91bb704db47998ac02f0312f99d73a54727c0bb9 GIT binary patch literal 2560 zcmajgXE+-U0>|-$#2&Rbv0AHE6-8qu${9teimenSVpnU`YHX?{_AWI-&6Zk~qC#Se zS*y0j>>owl|DNaWxqEl-e{a8^Uj}6s$R3q(2S@|xbuf28HgLObpw@t|ftZ!}MNx`- z^5SA{?s680`8|&@C%U88 z_7odja_GOM032=e#jh3%JNIpkCTz^?MOrVRWuamm@GK*V)>X(?nD@Eh=_-B6a@Bk; z{~(2c)FDypmZtJegizQLb^>bF82b*IJ(1ny<>gs-XTF;wN#-OKo0NgK!a6CFLgx82 z1;0~;dR0CwK>4ZR(Dl}j4!P4~6~L7~jfrme&(->Nw`ieMtm?vXf>sJ$BV0LBeOB#a zD@7HON#Gb~g00dBKn@v02Xubwbpu`*!n8peMW!UcAtANlmLU=9{4Vs>*f9HM*_B z*MLf#8b$(yAo!}WOOqX)W^Q6r2Y@GSfbiz+sYy*NW{K!?QTCL zYk!fxPgJGIoyhbeR5?n? zu>5EKu;o^pKLI|x#2|M*fQH$Txic;NY>rHNnbY`=hxDu?Zog&|@Yk>y?6z^-*v!Gy zvJ^-#cJ+*e_^KV7+PH@cRU1UfDId1E{vh%+1hr=xqTL z-Wn%4)9J;xFn3*(XV%pCQ9Ad?J)sKCOCTv4W0Y4MXQ&nHvJhEl=^&nR1FXzPIO)<0 ziilbEZC6svW4*+!#2o5o++LP$AdN5@4T1pQN9u=7rcVbm__5wFgJxcr{)%Djl!2yLn`(;^0xEeK;8`Xtl znxNW#ZYW(C1g;2&Sh)HJKpuFwd3$)fK@44=LnL4*h&L(#66o(L3DNLI`MLUdJ=by# zc0&4ixyn99A))a=6A;_qU8`~mgSO$h2G+Knnxp5Xl#^cf@m6InFfhfx!l&S)1k=;f zP*YHG(9zP+Q-C-rD1j7$Knkvx06N-h{~2ee(ExILd1%I)%gR~qYKAlrTd_fiYx; zJFEBBPL6L|>!ovuKGQw;aV#ofZn8c5%ZC=eg>C84_e((r%QUHF9Gk6(@fZ zFPqdRDD=5s$E0#BXH6tN8<3DWFv8AN`WXt}u()W}uFv9if{J~FXU@&Xnax)Fj(ReZ>d>Vz$Z@BYd-XVQXiEk9M)NIDQG_(k)7+AmRKqJ8l`jKtH`Gi-J;N)jL^(CR!h!m&4FgJ3K90|Ipr}W&?oh$w84ml z%ykrgb+7l00de#?;tSrQ##v)dY-?WfmBYWSyr4-*X$gQS_ohYG6&vNpqV7axkq73t zs>JL}T^PRV?46_~edhHJt~KY}!j*r~v3yWHX8ahk_6>sEuk-y@8N_vIOM1ec5pv)pU`BNv+LP;wvx7^rS>0Q?IOMjor z3)El3Dr=o5Z}J&>6pU6sydU>U93Ua$^TR*0c=#@Ezsu6~)W5w4CvIE7a7?FK0K3vE z)7T*c6AV2cGc<8}#JN3YyHvIMgpu@i+Ey$hY^q&XooZUz%McpSk*h0tUc&KbJXy5X z50K}d>FI=uREZR=VK7ubD6R_lq0#|-ZwUh6a?{3;@)#n~`-HZ;)h;44IFc=tC@6kW zDPVY!(&mR{3kpJysG^~E#aSpW@5oF3@vKMV;a@iD(1at8`~iQ*5pe%2Vn9WAhfF4a zU~utHnRL3!LZ22T2PXew+(s&jO$Hg&viz8nXYy(AVE&3TUmWifCGZ&} z$LB_-8NzO~z0gO)OLR^g_%Vxpgo95$+2EP(A(47>+eicA%PJ~hde3f!*?r-_Vx5Vh zwc?^9x}4!bt8^kQ#V*3Ue39BfK+~1-DsaehTQrFouOHy#;S0gJP4ZhP&T8Ll;{E9E zEWP)nC^ch^M%_xfI^j&h(mcORtHxTp5hz9Da7Y(gZ12m`BP4G^8qw(WT33kfL$jjA z=?JUA?bl9)hb2EUTfU2qw)OBk4Rd6vY2r)ybEATvCQ<0O^A|IE>eyf$52{4ES&1AE zR0eVXZ5`zQSjX_crv3bj`i~fif82NsWK>x9D{HvcrG@Mw8=2a28$3Azzbd8RkTs_( z++itc8vGh?oGS0s6;RKiePm$N4(nne&@tAa9!ic7scmt`a;`byH7=# z&pza5+qaPAGnU4uRV+llWo&lXts2zB+;qxDZfUCKa)ueBD?B%3`o|;?#!S>9(Neyd zj*b~b#@CkX-;94dZHE8axRHTf>1l*Tyxcz3OzKszk9j-q+2nbwXC6T@T*39LO>mO{ SPu#V;nd|?8L>TN+2>&O{m.message} )} +
+
GPG signature
+
{m.signatureInfo || '-'}
+
)} diff --git a/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx b/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx index dd71dba7a2664..ade766bfd5d80 100644 --- a/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx +++ b/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx @@ -23,6 +23,8 @@ export const RevisionMetadataPanel = (props: {appName: string; type: string; rev
)} + {m.signatureInfo} +
{m.message} } @@ -31,7 +33,7 @@ export const RevisionMetadataPanel = (props: {appName: string; type: string; rev
{m.author && ( - Authored by {m.author} + Authored by {m.author} - {m.signatureInfo}
)} diff --git a/ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.scss b/ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.scss new file mode 100644 index 0000000000000..f283a2b65f9d9 --- /dev/null +++ b/ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.scss @@ -0,0 +1,44 @@ +@import 'node_modules/argo-ui/src/styles/config'; + +.gpgkeys-list { + &__top-panel { + padding: 1em; + + & > .columns:first-child { + font-size: 8em; + text-align: center; + } + + & > .columns:last-child { + text-align: center; + border-left: 2px solid $argo-color-gray-4; + + & > p { + margin-bottom: 0; + margin-top: 24px; + &:first-of-type { + font-size: 1.25em; + } + } + + & > button { + width: 15em; + } + } + } + + .argo-table-list { + .argo-dropdown { + float: right; + } + } + + textarea.argo-field { + height: 25em; + width: 1024em; + white-space: pre; + overflow-wrap: normal; + overflow-x: scroll; + } +} + diff --git a/ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.tsx b/ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.tsx new file mode 100644 index 0000000000000..cc648d0a9ff1d --- /dev/null +++ b/ui/src/app/settings/components/gpgkeys-list/gpgkeys-list.tsx @@ -0,0 +1,196 @@ +import {DropDownMenu, FormField, NotificationType, SlidingPanel} from 'argo-ui'; +import * as PropTypes from 'prop-types'; +import * as React from 'react'; +import {Form, FormApi, TextArea} from 'react-form'; +import {RouteComponentProps} from 'react-router'; + +import {DataLoader, EmptyState, ErrorNotification, Page} from '../../../shared/components'; +import {AppContext} from '../../../shared/context'; +import * as models from '../../../shared/models'; +import {services} from '../../../shared/services'; + +require('./gpgkeys-list.scss'); + +interface NewGnuPGPublicKeyParams { + keyData: string; +} + +export class GpgKeysList extends React.Component> { + public static contextTypes = { + router: PropTypes.object, + apis: PropTypes.object, + history: PropTypes.object + }; + + private formApi: FormApi; + private loader: DataLoader; + + public render() { + return ( + (this.showAddGnuPGKey = true) + } + ] + } + }}> +
+
+ services.gpgkeys.list()} ref={loader => (this.loader = loader)}> + {(gpgkeys: models.GnuPGPublicKey[]) => + (gpgkeys.length > 0 && ( +
+
+
+
KEY ID
+
KEY TYPE
+
IDENTITY
+
+
+ {gpgkeys.map(gpgkey => ( +
+
+
+ {gpgkey.keyID} +
+
{gpgkey.subType.toUpperCase()}
+
+ {gpgkey.owner} + ( + + )} + items={[ + { + title: 'Remove', + action: () => this.removeKey(gpgkey.keyID) + } + ]} + /> +
+
+
+ ))} +
+ )) || ( + +

No GnuPG public keys currently configured

+
You can add GnuPG public keys below..
+ +
+ ) + } +
+
+
+ (this.showAddGnuPGKey = false)} + header={ +
+ {' '} + +
+ }> +

Add GnuPG public key

+
this.addGnuPGPublicKey({keyData: params.keyData})} + getApi={api => (this.formApi = api)} + preSubmit={(params: NewGnuPGPublicKeyParams) => ({ + keyData: params.keyData + })} + validateError={(params: NewGnuPGPublicKeyParams) => ({ + keyData: !params.keyData && 'Key data is required' + })}> + {formApi => ( + +
+ +
+
+ )} + +
+
+ ); + } + + private clearForms() { + this.formApi.resetAll(); + } + + private validateKeyInputfield(data: string): boolean { + if (data == null || data === '') { + return false; + } + const str = data.trim(); + const startNeedle = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n'; + const endNeedle = '\n-----END PGP PUBLIC KEY BLOCK-----'; + + if (str.length < startNeedle.length + endNeedle.length) { + return false; + } + if (!str.startsWith(startNeedle)) { + return false; + } + if (!str.endsWith(endNeedle)) { + return false; + } + return true; + } + + private async addGnuPGPublicKey(params: NewGnuPGPublicKeyParams) { + try { + if (!this.validateKeyInputfield(params.keyData)) { + throw { + name: 'Invalid key exception', + message: 'Invalid GnuPG key data found - must be ASCII armored' + }; + } + await services.gpgkeys.create({keyData: params.keyData}); + this.showAddGnuPGKey = false; + this.loader.reload(); + } catch (e) { + this.appContext.apis.notifications.show({ + content: , + type: NotificationType.Error + }); + } + } + + private async removeKey(keyId: string) { + const confirmed = await this.appContext.apis.popup.confirm('Remove GPG public key', 'Are you sure you want to remove GPG key with ID ' + keyId + '?'); + if (confirmed) { + await services.gpgkeys.delete(keyId); + this.loader.reload(); + } + } + + private get showAddGnuPGKey() { + return new URLSearchParams(this.props.location.search).get('addGnuPGPublicKey') === 'true'; + } + + private set showAddGnuPGKey(val: boolean) { + this.clearForms(); + this.appContext.router.history.push(`${this.props.match.url}?addGnuPGPublicKey=${val}`); + } + + private get appContext(): AppContext { + return this.context as AppContext; + } +} diff --git a/ui/src/app/settings/components/project-details/project-details.tsx b/ui/src/app/settings/components/project-details/project-details.tsx index cca90a5b7f0fc..cf0a8b21316c9 100644 --- a/ui/src/app/settings/components/project-details/project-details.tsx +++ b/ui/src/app/settings/components/project-details/project-details.tsx @@ -136,6 +136,7 @@ export class ProjectDetails extends React.Component )} +

Required signature keys {helpTip('IDs of GnuPG keys that commits must be signed with in order to be allowed to sync to')}

+ {((proj.spec.signatureKeys || []).length > 0 && ( +
+
+
+
KEY ID
+
+
+ {(proj.spec.signatureKeys || []).map(res => ( +
+
+
{res.keyID}
+
+
+ ))} +
+ )) || ( +
+

Commit signatures are not required

+
+ )} +

Orphaned Resource Monitoring {helpTip('Enables monitoring of top level resources in the application target namespace')}

diff --git a/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx b/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx index 1e41306ef1c5b..b5b592fdf12bb 100644 --- a/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx +++ b/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx @@ -26,6 +26,7 @@ export const ProjectEditPanel = (props: {nameReadonly?: boolean; defaultParams?: clusterResourceWhitelist: [], namespaceResourceBlacklist: [], namespaceResourceWhitelist: [], + signatureKeys: [], ...props.defaultParams }} validateError={(params: ProjectParams) => ({ @@ -200,6 +201,31 @@ export const ProjectEditPanel = (props: {nameReadonly?: boolean; defaultParams?: + services.gpgkeys.list().then(gpgkeys => gpgkeys.map(gpgkey => gpgkey.keyID))}> + {gpgkeys => ( + +

Required signature keys

+
GnuPG key IDs which commits to be synced to must be signed with
+ {(api.values.signatureKeys as Array).map((_, i) => ( +
+
+ + api.setValue('signatureKeys', removeEl(api.values.signatureKeys, i))} /> +
+
+ ))} + api.setValue('signatureKeys', api.values.signatureKeys.concat(gpgkeys[0]))}>add GnuPG key ID +
+ )} +
+

Orphaned Resource Monitoring

Enables monitoring of top level resources in the application target namespace
diff --git a/ui/src/app/settings/components/settings-container.tsx b/ui/src/app/settings/components/settings-container.tsx index ab7b5a9a87727..152b93cf1536b 100644 --- a/ui/src/app/settings/components/settings-container.tsx +++ b/ui/src/app/settings/components/settings-container.tsx @@ -5,6 +5,7 @@ import {AccountDetails} from './account-details/account-details'; import {AccountsList} from './accounts-list/accounts-list'; import {CertsList} from './certs-list/certs-list'; import {ClustersList} from './clusters-list/clusters-list'; +import {GpgKeysList} from './gpgkeys-list/gpgkeys-list'; import {ProjectDetails} from './project-details/project-details'; import {ProjectsList} from './projects-list/projects-list'; import {ReposList} from './repos-list/repos-list'; @@ -15,6 +16,7 @@ export const SettingsContainer = (props: RouteComponentProps) => ( + diff --git a/ui/src/app/settings/components/settings-overview/settings-overview.tsx b/ui/src/app/settings/components/settings-overview/settings-overview.tsx index 6a0d772c57e7b..106746159f4eb 100644 --- a/ui/src/app/settings/components/settings-overview/settings-overview.tsx +++ b/ui/src/app/settings/components/settings-overview/settings-overview.tsx @@ -17,6 +17,11 @@ const settings = [ description: 'Configure certificates for connecting Git repositories', path: './certs' }, + { + title: 'GnuPG keys', + description: 'Configure GnuPG public keys for commit verification', + path: './gpgkeys' + }, { title: 'Clusters', description: 'Configure connected Kubernetes clusters', diff --git a/ui/src/app/shared/models.ts b/ui/src/app/shared/models.ts index c802965d7607e..33cccb7eabd04 100644 --- a/ui/src/app/shared/models.ts +++ b/ui/src/app/shared/models.ts @@ -80,6 +80,7 @@ export interface RevisionMetadata { date: models.Time; tags?: string[]; message?: string; + signatureInfo?: string; } export interface SyncOperationResult { @@ -587,6 +588,10 @@ export interface GroupKind { kind: string; } +export interface ProjectSignatureKey { + keyID: string; +} + export interface ProjectSpec { sourceRepos: string[]; destinations: ApplicationDestination[]; @@ -595,6 +600,7 @@ export interface ProjectSpec { clusterResourceWhitelist: GroupKind[]; namespaceResourceBlacklist: GroupKind[]; namespaceResourceWhitelist: GroupKind[]; + signatureKeys: ProjectSignatureKey[]; orphanedResources?: {warn?: boolean}; syncWindows?: SyncWindows; } @@ -668,3 +674,13 @@ export interface Account { capabilities: string[]; tokens: Token[]; } + +export interface GnuPGPublicKey { + keyID?: string; + fingerprint?: string; + subType?: string; + owner?: string; + keyData?: string; +} + +export interface GnuPGPublicKeyList extends ItemsList {} diff --git a/ui/src/app/shared/services/gpgkey-service.ts b/ui/src/app/shared/services/gpgkey-service.ts new file mode 100644 index 0000000000000..3d8e147752ce7 --- /dev/null +++ b/ui/src/app/shared/services/gpgkey-service.ts @@ -0,0 +1,26 @@ +import * as models from '../models'; +import requests from './requests'; + +export class GnuPGPublicKeyService { + public list(): Promise { + return requests + .get('/gpgkeys') + .then(res => res.body as models.GnuPGPublicKeyList) + .then(list => list.items || []); + } + + public create(publickey: models.GnuPGPublicKey): Promise { + return requests + .post('/gpgkeys') + .send(publickey) + .then(res => res.body as models.GnuPGPublicKeyList); + } + + public delete(keyID: string): Promise { + return requests + .delete('/gpgkeys') + .query({keyID}) + .send() + .then(res => res.body as models.GnuPGPublicKey); + } +} diff --git a/ui/src/app/shared/services/index.ts b/ui/src/app/shared/services/index.ts index 303c318228057..65b91a2e4d56f 100644 --- a/ui/src/app/shared/services/index.ts +++ b/ui/src/app/shared/services/index.ts @@ -3,6 +3,7 @@ import {ApplicationsService} from './applications-service'; import {AuthService} from './auth-service'; import {CertificatesService} from './cert-service'; import {ClustersService} from './clusters-service'; +import {GnuPGPublicKeyService} from './gpgkey-service'; import {ProjectsService} from './projects-service'; import {RepositoriesService} from './repo-service'; import {RepoCredsService} from './repocreds-service'; @@ -22,6 +23,7 @@ export interface Services { viewPreferences: ViewPreferencesService; version: VersionService; accounts: AccountsService; + gpgkeys: GnuPGPublicKeyService; } export const services: Services = { @@ -35,7 +37,8 @@ export const services: Services = { projects: new ProjectsService(), viewPreferences: new ViewPreferencesService(), version: new VersionService(), - accounts: new AccountsService() + accounts: new AccountsService(), + gpgkeys: new GnuPGPublicKeyService() }; export {ProjectParams, ProjectRoleParams, CreateJWTTokenParams, DeleteJWTTokenParams, JWTTokenResponse} from './projects-service'; diff --git a/ui/src/app/shared/services/projects-service.ts b/ui/src/app/shared/services/projects-service.ts index 12bc069118db9..dbcd68009e7f3 100644 --- a/ui/src/app/shared/services/projects-service.ts +++ b/ui/src/app/shared/services/projects-service.ts @@ -11,6 +11,7 @@ export interface ProjectParams { clusterResourceWhitelist: models.GroupKind[]; namespaceResourceBlacklist: models.GroupKind[]; namespaceResourceWhitelist: models.GroupKind[]; + signatureKeys: models.ProjectSignatureKey[]; orphanedResourcesEnabled: boolean; orphanedResourcesWarn: boolean; syncWindows: models.SyncWindow[]; @@ -80,6 +81,7 @@ function paramsToProj(params: ProjectParams) { clusterResourceWhitelist: params.clusterResourceWhitelist, namespaceResourceBlacklist: params.namespaceResourceBlacklist, namespaceResourceWhitelist: params.namespaceResourceWhitelist, + signatureKeys: params.signatureKeys, orphanedResources: (params.orphanedResourcesEnabled && {warn: !!params.orphanedResourcesWarn}) || null } }; diff --git a/util/db/db.go b/util/db/db.go index 068f48978d799..9503b57df843e 100644 --- a/util/db/db.go +++ b/util/db/db.go @@ -59,6 +59,13 @@ type ArgoDB interface { // ListHelmRepositories lists repositories ListHelmRepositories(ctx context.Context) ([]*appv1.Repository, error) + + // ListConfiguredGPGPublicKeys returns all GPG public key IDs that are configured + ListConfiguredGPGPublicKeys(ctx context.Context) (map[string]*appv1.GnuPGPublicKey, error) + // AddGPGPublicKey adds one ore more GPG public keys to the configuration + AddGPGPublicKey(ctx context.Context, keyData string) (map[string]*appv1.GnuPGPublicKey, []string, error) + // DeleteGPGPublicKey removes a GPG public key from the configuration + DeleteGPGPublicKey(ctx context.Context, keyID string) error } type db struct { diff --git a/util/db/gpgkeys.go b/util/db/gpgkeys.go new file mode 100644 index 0000000000000..776a41a9741e5 --- /dev/null +++ b/util/db/gpgkeys.go @@ -0,0 +1,142 @@ +package db + +import ( + "context" + "fmt" + "io/ioutil" + "os" + + log "github.com/sirupsen/logrus" + + "github.com/argoproj/argo-cd/common" + appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/util/gpg" +) + +// Validates a single GnuPG key and returns the key's ID +func validatePGPKey(keyData string) (*appsv1.GnuPGPublicKey, error) { + f, err := ioutil.TempFile("", "gpg-public-key") + if err != nil { + return nil, err + } + defer os.Remove(f.Name()) + + err = ioutil.WriteFile(f.Name(), []byte(keyData), 0600) + if err != nil { + return nil, err + } + f.Close() + + parsed, err := gpg.ValidatePGPKeys(f.Name()) + if err != nil { + return nil, err + } + + // Each key/value pair in the config map must exactly contain one public key, with the (short) GPG key ID as key + if len(parsed) != 1 { + return nil, fmt.Errorf("More than one key found in input data") + } + + var retKey *appsv1.GnuPGPublicKey = nil + // Is there a better way to get the first element from a map without knowing its key? + for _, k := range parsed { + retKey = k + break + } + if retKey != nil { + retKey.KeyData = keyData + return retKey, nil + } else { + return nil, fmt.Errorf("Could not find the GPG key") + } +} + +// ListConfiguredGPGPublicKeys returns a list of all configured GPG public keys from the ConfigMap +func (db *db) ListConfiguredGPGPublicKeys(ctx context.Context) (map[string]*appsv1.GnuPGPublicKey, error) { + log.Debugf("Loading PGP public keys from config map") + result := make(map[string]*appsv1.GnuPGPublicKey) + keysCM, err := db.settingsMgr.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + if err != nil { + return nil, err + } + + // We have to verify all PGP keys in the ConfigMap to be valid keys before. To do so, + // we write each single one out to a temporary file and validate them through gpg. + // This is not optimal, but the executil from argo-pkg does not support writing to + // stdin of the forked process. So for now, we must live with that. + for k, p := range keysCM.Data { + if expectedKeyID := gpg.KeyID(k); expectedKeyID != "" { + parsedKey, err := validatePGPKey(p) + if err != nil { + return nil, fmt.Errorf("Could not parse GPG key for entry '%s': %s", expectedKeyID, err.Error()) + } + if expectedKeyID != parsedKey.KeyID { + return nil, fmt.Errorf("Key parsed for entry with key ID '%s' had different key ID '%s'", expectedKeyID, parsedKey.KeyID) + } + result[parsedKey.KeyID] = parsedKey + } else { + return nil, fmt.Errorf("Found entry with key '%s' in ConfigMap, but this is not a valid PGP key ID", k) + } + } + + return result, nil +} + +// AddGPGPublicKey adds one or more public keys to the configuration +func (db *db) AddGPGPublicKey(ctx context.Context, keyData string) (map[string]*appsv1.GnuPGPublicKey, []string, error) { + result := make(map[string]*appsv1.GnuPGPublicKey) + skipped := make([]string, 0) + + keys, err := gpg.ValidatePGPKeysFromString(keyData) + if err != nil { + return nil, nil, err + } + + keysCM, err := db.settingsMgr.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + if err != nil { + return nil, nil, err + } + + if keysCM.Data == nil { + keysCM.Data = make(map[string]string) + } + + for kid, key := range keys { + if _, ok := keysCM.Data[kid]; ok { + skipped = append(skipped, kid) + log.Debugf("Not adding incoming key with kid=%s because it is configured already", kid) + } else { + result[kid] = key + keysCM.Data[kid] = key.KeyData + log.Debugf("Adding incoming key with kid=%s to database", kid) + } + } + + err = db.settingsMgr.SaveGPGPublicKeyData(ctx, keysCM.Data) + if err != nil { + return nil, nil, err + } + + return result, skipped, nil +} + +// DeleteGPGPublicKey deletes a GPG public key from the configuration +func (db *db) DeleteGPGPublicKey(ctx context.Context, keyID string) error { + keysCM, err := db.settingsMgr.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + if err != nil { + return err + } + + if keysCM.Data == nil { + return fmt.Errorf("No such key configured: %s", keyID) + } + + if _, ok := keysCM.Data[keyID]; !ok { + return fmt.Errorf("No such key configured: %s", keyID) + } + + delete(keysCM.Data, keyID) + + err = db.settingsMgr.SaveGPGPublicKeyData(ctx, keysCM.Data) + return err +} diff --git a/util/db/gpgkeys_test.go b/util/db/gpgkeys_test.go new file mode 100644 index 0000000000000..4d1ad9eac7b3c --- /dev/null +++ b/util/db/gpgkeys_test.go @@ -0,0 +1,304 @@ +package db + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + + "github.com/argoproj/argo-cd/common" + "github.com/argoproj/argo-cd/test" + "github.com/argoproj/argo-cd/util/settings" +) + +// GPG config map with a single key and good mapping +var gpgCMEmpty = v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDGPGKeysConfigMapName, + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, +} + +// GPG config map with a single key and good mapping +var gpgCMSingleGoodPubkey = v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDGPGKeysConfigMapName, + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + Data: map[string]string{ + "4AEE18F83AFDEB23": test.MustLoadFileToString("../gpg/testdata/github.asc"), + }, +} + +// GPG config map with two keys and good mapping +var gpgCMMultiGoodPubkey = v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDGPGKeysConfigMapName, + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + Data: map[string]string{ + "FDC79815400D88A9": test.MustLoadFileToString("../gpg/testdata/johndoe.asc"), + "F7842A5CEAA9C0B1": test.MustLoadFileToString("../gpg/testdata/janedoe.asc"), + }, +} + +// GPG config map with a single key and bad mapping +var gpgCMSingleKeyWrongId = v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDGPGKeysConfigMapName, + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + Data: map[string]string{ + "5AEE18F83AFDEB23": test.MustLoadFileToString("../gpg/testdata/github.asc"), + }, +} + +// GPG config map with a garbage pub key +var gpgCMGarbagePubkey = v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDGPGKeysConfigMapName, + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + Data: map[string]string{ + "4AEE18F83AFDEB23": test.MustLoadFileToString("../gpg/testdata/garbage.asc"), + }, +} + +// GPG config map with a wrong key +var gpgCMGarbageCMKey = v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDGPGKeysConfigMapName, + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + Data: map[string]string{ + "wullerosekaufe": test.MustLoadFileToString("../gpg/testdata/github.asc"), + }, +} + +// Returns a fake client set for use in tests +func getGPGKeysClientset(gpgCM v1.ConfigMap) *fake.Clientset { + cm := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-cm", + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + Data: nil, + } + + return fake.NewSimpleClientset([]runtime.Object{&cm, &gpgCM}...) +} + +func Test_ValidatePGPKey(t *testing.T) { + // Good case - single PGP key + { + key, err := validatePGPKey(test.MustLoadFileToString("../gpg/testdata/github.asc")) + assert.NoError(t, err) + assert.NotNil(t, key) + assert.Equal(t, "4AEE18F83AFDEB23", key.KeyID) + assert.NotEmpty(t, key.Owner) + assert.NotEmpty(t, key.KeyData) + assert.NotEmpty(t, key.SubType) + } + // Bad case - Garbage + { + key, err := validatePGPKey(test.MustLoadFileToString("../gpg/testdata/garbage.asc")) + assert.Error(t, err) + assert.Nil(t, key) + } + // Bad case - more than one key + { + key, err := validatePGPKey(test.MustLoadFileToString("../gpg/testdata/multi.asc")) + assert.Error(t, err) + assert.Nil(t, key) + } +} + +func Test_ListConfiguredGPGPublicKeys(t *testing.T) { + // Good case. Single key in input, right mapping to Key ID in CM + { + clientset := getGPGKeysClientset(gpgCMSingleGoodPubkey) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + if db == nil { + panic("could not get database") + } + keys, err := db.ListConfiguredGPGPublicKeys(context.Background()) + assert.NoError(t, err) + assert.Len(t, keys, 1) + } + // Good case. No certificates in ConfigMap + { + clientset := getGPGKeysClientset(gpgCMEmpty) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + if db == nil { + panic("could not get database") + } + keys, err := db.ListConfiguredGPGPublicKeys(context.Background()) + assert.NoError(t, err) + assert.Len(t, keys, 0) + } + // Bad case. Single key in input, wrong mapping to Key ID in CM + { + clientset := getGPGKeysClientset(gpgCMSingleKeyWrongId) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + if db == nil { + panic("could not get database") + } + keys, err := db.ListConfiguredGPGPublicKeys(context.Background()) + assert.Error(t, err) + assert.Len(t, keys, 0) + } + // Bad case. Garbage public key + { + clientset := getGPGKeysClientset(gpgCMGarbagePubkey) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + if db == nil { + panic("could not get database") + } + keys, err := db.ListConfiguredGPGPublicKeys(context.Background()) + assert.Error(t, err) + assert.Len(t, keys, 0) + } + // Bad case. Garbage ConfigMap key in data + { + clientset := getGPGKeysClientset(gpgCMGarbageCMKey) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + if db == nil { + panic("could not get database") + } + keys, err := db.ListConfiguredGPGPublicKeys(context.Background()) + assert.Error(t, err) + assert.Len(t, keys, 0) + } +} + +func Test_AddGPGPublicKey(t *testing.T) { + // Good case + { + clientset := getGPGKeysClientset(gpgCMEmpty) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + + // Key should be added + new, skipped, err := db.AddGPGPublicKey(context.Background(), test.MustLoadFileToString("../gpg/testdata/github.asc")) + assert.NoError(t, err) + assert.Len(t, new, 1) + assert.Len(t, skipped, 0) + cm, err := settings.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + assert.NoError(t, err) + assert.Len(t, cm.Data, 1) + + // Same key should not be added, but skipped + new, skipped, err = db.AddGPGPublicKey(context.Background(), test.MustLoadFileToString("../gpg/testdata/github.asc")) + assert.NoError(t, err) + assert.Len(t, new, 0) + assert.Len(t, skipped, 1) + cm, err = settings.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + assert.NoError(t, err) + assert.Len(t, cm.Data, 1) + + // New keys should be added + new, skipped, err = db.AddGPGPublicKey(context.Background(), test.MustLoadFileToString("../gpg/testdata/multi.asc")) + assert.NoError(t, err) + assert.Len(t, new, 2) + assert.Len(t, skipped, 0) + cm, err = settings.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + assert.NoError(t, err) + assert.Len(t, cm.Data, 3) + + // Same new keys should be skipped + new, skipped, err = db.AddGPGPublicKey(context.Background(), test.MustLoadFileToString("../gpg/testdata/multi.asc")) + assert.NoError(t, err) + assert.Len(t, new, 0) + assert.Len(t, skipped, 2) + cm, err = settings.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + assert.NoError(t, err) + assert.Len(t, cm.Data, 3) + + // Garbage input should result in error + new, skipped, err = db.AddGPGPublicKey(context.Background(), test.MustLoadFileToString("../gpg/testdata/garbage.asc")) + assert.Error(t, err) + assert.Nil(t, new) + assert.Nil(t, skipped) + cm, err = settings.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + assert.NoError(t, err) + assert.Len(t, cm.Data, 3) + } +} + +func Test_DeleteGPGPublicKey(t *testing.T) { + defer os.Setenv("GNUPGHOME", "") + // Good case + { + clientset := getGPGKeysClientset(gpgCMMultiGoodPubkey) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + + // Key should be removed + err := db.DeleteGPGPublicKey(context.Background(), "FDC79815400D88A9") + assert.NoError(t, err) + + // Key should not exist anymore, therefore can't be deleted again + err = db.DeleteGPGPublicKey(context.Background(), "FDC79815400D88A9") + assert.Error(t, err) + + // One key left in configuration + n, err := db.ListConfiguredGPGPublicKeys(context.Background()) + assert.NoError(t, err) + assert.Len(t, n, 1) + + // Key should be removed + err = db.DeleteGPGPublicKey(context.Background(), "F7842A5CEAA9C0B1") + assert.NoError(t, err) + + // Key should not exist anymore, therefore can't be deleted again + err = db.DeleteGPGPublicKey(context.Background(), "F7842A5CEAA9C0B1") + assert.Error(t, err) + + // No key left in configuration + n, err = db.ListConfiguredGPGPublicKeys(context.Background()) + assert.NoError(t, err) + assert.Len(t, n, 0) + + } + // Bad case - empty ConfigMap + { + clientset := getGPGKeysClientset(gpgCMEmpty) + settings := settings.NewSettingsManager(context.Background(), clientset, testNamespace) + db := NewDB(testNamespace, settings, clientset) + + // Key should be removed + err := db.DeleteGPGPublicKey(context.Background(), "F7842A5CEAA9C0B1") + assert.Error(t, err) + } +} diff --git a/util/db/mocks/ArgoDB.go b/util/db/mocks/ArgoDB.go index 615e2b4908cd8..e5e3d3cb6c767 100644 --- a/util/db/mocks/ArgoDB.go +++ b/util/db/mocks/ArgoDB.go @@ -1,4 +1,4 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v1.1.2. DO NOT EDIT. package mocks @@ -16,6 +16,38 @@ type ArgoDB struct { mock.Mock } +// AddGPGPublicKey provides a mock function with given fields: ctx, keyData +func (_m *ArgoDB) AddGPGPublicKey(ctx context.Context, keyData string) (map[string]*v1alpha1.GnuPGPublicKey, []string, error) { + ret := _m.Called(ctx, keyData) + + var r0 map[string]*v1alpha1.GnuPGPublicKey + if rf, ok := ret.Get(0).(func(context.Context, string) map[string]*v1alpha1.GnuPGPublicKey); ok { + r0 = rf(ctx, keyData) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]*v1alpha1.GnuPGPublicKey) + } + } + + var r1 []string + if rf, ok := ret.Get(1).(func(context.Context, string) []string); ok { + r1 = rf(ctx, keyData) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]string) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { + r2 = rf(ctx, keyData) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // CreateCluster provides a mock function with given fields: ctx, c func (_m *ArgoDB) CreateCluster(ctx context.Context, c *v1alpha1.Cluster) (*v1alpha1.Cluster, error) { ret := _m.Called(ctx, c) @@ -122,6 +154,20 @@ func (_m *ArgoDB) DeleteCluster(ctx context.Context, server string) error { return r0 } +// DeleteGPGPublicKey provides a mock function with given fields: ctx, keyID +func (_m *ArgoDB) DeleteGPGPublicKey(ctx context.Context, keyID string) error { + ret := _m.Called(ctx, keyID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, keyID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // DeleteRepository provides a mock function with given fields: ctx, name func (_m *ArgoDB) DeleteRepository(ctx context.Context, name string) error { ret := _m.Called(ctx, name) @@ -242,6 +288,29 @@ func (_m *ArgoDB) ListClusters(ctx context.Context) (*v1alpha1.ClusterList, erro return r0, r1 } +// ListConfiguredGPGPublicKeys provides a mock function with given fields: ctx +func (_m *ArgoDB) ListConfiguredGPGPublicKeys(ctx context.Context) (map[string]*v1alpha1.GnuPGPublicKey, error) { + ret := _m.Called(ctx) + + var r0 map[string]*v1alpha1.GnuPGPublicKey + if rf, ok := ret.Get(0).(func(context.Context) map[string]*v1alpha1.GnuPGPublicKey); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]*v1alpha1.GnuPGPublicKey) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ListHelmRepositories provides a mock function with given fields: ctx func (_m *ArgoDB) ListHelmRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) { ret := _m.Called(ctx) diff --git a/util/git/client.go b/util/git/client.go index 1802eecff91ce..89c29868c837c 100644 --- a/util/git/client.go +++ b/util/git/client.go @@ -47,6 +47,7 @@ type Client interface { LsLargeFiles() ([]string, error) CommitSHA() (string, error) RevisionMetadata(revision string) (*RevisionMetadata, error) + VerifyCommitSignature(string) (string, error) } // nativeGitClient implements Client interface using git CLI @@ -439,6 +440,22 @@ func (m *nativeGitClient) RevisionMetadata(revision string) (*RevisionMetadata, return &RevisionMetadata{author, time.Unix(authorDateUnixTimestamp, 0), tags, message}, nil } +// VerifyCommitSignature Runs verify-commit on a given revision and returns the output +func (m *nativeGitClient) VerifyCommitSignature(revision string) (string, error) { + out, err := m.runGnuPGWrapper("git-verify-wrapper.sh", revision) + if err != nil { + return "", err + } + return out, nil +} + +// runWrapper runs a custom command with all the semantics of running the Git client +func (m *nativeGitClient) runGnuPGWrapper(wrapper string, args ...string) (string, error) { + cmd := exec.Command(wrapper, args...) + cmd.Env = append(cmd.Env, fmt.Sprintf("GNUPGHOME=%s", common.GetGnuPGHomePath())) + return m.runCmdOutput(cmd) +} + // runCmd is a convenience function to run a command in a given directory and return its output func (m *nativeGitClient) runCmd(args ...string) (string, error) { cmd := exec.Command("git", args...) diff --git a/util/git/git_test.go b/util/git/git_test.go index 30714e920e63a..41d45229c15c5 100644 --- a/util/git/git_test.go +++ b/util/git/git_test.go @@ -270,6 +270,45 @@ func TestLFSClient(t *testing.T) { } } +func TestVerifyCommitSignature(t *testing.T) { + p, err := ioutil.TempDir("", "test-verify-commit-sig") + if err != nil { + panic(err.Error()) + } + defer os.RemoveAll(p) + + client, err := NewClientExt("https://github.com/argoproj/argo-cd.git", p, NopCreds{}, false, false) + assert.NoError(t, err) + + err = client.Init() + assert.NoError(t, err) + + err = client.Fetch() + assert.NoError(t, err) + + commitSHA, err := client.LsRemote("HEAD") + assert.NoError(t, err) + + err = client.Checkout(commitSHA) + assert.NoError(t, err) + + // 28027897aad1262662096745f2ce2d4c74d02b7f is a commit that is signed in the repo + // It doesn't matter whether we know the key or not at this stage + { + out, err := client.VerifyCommitSignature("28027897aad1262662096745f2ce2d4c74d02b7f") + assert.NoError(t, err) + assert.NotEmpty(t, out) + assert.Contains(t, out, "gpg: Signature made") + } + + // 85d660f0b967960becce3d49bd51c678ba2a5d24 is a commit that is not signed + { + out, err := client.VerifyCommitSignature("85d660f0b967960becce3d49bd51c678ba2a5d24") + assert.NoError(t, err) + assert.Empty(t, out) + } +} + func TestNewFactory(t *testing.T) { addBinDirToPath := path.NewBinDirToPath() defer addBinDirToPath.Close() diff --git a/util/git/mocks/Client.go b/util/git/mocks/Client.go index 180a76b01c084..937a3bb23cbb7 100644 --- a/util/git/mocks/Client.go +++ b/util/git/mocks/Client.go @@ -2,11 +2,8 @@ package mocks -import ( - mock "github.com/stretchr/testify/mock" - - git "github.com/argoproj/argo-cd/util/git" -) +import git "github.com/argoproj/argo-cd/util/git" +import mock "github.com/stretchr/testify/mock" // Client is an autogenerated mock type for the Client type type Client struct { @@ -179,3 +176,24 @@ func (_m *Client) Root() string { return r0 } + +// VerifyCommitSignature provides a mock function with given fields: _a0 +func (_m *Client) VerifyCommitSignature(_a0 string) (string, error) { + ret := _m.Called(_a0) + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/util/gpg/gpg.go b/util/gpg/gpg.go new file mode 100644 index 0000000000000..ec7575f812716 --- /dev/null +++ b/util/gpg/gpg.go @@ -0,0 +1,682 @@ +package gpg + +import ( + "bufio" + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strings" + + executil "github.com/argoproj/gitops-engine/pkg/utils/exec" + + "github.com/argoproj/argo-cd/common" + appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" +) + +// Regular expression to match public key beginning +var subTypeMatch = regexp.MustCompile(`^pub\s+([a-z0-9]+)\s\d+-\d+-\d+\s\[[A-Z]+\].*$`) + +// Regular expression to match key ID output from gpg +var keyIdMatch = regexp.MustCompile(`^\s+([0-9A-Za-z]+)\s*$`) + +// Regular expression to match identity output from gpg +var uidMatch = regexp.MustCompile(`^uid\s*\[\s*([a-z]+)\s*\]\s+(.*)$`) + +// Regular expression to match import status +var importMatch = regexp.MustCompile(`^gpg: key ([A-Z0-9]+): public key "([^"]+)" imported$`) + +// Regular expression to match the start of a commit signature verification +var verificationStartMatch = regexp.MustCompile(`^gpg: Signature made ([a-zA-Z0-9\ :]+)$`) + +// Regular expression to match the key ID of a commit signature verification +var verificationKeyIDMatch = regexp.MustCompile(`^gpg:\s+using\s([A-Za-z]+)\skey\s([a-zA-Z0-9]+)$`) + +// Regular expression to match the signature status of a commit signature verification +var verificationStatusMatch = regexp.MustCompile(`^gpg: ([a-zA-Z]+) signature from "([^"]+)" \[([a-zA-Z]+)\]$`) + +// This is the recipe for automatic key generation, passed to gpg --batch --generate-key +// for initializing our keyring with a trustdb. A new private key will be generated each +// time argocd-server starts, so it's transient and is not used for anything except for +// creating the trustdb in a specific argocd-repo-server pod. +var batchKeyCreateRecipe = `%no-protection +%transient-key +Key-Type: default +Key-Length: 2048 +Key-Usage: sign +Name-Real: Anon Ymous +Name-Comment: ArgoCD key signing key +Name-Email: noreply@argoproj.io +Expire-Date: 6m +%commit +` + +type PGPKeyID string + +func isHexString(s string) bool { + _, err := hex.DecodeString(s) + if err != nil { + return false + } else { + return true + } +} + +// KeyID get the actual correct (short) key ID from either a fingerprint or the key ID. Returns the empty string if k seems not to be a PGP key ID. +func KeyID(k string) string { + if IsLongKeyID(k) { + return k[24:] + } else if IsShortKeyID(k) { + return k + } + // Invalid key + return "" +} + +// IsLongKeyID returns true if the string represents a long key ID (aka fingerprint) +func IsLongKeyID(k string) bool { + if len(k) == 40 && isHexString(k) { + return true + } else { + return false + } +} + +// IsShortKeyID returns true if the string represents a short key ID +func IsShortKeyID(k string) bool { + if len(k) == 16 && isHexString(k) { + return true + } else { + return false + } +} + +// Result of a git commit verification +type PGPVerifyResult struct { + // Date the signature was made + Date string + // KeyID the signature was made with + KeyID string + // Identity + Identity string + // Trust level of the key + Trust string + // Cipher of the key the signature was made with + Cipher string + // Result of verification - "unknown", "good" or "bad" + Result string + // Additional informational message + Message string +} + +// Signature verification results +const ( + VerifyResultGood = "Good" + VerifyResultBad = "Bad" + VerifyResultInvalid = "Invalid" + VerifyResultUnknown = "Unknown" +) + +// Key trust values +const ( + TrustUnknown = "unknown" + TrustNone = "never" + TrustMarginal = "marginal" + TrustFull = "full" + TrustUltimate = "ultimate" +) + +// Key trust mappings +var pgpTrustLevels = map[string]int{ + TrustUnknown: 2, + TrustNone: 3, + TrustMarginal: 4, + TrustFull: 5, + TrustUltimate: 6, +} + +// Maximum number of lines to parse for a gpg verify-commit output +const MaxVerificationLinesToParse = 40 + +// Helper function to append GNUPGHOME for a command execution environment +func getGPGEnviron() []string { + return append(os.Environ(), fmt.Sprintf("GNUPGHOME=%s", common.GetGnuPGHomePath())) +} + +// Helper function to write some data to a temp file and return its path +func writeKeyToFile(keyData string) (string, error) { + f, err := ioutil.TempFile("", "gpg-public-key") + if err != nil { + return "", err + } + + err = ioutil.WriteFile(f.Name(), []byte(keyData), 0600) + if err != nil { + os.Remove(f.Name()) + return "", err + } + f.Close() + return f.Name(), nil +} + +// IsGPGEnabled returns true if GPG feature is enabled +func IsGPGEnabled() bool { + if en := os.Getenv("ARGOCD_GPG_ENABLED"); strings.ToLower(en) == "false" || strings.ToLower(en) == "no" { + return false + } + return true +} + +// InitializePGP will initialize a GnuPG working directory and also create a +// transient private key so that the trust DB will work correctly. +func InitializeGnuPG() error { + + gnuPgHome := common.GetGnuPGHomePath() + + // We only operate if ARGOCD_GNUPGHOME is set + if gnuPgHome == "" { + return fmt.Errorf("%s is not set; refusing to initialize", common.EnvGnuPGHome) + } + + // Directory set in ARGOCD_GNUPGHOME must exist and has to be a directory + st, err := os.Stat(gnuPgHome) + if err != nil { + return err + } + + if !st.IsDir() { + return fmt.Errorf("%s ('%s') does not point to a directory", common.EnvGnuPGHome, gnuPgHome) + } + + // Check for sane permissions as well (GPG will issue a warning otherwise) + if st.Mode().Perm() != 0700 { + return fmt.Errorf("%s at '%s' has too wide permissions, must be 0700", common.EnvGnuPGHome, gnuPgHome) + } + + _, err = os.Stat(path.Join(gnuPgHome, "trustdb.gpg")) + if err != nil { + if !os.IsNotExist(err) { + return err + } + } else { + // We can't initialize a second time + return fmt.Errorf("%s at %s already initialized, can't initialize again.", common.EnvGnuPGHome, gnuPgHome) + } + + f, err := ioutil.TempFile("", "gpg-key-recipe") + if err != nil { + return err + } + + defer os.Remove(f.Name()) + + _, err = f.WriteString(batchKeyCreateRecipe) + if err != nil { + return err + } + + f.Close() + + cmd := exec.Command("gpg", "--logger-fd", "1", "--batch", "--generate-key", f.Name()) + cmd.Env = getGPGEnviron() + + _, err = executil.Run(cmd) + return err +} + +func ParsePGPKeyBlock(keyFile string) ([]string, error) { + return nil, nil +} + +func ImportPGPKeysFromString(keyData string) ([]*appsv1.GnuPGPublicKey, error) { + f, err := ioutil.TempFile("", "gpg-key-import") + if err != nil { + return nil, err + } + defer os.Remove(f.Name()) + _, err = f.WriteString(keyData) + if err != nil { + return nil, err + } + f.Close() + return ImportPGPKeys(f.Name()) +} + +// ImportPGPKey imports one or more keys from a file into the local keyring and optionally +// signs them with the transient private key for leveraging the trust DB. +func ImportPGPKeys(keyFile string) ([]*appsv1.GnuPGPublicKey, error) { + keys := make([]*appsv1.GnuPGPublicKey, 0) + + cmd := exec.Command("gpg", "--logger-fd", "1", "--import", keyFile) + cmd.Env = getGPGEnviron() + + out, err := executil.Run(cmd) + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(strings.NewReader(out)) + for scanner.Scan() { + if !strings.HasPrefix(scanner.Text(), "gpg: ") { + continue + } + // We ignore lines that are not of interest + token := importMatch.FindStringSubmatch(scanner.Text()) + if len(token) != 3 { + continue + } + + key := appsv1.GnuPGPublicKey{ + KeyID: token[1], + Owner: token[2], + // By default, trust level is unknown + Trust: TrustUnknown, + // Subtype is unknown at this point + SubType: "unknown", + Fingerprint: "", + } + + keys = append(keys, &key) + } + + return keys, nil +} + +func ValidatePGPKeysFromString(keyData string) (map[string]*appsv1.GnuPGPublicKey, error) { + f, err := writeKeyToFile(keyData) + if err != nil { + return nil, err + } + defer os.Remove(f) + + return ValidatePGPKeys(f) +} + +// ValidatePGPKeys validates whether the keys in keyFile are valid PGP keys and can be imported +// It does so by importing them into a temporary keyring. The returned keys are complete, that +// is, they contain all relevant information +func ValidatePGPKeys(keyFile string) (map[string]*appsv1.GnuPGPublicKey, error) { + keys := make(map[string]*appsv1.GnuPGPublicKey) + tempHome, err := ioutil.TempDir("", "gpg-verify-key") + if err != nil { + return nil, err + } + defer os.RemoveAll(tempHome) + + // Remember original GNUPGHOME, then set it to temp directory + oldGPGHome := os.Getenv(common.EnvGnuPGHome) + defer os.Setenv(common.EnvGnuPGHome, oldGPGHome) + os.Setenv(common.EnvGnuPGHome, tempHome) + + // Import they keys to our temporary keyring... + _, err = ImportPGPKeys(keyFile) + if err != nil { + return nil, err + } + + // ... and export them again, to get key data and fingerprint + imported, err := GetInstalledPGPKeys(nil) + if err != nil { + return nil, err + } + + for _, key := range imported { + keys[key.KeyID] = key + } + + return keys, nil +} + +// SetPGPTrustLevel sets the given trust level on keys with specified key IDs +func SetPGPTrustLevelById(kids []string, trustLevel string) error { + keys := make([]*appsv1.GnuPGPublicKey, 0) + for _, kid := range kids { + keys = append(keys, &appsv1.GnuPGPublicKey{KeyID: kid}) + } + return SetPGPTrustLevel(keys, trustLevel) +} + +// SetPGPTrustLevel sets the given trust level on specified keys +func SetPGPTrustLevel(pgpKeys []*appsv1.GnuPGPublicKey, trustLevel string) error { + trust, ok := pgpTrustLevels[trustLevel] + if !ok { + return fmt.Errorf("Unknown trust level: %s", trustLevel) + } + + // We need to store ownertrust specification in a temp file. Format is : + f, err := ioutil.TempFile("", "gpg-key-fps") + if err != nil { + return err + } + + defer os.Remove(f.Name()) + + for _, k := range pgpKeys { + _, err := f.WriteString(fmt.Sprintf("%s:%d\n", k.KeyID, trust)) + if err != nil { + return err + } + } + + f.Close() + + // Load ownertrust from the file we have constructed and instruct gpg to update the trustdb + cmd := exec.Command("gpg", "--import-ownertrust", f.Name()) + cmd.Env = getGPGEnviron() + + _, err = executil.Run(cmd) + if err != nil { + return err + } + + // Update the trustdb once we updated the ownertrust, to prevent gpg to do it once we validate a signature + cmd = exec.Command("gpg", "--update-trustdb") + cmd.Env = getGPGEnviron() + _, err = executil.Run(cmd) + if err != nil { + return err + } + + return nil +} + +// DeletePGPKey deletes a key from our GnuPG key ring +func DeletePGPKey(keyID string) error { + args := append([]string{}, "--yes", "--batch", "--delete-keys", keyID) + cmd := exec.Command("gpg", args...) + cmd.Env = getGPGEnviron() + + _, err := executil.Run(cmd) + if err != nil { + return err + } + + return nil +} + +// IsSecretKey returns true if the keyID also has a private key in the keyring +func IsSecretKey(keyID string) (bool, error) { + args := append([]string{}, "--list-secret-keys", keyID) + cmd := exec.Command("gpg-wrapper.sh", args...) + cmd.Env = getGPGEnviron() + out, err := executil.Run(cmd) + if err != nil { + return false, err + } + if strings.HasPrefix(out, "gpg: error reading key: No secret key") { + return false, nil + } + return true, nil +} + +// GetInstalledPGPKeys() runs gpg to retrieve public keys from our keyring. If kids is non-empty, limit result to those key IDs +func GetInstalledPGPKeys(kids []string) ([]*appsv1.GnuPGPublicKey, error) { + keys := make([]*appsv1.GnuPGPublicKey, 0) + + args := append([]string{}, "--list-public-keys") + // kids can contain an arbitrary list of key IDs we want to list. If empty, we list all keys. + if len(kids) > 0 { + args = append(args, kids...) + } + cmd := exec.Command("gpg", args...) + cmd.Env = getGPGEnviron() + + out, err := executil.Run(cmd) + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(strings.NewReader(out)) + var curKey *appsv1.GnuPGPublicKey = nil + for scanner.Scan() { + if strings.HasPrefix(scanner.Text(), "pub ") { + // This is the beginning of a new key, time to store the previously parsed one in our list and start fresh. + if curKey != nil { + keys = append(keys, curKey) + curKey = nil + } + + key := appsv1.GnuPGPublicKey{} + + // Second field in pub output denotes key sub type (cipher and length) + token := subTypeMatch.FindStringSubmatch(scanner.Text()) + if len(token) != 2 { + return nil, fmt.Errorf("Invalid line: %s (len=%d)", scanner.Text(), len(token)) + } + key.SubType = token[1] + + // Next line should be the key ID, no prefix + if !scanner.Scan() { + return nil, fmt.Errorf("Invalid output from gpg, end of text after primary key") + } + + token = keyIdMatch.FindStringSubmatch(scanner.Text()) + if len(token) != 2 { + return nil, fmt.Errorf("Invalid output from gpg, no key ID for primary key") + } + + key.Fingerprint = token[1] + // KeyID is just the last bytes of the fingerprint + key.KeyID = token[1][24:] + + if curKey == nil { + curKey = &key + } + + // Next line should be UID + if !scanner.Scan() { + return nil, fmt.Errorf("Invalid output from gpg, end of text after key ID") + } + + if !strings.HasPrefix(scanner.Text(), "uid ") { + return nil, fmt.Errorf("Invalid output from gpg, no identity for primary key") + } + + token = uidMatch.FindStringSubmatch(scanner.Text()) + + if len(token) < 3 { + return nil, fmt.Errorf("Malformed identity line: %s (len=%d)", scanner.Text(), len(token)) + } + + // Store trust level + key.Trust = token[1] + + // Identity - we are only interested in the first uid + key.Owner = token[2] + } + } + + // Also store the last processed key into our list to be returned + if curKey != nil { + keys = append(keys, curKey) + } + + // We need to get the final key for each imported key, so we run --export on each key + for _, key := range keys { + cmd := exec.Command("gpg", "-a", "--export", key.KeyID) + cmd.Env = getGPGEnviron() + + out, err := executil.Run(cmd) + if err != nil { + return nil, err + } + key.KeyData = out + } + + return keys, nil +} + +// ParsePGPCommitSignature parses the output of "git verify-commit" and returns the result +func ParseGitCommitVerification(signature string) (PGPVerifyResult, error) { + result := PGPVerifyResult{Result: VerifyResultUnknown} + parseOk := false + linesParsed := 0 + + scanner := bufio.NewScanner(strings.NewReader(signature)) + for scanner.Scan() && linesParsed < MaxVerificationLinesToParse { + linesParsed += 1 + + // Indicating the beginning of a signature + start := verificationStartMatch.FindStringSubmatch(scanner.Text()) + if len(start) == 2 { + result.Date = start[1] + if !scanner.Scan() { + return PGPVerifyResult{}, fmt.Errorf("Unexpected end-of-file while parsing commit verification output.") + } + + linesParsed += 1 + + // What key has made the signature? + keyID := verificationKeyIDMatch.FindStringSubmatch(scanner.Text()) + if len(keyID) != 3 { + return PGPVerifyResult{}, fmt.Errorf("Could not parse key ID of commit verification output.") + } + + result.Cipher = keyID[1] + result.KeyID = KeyID(keyID[2]) + if result.KeyID == "" { + return PGPVerifyResult{}, fmt.Errorf("Invalid PGP key ID found in verification result: %s", result.KeyID) + } + + // What was the result of signature verification? + if !scanner.Scan() { + return PGPVerifyResult{}, fmt.Errorf("Unexpected end-of-file while parsing commit verification output.") + } + + linesParsed += 1 + + if strings.HasPrefix(scanner.Text(), "gpg: Can't check signature: ") { + result.Result = VerifyResultInvalid + result.Identity = "unknown" + result.Trust = TrustUnknown + result.Message = scanner.Text() + } else { + sigState := verificationStatusMatch.FindStringSubmatch(scanner.Text()) + if len(sigState) != 4 { + return PGPVerifyResult{}, fmt.Errorf("Could not parse result of verify operation, check logs for more information.") + } + + switch strings.ToLower(sigState[1]) { + case "good": + result.Result = VerifyResultGood + case "bad": + result.Result = VerifyResultBad + default: + result.Result = VerifyResultInvalid + } + result.Identity = sigState[2] + + // Did we catch a valid trust? + if _, ok := pgpTrustLevels[sigState[3]]; ok { + result.Trust = sigState[3] + } else { + result.Trust = TrustUnknown + } + result.Message = "Success verifying the commit signature." + } + + // No more data to parse here + parseOk = true + break + } + } + + if parseOk && linesParsed < MaxVerificationLinesToParse { + // Operation successfull - return result + return result, nil + } else if linesParsed >= MaxVerificationLinesToParse { + // Too many output lines, return error + return PGPVerifyResult{}, fmt.Errorf("Too many lines of gpg verify-commit output, abort.") + } else { + // No data found, return error + return PGPVerifyResult{}, fmt.Errorf("Could not parse output of verify-commit, no verification data found.") + } +} + +// SyncKeyRingFromDirectory will sync the GPG keyring with files in a directory. This is a one-way sync, +// with the configuration being the leading information. +// Files must have a file name matching their Key ID. Keys that are found in the directory but are not +// in the keyring will be installed to the keyring, files that exist in the keyring but do not exist in +// the directory will be deleted. +func SyncKeyRingFromDirectory(basePath string) ([]string, []string, error) { + configured := make(map[string]interface{}) + newKeys := make([]string, 0) + fingerprints := make([]string, 0) + removedKeys := make([]string, 0) + st, err := os.Stat(basePath) + + if err != nil { + return nil, nil, err + } + if !st.IsDir() { + return nil, nil, fmt.Errorf("%s is not a directory", basePath) + } + + // Collect configuration, i.e. files in basePath + err = filepath.Walk(basePath, func(path string, fi os.FileInfo, err error) error { + if IsShortKeyID(fi.Name()) { + configured[fi.Name()] = true + } + return nil + }) + if err != nil { + return nil, nil, err + } + + // Collect GPG keys installed in the key ring + installed := make(map[string]*appsv1.GnuPGPublicKey) + keys, err := GetInstalledPGPKeys(nil) + if err != nil { + return nil, nil, err + } + for _, v := range keys { + installed[v.KeyID] = v + } + + // First, add all keys that are found in the configuration but are not yet in the keyring + for key := range configured { + if _, ok := installed[key]; !ok { + addedKey, err := ImportPGPKeys(path.Join(basePath, key)) + if err != nil { + return nil, nil, err + } + if len(addedKey) != 1 { + return nil, nil, fmt.Errorf("Invalid key found in %s", path.Join(basePath, key)) + } + importedKey, err := GetInstalledPGPKeys([]string{addedKey[0].KeyID}) + if err != nil { + return nil, nil, err + } else if len(importedKey) != 1 { + return nil, nil, fmt.Errorf("Could not get details of imported key ID %s", importedKey) + } + newKeys = append(newKeys, key) + fingerprints = append(fingerprints, importedKey[0].Fingerprint) + } + } + + // Delete all keys from the keyring that are not found in the configuration anymore. + for key := range installed { + secret, err := IsSecretKey(key) + if err != nil { + return nil, nil, err + } + if _, ok := configured[key]; !ok && !secret { + err := DeletePGPKey(key) + if err != nil { + return nil, nil, err + } + removedKeys = append(removedKeys, key) + } + } + + // Update owner trust for new keys + if len(fingerprints) > 0 { + _ = SetPGPTrustLevelById(fingerprints, TrustUltimate) + } + + return newKeys, removedKeys, err +} diff --git a/util/gpg/gpg_test.go b/util/gpg/gpg_test.go new file mode 100644 index 0000000000000..7a4bc9f9350ff --- /dev/null +++ b/util/gpg/gpg_test.go @@ -0,0 +1,580 @@ +package gpg + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/argoproj/argo-cd/common" + "github.com/argoproj/argo-cd/test" +) + +const ( + longKeyID = "5DE3E0509C47EA3CF04A42D34AEE18F83AFDEB23" + shortKeyID = "4AEE18F83AFDEB23" +) + +var syncTestSources = map[string]string{ + "F7842A5CEAA9C0B1": "testdata/janedoe.asc", + "FDC79815400D88A9": "testdata/johndoe.asc", + "4AEE18F83AFDEB23": "testdata/github.asc", +} + +// Helper function to create temporary GNUPGHOME +func initTempDir() string { + p, err := ioutil.TempDir("", "gpg-test") + if err != nil { + // makes no sense to continue test without temp dir + panic(err.Error()) + } + fmt.Printf("-> Using %s as GNUPGHOME\n", p) + os.Setenv(common.EnvGnuPGHome, p) + return p +} + +func Test_IsGPGEnabled(t *testing.T) { + os.Setenv("ARGOCD_GPG_ENABLED", "true") + assert.True(t, IsGPGEnabled()) + os.Setenv("ARGOCD_GPG_ENABLED", "false") + assert.False(t, IsGPGEnabled()) + os.Setenv("ARGOCD_GPG_ENABLED", "") + assert.True(t, IsGPGEnabled()) +} + +func Test_GPG_InitializeGnuPG(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + // First run should initialize fine + err := InitializeGnuPG() + assert.NoError(t, err) + + // We should have exactly one public key with ultimate trust (our own) in the keyring + keys, err := GetInstalledPGPKeys(nil) + assert.NoError(t, err) + assert.Len(t, keys, 1) + assert.Equal(t, keys[0].Trust, "ultimate") + + // Second run should return error + err = InitializeGnuPG() + assert.Error(t, err) + assert.Contains(t, err.Error(), "already initialized") + + // GNUPGHOME is a file - we need to error out + f, err := ioutil.TempFile("", "gpg-test") + assert.NoError(t, err) + defer os.Remove(f.Name()) + + os.Setenv(common.EnvGnuPGHome, f.Name()) + err = InitializeGnuPG() + assert.Error(t, err) + assert.Contains(t, err.Error(), "does not point to a directory") + + // Unaccessible GNUPGHOME + p = initTempDir() + defer os.RemoveAll(p) + fp := fmt.Sprintf("%s/gpg", p) + err = os.Mkdir(fp, 0000) + if err != nil { + panic(err.Error()) + } + if err != nil { + panic(err.Error()) + } + os.Setenv(common.EnvGnuPGHome, fp) + err = InitializeGnuPG() + assert.Error(t, err) + // Restore permissions so path can be deleted + err = os.Chmod(fp, 0700) + if err != nil { + panic(err.Error()) + } + + // GNUPGHOME with too wide permissions + p = initTempDir() + defer os.RemoveAll(p) + err = os.Chmod(p, 0777) + if err != nil { + panic(err.Error()) + } + os.Setenv(common.EnvGnuPGHome, p) + err = InitializeGnuPG() + assert.Error(t, err) + +} + +func Test_GPG_KeyManagement(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + err := InitializeGnuPG() + assert.NoError(t, err) + + // Import a single good key + keys, err := ImportPGPKeys("testdata/github.asc") + assert.NoError(t, err) + assert.Len(t, keys, 1) + assert.Equal(t, "4AEE18F83AFDEB23", keys[0].KeyID) + assert.Contains(t, keys[0].Owner, "noreply@github.com") + assert.Equal(t, "unknown", keys[0].Trust) + assert.Equal(t, "unknown", keys[0].SubType) + + kids := make([]string, 0) + importedKeyId := keys[0].KeyID + + // We should have a total of 2 keys in the keyring now + { + keys, err := GetInstalledPGPKeys(nil) + assert.NoError(t, err) + assert.Len(t, keys, 2) + } + + // We should now have that key in our keyring with unknown trust (trustdb not updated) + { + keys, err := GetInstalledPGPKeys([]string{importedKeyId}) + assert.NoError(t, err) + assert.Len(t, keys, 1) + assert.Equal(t, "4AEE18F83AFDEB23", keys[0].KeyID) + assert.Contains(t, keys[0].Owner, "noreply@github.com") + assert.Equal(t, "unknown", keys[0].Trust) + assert.Equal(t, "rsa2048", keys[0].SubType) + kids = append(kids, keys[0].Fingerprint) + } + + assert.Len(t, kids, 1) + + // Set trust level for our key and check the result + { + err := SetPGPTrustLevelById(kids, "ultimate") + assert.NoError(t, err) + keys, err := GetInstalledPGPKeys(kids) + assert.NoError(t, err) + assert.Len(t, keys, 1) + assert.Equal(t, kids[0], keys[0].Fingerprint) + assert.Equal(t, "ultimate", keys[0].Trust) + } + + // Import garbage - error expected + keys, err = ImportPGPKeys("testdata/garbage.asc") + assert.Error(t, err) + assert.Len(t, keys, 0) + + // We should still have a total of 2 keys in the keyring now + { + keys, err := GetInstalledPGPKeys(nil) + assert.NoError(t, err) + assert.Len(t, keys, 2) + } + + // Delete previously imported public key + { + err := DeletePGPKey(importedKeyId) + assert.NoError(t, err) + keys, err := GetInstalledPGPKeys(nil) + assert.NoError(t, err) + assert.Len(t, keys, 1) + } + + // Delete non-existing key + { + err := DeletePGPKey(importedKeyId) + assert.Error(t, err) + } + + // Import multiple keys + { + keys, err := ImportPGPKeys("testdata/multi.asc") + assert.NoError(t, err) + assert.Len(t, keys, 2) + assert.Contains(t, keys[0].Owner, "john.doe@example.com") + assert.Contains(t, keys[1].Owner, "jane.doe@example.com") + } + + // Check if they were really imported + { + keys, err := GetInstalledPGPKeys(nil) + assert.NoError(t, err) + assert.Len(t, keys, 3) + } + +} + +func Test_ImportPGPKeysFromString(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + err := InitializeGnuPG() + assert.NoError(t, err) + + // Import a single good key + keys, err := ImportPGPKeysFromString(test.MustLoadFileToString("testdata/github.asc")) + assert.NoError(t, err) + assert.Len(t, keys, 1) + assert.Equal(t, "4AEE18F83AFDEB23", keys[0].KeyID) + assert.Contains(t, keys[0].Owner, "noreply@github.com") + assert.Equal(t, "unknown", keys[0].Trust) + assert.Equal(t, "unknown", keys[0].SubType) + +} + +func Test_ValidateGPGKeysFromString(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + err := InitializeGnuPG() + assert.NoError(t, err) + + { + keyData := test.MustLoadFileToString("testdata/github.asc") + keys, err := ValidatePGPKeysFromString(keyData) + assert.NoError(t, err) + assert.Len(t, keys, 1) + } + + { + keyData := test.MustLoadFileToString("testdata/multi.asc") + keys, err := ValidatePGPKeysFromString(keyData) + assert.NoError(t, err) + assert.Len(t, keys, 2) + } + +} + +func Test_ValidateGPGKeys(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + err := InitializeGnuPG() + assert.NoError(t, err) + + // Validation good case - 1 key + { + keys, err := ValidatePGPKeys("testdata/github.asc") + assert.NoError(t, err) + assert.Len(t, keys, 1) + assert.Contains(t, keys, "4AEE18F83AFDEB23") + } + + // Validation bad case + { + keys, err := ValidatePGPKeys("testdata/garbage.asc") + assert.Error(t, err) + assert.Len(t, keys, 0) + } + + // We should still have a total of 1 keys in the keyring now + { + keys, err := GetInstalledPGPKeys(nil) + assert.NoError(t, err) + assert.Len(t, keys, 1) + } +} + +func Test_GPG_ParseGitCommitVerification(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + err := InitializeGnuPG() + assert.NoError(t, err) + + keys, err := ImportPGPKeys("testdata/github.asc") + assert.NoError(t, err) + assert.Len(t, keys, 1) + + // Good case + { + c, err := ioutil.ReadFile("testdata/good_signature.txt") + if err != nil { + panic(err.Error()) + } + res, err := ParseGitCommitVerification(string(c)) + assert.NoError(t, err) + assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID) + assert.Equal(t, "RSA", res.Cipher) + assert.Equal(t, "ultimate", res.Trust) + assert.Equal(t, "Wed Feb 26 23:22:34 2020 CET", res.Date) + assert.Equal(t, VerifyResultGood, res.Result) + } + + // Signature with unknown key - considered invalid + { + c, err := ioutil.ReadFile("testdata/unknown_signature.txt") + if err != nil { + panic(err.Error()) + } + res, err := ParseGitCommitVerification(string(c)) + assert.NoError(t, err) + assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID) + assert.Equal(t, "RSA", res.Cipher) + assert.Equal(t, TrustUnknown, res.Trust) + assert.Equal(t, "Mon Aug 26 20:59:48 2019 CEST", res.Date) + assert.Equal(t, VerifyResultInvalid, res.Result) + } + + // Bad signature with known key + { + c, err := ioutil.ReadFile("testdata/bad_signature_bad.txt") + if err != nil { + panic(err.Error()) + } + res, err := ParseGitCommitVerification(string(c)) + assert.NoError(t, err) + assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID) + assert.Equal(t, "RSA", res.Cipher) + assert.Equal(t, "ultimate", res.Trust) + assert.Equal(t, "Wed Feb 26 23:22:34 2020 CET", res.Date) + assert.Equal(t, VerifyResultBad, res.Result) + } + + // Bad case: Manipulated/invalid clear text signature + { + c, err := ioutil.ReadFile("testdata/bad_signature_manipulated.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Could not parse output") + } + + // Bad case: Incomplete signature data #1 + { + c, err := ioutil.ReadFile("testdata/bad_signature_preeof1.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "end-of-file") + } + + // Bad case: Incomplete signature data #2 + { + c, err := ioutil.ReadFile("testdata/bad_signature_preeof2.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "end-of-file") + } + + // Bad case: No signature data #1 + { + c, err := ioutil.ReadFile("testdata/bad_signature_nodata.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no verification data found") + } + + // Bad case: Malformed signature data #1 + { + c, err := ioutil.ReadFile("testdata/bad_signature_malformed1.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no verification data found") + } + + // Bad case: Malformed signature data #2 + { + c, err := ioutil.ReadFile("testdata/bad_signature_malformed2.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Could not parse key ID") + } + + // Bad case: Malformed signature data #3 + { + c, err := ioutil.ReadFile("testdata/bad_signature_malformed3.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Could not parse result of verify") + } + + // Bad case: Invalid key ID in signature + { + c, err := ioutil.ReadFile("testdata/bad_signature_badkeyid.txt") + if err != nil { + panic(err.Error()) + } + _, err = ParseGitCommitVerification(string(c)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Invalid PGP key ID") + } +} + +func Test_GetGnuPGHomePath(t *testing.T) { + { + os.Setenv(common.EnvGnuPGHome, "") + p := common.GetGnuPGHomePath() + assert.Equal(t, common.DefaultGnuPgHomePath, p) + } + { + os.Setenv(common.EnvGnuPGHome, "/tmp/gpghome") + p := common.GetGnuPGHomePath() + assert.Equal(t, "/tmp/gpghome", p) + } +} + +func Test_KeyID(t *testing.T) { + // Good case - long key ID (aka fingerprint) to short key ID + { + res := KeyID(longKeyID) + assert.Equal(t, shortKeyID, res) + } + // Good case - short key ID remains same + { + res := KeyID(shortKeyID) + assert.Equal(t, shortKeyID, res) + } + // Bad case - key ID too short + { + keyID := "AEE18F83AFDEB23" + res := KeyID(keyID) + assert.Empty(t, res) + } + // Bad case - key ID too long + { + keyID := "5DE3E0509C47EA3CF04A42D34AEE18F83AFDEB2323" + res := KeyID(keyID) + assert.Empty(t, res) + } + // Bad case - right length, but not hex string + { + keyID := "abcdefghijklmn" + res := KeyID(keyID) + assert.Empty(t, res) + } +} + +func Test_IsShortKeyID(t *testing.T) { + assert.True(t, IsShortKeyID(shortKeyID)) + assert.False(t, IsShortKeyID(longKeyID)) + assert.False(t, IsShortKeyID("ab")) +} +func Test_IsLongKeyID(t *testing.T) { + assert.True(t, IsLongKeyID(longKeyID)) + assert.False(t, IsLongKeyID(shortKeyID)) + assert.False(t, IsLongKeyID(longKeyID+"a")) +} + +func Test_isHexString(t *testing.T) { + assert.True(t, isHexString("ab0099")) + assert.True(t, isHexString("AB0099")) + assert.False(t, isHexString("foobar")) +} + +func Test_IsSecretKey(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + // First run should initialize fine + err := InitializeGnuPG() + assert.NoError(t, err) + + // We should have exactly one public key with ultimate trust (our own) in the keyring + keys, err := GetInstalledPGPKeys(nil) + assert.NoError(t, err) + assert.Len(t, keys, 1) + assert.Equal(t, keys[0].Trust, "ultimate") + + { + secret, err := IsSecretKey(keys[0].KeyID) + assert.NoError(t, err) + assert.True(t, secret) + } + + { + secret, err := IsSecretKey("invalid") + assert.NoError(t, err) + assert.False(t, secret) + } + +} + +func Test_SyncKeyRingFromDirectory(t *testing.T) { + p := initTempDir() + defer os.RemoveAll(p) + + // First run should initialize fine + err := InitializeGnuPG() + assert.NoError(t, err) + + tempDir, err := ioutil.TempDir("", "gpg-sync-test") + if err != nil { + panic(err.Error()) + } + defer os.RemoveAll(tempDir) + + { + new, removed, err := SyncKeyRingFromDirectory(tempDir) + assert.NoError(t, err) + assert.Len(t, new, 0) + assert.Len(t, removed, 0) + } + + { + for k, v := range syncTestSources { + src, err := os.Open(v) + if err != nil { + panic(err.Error()) + } + defer src.Close() + dst, err := os.Create(path.Join(tempDir, k)) + if err != nil { + panic(err.Error()) + } + defer dst.Close() + _, err = io.Copy(dst, src) + if err != nil { + panic(err.Error()) + } + dst.Close() + } + + new, removed, err := SyncKeyRingFromDirectory(tempDir) + assert.NoError(t, err) + assert.Len(t, new, 3) + assert.Len(t, removed, 0) + + installed, err := GetInstalledPGPKeys(new) + assert.NoError(t, err) + for _, k := range installed { + assert.Contains(t, new, k.KeyID) + } + } + + { + err := os.Remove(path.Join(tempDir, "4AEE18F83AFDEB23")) + if err != nil { + panic(err.Error()) + } + new, removed, err := SyncKeyRingFromDirectory(tempDir) + assert.NoError(t, err) + assert.Len(t, new, 0) + assert.Len(t, removed, 1) + + installed, err := GetInstalledPGPKeys(new) + assert.NoError(t, err) + for _, k := range installed { + assert.NotEqual(t, k.KeyID, removed[0]) + } + } +} diff --git a/util/gpg/testdata/bad_signature_bad.txt b/util/gpg/testdata/bad_signature_bad.txt new file mode 100644 index 0000000000000..fabce8968443d --- /dev/null +++ b/util/gpg/testdata/bad_signature_bad.txt @@ -0,0 +1,3 @@ +gpg: Signature made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key 4AEE18F83AFDEB23 +gpg: BAD signature from "GitHub (web-flow commit signing) " [ultimate] diff --git a/util/gpg/testdata/bad_signature_badkeyid.txt b/util/gpg/testdata/bad_signature_badkeyid.txt new file mode 100644 index 0000000000000..ca4f04b2d4909 --- /dev/null +++ b/util/gpg/testdata/bad_signature_badkeyid.txt @@ -0,0 +1,3 @@ +gpg: Signature made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key 5F4AEE18F83AFDEB23 +gpg: Good signature from "GitHub (web-flow commit signing) " [ultimate] diff --git a/util/gpg/testdata/bad_signature_malformed1.txt b/util/gpg/testdata/bad_signature_malformed1.txt new file mode 100644 index 0000000000000..c18cded3176a6 --- /dev/null +++ b/util/gpg/testdata/bad_signature_malformed1.txt @@ -0,0 +1,3 @@ +gpg: Signature was made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key 4AEE18F83AFDEB23 +gpg: Good signature from "GitHub (web-flow commit signing) " [ultimate] diff --git a/util/gpg/testdata/bad_signature_malformed2.txt b/util/gpg/testdata/bad_signature_malformed2.txt new file mode 100644 index 0000000000000..8d3c3b27cfc7c --- /dev/null +++ b/util/gpg/testdata/bad_signature_malformed2.txt @@ -0,0 +1,3 @@ +gpg: Signature made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key noreply@github.com +gpg: Good signature from "GitHub (web-flow commit signing) " [ultimate] diff --git a/util/gpg/testdata/bad_signature_malformed3.txt b/util/gpg/testdata/bad_signature_malformed3.txt new file mode 100644 index 0000000000000..f2aef460f1490 --- /dev/null +++ b/util/gpg/testdata/bad_signature_malformed3.txt @@ -0,0 +1,3 @@ +gpg: Signature made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key 4AEE18F83AFDEB23 +gpg: Good signature from "GitHub (web-flow commit signing)" " [ultimate] diff --git a/util/gpg/testdata/bad_signature_manipulated.txt b/util/gpg/testdata/bad_signature_manipulated.txt new file mode 100644 index 0000000000000..26f8e767fc5ce --- /dev/null +++ b/util/gpg/testdata/bad_signature_manipulated.txt @@ -0,0 +1,6 @@ +gpg: CRC error; AF65FD - 3ABB26 +gpg: [don't know]: invalid packet (ctb=78) +gpg: no signature found +gpg: the signature could not be verified. +Please remember that the signature file (.sig or .asc) +should be the first file given on the command line. diff --git a/util/gpg/testdata/bad_signature_nodata.txt b/util/gpg/testdata/bad_signature_nodata.txt new file mode 100644 index 0000000000000..28bff5a5e5375 --- /dev/null +++ b/util/gpg/testdata/bad_signature_nodata.txt @@ -0,0 +1,3 @@ +Lorem ipsum +Lorem ipsum +Lorem ipsum diff --git a/util/gpg/testdata/bad_signature_preeof1.txt b/util/gpg/testdata/bad_signature_preeof1.txt new file mode 100644 index 0000000000000..b6c0f7c2cb6e7 --- /dev/null +++ b/util/gpg/testdata/bad_signature_preeof1.txt @@ -0,0 +1,2 @@ +gpg: Signature made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key 4AEE18F83AFDEB23 diff --git a/util/gpg/testdata/bad_signature_preeof2.txt b/util/gpg/testdata/bad_signature_preeof2.txt new file mode 100644 index 0000000000000..e02e945d95d33 --- /dev/null +++ b/util/gpg/testdata/bad_signature_preeof2.txt @@ -0,0 +1 @@ +gpg: Signature made Wed Feb 26 23:22:34 2020 CET diff --git a/util/gpg/testdata/garbage.asc b/util/gpg/testdata/garbage.asc new file mode 100644 index 0000000000000..24f77b8757a13 --- /dev/null +++ b/util/gpg/testdata/garbage.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFmUaEEBCACzXTDt6ZnyaVtueZASBzgnAmK13q9Urgch+sKYeIhdymjuMQta +SQzvYjsE4I34To4UdE9KA97wrQjGoz2Bx72WDLyWwctD3DKQtYeHXswXXtXwKfjQ +7Fy4+Bf5IPh76dA8NJ6UtjjLIDlKqdxLW4atHe6xWFaJ+XdLUtsAroZcXBeWDCPa +buXCDscJcLJRKZVc62gOZXXtPfoHqvUPp3nuLA4YjH9bphbrMWMf810Wxz9JTd3v +yWgGqNY0zbBqeZoGv+TuExlRHT8ASGFS9SVDABEBAAG0NUdpdEh1YiAod2ViLWZs +b3cgY29tbWl0IHNpZ25pbmcpIDxub3JlcGx5QGdpdGh1Yi5jb20+iQEiBBMBCAAW +BQJZlGhBCRBK7hj4Ov3rIwIbAwIZAQAAmQEH/iATWFmi2oxlBh3wAsySNCNV4IPf +DDMeh6j80WT7cgoX7V7xqJOxrfrqPEthQ3hgHIm7b5MPQlUr2q+UPL22t/I+ESF6 +9b0QWLFSMJbMSk+BXkvSjH9q8jAO0986/pShPV5DU2sMxnx4LfLfHNhTzjXKokws ++8ptJ8uhMNIDXfXuzkZHIxoXk3rNcjDN5c5X+sK8UBRH092BIJWCOfaQt7v7wig5 +4Ra28pM9GbHKXVNxmdLpCFyzvyMuCmINYYADsC848QQFFwnd4EQnupo6QvhEVx1O +j7wDwvuH5dCrLuLwtwXaQh0onG4583p0LGms2Mf5F+Ick6o/4peOlBoZz48= +=Bvzs +-----END PGP PUBLIC KEY BLOCK----- diff --git a/util/gpg/testdata/github.asc b/util/gpg/testdata/github.asc new file mode 100644 index 0000000000000..bd79014b2ae84 --- /dev/null +++ b/util/gpg/testdata/github.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFmUaEEBCACzXTDt6ZnyaVtueZASBzgnAmK13q9Urgch+sKYeIhdymjuMQta +x15OklctmrZtqre5kwPUosG3/B2/ikuPYElcHgGPL4uL5Em6S5C/oozfkYzhwRrT +SQzvYjsE4I34To4UdE9KA97wrQjGoz2Bx72WDLyWwctD3DKQtYeHXswXXtXwKfjQ +7Fy4+Bf5IPh76dA8NJ6UtjjLIDlKqdxLW4atHe6xWFaJ+XdLUtsAroZcXBeWDCPa +buXCDscJcLJRKZVc62gOZXXtPfoHqvUPp3nuLA4YjH9bphbrMWMf810Wxz9JTd3v +yWgGqNY0zbBqeZoGv+TuExlRHT8ASGFS9SVDABEBAAG0NUdpdEh1YiAod2ViLWZs +b3cgY29tbWl0IHNpZ25pbmcpIDxub3JlcGx5QGdpdGh1Yi5jb20+iQEiBBMBCAAW +BQJZlGhBCRBK7hj4Ov3rIwIbAwIZAQAAmQEH/iATWFmi2oxlBh3wAsySNCNV4IPf +DDMeh6j80WT7cgoX7V7xqJOxrfrqPEthQ3hgHIm7b5MPQlUr2q+UPL22t/I+ESF6 +9b0QWLFSMJbMSk+BXkvSjH9q8jAO0986/pShPV5DU2sMxnx4LfLfHNhTzjXKokws ++8ptJ8uhMNIDXfXuzkZHIxoXk3rNcjDN5c5X+sK8UBRH092BIJWCOfaQt7v7wig5 +4Ra28pM9GbHKXVNxmdLpCFyzvyMuCmINYYADsC848QQFFwnd4EQnupo6QvhEVx1O +j7wDwvuH5dCrLuLwtwXaQh0onG4583p0LGms2Mf5F+Ick6o/4peOlBoZz48= +=Bvzs +-----END PGP PUBLIC KEY BLOCK----- diff --git a/util/gpg/testdata/good_signature.txt b/util/gpg/testdata/good_signature.txt new file mode 100644 index 0000000000000..577a722bc1675 --- /dev/null +++ b/util/gpg/testdata/good_signature.txt @@ -0,0 +1,3 @@ +gpg: Signature made Wed Feb 26 23:22:34 2020 CET +gpg: using RSA key 4AEE18F83AFDEB23 +gpg: Good signature from "GitHub (web-flow commit signing) " [ultimate] diff --git a/util/gpg/testdata/janedoe.asc b/util/gpg/testdata/janedoe.asc new file mode 100644 index 0000000000000..777fe6e8c8f5b --- /dev/null +++ b/util/gpg/testdata/janedoe.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBF5izVcBCADKkNZwGmtcTR7TN1tuC326+oXNewWRraKdnxWiKXW1gUROBDiW +Pic9hImYYjkyt6dz4DkAB/qJfAiRTZG/zz/qnTgbrzK9j3v4TlBTUcTtCI4fF/Sh +zutKpaIfWFDelKSIoWRh/gY6LrtnXm+PRLTckzQxUP71HrHlFFk3462+Ph+7V3z5 +PrUZvbv+wJ3U5GdhhYEIBpq2fkvv2K9l9MFVWXcH7mDLxX7p/Q8OaHaSsdTtpBpk +y4IuA0RQiej0gXAEPuoO/TXKwtZ6G7eFjtcndomM2H3N7oYqZZuNW8lU+zcaV8HW +KlwNZFvnkRAO05zCtN8ljUTWkZwM8k6BOp3bABEBAAG0H0phbmUgRG9lIDxqYW5l +LmRvZUBleGFtcGxlLmNvbT6JAVQEEwEIAD4WIQQM1Ty6UL70MGRjStf3hCpc6qnA +sQUCXmLNVwIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD3hCpc +6qnAsT7uCACeKa0jKSzGmjVhxzTT8uO1bxZXzTLi2vDQcqFBVYUBe0TXgf9I8+0s +WkvZfvZw9Mju3bxY+Tp1/e7+nKsEkQO/7rRureOa7OF/D5jJNX1QUNqUFF6LCPAB +P5RroHS3uGfdCHKyj84jrZPAhTDPMyYlrWkv8EX2YOT6dlnxgElIdc3WcwJSAtFT +WQXoY/kHmjoUe8c8NJFN1nwEHzbKtjsnkcXvs7XruUhhmqsizyCyrIS9We4Sl5r+ +4zKi10FKoN/kzCjU3EHOFiQ8/l5rKTMM1lAN4q3Wyq2xeqyJ+UDx7hOGnnxKjeX9 +uLay5cAy7XhOwghIQKCXtcd3T/EzlYQNuQENBF5izVcBCACiADPuJIRFkIuMLiov +rWCAtlXt3OyyZrchtRDzxLJW6nL6vaMoJ7nUabD6mlv9mfWRLG4exUID6632/mXb +lVcPYU4ZQM9HFutwi8cgq2SuiX/UJM0deJzmiQKxMNO4hUf7eQU7227jRdxkWaOj +zN7xdayH6yldVyrPWQM8i4qmpsGPZ3/EYswDhxcPYPhkA1gW1tgaUxWf/k9U1+GY +myaAI71pImRExUIc0pIv44IdGQRU3iPusgljDDXgPVhwF1EmAqFQ6aIM32h6x3WM +T6u6OtWfGUksG3RBv3M380Tegppzk2X+2V38YaJH4u/jNUXhwbu/9yR4xuA02/Z1 +E44bABEBAAGJATwEGAEIACYWIQQM1Ty6UL70MGRjStf3hCpc6qnAsQUCXmLNVwIb +DAUJA8JnAAAKCRD3hCpc6qnAsWb/CACKcrLYF4yK+vhdlTJw65znSBjIw16iH5SV +yd+z3MmiQzlqpjxfMg7iJG1wDNl4wDRa3QipRrVhKvc8wRuUK7xUKUcbIJDBmoTc +Mj5iicPucr1WQvv9OY3wXBBoWxdPgxWRBglbuZmp7s2D3ixbrd3nxGisFGXIkOAe +WtOibFHnnaKwsM+xqpYDwn4PL/DYWuh0gT5RW6JG1PWdGWMr1CHjV8VyPD3dR59l +976sRGz2oyu5gefZHtRecI65/BHtKtdf6ZZaonNXilK1XVrD2hahn0CKwumeQtS4 +kx/hB+SPrcCgCdUD7FFlMA3kGZ7rBKMkHVxoOondTAJFUw4TAE6j +=ygiZ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/util/gpg/testdata/johndoe.asc b/util/gpg/testdata/johndoe.asc new file mode 100644 index 0000000000000..c268fb43fe675 --- /dev/null +++ b/util/gpg/testdata/johndoe.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBF5izSABCADAED7eSbx+ol3TLt/fJ6UZciaItts4Rar83bj8LPTFZebWTHzy +m0zoNdU3UrH3I8iWhoUUE1voqp2Hs3GEX3fHK70BodhGkGl5W931l8yYqTVlLYhE +8MxwWZKwh3phK6Wcm9GUEA3BQr5rNApWwUfgCK8NHRl2Kmb5ujmPgoap2RsH6Fpn +85gaCfUOvTV7jAZtY+LU84ZsVh0TcNoA4UieYHYWvXtYci9C0EkVbjpoRhZOkv5h +oQSBm/5Kfv+d7kZUluBsm1yyXfdHJBVuNYd7SpHe6PO3+eQ/JgqlRSfs1UBKYgx3 +Sxapy16hm8vVAzE9vnxB87z0+kS0uc0Ri+abABEBAAG0H0pvaG4gRG9lIDxqb2hu +LmRvZUBleGFtcGxlLmNvbT6JAVQEEwEIAD4WIQRG7spZs6IkAFi4SpP9x5gVQA2I +qQUCXmLNIAIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD9x5gV +QA2IqS84CACXKj4xJ+UAkh5q/M0jXC9JxzQu8JtVE7cGTTFyjLBApGmtfa5RtEct +QShhpdVhpuh2DhsySoza6acvwaP356HywFH64Q0MXo98XosEnSwab74k1yyd2QPR +u+kIskEbfs/j6e5uYpqf0tCvXsxIywktGcdvLE/98ISXqHS8R1uCuMrfWR9Rrz/b +8k4NY5u6IZCa+HmrZ7v3K4s1XaHbSJaz5MzJI2kFT6Ai485KBf7Iof5llr9x5U0L +rEiH1u2xIh64WvqO6u6xqxas1ewzuI6tGECU2sllZxPIt6/onCZy9LnOjJOhEAyT +P7N+q5jsF+NvqvCame9hmYDSfUv6TP9IuQENBF5izSABCACv6y8rmRC0otzl7A9p +yfoNH9FNpLaiYuT6XUMSSC97TG1jjPZ3+6TP1Ff6nEwDxf57zRq8yJZO8LMRXwyA +kIT6ZPB9lY4Z6qy1TZAd2/UVG6KR9kml+S/hOo2Y9WAz8tDpYM9rGieIW+LXcueK +lkI0TYS7FX49UFB/hXJMnnOhzZxihVo/g1rlAPLsxE2i/1TVmDD0EOMwiuOBwoyN +UurJq41sXsxYZQFAjCbUfuvWgXjM/ir97Rr8Vca5SjGNf9C4yLsDGl/eKfKPLUwP +7cgnq/pSpVaWDEAb6DyU8ttY7zZQOQjT5Gwfggxzz9U4qUOOtFkQ6piQoe1Lyzi8 +6cHZABEBAAGJATwEGAEIACYWIQRG7spZs6IkAFi4SpP9x5gVQA2IqQUCXmLNIAIb +DAUJA8JnAAAKCRD9x5gVQA2IqX09CACTALlaIOxa9VlBrhaj5bHkMwXJG3DDDLm1 +9aJDJfwjqEnCFT7SCggZFCBpu3PqEkq8jHGC/gnWcDoPhWtMldBRVb3MjsxjOi9t +Lk39XcoQOgYo6aFMD1Ughbg+P2QrQwvLhtIl7134MUiB65IsDRLrjXkkMhVEe1Um +0yL4doZPxZ/jm+dGxtFWcAXWBTL4lzE3fWCwMmygiuxljLl9n67glsZG7isRVMfY +U9O8kAMRoMCiktnIe+Ecw1RmAmjgDmA/jTKPGuJRTj/WO5LtWwHUXa7jptJLU1tZ +kdXx0SzOArmDG0dMwggSm9ms4Z8FT+XXe1BWKV1jLTDqBRP1z/KD +=8jOZ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/util/gpg/testdata/multi.asc b/util/gpg/testdata/multi.asc new file mode 100644 index 0000000000000..ab4df016381c8 --- /dev/null +++ b/util/gpg/testdata/multi.asc @@ -0,0 +1,56 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBF5izSABCADAED7eSbx+ol3TLt/fJ6UZciaItts4Rar83bj8LPTFZebWTHzy +m0zoNdU3UrH3I8iWhoUUE1voqp2Hs3GEX3fHK70BodhGkGl5W931l8yYqTVlLYhE +8MxwWZKwh3phK6Wcm9GUEA3BQr5rNApWwUfgCK8NHRl2Kmb5ujmPgoap2RsH6Fpn +85gaCfUOvTV7jAZtY+LU84ZsVh0TcNoA4UieYHYWvXtYci9C0EkVbjpoRhZOkv5h +oQSBm/5Kfv+d7kZUluBsm1yyXfdHJBVuNYd7SpHe6PO3+eQ/JgqlRSfs1UBKYgx3 +Sxapy16hm8vVAzE9vnxB87z0+kS0uc0Ri+abABEBAAG0H0pvaG4gRG9lIDxqb2hu +LmRvZUBleGFtcGxlLmNvbT6JAVQEEwEIAD4WIQRG7spZs6IkAFi4SpP9x5gVQA2I +qQUCXmLNIAIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD9x5gV +QA2IqS84CACXKj4xJ+UAkh5q/M0jXC9JxzQu8JtVE7cGTTFyjLBApGmtfa5RtEct +QShhpdVhpuh2DhsySoza6acvwaP356HywFH64Q0MXo98XosEnSwab74k1yyd2QPR +u+kIskEbfs/j6e5uYpqf0tCvXsxIywktGcdvLE/98ISXqHS8R1uCuMrfWR9Rrz/b +8k4NY5u6IZCa+HmrZ7v3K4s1XaHbSJaz5MzJI2kFT6Ai485KBf7Iof5llr9x5U0L +rEiH1u2xIh64WvqO6u6xqxas1ewzuI6tGECU2sllZxPIt6/onCZy9LnOjJOhEAyT +P7N+q5jsF+NvqvCame9hmYDSfUv6TP9IuQENBF5izSABCACv6y8rmRC0otzl7A9p +yfoNH9FNpLaiYuT6XUMSSC97TG1jjPZ3+6TP1Ff6nEwDxf57zRq8yJZO8LMRXwyA +kIT6ZPB9lY4Z6qy1TZAd2/UVG6KR9kml+S/hOo2Y9WAz8tDpYM9rGieIW+LXcueK +lkI0TYS7FX49UFB/hXJMnnOhzZxihVo/g1rlAPLsxE2i/1TVmDD0EOMwiuOBwoyN +UurJq41sXsxYZQFAjCbUfuvWgXjM/ir97Rr8Vca5SjGNf9C4yLsDGl/eKfKPLUwP +7cgnq/pSpVaWDEAb6DyU8ttY7zZQOQjT5Gwfggxzz9U4qUOOtFkQ6piQoe1Lyzi8 +6cHZABEBAAGJATwEGAEIACYWIQRG7spZs6IkAFi4SpP9x5gVQA2IqQUCXmLNIAIb +DAUJA8JnAAAKCRD9x5gVQA2IqX09CACTALlaIOxa9VlBrhaj5bHkMwXJG3DDDLm1 +9aJDJfwjqEnCFT7SCggZFCBpu3PqEkq8jHGC/gnWcDoPhWtMldBRVb3MjsxjOi9t +Lk39XcoQOgYo6aFMD1Ughbg+P2QrQwvLhtIl7134MUiB65IsDRLrjXkkMhVEe1Um +0yL4doZPxZ/jm+dGxtFWcAXWBTL4lzE3fWCwMmygiuxljLl9n67glsZG7isRVMfY +U9O8kAMRoMCiktnIe+Ecw1RmAmjgDmA/jTKPGuJRTj/WO5LtWwHUXa7jptJLU1tZ +kdXx0SzOArmDG0dMwggSm9ms4Z8FT+XXe1BWKV1jLTDqBRP1z/KDmQENBF5izVcB +CADKkNZwGmtcTR7TN1tuC326+oXNewWRraKdnxWiKXW1gUROBDiWPic9hImYYjky +t6dz4DkAB/qJfAiRTZG/zz/qnTgbrzK9j3v4TlBTUcTtCI4fF/ShzutKpaIfWFDe +lKSIoWRh/gY6LrtnXm+PRLTckzQxUP71HrHlFFk3462+Ph+7V3z5PrUZvbv+wJ3U +5GdhhYEIBpq2fkvv2K9l9MFVWXcH7mDLxX7p/Q8OaHaSsdTtpBpky4IuA0RQiej0 +gXAEPuoO/TXKwtZ6G7eFjtcndomM2H3N7oYqZZuNW8lU+zcaV8HWKlwNZFvnkRAO +05zCtN8ljUTWkZwM8k6BOp3bABEBAAG0H0phbmUgRG9lIDxqYW5lLmRvZUBleGFt +cGxlLmNvbT6JAVQEEwEIAD4WIQQM1Ty6UL70MGRjStf3hCpc6qnAsQUCXmLNVwIb +AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD3hCpc6qnAsT7uCACe +Ka0jKSzGmjVhxzTT8uO1bxZXzTLi2vDQcqFBVYUBe0TXgf9I8+0sWkvZfvZw9Mju +3bxY+Tp1/e7+nKsEkQO/7rRureOa7OF/D5jJNX1QUNqUFF6LCPABP5RroHS3uGfd +CHKyj84jrZPAhTDPMyYlrWkv8EX2YOT6dlnxgElIdc3WcwJSAtFTWQXoY/kHmjoU +e8c8NJFN1nwEHzbKtjsnkcXvs7XruUhhmqsizyCyrIS9We4Sl5r+4zKi10FKoN/k +zCjU3EHOFiQ8/l5rKTMM1lAN4q3Wyq2xeqyJ+UDx7hOGnnxKjeX9uLay5cAy7XhO +wghIQKCXtcd3T/EzlYQNuQENBF5izVcBCACiADPuJIRFkIuMLiovrWCAtlXt3Oyy +ZrchtRDzxLJW6nL6vaMoJ7nUabD6mlv9mfWRLG4exUID6632/mXblVcPYU4ZQM9H +Futwi8cgq2SuiX/UJM0deJzmiQKxMNO4hUf7eQU7227jRdxkWaOjzN7xdayH6yld +VyrPWQM8i4qmpsGPZ3/EYswDhxcPYPhkA1gW1tgaUxWf/k9U1+GYmyaAI71pImRE +xUIc0pIv44IdGQRU3iPusgljDDXgPVhwF1EmAqFQ6aIM32h6x3WMT6u6OtWfGUks +G3RBv3M380Tegppzk2X+2V38YaJH4u/jNUXhwbu/9yR4xuA02/Z1E44bABEBAAGJ +ATwEGAEIACYWIQQM1Ty6UL70MGRjStf3hCpc6qnAsQUCXmLNVwIbDAUJA8JnAAAK +CRD3hCpc6qnAsWb/CACKcrLYF4yK+vhdlTJw65znSBjIw16iH5SVyd+z3MmiQzlq +pjxfMg7iJG1wDNl4wDRa3QipRrVhKvc8wRuUK7xUKUcbIJDBmoTcMj5iicPucr1W +Qvv9OY3wXBBoWxdPgxWRBglbuZmp7s2D3ixbrd3nxGisFGXIkOAeWtOibFHnnaKw +sM+xqpYDwn4PL/DYWuh0gT5RW6JG1PWdGWMr1CHjV8VyPD3dR59l976sRGz2oyu5 +gefZHtRecI65/BHtKtdf6ZZaonNXilK1XVrD2hahn0CKwumeQtS4kx/hB+SPrcCg +CdUD7FFlMA3kGZ7rBKMkHVxoOondTAJFUw4TAE6j +=DEtc +-----END PGP PUBLIC KEY BLOCK----- diff --git a/util/gpg/testdata/multi2.asc b/util/gpg/testdata/multi2.asc new file mode 100644 index 0000000000000..8e15508ff4949 --- /dev/null +++ b/util/gpg/testdata/multi2.asc @@ -0,0 +1,73 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBF5izSABCADAED7eSbx+ol3TLt/fJ6UZciaItts4Rar83bj8LPTFZebWTHzy +m0zoNdU3UrH3I8iWhoUUE1voqp2Hs3GEX3fHK70BodhGkGl5W931l8yYqTVlLYhE +8MxwWZKwh3phK6Wcm9GUEA3BQr5rNApWwUfgCK8NHRl2Kmb5ujmPgoap2RsH6Fpn +85gaCfUOvTV7jAZtY+LU84ZsVh0TcNoA4UieYHYWvXtYci9C0EkVbjpoRhZOkv5h +oQSBm/5Kfv+d7kZUluBsm1yyXfdHJBVuNYd7SpHe6PO3+eQ/JgqlRSfs1UBKYgx3 +Sxapy16hm8vVAzE9vnxB87z0+kS0uc0Ri+abABEBAAG0H0pvaG4gRG9lIDxqb2hu +LmRvZUBleGFtcGxlLmNvbT6JAVQEEwEIAD4WIQRG7spZs6IkAFi4SpP9x5gVQA2I +qQUCXmLNIAIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD9x5gV +QA2IqS84CACXKj4xJ+UAkh5q/M0jXC9JxzQu8JtVE7cGTTFyjLBApGmtfa5RtEct +QShhpdVhpuh2DhsySoza6acvwaP356HywFH64Q0MXo98XosEnSwab74k1yyd2QPR +u+kIskEbfs/j6e5uYpqf0tCvXsxIywktGcdvLE/98ISXqHS8R1uCuMrfWR9Rrz/b +8k4NY5u6IZCa+HmrZ7v3K4s1XaHbSJaz5MzJI2kFT6Ai485KBf7Iof5llr9x5U0L +rEiH1u2xIh64WvqO6u6xqxas1ewzuI6tGECU2sllZxPIt6/onCZy9LnOjJOhEAyT +P7N+q5jsF+NvqvCame9hmYDSfUv6TP9IuQENBF5izSABCACv6y8rmRC0otzl7A9p +yfoNH9FNpLaiYuT6XUMSSC97TG1jjPZ3+6TP1Ff6nEwDxf57zRq8yJZO8LMRXwyA +kIT6ZPB9lY4Z6qy1TZAd2/UVG6KR9kml+S/hOo2Y9WAz8tDpYM9rGieIW+LXcueK +lkI0TYS7FX49UFB/hXJMnnOhzZxihVo/g1rlAPLsxE2i/1TVmDD0EOMwiuOBwoyN +UurJq41sXsxYZQFAjCbUfuvWgXjM/ir97Rr8Vca5SjGNf9C4yLsDGl/eKfKPLUwP +7cgnq/pSpVaWDEAb6DyU8ttY7zZQOQjT5Gwfggxzz9U4qUOOtFkQ6piQoe1Lyzi8 +6cHZABEBAAGJATwEGAEIACYWIQRG7spZs6IkAFi4SpP9x5gVQA2IqQUCXmLNIAIb +DAUJA8JnAAAKCRD9x5gVQA2IqX09CACTALlaIOxa9VlBrhaj5bHkMwXJG3DDDLm1 +9aJDJfwjqEnCFT7SCggZFCBpu3PqEkq8jHGC/gnWcDoPhWtMldBRVb3MjsxjOi9t +Lk39XcoQOgYo6aFMD1Ughbg+P2QrQwvLhtIl7134MUiB65IsDRLrjXkkMhVEe1Um +0yL4doZPxZ/jm+dGxtFWcAXWBTL4lzE3fWCwMmygiuxljLl9n67glsZG7isRVMfY +U9O8kAMRoMCiktnIe+Ecw1RmAmjgDmA/jTKPGuJRTj/WO5LtWwHUXa7jptJLU1tZ +kdXx0SzOArmDG0dMwggSm9ms4Z8FT+XXe1BWKV1jLTDqBRP1z/KDmQENBF5izVcB +CADKkNZwGmtcTR7TN1tuC326+oXNewWRraKdnxWiKXW1gUROBDiWPic9hImYYjky +t6dz4DkAB/qJfAiRTZG/zz/qnTgbrzK9j3v4TlBTUcTtCI4fF/ShzutKpaIfWFDe +lKSIoWRh/gY6LrtnXm+PRLTckzQxUP71HrHlFFk3462+Ph+7V3z5PrUZvbv+wJ3U +5GdhhYEIBpq2fkvv2K9l9MFVWXcH7mDLxX7p/Q8OaHaSsdTtpBpky4IuA0RQiej0 +gXAEPuoO/TXKwtZ6G7eFjtcndomM2H3N7oYqZZuNW8lU+zcaV8HWKlwNZFvnkRAO +05zCtN8ljUTWkZwM8k6BOp3bABEBAAG0H0phbmUgRG9lIDxqYW5lLmRvZUBleGFt +cGxlLmNvbT6JAVQEEwEIAD4WIQQM1Ty6UL70MGRjStf3hCpc6qnAsQUCXmLNVwIb +AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD3hCpc6qnAsT7uCACe +Ka0jKSzGmjVhxzTT8uO1bxZXzTLi2vDQcqFBVYUBe0TXgf9I8+0sWkvZfvZw9Mju +3bxY+Tp1/e7+nKsEkQO/7rRureOa7OF/D5jJNX1QUNqUFF6LCPABP5RroHS3uGfd +CHKyj84jrZPAhTDPMyYlrWkv8EX2YOT6dlnxgElIdc3WcwJSAtFTWQXoY/kHmjoU +e8c8NJFN1nwEHzbKtjsnkcXvs7XruUhhmqsizyCyrIS9We4Sl5r+4zKi10FKoN/k +zCjU3EHOFiQ8/l5rKTMM1lAN4q3Wyq2xeqyJ+UDx7hOGnnxKjeX9uLay5cAy7XhO +wghIQKCXtcd3T/EzlYQNuQENBF5izVcBCACiADPuJIRFkIuMLiovrWCAtlXt3Oyy +ZrchtRDzxLJW6nL6vaMoJ7nUabD6mlv9mfWRLG4exUID6632/mXblVcPYU4ZQM9H +Futwi8cgq2SuiX/UJM0deJzmiQKxMNO4hUf7eQU7227jRdxkWaOjzN7xdayH6yld +VyrPWQM8i4qmpsGPZ3/EYswDhxcPYPhkA1gW1tgaUxWf/k9U1+GYmyaAI71pImRE +xUIc0pIv44IdGQRU3iPusgljDDXgPVhwF1EmAqFQ6aIM32h6x3WMT6u6OtWfGUks +G3RBv3M380Tegppzk2X+2V38YaJH4u/jNUXhwbu/9yR4xuA02/Z1E44bABEBAAGJ +ATwEGAEIACYWIQQM1Ty6UL70MGRjStf3hCpc6qnAsQUCXmLNVwIbDAUJA8JnAAAK +CRD3hCpc6qnAsWb/CACKcrLYF4yK+vhdlTJw65znSBjIw16iH5SVyd+z3MmiQzlq +pjxfMg7iJG1wDNl4wDRa3QipRrVhKvc8wRuUK7xUKUcbIJDBmoTcMj5iicPucr1W +Qvv9OY3wXBBoWxdPgxWRBglbuZmp7s2D3ixbrd3nxGisFGXIkOAeWtOibFHnnaKw +sM+xqpYDwn4PL/DYWuh0gT5RW6JG1PWdGWMr1CHjV8VyPD3dR59l976sRGz2oyu5 +gefZHtRecI65/BHtKtdf6ZZaonNXilK1XVrD2hahn0CKwumeQtS4kx/hB+SPrcCg +CdUD7FFlMA3kGZ7rBKMkHVxoOondTAJFUw4TAE6j +=DEtc +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFmUaEEBCACzXTDt6ZnyaVtueZASBzgnAmK13q9Urgch+sKYeIhdymjuMQta +x15OklctmrZtqre5kwPUosG3/B2/ikuPYElcHgGPL4uL5Em6S5C/oozfkYzhwRrT +SQzvYjsE4I34To4UdE9KA97wrQjGoz2Bx72WDLyWwctD3DKQtYeHXswXXtXwKfjQ +7Fy4+Bf5IPh76dA8NJ6UtjjLIDlKqdxLW4atHe6xWFaJ+XdLUtsAroZcXBeWDCPa +buXCDscJcLJRKZVc62gOZXXtPfoHqvUPp3nuLA4YjH9bphbrMWMf810Wxz9JTd3v +yWgGqNY0zbBqeZoGv+TuExlRHT8ASGFS9SVDABEBAAG0NUdpdEh1YiAod2ViLWZs +b3cgY29tbWl0IHNpZ25pbmcpIDxub3JlcGx5QGdpdGh1Yi5jb20+iQEiBBMBCAAW +BQJZlGhBCRBK7hj4Ov3rIwIbAwIZAQAAmQEH/iATWFmi2oxlBh3wAsySNCNV4IPf +DDMeh6j80WT7cgoX7V7xqJOxrfrqPEthQ3hgHIm7b5MPQlUr2q+UPL22t/I+ESF6 +9b0QWLFSMJbMSk+BXkvSjH9q8jAO0986/pShPV5DU2sMxnx4LfLfHNhTzjXKokws ++8ptJ8uhMNIDXfXuzkZHIxoXk3rNcjDN5c5X+sK8UBRH092BIJWCOfaQt7v7wig5 +4Ra28pM9GbHKXVNxmdLpCFyzvyMuCmINYYADsC848QQFFwnd4EQnupo6QvhEVx1O +j7wDwvuH5dCrLuLwtwXaQh0onG4583p0LGms2Mf5F+Ick6o/4peOlBoZz48= +=Bvzs +-----END PGP PUBLIC KEY BLOCK----- diff --git a/util/gpg/testdata/unknown_signature.txt b/util/gpg/testdata/unknown_signature.txt new file mode 100644 index 0000000000000..e2c01e6bbf04b --- /dev/null +++ b/util/gpg/testdata/unknown_signature.txt @@ -0,0 +1,3 @@ +gpg: Signature made Mon Aug 26 20:59:48 2019 CEST +gpg: using RSA key 4AEE18F83AFDEB23 +gpg: Can't check signature: No public key diff --git a/util/settings/settings.go b/util/settings/settings.go index 9487005a7fbe7..b33b192895326 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -891,6 +891,27 @@ func (mgr *SettingsManager) SaveTLSCertificateData(ctx context.Context, tlsCerti return mgr.ResyncInformers() } +func (mgr *SettingsManager) SaveGPGPublicKeyData(ctx context.Context, gpgPublicKeys map[string]string) error { + err := mgr.ensureSynced(false) + if err != nil { + return err + } + + keysCM, err := mgr.GetConfigMapByName(common.ArgoCDGPGKeysConfigMapName) + if err != nil { + return err + } + + keysCM.Data = gpgPublicKeys + _, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(keysCM) + if err != nil { + return err + } + + return mgr.ResyncInformers() + +} + // NewSettingsManager generates a new SettingsManager pointer and returns it func NewSettingsManager(ctx context.Context, clientset kubernetes.Interface, namespace string) *SettingsManager {