Compare commits

...

13 Commits

Author SHA1 Message Date
argo-bot
2e550c3f07 Bump version to 2.2.6 2022-03-06 05:50:51 +00:00
argo-bot
d841aae433 Bump version to 2.2.6 2022-03-06 05:50:37 +00:00
Alexander Matyushentsev
b570ab8b17 fix: prevent file traversal using helm file values param and application details api (#8606)
* fix: prevent file traversal using helm file values param and application details api

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

* apply reviewer notes: move resolve.go into separate package; use uuid to generate random file

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-03-03 13:37:33 -08:00
Jesse Suen
8c82655c66 fix!: enforce app create/update privileges when getting repo details (#8558)
Signed-off-by: Jesse Suen <jesse@akuity.io>
2022-03-03 13:03:20 -08:00
Alexander Matyushentsev
a9e1040314 feat: support custom helm values file schemes (#8535)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-03-03 12:55:07 -08:00
Jesse Suen
6f4bbb5a55 docs: add security documentation related to git repositories (#8463)
Signed-off-by: Jesse Suen <jesse@akuity.io>
2022-02-11 13:49:01 -08:00
argo-bot
8f981ccfcf Bump version to 2.2.5 2022-02-05 01:26:23 +00:00
argo-bot
dbf043e6f1 Bump version to 2.2.5 2022-02-05 01:26:11 +00:00
jannfis
f6501652c4 fix: Resolve symlinked value files correctly (#8387)
* fix: Resolve symlinked value files correctly

Signed-off-by: jannfis <jann@mistrust.net>

* fix: Resolve symlinked value files correctly

Signed-off-by: jannfis <jann@mistrust.net>
2022-02-04 15:11:07 -08:00
argo-bot
78d749ec88 Bump version to 2.2.4 2022-02-03 20:33:05 +00:00
argo-bot
8217d70085 Bump version to 2.2.4 2022-02-03 20:32:49 +00:00
jannfis
02e61797b3 Merge pull request from GHSA-63qx-x74g-jcr7
Signed-off-by: jannfis <jann@mistrust.net>
2022-02-03 20:37:46 +01:00
jannfis
998f063a80 chore: upgrade dex to v2.30.2 (backport of #8237) (#8257)
Signed-off-by: jannfis <jann@mistrust.net>

Co-authored-by: Alexander Matyushentsev <Alexander_Matyushentsev@intuit.com>
2022-01-24 10:17:41 -08:00
52 changed files with 2254 additions and 621 deletions

View File

@@ -1,6 +1,6 @@
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} ARGOCD_BINARY_NAME=argocd-application-controller go run ./cmd/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} ARGOCD_BINARY_NAME=argocd-server go run ./cmd/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} "
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd 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 ghcr.io/dexidp/dex:v2.30.0 serve /dex.yaml"
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd 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 ghcr.io/dexidp/dex:v2.30.2 dex serve /dex.yaml"
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" == 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:6.2.6-alpine --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-/tmp/argo-e2e/app/config/plugin} 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} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} go run ./cmd/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'

View File

@@ -1 +1 @@
2.2.3
2.2.6

View File

@@ -2702,6 +2702,16 @@
"type": "string",
"name": "revision",
"in": "query"
},
{
"type": "string",
"name": "appName",
"in": "query"
},
{
"type": "string",
"name": "appProject",
"in": "query"
}
],
"responses": {
@@ -4042,6 +4052,9 @@
"appName": {
"type": "string"
},
"appProject": {
"type": "string"
},
"source": {
"$ref": "#/definitions/v1alpha1ApplicationSource"
}

View File

@@ -155,6 +155,11 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
if err != nil {
return nil, nil, err
}
helmOptions, err := m.settingsMgr.GetHelmSettings()
if err != nil {
return nil, nil, err
}
ts.AddCheckpoint("build_options_ms")
serverVersion, apiResources, err := m.liveStateCache.GetVersionsInfo(app.Spec.Destination.Server)
if err != nil {
@@ -178,6 +183,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
VerifySignature: verifySignature,
HelmRepoCreds: permittedHelmCredentials,
TrackingMethod: string(argo.GetTrackingMethod(m.settingsMgr)),
HelmOptions: helmOptions,
})
if err != nil {
return nil, nil, err

View File

@@ -194,6 +194,10 @@ data:
kustomize.version.v3.5.1: /custom-tools/kustomize_3_5_1
kustomize.version.v3.5.4: /custom-tools/kustomize_3_5_4
# Comma delimited list of additional custom remote values file schemes (http are https are allowed by default).
# Change to empty value if you want to disable remote values files altogether.
helm.valuesFileSchemes: http, https
# The metadata.label key name where Argo CD injects the app name as a tracking label (optional).
# Tracking labels are used to determine which resources need to be deleted when pruning.
# If omitted, Argo CD injects the app name into the label: 'app.kubernetes.io/instance'

View File

@@ -40,6 +40,48 @@ the three components (argocd-server, argocd-repo-server, argocd-application-cont
API server can enforce the use of TLS 1.2 using the flag: `--tlsminversion 1.2`.
Communication with Redis is performed over plain HTTP by default. TLS can be setup with command line arguments.
## Git & Helm Repositories
Git and helm repositories are managed by a stand-alone service, called the repo-server. The
repo-server does not carry any Kubernetes privileges and does not store credentials to any services
(including git). The repo-server is responsible for cloning repositories which have been permitted
and trusted by Argo CD operators, and generating kubernetes manifests at a given path in the
repository. For performance and bandwidth efficiency, the repo-server maintains local clones of
these repositories so that subsequent commits to the repository are efficiently downloaded.
There are security considerations when configuring git repositories that Argo CD is permitted to
deploy from. In short, gaining unauthorized write access to a git repository trusted by Argo CD
will have serious security implications outlined below.
### Unauthorized Deployments
Since Argo CD deploys the Kubernetes resources defined in git, an attacker with access to a trusted
git repo would be able to affect the Kubernetes resources which are deployed. For example, an
attacker could update the deployment manifest deploy malicious container images to the environment,
or delete resources in git causing them to be pruned in the live environment.
### Tool command invocation
In addition to raw YAML, Argo CD natively supports two popular Kubernetes config management tools,
helm and kustomize. When rendering manifests, Argo CD executes these config management tools
(i.e. `helm template`, `kustomize build`) to generate the manifests. It is possible that an attacker
with write access to a trusted git repository may construct malicious helm charts or kustomizations
that attempt to read files out-of-tree. This includes adjacent git repos, as well as files on the
repo-server itself. Whether or not this is a risk to your organization depends on if the contents
in the git repos are sensitive in nature. By default, the repo-server itself does not contain
sensitive information, but might be configured with Config Management Plugins which do
(e.g. decryption keys). If such plugins are used, extreme care must be taken to ensure the
repository contents can be trusted at all times.
### Remote bases and helm chart dependencies
Argo CD's repository allow-list only restricts the initial repository which is cloned. However, both
kustomize and helm contain features to reference and follow *additional* repositories
(e.g. kustomize remote bases, helm chart dependencies), of which might not be in the repository
allow-list. Argo CD operators must understand that users with write access to trusted git
repositories could reference other remote git repositories containing Kubernetes resources not
easily searchable or auditable in the configured git repositories.
## Sensitive Information
### Secrets

View File

@@ -28,7 +28,7 @@ spec:
name: dexconfig
containers:
- name: dex
image: ghcr.io/dexidp/dex:v2.30.0
image: ghcr.io/dexidp/dex:v2.30.2
imagePullPolicy: Always
command: [/shared/argocd-dex, rundex]
securityContext:

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.2.3
newTag: v2.2.6
resources:
- ./application-controller
- ./dex

View File

@@ -3018,7 +3018,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -3067,7 +3067,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -3232,7 +3232,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -11,4 +11,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.2.3
newTag: v2.2.6

View File

@@ -11,7 +11,7 @@ patchesStrategicMerge:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.2.3
newTag: v2.2.6
resources:
- ../../base/application-controller
- ../../base/dex

View File

@@ -3687,7 +3687,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.0
image: ghcr.io/dexidp/dex:v2.30.2
imagePullPolicy: Always
name: dex
ports:
@@ -3709,7 +3709,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3926,7 +3926,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -3975,7 +3975,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -4202,7 +4202,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -4398,7 +4398,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -1046,7 +1046,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.0
image: ghcr.io/dexidp/dex:v2.30.2
imagePullPolicy: Always
name: dex
ports:
@@ -1068,7 +1068,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -1285,7 +1285,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1334,7 +1334,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -1561,7 +1561,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1757,7 +1757,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -3057,7 +3057,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.0
image: ghcr.io/dexidp/dex:v2.30.2
imagePullPolicy: Always
name: dex
ports:
@@ -3079,7 +3079,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3260,7 +3260,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -3309,7 +3309,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -3532,7 +3532,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3722,7 +3722,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -416,7 +416,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.0
image: ghcr.io/dexidp/dex:v2.30.2
imagePullPolicy: Always
name: dex
ports:
@@ -438,7 +438,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -619,7 +619,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -668,7 +668,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -891,7 +891,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1081,7 +1081,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.2.3
image: quay.io/argoproj/argocd:v2.2.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -39,6 +39,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type RepoAppsQuery struct {
Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
AppName string `protobuf:"bytes,3,opt,name=appName,proto3" json:"appName,omitempty"`
AppProject string `protobuf:"bytes,4,opt,name=appProject,proto3" json:"appProject,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -91,6 +93,20 @@ func (m *RepoAppsQuery) GetRevision() string {
return ""
}
func (m *RepoAppsQuery) GetAppName() string {
if m != nil {
return m.AppName
}
return ""
}
func (m *RepoAppsQuery) GetAppProject() string {
if m != nil {
return m.AppProject
}
return ""
}
// AppInfo contains application type and app file path
type AppInfo struct {
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
@@ -151,6 +167,7 @@ func (m *AppInfo) GetPath() string {
type RepoAppDetailsQuery struct {
Source *v1alpha1.ApplicationSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"`
AppName string `protobuf:"bytes,2,opt,name=appName,proto3" json:"appName,omitempty"`
AppProject string `protobuf:"bytes,3,opt,name=appProject,proto3" json:"appProject,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -203,6 +220,13 @@ func (m *RepoAppDetailsQuery) GetAppName() string {
return ""
}
func (m *RepoAppDetailsQuery) GetAppProject() string {
if m != nil {
return m.AppProject
}
return ""
}
// RepoAppsResponse contains applications of specified repository
type RepoAppsResponse struct {
Items []*AppInfo `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
@@ -663,77 +687,78 @@ func init() {
}
var fileDescriptor_8d38260443475705 = []byte{
// 1105 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x6f, 0x1b, 0x45,
0x10, 0xd7, 0xe5, 0x8f, 0x93, 0x6c, 0xfe, 0xd4, 0xd9, 0x84, 0x72, 0xb8, 0x69, 0x1a, 0x6d, 0x4b,
0x15, 0xa2, 0x72, 0xd7, 0x18, 0x21, 0xaa, 0x22, 0x40, 0x69, 0x12, 0xb5, 0x11, 0x11, 0x81, 0xab,
0xc2, 0x03, 0x02, 0xa1, 0xcd, 0x79, 0x62, 0x1f, 0x39, 0xdf, 0x6e, 0x77, 0xd7, 0x06, 0xab, 0xea,
0x0b, 0x4f, 0x48, 0xf0, 0x82, 0x10, 0x52, 0xdf, 0x78, 0x41, 0xe2, 0x81, 0xcf, 0xc0, 0x3b, 0x8f,
0x48, 0x7c, 0x01, 0x14, 0xf1, 0x39, 0x10, 0xda, 0xdd, 0xf3, 0xdd, 0x39, 0xb1, 0x9d, 0x54, 0x84,
0xbc, 0xed, 0xfc, 0x66, 0x76, 0xe6, 0x37, 0xe3, 0x99, 0x59, 0x1f, 0x22, 0x12, 0x44, 0x1b, 0x84,
0x2f, 0x80, 0x33, 0x19, 0x29, 0x26, 0x3a, 0x85, 0xa3, 0xc7, 0x05, 0x53, 0x0c, 0xa3, 0x1c, 0xa9,
0x2c, 0xd6, 0x59, 0x9d, 0x19, 0xd8, 0xd7, 0x27, 0x6b, 0x51, 0x59, 0xaa, 0x33, 0x56, 0x8f, 0xc1,
0xa7, 0x3c, 0xf2, 0x69, 0x92, 0x30, 0x45, 0x55, 0xc4, 0x12, 0x99, 0x6a, 0xc9, 0xd1, 0x3d, 0xe9,
0x45, 0xcc, 0x68, 0x43, 0x26, 0xc0, 0x6f, 0xaf, 0xfb, 0x75, 0x48, 0x40, 0x50, 0x05, 0xb5, 0xd4,
0x66, 0xb7, 0x1e, 0xa9, 0x46, 0xeb, 0xc0, 0x0b, 0x59, 0xd3, 0xa7, 0xc2, 0x84, 0xf8, 0xc2, 0x1c,
0x5e, 0x0f, 0x6b, 0x7e, 0xbb, 0xea, 0xf3, 0xa3, 0xba, 0xbe, 0x2f, 0x7d, 0xca, 0x79, 0x1c, 0x85,
0xc6, 0xbf, 0xdf, 0x5e, 0xa7, 0x31, 0x6f, 0xd0, 0xd3, 0xde, 0xb6, 0xcf, 0xf0, 0x66, 0x12, 0x3a,
0x33, 0x71, 0xf2, 0x1e, 0x9a, 0x0d, 0x80, 0xb3, 0x0d, 0xce, 0xe5, 0x47, 0x2d, 0x10, 0x1d, 0x8c,
0xd1, 0x98, 0x36, 0x72, 0x9d, 0x15, 0x67, 0x75, 0x2a, 0x30, 0x67, 0x5c, 0x41, 0x93, 0x02, 0xda,
0x91, 0x8c, 0x58, 0xe2, 0x8e, 0x18, 0x3c, 0x93, 0xc9, 0x3a, 0x9a, 0xd8, 0xe0, 0x7c, 0x27, 0x39,
0x64, 0xfa, 0xaa, 0xea, 0x70, 0xe8, 0x5e, 0xd5, 0x67, 0x8d, 0x71, 0xaa, 0x1a, 0xe9, 0x35, 0x73,
0x26, 0xcf, 0x1d, 0xb4, 0x90, 0x06, 0xdd, 0x02, 0x45, 0xa3, 0x38, 0x0d, 0x5d, 0x47, 0x25, 0xc9,
0x5a, 0x22, 0xb4, 0x1e, 0xa6, 0xab, 0x7b, 0x5e, 0x9e, 0xa3, 0xd7, 0xcd, 0xd1, 0x1c, 0x3e, 0x0f,
0x6b, 0x5e, 0xbb, 0xea, 0xf1, 0xa3, 0xba, 0xa7, 0x2b, 0xe6, 0x15, 0x2a, 0xe6, 0x75, 0x2b, 0xe6,
0x6d, 0xe4, 0xe0, 0x63, 0xe3, 0x36, 0x48, 0xdd, 0x63, 0x17, 0x4d, 0x50, 0xce, 0x3f, 0xa0, 0x4d,
0x48, 0x79, 0x75, 0x45, 0xf2, 0x0e, 0x2a, 0x77, 0xcb, 0x11, 0x80, 0xe4, 0x2c, 0x91, 0x80, 0x5f,
0x43, 0xe3, 0x91, 0x82, 0xa6, 0x74, 0x9d, 0x95, 0xd1, 0xd5, 0xe9, 0xea, 0x82, 0x57, 0x28, 0x62,
0x9a, 0x7a, 0x60, 0x2d, 0xc8, 0x26, 0x9a, 0xd2, 0xd7, 0x07, 0x57, 0x92, 0xa0, 0x99, 0x43, 0xa6,
0xa9, 0xc0, 0xa1, 0x00, 0x69, 0xcb, 0x32, 0x19, 0xf4, 0x60, 0xe4, 0xb7, 0x31, 0x74, 0xc5, 0x90,
0x08, 0x43, 0x90, 0xc3, 0x7f, 0x95, 0x96, 0x04, 0x91, 0xe4, 0x69, 0x64, 0xb2, 0xd6, 0x71, 0x2a,
0xe5, 0x97, 0x4c, 0xd4, 0xdc, 0x51, 0xab, 0xeb, 0xca, 0xf8, 0x16, 0x9a, 0x95, 0xb2, 0xf1, 0xa1,
0x88, 0xda, 0x54, 0xc1, 0xfb, 0xd0, 0x71, 0xc7, 0x8c, 0x41, 0x2f, 0xa8, 0x3d, 0x44, 0x89, 0x84,
0xb0, 0x25, 0xc0, 0x1d, 0x37, 0x2c, 0x33, 0x19, 0xdf, 0x41, 0xf3, 0x2a, 0x96, 0x9b, 0x71, 0x04,
0x89, 0xda, 0x04, 0xa1, 0xb6, 0xa8, 0xa2, 0x6e, 0xc9, 0x78, 0x39, 0xad, 0xc0, 0x6b, 0xa8, 0xdc,
0x03, 0xea, 0x90, 0x13, 0xc6, 0xf8, 0x14, 0x9e, 0xb5, 0xd0, 0x54, 0x6f, 0x0b, 0x99, 0x1c, 0x91,
0xc5, 0x4c, 0x7e, 0x4b, 0x68, 0x0a, 0x12, 0x7a, 0x10, 0xc3, 0x5e, 0x18, 0xb9, 0xd3, 0x86, 0x5e,
0x0e, 0xe0, 0xbb, 0x68, 0xc1, 0x76, 0xce, 0x06, 0xe7, 0x85, 0x3c, 0x67, 0x8c, 0x83, 0x7e, 0x2a,
0xbc, 0x82, 0xa6, 0x33, 0x78, 0x67, 0xcb, 0x9d, 0x5d, 0x71, 0x56, 0x47, 0x83, 0x22, 0x84, 0xef,
0xa1, 0x97, 0x73, 0x31, 0x91, 0x8a, 0xc6, 0xb1, 0x69, 0xad, 0x9d, 0x2d, 0x77, 0xce, 0x58, 0x0f,
0x52, 0xe3, 0x77, 0x51, 0x25, 0x53, 0x6d, 0x27, 0x0a, 0x04, 0x17, 0x91, 0x84, 0x07, 0x54, 0xc2,
0xbe, 0x88, 0xdd, 0x2b, 0x86, 0xd4, 0x10, 0x0b, 0xbc, 0x88, 0xc6, 0xb9, 0x60, 0x5f, 0x75, 0xdc,
0xb2, 0x31, 0xb5, 0x82, 0xee, 0x61, 0x3d, 0x0e, 0x10, 0x2a, 0x77, 0xde, 0xf6, 0x70, 0x2a, 0x92,
0x39, 0x34, 0xa3, 0xdb, 0xa7, 0xdb, 0xbf, 0xe4, 0x17, 0x07, 0xcd, 0x6b, 0x60, 0x53, 0x00, 0x55,
0x10, 0xc0, 0x93, 0x16, 0x48, 0x85, 0x3f, 0x2d, 0x74, 0xd4, 0x74, 0xf5, 0xd1, 0x7f, 0x1b, 0xb5,
0x20, 0x9b, 0x88, 0xb4, 0x37, 0xaf, 0xa2, 0x52, 0x8b, 0x4b, 0x10, 0x2a, 0xed, 0xf0, 0x54, 0xd2,
0xbf, 0x5b, 0x28, 0xa0, 0x26, 0xf7, 0x92, 0xb8, 0x63, 0x1a, 0x73, 0x32, 0xc8, 0x01, 0xf2, 0xc4,
0x12, 0xdd, 0xe7, 0xb5, 0xcb, 0x22, 0x5a, 0xfd, 0x67, 0xce, 0xc6, 0xb4, 0xe0, 0x63, 0x10, 0xed,
0x28, 0x04, 0xfc, 0x9d, 0x83, 0xc6, 0x76, 0x23, 0xa9, 0xf0, 0x4b, 0xc5, 0x61, 0xcf, 0x46, 0xbb,
0xb2, 0x7b, 0x51, 0x2c, 0x74, 0x10, 0x72, 0xe3, 0xeb, 0x3f, 0xff, 0xfe, 0x61, 0xe4, 0x2a, 0x5e,
0x34, 0xcf, 0x47, 0x7b, 0x3d, 0xdf, 0xd2, 0x11, 0xc8, 0x6f, 0x46, 0x1c, 0xfc, 0xad, 0x83, 0x46,
0x1f, 0xc2, 0x40, 0x36, 0x17, 0x56, 0x13, 0x72, 0xd3, 0x30, 0xb9, 0x8e, 0xaf, 0xf5, 0x63, 0xe2,
0x3f, 0xd5, 0xd2, 0x33, 0xfc, 0xa3, 0x83, 0xca, 0x9a, 0x77, 0x50, 0xd0, 0x5d, 0x4e, 0xa1, 0x96,
0x86, 0x15, 0x0a, 0x7f, 0x86, 0x26, 0x2d, 0xad, 0xc3, 0x81, 0x74, 0xca, 0xbd, 0xf0, 0xa1, 0x24,
0xab, 0xc6, 0x25, 0xc1, 0x2b, 0x43, 0x32, 0xf6, 0x85, 0x76, 0xd9, 0xb4, 0xee, 0xf5, 0xd3, 0x80,
0x5f, 0x39, 0xe9, 0x3e, 0x7b, 0x3f, 0x2b, 0x4b, 0xfd, 0x54, 0xd9, 0x2c, 0x9e, 0x2b, 0x1c, 0xd5,
0x21, 0xbe, 0x77, 0xd0, 0xec, 0x43, 0x50, 0xf9, 0x1b, 0x89, 0x6f, 0xf4, 0xf1, 0x5c, 0x7c, 0x3f,
0x2b, 0x64, 0xb0, 0x41, 0x46, 0xe0, 0x6d, 0x43, 0xe0, 0x4d, 0x72, 0xb7, 0x3f, 0x01, 0xfb, 0x40,
0x1a, 0x3f, 0xfb, 0xc1, 0xae, 0xa1, 0x52, 0xb3, 0x1e, 0xee, 0x3b, 0x6b, 0xb8, 0x6d, 0x28, 0x3d,
0x82, 0xb8, 0xb9, 0xd9, 0xa0, 0x42, 0x0d, 0x2c, 0xf3, 0x72, 0x11, 0xce, 0xcd, 0x33, 0x12, 0x9e,
0x21, 0xb1, 0x8a, 0x6f, 0x0f, 0xab, 0x42, 0x03, 0xe2, 0x66, 0x68, 0xc3, 0x3c, 0x77, 0x50, 0xc9,
0x6e, 0x2f, 0x7c, 0xfd, 0x64, 0xc4, 0x9e, 0xad, 0x76, 0x81, 0xa3, 0xf0, 0xaa, 0xe1, 0xb8, 0x44,
0xfa, 0xf6, 0xda, 0x7d, 0xb3, 0x3c, 0xf4, 0x68, 0xfe, 0xe4, 0xa0, 0x72, 0x97, 0x42, 0xf7, 0xee,
0xe5, 0x91, 0x24, 0x67, 0x93, 0xc4, 0x3f, 0x3b, 0xa8, 0x64, 0x37, 0xea, 0x69, 0x5e, 0x3d, 0x9b,
0xf6, 0x02, 0x79, 0xad, 0xdb, 0x1f, 0xb8, 0x32, 0xa4, 0xcd, 0x0d, 0x95, 0x67, 0x79, 0x21, 0x7f,
0x75, 0x50, 0xb9, 0x4b, 0x67, 0x70, 0x21, 0xff, 0x2f, 0xc2, 0xde, 0x8b, 0x11, 0xc6, 0x14, 0x95,
0xb6, 0x20, 0x06, 0x05, 0x83, 0x46, 0xc0, 0x3d, 0x09, 0x67, 0xcd, 0x7f, 0xdb, 0xee, 0xd8, 0xb5,
0x61, 0x3b, 0x56, 0x17, 0xa4, 0x81, 0xca, 0x36, 0x44, 0xa1, 0x1e, 0x2f, 0x1c, 0xec, 0xe6, 0x39,
0x82, 0xe1, 0xa7, 0x68, 0xee, 0x63, 0x1a, 0x47, 0xba, 0xb2, 0xf6, 0x3f, 0x27, 0xbe, 0x76, 0x6a,
0x93, 0xe4, 0xff, 0x45, 0x87, 0x44, 0xab, 0x9a, 0x68, 0x77, 0xc8, 0xad, 0x61, 0x73, 0xdd, 0x4e,
0x43, 0xd9, 0x4a, 0x3e, 0xd8, 0xfe, 0xfd, 0x78, 0xd9, 0xf9, 0xe3, 0x78, 0xd9, 0xf9, 0xeb, 0x78,
0xd9, 0xf9, 0xe4, 0xad, 0xf3, 0x7d, 0x23, 0x85, 0xe6, 0x4f, 0x63, 0xe1, 0x6b, 0xe6, 0xa0, 0x64,
0x3e, 0x67, 0xde, 0xf8, 0x37, 0x00, 0x00, 0xff, 0xff, 0x38, 0x35, 0xe9, 0x0a, 0xed, 0x0d, 0x00,
0x00,
// 1129 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4d, 0x6f, 0x1c, 0x45,
0x13, 0xd6, 0xf8, 0x63, 0x6d, 0xb7, 0x3f, 0xb2, 0x6e, 0xfb, 0xcd, 0x3b, 0x6c, 0x1c, 0xc7, 0x9a,
0x84, 0xc8, 0x58, 0x61, 0x26, 0x5e, 0x84, 0x88, 0x82, 0x40, 0x72, 0x6c, 0x2b, 0xb1, 0xb0, 0x70,
0x98, 0xc8, 0x1c, 0x10, 0x08, 0xb5, 0x67, 0x6b, 0x77, 0x27, 0x9e, 0x9d, 0xee, 0x74, 0xf7, 0x0e,
0xac, 0xa2, 0x5c, 0x38, 0x21, 0xc1, 0x05, 0x21, 0x24, 0x6e, 0x5c, 0x90, 0x38, 0xf0, 0x07, 0xb8,
0x70, 0xe7, 0x88, 0xc4, 0x1f, 0x40, 0x16, 0xbf, 0x03, 0xa1, 0xee, 0x9e, 0x9d, 0x99, 0xf5, 0x7e,
0xd8, 0x11, 0xc6, 0xb7, 0xae, 0xa7, 0x6a, 0xab, 0x9e, 0x7a, 0xba, 0xba, 0x7b, 0x07, 0x39, 0x02,
0x78, 0x02, 0xdc, 0xe3, 0xc0, 0xa8, 0x08, 0x25, 0xe5, 0x9d, 0xc2, 0xd2, 0x65, 0x9c, 0x4a, 0x8a,
0x51, 0x8e, 0x54, 0x96, 0x1b, 0xb4, 0x41, 0x35, 0xec, 0xa9, 0x95, 0x89, 0xa8, 0xac, 0x34, 0x28,
0x6d, 0x44, 0xe0, 0x11, 0x16, 0x7a, 0x24, 0x8e, 0xa9, 0x24, 0x32, 0xa4, 0xb1, 0x48, 0xbd, 0xce,
0xf1, 0x3d, 0xe1, 0x86, 0x54, 0x7b, 0x03, 0xca, 0xc1, 0x4b, 0x36, 0xbd, 0x06, 0xc4, 0xc0, 0x89,
0x84, 0x5a, 0x1a, 0xb3, 0xdf, 0x08, 0x65, 0xb3, 0x7d, 0xe4, 0x06, 0xb4, 0xe5, 0x11, 0xae, 0x4b,
0x3c, 0xd5, 0x8b, 0xd7, 0x83, 0x9a, 0x97, 0x54, 0x3d, 0x76, 0xdc, 0x50, 0xbf, 0x17, 0x1e, 0x61,
0x2c, 0x0a, 0x03, 0x9d, 0xdf, 0x4b, 0x36, 0x49, 0xc4, 0x9a, 0xa4, 0x3f, 0xdb, 0xee, 0x19, 0xd9,
0x74, 0x43, 0x67, 0x36, 0xee, 0x74, 0xd0, 0xbc, 0x0f, 0x8c, 0x6e, 0x31, 0x26, 0x3e, 0x68, 0x03,
0xef, 0x60, 0x8c, 0x26, 0x54, 0x90, 0x6d, 0xad, 0x59, 0xeb, 0x33, 0xbe, 0x5e, 0xe3, 0x0a, 0x9a,
0xe6, 0x90, 0x84, 0x22, 0xa4, 0xb1, 0x3d, 0xa6, 0xf1, 0xcc, 0xc6, 0x36, 0x9a, 0x22, 0x8c, 0xbd,
0x4f, 0x5a, 0x60, 0x8f, 0x6b, 0x57, 0xd7, 0xc4, 0xab, 0x08, 0x11, 0xc6, 0x1e, 0x73, 0xfa, 0x14,
0x02, 0x69, 0x4f, 0x68, 0x67, 0x01, 0x71, 0x36, 0xd1, 0xd4, 0x16, 0x63, 0x7b, 0x71, 0x9d, 0xaa,
0xa2, 0xb2, 0xc3, 0xa0, 0x5b, 0x54, 0xad, 0x15, 0xc6, 0x88, 0x6c, 0xa6, 0x05, 0xf5, 0xda, 0xf9,
0xc5, 0x42, 0x4b, 0x29, 0xdd, 0x1d, 0x90, 0x24, 0x8c, 0x52, 0xd2, 0x0d, 0x54, 0x12, 0xb4, 0xcd,
0x03, 0x93, 0x61, 0xb6, 0x7a, 0xe0, 0xe6, 0xea, 0xb8, 0x5d, 0x75, 0xf4, 0xe2, 0xd3, 0xa0, 0xe6,
0x26, 0x55, 0x97, 0x1d, 0x37, 0x5c, 0xa5, 0xb5, 0x5b, 0xd0, 0xda, 0xed, 0x6a, 0xed, 0x6e, 0xe5,
0xe0, 0x13, 0x9d, 0xd6, 0x4f, 0xd3, 0x17, 0xbb, 0x1d, 0x1b, 0xd5, 0xed, 0x78, 0x5f, 0xb7, 0xef,
0xa0, 0x72, 0x57, 0x68, 0x1f, 0x04, 0xa3, 0xb1, 0x00, 0xfc, 0x1a, 0x9a, 0x0c, 0x25, 0xb4, 0x84,
0x6d, 0xad, 0x8d, 0xaf, 0xcf, 0x56, 0x97, 0xdc, 0xc2, 0xf6, 0xa4, 0xd2, 0xf8, 0x26, 0xc2, 0xd9,
0x46, 0x33, 0xea, 0xe7, 0xc3, 0xf7, 0xc8, 0x41, 0x73, 0x75, 0xaa, 0xa8, 0x42, 0x9d, 0x83, 0x30,
0xb2, 0x4d, 0xfb, 0x3d, 0x98, 0xf3, 0xeb, 0x04, 0xba, 0xa2, 0x49, 0x04, 0x01, 0x88, 0xd1, 0xfb,
0xdd, 0x16, 0xc0, 0xe3, 0xbc, 0xcd, 0xcc, 0x56, 0x3e, 0x46, 0x84, 0xf8, 0x8c, 0xf2, 0x5a, 0xda,
0x65, 0x66, 0xe3, 0x5b, 0x68, 0x5e, 0x88, 0xe6, 0x63, 0x1e, 0x26, 0x44, 0xc2, 0x7b, 0xd0, 0x49,
0x37, 0xbd, 0x17, 0x54, 0x19, 0xc2, 0x58, 0x40, 0xd0, 0xe6, 0x60, 0x4f, 0x6a, 0x96, 0x99, 0x8d,
0xef, 0xa0, 0x45, 0x19, 0x89, 0xed, 0x28, 0x84, 0x58, 0x6e, 0x03, 0x97, 0x3b, 0x44, 0x12, 0xbb,
0xa4, 0xb3, 0xf4, 0x3b, 0xf0, 0x06, 0x2a, 0xf7, 0x80, 0xaa, 0xe4, 0x94, 0x0e, 0xee, 0xc3, 0xb3,
0x11, 0x9b, 0xe9, 0x1d, 0x31, 0xdd, 0x23, 0x32, 0x98, 0xee, 0x6f, 0x05, 0xcd, 0x40, 0x4c, 0x8e,
0x22, 0x38, 0x08, 0x42, 0x7b, 0x56, 0xd3, 0xcb, 0x01, 0x7c, 0x17, 0x2d, 0x99, 0xc9, 0xda, 0x52,
0x3b, 0x9b, 0xf5, 0x39, 0xa7, 0x13, 0x0c, 0x72, 0xe1, 0x35, 0x34, 0x9b, 0xc1, 0x7b, 0x3b, 0xf6,
0xfc, 0x9a, 0xb5, 0x3e, 0xee, 0x17, 0x21, 0x7c, 0x0f, 0xfd, 0x3f, 0x37, 0x63, 0x21, 0x49, 0x14,
0xe9, 0xd1, 0xdb, 0xdb, 0xb1, 0x17, 0x74, 0xf4, 0x30, 0x37, 0x7e, 0x17, 0x55, 0x32, 0xd7, 0x6e,
0x2c, 0x81, 0x33, 0x1e, 0x0a, 0x78, 0x40, 0x04, 0x1c, 0xf2, 0xc8, 0xbe, 0xa2, 0x49, 0x8d, 0x88,
0xc0, 0xcb, 0x68, 0x92, 0x71, 0xfa, 0x79, 0xc7, 0x2e, 0xeb, 0x50, 0x63, 0xa8, 0x19, 0x67, 0xe9,
0x18, 0x2f, 0x9a, 0x19, 0x4f, 0x4d, 0x67, 0x01, 0xcd, 0xa9, 0xf1, 0xe9, 0xce, 0xaf, 0xf3, 0x93,
0x85, 0x16, 0x15, 0xb0, 0xcd, 0x81, 0x48, 0xf0, 0xe1, 0x59, 0x1b, 0x84, 0xc4, 0x1f, 0x17, 0x26,
0x6a, 0xb6, 0xfa, 0xe8, 0xdf, 0x1d, 0x45, 0x3f, 0x3b, 0x11, 0xe9, 0x6c, 0x5e, 0x45, 0xa5, 0x36,
0x13, 0xc0, 0x65, 0x3a, 0xe1, 0xa9, 0xa5, 0xf6, 0x2d, 0xe0, 0x50, 0x13, 0x07, 0x71, 0xd4, 0xd1,
0x83, 0x39, 0xed, 0xe7, 0x80, 0xf3, 0xcc, 0x10, 0x3d, 0x64, 0xb5, 0xcb, 0x22, 0x5a, 0xfd, 0x7b,
0xc1, 0xd4, 0x34, 0xe0, 0x13, 0xe0, 0x49, 0x18, 0x00, 0xfe, 0xda, 0x42, 0x13, 0xfb, 0xa1, 0x90,
0xf8, 0x7f, 0xc5, 0xc3, 0x9e, 0x1d, 0xed, 0xca, 0xfe, 0x45, 0xb1, 0x50, 0x45, 0x9c, 0x1b, 0x5f,
0xfc, 0xf1, 0xd7, 0xb7, 0x63, 0x57, 0xf1, 0xb2, 0x7e, 0x98, 0x92, 0xcd, 0xfc, 0xfe, 0x0f, 0x41,
0x7c, 0x39, 0x66, 0xe1, 0xaf, 0x2c, 0x34, 0xfe, 0x10, 0x86, 0xb2, 0xb9, 0x30, 0x4d, 0x9c, 0x9b,
0x9a, 0xc9, 0x75, 0x7c, 0x6d, 0x10, 0x13, 0xef, 0xb9, 0xb2, 0x5e, 0xe0, 0xef, 0x2c, 0x54, 0x56,
0xbc, 0xfd, 0x82, 0xef, 0x72, 0x84, 0x5a, 0x19, 0x25, 0x14, 0xfe, 0x04, 0x4d, 0x1b, 0x5a, 0xf5,
0xa1, 0x74, 0xca, 0xbd, 0x70, 0x5d, 0x38, 0xeb, 0x3a, 0xa5, 0x83, 0xd7, 0x46, 0x74, 0xec, 0x71,
0x95, 0xb2, 0x65, 0xd2, 0xab, 0xa7, 0x01, 0xbf, 0x72, 0x3a, 0x7d, 0xf6, 0x32, 0x57, 0x56, 0x06,
0xb9, 0xb2, 0xb3, 0x78, 0xae, 0x72, 0x44, 0x95, 0xf8, 0xc6, 0x42, 0xf3, 0x0f, 0x41, 0xe6, 0x6f,
0x28, 0xbe, 0x31, 0x20, 0x73, 0xf1, 0x7d, 0xad, 0x38, 0xc3, 0x03, 0x32, 0x02, 0x6f, 0x6b, 0x02,
0x6f, 0x3a, 0x77, 0x07, 0x13, 0x30, 0x0f, 0xa8, 0xce, 0x73, 0xe8, 0xef, 0x6b, 0x2a, 0x35, 0x93,
0xe1, 0xbe, 0xb5, 0x81, 0x13, 0x4d, 0xe9, 0x11, 0x44, 0xad, 0xed, 0x26, 0xe1, 0x72, 0xa8, 0xcc,
0xab, 0x45, 0x38, 0x0f, 0xcf, 0x48, 0xb8, 0x9a, 0xc4, 0x3a, 0xbe, 0x3d, 0x4a, 0x85, 0x26, 0x44,
0xad, 0xc0, 0x94, 0xf9, 0xde, 0x42, 0x25, 0x73, 0x7b, 0xe1, 0xeb, 0xa7, 0x2b, 0xf6, 0xdc, 0x6a,
0x17, 0x78, 0x14, 0x5e, 0xd5, 0x1c, 0x57, 0x9c, 0x81, 0xb3, 0x76, 0x5f, 0x5f, 0x1e, 0xea, 0x68,
0xfe, 0x60, 0xa1, 0x72, 0x97, 0x42, 0xf7, 0xb7, 0x97, 0x47, 0xd2, 0x39, 0x9b, 0x24, 0xfe, 0xd1,
0x42, 0x25, 0x73, 0xa3, 0xf6, 0xf3, 0xea, 0xb9, 0x69, 0x2f, 0x90, 0xd7, 0xa6, 0xd9, 0xe0, 0xca,
0x88, 0x31, 0xd7, 0x54, 0x5e, 0xe4, 0x42, 0xfe, 0x6c, 0xa1, 0x72, 0x97, 0xce, 0x70, 0x21, 0xff,
0x2b, 0xc2, 0xee, 0xcb, 0x11, 0xc6, 0x04, 0x95, 0x76, 0x20, 0x02, 0x09, 0xc3, 0x8e, 0x80, 0x7d,
0x1a, 0xce, 0x86, 0xff, 0xb6, 0xb9, 0x63, 0x37, 0x46, 0xdd, 0xb1, 0x4a, 0x90, 0x26, 0x2a, 0x9b,
0x12, 0x05, 0x3d, 0x5e, 0xba, 0xd8, 0xcd, 0x73, 0x14, 0xc3, 0xcf, 0xd1, 0xc2, 0x87, 0x24, 0x0a,
0x95, 0xb2, 0xe6, 0x3f, 0x27, 0xbe, 0xd6, 0x77, 0x93, 0xe4, 0xff, 0x45, 0x47, 0x54, 0xab, 0xea,
0x6a, 0x77, 0x9c, 0x5b, 0xa3, 0xce, 0x75, 0x92, 0x96, 0x32, 0x4a, 0x3e, 0xd8, 0xfd, 0xed, 0x64,
0xd5, 0xfa, 0xfd, 0x64, 0xd5, 0xfa, 0xf3, 0x64, 0xd5, 0xfa, 0xe8, 0xad, 0xf3, 0x7d, 0x7d, 0x05,
0xfa, 0x4f, 0x63, 0xe1, 0x3b, 0xe9, 0xa8, 0xa4, 0x3f, 0x94, 0xde, 0xf8, 0x27, 0x00, 0x00, 0xff,
0xff, 0x52, 0x69, 0xb3, 0xbe, 0x47, 0x0e, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@@ -1338,6 +1363,20 @@ func (m *RepoAppsQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.AppProject) > 0 {
i -= len(m.AppProject)
copy(dAtA[i:], m.AppProject)
i = encodeVarintRepository(dAtA, i, uint64(len(m.AppProject)))
i--
dAtA[i] = 0x22
}
if len(m.AppName) > 0 {
i -= len(m.AppName)
copy(dAtA[i:], m.AppName)
i = encodeVarintRepository(dAtA, i, uint64(len(m.AppName)))
i--
dAtA[i] = 0x1a
}
if len(m.Revision) > 0 {
i -= len(m.Revision)
copy(dAtA[i:], m.Revision)
@@ -1420,6 +1459,13 @@ func (m *RepoAppDetailsQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.AppProject) > 0 {
i -= len(m.AppProject)
copy(dAtA[i:], m.AppProject)
i = encodeVarintRepository(dAtA, i, uint64(len(m.AppProject)))
i--
dAtA[i] = 0x1a
}
if len(m.AppName) > 0 {
i -= len(m.AppName)
copy(dAtA[i:], m.AppName)
@@ -1822,6 +1868,14 @@ func (m *RepoAppsQuery) Size() (n int) {
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
l = len(m.AppName)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
l = len(m.AppProject)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -1862,6 +1916,10 @@ func (m *RepoAppDetailsQuery) Size() (n int) {
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
l = len(m.AppProject)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -2126,6 +2184,70 @@ func (m *RepoAppsQuery) Unmarshal(dAtA []byte) error {
}
m.Revision = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AppName", 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.AppName = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AppProject", 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.AppProject = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
@@ -2360,6 +2482,38 @@ func (m *RepoAppDetailsQuery) Unmarshal(dAtA []byte) error {
}
m.AppName = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AppProject", 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.AppProject = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])

View File

@@ -27,6 +27,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/ap
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,Command,Args
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,Command,Command
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ExecProviderConfig,Args
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,HelmOptions,ValuesFileSchemes
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,HostInfo,ResourcesInfo
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,JWTTokens,Items
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,Operation,Info
@@ -61,6 +62,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/ap
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourceJsonnet,TLAs
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ClusterCacheInfo,APIsCount
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ConnectionState,ModifiedAt
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,HelmOptions,ValuesFileSchemes
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,JWTToken,ExpiresAt
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,JWTToken,IssuedAt
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,KustomizeOptions,BinaryPath

File diff suppressed because it is too large Load Diff

View File

@@ -602,6 +602,11 @@ message HelmFileParameter {
optional string path = 2;
}
// HelmOptions holds helm options
message HelmOptions {
repeated string valuesFileSchemes = 1;
}
// HelmParameter is a parameter that's passed to helm template during manifest generation
message HelmParameter {
// Name is the name of the Helm parameter

View File

@@ -51,6 +51,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.GnuPGPublicKeyList": schema_pkg_apis_application_v1alpha1_GnuPGPublicKeyList(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HealthStatus": schema_pkg_apis_application_v1alpha1_HealthStatus(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HelmFileParameter": schema_pkg_apis_application_v1alpha1_HelmFileParameter(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HelmOptions": schema_pkg_apis_application_v1alpha1_HelmOptions(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HelmParameter": schema_pkg_apis_application_v1alpha1_HelmParameter(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HostInfo": schema_pkg_apis_application_v1alpha1_HostInfo(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HostResourceInfo": schema_pkg_apis_application_v1alpha1_HostResourceInfo(ref),
@@ -2104,6 +2105,34 @@ func schema_pkg_apis_application_v1alpha1_HelmFileParameter(ref common.Reference
}
}
func schema_pkg_apis_application_v1alpha1_HelmOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "HelmOptions holds helm options",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"ValuesFileSchemes": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"ValuesFileSchemes"},
},
},
}
}
func schema_pkg_apis_application_v1alpha1_HelmParameter(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@@ -2067,6 +2067,11 @@ type ConfigManagementPlugin struct {
LockRepo bool `json:"lockRepo,omitempty" protobuf:"bytes,4,name=lockRepo"`
}
// HelmOptions holds helm options
type HelmOptions struct {
ValuesFileSchemes []string `protobuf:"bytes,1,opt,name=valuesFileSchemes"`
}
// KustomizeOptions are options for kustomize to use when building manifests
type KustomizeOptions struct {
// BuildOptions is a string of build parameters to use when calling `kustomize build`

View File

@@ -1066,6 +1066,27 @@ func (in *HelmFileParameter) DeepCopy() *HelmFileParameter {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HelmOptions) DeepCopyInto(out *HelmOptions) {
*out = *in
if in.ValuesFileSchemes != nil {
in, out := &in.ValuesFileSchemes, &out.ValuesFileSchemes
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmOptions.
func (in *HelmOptions) DeepCopy() *HelmOptions {
if in == nil {
return nil
}
out := new(HelmOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HelmParameter) DeepCopyInto(out *HelmParameter) {
*out = *in

View File

@@ -0,0 +1,15 @@
package apiclient
func (q *ManifestRequest) GetValuesFileSchemes() []string {
if q.HelmOptions == nil {
return nil
}
return q.HelmOptions.ValuesFileSchemes
}
func (q *RepoServerAppDetailsQuery) GetValuesFileSchemes() []string {
if q.HelmOptions == nil {
return nil
}
return q.HelmOptions.ValuesFileSchemes
}

View File

@@ -51,6 +51,7 @@ type ManifestRequest struct {
HelmRepoCreds []*v1alpha1.RepoCreds `protobuf:"bytes,17,rep,name=helmRepoCreds,proto3" json:"helmRepoCreds,omitempty"`
NoRevisionCache bool `protobuf:"varint,18,opt,name=noRevisionCache,proto3" json:"noRevisionCache,omitempty"`
TrackingMethod string `protobuf:"bytes,19,opt,name=trackingMethod,proto3" json:"trackingMethod,omitempty"`
HelmOptions *v1alpha1.HelmOptions `protobuf:"bytes,21,opt,name=helmOptions,proto3" json:"helmOptions,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -201,6 +202,13 @@ func (m *ManifestRequest) GetTrackingMethod() string {
return ""
}
func (m *ManifestRequest) GetHelmOptions() *v1alpha1.HelmOptions {
if m != nil {
return m.HelmOptions
}
return nil
}
// TestRepositoryRequest is a query to test repository is valid or not and has valid access.
type TestRepositoryRequest struct {
Repo *v1alpha1.Repository `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
@@ -604,6 +612,7 @@ type RepoServerAppDetailsQuery struct {
NoCache bool `protobuf:"varint,6,opt,name=noCache,proto3" json:"noCache,omitempty"`
NoRevisionCache bool `protobuf:"varint,7,opt,name=noRevisionCache,proto3" json:"noRevisionCache,omitempty"`
TrackingMethod string `protobuf:"bytes,8,opt,name=trackingMethod,proto3" json:"trackingMethod,omitempty"`
HelmOptions *v1alpha1.HelmOptions `protobuf:"bytes,10,opt,name=helmOptions,proto3" json:"helmOptions,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -698,6 +707,13 @@ func (m *RepoServerAppDetailsQuery) GetTrackingMethod() string {
return ""
}
func (m *RepoServerAppDetailsQuery) GetHelmOptions() *v1alpha1.HelmOptions {
if m != nil {
return m.HelmOptions
}
return nil
}
// RepoAppDetailsResponse application details
type RepoAppDetailsResponse struct {
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
@@ -1383,96 +1399,98 @@ func init() {
}
var fileDescriptor_dd8723cfcc820480 = []byte{
// 1423 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0x5b, 0x6f, 0x1b, 0xc5,
0x17, 0xcf, 0x26, 0x4e, 0x62, 0x1f, 0xb7, 0x89, 0x33, 0xbd, 0xfc, 0xf7, 0x6f, 0x52, 0x2b, 0x5d,
0x89, 0x2a, 0x50, 0xba, 0x56, 0xdd, 0x0a, 0xaa, 0x56, 0x42, 0x32, 0x69, 0x9b, 0x4a, 0x69, 0x9a,
0xb0, 0x29, 0x48, 0xa0, 0x8a, 0x6a, 0xb2, 0x3e, 0x59, 0x0f, 0xb6, 0x77, 0xa7, 0xbb, 0x6b, 0xa3,
0x54, 0xe2, 0x11, 0xf1, 0xc0, 0x33, 0x7c, 0x1d, 0x5e, 0xb8, 0x3d, 0xf2, 0x11, 0x50, 0xbf, 0x05,
0x6f, 0x68, 0x66, 0x6f, 0xb3, 0xeb, 0x4d, 0x40, 0x72, 0x9b, 0xbe, 0x24, 0x33, 0xe7, 0x3e, 0x67,
0xce, 0xfc, 0xce, 0xf1, 0xc2, 0x35, 0x1f, 0xb9, 0x17, 0xa0, 0x3f, 0x41, 0xbf, 0x2d, 0x97, 0x2c,
0xf4, 0xfc, 0x63, 0x65, 0x69, 0x72, 0xdf, 0x0b, 0x3d, 0x02, 0x19, 0xa5, 0x79, 0xd1, 0xf1, 0x1c,
0x4f, 0x92, 0xdb, 0x62, 0x15, 0x49, 0x34, 0xd7, 0x1d, 0xcf, 0x73, 0x86, 0xd8, 0xa6, 0x9c, 0xb5,
0xa9, 0xeb, 0x7a, 0x21, 0x0d, 0x99, 0xe7, 0x06, 0x31, 0xd7, 0x18, 0xdc, 0x09, 0x4c, 0xe6, 0x49,
0xae, 0xed, 0xf9, 0xd8, 0x9e, 0xdc, 0x6c, 0x3b, 0xe8, 0xa2, 0x4f, 0x43, 0xec, 0xc5, 0x32, 0x8f,
0x1d, 0x16, 0xf6, 0xc7, 0x87, 0xa6, 0xed, 0x8d, 0xda, 0xd4, 0x97, 0x2e, 0xbe, 0x96, 0x8b, 0x1b,
0x76, 0xaf, 0x3d, 0xe9, 0xb4, 0xf9, 0xc0, 0x11, 0xfa, 0x41, 0x9b, 0x72, 0x3e, 0x64, 0xb6, 0xb4,
0xdf, 0x9e, 0xdc, 0xa4, 0x43, 0xde, 0xa7, 0x53, 0xd6, 0x8c, 0xbf, 0x97, 0x61, 0x75, 0x97, 0xba,
0xec, 0x08, 0x83, 0xd0, 0xc2, 0x17, 0x63, 0x0c, 0x42, 0xf2, 0x0c, 0x2a, 0xe2, 0x1c, 0xba, 0xb6,
0xa1, 0x6d, 0xd6, 0x3b, 0x8f, 0xcc, 0xcc, 0xa1, 0x99, 0x38, 0x94, 0x8b, 0xe7, 0x76, 0xcf, 0x9c,
0x74, 0x4c, 0x3e, 0x70, 0x4c, 0xe1, 0xd0, 0x54, 0x1c, 0x9a, 0x89, 0x43, 0xd3, 0x4a, 0x33, 0x62,
0x49, 0xab, 0xa4, 0x09, 0x55, 0x1f, 0x27, 0x2c, 0x60, 0x9e, 0xab, 0xcf, 0x6f, 0x68, 0x9b, 0x35,
0x2b, 0xdd, 0x13, 0x1d, 0x96, 0x5d, 0x6f, 0x8b, 0xda, 0x7d, 0xd4, 0x17, 0x36, 0xb4, 0xcd, 0xaa,
0x95, 0x6c, 0xc9, 0x06, 0xd4, 0x29, 0xe7, 0x8f, 0xe9, 0x21, 0x0e, 0x77, 0xf0, 0x58, 0xaf, 0x48,
0x45, 0x95, 0x24, 0x74, 0x29, 0xe7, 0x4f, 0xe8, 0x08, 0xf5, 0x45, 0xc9, 0x4d, 0xb6, 0x64, 0x1d,
0x6a, 0x2e, 0x1d, 0x61, 0xc0, 0xa9, 0x8d, 0x7a, 0x55, 0xf2, 0x32, 0x02, 0xf9, 0x16, 0xd6, 0x94,
0xc0, 0x0f, 0xbc, 0xb1, 0x6f, 0xa3, 0x0e, 0xf2, 0xe8, 0x7b, 0xb3, 0x1d, 0xbd, 0x5b, 0x34, 0x6b,
0x4d, 0x7b, 0x22, 0x5f, 0xc1, 0xa2, 0x2c, 0x1a, 0xbd, 0xbe, 0xb1, 0xf0, 0x5a, 0xb3, 0x1d, 0x99,
0x25, 0x2e, 0x2c, 0xf3, 0xe1, 0xd8, 0x61, 0x6e, 0xa0, 0x9f, 0x93, 0x1e, 0x9e, 0xce, 0xe6, 0x61,
0xcb, 0x73, 0x8f, 0x98, 0xb3, 0x4b, 0x5d, 0xea, 0xe0, 0x08, 0xdd, 0x70, 0x5f, 0x1a, 0xb7, 0x12,
0x27, 0xe4, 0x25, 0x34, 0x06, 0xe3, 0x20, 0xf4, 0x46, 0xec, 0x25, 0xee, 0x71, 0x59, 0xdc, 0xfa,
0x79, 0x99, 0xcd, 0x27, 0xb3, 0x39, 0xde, 0x29, 0x58, 0xb5, 0xa6, 0xfc, 0x88, 0x22, 0x19, 0x8c,
0x0f, 0xf1, 0x73, 0xf4, 0x65, 0x75, 0xad, 0x44, 0x45, 0xa2, 0x90, 0xa2, 0x32, 0x62, 0xf1, 0x2e,
0xd0, 0x57, 0x37, 0x16, 0xa2, 0x32, 0x4a, 0x49, 0x64, 0x13, 0x56, 0x27, 0xe8, 0xb3, 0xa3, 0xe3,
0x03, 0xe6, 0xb8, 0x34, 0x1c, 0xfb, 0xa8, 0x37, 0x64, 0x29, 0x16, 0xc9, 0x64, 0x04, 0xe7, 0xfb,
0x38, 0x1c, 0x89, 0x94, 0x6f, 0xf9, 0xd8, 0x0b, 0xf4, 0x35, 0x99, 0xdf, 0xed, 0xd9, 0x6f, 0x50,
0x9a, 0xb3, 0xf2, 0xd6, 0x45, 0x60, 0xae, 0x67, 0xc5, 0x2f, 0x25, 0x7a, 0x23, 0x24, 0x0a, 0xac,
0x40, 0x26, 0xd7, 0x60, 0x25, 0xf4, 0xa9, 0x3d, 0x60, 0xae, 0xb3, 0x8b, 0x61, 0xdf, 0xeb, 0xe9,
0x17, 0x64, 0x26, 0x0a, 0x54, 0x63, 0x0c, 0x97, 0x9e, 0xca, 0x67, 0x9f, 0xd6, 0xcc, 0x59, 0x00,
0x80, 0xf1, 0x08, 0x2e, 0x17, 0xdd, 0x06, 0xdc, 0x73, 0x03, 0x24, 0x26, 0x10, 0x99, 0x64, 0x86,
0xbd, 0x8c, 0x2b, 0xa3, 0xa8, 0x5a, 0x25, 0x1c, 0xe3, 0x37, 0x0d, 0x1a, 0x19, 0x78, 0xc5, 0x46,
0xd6, 0xa1, 0x36, 0x8a, 0x69, 0x81, 0xae, 0xc9, 0x0b, 0xce, 0x08, 0x79, 0x2c, 0x98, 0x2f, 0x62,
0xc1, 0x65, 0x58, 0x8a, 0x50, 0x5e, 0xc2, 0x4f, 0xcd, 0x8a, 0x77, 0x39, 0xcc, 0xaa, 0x14, 0x30,
0xab, 0x05, 0x10, 0xc8, 0xa7, 0xfc, 0xf4, 0x98, 0xa3, 0xbe, 0x24, 0xb9, 0x0a, 0x85, 0x18, 0x70,
0x2e, 0xaa, 0x1c, 0x0b, 0x83, 0xf1, 0x30, 0xd4, 0x97, 0xa5, 0x44, 0x8e, 0x66, 0x78, 0xb0, 0xfa,
0x98, 0x89, 0x33, 0x1c, 0x05, 0x67, 0x73, 0x07, 0x1f, 0x42, 0x45, 0x38, 0x13, 0x07, 0x3b, 0xf4,
0xa9, 0x6b, 0xf7, 0x31, 0xc9, 0x55, 0xba, 0x27, 0x04, 0x2a, 0x21, 0x75, 0x02, 0x7d, 0x5e, 0xd2,
0xe5, 0xda, 0xf8, 0x41, 0x8b, 0x22, 0xed, 0x72, 0x1e, 0xbc, 0xf5, 0x76, 0x61, 0x8c, 0x61, 0xb9,
0xcb, 0xb9, 0x88, 0x87, 0xdc, 0x84, 0x0a, 0xe5, 0x3c, 0x3a, 0x44, 0xbd, 0x73, 0xc5, 0x54, 0x5a,
0x73, 0x2c, 0x22, 0xfe, 0x07, 0x0f, 0xdc, 0x50, 0x58, 0x16, 0xa2, 0xcd, 0x8f, 0xa0, 0x96, 0x92,
0x48, 0x03, 0x16, 0x06, 0x18, 0xd5, 0x5a, 0xcd, 0x12, 0x4b, 0x72, 0x11, 0x16, 0x27, 0x74, 0x38,
0x4e, 0xaa, 0x24, 0xda, 0xdc, 0x9d, 0xbf, 0xa3, 0x19, 0xbf, 0x54, 0xe0, 0xff, 0x22, 0xce, 0x03,
0x59, 0x1c, 0x5d, 0xce, 0xef, 0x63, 0x48, 0xd9, 0x30, 0xf8, 0x74, 0x8c, 0xfe, 0xf1, 0x1b, 0x4e,
0x87, 0x03, 0x4b, 0x51, 0x6d, 0xc9, 0xb0, 0xde, 0x40, 0x8b, 0x8a, 0xcd, 0x67, 0x7d, 0x69, 0xe1,
0xcd, 0xf4, 0xa5, 0xb2, 0x3e, 0x51, 0x39, 0xa3, 0x3e, 0x71, 0xf2, 0xa8, 0xa0, 0x0c, 0x20, 0x4b,
0xf9, 0x01, 0xa4, 0x04, 0x7e, 0x97, 0xff, 0x2b, 0xfc, 0x56, 0x4b, 0xe1, 0xf7, 0xfb, 0x79, 0xb8,
0x2c, 0xf2, 0x92, 0x15, 0x50, 0x8a, 0x61, 0xe2, 0xe9, 0x09, 0x34, 0x89, 0xca, 0x51, 0xae, 0xc9,
0x6d, 0x58, 0x1e, 0x04, 0x9e, 0xeb, 0x62, 0x18, 0x5f, 0x7d, 0x53, 0x2d, 0xf2, 0x9d, 0x88, 0xd5,
0xe5, 0xfc, 0x80, 0xa3, 0x6d, 0x25, 0xa2, 0xe4, 0x3a, 0x54, 0x44, 0x1b, 0x91, 0x78, 0x56, 0xef,
0xfc, 0x4f, 0x55, 0x79, 0x84, 0xc3, 0x51, 0x22, 0x2f, 0x85, 0xc8, 0x5d, 0xa8, 0xa5, 0xb9, 0x8a,
0x2f, 0x63, 0x3d, 0xe7, 0x24, 0x61, 0x26, 0x6a, 0x99, 0xb8, 0xd0, 0xed, 0x31, 0x1f, 0x6d, 0x09,
0xd9, 0x8b, 0xd3, 0xba, 0xf7, 0x13, 0x66, 0xaa, 0x9b, 0x8a, 0x1b, 0xbf, 0x6a, 0x70, 0x35, 0x7b,
0x50, 0x49, 0x36, 0x77, 0x31, 0xa4, 0x3d, 0x1a, 0xd2, 0xb7, 0x3f, 0x96, 0x5e, 0x83, 0x15, 0xbb,
0x8f, 0xf6, 0x20, 0x1b, 0x09, 0xa2, 0xe9, 0xb4, 0x40, 0x35, 0x7e, 0x9f, 0x87, 0x95, 0xfc, 0x45,
0x88, 0x9b, 0x14, 0xed, 0x25, 0xb9, 0x49, 0xb1, 0x26, 0xfb, 0x70, 0x0e, 0xdd, 0x09, 0xf3, 0x3d,
0x57, 0x0c, 0x50, 0xc9, 0x0b, 0xfb, 0xe0, 0xe4, 0xeb, 0x34, 0x1f, 0x28, 0xe2, 0x11, 0x84, 0xe5,
0x2c, 0x10, 0x17, 0x80, 0x53, 0x9f, 0x8e, 0x30, 0x44, 0x5f, 0x3c, 0xa3, 0x85, 0xd7, 0xf0, 0x8c,
0xa2, 0x08, 0xf6, 0x13, 0xb3, 0x96, 0xe2, 0xa1, 0xf9, 0x1c, 0xd6, 0xa6, 0x42, 0x2a, 0x81, 0xd0,
0xdb, 0x2a, 0x84, 0xd6, 0x3b, 0xad, 0x92, 0x13, 0x2a, 0x66, 0x54, 0x88, 0xfd, 0x79, 0x1e, 0xea,
0x4a, 0x7d, 0x96, 0xa6, 0xb1, 0x05, 0x20, 0x15, 0x1e, 0xb2, 0x21, 0x46, 0x49, 0xac, 0x59, 0x0a,
0x85, 0x0c, 0x4a, 0x92, 0xb2, 0x33, 0x5b, 0x52, 0x44, 0x48, 0xa5, 0x19, 0x11, 0x93, 0x83, 0x74,
0x1d, 0xc4, 0x88, 0x12, 0xef, 0xc8, 0x37, 0xb0, 0x72, 0xc4, 0x86, 0xb8, 0x9f, 0x05, 0xb2, 0x24,
0x03, 0xd9, 0x9b, 0x3d, 0x90, 0x87, 0xaa, 0x5d, 0xab, 0xe0, 0xc6, 0x78, 0x1f, 0x1a, 0xc5, 0xe7,
0x2a, 0x82, 0x64, 0x23, 0xea, 0xa4, 0xd9, 0x8a, 0x77, 0xc6, 0x8f, 0x1a, 0x90, 0xe9, 0xfb, 0x38,
0x29, 0xe9, 0x83, 0x3b, 0x41, 0x32, 0x61, 0x47, 0x0f, 0x45, 0xa1, 0x90, 0x1d, 0xa8, 0xf7, 0x30,
0x08, 0x99, 0x2b, 0x03, 0x8e, 0x41, 0xe4, 0xbd, 0xd3, 0x2f, 0xfe, 0x7e, 0xa6, 0x60, 0xa9, 0xda,
0xc6, 0x67, 0x70, 0xe5, 0x54, 0x69, 0x65, 0x5e, 0xd3, 0x72, 0xf3, 0xda, 0xa9, 0x53, 0x9e, 0x41,
0xa0, 0x51, 0x44, 0x23, 0xe3, 0x05, 0xac, 0x89, 0x9c, 0x6e, 0xf5, 0xa9, 0x1f, 0x9e, 0xd1, 0x0c,
0x76, 0x0f, 0x6a, 0xa9, 0xcb, 0xd2, 0x5c, 0x37, 0xa1, 0x3a, 0x49, 0x7e, 0xa9, 0x44, 0x43, 0x58,
0xba, 0x37, 0xba, 0x40, 0xd4, 0x78, 0xe3, 0xbe, 0x71, 0x1d, 0x16, 0x59, 0x88, 0xa3, 0x64, 0x0c,
0xba, 0x54, 0x84, 0x7b, 0x29, 0x6e, 0x45, 0x32, 0x9d, 0xef, 0x16, 0x61, 0x2d, 0x43, 0x5d, 0xf1,
0x97, 0xd9, 0x48, 0xf6, 0xa0, 0xb1, 0x1d, 0x7f, 0x23, 0x48, 0x46, 0x6b, 0xf2, 0x8e, 0x6a, 0xa7,
0xf0, 0xb5, 0xa0, 0xb9, 0x5e, 0xce, 0x8c, 0x22, 0x32, 0xe6, 0xc8, 0x17, 0xb0, 0x92, 0x1f, 0xf7,
0xc9, 0x55, 0x55, 0xa3, 0xf4, 0x17, 0x48, 0xd3, 0x38, 0x4d, 0x24, 0x35, 0x7d, 0x0f, 0xaa, 0xc9,
0xd8, 0x9c, 0x8f, 0xb1, 0x30, 0x4c, 0x37, 0x1b, 0x2a, 0x53, 0x30, 0x8c, 0x39, 0xf2, 0x71, 0xa4,
0x2c, 0x46, 0xc0, 0x69, 0x65, 0x65, 0xbe, 0x6d, 0x5e, 0x28, 0x19, 0x26, 0x8d, 0x39, 0xf2, 0x0c,
0xce, 0x6f, 0x4b, 0x84, 0x8e, 0x9b, 0x37, 0x79, 0x37, 0xef, 0xe4, 0x84, 0xf9, 0x30, 0x7f, 0xb4,
0xf2, 0xfe, 0x6f, 0xcc, 0x91, 0x9f, 0x34, 0xb8, 0xb0, 0x8d, 0x61, 0xb1, 0x17, 0x92, 0x1b, 0xe5,
0x4e, 0x4e, 0xe8, 0x99, 0xcd, 0x27, 0xb3, 0xd6, 0x6c, 0xde, 0xac, 0x31, 0x47, 0xf6, 0xe5, 0xb1,
0xb3, 0xda, 0x23, 0x57, 0x4a, 0x8b, 0x2c, 0xcd, 0x5e, 0xeb, 0x24, 0x76, 0x72, 0xd4, 0x4f, 0xba,
0x7f, 0xbc, 0x6a, 0x69, 0x7f, 0xbe, 0x6a, 0x69, 0x7f, 0xbd, 0x6a, 0x69, 0x5f, 0xde, 0xfa, 0x97,
0xcf, 0x5b, 0xca, 0x97, 0x38, 0xca, 0x99, 0x3d, 0x64, 0xe8, 0x86, 0x87, 0x4b, 0xf2, 0x63, 0xd6,
0xad, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x25, 0x4e, 0x84, 0xa1, 0xa8, 0x13, 0x00, 0x00,
// 1447 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0xdd, 0x6e, 0x1b, 0xc5,
0x17, 0xcf, 0x3a, 0x76, 0x62, 0x1f, 0xb7, 0x89, 0x33, 0xfd, 0xf8, 0xef, 0xdf, 0xa4, 0x56, 0xba,
0x12, 0x55, 0xa0, 0x74, 0xad, 0xba, 0x15, 0x54, 0xad, 0x84, 0x64, 0xd2, 0x36, 0x45, 0x69, 0x9a,
0xb0, 0x29, 0x48, 0xa0, 0x8a, 0x6a, 0xb2, 0x9e, 0xac, 0x07, 0xdb, 0xbb, 0xd3, 0xdd, 0xb1, 0x51,
0x2a, 0x71, 0x89, 0xb8, 0xe0, 0x1a, 0xde, 0x83, 0x27, 0xe0, 0x8a, 0x8f, 0x4b, 0x1e, 0x01, 0xf5,
0x82, 0xe7, 0x40, 0x33, 0xfb, 0x35, 0xbb, 0x5e, 0x87, 0x4a, 0x6e, 0xd2, 0x9b, 0x64, 0xe6, 0xcc,
0xf9, 0x9a, 0x73, 0xce, 0xfc, 0xce, 0xf1, 0xc2, 0x35, 0x9f, 0x30, 0x2f, 0x20, 0xfe, 0x84, 0xf8,
0x6d, 0xb9, 0xa4, 0xdc, 0xf3, 0x8f, 0x95, 0xa5, 0xc9, 0x7c, 0x8f, 0x7b, 0x08, 0x52, 0x4a, 0xf3,
0xa2, 0xe3, 0x39, 0x9e, 0x24, 0xb7, 0xc5, 0x2a, 0xe4, 0x68, 0xae, 0x3b, 0x9e, 0xe7, 0x0c, 0x49,
0x1b, 0x33, 0xda, 0xc6, 0xae, 0xeb, 0x71, 0xcc, 0xa9, 0xe7, 0x06, 0xd1, 0xa9, 0x31, 0xb8, 0x13,
0x98, 0xd4, 0x93, 0xa7, 0xb6, 0xe7, 0x93, 0xf6, 0xe4, 0x66, 0xdb, 0x21, 0x2e, 0xf1, 0x31, 0x27,
0xbd, 0x88, 0xe7, 0xb1, 0x43, 0x79, 0x7f, 0x7c, 0x68, 0xda, 0xde, 0xa8, 0x8d, 0x7d, 0x69, 0xe2,
0x1b, 0xb9, 0xb8, 0x61, 0xf7, 0xda, 0x93, 0x4e, 0x9b, 0x0d, 0x1c, 0x21, 0x1f, 0xb4, 0x31, 0x63,
0x43, 0x6a, 0x4b, 0xfd, 0xed, 0xc9, 0x4d, 0x3c, 0x64, 0x7d, 0x3c, 0xa5, 0xcd, 0xf8, 0xa7, 0x0a,
0xab, 0xbb, 0xd8, 0xa5, 0x47, 0x24, 0xe0, 0x16, 0x79, 0x31, 0x26, 0x01, 0x47, 0xcf, 0xa0, 0x2c,
0xee, 0xa1, 0x6b, 0x1b, 0xda, 0x66, 0xbd, 0xf3, 0xc8, 0x4c, 0x0d, 0x9a, 0xb1, 0x41, 0xb9, 0x78,
0x6e, 0xf7, 0xcc, 0x49, 0xc7, 0x64, 0x03, 0xc7, 0x14, 0x06, 0x4d, 0xc5, 0xa0, 0x19, 0x1b, 0x34,
0xad, 0x24, 0x22, 0x96, 0xd4, 0x8a, 0x9a, 0x50, 0xf5, 0xc9, 0x84, 0x06, 0xd4, 0x73, 0xf5, 0xd2,
0x86, 0xb6, 0x59, 0xb3, 0x92, 0x3d, 0xd2, 0x61, 0xd9, 0xf5, 0xb6, 0xb0, 0xdd, 0x27, 0xfa, 0xe2,
0x86, 0xb6, 0x59, 0xb5, 0xe2, 0x2d, 0xda, 0x80, 0x3a, 0x66, 0xec, 0x31, 0x3e, 0x24, 0xc3, 0x1d,
0x72, 0xac, 0x97, 0xa5, 0xa0, 0x4a, 0x12, 0xb2, 0x98, 0xb1, 0x27, 0x78, 0x44, 0xf4, 0x8a, 0x3c,
0x8d, 0xb7, 0x68, 0x1d, 0x6a, 0x2e, 0x1e, 0x91, 0x80, 0x61, 0x9b, 0xe8, 0x55, 0x79, 0x96, 0x12,
0xd0, 0x77, 0xb0, 0xa6, 0x38, 0x7e, 0xe0, 0x8d, 0x7d, 0x9b, 0xe8, 0x20, 0xaf, 0xbe, 0x37, 0xdf,
0xd5, 0xbb, 0x79, 0xb5, 0xd6, 0xb4, 0x25, 0xf4, 0x35, 0x54, 0x64, 0xd1, 0xe8, 0xf5, 0x8d, 0xc5,
0x37, 0x1a, 0xed, 0x50, 0x2d, 0x72, 0x61, 0x99, 0x0d, 0xc7, 0x0e, 0x75, 0x03, 0xfd, 0x9c, 0xb4,
0xf0, 0x74, 0x3e, 0x0b, 0x5b, 0x9e, 0x7b, 0x44, 0x9d, 0x5d, 0xec, 0x62, 0x87, 0x8c, 0x88, 0xcb,
0xf7, 0xa5, 0x72, 0x2b, 0x36, 0x82, 0x5e, 0x42, 0x63, 0x30, 0x0e, 0xb8, 0x37, 0xa2, 0x2f, 0xc9,
0x1e, 0x93, 0xc5, 0xad, 0x9f, 0x97, 0xd1, 0x7c, 0x32, 0x9f, 0xe1, 0x9d, 0x9c, 0x56, 0x6b, 0xca,
0x8e, 0x28, 0x92, 0xc1, 0xf8, 0x90, 0x7c, 0x41, 0x7c, 0x59, 0x5d, 0x2b, 0x61, 0x91, 0x28, 0xa4,
0xb0, 0x8c, 0x68, 0xb4, 0x0b, 0xf4, 0xd5, 0x8d, 0xc5, 0xb0, 0x8c, 0x12, 0x12, 0xda, 0x84, 0xd5,
0x09, 0xf1, 0xe9, 0xd1, 0xf1, 0x01, 0x75, 0x5c, 0xcc, 0xc7, 0x3e, 0xd1, 0x1b, 0xb2, 0x14, 0xf3,
0x64, 0x34, 0x82, 0xf3, 0x7d, 0x32, 0x1c, 0x89, 0x90, 0x6f, 0xf9, 0xa4, 0x17, 0xe8, 0x6b, 0x32,
0xbe, 0xdb, 0xf3, 0x67, 0x50, 0xaa, 0xb3, 0xb2, 0xda, 0x85, 0x63, 0xae, 0x67, 0x45, 0x2f, 0x25,
0x7c, 0x23, 0x28, 0x74, 0x2c, 0x47, 0x46, 0xd7, 0x60, 0x85, 0xfb, 0xd8, 0x1e, 0x50, 0xd7, 0xd9,
0x25, 0xbc, 0xef, 0xf5, 0xf4, 0x0b, 0x32, 0x12, 0x39, 0x2a, 0x1a, 0x40, 0x5d, 0x98, 0x88, 0xb3,
0x74, 0x49, 0x66, 0xe9, 0xd3, 0xf9, 0xdc, 0x7f, 0x94, 0x2a, 0xb4, 0x54, 0xed, 0xc6, 0x18, 0x2e,
0x3d, 0x95, 0x18, 0x93, 0x14, 0xe8, 0x59, 0xa0, 0x8d, 0xf1, 0x08, 0x2e, 0xe7, 0xcd, 0x06, 0xcc,
0x73, 0x03, 0x82, 0x4c, 0x40, 0x32, 0xa3, 0x94, 0xf4, 0xd2, 0x53, 0xe9, 0x45, 0xd5, 0x2a, 0x38,
0x31, 0x7e, 0xd7, 0xa0, 0x91, 0x22, 0x65, 0xa4, 0x64, 0x1d, 0x6a, 0xa3, 0x88, 0x16, 0xe8, 0x9a,
0xac, 0xa6, 0x94, 0x90, 0x05, 0x9e, 0x52, 0x1e, 0x78, 0x2e, 0xc3, 0x52, 0xd8, 0x52, 0x24, 0xd6,
0xd5, 0xac, 0x68, 0x97, 0x01, 0xc8, 0x72, 0x0e, 0x20, 0x5b, 0x00, 0x81, 0xc4, 0x8d, 0xa7, 0xc7,
0x8c, 0xe8, 0x4b, 0xf2, 0x54, 0xa1, 0x20, 0x03, 0xce, 0x85, 0x65, 0x6a, 0x91, 0x60, 0x3c, 0xe4,
0xfa, 0xb2, 0xe4, 0xc8, 0xd0, 0x0c, 0x0f, 0x56, 0x1f, 0x53, 0x71, 0x87, 0xa3, 0xe0, 0x6c, 0x72,
0xf0, 0x21, 0x94, 0x85, 0x31, 0x71, 0xb1, 0x43, 0x1f, 0xbb, 0x76, 0x9f, 0xc4, 0xb1, 0x4a, 0xf6,
0x08, 0x41, 0x99, 0x63, 0x27, 0xd0, 0x4b, 0x92, 0x2e, 0xd7, 0xc6, 0x8f, 0x5a, 0xe8, 0x69, 0x97,
0xb1, 0xe0, 0xad, 0xf7, 0x26, 0x63, 0x0c, 0xcb, 0x5d, 0xc6, 0x84, 0x3f, 0xe8, 0x26, 0x94, 0x31,
0x63, 0xe1, 0x25, 0xea, 0x9d, 0x2b, 0xa6, 0x32, 0x07, 0x44, 0x2c, 0xe2, 0x7f, 0xf0, 0xc0, 0xe5,
0x42, 0xb3, 0x60, 0x6d, 0x7e, 0x04, 0xb5, 0x84, 0x84, 0x1a, 0xb0, 0x38, 0x20, 0x61, 0xad, 0xd5,
0x2c, 0xb1, 0x44, 0x17, 0xa1, 0x32, 0xc1, 0xc3, 0x71, 0x5c, 0x25, 0xe1, 0xe6, 0x6e, 0xe9, 0x8e,
0x66, 0xfc, 0x52, 0x81, 0xff, 0x0b, 0x3f, 0x0f, 0x64, 0x71, 0x74, 0x19, 0xbb, 0x4f, 0x38, 0xa6,
0xc3, 0xe0, 0xb3, 0x31, 0xf1, 0x8f, 0x4f, 0x39, 0x1c, 0x0e, 0x2c, 0x85, 0xb5, 0x25, 0xdd, 0x3a,
0x85, 0x7e, 0x18, 0xa9, 0x4f, 0x9b, 0xe0, 0xe2, 0xe9, 0x34, 0xc1, 0xa2, 0xa6, 0x54, 0x3e, 0xa3,
0xa6, 0x34, 0x7b, 0x2e, 0x51, 0xa6, 0x9d, 0xa5, 0xec, 0xb4, 0x53, 0x80, 0xf5, 0xcb, 0xaf, 0x8b,
0xf5, 0xd5, 0xd7, 0xc1, 0x7a, 0x38, 0x55, 0xac, 0xff, 0xa1, 0x04, 0x97, 0x45, 0x12, 0xd2, 0x6a,
0x4d, 0x00, 0x53, 0xbc, 0x73, 0x01, 0x5d, 0x61, 0xed, 0xcb, 0x35, 0xba, 0x0d, 0xcb, 0x83, 0xc0,
0x73, 0x5d, 0xc2, 0xa3, 0x3a, 0x6b, 0xaa, 0x2f, 0x6a, 0x27, 0x3c, 0xea, 0x32, 0x76, 0xc0, 0x88,
0x6d, 0xc5, 0xac, 0xe8, 0x3a, 0x94, 0x85, 0x4d, 0x09, 0x9e, 0xf5, 0xce, 0xff, 0x54, 0x11, 0xe1,
0x58, 0xcc, 0x2f, 0x99, 0xd0, 0x5d, 0xa8, 0x25, 0x89, 0x89, 0x32, 0xbf, 0x9e, 0x31, 0x12, 0x1f,
0xc6, 0x62, 0x29, 0xbb, 0x90, 0xed, 0x51, 0x9f, 0xd8, 0xb2, 0x3f, 0x54, 0xa6, 0x65, 0xef, 0xc7,
0x87, 0x89, 0x6c, 0xc2, 0x6e, 0xfc, 0xa6, 0xc1, 0xd5, 0xf4, 0xf5, 0xc6, 0xa9, 0xdb, 0x25, 0x1c,
0xf7, 0x30, 0xc7, 0x6f, 0x7f, 0xe0, 0xbe, 0x06, 0x2b, 0x76, 0x9f, 0xd8, 0x83, 0x74, 0xd8, 0x09,
0xe7, 0xee, 0x1c, 0xd5, 0xf8, 0xa3, 0x04, 0x2b, 0xd9, 0x44, 0x88, 0x4c, 0x8a, 0x5e, 0x16, 0x67,
0x52, 0xac, 0xd1, 0x3e, 0x9c, 0x23, 0xee, 0x84, 0xfa, 0x9e, 0x2b, 0x46, 0xc3, 0xf8, 0x39, 0x7f,
0x30, 0x3b, 0x9d, 0xe6, 0x03, 0x85, 0x3d, 0xc4, 0xcb, 0x8c, 0x06, 0xe4, 0x02, 0x30, 0xec, 0xe3,
0x11, 0xe1, 0xc4, 0x17, 0x6f, 0x76, 0xf1, 0x0d, 0xbc, 0xd9, 0xd0, 0x83, 0xfd, 0x58, 0xad, 0xa5,
0x58, 0x68, 0x3e, 0x87, 0xb5, 0x29, 0x97, 0x0a, 0xf0, 0xfa, 0xb6, 0x8a, 0xd7, 0xf5, 0x4e, 0xab,
0xe0, 0x86, 0x8a, 0x1a, 0x15, 0xcf, 0x7f, 0x2d, 0x41, 0x5d, 0xa9, 0xcf, 0xc2, 0x30, 0xb6, 0x00,
0xa4, 0xc0, 0x43, 0x3a, 0x24, 0x61, 0x10, 0x6b, 0x96, 0x42, 0x41, 0x83, 0x82, 0xa0, 0xec, 0xcc,
0xff, 0x96, 0x0b, 0x23, 0x22, 0xc6, 0x14, 0x69, 0x3a, 0x88, 0xe0, 0x2b, 0xda, 0xa1, 0x6f, 0x61,
0xe5, 0x88, 0x0e, 0xc9, 0x7e, 0xea, 0xc8, 0x92, 0x74, 0x64, 0x6f, 0x7e, 0x47, 0x1e, 0xaa, 0x7a,
0xad, 0x9c, 0x19, 0xe3, 0x7d, 0x68, 0xe4, 0x9f, 0xab, 0x70, 0x92, 0x8e, 0xb0, 0x93, 0x44, 0x2b,
0xda, 0x19, 0x3f, 0x69, 0x80, 0xa6, 0xf3, 0x31, 0x2b, 0xe8, 0x83, 0x3b, 0x41, 0xfc, 0xdb, 0x21,
0x7c, 0x28, 0x0a, 0x05, 0xed, 0x40, 0xbd, 0x47, 0x02, 0x4e, 0x5d, 0xe9, 0x70, 0x04, 0x22, 0xef,
0x9d, 0x9c, 0xf8, 0xfb, 0xa9, 0x80, 0xa5, 0x4a, 0x1b, 0x9f, 0xc3, 0x95, 0x13, 0xb9, 0x95, 0xe1,
0x50, 0xcb, 0x0c, 0x87, 0x27, 0x8e, 0x94, 0x06, 0x82, 0x46, 0x1e, 0x8d, 0x8c, 0x17, 0xb0, 0x26,
0x62, 0xba, 0xd5, 0xc7, 0x3e, 0x3f, 0xa3, 0x81, 0xef, 0x1e, 0xd4, 0x12, 0x93, 0x85, 0xb1, 0x6e,
0x42, 0x75, 0x12, 0xff, 0x06, 0x0b, 0x27, 0xbe, 0x64, 0x6f, 0x74, 0x01, 0xa9, 0xfe, 0x46, 0x7d,
0xe3, 0x3a, 0x54, 0x28, 0x27, 0xa3, 0x78, 0xe6, 0xba, 0x94, 0x87, 0x7b, 0xc9, 0x6e, 0x85, 0x3c,
0x9d, 0xef, 0x2b, 0xb0, 0x96, 0xa2, 0xae, 0xf8, 0x4b, 0x6d, 0x82, 0xf6, 0xa0, 0xb1, 0x1d, 0x7d,
0xfd, 0x88, 0xe7, 0x78, 0xf4, 0x8e, 0xaa, 0x27, 0xf7, 0x1d, 0xa4, 0xb9, 0x5e, 0x7c, 0x18, 0x7a,
0x64, 0x2c, 0xa0, 0x2f, 0x61, 0x25, 0xfb, 0xdb, 0x02, 0x5d, 0x55, 0x25, 0x0a, 0x7f, 0xee, 0x34,
0x8d, 0x93, 0x58, 0x12, 0xd5, 0xf7, 0xa0, 0x1a, 0xcf, 0xe8, 0x59, 0x1f, 0x73, 0x93, 0x7b, 0xb3,
0xa1, 0x1e, 0x8a, 0x03, 0x63, 0x01, 0x7d, 0x1c, 0x0a, 0x8b, 0x79, 0x73, 0x5a, 0x58, 0x19, 0xa6,
0x9b, 0x17, 0x0a, 0x26, 0x57, 0x63, 0x01, 0x3d, 0x83, 0xf3, 0xdb, 0x12, 0xa1, 0xa3, 0xe6, 0x8d,
0xde, 0xcd, 0x1a, 0x99, 0x31, 0x8c, 0x66, 0xaf, 0x56, 0xdc, 0xff, 0x8d, 0x05, 0xf4, 0xb3, 0x06,
0x17, 0xb6, 0x09, 0xcf, 0xf7, 0x42, 0x74, 0xa3, 0xd8, 0xc8, 0x8c, 0x9e, 0xd9, 0x7c, 0x32, 0x6f,
0xcd, 0x66, 0xd5, 0x1a, 0x0b, 0x68, 0x5f, 0x5e, 0x3b, 0xad, 0x3d, 0x74, 0xa5, 0xb0, 0xc8, 0x92,
0xe8, 0xb5, 0x66, 0x1d, 0xc7, 0x57, 0xfd, 0xa4, 0xfb, 0xe7, 0xab, 0x96, 0xf6, 0xd7, 0xab, 0x96,
0xf6, 0xf7, 0xab, 0x96, 0xf6, 0xd5, 0xad, 0xff, 0xf8, 0x70, 0xa7, 0x7c, 0x63, 0xc4, 0x8c, 0xda,
0x43, 0x4a, 0x5c, 0x7e, 0xb8, 0x24, 0x3f, 0xd3, 0xdd, 0xfa, 0x37, 0x00, 0x00, 0xff, 0xff, 0x98,
0x26, 0x41, 0x78, 0x82, 0x14, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@@ -1809,6 +1827,20 @@ func (m *ManifestRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if m.HelmOptions != nil {
{
size, err := m.HelmOptions.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintRepository(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xaa
}
if len(m.TrackingMethod) > 0 {
i -= len(m.TrackingMethod)
copy(dAtA[i:], m.TrackingMethod)
@@ -2326,6 +2358,18 @@ func (m *RepoServerAppDetailsQuery) MarshalToSizedBuffer(dAtA []byte) (int, erro
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if m.HelmOptions != nil {
{
size, err := m.HelmOptions.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintRepository(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x52
}
if len(m.TrackingMethod) > 0 {
i -= len(m.TrackingMethod)
copy(dAtA[i:], m.TrackingMethod)
@@ -3069,6 +3113,10 @@ func (m *ManifestRequest) Size() (n int) {
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
if m.HelmOptions != nil {
l = m.HelmOptions.Size()
n += 2 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -3262,6 +3310,10 @@ func (m *RepoServerAppDetailsQuery) Size() (n int) {
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.HelmOptions != nil {
l = m.HelmOptions.Size()
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -4055,6 +4107,42 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
}
m.TrackingMethod = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 21:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field HelmOptions", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.HelmOptions == nil {
m.HelmOptions = &v1alpha1.HelmOptions{}
}
if err := m.HelmOptions.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
@@ -5252,6 +5340,42 @@ func (m *RepoServerAppDetailsQuery) Unmarshal(dAtA []byte) error {
}
m.TrackingMethod = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 10:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field HelmOptions", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.HelmOptions == nil {
m.HelmOptions = &v1alpha1.HelmOptions{}
}
if err := m.HelmOptions.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])

View File

@@ -26,6 +26,7 @@ import (
jsonpatch "github.com/evanphx/json-patch"
"github.com/ghodss/yaml"
"github.com/google/go-jsonnet"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/semaphore"
"google.golang.org/grpc/codes"
@@ -49,9 +50,9 @@ import (
"github.com/argoproj/argo-cd/v2/util/gpg"
"github.com/argoproj/argo-cd/v2/util/helm"
"github.com/argoproj/argo-cd/v2/util/io"
pathutil "github.com/argoproj/argo-cd/v2/util/io/path"
"github.com/argoproj/argo-cd/v2/util/ksonnet"
"github.com/argoproj/argo-cd/v2/util/kustomize"
"github.com/argoproj/argo-cd/v2/util/security"
"github.com/argoproj/argo-cd/v2/util/text"
)
@@ -567,7 +568,7 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie
APIVersions: q.ApiVersions,
Set: map[string]string{},
SetString: map[string]string{},
SetFile: map[string]string{},
SetFile: map[string]pathutil.ResolvedFilePath{},
}
appHelm := q.ApplicationSource.Helm
@@ -582,46 +583,28 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie
}
for _, val := range appHelm.ValueFiles {
// If val is not a URL, run it against the directory enforcer. If it is a URL, use it without checking
if _, err := url.ParseRequestURI(val); err != nil {
// Ensure that the repo root provided is absolute
absRepoPath, err := filepath.Abs(repoRoot)
if err != nil {
return nil, err
}
// If the path to the file is relative, join it with the current working directory (appPath)
path := val
if !filepath.IsAbs(path) {
absWorkDir, err := filepath.Abs(appPath)
if err != nil {
return nil, err
}
path = filepath.Join(absWorkDir, path)
}
_, err = security.EnforceToCurrentRoot(absRepoPath, path)
if err != nil {
return nil, err
}
}
templateOpts.Values = append(templateOpts.Values, val)
}
if appHelm.Values != "" {
file, err := ioutil.TempFile("", "values-*.yaml")
// This will resolve val to an absolute path (or an URL)
path, _, err := pathutil.ResolveFilePath(appPath, repoRoot, val, q.GetValuesFileSchemes())
if err != nil {
return nil, err
}
p := file.Name()
templateOpts.Values = append(templateOpts.Values, path)
}
if appHelm.Values != "" {
rand, err := uuid.NewRandom()
if err != nil {
return nil, err
}
p := path.Join(os.TempDir(), rand.String())
defer func() { _ = os.RemoveAll(p) }()
err = ioutil.WriteFile(p, []byte(appHelm.Values), 0644)
if err != nil {
return nil, err
}
defer file.Close()
templateOpts.Values = append(templateOpts.Values, p)
templateOpts.Values = append(templateOpts.Values, pathutil.ResolvedFilePath(p))
}
for _, p := range appHelm.Parameters {
@@ -632,7 +615,11 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie
}
}
for _, p := range appHelm.FileParameters {
templateOpts.SetFile[p.Name] = p.Path
resolvedPath, _, err := pathutil.ResolveFilePath(appPath, repoRoot, env.Envsubst(p.Path), q.GetValuesFileSchemes())
if err != nil {
return nil, err
}
templateOpts.SetFile[p.Name] = resolvedPath
}
passCredentials = appHelm.PassCredentials
}
@@ -645,9 +632,6 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie
for i, j := range templateOpts.SetString {
templateOpts.SetString[i] = env.Envsubst(j)
}
for i, j := range templateOpts.SetFile {
templateOpts.SetFile[i] = env.Envsubst(j)
}
repos, err := getHelmDependencyRepos(appPath)
if err != nil {
@@ -1087,11 +1071,11 @@ func makeJsonnetVm(appPath string, repoRoot string, sourceJsonnet v1alpha1.Appli
// Jsonnet Imports relative to the repository path
jpaths := []string{appPath}
for _, p := range sourceJsonnet.Libs {
jpath := path.Join(repoRoot, p)
if !strings.HasPrefix(jpath, repoRoot) {
return nil, status.Errorf(codes.FailedPrecondition, "%s: referenced library points outside the repository", p)
jpath, _, err := pathutil.ResolveFilePath(appPath, repoRoot, p, nil)
if err != nil {
return nil, err
}
jpaths = append(jpaths, jpath)
jpaths = append(jpaths, string(jpath))
}
vm.Importer(&jsonnet.FileImporter{
@@ -1259,7 +1243,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
return err
}
case v1alpha1.ApplicationSourceTypeHelm:
if err := populateHelmAppDetails(res, ctx.appPath, q); err != nil {
if err := populateHelmAppDetails(res, ctx.appPath, repoRoot, q); err != nil {
return err
}
case v1alpha1.ApplicationSourceTypeKustomize:
@@ -1321,7 +1305,7 @@ func populateKsonnetAppDetails(res *apiclient.RepoAppDetailsResponse, appPath st
return nil
}
func populateHelmAppDetails(res *apiclient.RepoAppDetailsResponse, appPath string, q *apiclient.RepoServerAppDetailsQuery) error {
func populateHelmAppDetails(res *apiclient.RepoAppDetailsResponse, appPath string, repoRoot string, q *apiclient.RepoServerAppDetailsQuery) error {
var selectedValueFiles []string
if q.Source.Helm != nil {
@@ -1355,7 +1339,16 @@ func populateHelmAppDetails(res *apiclient.RepoAppDetailsResponse, appPath strin
if err := loadFileIntoIfExists(filepath.Join(appPath, "values.yaml"), &res.Helm.Values); err != nil {
return err
}
params, err := h.GetParameters(selectedValueFiles)
var resolvedSelectedValueFiles []pathutil.ResolvedFilePath
// drop not allowed values files
for _, file := range selectedValueFiles {
if resolvedFile, _, err := pathutil.ResolveFilePath(appPath, repoRoot, file, q.GetValuesFileSchemes()); err == nil {
resolvedSelectedValueFiles = append(resolvedSelectedValueFiles, resolvedFile)
} else {
log.Debugf("Values file %s is not allowed: %v", file, err)
}
}
params, err := h.GetParameters(resolvedSelectedValueFiles)
if err != nil {
return err
}

View File

@@ -29,6 +29,7 @@ message ManifestRequest {
repeated github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.RepoCreds helmRepoCreds = 17;
bool noRevisionCache = 18;
string trackingMethod = 19;
github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.HelmOptions helmOptions = 21;
}
// TestRepositoryRequest is a query to test repository is valid or not and has valid access.
@@ -84,6 +85,7 @@ message RepoServerAppDetailsQuery {
bool noCache = 6;
bool noRevisionCache = 7;
string trackingMethod = 8;
github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.HelmOptions helmOptions = 10;
}
// RepoAppDetailsResponse application details

View File

@@ -250,7 +250,7 @@ func TestGenerateJsonnetManifestInDir(t *testing.T) {
Jsonnet: argoappv1.ApplicationSourceJsonnet{
ExtVars: []argoappv1.JsonnetVar{{Name: "extVarString", Value: "extVarString"}, {Name: "extVarCode", Value: "\"extVarCode\"", Code: true}},
TLAs: []argoappv1.JsonnetVar{{Name: "tlaString", Value: "tlaString"}, {Name: "tlaCode", Value: "\"tlaCode\"", Code: true}},
Libs: []string{"testdata/jsonnet/vendor"},
Libs: []string{"./vendor"},
},
},
},
@@ -260,6 +260,25 @@ func TestGenerateJsonnetManifestInDir(t *testing.T) {
assert.Equal(t, 2, len(res1.Manifests))
}
func TestGenerateJsonnetLibOutside(t *testing.T) {
service := newService(".")
q := apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
ApplicationSource: &argoappv1.ApplicationSource{
Path: "./testdata/jsonnet",
Directory: &argoappv1.ApplicationSourceDirectory{
Jsonnet: argoappv1.ApplicationSourceJsonnet{
Libs: []string{"../../../testdata/jsonnet/vendor"},
},
},
},
}
_, err := service.GenerateManifest(context.Background(), &q)
require.Error(t, err)
require.Contains(t, err.Error(), "value file '../../../testdata/jsonnet/vendor' resolved to outside repository root")
}
func TestGenerateKsonnetManifest(t *testing.T) {
service := newService("../..")
@@ -728,7 +747,37 @@ func TestHelmManifestFromChartRepoWithValueFileOutsideRepo(t *testing.T) {
}
request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: source, NoCache: true}
_, err := service.GenerateManifest(context.Background(), request)
assert.Error(t, err, "should be on or under current directory")
assert.Error(t, err)
}
func TestHelmManifestFromChartRepoWithValueFileLinks(t *testing.T) {
t.Run("Valid symlink", func(t *testing.T) {
service := newService("../..")
source := &argoappv1.ApplicationSource{
Chart: "my-chart",
TargetRevision: ">= 1.0.0",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"my-chart-link.yaml"},
},
}
request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: source, NoCache: true}
_, err := service.GenerateManifest(context.Background(), request)
assert.NoError(t, err)
})
t.Run("Symlink pointing to outside", func(t *testing.T) {
service := newService("../..")
source := &argoappv1.ApplicationSource{
Chart: "my-chart",
TargetRevision: ">= 1.0.0",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"my-chart-outside-link.yaml"},
},
}
request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: source, NoCache: true}
_, err := service.GenerateManifest(context.Background(), request)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
})
}
func TestGenerateHelmWithURL(t *testing.T) {
@@ -744,6 +793,7 @@ func TestGenerateHelmWithURL(t *testing.T) {
Values: `cluster: {slaveCount: 2}`,
},
},
HelmOptions: &argoappv1.HelmOptions{ValuesFileSchemes: []string{"https"}},
})
assert.NoError(t, err)
}
@@ -751,38 +801,108 @@ func TestGenerateHelmWithURL(t *testing.T) {
// The requested value file (`../../../../../minio/values.yaml`) is outside the repo directory
// (`~/go/src/github.com/argoproj/argo-cd`), so it is blocked
func TestGenerateHelmWithValuesDirectoryTraversalOutsideRepo(t *testing.T) {
service := newService("../..")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: "./util/helm/testdata/redis",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"../../../../../minio/values.yaml"},
Values: `cluster: {slaveCount: 2}`,
t.Run("Values file with relative path pointing outside repo root", func(t *testing.T) {
service := newService("../..")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: "./util/helm/testdata/redis",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"../../../../../minio/values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
},
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
})
assert.Error(t, err, "should be on or under current directory")
service = newService("./testdata/my-chart")
_, err = service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"../my-chart-2/values.yaml"},
Values: `cluster: {slaveCount: 2}`,
t.Run("Values file with relative path pointing inside repo root", func(t *testing.T) {
service := newService("./testdata/my-chart")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"../my-chart/my-chart-values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
},
})
assert.NoError(t, err)
})
t.Run("Values file with absolute path stays within repo root", func(t *testing.T) {
service := newService("./testdata/my-chart")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"/my-chart-values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
})
assert.NoError(t, err)
})
t.Run("Values file with absolute path using back-references outside repo root", func(t *testing.T) {
service := newService("./testdata/my-chart")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"/../../../my-chart-values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
})
t.Run("Remote values file from forbidden protocol", func(t *testing.T) {
service := newService("./testdata/my-chart")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"file://../../../../my-chart-values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "is not allowed")
})
t.Run("Remote values file from custom allowed protocol", func(t *testing.T) {
service := newService("./testdata/my-chart")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppName: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"s3://my-bucket/my-chart-values.yaml"},
},
},
HelmOptions: &argoappv1.HelmOptions{ValuesFileSchemes: []string{"s3"}},
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "s3://my-bucket/my-chart-values.yaml: no such file or directory")
})
assert.Error(t, err, "should be on or under current directory")
}
// The requested file parameter (`/tmp/external-secret.txt`) is outside the app path
// (`./util/helm/testdata/redis`), and outside the repo directory. It is used as a means
// of providing direct content to a helm chart via a specific key.
// File parameter should not allow traversal outside of the repository root
func TestGenerateHelmWithAbsoluteFileParameter(t *testing.T) {
service := newService("../..")
@@ -804,16 +924,14 @@ func TestGenerateHelmWithAbsoluteFileParameter(t *testing.T) {
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"values-production.yaml"},
Values: `cluster: {slaveCount: 2}`,
FileParameters: []argoappv1.HelmFileParameter{
argoappv1.HelmFileParameter{
Name: "passwordContent",
Path: externalSecretPath,
},
},
FileParameters: []argoappv1.HelmFileParameter{{
Name: "passwordContent",
Path: externalSecretPath,
}},
},
},
})
assert.NoError(t, err)
assert.Error(t, err)
}
// The requested file parameter (`../external/external-secret.txt`) is outside the app path

View File

@@ -0,0 +1 @@
my-chart-values.yaml

View File

@@ -0,0 +1 @@
../my-chart-2/my-chart-2-values.yaml

View File

@@ -230,6 +230,7 @@ func (s *Server) queryRepoServer(ctx context.Context, a *v1alpha1.Application, a
repo *appv1.Repository,
helmRepos []*appv1.Repository,
helmCreds []*v1alpha1.RepoCreds,
helmOptions *v1alpha1.HelmOptions,
kustomizeOptions *v1alpha1.KustomizeOptions,
) error) error {
@@ -271,11 +272,16 @@ func (s *Server) queryRepoServer(ctx context.Context, a *v1alpha1.Application, a
if err != nil {
return err
}
helmOptions, err := s.settingsMgr.GetHelmSettings()
if err != nil {
return err
}
permittedHelmCredentials, err := argo.GetPermittedReposCredentials(proj, helmRepositoryCredentials)
if err != nil {
return err
}
return action(client, repo, permittedHelmRepos, permittedHelmCredentials, kustomizeOptions)
return action(client, repo, permittedHelmRepos, permittedHelmCredentials, helmOptions, kustomizeOptions)
}
// GetManifests returns application manifests
@@ -290,7 +296,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
var manifestInfo *apiclient.ManifestResponse
err = s.queryRepoServer(ctx, a, func(
client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, kustomizeOptions *appv1.KustomizeOptions) error {
client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions) error {
revision := a.Spec.Source.TargetRevision
if q.Revision != "" {
revision = q.Revision
@@ -332,6 +338,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
KubeVersion: serverVersion,
ApiVersions: argo.APIResourcesToStrings(apiResources, true),
HelmRepoCreds: helmCreds,
HelmOptions: helmOptions,
TrackingMethod: string(argoutil.GetTrackingMethod(s.settingsMgr)),
})
return err
@@ -407,6 +414,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
repo *appv1.Repository,
helmRepos []*appv1.Repository,
_ []*appv1.RepoCreds,
helmOptions *appv1.HelmOptions,
kustomizeOptions *appv1.KustomizeOptions,
) error {
_, err := client.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{
@@ -417,6 +425,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
Repos: helmRepos,
NoCache: true,
TrackingMethod: string(argoutil.GetTrackingMethod(s.settingsMgr)),
HelmOptions: helmOptions,
})
return err
}); err != nil {

View File

@@ -4,22 +4,24 @@ import (
"fmt"
"reflect"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/argoproj/gitops-engine/pkg/utils/text"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/common"
repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
servercache "github.com/argoproj/argo-cd/v2/server/cache"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/io"
@@ -33,6 +35,8 @@ type Server struct {
repoClientset apiclient.Clientset
enf *rbac.Enforcer
cache *servercache.Cache
appLister applisters.ApplicationNamespaceLister
projLister applisters.AppProjectNamespaceLister
settings *settings.SettingsManager
}
@@ -42,6 +46,8 @@ func NewServer(
db db.ArgoDB,
enf *rbac.Enforcer,
cache *servercache.Cache,
appLister applisters.ApplicationNamespaceLister,
projLister applisters.AppProjectNamespaceLister,
settings *settings.SettingsManager,
) *Server {
return &Server{
@@ -49,14 +55,20 @@ func NewServer(
repoClientset: repoClientset,
enf: enf,
cache: cache,
appLister: appLister,
projLister: projLister,
settings: settings,
}
}
var (
errPermissionDenied = status.Error(codes.PermissionDenied, "permission denied")
)
func (s *Server) getRepo(ctx context.Context, url string) (*appsv1.Repository, error) {
repo, err := s.db.GetRepository(ctx, url)
if err != nil {
return nil, status.Error(codes.PermissionDenied, "permission denied")
return nil, errPermissionDenied
}
return repo, nil
}
@@ -213,14 +225,29 @@ func (s *Server) ListRefs(ctx context.Context, q *repositorypkg.RepoQuery) (*api
})
}
// ListApps returns list of apps in the repo
// ListApps performs discovery of a git repository for potential sources of applications. Used
// as a convenience to the UI for auto-complete.
func (s *Server) ListApps(ctx context.Context, q *repositorypkg.RepoAppsQuery) (*repositorypkg.RepoAppsResponse, error) {
repo, err := s.getRepo(ctx, q.Repo)
if err != nil {
return nil, err
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
claims := ctx.Value("claims")
if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
return nil, err
}
// This endpoint causes us to clone git repos & invoke config management tooling for the purposes
// of app discovery. Only allow this to happen if user has privileges to create or update the
// application which it wants to retrieve these details for.
appRBACresource := fmt.Sprintf("%s/%s", q.AppProject, q.AppName)
if !s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, appRBACresource) &&
!s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, appRBACresource) {
return nil, errPermissionDenied
}
// Also ensure the repo is actually allowed in the project in question
if err := s.isRepoPermittedInProject(q.Repo, q.AppProject); err != nil {
return nil, err
}
@@ -245,6 +272,9 @@ func (s *Server) ListApps(ctx context.Context, q *repositorypkg.RepoAppsQuery) (
return &repositorypkg.RepoAppsResponse{Items: items}, nil
}
// GetAppDetails shows parameter values to various config tools (e.g. helm/kustomize values)
// This is used by UI for parameter form fields during app create & edit pages.
// It is also used when showing history of parameters used in previous syncs in the app history.
func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDetailsQuery) (*apiclient.RepoAppDetailsResponse, error) {
if q.Source == nil {
return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
@@ -253,9 +283,38 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta
if err != nil {
return nil, err
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
claims := ctx.Value("claims")
if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
return nil, err
}
app, err := s.appLister.Get(q.AppName)
appRBACObj := createRBACObject(q.AppProject, q.AppName)
// ensure caller has read privileges to app
if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACObj); err != nil {
return nil, err
}
if apierr.IsNotFound(err) {
// app doesn't exist since it still is being formulated. verify they can create the app
// before we reveal repo details
if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, appRBACObj); err != nil {
return nil, err
}
} else {
// if we get here we are returning repo details of an existing app
if q.AppProject != app.Spec.Project {
return nil, errPermissionDenied
}
// verify caller is not making a request with arbitrary source values which were not in our history
if !isSourceInHistory(app, *q.Source) {
return nil, errPermissionDenied
}
}
// Ensure the repo is actually allowed in the project in question
if err := s.isRepoPermittedInProject(q.Source.RepoURL, q.AppProject); err != nil {
return nil, err
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return nil, err
@@ -273,11 +332,16 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta
if err != nil {
return nil, err
}
helmOptions, err := s.settings.GetHelmSettings()
if err != nil {
return nil, err
}
return repoClient.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{
Repo: repo,
Source: q.Source,
Repos: helmRepos,
KustomizeOptions: kustomizeOptions,
HelmOptions: helmOptions,
AppName: q.AppName,
})
}
@@ -473,3 +537,33 @@ func (s *Server) testRepo(ctx context.Context, repo *appsv1.Repository) error {
})
return err
}
func (s *Server) isRepoPermittedInProject(repo string, projName string) error {
proj, err := s.projLister.Get(projName)
if err != nil {
return err
}
if !proj.IsSourcePermitted(appsv1.ApplicationSource{RepoURL: repo}) {
return status.Errorf(codes.PermissionDenied, "repository '%s' not permitted in project '%s'", repo, projName)
}
return nil
}
// isSourceInHistory checks if the supplied application source is either our current application
// source, or was something which we synced to previously.
func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource) bool {
if source.Equals(app.Spec.Source) {
return true
}
// Iterate history. When comparing items in our history, use the actual synced revision to
// compare with the supplied source.targetRevision in the request. This is because
// history[].source.targetRevision is ambiguous (e.g. HEAD), whereas
// history[].revision will contain the explicit SHA
for _, h := range app.Status.History {
h.Source.TargetRevision = h.Revision
if source.Equals(h.Source) {
return true
}
}
return false
}

View File

@@ -16,6 +16,8 @@ import "github.com/argoproj/argo-cd/v2/reposerver/repository/repository.proto";
message RepoAppsQuery {
string repo = 1;
string revision = 2;
string appName = 3;
string appProject = 4;
}
@@ -29,6 +31,7 @@ message AppInfo {
message RepoAppDetailsQuery {
github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.ApplicationSource source = 1;
string appName = 2;
string appProject = 3;
}
// RepoAppsResponse contains applications of specified repository

View File

@@ -6,43 +6,143 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/server/cache"
"github.com/stretchr/testify/mock"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
"github.com/argoproj/argo-cd/v2/util/db"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/util/assets"
"github.com/argoproj/argo-cd/v2/util/rbac"
"github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
fakeapps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
"github.com/stretchr/testify/assert"
"github.com/dgrijalva/jwt-go/v4"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/server/cache"
"github.com/argoproj/argo-cd/v2/util/assets"
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/db"
dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
"github.com/argoproj/argo-cd/v2/util/rbac"
"github.com/argoproj/argo-cd/v2/util/settings"
)
const testNamespace = "default"
var (
argocdCM = corev1.ConfigMap{
ObjectMeta: v1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
}
argocdSecret = corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "argocd-secret",
Namespace: testNamespace,
},
Data: map[string][]byte{
"admin.password": []byte("test"),
"server.secretkey": []byte("test"),
},
}
defaultProj = &appsv1.AppProject{
TypeMeta: metav1.TypeMeta{
Kind: "AppProject",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: testNamespace,
},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
},
}
defaultProjNoSources = &appsv1.AppProject{
TypeMeta: metav1.TypeMeta{
Kind: "AppProject",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: testNamespace,
},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
},
}
guestbookApp = &appsv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "guestbook",
Namespace: testNamespace,
},
Spec: appsv1.ApplicationSpec{
Project: "default",
Source: appsv1.ApplicationSource{
RepoURL: "https://test",
TargetRevision: "HEAD",
Helm: &appsv1.ApplicationSourceHelm{
ValueFiles: []string{"values.yaml"},
},
},
},
Status: appsv1.ApplicationStatus{
History: appsv1.RevisionHistories{
{
Revision: "abcdef123567",
Source: appsv1.ApplicationSource{
RepoURL: "https://test",
TargetRevision: "HEAD",
Helm: &appsv1.ApplicationSourceHelm{
ValueFiles: []string{"values-old.yaml"},
},
},
},
},
},
}
)
func newAppAndProjLister(objects ...runtime.Object) (applisters.ApplicationNamespaceLister, applisters.AppProjectNamespaceLister) {
fakeAppsClientset := fakeapps.NewSimpleClientset(objects...)
factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
projInformer := factory.Argoproj().V1alpha1().AppProjects()
appsInformer := factory.Argoproj().V1alpha1().Applications()
for _, obj := range objects {
switch obj.(type) {
case *appsv1.AppProject:
_ = projInformer.Informer().GetStore().Add(obj)
case *appsv1.Application:
_ = appsInformer.Informer().GetStore().Add(obj)
}
}
appLister := appsInformer.Lister().Applications(testNamespace)
projLister := projInformer.Lister().AppProjects(testNamespace)
return appLister, projLister
}
func Test_createRBACObject(t *testing.T) {
object := createRBACObject("test-prj", "test-repo")
assert.Equal(t, "test-prj/test-repo", object)
@@ -51,34 +151,17 @@ func Test_createRBACObject(t *testing.T) {
}
func TestRepositoryServer(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(&corev1.ConfigMap{
ObjectMeta: v1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
}, &corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "argocd-secret",
Namespace: testNamespace,
},
Data: map[string][]byte{
"admin.password": []byte("test"),
"server.secretkey": []byte("test"),
},
})
kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
enforcer := newEnforcer(kubeclientset)
appLister, projLister := newAppAndProjLister(defaultProj)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
t.Run("Test_getRepo", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
s := NewServer(&repoServerClientset, argoDB, enforcer, nil, settingsMgr)
s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projLister, settingsMgr)
url := "https://test"
repo, _ := s.getRepo(context.TODO(), url)
assert.Equal(t, repo.Repo, url)
@@ -89,7 +172,7 @@ func TestRepositoryServer(t *testing.T) {
repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
s := NewServer(&repoServerClientset, argoDB, enforcer, nil, settingsMgr)
s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projLister, settingsMgr)
url := "https://test"
_, err := s.ValidateAccess(context.TODO(), &repository.RepoAccessQuery{
Repo: url,
@@ -104,10 +187,10 @@ func TestRepositoryServer(t *testing.T) {
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&v1alpha1.Repository{Repo: url}, nil)
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
db.On("RepositoryExists", context.TODO(), url).Return(true, nil)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
repo, err := s.Get(context.TODO(), &repository.RepoQuery{
Repo: url,
})
@@ -124,12 +207,12 @@ func TestRepositoryServer(t *testing.T) {
db.On("GetRepository", context.TODO(), url).Return(nil, errors.New("some error"))
db.On("RepositoryExists", context.TODO(), url).Return(true, nil)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
repo, err := s.Get(context.TODO(), &repository.RepoQuery{
Repo: url,
})
assert.Nil(t, repo)
assert.Equal(t, err.Error(), "rpc error: code = PermissionDenied desc = permission denied")
assert.Equal(t, err, errPermissionDenied)
})
t.Run("Test_GetWithNotExistRepoShouldReturn404", func(t *testing.T) {
@@ -138,15 +221,15 @@ func TestRepositoryServer(t *testing.T) {
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&v1alpha1.Repository{Repo: url}, nil)
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
db.On("RepositoryExists", context.TODO(), url).Return(false, nil)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
repo, err := s.Get(context.TODO(), &repository.RepoQuery{
Repo: url,
})
assert.Nil(t, repo)
assert.Equal(t, err.Error(), "rpc error: code = NotFound desc = repo 'https://test' not found")
assert.Equal(t, "rpc error: code = NotFound desc = repo 'https://test' not found", err.Error())
})
t.Run("Test_CreateRepositoryWithoutUpsert", func(t *testing.T) {
@@ -156,14 +239,14 @@ func TestRepositoryServer(t *testing.T) {
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), "test").Return(nil, errors.New("not found"))
db.On("CreateRepository", context.TODO(), mock.Anything).Return(&apiclient.TestRepositoryResponse{}).Return(&v1alpha1.Repository{
db.On("CreateRepository", context.TODO(), mock.Anything).Return(&apiclient.TestRepositoryResponse{}).Return(&appsv1.Repository{
Repo: "repo",
Project: "proj",
}, nil)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
repo, err := s.CreateRepository(context.TODO(), &repository.RepoCreateRequest{
Repo: &v1alpha1.Repository{
Repo: &appsv1.Repository{
Repo: "test",
Username: "test",
},
@@ -178,16 +261,16 @@ func TestRepositoryServer(t *testing.T) {
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), "test").Return(&v1alpha1.Repository{
db.On("GetRepository", context.TODO(), "test").Return(&appsv1.Repository{
Repo: "test",
Username: "test",
}, nil)
db.On("CreateRepository", context.TODO(), mock.Anything).Return(nil, status.Errorf(codes.AlreadyExists, "repository already exists"))
db.On("UpdateRepository", context.TODO(), mock.Anything).Return(nil, nil)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, settingsMgr)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
repo, err := s.CreateRepository(context.TODO(), &repository.RepoCreateRequest{
Repo: &v1alpha1.Repository{
Repo: &appsv1.Repository{
Repo: "test",
Username: "test",
},
@@ -200,6 +283,295 @@ func TestRepositoryServer(t *testing.T) {
}
func TestRepositoryServerListApps(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
t.Run("Test_WithoutAppCreateUpdatePrivileges", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
enforcer.SetDefaultRole("role:readonly")
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
appLister, projLister := newAppAndProjLister(defaultProj)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
Repo: "https://test",
Revision: "HEAD",
AppName: "foo",
AppProject: "default",
})
assert.Nil(t, resp)
assert.Equal(t, err, errPermissionDenied)
})
t.Run("Test_WithAppCreateUpdatePrivileges", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
enforcer.SetDefaultRole("role:admin")
appLister, projLister := newAppAndProjLister(defaultProj)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{
Apps: map[string]string{
"path/to/dir": "Kustomize",
},
}, nil)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
Repo: "https://test",
Revision: "HEAD",
AppName: "foo",
AppProject: "default",
})
assert.NoError(t, err)
assert.Len(t, resp.Items, 1)
assert.Equal(t, "path/to/dir", resp.Items[0].Path)
assert.Equal(t, "Kustomize", resp.Items[0].Type)
})
t.Run("Test_WithAppCreateUpdatePrivilegesRepoNotAllowed", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
enforcer.SetDefaultRole("role:admin")
appLister, projLister := newAppAndProjLister(defaultProjNoSources)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{
Apps: map[string]string{
"path/to/dir": "Kustomize",
},
}, nil)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
Repo: "https://test",
Revision: "HEAD",
AppName: "foo",
AppProject: "default",
})
assert.Nil(t, resp)
assert.Error(t, err, "repository 'https://test' not permitted in project 'default'")
})
}
func TestRepositoryServerGetAppDetails(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
t.Run("Test_WithoutRepoReadPrivileges", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
enforcer.SetDefaultRole("")
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
appLister, projLister := newAppAndProjLister(defaultProj)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: &appsv1.ApplicationSource{
RepoURL: url,
},
AppName: "newapp",
AppProject: "default",
})
assert.Nil(t, resp)
assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: repositories, get, https://test")
})
t.Run("Test_WithoutAppReadPrivileges", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
_ = enforcer.SetUserPolicy("p, role:readrepos, repositories, get, *, allow")
enforcer.SetDefaultRole("role:readrepos")
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
appLister, projLister := newAppAndProjLister(defaultProj)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: &appsv1.ApplicationSource{
RepoURL: url,
},
AppName: "newapp",
AppProject: "default",
})
assert.Nil(t, resp)
assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: applications, get, default/newapp")
})
t.Run("Test_WithoutCreatePrivileges", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
enforcer.SetDefaultRole("role:readonly")
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
appLister, projLister := newAppAndProjLister(defaultProj)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: &appsv1.ApplicationSource{
RepoURL: url,
},
AppName: "newapp",
AppProject: "default",
})
assert.Nil(t, resp)
assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: applications, create, default/newapp")
})
t.Run("Test_WithCreatePrivileges", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
appLister, projLister := newAppAndProjLister(defaultProj)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: &appsv1.ApplicationSource{
RepoURL: url,
},
AppName: "newapp",
AppProject: "default",
})
assert.NoError(t, err)
assert.Equal(t, expectedResp, *resp)
})
t.Run("Test_RepoNotPermitted", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
appLister, projLister := newAppAndProjLister(defaultProjNoSources)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: &appsv1.ApplicationSource{
RepoURL: url,
},
AppName: "newapp",
AppProject: "default",
})
assert.Error(t, err, "repository 'https://test' not permitted in project 'default'")
assert.Nil(t, resp)
})
t.Run("Test_ExistingApp", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: &guestbookApp.Spec.Source,
AppName: "guestbook",
AppProject: "default",
})
assert.NoError(t, err)
assert.Equal(t, expectedResp, *resp)
})
t.Run("Test_ExistingAppMismatchedProjectName", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: &guestbookApp.Spec.Source,
AppName: "guestbook",
AppProject: "mismatch",
})
assert.Equal(t, errPermissionDenied, err)
assert.Nil(t, resp)
})
t.Run("Test_ExistingAppSourceNotInHistory", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
differentSource := guestbookApp.Spec.Source.DeepCopy()
differentSource.Helm.ValueFiles = []string{"/etc/passwd"}
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: differentSource,
AppName: "guestbook",
AppProject: "default",
})
assert.Equal(t, errPermissionDenied, err)
assert.Nil(t, resp)
})
t.Run("Test_ExistingAppSourceInHistory", func(t *testing.T) {
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
enforcer := newEnforcer(kubeclientset)
url := "https://test"
db := &dbmocks.ArgoDB{}
db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
previousSource := guestbookApp.Status.History[0].Source.DeepCopy()
previousSource.TargetRevision = guestbookApp.Status.History[0].Revision
s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, settingsMgr)
resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
Source: previousSource,
AppName: "guestbook",
AppProject: "default",
})
assert.NoError(t, err)
assert.Equal(t, expectedResp, *resp)
})
}
type fixtures struct {
*cache.Cache
}

View File

@@ -154,6 +154,7 @@ type ArgoCDServer struct {
settingsMgr *settings_util.SettingsManager
enf *rbac.Enforcer
projInformer cache.SharedIndexInformer
projLister applisters.AppProjectNamespaceLister
policyEnforcer *rbacpolicy.RBACPolicyEnforcer
appInformer cache.SharedIndexInformer
appLister applisters.ApplicationNamespaceLister
@@ -248,6 +249,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
settingsMgr: settingsMgr,
enf: enf,
projInformer: projInformer,
projLister: projLister,
appInformer: appInformer,
appLister: appLister,
policyEnforcer: policyEnf,
@@ -562,7 +564,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
db := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset)
kubectl := kubeutil.NewKubectl()
clusterService := cluster.NewServer(db, a.enf, a.Cache, kubectl)
repoService := repository.NewServer(a.RepoClientset, db, a.enf, a.Cache, a.settingsMgr)
repoService := repository.NewServer(a.RepoClientset, db, a.enf, a.Cache, a.appLister, a.projLister, a.settingsMgr)
repoCredsService := repocreds.NewServer(a.RepoClientset, db, a.enf, a.settingsMgr)
var loginRateLimiter func() (io.Closer, error)
if maxConcurrentLoginRequestsCount > 0 {

View File

@@ -643,7 +643,9 @@ func TestKsonnetApp(t *testing.T) {
defer io.Close(closer)
details, err := client.GetAppDetails(context.Background(), &repositorypkg.RepoAppDetailsQuery{
Source: &app.Spec.Source,
AppName: app.Name,
AppProject: app.Spec.Project,
Source: &app.Spec.Source,
})
assert.NoError(t, err)

View File

@@ -281,7 +281,7 @@ export const ApplicationCreatePanel = (props: {
load={async src =>
(src.repoURL &&
services.repos
.apps(src.repoURL, src.revision)
.apps(src.repoURL, src.revision, app.metadata.name, app.spec.project)
.then(apps => Array.from(new Set(apps.map(item => item.path))).sort())
.catch(() => new Array<string>())) ||
new Array<string>()
@@ -421,7 +421,7 @@ export const ApplicationCreatePanel = (props: {
}}
load={async src => {
if (src.repoURL && src.targetRevision && (src.path || src.chart)) {
return services.repos.appDetails(src, src.appName).catch(() => ({
return services.repos.appDetails(src, src.appName, app.spec.project).catch(() => ({
type: 'Directory',
details: {}
}));

View File

@@ -80,7 +80,7 @@ export const ApplicationDeploymentHistory = ({
/>
<DataLoader
input={{...recentDeployments[index].source, targetRevision: recentDeployments[index].revision, appName: app.metadata.name}}
load={src => services.repos.appDetails(src, src.appName)}>
load={src => services.repos.appDetails(src, src.appName, app.spec.project)}>
{(details: models.RepoAppDetails) => (
<div>
<ApplicationParameters

View File

@@ -145,7 +145,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
key='appDetails'
input={application}
load={app =>
services.repos.appDetails(app.spec.source, app.metadata.name).catch(() => ({
services.repos.appDetails(app.spec.source, app.metadata.name, app.spec.project).catch(() => ({
type: 'Directory' as AppSourceType,
path: application.spec.source.path
}))

View File

@@ -132,10 +132,12 @@ export class RepositoriesService {
return requests.get(`/repositories/${encodeURIComponent(repo)}/refs`).then(res => res.body as models.RefsInfo);
}
public apps(repo: string, revision: string): Promise<models.AppInfo[]> {
public apps(repo: string, revision: string, appName: string, appProject: string): Promise<models.AppInfo[]> {
return requests
.get(`/repositories/${encodeURIComponent(repo)}/apps`)
.query({revision})
.query({appName})
.query({appProject})
.then(res => (res.body.items as models.AppInfo[]) || []);
}
@@ -143,10 +145,10 @@ export class RepositoriesService {
return requests.get(`/repositories/${encodeURIComponent(repo)}/helmcharts`).then(res => (res.body.items as models.HelmChart[]) || []);
}
public appDetails(source: models.ApplicationSource, appName: string): Promise<models.RepoAppDetails> {
public appDetails(source: models.ApplicationSource, appName: string, appProject: string): Promise<models.RepoAppDetails> {
return requests
.post(`/repositories/${encodeURIComponent(source.repoURL)}/appdetails`)
.send({source, appName})
.send({source, appName, appProject})
.then(res => res.body as models.RepoAppDetails);
}
}

View File

@@ -222,6 +222,11 @@ func ValidateRepo(
return conditions, nil
}
helmOptions, err := settingsMgr.GetHelmSettings()
if err != nil {
return nil, err
}
helmRepos, err := db.ListHelmRepositories(ctx)
if err != nil {
return nil, err
@@ -247,6 +252,7 @@ func ValidateRepo(
// don't use case during application change to make sure to fetch latest git/helm revisions
NoRevisionCache: true,
TrackingMethod: string(GetTrackingMethod(settingsMgr)),
HelmOptions: helmOptions,
})
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
@@ -276,7 +282,18 @@ func ValidateRepo(
return nil, err
}
conditions = append(conditions, verifyGenerateManifests(
ctx, repo, permittedHelmRepos, app, repoClient, kustomizeOptions, plugins, cluster.ServerVersion, APIResourcesToStrings(apiGroups, true), permittedHelmCredentials, settingsMgr)...)
ctx,
repo,
permittedHelmRepos,
helmOptions,
app,
repoClient,
kustomizeOptions,
plugins,
cluster.ServerVersion,
APIResourcesToStrings(apiGroups, true),
permittedHelmCredentials,
settingsMgr)...)
return conditions, nil
}
@@ -457,19 +474,7 @@ func GetAppProject(spec *argoappv1.ApplicationSpec, projLister applicationsv1.Ap
}
// verifyGenerateManifests verifies a repo path can generate manifests
func verifyGenerateManifests(
ctx context.Context,
repoRes *argoappv1.Repository,
helmRepos argoappv1.Repositories,
app *argoappv1.Application,
repoClient apiclient.RepoServerServiceClient,
kustomizeOptions *argoappv1.KustomizeOptions,
plugins []*argoappv1.ConfigManagementPlugin,
kubeVersion string,
apiVersions []string,
repositoryCredentials []*argoappv1.RepoCreds,
settingsMgr *settings.SettingsManager,
) []argoappv1.ApplicationCondition {
func verifyGenerateManifests(ctx context.Context, repoRes *argoappv1.Repository, helmRepos argoappv1.Repositories, helmOptions *argoappv1.HelmOptions, app *argoappv1.Application, repoClient apiclient.RepoServerServiceClient, kustomizeOptions *argoappv1.KustomizeOptions, plugins []*argoappv1.ConfigManagementPlugin, kubeVersion string, apiVersions []string, repositoryCredentials []*argoappv1.RepoCreds, settingsMgr *settings.SettingsManager) []argoappv1.ApplicationCondition {
spec := &app.Spec
var conditions []argoappv1.ApplicationCondition
if spec.Destination.Server == "" {
@@ -495,6 +500,7 @@ func verifyGenerateManifests(
KustomizeOptions: kustomizeOptions,
KubeVersion: kubeVersion,
ApiVersions: apiVersions,
HelmOptions: helmOptions,
HelmRepoCreds: repositoryCredentials,
TrackingMethod: string(GetTrackingMethod(settingsMgr)),
NoRevisionCache: true,

View File

@@ -268,6 +268,7 @@ func TestValidateRepo(t *testing.T) {
Source: &app.Spec.Source,
Repos: helmRepos,
KustomizeOptions: kustomizeOptions,
HelmOptions: &argoappv1.HelmOptions{ValuesFileSchemes: []string{"https", "http"}},
NoRevisionCache: true,
}).Return(&apiclient.RepoAppDetailsResponse{}, nil)

View File

@@ -11,6 +11,7 @@ import (
executil "github.com/argoproj/argo-cd/v2/util/exec"
"github.com/argoproj/argo-cd/v2/util/io"
pathutil "github.com/argoproj/argo-cd/v2/util/io/path"
"github.com/argoproj/argo-cd/v2/util/proxy"
)
@@ -286,8 +287,8 @@ type TemplateOpts struct {
APIVersions []string
Set map[string]string
SetString map[string]string
SetFile map[string]string
Values []string
SetFile map[string]pathutil.ResolvedFilePath
Values []pathutil.ResolvedFilePath
}
var (
@@ -322,10 +323,10 @@ func (c *Cmd) template(chartPath string, opts *TemplateOpts) (string, error) {
args = append(args, "--set-string", key+"="+cleanSetParameters(val))
}
for key, val := range opts.SetFile {
args = append(args, "--set-file", key+"="+cleanSetParameters(val))
args = append(args, "--set-file", key+"="+cleanSetParameters(string(val)))
}
for _, val := range opts.Values {
args = append(args, "--values", val)
args = append(args, "--values", string(val))
}
for _, v := range opts.APIVersions {
args = append(args, "--api-versions", v)

View File

@@ -13,6 +13,7 @@ import (
"github.com/argoproj/argo-cd/v2/util/config"
executil "github.com/argoproj/argo-cd/v2/util/exec"
pathutil "github.com/argoproj/argo-cd/v2/util/io/path"
)
type HelmRepository struct {
@@ -27,7 +28,7 @@ type Helm interface {
// Template returns a list of unstructured objects from a `helm template` command
Template(opts *TemplateOpts) (string, error)
// GetParameters returns a list of chart parameters taking into account values in provided YAML files.
GetParameters(valuesFiles []string) (map[string]string, error)
GetParameters(valuesFiles []pathutil.ResolvedFilePath) (map[string]string, error)
// DependencyBuild runs `helm dependency build` to download a chart's dependencies
DependencyBuild() error
// Init runs `helm init --client-only`
@@ -129,13 +130,14 @@ func Version(shortForm bool) (string, error) {
return strings.TrimSpace(version), nil
}
func (h *helm) GetParameters(valuesFiles []string) (map[string]string, error) {
func (h *helm) GetParameters(valuesFiles []pathutil.ResolvedFilePath) (map[string]string, error) {
out, err := h.cmd.inspectValues(".")
if err != nil {
return nil, err
}
values := []string{out}
for _, file := range valuesFiles {
for i := range valuesFiles {
file := string(valuesFiles[i])
var fileValues []byte
parsedURL, err := url.ParseRequestURI(file)
if err == nil && (parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {

View File

@@ -5,8 +5,9 @@ import (
"os"
"testing"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/argoproj/argo-cd/v2/util/io/path"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
@@ -56,7 +57,7 @@ func TestHelmTemplateValues(t *testing.T) {
assert.NoError(t, err)
opts := TemplateOpts{
Name: "test",
Values: []string{"values-production.yaml"},
Values: []path.ResolvedFilePath{"values-production.yaml"},
}
objs, err := template(h, &opts)
assert.Nil(t, err)
@@ -75,7 +76,7 @@ func TestHelmTemplateValues(t *testing.T) {
func TestHelmGetParams(t *testing.T) {
h, err := NewHelmApp("./testdata/redis", nil, false, "", "", false)
assert.NoError(t, err)
params, err := h.GetParameters([]string{})
params, err := h.GetParameters(nil)
assert.Nil(t, err)
slaveCountParam := params["cluster.slaveCount"]
@@ -85,7 +86,7 @@ func TestHelmGetParams(t *testing.T) {
func TestHelmGetParamsValueFiles(t *testing.T) {
h, err := NewHelmApp("./testdata/redis", nil, false, "", "", false)
assert.NoError(t, err)
params, err := h.GetParameters([]string{"values-production.yaml"})
params, err := h.GetParameters([]path.ResolvedFilePath{"values-production.yaml"})
assert.Nil(t, err)
slaveCountParam := params["cluster.slaveCount"]
@@ -95,7 +96,7 @@ func TestHelmGetParamsValueFiles(t *testing.T) {
func TestHelmGetParamsValueFilesThatExist(t *testing.T) {
h, err := NewHelmApp("./testdata/redis", nil, false, "", "", false)
assert.NoError(t, err)
params, err := h.GetParameters([]string{"values-missing.yaml", "values-production.yaml"})
params, err := h.GetParameters([]path.ResolvedFilePath{"values-missing.yaml", "values-production.yaml"})
assert.Nil(t, err)
slaveCountParam := params["cluster.slaveCount"]

158
util/io/path/resolved.go Normal file
View File

@@ -0,0 +1,158 @@
package path
import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
)
// ResolvedFilePath represents a resolved file path and intended to prevent unintentional use of not verified file path.
type ResolvedFilePath string
// resolveSymbolicLinkRecursive resolves the symlink path recursively to its
// canonical path on the file system, with a maximum nesting level of maxDepth.
// If path is not a symlink, returns the verbatim copy of path and err of nil.
func resolveSymbolicLinkRecursive(path string, maxDepth int) (string, error) {
resolved, err := os.Readlink(path)
if err != nil {
// path is not a symbolic link
_, ok := err.(*os.PathError)
if ok {
return path, nil
}
// Other error has occured
return "", err
}
if maxDepth == 0 {
return "", fmt.Errorf("maximum nesting level reached")
}
// If we resolved to a relative symlink, make sure we use the absolute
// path for further resolving
if !strings.HasPrefix(resolved, "/") {
basePath := filepath.Dir(path)
resolved = filepath.Join(basePath, resolved)
}
return resolveSymbolicLinkRecursive(resolved, maxDepth-1)
}
// isURLSchemeAllowed returns true if the protocol scheme is in the list of
// allowed URL schemes.
func isURLSchemeAllowed(scheme string, allowed []string) bool {
isAllowed := false
if len(allowed) > 0 {
for _, s := range allowed {
if strings.EqualFold(scheme, s) {
isAllowed = true
break
}
}
}
// Empty scheme means local file
return isAllowed && scheme != ""
}
// ResolveFilePath will inspect and resolve given file, and make sure that its final path is within the boundaries of
// the path specified in repoRoot.
//
// appPath is the path we're operating in, e.g. where a Helm chart was unpacked
// to. repoRoot is the path to the root of the repository.
//
// If either appPath or repoRoot is relative, it will be treated as relative
// to the current working directory.
//
// valueFile is the path to a value file, relative to appPath. If valueFile is
// specified as an absolute path (i.e. leading slash), it will be treated as
// relative to the repoRoot. In case valueFile is a symlink in the extracted
// chart, it will be resolved recursively and the decision of whether it is in
// the boundary of repoRoot will be made using the final resolved path.
// valueFile can also be a remote URL with a protocol scheme as prefix,
// in which case the scheme must be included in the list of allowed schemes
// specified by allowedURLSchemes.
//
// Will return an error if either valueFile is outside the boundaries of the
// repoRoot, valueFile is an URL with a forbidden protocol scheme or if
// valueFile is a recursive symlink nested too deep. May return errors for
// other reasons as well.
//
// resolvedPath will hold the absolute, resolved path for valueFile on success
// or set to the empty string on failure.
//
// isRemote will be set to true if valueFile is an URL using an allowed
// protocol scheme, or to false if it resolved to a local file.
func ResolveFilePath(appPath, repoRoot, valueFile string, allowedURLSchemes []string) (resolvedPath ResolvedFilePath, isRemote bool, err error) {
// We do not provide the path in the error message, because it will be
// returned to the user and could be used for information gathering.
// Instead, we log the concrete error details.
resolveFailure := func(path string, err error) error {
log.Errorf("failed to resolve path '%s': %v", path, err)
return fmt.Errorf("internal error: failed to resolve path. Check logs for more details")
}
// A value file can be specified as an URL to a remote resource.
// We only allow certain URL schemes for remote value files.
url, err := url.Parse(valueFile)
if err == nil {
// If scheme is empty, it means we parsed a path only
if url.Scheme != "" {
if isURLSchemeAllowed(url.Scheme, allowedURLSchemes) {
return ResolvedFilePath(valueFile), true, nil
} else {
return "", false, fmt.Errorf("the URL scheme '%s' is not allowed", url.Scheme)
}
}
}
// Ensure that our repository root is absolute
absRepoPath, err := filepath.Abs(repoRoot)
if err != nil {
return "", false, resolveFailure(repoRoot, err)
}
// If the path to the file is relative, join it with the current working directory (appPath)
// Otherwise, join it with the repository's root
path := valueFile
if !filepath.IsAbs(path) {
absWorkDir, err := filepath.Abs(appPath)
if err != nil {
return "", false, resolveFailure(repoRoot, err)
}
path = filepath.Join(absWorkDir, path)
} else {
path = filepath.Join(absRepoPath, path)
}
// Ensure any symbolic link is resolved before we
delinkedPath, err := resolveSymbolicLinkRecursive(path, 10)
if err != nil {
return "", false, resolveFailure(path, err)
}
path = delinkedPath
// Resolve the joined path to an absolute path
path, err = filepath.Abs(path)
if err != nil {
return "", false, resolveFailure(path, err)
}
// Ensure our root path has a trailing slash, otherwise the following check
// would return true if root is /foo and path would be /foo2
requiredRootPath := absRepoPath
if !strings.HasSuffix(requiredRootPath, "/") {
requiredRootPath += "/"
}
// Make sure that the resolved path to values file is within the repository's root path
if !strings.HasPrefix(path, requiredRootPath) {
return "", false, fmt.Errorf("value file '%s' resolved to outside repository root", valueFile)
}
return ResolvedFilePath(path), false, nil
}

View File

@@ -0,0 +1,181 @@
package path
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_resolveSymlinkRecursive(t *testing.T) {
testsDir, err := filepath.Abs("./testdata")
if err != nil {
panic(err)
}
t.Run("Resolve non-symlink", func(t *testing.T) {
r, err := resolveSymbolicLinkRecursive(testsDir+"/foo", 2)
assert.NoError(t, err)
assert.Equal(t, testsDir+"/foo", r)
})
t.Run("Successfully resolve symlink", func(t *testing.T) {
r, err := resolveSymbolicLinkRecursive(testsDir+"/bar", 2)
assert.NoError(t, err)
assert.Equal(t, testsDir+"/foo", r)
})
t.Run("Do not allow symlink at all", func(t *testing.T) {
r, err := resolveSymbolicLinkRecursive(testsDir+"/bar", 0)
assert.Error(t, err)
assert.Equal(t, "", r)
})
t.Run("Error because too nested symlink", func(t *testing.T) {
r, err := resolveSymbolicLinkRecursive(testsDir+"/bam", 2)
assert.Error(t, err)
assert.Equal(t, "", r)
})
t.Run("No such file or directory", func(t *testing.T) {
r, err := resolveSymbolicLinkRecursive(testsDir+"/foobar", 2)
assert.NoError(t, err)
assert.Equal(t, testsDir+"/foobar", r)
})
}
func Test_isURLSchemeAllowed(t *testing.T) {
type testdata struct {
name string
scheme string
allowed []string
expected bool
}
var tts []testdata = []testdata{
{
name: "Allowed scheme matches",
scheme: "http",
allowed: []string{"http", "https"},
expected: true,
},
{
name: "Allowed scheme matches only partially",
scheme: "http",
allowed: []string{"https"},
expected: false,
},
{
name: "Scheme is not allowed",
scheme: "file",
allowed: []string{"http", "https"},
expected: false,
},
{
name: "Empty scheme with valid allowances is forbidden",
scheme: "",
allowed: []string{"http", "https"},
expected: false,
},
{
name: "Empty scheme with empty allowances is forbidden",
scheme: "",
allowed: []string{},
expected: false,
},
{
name: "Some scheme with empty allowances is forbidden",
scheme: "file",
allowed: []string{},
expected: false,
},
}
for _, tt := range tts {
t.Run(tt.name, func(t *testing.T) {
r := isURLSchemeAllowed(tt.scheme, tt.allowed)
assert.Equal(t, tt.expected, r)
})
}
}
var allowedRemoteProtocols = []string{"http", "https"}
func Test_resolveFilePath(t *testing.T) {
t.Run("Resolve normal relative path into absolute path", func(t *testing.T) {
p, remote, err := ResolveFilePath("/foo/bar", "/foo", "baz/bim.yaml", allowedRemoteProtocols)
assert.NoError(t, err)
assert.False(t, remote)
assert.Equal(t, "/foo/bar/baz/bim.yaml", string(p))
})
t.Run("Resolve normal relative path into absolute path", func(t *testing.T) {
p, remote, err := ResolveFilePath("/foo/bar", "/foo", "baz/../../bim.yaml", allowedRemoteProtocols)
assert.NoError(t, err)
assert.False(t, remote)
assert.Equal(t, "/foo/bim.yaml", string(p))
})
t.Run("Error on path resolving outside repository root", func(t *testing.T) {
p, remote, err := ResolveFilePath("/foo/bar", "/foo", "baz/../../../bim.yaml", allowedRemoteProtocols)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
assert.False(t, remote)
assert.Equal(t, "", string(p))
})
t.Run("Return verbatim URL", func(t *testing.T) {
url := "https://some.where/foo,yaml"
p, remote, err := ResolveFilePath("/foo/bar", "/foo", url, allowedRemoteProtocols)
assert.NoError(t, err)
assert.True(t, remote)
assert.Equal(t, url, string(p))
})
t.Run("URL scheme not allowed", func(t *testing.T) {
url := "file:///some.where/foo,yaml"
p, remote, err := ResolveFilePath("/foo/bar", "/foo", url, allowedRemoteProtocols)
assert.Error(t, err)
assert.False(t, remote)
assert.Equal(t, "", string(p))
})
t.Run("Implicit URL by absolute path", func(t *testing.T) {
p, remote, err := ResolveFilePath("/foo/bar", "/foo", "/baz.yaml", allowedRemoteProtocols)
assert.NoError(t, err)
assert.False(t, remote)
assert.Equal(t, "/foo/baz.yaml", string(p))
})
t.Run("Relative app path", func(t *testing.T) {
p, remote, err := ResolveFilePath(".", "/foo", "/baz.yaml", allowedRemoteProtocols)
assert.NoError(t, err)
assert.False(t, remote)
assert.Equal(t, "/foo/baz.yaml", string(p))
})
t.Run("Relative repo path", func(t *testing.T) {
c, err := os.Getwd()
require.NoError(t, err)
p, remote, err := ResolveFilePath(".", ".", "baz.yaml", allowedRemoteProtocols)
assert.NoError(t, err)
assert.False(t, remote)
assert.Equal(t, c+"/baz.yaml", string(p))
})
t.Run("Overlapping root prefix without trailing slash", func(t *testing.T) {
p, remote, err := ResolveFilePath(".", "/foo", "../foo2/baz.yaml", allowedRemoteProtocols)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
assert.False(t, remote)
assert.Equal(t, "", string(p))
})
t.Run("Overlapping root prefix with trailing slash", func(t *testing.T) {
p, remote, err := ResolveFilePath(".", "/foo/", "../foo2/baz.yaml", allowedRemoteProtocols)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
assert.False(t, remote)
assert.Equal(t, "", string(p))
})
t.Run("Garbage input as values file", func(t *testing.T) {
p, remote, err := ResolveFilePath(".", "/foo/", "kfdj\\ks&&&321209.,---e32908923%$§!\"", allowedRemoteProtocols)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
assert.False(t, remote)
assert.Equal(t, "", string(p))
})
t.Run("NUL-byte path input as values file", func(t *testing.T) {
p, remote, err := ResolveFilePath(".", "/foo/", "\000", allowedRemoteProtocols)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside repository root")
assert.False(t, remote)
assert.Equal(t, "", string(p))
})
}

1
util/io/path/testdata/bam vendored Symbolic link
View File

@@ -0,0 +1 @@
baz

1
util/io/path/testdata/bar vendored Symbolic link
View File

@@ -0,0 +1 @@
foo

1
util/io/path/testdata/baz vendored Symbolic link
View File

@@ -0,0 +1 @@
bar

1
util/io/path/testdata/foo vendored Normal file
View File

@@ -0,0 +1 @@
hello

View File

@@ -380,6 +380,8 @@ const (
partOfArgoCDSelector = "app.kubernetes.io/part-of=argocd"
// settingsPasswordPatternKey is the key to configure user password regular expression
settingsPasswordPatternKey = "passwordPattern"
// helmValuesFileSchemesKey is the key to configure the list of supported helm values file schemas
helmValuesFileSchemesKey = "helm.valuesFileSchemes"
)
// SettingsManager holds config info for a new manager with which to access Kubernetes ConfigMaps.
@@ -803,6 +805,25 @@ func (mgr *SettingsManager) GetResourceCompareOptions() (ArgoCDDiffOptions, erro
return diffOptions, nil
}
// GetHelmSettings returns helm settings
func (mgr *SettingsManager) GetHelmSettings() (*v1alpha1.HelmOptions, error) {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return nil, err
}
helmOptions := &v1alpha1.HelmOptions{}
if value, ok := argoCDCM.Data[helmValuesFileSchemesKey]; ok {
for _, item := range strings.Split(value, ",") {
if item := strings.TrimSpace(item); item != "" {
helmOptions.ValuesFileSchemes = append(helmOptions.ValuesFileSchemes, item)
}
}
} else {
helmOptions.ValuesFileSchemes = []string{"https", "http"}
}
return helmOptions, nil
}
// GetKustomizeSettings loads the kustomize settings from argocd-cm ConfigMap
func (mgr *SettingsManager) GetKustomizeSettings() (*KustomizeSettings, error) {
argoCDCM, err := mgr.getConfigMap()

View File

@@ -947,3 +947,72 @@ requestedIDTokenClaims: {"groups": {"essential": true}}`,
oidcConfig := settings.OIDCConfig()
assert.Equal(t, oidcConfig.ClientSecret, "deadbeef")
}
func TestGetHelmSettings(t *testing.T) {
testCases := []struct {
name string
data map[string]string
expected []string
}{{
name: "Default",
data: map[string]string{},
expected: []string{"http", "https"},
}, {
name: "Configured Not Empty",
data: map[string]string{
"helm.valuesFileSchemes": "s3, git",
},
expected: []string{"s3", "git"},
}, {
name: "Configured Empty",
data: map[string]string{
"helm.valuesFileSchemes": "",
},
expected: nil,
}}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
cm := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: tc.data,
}
argocdSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDSecretName,
Namespace: "default",
},
Data: map[string][]byte{
"admin.password": nil,
"server.secretkey": nil,
},
}
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "acme",
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string][]byte{
"clientSecret": []byte("deadbeef"),
},
}
kubeClient := fake.NewSimpleClientset(cm, secret, argocdSecret)
settingsManager := NewSettingsManager(context.Background(), kubeClient, "default")
helmSettings, err := settingsManager.GetHelmSettings()
assert.NoError(t, err)
assert.ElementsMatch(t, tc.expected, helmSettings.ValuesFileSchemes)
})
}
}