Compare commits

...

51 Commits

Author SHA1 Message Date
argo-bot
cedd3a664e Bump version to 2.3.9 2022-10-05 17:02:10 +00:00
argo-bot
f035fb2802 Bump version to 2.3.9 2022-10-05 17:01:55 +00:00
Michael Crenshaw
defdd1a2ba chore: upgrade dex to v2.35.1 (#10797) (#10799)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-05 11:45:37 -04:00
argo-bot
8b9ff390e8 Bump version to 2.3.8 2022-10-03 20:49:52 +00:00
argo-bot
437b0554ed Bump version to 2.3.8 2022-10-03 20:49:39 +00:00
Michael Crenshaw
c4e7326aad chore: upgrade Dex to 2.35.0 (#10775)
* chore: upgrade dex to v2.35.0

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* upgrade github workflow too

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-03 15:46:12 -04:00
Michael Crenshaw
919582de89 chore: upgrade dex to v2.32.1-distroless (#10746)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-09-30 18:55:54 -04:00
JesseBot
5940e56c8b docs: Add "Create Namespace" to sync options doc (#3490) (#10326)
* Add create namespace to the sync options doc

Signed-off-by: JesseBot <jessebot@linux.com>

* Update docs/user-guide/sync-options.md

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>

Signed-off-by: JesseBot <jessebot@linux.com>
Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
2022-08-17 15:12:18 -04:00
Michael Crenshaw
4a299a2f2e docs: clusterResources in declarative cluster config (#10219)
* docs: clusterResources in declarative cluster config

Signed-off-by: CI <michael@crenshaw.dev>

* add article

Signed-off-by: CI <michael@crenshaw.dev>

Signed-off-by: CI <michael@crenshaw.dev>
2022-08-11 13:49:59 -04:00
argo-bot
402da6f64c Bump version to 2.3.7 2022-07-29 14:48:08 +00:00
argo-bot
89c600a6fe Bump version to 2.3.7 2022-07-29 14:47:55 +00:00
jannfis
76cd161e0a test: Remove cluster e2e tests not intended for release-2.3
Signed-off-by: jannfis <jann@mistrust.net>
2022-07-29 07:51:33 +00:00
jannfis
4b8b5918f8 test: Remove circular symlinks from testdata (#9886)
* test: Remove circular symlinks from testdata

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

* Another test case

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

* Use defer for changing back to original workdir

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

* Abort the test on error in defer

Signed-off-by: jannfis <jann@mistrust.net>
2022-07-28 20:25:30 +00:00
Michael Crenshaw
6d0b9caed5 fix: skip redirect url validation when it's the base href (#10058) (#10116)
* fix: skip redirect url validation when it's the base href (#10058)

Signed-off-by: CI <michael@crenshaw.dev>

nicer way of doing it

Signed-off-by: CI <michael@crenshaw.dev>

* fix missin arg

Signed-off-by: CI <michael@crenshaw.dev>
2022-07-27 16:38:24 -04:00
dependabot[bot]
4bb00bade6 chore(deps): bump moment from 2.29.3 to 2.29.4 in /ui (#9897)
Bumps [moment](https://github.com/moment/moment) from 2.29.3 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.3...2.29.4)

Signed-off-by: CI <michael@crenshaw.dev>

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-27 16:28:30 -04:00
Snyk bot
14424c58b2 fix: upgrade moment from 2.29.2 to 2.29.3 (#9330)
Snyk has created this PR to upgrade moment from 2.29.2 to 2.29.3.

See this package in npm:

See this project in Snyk:
https://app.snyk.io/org/argoproj/project/d2931792-eef9-4d7c-b9d6-c0cbd2bd4dbe?utm_source=github&utm_medium=referral&page=upgrade-pr

Signed-off-by: CI <michael@crenshaw.dev>
2022-07-27 16:22:59 -04:00
Alexander Matyushentsev
c0d2e13b42 chore: upgrade moment to latest version to fix CVE (#9005)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-07-27 16:04:37 -04:00
Michael Crenshaw
6de0d9dced chore: move dependencies to dev dependencies (#8541)
chore: move dependencies to dev dependencies (#8541)

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-07-27 15:37:49 -04:00
Michael Crenshaw
5c31b47517 docs: add OpenSSH breaking change notes (#10104)
Signed-off-by: CI <michael@crenshaw.dev>
2022-07-27 15:28:23 -04:00
Michael Crenshaw
92d26b55bd fix: avoid CVE-2022-28948 (#10093)
Signed-off-by: CI <michael@crenshaw.dev>
2022-07-27 15:08:17 -04:00
Michael Crenshaw
a25a6dcfe3 chore: update parse-url (#10101)
* chore: upgrade parse-url

Signed-off-by: CI <michael@crenshaw.dev>

* edit a generated file, because that's smart

Signed-off-by: CI <michael@crenshaw.dev>
2022-07-27 15:00:13 -04:00
Michael Crenshaw
fe6c0f1a38 chore: upgrade base image to 22.04 (#10103)
Signed-off-by: douhunt <douhunt@protonmail.com>

Co-authored-by: douhunt <douhunt@protonmail.com>
Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

chore: update Kex-Algorithms (#9561)

* chore: update Kex-Algorithms

Signed-off-by: douhunt <douhunt@protonmail.com>

* sorted kex-algorithms

Signed-off-by: 34FathomBelow <34fathombelow@protonmail.com>

Co-authored-by: douhunt <douhunt@protonmail.com>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

chore upgrade base image for test containers Ubuntu:22.04 (#9563)

Signed-off-by: 34FathomBelow <34fathombelow@protonmail.com>

Co-authored-by: 34FathomBelow <34fathombelow@protonmail.com>
2022-07-26 14:49:06 -04:00
Michael Crenshaw
0851ea54b8 docs: simplify Docker toolchain docs (#9966) (#10006)
* docs: simplify Docker toolchain docs (#9966)

Signed-off-by: CI <michael@crenshaw.dev>

* to be or not to be

Signed-off-by: CI <michael@crenshaw.dev>

* pin dependencies to avoid absurdity

Signed-off-by: CI <michael@crenshaw.dev>
2022-07-26 14:06:27 -04:00
34FathomBelow
3e920bf3b6 chore: update redis to 6.2.7 avoid CVE-2022-30065/CVE-2022-2097 (#10062)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-07-25 14:24:45 -04:00
Michael Crenshaw
a606b0ab01 chore: upgrade Dex to 2.32.0 (#10036) (#10042)
Signed-off-by: CI <michael@crenshaw.dev>
2022-07-20 10:45:44 -04:00
34FathomBelow
e4074454c6 chore: update haproxy to 2.0.29 for redis-ha (#10045)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-07-19 15:05:54 -04:00
Daniel Helfand
d0e30d961a fix: use serviceaccount name instead of struct (#9614)
* fix: use serviceaccount name instead of struct

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>

* fix: change name of param from sa to serviceAccount

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>
2022-07-13 16:29:39 -04:00
Daniel Helfand
98aadc7dc1 fix: create serviceaccount token for v1.24 clusters (#9546)
* fix: create serviceaccount token for v1.24 clusters

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>

* change create to get in err

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>
2022-07-13 16:29:03 -04:00
Michael Crenshaw
efdec2888e test: check for error messages from CI env (#9953)
test: check for error messages from CI env (#9953)

Signed-off-by: CI <michael@crenshaw.dev>
2022-07-12 15:20:20 -04:00
argo-bot
0badce7840 Bump version to 2.3.6 2022-07-12 16:31:10 +00:00
argo-bot
ac6fce35e2 Bump version to 2.3.6 2022-07-12 16:30:56 +00:00
Michael Crenshaw
3172b6b2c7 Merge pull request from GHSA-7943-82jg-wmw5
* add tests to demonstrate issue

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

more

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

docs

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

settings tests

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

tests for OIDC handlers, consolidating test helpers

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

consolidate

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

consolidate

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

docs

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix log message

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-07-12 08:46:13 -04:00
Michael Crenshaw
8d5119b1e3 Merge pull request from GHSA-pmjg-52h9-72qv
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

formatting

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

fixes from comments

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

fix test

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-07-12 08:45:22 -04:00
Nicolas Fillot
068ca899ce [ArgoCD] Fixing webhook typo in case of error in GetManifests (#9671)
Signed-off-by: Nicolas Fillot <nfillot@weborama.com>

Co-authored-by: Nicolas Fillot <nfillot@weborama.com>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-23 09:27:06 -04:00
argo-bot
1287d24bfe Bump version to 2.3.5 2022-06-21 16:28:23 +00:00
argo-bot
68e825cf9b Bump version to 2.3.5 2022-06-21 16:28:06 +00:00
Michael Crenshaw
738384bdd0 chore: fix docs gen
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-21 10:41:40 -04:00
Michael Crenshaw
7fb77d578d Merge pull request from GHSA-jhqp-vf4w-rpwq
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

defer instead of multiple close calls

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

oops

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

don't count jsonnet against max

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

fix codegen

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

add caveat about 300x ratio

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

fix versions

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

fix tests/lint

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-21 09:40:36 -04:00
Michael Crenshaw
336d61cac6 Merge pull request from GHSA-q4w5-4gq2-98vm
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-21 09:39:56 -04:00
Michael Crenshaw
c0b62972ca Merge pull request from GHSA-2m7h-86qq-fp4v
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

fix references

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

use long enough state param for oauth2

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

typo

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

more entropy

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

fix test

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-21 09:39:01 -04:00
Michael Crenshaw
048c025cfe Merge pull request from GHSA-h4w9-6x78-8vrj
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-21 09:36:38 -04:00
Michael Crenshaw
7eb34755e5 test: directory app manifest generation (#9503)
* test: directory app manifest generation

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* git doesn't support empty dirs

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-14 16:13:22 -04:00
Michael Crenshaw
2f4afe8c08 chore: eliminate go-mpatch dependency (#9045)
* chore: eliminate go-mpatch dependency

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* chore: abstract out resource list function

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* chore: don't exit the program in anything but the main function

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* chore: better error messages

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* chore: better error messages

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-13 17:31:42 -04:00
jannfis
92f53aeb72 chore: Make unit tests run on platforms other than amd64 (#8995)
Signed-off-by: jannfis <jann@mistrust.net>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-13 17:30:09 -04:00
Alexander Matyushentsev
43f8027f6d chore: remove obsolete repo-server unit test (#9559)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-06-13 17:24:15 -04:00
Michael Crenshaw
909f94a383 chore: update golangci-lint (#8988)
* chore: update golangci-lint

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-13 16:55:45 -04:00
Michael Crenshaw
af5348c9b1 chore: lint issues
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-13 16:54:18 -04:00
Michael Crenshaw
d4faef6324 fix: test race (#9469)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-13 16:48:17 -04:00
Tommaso Sardelli
af45491a7d chore: upgrade golangci-lint to v1.46.2 (#9448)
* chore: upgrade golangci-lint to v1.46.2

Because:

* Installation of golangci-lint v1.45.2 is currently broken and fails
  silently due to a redacted dependency
  (https://github.com/blizzy78/varnamelen/issues/13)

This commit:

* Upgrades golangci-lint to v1.46.2

Signed-off-by: Tommaso Sardelli <lacapannadelloziotom@gmail.com>

* fix: lint

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix: lint

Signed-off-by: Tommaso Sardelli <lacapannadelloziotom@gmail.com>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-13 16:44:47 -04:00
Michael Crenshaw
b0e4473fcd fix: missing Helm params (#9565) (#9566)
* fix: missing Helm params

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* use absolute paths, fix tests

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix race in test

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-13 16:05:11 -04:00
Michael Crenshaw
cf1f44e379 test: fix ErrorContains (#9445)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-18 09:21:10 -04:00
116 changed files with 2657 additions and 581 deletions

View File

@@ -61,10 +61,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
version: v1.38.0
args: --timeout 10m --exclude SA5011
version: v1.46.2
args: --timeout 10m --exclude SA5011 --verbose
test-go:
name: Run unit tests for Go packages
@@ -392,9 +392,9 @@ jobs:
git config --global user.email "john.doe@example.com"
- name: Pull Docker image required for tests
run: |
docker pull quay.io/dexidp/dex:v2.25.0
docker pull ghcr.io/dexidp/dex:v2.35.1-distroless
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:6.2.6-alpine
docker pull redis:6.2.7-alpine
- name: Create target directory for binaries in the build-process
run: |
mkdir -p dist

View File

@@ -1,22 +0,0 @@
run:
timeout: 2m
skip-files:
- ".*\\.pb\\.go"
skip-dirs:
- pkg/client/
- vendor/
linters:
enable:
- vet
- deadcode
- goimports
- varcheck
- structcheck
- ineffassign
- unconvert
- unparam
linters-settings:
goimports:
local-prefixes: github.com/argoproj/argo-cd
service:
golangci-lint-version: 1.21.0

View File

@@ -1,4 +1,4 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:21.10
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
####################################################################################################
# Builder image
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
@@ -69,7 +69,7 @@ RUN ln -s /usr/local/aws-cli/v2/current/dist/aws /usr/local/bin/aws
# support for mounting configuration from a configmap
RUN mkdir -p /app/config/ssh && \
touch /app/config/ssh/ssh_known_hosts && \
ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts
ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts
RUN mkdir -p /app/config/tls
RUN mkdir -p /app/config/gpg/source && \

View File

@@ -1,7 +1,7 @@
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && 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 $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && 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 $COMMAND --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.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"
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.7-alpine --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
repo-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && 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} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh

View File

@@ -1 +1 @@
2.3.4
2.3.9

View File

@@ -13,6 +13,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc/health/grpc_health_v1"
"k8s.io/apimachinery/pkg/api/resource"
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
"github.com/argoproj/argo-cd/v2/common"
@@ -68,14 +69,15 @@ func getSubmoduleEnabled() bool {
func NewCommand() *cobra.Command {
var (
parallelismLimit int64
listenPort int
metricsPort int
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizer tls.ConfigCustomizer
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
redisClient *redis.Client
disableTLS bool
parallelismLimit int64
listenPort int
metricsPort int
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizer tls.ConfigCustomizer
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
redisClient *redis.Client
disableTLS bool
maxCombinedDirectoryManifestsSize string
)
var command = cobra.Command{
Use: cliName,
@@ -95,15 +97,19 @@ func NewCommand() *cobra.Command {
cache, err := cacheSrc()
errors.CheckError(err)
maxCombinedDirectoryManifestsQuantity, err := resource.ParseQuantity(maxCombinedDirectoryManifestsSize)
errors.CheckError(err)
askPassServer := askpass.NewServer()
metricsServer := metrics.NewMetricsServer()
cacheutil.CollectMetrics(redisClient, metricsServer)
server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, repository.RepoServerInitConstants{
ParallelismLimit: parallelismLimit,
ParallelismLimit: parallelismLimit,
PauseGenerationAfterFailedGenerationAttempts: getPauseGenerationAfterFailedGenerationAttempts(),
PauseGenerationOnFailureForMinutes: getPauseGenerationOnFailureForMinutes(),
PauseGenerationOnFailureForRequests: getPauseGenerationOnFailureForRequests(),
SubmoduleEnabled: getSubmoduleEnabled(),
MaxCombinedDirectoryManifestsSize: maxCombinedDirectoryManifestsQuantity,
}, askPassServer)
errors.CheckError(err)
@@ -168,6 +174,7 @@ func NewCommand() *cobra.Command {
command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
command.Flags().BoolVar(&disableTLS, "disable-tls", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_TLS", false), "Disable TLS on the gRPC endpoint")
command.Flags().StringVar(&maxCombinedDirectoryManifestsSize, "max-combined-directory-manifests-size", env.StringFromEnv("ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE", "10M"), "Max combined size of manifest files in a directory-type Application")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {

View File

@@ -609,7 +609,7 @@ func GenerateToken(clusterOpts cmdutil.ClusterOptions, conf *rest.Config) (strin
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
bearerToken, err := clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount)
bearerToken, err := clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount, common.BearerTokenTimeout)
if err != nil {
return "", err
}

View File

@@ -2,6 +2,7 @@ package admin
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
@@ -63,7 +64,10 @@ func NewProjectAllowListGenCommand() *cobra.Command {
}()
}
globalProj := generateProjectAllowList(clientConfig, clusterRoleFileName, projName)
resourceList, err := getResourceList(clientConfig)
errors.CheckError(err)
globalProj, err := generateProjectAllowList(resourceList, clusterRoleFileName, projName)
errors.CheckError(err)
yamlBytes, err := yaml.Marshal(globalProj)
errors.CheckError(err)
@@ -78,23 +82,38 @@ func NewProjectAllowListGenCommand() *cobra.Command {
return command
}
func generateProjectAllowList(clientConfig clientcmd.ClientConfig, clusterRoleFileName string, projName string) v1alpha1.AppProject {
func getResourceList(clientConfig clientcmd.ClientConfig) ([]*metav1.APIResourceList, error) {
config, err := clientConfig.ClientConfig()
if err != nil {
return nil, fmt.Errorf("error while creating client config: %s", err)
}
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, fmt.Errorf("error while creating discovery client: %s", err)
}
serverResources, err := disco.ServerPreferredResources()
if err != nil {
return nil, fmt.Errorf("error while getting server resources: %s", err)
}
return serverResources, nil
}
func generateProjectAllowList(serverResources []*metav1.APIResourceList, clusterRoleFileName string, projName string) (*v1alpha1.AppProject, error) {
yamlBytes, err := ioutil.ReadFile(clusterRoleFileName)
errors.CheckError(err)
if err != nil {
return nil, fmt.Errorf("error reading cluster role file: %s", err)
}
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
errors.CheckError(err)
if err != nil {
return nil, fmt.Errorf("error unmarshalling cluster role file yaml: %s", err)
}
clusterRole := &rbacv1.ClusterRole{}
err = scheme.Scheme.Convert(&obj, clusterRole, nil)
errors.CheckError(err)
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
disco, err := discovery.NewDiscoveryClientForConfig(config)
errors.CheckError(err)
serverResources, err := disco.ServerPreferredResources()
errors.CheckError(err)
if err != nil {
return nil, fmt.Errorf("error converting cluster role yaml into ClusterRole struct: %s", err)
}
resourceList := make([]metav1.GroupKind, 0)
for _, rule := range clusterRole.Rules {
@@ -140,5 +159,5 @@ func generateProjectAllowList(clientConfig clientcmd.ClientConfig, clusterRoleFi
Spec: v1alpha1.AppProjectSpec{},
}
globalProj.Spec.NamespaceResourceWhitelist = resourceList
return globalProj
return &globalProj, nil
}

View File

@@ -1,57 +1,20 @@
package admin
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/undefinedlabs/go-mpatch"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func TestProjectAllowListGen(t *testing.T) {
useMock := true
rules := clientcmd.NewDefaultClientConfigLoadingRules()
overrides := &clientcmd.ConfigOverrides{}
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
if useMock {
var patchClientConfig *mpatch.Patch
patchClientConfig, err := mpatch.PatchInstanceMethodByName(reflect.TypeOf(clientConfig), "ClientConfig", func(*clientcmd.DeferredLoadingClientConfig) (*restclient.Config, error) {
return nil, nil
})
assert.NoError(t, err)
patch, err := mpatch.PatchMethod(discovery.NewDiscoveryClientForConfig, func(c *restclient.Config) (*discovery.DiscoveryClient, error) {
return &discovery.DiscoveryClient{LegacyPrefix: "/api"}, nil
})
assert.NoError(t, err)
var patchSeverPreferredResources *mpatch.Patch
discoClient := &discovery.DiscoveryClient{}
patchSeverPreferredResources, err = mpatch.PatchInstanceMethodByName(reflect.TypeOf(discoClient), "ServerPreferredResources", func(*discovery.DiscoveryClient) ([]*metav1.APIResourceList, error) {
res := metav1.APIResource{
Name: "services",
Kind: "Service",
}
resourceList := []*metav1.APIResourceList{{APIResources: []metav1.APIResource{res}}}
return resourceList, nil
})
assert.NoError(t, err)
defer func() {
err = patchClientConfig.Unpatch()
assert.NoError(t, err)
err = patch.Unpatch()
assert.NoError(t, err)
err = patchSeverPreferredResources.Unpatch()
err = patch.Unpatch()
}()
res := metav1.APIResource{
Name: "services",
Kind: "Service",
}
resourceList := []*metav1.APIResourceList{{APIResources: []metav1.APIResource{res}}}
globalProj := generateProjectAllowList(clientConfig, "testdata/test_clusterrole.yaml", "testproj")
globalProj, err := generateProjectAllowList(resourceList, "testdata/test_clusterrole.yaml", "testproj")
assert.NoError(t, err)
assert.True(t, len(globalProj.Spec.NamespaceResourceWhitelist) > 0)
}

View File

@@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
@@ -776,7 +777,7 @@ func getLocalObjectsString(app *argoappv1.Application, local, localRepoRoot, app
ApiVersions: apiVersions,
Plugins: configManagementPlugins,
TrackingMethod: trackingMethod,
}, true, &git.NoopCredsStore{})
}, true, &git.NoopCredsStore{}, resource.MustParse("0"))
errors.CheckError(err)
return res.Manifests

View File

@@ -113,7 +113,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
if clusterOpts.ServiceAccount != "" {
managerBearerToken, err = clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount)
managerBearerToken, err = clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount, common.BearerTokenTimeout)
} else {
isTerminal := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
@@ -123,7 +123,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
os.Exit(1)
}
}
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, clusterOpts.SystemNamespace, clusterOpts.Namespaces)
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, clusterOpts.SystemNamespace, clusterOpts.Namespaces, common.BearerTokenTimeout)
}
errors.CheckError(err)
}

View File

@@ -200,7 +200,10 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
// completionChan is to signal flow completed. Non-empty string indicates error
completionChan := make(chan string)
// stateNonce is an OAuth2 state nonce
stateNonce := rand.RandString(10)
// According to the spec (https://www.rfc-editor.org/rfc/rfc6749#section-10.10), this must be guessable with
// probability <= 2^(-128). The following call generates one of 52^24 random strings, ~= 2^136 possibilities.
stateNonce, err := rand.String(24)
errors.CheckError(err)
var tokenString string
var refreshToken string
@@ -210,7 +213,8 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
}
// PKCE implementation of https://tools.ietf.org/html/rfc7636
codeVerifier := rand.RandStringCharset(43, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
codeVerifier, err := rand.StringFromCharset(43, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
errors.CheckError(err)
codeChallengeHash := sha256.Sum256([]byte(codeVerifier))
codeChallenge := base64.RawURLEncoding.EncodeToString(codeChallengeHash[:])
@@ -294,7 +298,8 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
opts = append(opts, oauth2.SetAuthURLParam("code_challenge_method", "S256"))
url = oauth2conf.AuthCodeURL(stateNonce, opts...)
case oidcutil.GrantTypeImplicit:
url = oidcutil.ImplicitFlowURL(oauth2conf, stateNonce, opts...)
url, err = oidcutil.ImplicitFlowURL(oauth2conf, stateNonce, opts...)
errors.CheckError(err)
default:
log.Fatalf("Unsupported grant type: %v", grantType)
}

View File

@@ -212,6 +212,12 @@ const (
CacheVersion = "1.8.3"
)
// Constants used by util/clusterauth package
const (
ClusterAuthRequestTimeout = 10 * time.Second
BearerTokenTimeout = 30 * time.Second
)
const (
DefaultGitRetryMaxDuration time.Duration = time.Second * 5 // 5s
DefaultGitRetryDuration time.Duration = time.Millisecond * 250 // 0.25s

View File

@@ -140,7 +140,13 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
}
atomic.AddUint64(&syncIdPrefix, 1)
syncId := fmt.Sprintf("%05d-%s", syncIdPrefix, rand.RandString(5))
randSuffix, err := rand.String(5)
if err != nil {
state.Phase = common.OperationError
state.Message = fmt.Sprintf("Failed generate random sync ID: %v", err)
return
}
syncId := fmt.Sprintf("%05d-%s", syncIdPrefix, randSuffix)
logEntry := log.WithFields(log.Fields{"application": app.Name, "syncId": syncId})
initialResourcesRes := make([]common.ResourceSyncResult, 0)

View File

@@ -24,8 +24,7 @@ You will need at least the following things in your toolchain in order to develo
* A Kubernetes cluster. You won't need a fully blown multi-master, multi-node cluster, but you will need something like K3S, Minikube or microk8s. You will also need a working Kubernetes client (`kubectl`) configuration in your development environment. The configuration must reside in `~/.kube/config` and the API server URL must point to the IP address of your local machine (or VM), and **not** to `localhost` or `127.0.0.1` if you are using the virtualized development toolchain (see below)
* You will also need a working Docker runtime environment, to be able to build and run images.
The Docker version must be fairly recent, and support multi-stage builds. You should not work as root. Make your local user a member of the `docker` group to be able to control the Docker service on your machine.
* You will also need a working Docker runtime environment, to be able to build and run images. The Docker version must be 17.05.0 or higher, to support multi-stage builds.
* Obviously, you will need a `git` client for pulling source code and pushing back your changes.

View File

@@ -265,3 +265,9 @@ data:
# published to the repository. Reconciliation by timeout is disabled if timeout is set to 0. Three minutes by default.
# > Note: argocd-repo-server deployment must be manually restarted after changing the setting.
timeout.reconciliation: 180s
# oidc.tls.insecure.skip.verify determines whether certificate verification is skipped when verifying tokens with the
# configured OIDC provider (either external or the bundled Dex instance). Setting this to "true" will cause JWT
# token verification to pass despite the OIDC provider having an invalid certificate. Only set to "true" if you
# understand the risks.
oidc.tls.insecure.skip.verify: "false"

View File

@@ -103,4 +103,8 @@ data:
reposerver.repo.cache.expiration: "24h0m0s"
# Cache expiration default (default 24h0m0s)
reposerver.default.cache.expiration: "24h0m0s"
# Max combined manifest file size for a single directory-type Application. In-memory manifest representation may be as
# much as 300x the manifest file size. Limit this to stay within the memory limits of the repo-server while allowing
# for 300x memory expansion and N Applications running at the same time.
# (example 10M max * 300 expansion * 10 Apps = 30G max theoretical memory usage).
reposerver.max.combined.directory.manifests.size: '10M'

View File

@@ -483,6 +483,7 @@ The secret data must include following fields:
* `name` - cluster name
* `server` - cluster api server url
* `namespaces` - optional comma-separated list of namespaces which are accessible in that cluster. Cluster level resources would be ignored if namespace list is not empty.
* `clusterResources` - optional boolean string (`"true"` or `"false"`) determining whether Argo CD can manage cluster-level resources on this cluster. This setting is used only if the list of managed namespaces is not empty.
* `config` - JSON representation of following data structure:
```yaml

View File

@@ -210,4 +210,45 @@ Argo CD logs payloads of most API requests except request that are considered se
can be found in [server/server.go](https://github.com/argoproj/argo-cd/blob/abba8dddce8cd897ba23320e3715690f465b4a95/server/server.go#L516).
Argo CD does not log IP addresses of clients requesting API endpoints, since the API server is typically behind a proxy. Instead, it is recommended
to configure IP addresses logging in the proxy server that sits in front of the API server.
to configure IP addresses logging in the proxy server that sits in front of the API server.
## Limiting Directory App Memory Usage
> >2.2.10, 2.1.16, >2.3.5
Directory-type Applications (those whose source is raw JSON or YAML files) can consume significant
[repo-server](architecture.md#repository-server) memory, depending on the size and structure of the YAML files.
To avoid over-using memory in the repo-server (potentially causing a crash and denial of service), set the
`reposerver.max.combined.directory.manifests.size` config option in [argocd-cmd-params-cm](argocd-cmd-params-cm.yaml).
This option limits the combined size of all JSON or YAML files in an individual app. Note that the in-memory
representation of a manifest may be as much as 300x the size of the manifest on disk. Also note that the limit is per
Application. If manifests are generated for multiple applications at once, memory usage will be higher.
**Example:**
Suppose your repo-server has a 10G memory limit, and you have ten Applications which use raw JSON or YAML files. To
calculate the max safe combined file size per Application, divide 10G by 300 * 10 Apps (300 being the worst-case memory
growth factor for the manifests).
```
10G / 300 * 10 = 3M
```
So a reasonably safe configuration for this setup would be a 3M limit per app.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
data:
reposerver.max.combined.directory.manifests.size: '3M'
```
The 300x ratio assumes a maliciously-crafted manifest file. If you only want to protect against accidental excessive
memory use, it is probably safe to use a smaller ratio.
Keep in mind that if a malicious user can create additional Applications, they can increase the total memory usage.
Grant [App creation privileges](rbac.md) carefully.

View File

@@ -13,27 +13,28 @@ argocd-repo-server [flags]
### Options
```
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--disable-tls Disable TLS on the gRPC endpoint
-h, --help help for argocd-repo-server
--logformat string Set the logging format. One of: text|json (default "text")
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
--metrics-port int Start metrics server on given port (default 8084)
--parallelismlimit int Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.
--port int Listen on given port for incoming connections (default 8081)
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
--redis-ca-certificate string Path to Redis server CA certificate (e.g. /etc/certs/redis/ca.crt). If not specified, system trusted CAs will be used for server certificate validation.
--redis-client-certificate string Path to Redis client certificate (e.g. /etc/certs/redis/client.crt).
--redis-client-key string Path to Redis client key (e.g. /etc/certs/redis/client.crt).
--redis-insecure-skip-tls-verify Skip Redis server certificate validation.
--redis-use-tls Use TLS when connecting to Redis.
--redisdb int Redis database.
--repo-cache-expiration duration Cache expiration for repo state, incl. app lists, app details, manifest generation, revision meta-data (default 24h0m0s)
--revision-cache-expiration duration Cache expiration for cached revision (default 3m0s)
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
--sentinelmaster string Redis sentinel master group name. (default "master")
--tlsciphers string The list of acceptable ciphers to be used when establishing TLS connections. Use 'list' to list available ciphers. (default "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_GCM_SHA384")
--tlsmaxversion string The maximum SSL/TLS version that is acceptable (one of: 1.0|1.1|1.2|1.3) (default "1.3")
--tlsminversion string The minimum SSL/TLS version that is acceptable (one of: 1.0|1.1|1.2|1.3) (default "1.2")
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--disable-tls Disable TLS on the gRPC endpoint
-h, --help help for argocd-repo-server
--logformat string Set the logging format. One of: text|json (default "text")
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
--max-combined-directory-manifests-size string Max combined size of manifest files in a directory-type Application (default "10M")
--metrics-port int Start metrics server on given port (default 8084)
--parallelismlimit int Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.
--port int Listen on given port for incoming connections (default 8081)
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
--redis-ca-certificate string Path to Redis server CA certificate (e.g. /etc/certs/redis/ca.crt). If not specified, system trusted CAs will be used for server certificate validation.
--redis-client-certificate string Path to Redis client certificate (e.g. /etc/certs/redis/client.crt).
--redis-client-key string Path to Redis client key (e.g. /etc/certs/redis/client.crt).
--redis-insecure-skip-tls-verify Skip Redis server certificate validation.
--redis-use-tls Use TLS when connecting to Redis.
--redisdb int Redis database.
--repo-cache-expiration duration Cache expiration for repo state, incl. app lists, app details, manifest generation, revision meta-data (default 24h0m0s)
--revision-cache-expiration duration Cache expiration for cached revision (default 3m0s)
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
--sentinelmaster string Redis sentinel master group name. (default "master")
--tlsciphers string The list of acceptable ciphers to be used when establishing TLS connections. Use 'list' to list available ciphers. (default "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_GCM_SHA384")
--tlsmaxversion string The maximum SSL/TLS version that is acceptable (one of: 1.0|1.1|1.2|1.3) (default "1.3")
--tlsminversion string The minimum SSL/TLS version that is acceptable (one of: 1.0|1.1|1.2|1.3) (default "1.2")
```

View File

@@ -14,3 +14,76 @@ Note that bundled Helm has been upgraded from 3.6.0 to v3.7+. This includes foll
- Experimental OCI support has been rewritten.
More information in the [Helm v3.7.0 release notes](https://github.com/helm/helm/releases/tag/v3.7.0).
## Support for private repo SSH keys using the SHA-1 signature hash algorithm is removed in 2.2.12
Argo CD 2.2.12 upgraded its base image from Ubuntu 21.10 to Ubuntu 22.04, which upgraded OpenSSH to 8.9. OpenSSH starting
with 8.8 [dropped support for the `ssh-rsa` SHA-1 key signature algorithm](https://www.openssh.com/txt/release-8.8).
The signature algorithm is _not_ the same as the algorithm used when generating the key. There is no need to update
keys.
The signature algorithm is negotiated with the SSH server when the connection is being set up. The client offers its
list of accepted signature algorithms, and if the server has a match, the connection proceeds. For most SSH servers on
up-to-date git providers, acceptable algorithms other than `ssh-rsa` should be available.
Before upgrading to Argo CD 2.2.12, check whether your git provider(s) using SSH authentication support algorithms newer
than `rsa-ssh`.
1. Make sure your version of SSH >= 8.9 (the version used by Argo CD). If not, upgrade it before proceeding.
```shell
ssh -V
```
Example output: `OpenSSH_8.9p1 Ubuntu-3, OpenSSL 3.0.2 15 Mar 2022`
2. Once you have a recent version of OpenSSH, follow the directions from the [OpenSSH 8.8 release notes](https://www.openssh.com/txt/release-8.7):
> To check whether a server is using the weak ssh-rsa public key
> algorithm, for host authentication, try to connect to it after
> removing the ssh-rsa algorithm from ssh(1)'s allowed list:
>
> ```shell
> ssh -oHostKeyAlgorithms=-ssh-rsa user@host
> ```
>
> If the host key verification fails and no other supported host key
> types are available, the server software on that host should be
> upgraded.
If the server does not support an acceptable version, you will get an error similar to this;
```
$ ssh -oHostKeyAlgorithms=-ssh-rsa vs-ssh.visualstudio.com
Unable to negotiate with 20.42.134.1 port 22: no matching host key type found. Their offer: ssh-rsa
```
This indicates that the server needs to update its supported key signature algorithms, and Argo CD will not connect
to it.
### Workaround
The [OpenSSH 8.8 release notes](https://www.openssh.com/txt/release-8.8) describe a workaround if you cannot change the
server's key signature algorithms configuration.
> Incompatibility is more likely when connecting to older SSH
> implementations that have not been upgraded or have not closely tracked
> improvements in the SSH protocol. For these cases, it may be necessary
> to selectively re-enable RSA/SHA1 to allow connection and/or user
> authentication via the HostkeyAlgorithms and PubkeyAcceptedAlgorithms
> options. For example, the following stanza in ~/.ssh/config will enable
> RSA/SHA1 for host and user authentication for a single destination host:
>
> ```
> Host old-host
> HostkeyAlgorithms +ssh-rsa
> PubkeyAcceptedAlgorithms +ssh-rsa
> ```
>
> We recommend enabling RSA/SHA1 only as a stopgap measure until legacy
> implementations can be upgraded or reconfigured with another key type
> (such as ECDSA or Ed25519).
To apply this to Argo CD, you could create a ConfigMap with the desired ssh config file and then mount it at
`/home/argocd/.ssh/config`.

View File

@@ -6,12 +6,15 @@ The Argo CD Notifications and ApplicationSet are part of Argo CD now. You no lon
The Notifications and ApplicationSet components are bundled into default Argo CD installation manifests.
The bundled manifests are drop-in replacements for the previous versions. If you are using Kustomize to bundle the manifests together then just
remove references to https://github.com/argoproj-labs/argocd-notifications and https://github.com/argoproj-labs/applicationset. No action is required
if you are using `kubectl apply`.
remove references to https://github.com/argoproj-labs/argocd-notifications and https://github.com/argoproj-labs/applicationset.
## Configure Additional ArgoCD Binaries
If you are using [the argocd-notifications helm chart](https://github.com/argoproj/argo-helm/tree/argocd-notifications-1.8.1/charts/argocd-notifications), you can move the chart [values](https://github.com/argoproj/argo-helm/blob/argocd-notifications-1.8.1/charts/argocd-notifications/values.yaml) to the `notifications` section of the argo-cd chart [values](https://github.com/argoproj/argo-helm/blob/main/charts/argo-cd/values.yaml#L2152). Although most values remain as is, for details please look up the values that are relevant to you.
We have removed non-Linux ArgoCD binaries (Darwin amd64 and Windows amd64) from the image ([#7668](https://github.com/argoproj/argo-cd/pull/7668)) and the associated download buttons in the help page in the UI.
No action is required if you are using `kubectl apply`.
## Configure Additional Argo CD Binaries
We have removed non-Linux Argo CD binaries (Darwin amd64 and Windows amd64) from the image ([#7668](https://github.com/argoproj/argo-cd/pull/7668)) and the associated download buttons in the help page in the UI.
Those removed binaries will still be included in the release assets and we made those configurable in [#7755](https://github.com/argoproj/argo-cd/pull/7755). You can add download buttons for other OS architectures by adding the following to your `argocd-cm` ConfigMap:
@@ -31,10 +34,89 @@ data:
help.download.windows-amd64: "path-or-url-to-download"
```
## Removed Python from the base image
If you are using a [Config Management Plugin](../../user-guide/config-management-plugins.md) that relies on Python, you
will need to build a custom image on the Argo CD base to install Python.
## Upgraded Kustomize Version
Note that bundled Kustomize version has been upgraded from 4.2.0 to 4.4.1.
## Upgrade Helm Version
## Upgraded Helm Version
Note that bundled Helm version has been upgraded from 3.7.1 to 3.8.0.
## Support for private repo SSH keys using the SHA-1 signature hash algorithm is removed in 2.3.7
Argo CD 2.3.7 upgraded its base image from Ubuntu 21.04 to Ubuntu 22.04, which upgraded OpenSSH to 8.9. OpenSSH starting
with 8.8 [dropped support for the `ssh-rsa` SHA-1 key signature algorithm](https://www.openssh.com/txt/release-8.8).
The signature algorithm is _not_ the same as the algorithm used when generating the key. There is no need to update
keys.
The signature algorithm is negotiated with the SSH server when the connection is being set up. The client offers its
list of accepted signature algorithms, and if the server has a match, the connection proceeds. For most SSH servers on
up-to-date git providers, acceptable algorithms other than `ssh-rsa` should be available.
Before upgrading to Argo CD 2.3.7, check whether your git provider(s) using SSH authentication support algorithms newer
than `rsa-ssh`.
1. Make sure your version of SSH >= 8.9 (the version used by Argo CD). If not, upgrade it before proceeding.
```shell
ssh -V
```
Example output: `OpenSSH_8.9p1 Ubuntu-3, OpenSSL 3.0.2 15 Mar 2022`
2. Once you have a recent version of OpenSSH, follow the directions from the [OpenSSH 8.8 release notes](https://www.openssh.com/txt/release-8.7):
> To check whether a server is using the weak ssh-rsa public key
> algorithm, for host authentication, try to connect to it after
> removing the ssh-rsa algorithm from ssh(1)'s allowed list:
>
> ```shell
> ssh -oHostKeyAlgorithms=-ssh-rsa user@host
> ```
>
> If the host key verification fails and no other supported host key
> types are available, the server software on that host should be
> upgraded.
If the server does not support an acceptable version, you will get an error similar to this;
```
$ ssh -oHostKeyAlgorithms=-ssh-rsa vs-ssh.visualstudio.com
Unable to negotiate with 20.42.134.1 port 22: no matching host key type found. Their offer: ssh-rsa
```
This indicates that the server needs to update its supported key signature algorithms, and Argo CD will not connect
to it.
### Workaround
The [OpenSSH 8.8 release notes](https://www.openssh.com/txt/release-8.8) describe a workaround if you cannot change the
server's key signature algorithms configuration.
> Incompatibility is more likely when connecting to older SSH
> implementations that have not been upgraded or have not closely tracked
> improvements in the SSH protocol. For these cases, it may be necessary
> to selectively re-enable RSA/SHA1 to allow connection and/or user
> authentication via the HostkeyAlgorithms and PubkeyAcceptedAlgorithms
> options. For example, the following stanza in ~/.ssh/config will enable
> RSA/SHA1 for host and user authentication for a single destination host:
>
> ```
> Host old-host
> HostkeyAlgorithms +ssh-rsa
> PubkeyAcceptedAlgorithms +ssh-rsa
> ```
>
> We recommend enabling RSA/SHA1 only as a stopgap measure until legacy
> implementations can be upgraded or reconfigured with another key type
> (such as ECDSA or Ed25519).
To apply this to Argo CD, you could create a ConfigMap with the desired ssh config file and then mount it at
`/home/argocd/.ssh/config`.
Note that bundled Helm version has been upgraded from 3.7.1 to 3.8.0.

View File

@@ -495,3 +495,20 @@ data:
clientSecret: $another-secret:oidc.auth0.clientSecret # Mind the ':'
...
```
### Skipping certificate verification on OIDC provider connections
By default, all connections made by the API server to OIDC providers (either external providers or the bundled Dex
instance) must pass certificate validation. These connections occur when getting the OIDC provider's well-known
configuration, when getting the OIDC provider's keys, and when exchanging an authorization code or verifying an ID
token as part of an OIDC login flow.
Disabling certificate verification might make sense if:
* You are using the bundled Dex instance **and** your Argo CD instance has TLS configured with a self-signed certificate
**and** you understand and accept the risks of skipping OIDC provider cert verification.
* You are using an external OIDC provider **and** that provider uses an invalid certificate **and** you cannot solve
the problem by setting `oidcConfig.rootCA` **and** you understand and accept the risks of skipping OIDC provider cert
verification.
If either of those two applies, then you can disable OIDC provider certificate verification by setting
`oidc.tls.insecure.skip.verify` to `"true"` in the `argocd-cm` ConfigMap.

View File

@@ -2,4 +2,5 @@ mkdocs==1.2.3
mkdocs-material==7.1.7
markdown_include==0.6.0
pygments==2.7.4
jinja2===3.0.3
jinja2==3.0.3
markdown==3.3.7

View File

@@ -187,3 +187,17 @@ spec:
```
The example above shows how an ArgoCD Application can be configured so it will ignore the `spec.replicas` field from the desired state (git) during the sync stage. This is achieve by calculating and pre-patching the desired state before applying it in the cluster. Note that the `RespectIgnoreDifferences` sync option is only effective when the resource is already created in the cluster. If the Application is being created and no live state exists, the desired state is applied as-is.
## Create Namespace
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
namespace: test
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
```
The example above shows how an Argo CD Application can be configured so it will create namespaces for the Application resources if the namespaces don't exist already. Without this either declared in the Application manifest or passed in the cli via `--sync-option CreateNamespace=true`, the Application will fail to sync if the resources' namespaces do not exist.

8
go.mod
View File

@@ -64,10 +64,9 @@ require (
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/undefinedlabs/go-mpatch v1.0.6
github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/net v0.0.0-20211209124913-491a49abca63
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
@@ -204,7 +203,7 @@ require (
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/apiserver v0.23.1 // indirect
k8s.io/apiserver v0.23.1
k8s.io/cli-runtime v0.23.1 // indirect
k8s.io/component-base v0.23.1 // indirect
k8s.io/component-helpers v0.23.1 // indirect
@@ -227,6 +226,9 @@ replace (
google.golang.org/grpc => google.golang.org/grpc v1.15.0
// Avoid CVE-2022-28948
gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
k8s.io/api => k8s.io/api v0.23.1
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1
k8s.io/apimachinery => k8s.io/apimachinery v0.23.1

12
go.sum
View File

@@ -963,8 +963,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/undefinedlabs/go-mpatch v1.0.6 h1:h8q5ORH/GaOE1Se1DMhrOyljXZEhRcROO7agMqWXCOY=
github.com/undefinedlabs/go-mpatch v1.0.6/go.mod h1:TyJZDQ/5AgyN7FSLiBJ8RO9u2c6wbtRvK827b6AVqY4=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@@ -1075,8 +1073,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1181,6 +1180,7 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1549,10 +1549,8 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=

View File

@@ -1,4 +1,4 @@
#!/bin/bash
set -eux -o pipefail
GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.38.0
GO111MODULE=on go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.46.2

View File

@@ -28,7 +28,7 @@ spec:
name: dexconfig
containers:
- name: dex
image: ghcr.io/dexidp/dex:v2.30.2
image: ghcr.io/dexidp/dex:v2.35.1-distroless
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.3.4
newTag: v2.3.9
resources:
- ./application-controller
- ./dex

View File

@@ -21,7 +21,7 @@ spec:
serviceAccountName: argocd-redis
containers:
- name: redis
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: Always
args:
- "--save"

View File

@@ -98,6 +98,12 @@ spec:
name: argocd-cmd-params-cm
key: reposerver.default.cache.expiration
optional: true
- name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: reposerver.max.combined.directory.manifests.size
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME

View File

@@ -9564,7 +9564,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -9686,13 +9686,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.max.combined.directory.manifests.size
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -9741,7 +9747,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -9906,7 +9912,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

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

View File

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

View File

@@ -770,7 +770,7 @@ spec:
topologyKey: kubernetes.io/hostname
initContainers:
- name: config-init
image: haproxy:2.0.25-alpine
image: haproxy:2.0.29-alpine
imagePullPolicy: IfNotPresent
resources:
{}
@@ -790,7 +790,7 @@ spec:
runAsUser: 1000
containers:
- name: haproxy
image: haproxy:2.0.25-alpine
image: haproxy:2.0.29-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
@@ -878,7 +878,7 @@ spec:
automountServiceAccountToken: false
initContainers:
- name: config-init
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
resources:
{}
@@ -906,7 +906,7 @@ spec:
containers:
- name: redis
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
@@ -947,7 +947,7 @@ spec:
lifecycle:
{}
- name: sentinel
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
command:
- redis-sentinel

View File

@@ -9,12 +9,12 @@ redis-ha:
haproxy:
enabled: true
image:
tag: 2.0.25-alpine
tag: 2.0.29-alpine
timeout:
server: 6m
client: 6m
checkInterval: 3s
image:
tag: 6.2.6-alpine
tag: 6.2.7-alpine
sentinel:
bind: "0.0.0.0"

View File

@@ -10494,7 +10494,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.2
image: ghcr.io/dexidp/dex:v2.35.1-distroless
imagePullPolicy: Always
name: dex
ports:
@@ -10516,7 +10516,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -10549,7 +10549,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -10612,7 +10612,7 @@ spec:
app.kubernetes.io/name: argocd-redis-ha-haproxy
topologyKey: kubernetes.io/hostname
containers:
- image: haproxy:2.0.25-alpine
- image: haproxy:2.0.29-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -10641,7 +10641,7 @@ spec:
- /readonly/haproxy_init.sh
command:
- sh
image: haproxy:2.0.25-alpine
image: haproxy:2.0.29-alpine
imagePullPolicy: IfNotPresent
name: config-init
volumeMounts:
@@ -10776,13 +10776,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.max.combined.directory.manifests.size
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -10831,7 +10837,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -11058,7 +11064,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -11254,7 +11260,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -11336,7 +11342,7 @@ spec:
- /data/conf/redis.conf
command:
- redis-server
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -11374,7 +11380,7 @@ spec:
- /data/conf/sentinel.conf
command:
- redis-sentinel
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -11420,7 +11426,7 @@ spec:
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
- name: SENTINEL_ID_2
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
name: config-init
volumeMounts:

View File

@@ -7790,7 +7790,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.2
image: ghcr.io/dexidp/dex:v2.35.1-distroless
imagePullPolicy: Always
name: dex
ports:
@@ -7812,7 +7812,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -7845,7 +7845,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -7908,7 +7908,7 @@ spec:
app.kubernetes.io/name: argocd-redis-ha-haproxy
topologyKey: kubernetes.io/hostname
containers:
- image: haproxy:2.0.25-alpine
- image: haproxy:2.0.29-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -7937,7 +7937,7 @@ spec:
- /readonly/haproxy_init.sh
command:
- sh
image: haproxy:2.0.25-alpine
image: haproxy:2.0.29-alpine
imagePullPolicy: IfNotPresent
name: config-init
volumeMounts:
@@ -8072,13 +8072,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.max.combined.directory.manifests.size
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -8127,7 +8133,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -8354,7 +8360,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -8550,7 +8556,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -8632,7 +8638,7 @@ spec:
- /data/conf/redis.conf
command:
- redis-server
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -8670,7 +8676,7 @@ spec:
- /data/conf/sentinel.conf
command:
- redis-sentinel
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -8716,7 +8722,7 @@ spec:
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
- name: SENTINEL_ID_2
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: IfNotPresent
name: config-init
volumeMounts:

View File

@@ -9864,7 +9864,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.2
image: ghcr.io/dexidp/dex:v2.35.1-distroless
imagePullPolicy: Always
name: dex
ports:
@@ -9886,7 +9886,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -9919,7 +9919,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -9988,7 +9988,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -10110,13 +10110,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.max.combined.directory.manifests.size
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -10165,7 +10171,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -10388,7 +10394,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -10578,7 +10584,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -7160,7 +7160,7 @@ spec:
- command:
- /shared/argocd-dex
- rundex
image: ghcr.io/dexidp/dex:v2.30.2
image: ghcr.io/dexidp/dex:v2.35.1-distroless
imagePullPolicy: Always
name: dex
ports:
@@ -7182,7 +7182,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -7215,7 +7215,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -7284,7 +7284,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:6.2.6-alpine
image: redis:6.2.7-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -7406,13 +7406,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.max.combined.directory.manifests.size
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -7461,7 +7467,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
name: copyutil
volumeMounts:
- mountPath: /var/run/argocd
@@ -7684,7 +7690,7 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -7874,7 +7880,7 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.3.4
image: quay.io/argoproj/argocd:v2.3.9
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -100,7 +100,11 @@ func (c *client) executeRequest(fullMethodName string, msg []byte, md metadata.M
}
func (c *client) startGRPCProxy() (*grpc.Server, net.Listener, error) {
serverAddr := fmt.Sprintf("%s/argocd-%s.sock", os.TempDir(), rand.RandString(16))
randSuffix, err := rand.String(16)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate random socket filename: %w", err)
}
serverAddr := fmt.Sprintf("%s/argocd-%s.sock", os.TempDir(), randSuffix)
ln, err := net.Listen("unix", serverAddr)
if err != nil {

View File

@@ -18,6 +18,10 @@ import (
"strings"
"time"
kubeyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/argoproj/argo-cd/v2/util/io/files"
"github.com/Masterminds/semver/v3"
@@ -71,6 +75,8 @@ const (
ociPrefix = "oci://"
)
var ErrExceededMaxCombinedManifestFileSize = errors.New("exceeded max combined manifest file size")
// Service implements ManifestService interface
type Service struct {
gitCredsStore git.CredsStore
@@ -96,6 +102,7 @@ type RepoServerInitConstants struct {
PauseGenerationOnFailureForMinutes int
PauseGenerationOnFailureForRequests int
SubmoduleEnabled bool
MaxCombinedDirectoryManifestsSize resource.Quantity
}
// NewService returns a new instance of the Manifest service
@@ -385,7 +392,7 @@ func (s *Service) runManifestGen(ctx context.Context, repoRoot, commitSHA, cache
var manifestGenResult *apiclient.ManifestResponse
opContext, err := opContextSrc()
if err == nil {
manifestGenResult, err = GenerateManifests(ctx, opContext.appPath, repoRoot, commitSHA, q, false, s.gitCredsStore)
manifestGenResult, err = GenerateManifests(ctx, opContext.appPath, repoRoot, commitSHA, q, false, s.gitCredsStore, s.initConstants.MaxCombinedDirectoryManifestsSize)
}
if err != nil {
@@ -771,7 +778,7 @@ func getRepoCredential(repoCredentials []*v1alpha1.RepoCreds, repoURL string) *v
}
// GenerateManifests generates manifests from a path
func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string, q *apiclient.ManifestRequest, isLocal bool, gitCredsStore git.CredsStore) (*apiclient.ManifestResponse, error) {
func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string, q *apiclient.ManifestRequest, isLocal bool, gitCredsStore git.CredsStore, maxCombinedManifestQuantity resource.Quantity) (*apiclient.ManifestResponse, error) {
var targetObjs []*unstructured.Unstructured
var dest *v1alpha1.ApplicationDestination
@@ -813,7 +820,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
directory = &v1alpha1.ApplicationSourceDirectory{}
}
logCtx := log.WithField("application", q.AppName)
targetObjs, err = findManifests(logCtx, appPath, repoRoot, env, *directory, q.EnabledSourceTypes)
targetObjs, err = findManifests(logCtx, appPath, repoRoot, env, *directory, q.EnabledSourceTypes, maxCombinedManifestQuantity)
}
if err != nil {
return nil, err
@@ -1015,63 +1022,30 @@ func ksShow(appLabelKey, appPath string, ksonnetOpts *v1alpha1.ApplicationSource
var manifestFile = regexp.MustCompile(`^.*\.(yaml|yml|json|jsonnet)$`)
// findManifests looks at all yaml files in a directory and unmarshals them into a list of unstructured objects
func findManifests(logCtx *log.Entry, appPath string, repoRoot string, env *v1alpha1.Env, directory v1alpha1.ApplicationSourceDirectory, enabledManifestGeneration map[string]bool) ([]*unstructured.Unstructured, error) {
func findManifests(logCtx *log.Entry, appPath string, repoRoot string, env *v1alpha1.Env, directory v1alpha1.ApplicationSourceDirectory, enabledManifestGeneration map[string]bool, maxCombinedManifestQuantity resource.Quantity) ([]*unstructured.Unstructured, error) {
// Validate the directory before loading any manifests to save memory.
potentiallyValidManifests, err := getPotentiallyValidManifests(logCtx, appPath, repoRoot, directory.Recurse, directory.Include, directory.Exclude, maxCombinedManifestQuantity)
if err != nil {
logCtx.Errorf("failed to get potentially valid manifests: %s", err)
return nil, fmt.Errorf("failed to get potentially valid manifests: %w", err)
}
var objs []*unstructured.Unstructured
err := filepath.Walk(appPath, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(appPath, path)
if err != nil {
return fmt.Errorf("failed to get relative path of symlink: %w", err)
}
if files.IsSymlink(f) {
realPath, err := filepath.EvalSymlinks(path)
if err != nil {
logCtx.Debugf("error checking symlink realpath: %s", err)
if os.IsNotExist(err) {
log.Warnf("ignoring out-of-bounds symlink at %q: %s", relPath, err)
return nil
} else {
return fmt.Errorf("failed to evaluate symlink at %q: %w", relPath, err)
}
}
if !files.Inbound(realPath, appPath) {
logCtx.Warnf("illegal filepath in symlink: %s", realPath)
return fmt.Errorf("illegal filepath in symlink at %q", relPath)
}
}
if f.IsDir() {
if path != appPath && !directory.Recurse {
return filepath.SkipDir
} else {
return nil
}
}
for _, potentiallyValidManifest := range potentiallyValidManifests {
manifestPath := potentiallyValidManifest.path
manifestFileInfo := potentiallyValidManifest.fileInfo
if !manifestFile.MatchString(f.Name()) {
return nil
}
if directory.Exclude != "" && glob.Match(directory.Exclude, relPath) {
return nil
}
if directory.Include != "" && !glob.Match(directory.Include, relPath) {
return nil
}
if strings.HasSuffix(f.Name(), ".jsonnet") {
if strings.HasSuffix(manifestFileInfo.Name(), ".jsonnet") {
if !discovery.IsManifestGenerationEnabled(v1alpha1.ApplicationSourceTypeDirectory, enabledManifestGeneration) {
return nil
continue
}
vm, err := makeJsonnetVm(appPath, repoRoot, directory.Jsonnet, env)
if err != nil {
return err
return nil, err
}
jsonStr, err := vm.EvaluateFile(path)
jsonStr, err := vm.EvaluateFile(manifestPath)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to evaluate jsonnet %q: %v", f.Name(), err)
return nil, status.Errorf(codes.FailedPrecondition, "Failed to evaluate jsonnet %q: %v", manifestFileInfo.Name(), err)
}
// attempt to unmarshal either array or single object
@@ -1083,49 +1057,207 @@ func findManifests(logCtx *log.Entry, appPath string, repoRoot string, env *v1al
var jsonObj unstructured.Unstructured
err = json.Unmarshal([]byte(jsonStr), &jsonObj)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal generated json %q: %v", f.Name(), err)
return nil, status.Errorf(codes.FailedPrecondition, "Failed to unmarshal generated json %q: %v", manifestFileInfo.Name(), err)
}
objs = append(objs, &jsonObj)
}
} else {
out, err := utfutil.ReadFile(path, utfutil.UTF8)
err := getObjsFromYAMLOrJson(logCtx, manifestPath, manifestFileInfo.Name(), &objs)
if err != nil {
return err
}
if strings.HasSuffix(f.Name(), ".json") {
var obj unstructured.Unstructured
err = json.Unmarshal(out, &obj)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
objs = append(objs, &obj)
} else {
yamlObjs, err := kube.SplitYAML(out)
if err != nil {
if len(yamlObjs) > 0 {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, let's see if it looks like a resource, if yes, we return error
if bytes.Contains(out, []byte("apiVersion:")) &&
bytes.Contains(out, []byte("kind:")) &&
bytes.Contains(out, []byte("metadata:")) {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, it might be a unrelated YAML file which we will ignore
return nil
}
objs = append(objs, yamlObjs...)
return nil, err
}
}
}
return objs, nil
}
// getObjsFromYAMLOrJson unmarshals the given yaml or json file and appends it to the given list of objects.
func getObjsFromYAMLOrJson(logCtx *log.Entry, manifestPath string, filename string, objs *[]*unstructured.Unstructured) error {
reader, err := utfutil.OpenFile(manifestPath, utfutil.UTF8)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to open %q", manifestPath)
}
defer func() {
err := reader.Close()
if err != nil {
logCtx.Errorf("failed to close %q - potential memory leak", manifestPath)
}
}()
if strings.HasSuffix(filename, ".json") {
var obj unstructured.Unstructured
decoder := json.NewDecoder(reader)
err = decoder.Decode(&obj)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", filename, err)
}
if decoder.More() {
return status.Errorf(codes.FailedPrecondition, "Found multiple objects in %q. Only single objects are allowed in JSON files.", filename)
}
*objs = append(*objs, &obj)
} else {
yamlObjs, err := splitYAMLOrJSON(reader)
if err != nil {
if len(yamlObjs) > 0 {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", filename, err)
}
// Read the whole file to check whether it looks like a manifest.
out, err := utfutil.ReadFile(manifestPath, utfutil.UTF8)
// Otherwise, let's see if it looks like a resource, if yes, we return error
if bytes.Contains(out, []byte("apiVersion:")) &&
bytes.Contains(out, []byte("kind:")) &&
bytes.Contains(out, []byte("metadata:")) {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", filename, err)
}
// Otherwise, it might be an unrelated YAML file which we will ignore
}
*objs = append(*objs, yamlObjs...)
}
return nil
}
// splitYAMLOrJSON reads a YAML or JSON file and gets each document as an unstructured object. If the unmarshaller
// encounters an error, objects read up until the error are returned.
func splitYAMLOrJSON(reader goio.Reader) ([]*unstructured.Unstructured, error) {
d := kubeyaml.NewYAMLOrJSONDecoder(reader, 4096)
var objs []*unstructured.Unstructured
for {
u := &unstructured.Unstructured{}
if err := d.Decode(&u); err != nil {
if err == goio.EOF {
break
}
return objs, fmt.Errorf("failed to unmarshal manifest: %v", err)
}
if u == nil {
continue
}
objs = append(objs, u)
}
return objs, nil
}
// getPotentiallyValidManifestFile checks whether the given path/FileInfo may be a valid manifest file. Returns a non-nil error if
// there was an error that should not be handled by ignoring the file. Returns non-nil realFileInfo if the file is a
// potential manifest. Returns a non-empty ignoreMessage if there's a message that should be logged about why the file
// was skipped. If realFileInfo is nil and the ignoreMessage is empty, there's no need to log the ignoreMessage; the
// file was skipped for a mundane reason.
//
// The file is still only a "potentially" valid manifest file because it could be invalid JSON or YAML, or it might not
// be a valid Kubernetes resource. This function tests everything possible without actually reading the file.
//
// repoPath must be absolute.
func getPotentiallyValidManifestFile(path string, f os.FileInfo, appPath, repoRoot, include, exclude string) (realFileInfo os.FileInfo, warning string, err error) {
relPath, err := filepath.Rel(appPath, path)
if err != nil {
return nil, "", fmt.Errorf("failed to get relative path of %q: %w", path, err)
}
if !manifestFile.MatchString(f.Name()) {
return nil, "", nil
}
// If the file is a symlink, these will be overridden with the destination file's info.
var relRealPath = relPath
realFileInfo = f
if files.IsSymlink(f) {
realPath, err := filepath.EvalSymlinks(path)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Sprintf("destination of symlink %q is missing", relPath), nil
}
return nil, "", fmt.Errorf("failed to evaluate symlink at %q: %w", relPath, err)
}
if !files.Inbound(realPath, repoRoot) {
return nil, "", fmt.Errorf("illegal filepath in symlink at %q", relPath)
}
realFileInfo, err = os.Stat(realPath)
if err != nil {
if os.IsNotExist(err) {
// This should have been caught by filepath.EvalSymlinks, but check again since that function's docs
// don't promise to return this error.
return nil, fmt.Sprintf("destination of symlink %q is missing at %q", relPath, realPath), nil
}
return nil, "", fmt.Errorf("failed to get file info for symlink at %q to %q: %w", relPath, realPath, err)
}
relRealPath, err = filepath.Rel(repoRoot, realPath)
if err != nil {
return nil, "", fmt.Errorf("failed to get relative path of %q: %w", realPath, err)
}
}
// FileInfo.Size() behavior is platform-specific for non-regular files. Allow only regular files, so we guarantee
// accurate file sizes.
if !realFileInfo.Mode().IsRegular() {
return nil, fmt.Sprintf("ignoring symlink at %q to non-regular file %q", relPath, relRealPath), nil
}
if exclude != "" && glob.Match(exclude, relPath) {
return nil, "", nil
}
if include != "" && !glob.Match(include, relPath) {
return nil, "", nil
}
return realFileInfo, "", nil
}
type potentiallyValidManifest struct {
path string
fileInfo os.FileInfo
}
// getPotentiallyValidManifests ensures that 1) there are no errors while checking for potential manifest files in the given dir
// and 2) the combined file size of the potentially-valid manifest files does not exceed the limit.
func getPotentiallyValidManifests(logCtx *log.Entry, appPath string, repoRoot string, recurse bool, include string, exclude string, maxCombinedManifestQuantity resource.Quantity) ([]potentiallyValidManifest, error) {
maxCombinedManifestFileSize := maxCombinedManifestQuantity.Value()
var currentCombinedManifestFileSize = int64(0)
var potentiallyValidManifests []potentiallyValidManifest
err := filepath.Walk(appPath, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
if path != appPath && !recurse {
return filepath.SkipDir
}
return nil
}
realFileInfo, warning, err := getPotentiallyValidManifestFile(path, f, appPath, repoRoot, include, exclude)
if err != nil {
return fmt.Errorf("invalid manifest file %q: %w", path, err)
}
if realFileInfo == nil {
if warning != "" {
logCtx.Warnf("skipping manifest file %q: %s", path, warning)
}
return nil
}
// Don't count jsonnet file size against max. It's jsonnet's responsibility to manage memory usage.
if !strings.HasSuffix(f.Name(), ".jsonnet") {
// We use the realFileInfo size (which is guaranteed to be a regular file instead of a symlink or other
// non-regular file) because .Size() behavior is platform-specific for non-regular files.
currentCombinedManifestFileSize += realFileInfo.Size()
if maxCombinedManifestFileSize != 0 && currentCombinedManifestFileSize > maxCombinedManifestFileSize {
return ErrExceededMaxCombinedManifestFileSize
}
}
potentiallyValidManifests = append(potentiallyValidManifests, potentiallyValidManifest{path: path, fileInfo: f})
return nil
})
if err != nil {
// Not wrapping, because this error should be wrapped by the caller.
return nil, err
}
return objs, nil
return potentiallyValidManifests, nil
}
func makeJsonnetVm(appPath string, repoRoot string, sourceJsonnet v1alpha1.ApplicationSourceJsonnet, env *v1alpha1.Env) (*jsonnet.VM, error) {
@@ -1431,8 +1563,12 @@ func populateHelmAppDetails(res *apiclient.RepoAppDetailsResponse, appPath strin
return err
}
if err := loadFileIntoIfExists(filepath.Join(appPath, "values.yaml"), &res.Helm.Values); err != nil {
return err
if resolvedValuesPath, _, err := pathutil.ResolveFilePath(appPath, repoRoot, "values.yaml", []string{}); err == nil {
if err := loadFileIntoIfExists(resolvedValuesPath, &res.Helm.Values); err != nil {
return err
}
} else {
log.Warnf("Values file %s is not allowed: %v", filepath.Join(appPath, "values.yaml"), err)
}
var resolvedSelectedValueFiles []pathutil.ResolvedFilePath
// drop not allowed values files
@@ -1440,10 +1576,10 @@ func populateHelmAppDetails(res *apiclient.RepoAppDetailsResponse, appPath strin
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)
log.Warnf("Values file %s is not allowed: %v", file, err)
}
}
params, err := h.GetParameters(resolvedSelectedValueFiles)
params, err := h.GetParameters(resolvedSelectedValueFiles, appPath, repoRoot)
if err != nil {
return err
}
@@ -1462,15 +1598,16 @@ func populateHelmAppDetails(res *apiclient.RepoAppDetailsResponse, appPath strin
return nil
}
func loadFileIntoIfExists(path string, destination *string) error {
info, err := os.Stat(path)
func loadFileIntoIfExists(path pathutil.ResolvedFilePath, destination *string) error {
stringPath := string(path)
info, err := os.Stat(stringPath)
if err == nil && !info.IsDir() {
if bytes, err := ioutil.ReadFile(path); err != nil {
*destination = string(bytes)
} else {
bytes, err := ioutil.ReadFile(stringPath);
if err != nil {
return err
}
*destination = string(bytes)
}
return nil

View File

@@ -1,47 +0,0 @@
//go:build !race
// +build !race
package repository
import (
"os"
"path/filepath"
"sync"
"testing"
"github.com/stretchr/testify/assert"
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
)
func TestHelmDependencyWithConcurrency(t *testing.T) {
// !race:
// Un-synchronized use of a random source, will be fixed when this is merged:
// https://github.com/argoproj/argo-cd/issues/4728
cleanup := func() {
_ = os.Remove(filepath.Join("../../util/helm/testdata/helm2-dependency", helmDepUpMarkerFile))
_ = os.RemoveAll(filepath.Join("../../util/helm/testdata/helm2-dependency", "charts"))
}
cleanup()
defer cleanup()
helmRepo := argoappv1.Repository{Name: "bitnami", Type: "helm", Repo: "https://charts.bitnami.com/bitnami"}
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
res, err := helmTemplate("../../util/helm/testdata/helm2-dependency", "../..", nil, &apiclient.ManifestRequest{
ApplicationSource: &argoappv1.ApplicationSource{},
Repos: []*argoappv1.Repository{&helmRepo},
}, false)
assert.NoError(t, err)
assert.NotNil(t, res)
wg.Done()
}()
}
wg.Wait()
}

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
goio "io"
"io/fs"
"io/ioutil"
"os"
"os/exec"
@@ -17,6 +18,7 @@ import (
"time"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
@@ -131,6 +133,31 @@ func newServiceWithCommitSHA(root, revision string) *Service {
return service
}
// createSymlink creates a symlink with name linkName to file destName in
// workingDir
func createSymlink(t *testing.T, workingDir, destName, linkName string) error {
oldWorkingDir, err := os.Getwd()
if err != nil {
return err
}
if workingDir != "" {
err = os.Chdir(workingDir)
if err != nil {
return err
}
defer func() {
if err := os.Chdir(oldWorkingDir); err != nil {
t.Fatal(err.Error())
}
}()
}
err = os.Symlink(destName, linkName)
if err != nil {
return err
}
return nil
}
func TestGenerateYamlManifestInDir(t *testing.T) {
service := newService("../..")
@@ -146,7 +173,7 @@ func TestGenerateYamlManifestInDir(t *testing.T) {
assert.Equal(t, countOfManifests, len(res1.Manifests))
// this will test concatenated manifests to verify we split YAMLs correctly
res2, err := GenerateManifests(context.Background(), "./testdata/concatenated", "/", "", &q, false, &git.NoopCredsStore{})
res2, err := GenerateManifests(context.Background(), "./testdata/concatenated", "/", "", &q, false, &git.NoopCredsStore{}, resource.MustParse("0"))
assert.NoError(t, err)
assert.Equal(t, 3, len(res2.Manifests))
}
@@ -202,7 +229,7 @@ func Test_GenerateManifests_NoOutOfBoundsAccess(t *testing.T) {
}
q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &argoappv1.ApplicationSource{}}
res, err := GenerateManifests(context.Background(), repoDir, "", "", &q, false, &git.NoopCredsStore{})
res, err := GenerateManifests(context.Background(), repoDir, "", "", &q, false, &git.NoopCredsStore{}, resource.MustParse("0"))
require.Error(t, err)
assert.NotContains(t, err.Error(), mustNotContain)
assert.Contains(t, err.Error(), "illegal filepath")
@@ -217,7 +244,7 @@ func TestGenerateManifests_MissingSymlinkDestination(t *testing.T) {
require.NoError(t, err)
q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &argoappv1.ApplicationSource{}}
_, err = GenerateManifests(context.Background(), repoDir, "", "", &q, false, &git.NoopCredsStore{})
_, err = GenerateManifests(context.Background(), repoDir, "", "", &q, false, &git.NoopCredsStore{}, resource.MustParse("0"))
require.NoError(t, err)
}
@@ -374,29 +401,6 @@ func TestGenerateKsonnetManifest(t *testing.T) {
assert.Equal(t, "https://kubernetes.default.svc", res.Server)
}
func TestGenerateHelmChartWithDependencies(t *testing.T) {
service := newService("../..")
cleanup := func() {
_ = os.Remove(filepath.Join("../../util/helm/testdata/helm2-dependency", helmDepUpMarkerFile))
_ = os.RemoveAll(filepath.Join("../../util/helm/testdata/helm2-dependency", "charts"))
}
cleanup()
defer cleanup()
helmRepo := argoappv1.Repository{Name: "bitnami", Type: "helm", Repo: "https://charts.bitnami.com/bitnami"}
q := apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
ApplicationSource: &argoappv1.ApplicationSource{
Path: "./util/helm/testdata/helm2-dependency",
},
Repos: []*argoappv1.Repository{&helmRepo},
}
res1, err := service.GenerateManifest(context.Background(), &q)
assert.Nil(t, err)
assert.Len(t, res1.Manifests, 10)
}
func TestManifestGenErrorCacheByNumRequests(t *testing.T) {
// Returns the state of the manifest generation cache, by querying the cache for the previously set result
@@ -1152,7 +1156,7 @@ func TestGenerateFromUTF16(t *testing.T) {
Repo: &argoappv1.Repository{},
ApplicationSource: &argoappv1.ApplicationSource{},
}
res1, err := GenerateManifests(context.Background(), "./testdata/utf-16", "/", "", &q, false, &git.NoopCredsStore{})
res1, err := GenerateManifests(context.Background(), "./testdata/utf-16", "/", "", &q, false, &git.NoopCredsStore{}, resource.MustParse("0"))
assert.Nil(t, err)
assert.Equal(t, 2, len(res1.Manifests))
}
@@ -1168,12 +1172,15 @@ func TestListApps(t *testing.T) {
"app-parameters/multi": "Kustomize",
"app-parameters/single-app-only": "Kustomize",
"app-parameters/single-global": "Kustomize",
"in-bounds-values-file-link": "Helm",
"invalid-helm": "Helm",
"invalid-kustomize": "Kustomize",
"kustomization_yaml": "Kustomize",
"kustomization_yml": "Kustomize",
"my-chart": "Helm",
"my-chart-2": "Helm",
"out-of-bounds-values-file-link": "Helm",
"values-files": "Helm",
}
assert.Equal(t, expectedApps, res.Apps)
}
@@ -1512,6 +1519,7 @@ func runWithTempTestdata(t *testing.T, path string, runner func(t *testing.T, pa
tempDir := mkTempParameters("./testdata/app-parameters")
defer os.RemoveAll(tempDir)
runner(t, filepath.Join(tempDir, "app-parameters", path))
os.RemoveAll(tempDir)
}
func TestGenerateManifestsWithAppParameterFile(t *testing.T) {
@@ -1717,7 +1725,7 @@ func TestFindResources(t *testing.T) {
Recurse: true,
Include: tc.include,
Exclude: tc.exclude,
}, map[string]bool{})
}, map[string]bool{}, resource.MustParse("0"))
if !assert.NoError(t, err) {
return
}
@@ -1734,7 +1742,7 @@ func TestFindManifests_Exclude(t *testing.T) {
objs, err := findManifests(&log.Entry{}, "testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
Recurse: true,
Exclude: "subdir/deploymentSub.yaml",
}, map[string]bool{})
}, map[string]bool{}, resource.MustParse("0"))
if !assert.NoError(t, err) || !assert.Len(t, objs, 1) {
return
@@ -1747,7 +1755,7 @@ func TestFindManifests_Exclude_NothingMatches(t *testing.T) {
objs, err := findManifests(&log.Entry{}, "testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
Recurse: true,
Exclude: "nothing.yaml",
}, map[string]bool{})
}, map[string]bool{}, resource.MustParse("0"))
if !assert.NoError(t, err) || !assert.Len(t, objs, 2) {
return
@@ -1757,6 +1765,479 @@ func TestFindManifests_Exclude_NothingMatches(t *testing.T) {
[]string{"nginx-deployment", "nginx-deployment-sub"}, []string{objs[0].GetName(), objs[1].GetName()})
}
func tempDir(t *testing.T) string {
dir, err := ioutil.TempDir(".", "")
require.NoError(t, err)
t.Cleanup(func() {
err = os.RemoveAll(dir)
if err != nil {
panic(err)
}
})
absDir, err := filepath.Abs(dir)
require.NoError(t, err)
return absDir
}
func walkFor(t *testing.T, root string, testPath string, run func(info fs.FileInfo)) {
var hitExpectedPath = false
err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
if path == testPath {
require.NoError(t, err)
hitExpectedPath = true
run(info)
}
return nil
})
require.NoError(t, err)
assert.True(t, hitExpectedPath, "did not hit expected path when walking directory")
}
func Test_getPotentiallyValidManifestFile(t *testing.T) {
// These tests use filepath.Walk instead of os.Stat to get file info, because FileInfo from os.Stat does not return
// true for IsSymlink like os.Walk does.
// These tests do not use t.TempDir() because those directories can contain symlinks which cause test to fail
// InBound checks.
t.Run("non-JSON/YAML is skipped with an empty ignore message", func(t *testing.T) {
appDir := tempDir(t)
filePath := filepath.Join(appDir, "not-json-or-yaml")
file, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
walkFor(t, appDir, filePath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(filePath, info, appDir, appDir, "", "")
assert.Nil(t, realFileInfo)
assert.Empty(t, ignoreMessage)
assert.NoError(t, err)
})
})
t.Run("circular link should throw an error", func(t *testing.T) {
appDir := tempDir(t)
aPath := filepath.Join(appDir, "a.json")
bPath := filepath.Join(appDir, "b.json")
err := os.Symlink(bPath, aPath)
require.NoError(t, err)
err = os.Symlink(aPath, bPath)
require.NoError(t, err)
walkFor(t, appDir, aPath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(aPath, info, appDir, appDir, "", "")
assert.Nil(t, realFileInfo)
assert.Empty(t, ignoreMessage)
assert.Error(t, err)
assert.Contains(t, err.Error(), "too many links")
})
})
t.Run("symlink with missing destination should throw an error", func(t *testing.T) {
appDir := tempDir(t)
aPath := filepath.Join(appDir, "a.json")
bPath := filepath.Join(appDir, "b.json")
err := os.Symlink(bPath, aPath)
require.NoError(t, err)
walkFor(t, appDir, aPath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(aPath, info, appDir, appDir, "", "")
assert.Nil(t, realFileInfo)
assert.NotEmpty(t, ignoreMessage)
assert.NoError(t, err)
})
})
t.Run("out-of-bounds symlink should throw an error", func(t *testing.T) {
appDir := tempDir(t)
linkPath := filepath.Join(appDir, "a.json")
err := os.Symlink("..", linkPath)
require.NoError(t, err)
walkFor(t, appDir, linkPath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(linkPath, info, appDir, appDir, "", "")
assert.Nil(t, realFileInfo)
assert.Empty(t, ignoreMessage)
assert.Error(t, err)
assert.Contains(t, err.Error(), "illegal filepath in symlink")
})
})
t.Run("symlink to a non-regular file should be skipped with warning", func(t *testing.T) {
appDir := tempDir(t)
dirPath := filepath.Join(appDir, "test.dir")
err := os.MkdirAll(dirPath, 0644)
require.NoError(t, err)
linkPath := filepath.Join(appDir, "test.json")
err = os.Symlink(dirPath, linkPath)
require.NoError(t, err)
walkFor(t, appDir, linkPath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(linkPath, info, appDir, appDir, "", "")
assert.Nil(t, realFileInfo)
assert.Contains(t, ignoreMessage, "non-regular file")
assert.NoError(t, err)
})
})
t.Run("non-included file should be skipped with no message", func(t *testing.T) {
appDir := tempDir(t)
filePath := filepath.Join(appDir, "not-included.yaml")
file, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
walkFor(t, appDir, filePath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(filePath, info, appDir, appDir, "*.json", "")
assert.Nil(t, realFileInfo)
assert.Empty(t, ignoreMessage)
assert.NoError(t, err)
})
})
t.Run("excluded file should be skipped with no message", func(t *testing.T) {
appDir := tempDir(t)
filePath := filepath.Join(appDir, "excluded.json")
file, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
walkFor(t, appDir, filePath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(filePath, info, appDir, appDir, "", "excluded.*")
assert.Nil(t, realFileInfo)
assert.Empty(t, ignoreMessage)
assert.NoError(t, err)
})
})
t.Run("symlink to a regular file is potentially valid", func(t *testing.T) {
appDir := tempDir(t)
filePath := filepath.Join(appDir, "regular-file")
file, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
linkPath := filepath.Join(appDir, "link.json")
err = os.Symlink(filePath, linkPath)
require.NoError(t, err)
walkFor(t, appDir, linkPath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(linkPath, info, appDir, appDir, "", "")
assert.NotNil(t, realFileInfo)
assert.Empty(t, ignoreMessage)
assert.NoError(t, err)
})
})
t.Run("a regular file is potentially valid", func(t *testing.T) {
appDir := tempDir(t)
filePath := filepath.Join(appDir, "regular-file.json")
file, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
walkFor(t, appDir, filePath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(filePath, info, appDir, appDir, "", "")
assert.NotNil(t, realFileInfo)
assert.Empty(t, ignoreMessage)
assert.NoError(t, err)
})
})
t.Run("realFileInfo is for the destination rather than the symlink", func(t *testing.T) {
appDir := tempDir(t)
filePath := filepath.Join(appDir, "regular-file")
file, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
linkPath := filepath.Join(appDir, "link.json")
err = os.Symlink(filePath, linkPath)
require.NoError(t, err)
walkFor(t, appDir, linkPath, func(info fs.FileInfo) {
realFileInfo, ignoreMessage, err := getPotentiallyValidManifestFile(linkPath, info, appDir, appDir, "", "")
assert.NotNil(t, realFileInfo)
assert.Equal(t, filepath.Base(filePath), realFileInfo.Name())
assert.Empty(t, ignoreMessage)
assert.NoError(t, err)
})
})
}
func Test_getPotentiallyValidManifests(t *testing.T) {
// Tests which return no manifests and an error check to make sure the directory exists before running. A missing
// directory would produce those same results.
logCtx := log.WithField("test", "test")
t.Run("unreadable file throws error", func(t *testing.T) {
appDir := t.TempDir()
unreadablePath := filepath.Join(appDir, "unreadable.json")
err := os.WriteFile(unreadablePath, []byte{}, 0666)
require.NoError(t, err)
err = os.Chmod(appDir, 0000)
require.NoError(t, err)
manifests, err := getPotentiallyValidManifests(logCtx, appDir, appDir, false, "", "", resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
// allow cleanup
err = os.Chmod(appDir, 0777)
if err != nil {
panic(err)
}
})
t.Run("no recursion when recursion is disabled", func(t *testing.T) {
manifests, err := getPotentiallyValidManifests(logCtx, "./testdata/recurse", "./testdata/recurse", false, "", "", resource.MustParse("0"))
assert.Len(t, manifests, 1)
assert.NoError(t, err)
})
t.Run("recursion when recursion is enabled", func(t *testing.T) {
manifests, err := getPotentiallyValidManifests(logCtx, "./testdata/recurse", "./testdata/recurse", true, "", "", resource.MustParse("0"))
assert.Len(t, manifests, 2)
assert.NoError(t, err)
})
t.Run("non-JSON/YAML is skipped", func(t *testing.T) {
manifests, err := getPotentiallyValidManifests(logCtx, "./testdata/non-manifest-file", "./testdata/non-manifest-file", false, "", "", resource.MustParse("0"))
assert.Empty(t, manifests)
assert.NoError(t, err)
})
t.Run("circular link should throw an error", func(t *testing.T) {
const testDir = "./testdata/circular-link"
require.DirExists(t, testDir)
require.NoError(t, createSymlink(t, testDir, "a.json", "b.json"))
defer os.Remove(path.Join(testDir, "a.json"))
require.NoError(t, createSymlink(t, testDir, "b.json", "a.json"))
defer os.Remove(path.Join(testDir, "b.json"))
manifests, err := getPotentiallyValidManifests(logCtx, "./testdata/circular-link", "./testdata/circular-link", false, "", "", resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("out-of-bounds symlink should throw an error", func(t *testing.T) {
require.DirExists(t, "./testdata/out-of-bounds-link")
manifests, err := getPotentiallyValidManifests(logCtx, "./testdata/out-of-bounds-link", "./testdata/out-of-bounds-link", false, "", "", resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("symlink to a regular file works", func(t *testing.T) {
repoRoot, err := filepath.Abs("./testdata/in-bounds-link")
require.NoError(t, err)
appPath, err := filepath.Abs("./testdata/in-bounds-link/app")
require.NoError(t, err)
manifests, err := getPotentiallyValidManifests(logCtx, appPath, repoRoot, false, "", "", resource.MustParse("0"))
assert.Len(t, manifests, 1)
assert.NoError(t, err)
})
t.Run("symlink to nowhere should be ignored", func(t *testing.T) {
manifests, err := getPotentiallyValidManifests(logCtx, "./testdata/link-to-nowhere", "./testdata/link-to-nowhere", false, "", "", resource.MustParse("0"))
assert.Empty(t, manifests)
assert.NoError(t, err)
})
t.Run("link to over-sized manifest fails", func(t *testing.T) {
repoRoot, err := filepath.Abs("./testdata/in-bounds-link")
require.NoError(t, err)
appPath, err := filepath.Abs("./testdata/in-bounds-link/app")
require.NoError(t, err)
// The file is 35 bytes.
manifests, err := getPotentiallyValidManifests(logCtx, appPath, repoRoot, false, "", "", resource.MustParse("34"))
assert.Empty(t, manifests)
assert.ErrorIs(t, err, ErrExceededMaxCombinedManifestFileSize)
})
t.Run("group of files should be limited at precisely the sum of their size", func(t *testing.T) {
// There is a total of 10 files, ech file being 10 bytes.
manifests, err := getPotentiallyValidManifests(logCtx, "./testdata/several-files", "./testdata/several-files", false, "", "", resource.MustParse("365"))
assert.Len(t, manifests, 10)
assert.NoError(t, err)
manifests, err = getPotentiallyValidManifests(logCtx, "./testdata/several-files", "./testdata/several-files", false, "", "", resource.MustParse("100"))
assert.Empty(t, manifests)
assert.ErrorIs(t, err, ErrExceededMaxCombinedManifestFileSize)
})
}
func Test_findManifests(t *testing.T) {
logCtx := log.WithField("test", "test")
noRecurse := argoappv1.ApplicationSourceDirectory{Recurse: false}
t.Run("unreadable file throws error", func(t *testing.T) {
appDir := t.TempDir()
unreadablePath := filepath.Join(appDir, "unreadable.json")
err := os.WriteFile(unreadablePath, []byte{}, 0666)
require.NoError(t, err)
err = os.Chmod(appDir, 0000)
require.NoError(t, err)
manifests, err := findManifests(logCtx, appDir, appDir, nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
// allow cleanup
err = os.Chmod(appDir, 0777)
if err != nil {
panic(err)
}
})
t.Run("no recursion when recursion is disabled", func(t *testing.T) {
manifests, err := findManifests(logCtx, "./testdata/recurse", "./testdata/recurse", nil, noRecurse, nil, resource.MustParse("0"))
assert.Len(t, manifests, 2)
assert.NoError(t, err)
})
t.Run("recursion when recursion is enabled", func(t *testing.T) {
recurse := argoappv1.ApplicationSourceDirectory{Recurse: true}
manifests, err := findManifests(logCtx, "./testdata/recurse", "./testdata/recurse", nil, recurse, nil, resource.MustParse("0"))
assert.Len(t, manifests, 4)
assert.NoError(t, err)
})
t.Run("non-JSON/YAML is skipped", func(t *testing.T) {
manifests, err := findManifests(logCtx, "./testdata/non-manifest-file", "./testdata/non-manifest-file", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.NoError(t, err)
})
t.Run("circular link should throw an error", func(t *testing.T) {
const testDir = "./testdata/circular-link"
require.DirExists(t, testDir)
require.NoError(t, createSymlink(t, testDir, "a.json", "b.json"))
defer os.Remove(path.Join(testDir, "a.json"))
require.NoError(t, createSymlink(t, testDir, "b.json", "a.json"))
defer os.Remove(path.Join(testDir, "b.json"))
manifests, err := findManifests(logCtx, "./testdata/circular-link", "./testdata/circular-link", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("out-of-bounds symlink should throw an error", func(t *testing.T) {
require.DirExists(t, "./testdata/out-of-bounds-link")
manifests, err := findManifests(logCtx, "./testdata/out-of-bounds-link", "./testdata/out-of-bounds-link", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("symlink to a regular file works", func(t *testing.T) {
repoRoot, err := filepath.Abs("./testdata/in-bounds-link")
require.NoError(t, err)
appPath, err := filepath.Abs("./testdata/in-bounds-link/app")
require.NoError(t, err)
manifests, err := findManifests(logCtx, appPath, repoRoot, nil, noRecurse, nil, resource.MustParse("0"))
assert.Len(t, manifests, 1)
assert.NoError(t, err)
})
t.Run("symlink to nowhere should be ignored", func(t *testing.T) {
manifests, err := findManifests(logCtx, "./testdata/link-to-nowhere", "./testdata/link-to-nowhere", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.NoError(t, err)
})
t.Run("link to over-sized manifest fails", func(t *testing.T) {
repoRoot, err := filepath.Abs("./testdata/in-bounds-link")
require.NoError(t, err)
appPath, err := filepath.Abs("./testdata/in-bounds-link/app")
require.NoError(t, err)
// The file is 35 bytes.
manifests, err := findManifests(logCtx, appPath, repoRoot, nil, noRecurse, nil, resource.MustParse("34"))
assert.Empty(t, manifests)
assert.ErrorIs(t, err, ErrExceededMaxCombinedManifestFileSize)
})
t.Run("group of files should be limited at precisely the sum of their size", func(t *testing.T) {
// There is a total of 10 files, each file being 10 bytes.
manifests, err := findManifests(logCtx, "./testdata/several-files", "./testdata/several-files", nil, noRecurse, nil, resource.MustParse("365"))
assert.Len(t, manifests, 10)
assert.NoError(t, err)
manifests, err = findManifests(logCtx, "./testdata/several-files", "./testdata/several-files", nil, noRecurse, nil, resource.MustParse("364"))
assert.Empty(t, manifests)
assert.ErrorIs(t, err, ErrExceededMaxCombinedManifestFileSize)
})
t.Run("jsonnet isn't counted against size limit", func(t *testing.T) {
// Each file is 36 bytes. Only the 36-byte json file should be counted against the limit.
manifests, err := findManifests(logCtx, "./testdata/jsonnet-and-json", "./testdata/jsonnet-and-json", nil, noRecurse, nil, resource.MustParse("36"))
assert.Len(t, manifests, 2)
assert.NoError(t, err)
manifests, err = findManifests(logCtx, "./testdata/jsonnet-and-json", "./testdata/jsonnet-and-json", nil, noRecurse, nil, resource.MustParse("35"))
assert.Empty(t, manifests)
assert.ErrorIs(t, err, ErrExceededMaxCombinedManifestFileSize)
})
t.Run("partially valid YAML file throws an error", func(t *testing.T) {
require.DirExists(t, "./testdata/partially-valid-yaml")
manifests, err := findManifests(logCtx, "./testdata/partially-valid-yaml", "./testdata/partially-valid-yaml", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("invalid manifest throws an error", func(t *testing.T) {
require.DirExists(t, "./testdata/invalid-manifests")
manifests, err := findManifests(logCtx, "./testdata/invalid-manifests", "./testdata/invalid-manifests", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("irrelevant YAML gets skipped, relevant YAML gets parsed", func(t *testing.T) {
manifests, err := findManifests(logCtx, "./testdata/irrelevant-yaml", "./testdata/irrelevant-yaml", nil, noRecurse, nil, resource.MustParse("0"))
assert.Len(t, manifests, 1)
assert.NoError(t, err)
})
t.Run("multiple JSON objects in one file throws an error", func(t *testing.T) {
require.DirExists(t, "./testdata/json-list")
manifests, err := findManifests(logCtx, "./testdata/json-list", "./testdata/json-list", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("invalid JSON throws an error", func(t *testing.T) {
require.DirExists(t, "./testdata/invalid-json")
manifests, err := findManifests(logCtx, "./testdata/invalid-json", "./testdata/invalid-json", nil, noRecurse, nil, resource.MustParse("0"))
assert.Empty(t, manifests)
assert.Error(t, err)
})
t.Run("valid JSON returns manifest and no error", func(t *testing.T) {
manifests, err := findManifests(logCtx, "./testdata/valid-json", "./testdata/valid-json", nil, noRecurse, nil, resource.MustParse("0"))
assert.Len(t, manifests, 1)
assert.NoError(t, err)
})
t.Run("YAML with an empty document doesn't throw an error", func(t *testing.T) {
manifests, err := findManifests(logCtx, "./testdata/yaml-with-empty-document", "./testdata/yaml-with-empty-document", nil, noRecurse, nil, resource.MustParse("0"))
assert.Len(t, manifests, 1)
assert.NoError(t, err)
})
}
func TestTestRepoOCI(t *testing.T) {
service := newService(".")
_, err := service.TestRepository(context.Background(), &apiclient.TestRepositoryRequest{
@@ -1936,3 +2417,52 @@ func runGit(t *testing.T, workDir string, args ...string) string {
require.NoError(t, err, stringOut)
return stringOut
}
func Test_findHelmValueFilesInPath(t *testing.T) {
t.Run("does not exist", func(t *testing.T) {
files, err := findHelmValueFilesInPath("/obviously/does/not/exist")
assert.Error(t, err)
assert.Empty(t, files)
})
t.Run("values files", func(t *testing.T) {
files, err := findHelmValueFilesInPath("./testdata/values-files")
assert.NoError(t, err)
assert.Len(t, files, 4)
})
}
func Test_populateHelmAppDetails(t *testing.T) {
res := apiclient.RepoAppDetailsResponse{}
q := apiclient.RepoServerAppDetailsQuery{
Repo: &argoappv1.Repository{},
Source: &argoappv1.ApplicationSource{
Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{"exclude.yaml", "has-the-word-values.yaml"}},
},
}
appPath, err := filepath.Abs("./testdata/values-files/")
require.NoError(t, err)
err = populateHelmAppDetails(&res, appPath, appPath, &q)
require.NoError(t, err)
assert.Len(t, res.Helm.Parameters, 3)
assert.Len(t, res.Helm.ValueFiles, 4)
}
func Test_populateHelmAppDetails_values_symlinks(t *testing.T) {
t.Run("inbound", func(t *testing.T) {
res := apiclient.RepoAppDetailsResponse{}
q := apiclient.RepoServerAppDetailsQuery{Repo: &argoappv1.Repository{}, Source: &argoappv1.ApplicationSource{}}
err := populateHelmAppDetails(&res, "./testdata/in-bounds-values-file-link/", "./testdata/in-bounds-values-file-link/", &q)
require.NoError(t, err)
assert.NotEmpty(t, res.Helm.Values)
assert.NotEmpty(t, res.Helm.Parameters)
})
t.Run("out of bounds", func(t *testing.T) {
res := apiclient.RepoAppDetailsResponse{}
q := apiclient.RepoServerAppDetailsQuery{Repo: &argoappv1.Repository{}, Source: &argoappv1.ApplicationSource{}}
err := populateHelmAppDetails(&res, "./testdata/out-of-bounds-values-file-link/", "./testdata/out-of-bounds-values-file-link/", &q)
require.NoError(t, err)
assert.Empty(t, res.Helm.Values)
assert.Empty(t, res.Helm.Parameters)
})
}

View File

View File

@@ -0,0 +1 @@
../cm.yaml

View File

@@ -0,0 +1,2 @@
apiVersion: v1
kind: ServiceAccount

View File

@@ -0,0 +1,2 @@
name: my-chart
version: 1.1.0

View File

@@ -0,0 +1 @@
some: yaml

View File

@@ -0,0 +1 @@
values-2.yaml

View File

@@ -0,0 +1 @@
[

View File

@@ -0,0 +1 @@
some: [irrelevant, yaml]

View File

@@ -0,0 +1,2 @@
apiVersion: v1
kind: ConfigMap

View File

@@ -0,0 +1,2 @@
{"apiVersion": "v1", "kind": "ConfigMap"}
{"apiVersion": "v1", "kind": "ConfigMap"}

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "Pod"}

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "Pod"}

View File

@@ -0,0 +1 @@
nowhere

View File

@@ -0,0 +1 @@
../out-of-bounds.json

View File

@@ -0,0 +1,2 @@
name: my-chart
version: 1.1.0

View File

@@ -0,0 +1 @@
../out-of-bounds.yaml

View File

View File

@@ -0,0 +1 @@
some: yaml

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: ConfigMap
---
invalid:

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "ConfigMap"}

View File

@@ -0,0 +1,2 @@
apiVersion: v1
kind: ConfigMap

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "ConfigMap"}

View File

@@ -0,0 +1,2 @@
apiVersion: v1
kind: ConfigMap

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "ConfigMap"}

View File

@@ -0,0 +1,2 @@
apiVersion: v1
kind: ConfigMap

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "ConfigMap"}

View File

@@ -0,0 +1,2 @@
apiVersion: v1
kind: ConfigMap

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "ConfigMap"}

View File

@@ -0,0 +1,2 @@
apiVersion: v1
kind: ConfigMap

View File

@@ -0,0 +1 @@
This file shouldn't be counted in the manifest file size limit, because it isn't JSON or YAML.

View File

@@ -0,0 +1 @@
{"apiVersion": "v1", "kind": "ConfigMap"}

View File

@@ -0,0 +1,2 @@
name: my-chart
version: 1.1.0

View File

@@ -0,0 +1 @@
exclude: yaml

View File

@@ -0,0 +1,4 @@
has:
the:
word:
values: yaml

View File

@@ -0,0 +1 @@
values: yaml

View File

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: ConfigMap
---
---

View File

@@ -952,6 +952,7 @@ func (a *ArgoCDServer) Authenticate(ctx context.Context) (context.Context, error
if !argoCDSettings.AnonymousUserEnabled {
return ctx, claimsErr
} else {
// nolint:staticcheck
ctx = context.WithValue(ctx, "claims", "")
}
}

View File

@@ -506,7 +506,7 @@ func getTestServer(t *testing.T, anonymousEnabled bool, withFakeSSO bool) (argoc
cm.Data["users.anonymous.enabled"] = "true"
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return // Start with a placeholder. We need the server URL before setting up the real handler.
// Start with a placeholder. We need the server URL before setting up the real handler.
}))
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
dexMockHandler(t, ts.URL)(w, r)
@@ -598,8 +598,10 @@ func TestAuthenticate_3rd_party_JWTs(t *testing.T) {
t.Run(testDataCopy.test, func(t *testing.T) {
t.Parallel()
// Must be declared here to avoid race.
ctx := context.Background() //nolint:ineffassign,staticcheck
argocd, dexURL := getTestServer(t, testDataCopy.anonymousEnabled, true)
ctx := context.Background()
testDataCopy.claims.Issuer = fmt.Sprintf("%s/api/dex", dexURL)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, testDataCopy.claims)
tokenString, err := token.SignedString([]byte("key"))
@@ -614,7 +616,8 @@ func TestAuthenticate_3rd_party_JWTs(t *testing.T) {
assert.Equal(t, testDataCopy.expectedClaims, claims)
}
if testDataCopy.expectedErrorContains != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorContains, "Authenticate should have thrown an error and blocked the request")
assert.Error(t, err, "Authenticate should have thrown an error and blocked the request")
assert.Contains(t, err.Error(), testDataCopy.expectedErrorContains)
} else {
assert.NoError(t, err)
}
@@ -657,7 +660,8 @@ func TestAuthenticate_no_request_metadata(t *testing.T) {
claims := ctx.Value("claims")
assert.Equal(t, testDataCopy.expectedClaims, claims)
if testDataCopy.expectedErrorContains != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorContains, "Authenticate should have thrown an error and blocked the request")
assert.Error(t, err, "Authenticate should have thrown an error and blocked the request")
assert.Contains(t, err.Error(), testDataCopy.expectedErrorContains)
} else {
assert.NoError(t, err)
}
@@ -693,8 +697,10 @@ func TestAuthenticate_no_SSO(t *testing.T) {
t.Run(testDataCopy.test, func(t *testing.T) {
t.Parallel()
// Must be declared here to avoid race.
ctx := context.Background() //nolint:ineffassign,staticcheck
argocd, dexURL := getTestServer(t, testDataCopy.anonymousEnabled, false)
ctx := context.Background()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{Issuer: fmt.Sprintf("%s/api/dex", dexURL)})
tokenString, err := token.SignedString([]byte("key"))
require.NoError(t, err)
@@ -704,7 +710,8 @@ func TestAuthenticate_no_SSO(t *testing.T) {
claims := ctx.Value("claims")
assert.Equal(t, testDataCopy.expectedClaims, claims)
if testDataCopy.expectedErrorMessage != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorMessage, "Authenticate should have thrown an error and blocked the request")
assert.Error(t, err, "Authenticate should have thrown an error and blocked the request")
assert.Contains(t, err.Error(), testDataCopy.expectedErrorMessage)
} else {
assert.NoError(t, err)
}
@@ -799,15 +806,18 @@ func TestAuthenticate_bad_request_metadata(t *testing.T) {
t.Run(testDataCopy.test, func(t *testing.T) {
t.Parallel()
// Must be declared here to avoid race.
ctx := context.Background() //nolint:ineffassign,staticcheck
argocd, _ := getTestServer(t, testDataCopy.anonymousEnabled, true)
ctx := context.Background()
ctx = metadata.NewIncomingContext(context.Background(), testDataCopy.metadata)
ctx, err := argocd.Authenticate(ctx)
claims := ctx.Value("claims")
assert.Equal(t, testDataCopy.expectedClaims, claims)
if testDataCopy.expectedErrorMessage != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorMessage, "Authenticate should have thrown an error and blocked the request")
assert.Error(t, err, "Authenticate should have thrown an error and blocked the request")
assert.Contains(t, err.Error(), testDataCopy.expectedErrorMessage)
} else {
assert.NoError(t, err)
}

View File

@@ -1,4 +1,4 @@
FROM redis:6.2.6 as redis
FROM redis:6.2.7 as redis
FROM node:12.18.4 as node
@@ -6,7 +6,7 @@ FROM golang:1.17 as golang
FROM registry:2.7.1 as registry
FROM ubuntu:21.10
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --fix-missing -y \
@@ -66,6 +66,11 @@ COPY ./test/fixture/testrepos/ssh_host_*_key* /etc/ssh/
# Copy redis binaries to the image
COPY --from=redis /usr/local/bin/* /usr/local/bin/
# Copy redis dependencies/shared libraries
# Ubuntu 22.04+ has moved to OpenSSL3 and no longer provides these libraries
COPY --from=redis /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /usr/lib/x86_64-linux-gnu/
COPY --from=redis /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 /usr/lib/x86_64-linux-gnu/
# Copy registry binaries to the image
COPY --from=registry /bin/registry /usr/local/bin/
COPY --from=registry /etc/docker/registry/config.yml /etc/docker/registry/config.yml

View File

@@ -182,3 +182,4 @@ func TestClusterURLInRestAPI(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, map[string]string{"test": "val"}, cluster.Labels)
}

View File

@@ -6,7 +6,11 @@ import (
"fmt"
"log"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/clusterauth"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
@@ -63,6 +67,30 @@ func (a *Actions) Create(args ...string) *Actions {
return a
}
func (a *Actions) CreateWithRBAC(args ...string) *Actions {
pathOpts := clientcmd.NewDefaultPathOptions()
config, err := pathOpts.GetStartingConfig()
if err != nil {
a.lastError = err
return a
}
clientConfig := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{})
conf, err := clientConfig.ClientConfig()
if err != nil {
a.lastError = err
return a
}
client := kubernetes.NewForConfigOrDie(conf)
_, err = clusterauth.InstallClusterManagerRBAC(client, "kube-system", []string{}, common.BearerTokenTimeout)
if err != nil {
a.lastError = err
return a
}
return a.Create()
}
func (a *Actions) List() *Actions {
a.context.t.Helper()
a.runCli("cluster", "list")
@@ -75,6 +103,20 @@ func (a *Actions) Get() *Actions {
return a
}
func (a *Actions) DeleteByName() *Actions {
a.context.t.Helper()
a.runCli("cluster", "rm", a.context.name)
return a
}
func (a *Actions) DeleteByServer() *Actions {
a.context.t.Helper()
a.runCli("cluster", "rm", a.context.server)
return a
}
func (a *Actions) Then() *Consequences {
a.context.t.Helper()
return &Consequences{a.context, a}

View File

@@ -554,7 +554,9 @@ func EnsureCleanState(t *testing.T) {
FailOnErr(Run("", "mkdir", "-p", TmpDir))
// random id - unique across test runs
postFix := "-" + strings.ToLower(rand.RandString(5))
randString, err := rand.String(5)
CheckError(err)
postFix := "-" + strings.ToLower(randString)
id = t.Name() + postFix
name = DnsFriendly(t.Name(), "")
deploymentNamespace = DnsFriendly(fmt.Sprintf("argocd-e2e-%s", t.Name()), postFix)

View File

@@ -7,6 +7,7 @@ import (
"github.com/argoproj/gitops-engine/pkg/health"
. "github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/stretchr/testify/require"
. "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
@@ -110,7 +111,9 @@ func TestSelectiveSyncWithNamespace(t *testing.T) {
}
func getNewNamespace(t *testing.T) string {
postFix := "-" + strings.ToLower(rand.RandString(5))
randStr, err := rand.String(5)
require.NoError(t, err)
postFix := "-" + strings.ToLower(randStr)
name := fixture.DnsFriendly(t.Name(), "")
return fixture.DnsFriendly(fmt.Sprintf("argocd-e2e-%s", name), postFix)
}

View File

@@ -3,7 +3,7 @@ FROM golang:1.17 AS go
RUN go install github.com/mattn/goreman@latest && \
go install github.com/kisielk/godepgraph@latest
FROM ubuntu:21.10
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \

View File

@@ -10,45 +10,22 @@
"test": "jest"
},
"dependencies": {
"@types/classnames": "^2.2.3",
"@types/cookie": "^0.3.1",
"@types/dagre": "^0.7.40",
"@types/deepmerge": "^2.2.0",
"@types/git-url-parse": "^9.0.0",
"@types/js-yaml": "^3.11.2",
"@types/minimatch": "^3.0.3",
"@types/prop-types": "^15.5.2",
"@types/react": "^16.8.5",
"@types/react-autocomplete": "^1.8.4",
"@types/react-dom": "^16.9.14",
"@types/react-form": "^2.16.0",
"@types/react-helmet": "^5.0.17",
"@types/react-paginate": "^6.2.0",
"@types/react-router": "^4.0.27",
"@types/react-router-dom": "^4.2.3",
"@types/superagent": "^3.5.7",
"ansi-to-react": "^6.1.6",
"argo-ui": "git+https://github.com/argoproj/argo-ui.git",
"classnames": "^2.2.5",
"color": "^3.1.0",
"cookie": "^0.3.1",
"copy-webpack-plugin": "^6.1.1",
"dagre": "^0.8.2",
"deepmerge": "^3.2.0",
"foundation-sites": "^6.4.3",
"git-url-parse": "^11.1.2",
"html-webpack-plugin": "^3.2.0",
"jest-junit": "^6.4.0",
"js-yaml": "^3.13.1",
"json-merge-patch": "^0.2.3",
"lodash-es": "^4.17.21",
"minimatch": "^3.0.4",
"moment": "^2.24.0",
"moment": "^2.29.4",
"monaco-editor": "^0.27.0",
"monaco-editor-webpack-plugin": "^6.0.0",
"node-sass": "^6.0.1",
"prop-types": "^15.6.0",
"raw-loader": "^0.5.1",
"react": "^16.9.3",
"react-autocomplete": "^1.8.1",
"react-diff-view": "^2.4.7",
@@ -64,21 +41,10 @@
"react-svg-piechart": "^2.1.1",
"redoc": "^2.0.0-rc.64",
"rxjs": "^6.6.6",
"sass-loader": "^6.0.6",
"source-map-loader": "^0.2.3",
"style-loader": "^0.20.1",
"superagent": "^3.8.2",
"superagent-promise": "^1.1.0",
"timezones-list": "3.0.1",
"ts-loader": "^6.0.4",
"ts-node": "^4.1.0",
"tslint": "^6.1.3",
"tslint-react": "^3.4.0",
"typescript": "^4.0.3",
"unidiff": "^1.0.2",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
"unidiff": "^1.0.2"
},
"resolutions": {
"@types/react": "^16.9.3",
@@ -90,23 +56,57 @@
"@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.7.0",
"@babel/preset-typescript": "^7.7.2",
"@types/classnames": "^2.2.3",
"@types/cookie": "^0.3.1",
"@types/dagre": "^0.7.40",
"@types/deepmerge": "^2.2.0",
"@types/git-url-parse": "^9.0.0",
"@types/jest": "^24.0.13",
"@types/js-yaml": "^3.11.2",
"@types/lodash-es": "^4.17.5",
"@types/minimatch": "^3.0.3",
"@types/prop-types": "^15.5.2",
"@types/react": "^16.8.5",
"@types/react-autocomplete": "^1.8.4",
"@types/react-dom": "^16.9.14",
"@types/react-form": "^2.16.0",
"@types/react-helmet": "^5.0.17",
"@types/react-paginate": "^6.2.0",
"@types/react-router": "^4.0.27",
"@types/react-router-dom": "^4.2.3",
"@types/react-test-renderer": "^16.8.3",
"@types/superagent": "^3.5.7",
"add": "^2.0.6",
"babel-jest": "^24.9.0",
"babel-loader": "^8.0.6",
"codecov": "^3.7.2",
"copy-webpack-plugin": "^6.1.1",
"esbuild-loader": "^2.15.1",
"html-webpack-plugin": "^3.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^24.9.0",
"jest-junit": "^6.4.0",
"jest-transform-css": "^2.0.0",
"monaco-editor-webpack-plugin": "^6.0.0",
"node-sass": "^6.0.1",
"postcss": "^8.2.13",
"prettier": "1.19",
"raw-loader": "^0.5.1",
"react-test-renderer": "16.8.3",
"sass-loader": "^6.0.6",
"source-map-loader": "^0.2.3",
"style-loader": "^0.20.1",
"ts-jest": "^24.1.0",
"ts-loader": "^6.0.4",
"ts-node": "^4.1.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.0.1",
"tslint-react": "^3.4.0",
"typescript": "^4.0.3",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"yarn": "^1.22.10"
}
}

View File

@@ -0,0 +1,20 @@
import {ExternalLink, InvalidExternalLinkError} from './application-urls';
test('rejects malicious URLs', () => {
expect(() => {
const _ = new ExternalLink('javascript:alert("hi")');
}).toThrowError(InvalidExternalLinkError);
expect(() => {
const _ = new ExternalLink('data:text/html;<h1>hi</h1>');
}).toThrowError(InvalidExternalLinkError);
});
test('allows absolute URLs', () => {
expect(new ExternalLink('https://localhost:8080/applications').ref).toEqual('https://localhost:8080/applications');
});
test('allows relative URLs', () => {
// @ts-ignore
window.location = new URL('https://localhost:8080/applications');
expect(new ExternalLink('/applications').ref).toEqual('/applications');
});

View File

@@ -1,7 +1,15 @@
import {DropDownMenu} from 'argo-ui';
import * as React from 'react';
class ExternalLink {
export class InvalidExternalLinkError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, InvalidExternalLinkError.prototype);
this.name = 'InvalidExternalLinkError';
}
}
export class ExternalLink {
public title: string;
public ref: string;
@@ -14,13 +22,36 @@ class ExternalLink {
this.title = url;
this.ref = url;
}
if (!ExternalLink.isValidURL(this.ref)) {
throw new InvalidExternalLinkError('Invalid URL');
}
}
private static isValidURL(url: string): boolean {
try {
const parsedUrl = new URL(url);
return parsedUrl.protocol !== 'javascript:' && parsedUrl.protocol !== 'data:';
} catch (TypeError) {
try {
// Try parsing as a relative URL.
const parsedUrl = new URL(url, window.location.origin);
return parsedUrl.protocol !== 'javascript:' && parsedUrl.protocol !== 'data:';
} catch (TypeError) {
return false;
}
}
}
}
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
const externalLinks: ExternalLink[] = [];
for (const url of urls || []) {
externalLinks.push(new ExternalLink(url));
try {
const externalLink = new ExternalLink(url);
externalLinks.push(externalLink);
} catch (InvalidExternalLinkError) {
continue;
}
}
// sorted alphabetically & links with titles first

View File

@@ -6159,6 +6159,11 @@ moment-timezone@^0.5.33:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
moment@^2.29.4:
version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
monaco-editor-webpack-plugin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-6.0.0.tgz#628956ce1851afa2a5f6c88d0ecbb24e9a444898"
@@ -6788,9 +6793,9 @@ parse-path@^4.0.0:
query-string "^6.13.8"
parse-url@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.0.tgz#f5dd262a7de9ec00914939220410b66cff09107d"
integrity sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw==
version "6.0.5"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.5.tgz#4acab8982cef1846a0f8675fa686cef24b2f6f9b"
integrity sha512-e35AeLTSIlkw/5GFq70IN7po8fmDUjpDPY1rIK+VubRfsUvBonjQ+PBZG+vWMACnQSmNlvl524IucoDmcioMxA==
dependencies:
is-ssh "^1.3.0"
normalize-url "^6.1.0"

View File

@@ -2,16 +2,19 @@ package clusterauth
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/argoproj/argo-cd/v2/common"
jwt "github.com/golang-jwt/jwt/v4"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
)
@@ -173,7 +176,7 @@ func upsertRoleBinding(clientset kubernetes.Interface, name string, roleName str
}
// InstallClusterManagerRBAC installs RBAC resources for a cluster manager to operate a cluster. Returns a token
func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string, namespaces []string) (string, error) {
func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string, namespaces []string, bearerTokenTimeout time.Duration) (string, error) {
err := CreateServiceAccount(clientset, ArgoCDManagerServiceAccount, ns)
if err != nil {
@@ -212,42 +215,123 @@ func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string, namesp
}
}
return GetServiceAccountBearerToken(clientset, ns, ArgoCDManagerServiceAccount)
return GetServiceAccountBearerToken(clientset, ns, ArgoCDManagerServiceAccount, bearerTokenTimeout)
}
// GetServiceAccountBearerToken will attempt to get the provided service account until it
// exists, iterate the secrets associated with it looking for one of type
// kubernetes.io/service-account-token, and return it's token if found.
func GetServiceAccountBearerToken(clientset kubernetes.Interface, ns string, sa string) (string, error) {
var serviceAccount *corev1.ServiceAccount
// GetServiceAccountBearerToken determines if a ServiceAccount has a
// bearer token secret to use or if a secret should be created. It then
// waits for the secret to have a bearer token if a secret needs to
// be created and returns the token in encoded base64.
func GetServiceAccountBearerToken(clientset kubernetes.Interface, ns string, sa string, timeout time.Duration) (string, error) {
secretName, err := getOrCreateServiceAccountTokenSecret(clientset, sa, ns)
if err != nil {
return "", err
}
var secret *corev1.Secret
var err error
err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) {
serviceAccount, err = clientset.CoreV1().ServiceAccounts(ns).Get(context.Background(), sa, metav1.GetOptions{})
err = wait.PollImmediate(500*time.Millisecond, timeout, func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), common.ClusterAuthRequestTimeout)
defer cancel()
secret, err = clientset.CoreV1().Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
if err != nil {
return false, err
return false, fmt.Errorf("failed to get secret %q for serviceaccount %q: %w", secretName, sa, err)
}
// Scan all secrets looking for one of the correct type:
for _, oRef := range serviceAccount.Secrets {
var getErr error
secret, err = clientset.CoreV1().Secrets(ns).Get(context.Background(), oRef.Name, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("Failed to retrieve secret %q: %v", oRef.Name, getErr)
}
if secret.Type == corev1.SecretTypeServiceAccountToken {
return true, nil
}
_, ok := secret.Data["token"]
if !ok {
return false, nil
}
return false, nil
return true, nil
})
if err != nil {
return "", fmt.Errorf("Failed to wait for service account secret: %v", err)
return "", fmt.Errorf("failed to get token for serviceaccount %q: %w", sa, err)
}
token, ok := secret.Data["token"]
if !ok {
return "", fmt.Errorf("Secret %q for service account %q did not have a token", secret.Name, serviceAccount)
return string(secret.Data["token"]), nil
}
// getOrCreateServiceAccountTokenSecret will check if a ServiceAccount
// already has a kubernetes.io/service-account-token secret associated
// with it or creates one if the ServiceAccount doesn't have one. This
// was added to help add k8s v1.24+ clusters.
func getOrCreateServiceAccountTokenSecret(clientset kubernetes.Interface, sa, ns string) (string, error) {
// Wait for sa to have secret, but don't wait too
// long for 1.24+ clusters
var serviceAccount *corev1.ServiceAccount
err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), common.ClusterAuthRequestTimeout)
defer cancel()
var getErr error
serviceAccount, getErr = clientset.CoreV1().ServiceAccounts(ns).Get(ctx, sa, metav1.GetOptions{})
if getErr != nil {
return false, fmt.Errorf("failed to get serviceaccount %q: %w", sa, getErr)
}
if len(serviceAccount.Secrets) == 0 {
return false, nil
}
return true, nil
})
if err != nil && err != wait.ErrWaitTimeout {
return "", fmt.Errorf("failed to get serviceaccount token secret: %w", err)
}
return string(token), nil
if serviceAccount == nil {
log.Errorf("Unexpected nil serviceaccount '%s/%s' with no error returned", ns, sa)
return "", fmt.Errorf("failed to create serviceaccount token secret: nil serviceaccount returned for '%s/%s' with no error", ns, sa)
}
outerCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
for _, s := range serviceAccount.Secrets {
innerCtx, cancel := context.WithTimeout(outerCtx, common.ClusterAuthRequestTimeout)
defer cancel()
existingSecret, err := clientset.CoreV1().Secrets(ns).Get(innerCtx, s.Name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to retrieve secret %q: %w", s.Name, err)
}
if existingSecret.Type == corev1.SecretTypeServiceAccountToken {
return existingSecret.Name, nil
}
}
return createServiceAccountToken(clientset, serviceAccount)
}
func createServiceAccountToken(clientset kubernetes.Interface, serviceAccount *corev1.ServiceAccount) (string, error) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: serviceAccount.Name + "-token-",
Namespace: serviceAccount.Namespace,
Annotations: map[string]string{
corev1.ServiceAccountNameKey: serviceAccount.Name,
},
},
Type: corev1.SecretTypeServiceAccountToken,
}
ctx, cancel := context.WithTimeout(context.Background(), common.ClusterAuthRequestTimeout)
defer cancel()
secret, err := clientset.CoreV1().Secrets(serviceAccount.Namespace).Create(ctx, secret, metav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("failed to create secret for serviceaccount %q: %w", serviceAccount.Name, err)
}
log.Infof("Created bearer token secret for ServiceAccount %q", serviceAccount.Name)
serviceAccount.Secrets = []corev1.ObjectReference{{
Name: secret.Name,
Namespace: secret.Namespace,
}}
patch, err := json.Marshal(serviceAccount)
if err != nil {
return "", fmt.Errorf("failed marshaling patch for serviceaccount %q: %w", serviceAccount.Name, err)
}
_, err = clientset.CoreV1().ServiceAccounts(serviceAccount.Namespace).Patch(ctx, serviceAccount.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{})
if err != nil {
return "", fmt.Errorf("failed to patch serviceaccount %q with bearer token secret: %w", serviceAccount.Name, err)
}
return secret.Name, nil
}
// UninstallClusterManagerRBAC removes RBAC resources for a cluster manager to operate a cluster

View File

@@ -4,21 +4,24 @@ import (
"context"
"io/ioutil"
"testing"
"time"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
"github.com/argoproj/argo-cd/v2/util/errors"
)
const (
testToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhcmdvY2QtbWFuYWdlci10b2tlbi10ajc5ciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhcmdvY2QtbWFuYWdlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjkxZGQzN2NmLThkOTItMTFlOS1hMDkxLWQ2NWYyYWU3ZmE4ZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphcmdvY2QtbWFuYWdlciJ9.ytZjt2pDV8-A7DBMR06zQ3wt9cuVEfq262TQw7sdra-KRpDpMPnziMhc8bkwvgW-LGhTWUh5iu1y-1QhEx6mtbCt7vQArlBRxfvM5ys6ClFkplzq5c2TtZ7EzGSD0Up7tdxuG9dvR6TGXYdfFcG779yCdZo2H48sz5OSJfdEriduMEY1iL5suZd3ebOoVi1fGflmqFEkZX6SvxkoArl5mtNP6TvZ1eTcn64xh4ws152hxio42E-eSnl_CET4tpB5vgP5BVlSKW2xB7w2GJxqdETA5LJRI_OilY77dTOp8cMr_Ck3EOeda3zHfh4Okflg8rZFEeAuJYahQNeAILLkcA"
testToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhcmdvY2QtbWFuYWdlci10b2tlbi10ajc5ciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhcmdvY2QtbWFuYWdlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjkxZGQzN2NmLThkOTItMTFlOS1hMDkxLWQ2NWYyYWU3ZmE4ZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphcmdvY2QtbWFuYWdlciJ9.ytZjt2pDV8-A7DBMR06zQ3wt9cuVEfq262TQw7sdra-KRpDpMPnziMhc8bkwvgW-LGhTWUh5iu1y-1QhEx6mtbCt7vQArlBRxfvM5ys6ClFkplzq5c2TtZ7EzGSD0Up7tdxuG9dvR6TGXYdfFcG779yCdZo2H48sz5OSJfdEriduMEY1iL5suZd3ebOoVi1fGflmqFEkZX6SvxkoArl5mtNP6TvZ1eTcn64xh4ws152hxio42E-eSnl_CET4tpB5vgP5BVlSKW2xB7w2GJxqdETA5LJRI_OilY77dTOp8cMr_Ck3EOeda3zHfh4Okflg8rZFEeAuJYahQNeAILLkcA"
testBearerTokenTimeout = 5 * time.Second
)
var (
@@ -132,7 +135,7 @@ func TestInstallClusterManagerRBAC(t *testing.T) {
Namespace: "test",
},
Secrets: []corev1.ObjectReference{
corev1.ObjectReference{
{
Kind: secret.GetObjectKind().GroupVersionKind().Kind,
APIVersion: secret.APIVersion,
Name: secret.GetName(),
@@ -145,7 +148,7 @@ func TestInstallClusterManagerRBAC(t *testing.T) {
t.Run("Cluster Scope - Success", func(t *testing.T) {
cs := fake.NewSimpleClientset(ns, secret, sa)
token, err := InstallClusterManagerRBAC(cs, "test", nil)
token, err := InstallClusterManagerRBAC(cs, "test", nil, testBearerTokenTimeout)
assert.NoError(t, err)
assert.Equal(t, "foobar", token)
})
@@ -154,14 +157,14 @@ func TestInstallClusterManagerRBAC(t *testing.T) {
nsecret := secret.DeepCopy()
nsecret.Data = make(map[string][]byte)
cs := fake.NewSimpleClientset(ns, nsecret, sa)
token, err := InstallClusterManagerRBAC(cs, "test", nil)
token, err := InstallClusterManagerRBAC(cs, "test", nil, testBearerTokenTimeout)
assert.Error(t, err)
assert.Empty(t, token)
})
t.Run("Namespace Scope - Success", func(t *testing.T) {
cs := fake.NewSimpleClientset(ns, secret, sa)
token, err := InstallClusterManagerRBAC(cs, "test", []string{"nsa"})
token, err := InstallClusterManagerRBAC(cs, "test", []string{"nsa"}, testBearerTokenTimeout)
assert.NoError(t, err)
assert.Equal(t, "foobar", token)
})
@@ -170,7 +173,7 @@ func TestInstallClusterManagerRBAC(t *testing.T) {
nsecret := secret.DeepCopy()
nsecret.Data = make(map[string][]byte)
cs := fake.NewSimpleClientset(ns, nsecret, sa)
token, err := InstallClusterManagerRBAC(cs, "test", []string{"nsa"})
token, err := InstallClusterManagerRBAC(cs, "test", []string{"nsa"}, testBearerTokenTimeout)
assert.Error(t, err)
assert.Empty(t, token)
})
@@ -254,7 +257,108 @@ func TestGetServiceAccountBearerToken(t *testing.T) {
}
kubeclientset := fake.NewSimpleClientset(sa, dockercfgSecret, tokenSecret)
token, err := GetServiceAccountBearerToken(kubeclientset, "kube-system", sa.Name)
token, err := GetServiceAccountBearerToken(kubeclientset, "kube-system", sa.Name, testBearerTokenTimeout)
assert.NoError(t, err)
assert.Equal(t, testToken, token)
}
func Test_getOrCreateServiceAccountTokenSecret_NoSecretForSA(t *testing.T) {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-system",
},
}
saWithoutSecret := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: ArgoCDManagerServiceAccount,
Namespace: ns.Name,
},
}
cs := fake.NewSimpleClientset(ns, saWithoutSecret)
cs.PrependReactor("create", "secrets",
func(a kubetesting.Action) (handled bool, ret runtime.Object, err error) {
s, ok := a.(kubetesting.CreateAction).GetObject().(*corev1.Secret)
if !ok {
return
}
if s.Name == "" && s.GenerateName != "" {
s.SetName(names.SimpleNameGenerator.GenerateName(s.GenerateName))
}
s.Data = make(map[string][]byte)
s.Data["token"] = []byte("fake-token")
return
})
got, err := getOrCreateServiceAccountTokenSecret(cs, ArgoCDManagerServiceAccount, ns.Name)
assert.NoError(t, err)
assert.Contains(t, got, "argocd-manager-token-")
obj, err := cs.Tracker().Get(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"},
ns.Name, ArgoCDManagerServiceAccount)
if err != nil {
t.Errorf("ServiceAccount %s not found but was expected to be found: %s", ArgoCDManagerServiceAccount, err.Error())
}
sa := obj.(*corev1.ServiceAccount)
assert.Equal(t, 1, len(sa.Secrets))
}
func Test_getOrCreateServiceAccountTokenSecret_SAHasSecret(t *testing.T) {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-system",
},
}
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "sa-secret",
Namespace: ns.Name,
},
Type: corev1.SecretTypeServiceAccountToken,
Data: map[string][]byte{
"token": []byte("foobar"),
},
}
saWithSecret := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: ArgoCDManagerServiceAccount,
Namespace: ns.Name,
},
Secrets: []corev1.ObjectReference{
{
Kind: secret.GetObjectKind().GroupVersionKind().Kind,
APIVersion: secret.APIVersion,
Name: secret.GetName(),
Namespace: secret.GetNamespace(),
UID: secret.GetUID(),
ResourceVersion: secret.GetResourceVersion(),
},
},
}
cs := fake.NewSimpleClientset(ns, saWithSecret, secret)
got, err := getOrCreateServiceAccountTokenSecret(cs, ArgoCDManagerServiceAccount, ns.Name)
assert.NoError(t, err)
assert.Equal(t, "sa-secret", got)
obj, err := cs.Tracker().Get(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"},
ns.Name, ArgoCDManagerServiceAccount)
if err != nil {
t.Errorf("ServiceAccount %s not found but was expected to be found: %s", ArgoCDManagerServiceAccount, err.Error())
}
sa := obj.(*corev1.ServiceAccount)
assert.Equal(t, 1, len(sa.Secrets))
// Adding if statement to prevent case where secret not found
// since accessing name by first index.
if len(sa.Secrets) != 0 {
assert.Equal(t, "sa-secret", sa.Secrets[0].Name)
}
}

View File

@@ -11,14 +11,14 @@ import (
// Unfortunately, crypto/ssh does not offer public constants or list for
// this.
var SupportedSSHKeyExchangeAlgorithms = []string{
"diffie-hellman-group1-sha1",
"diffie-hellman-group14-sha1",
"curve25519-sha256",
"curve25519-sha256@libssh.org",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"curve25519-sha256@libssh.org",
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group14-sha256",
"diffie-hellman-group14-sha1",
}
// List of default key exchange algorithms to use. We use those that are

View File

@@ -6,10 +6,11 @@ import (
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/util/config"
executil "github.com/argoproj/argo-cd/v2/util/exec"
@@ -28,7 +29,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 []pathutil.ResolvedFilePath) (map[string]string, error)
GetParameters(valuesFiles []pathutil.ResolvedFilePath, appPath, repoRoot string) (map[string]string, error)
// DependencyBuild runs `helm dependency build` to download a chart's dependencies
DependencyBuild() error
// Init runs `helm init --client-only`
@@ -130,12 +131,19 @@ func Version(shortForm bool) (string, error) {
return strings.TrimSpace(version), nil
}
func (h *helm) GetParameters(valuesFiles []pathutil.ResolvedFilePath) (map[string]string, error) {
out, err := h.cmd.inspectValues(".")
if err != nil {
return nil, err
func (h *helm) GetParameters(valuesFiles []pathutil.ResolvedFilePath, appPath, repoRoot string) (map[string]string, error) {
var values []string
// Don't load values.yaml if it's an out-of-bounds link.
if resolved, _, err := pathutil.ResolveFilePath(appPath, repoRoot, "values.yaml", []string{}); err == nil {
fmt.Println(resolved)
out, err := h.cmd.inspectValues(".")
if err != nil {
return nil, err
}
values = append(values, out)
} else {
log.Warnf("Values file %s is not allowed: %v", filepath.Join(appPath, "values.yaml"), err)
}
values := []string{out}
for i := range valuesFiles {
file := string(valuesFiles[i])
var fileValues []byte
@@ -143,11 +151,10 @@ func (h *helm) GetParameters(valuesFiles []pathutil.ResolvedFilePath) (map[strin
if err == nil && (parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
fileValues, err = config.ReadRemoteFile(file)
} else {
filePath := path.Join(h.cmd.WorkDir, file)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
if _, err := os.Stat(file); os.IsNotExist(err) {
continue
}
fileValues, err = ioutil.ReadFile(filePath)
fileValues, err = ioutil.ReadFile(file)
}
if err != nil {
return nil, fmt.Errorf("failed to read value file %s: %s", file, err)
@@ -158,7 +165,7 @@ func (h *helm) GetParameters(valuesFiles []pathutil.ResolvedFilePath) (map[strin
output := map[string]string{}
for _, file := range values {
values := map[string]interface{}{}
if err = yaml.Unmarshal([]byte(file), &values); err != nil {
if err := yaml.Unmarshal([]byte(file), &values); err != nil {
return nil, fmt.Errorf("failed to parse values: %s", err)
}
flatVals(values, output)

Some files were not shown because too many files have changed in this diff Show More