Compare commits

...

45 Commits

Author SHA1 Message Date
argo-bot
92b02379b9 Bump version to 1.7.14 2021-03-03 18:26:25 +00:00
argo-bot
7423c0bb20 Bump version to 1.7.14 2021-03-03 18:26:08 +00:00
kshamajain99
528fb17951 fix: redact sensitive data in logs (#5662)
Signed-off-by: kshamajain99 <kshamajain99@gmail.com>
2021-03-02 23:55:21 -08:00
Jan Gräfen
cdc1aaa8a7 fix: Empty resource whitelist allowed all resources (#5540) (#5551)
* fix: Empty resource whitelist allowed all resources

This requires setting the default in quite a few
places around the code base as well as adapting
a couple of tests

Signed-off-by: Jan Graefen <223234+jangraefen@users.noreply.github.com>

* Improve default behavior and not require explicitly set whitelist

Signed-off-by: Jan Graefen <223234+jangraefen@users.noreply.github.com>
2021-03-01 10:22:34 -08:00
jannfis
73d73e11f9 chore: Fix release script for 1.7 branch (#5626)
Signed-off-by: jannfis <jann@mistrust.net>
2021-02-26 13:09:14 -08:00
argo-bot
76ed3d6d72 Bump version to 1.7.13 2021-02-26 17:12:12 +00:00
argo-bot
f77a3cbadb Bump version to 1.7.13 2021-02-26 17:11:59 +00:00
jannfis
2e215df16d fix: Properly escape HTML for error message from CLI SSO (#5563)
Signed-off-by: jannfis <jann@mistrust.net>
2021-02-26 10:30:01 +01:00
Alexander Matyushentsev
c2e3fb96fb fix: API server should not print resource body when resource update fails (#5617)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-25 19:26:07 -08:00
kshamajain99
f2562b41a0 fix: fix memory leak in application controller (#5604)
fix: fix memory leak in application controller
2021-02-25 19:26:02 -08:00
argo-bot
602df423e0 Bump version to 1.7.12 2021-02-05 20:14:20 +00:00
argo-bot
5be98f51c8 Bump version to 1.7.12 2021-02-05 20:14:08 +00:00
Alexander Matyushentsev
fb6b32babc fix: fix merge issue: remove unused jsonnetVersion field
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 11:54:46 -08:00
Isaac Gaskin
a95c7ffd98 chore: helm2 verison bump (#4724)
* chore: helm2 verison bump
2021-02-05 11:52:11 -08:00
Alexander Matyushentsev
8663126343 fix: version info should be avaialble if anonymous access is enabled (#5422)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 11:43:44 -08:00
Alexander Matyushentsev
94e6075757 fix: /api/version should not return tools version for unauthenticated requests (#5415)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 11:41:33 -08:00
Alexander Matyushentsev
fd07e1c00a fix: account tokens should be rejected if required capability is disabled (#5414)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 11:38:42 -08:00
Alexander Matyushentsev
2b132cbcee feat: set X-XSS-Protection while serving static content (#5412)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 11:38:33 -08:00
Alexander Matyushentsev
d0d6dae7af fix: tokens keep working after account is deactivated (#5402)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 11:38:21 -08:00
Alexander Matyushentsev
cf6551d29b fix: a request which was using a revoked project token, would still be allowed to perform requests allowed by default policy (#5378)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 11:38:11 -08:00
Liviu Costea
1bf11f72a4 refactor(jwt): use typed access to claims (#5075)
Signed-off-by: Liviu Costea <email.lcostea@gmail.com>
2021-02-05 10:47:52 -08:00
jannfis
4ef0245ce8 fix: [backport 1.7] Allow correct SSO redirect URL for CLI static client (#5106)
Signed-off-by: jannfis <jann@mistrust.net>
2020-12-22 11:53:39 -08:00
jannfis
9cd980bd69 chore: Update Dex to v2.27.0 (#5071)
Signed-off-by: jannfis <jann@mistrust.net>
2020-12-16 17:58:49 +01:00
argo-bot
97401f9bb9 Bump version to 1.7.11 2020-12-10 02:30:08 +00:00
argo-bot
2720bef5ce Bump version to 1.7.11 2020-12-10 02:29:58 +00:00
Alexander Matyushentsev
d8441b4292 fix: sync retry is broken for multi-phase syncs (#5017)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-09 18:11:15 -08:00
argo-bot
bcb05b0c2e Bump version to 1.7.10 2020-11-20 19:41:11 +00:00
argo-bot
7248fee361 Bump version to 1.7.10 2020-11-20 19:41:03 +00:00
Alexander Matyushentsev
cf541c6200 fix: increase max grpc message size (#4869)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-11-20 11:20:28 -08:00
argo-bot
f6dc8c389a Bump version to 1.7.9 2020-11-17 23:07:44 +00:00
argo-bot
26ff594063 Bump version to 1.7.9 2020-11-17 23:07:32 +00:00
jannfis
d4e8213e28 chore: Replace deprecated commands for release action (#4593) 2020-11-17 14:45:49 -08:00
Sven Walter
89e28c64aa fix: improve commit verification tolerance (#4825)
The `git verify-commit` output might have additional fields like
`issuer`. This change will make the parser skip the additional fields
instead of returning an error.

    gpg: Signature made Mon Aug 26 20:59:48 2019 CEST
    gpg:                using RSA key 4AEE18F83AFDEB23
    gpg:                issuer "j.doe@example.com"
    gpg: Can't check signature: No public key

This change is designed so it is easy to specify additional fields that
need to get skipped, by adjusting the regex.

Signed-off-by: Sven Walter <s.walter@rebuy.com>
2020-11-17 14:11:26 -08:00
jannfis
52fc8a0024 chore: Update redis to 5.0.10 (#4767)
Signed-off-by: jannfis <jann@mistrust.net>
2020-11-17 14:11:16 -08:00
jannfis
39d7891f85 chore: Replace deprecated GH actions directives for integration tests (#4589)
* chore: Replace deprecated set-env directives

* revert lint version change

* Revert go.mod and go.sum changes

* Fix typo

* Update golangci-lint-action to v2

* Fix golangci-lint version

* Skip new lint complaints in test

* Skip more new lint complaints in test

* Exclude new SA5011 check in lint
2020-11-17 11:03:42 -08:00
Alexander Matyushentsev
fa97ddd36a fix: argocd diff --local should not print data of local secrets (#4850)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-11-17 10:40:45 -08:00
jannfis
2e8751c01a chore: Update golang to v1.14.12 [backport to release-1.7] (#4834)
* chore: Update golang to v1.14.12

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

* Allow CI checks to run on PRs to release branch

Signed-off-by: jannfis <jann@mistrust.net>
2020-11-16 09:07:34 -08:00
Remington Breeze
ad4b60aec0 fix(ui): stack overflow crash of resource tree view for large applications (#4685) 2020-10-28 15:57:35 -07:00
argo-bot
ef5010c3a0 Bump version to 1.7.8 2020-10-15 22:24:07 +00:00
argo-bot
592476baff Bump version to 1.7.8 2020-10-15 22:23:58 +00:00
Isaac Gaskin
6929423f4a fix(logging.go): changing marshaler for JSON logging to use gogo (#4319)
* fix(logging.go): changing marshaler for JSON logging to use gogo

grpc-gateway json marshaler breaks with gogo protos

#4117

* Retrigger CI pipeline
2020-10-15 13:43:13 -07:00
May Zhang
c277ef8442 fix: login with apiKey capability (#4557)
* fix: login with apiKey capability

* fix: update based on code review.

* fix: update based on code review.

* fix: check pws first.
2020-10-14 13:16:24 -07:00
Alexander Matyushentsev
adec0701a2 fix: api-server should not try creating default project it is exists already (#4517) 2020-10-13 22:05:49 -07:00
Alexander Matyushentsev
f6f96e7709 fix: JS error on application list page if app has no namespace (#4499) 2020-10-13 22:05:45 -07:00
May Zhang
001c227222 Revert "feat: autosync protection (#3996)"
This reverts commit 382bbdf031.
2020-09-30 14:37:59 -07:00
59 changed files with 728 additions and 271 deletions

View File

@@ -9,6 +9,7 @@ on:
pull_request:
branches:
- 'master'
- 'release-1.7'
jobs:
build-docker:
@@ -30,7 +31,7 @@ jobs:
- name: Setup Golang
uses: actions/setup-go@v1
with:
go-version: '1.14.2'
go-version: '1.14.12'
- name: Download all Go modules
run: |
go mod download
@@ -48,7 +49,7 @@ jobs:
- name: Setup Golang
uses: actions/setup-go@v1
with:
go-version: '1.14.2'
go-version: '1.14.12'
- name: Restore go build cache
uses: actions/cache@v1
with:
@@ -67,10 +68,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v1
uses: golangci/golangci-lint-action@v2
with:
version: v1.26
args: --timeout 5m
version: v1.29
args: --timeout 5m --exclude SA5011
test-go:
name: Run unit tests for Go packages
@@ -87,7 +88,7 @@ jobs:
- name: Setup Golang
uses: actions/setup-go@v1
with:
go-version: '1.14.2'
go-version: '1.14.12'
- name: Install required packages
run: |
sudo apt-get install git -y
@@ -99,9 +100,11 @@ jobs:
run: |
git fetch --prune --no-tags --depth=1 origin +refs/heads/*:refs/remotes/origin/*
- name: Add ~/go/bin to PATH
run: echo "::add-path::/home/runner/go/bin"
run: |
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: echo "::add-path::/usr/local/bin"
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@v1
with:
@@ -139,15 +142,17 @@ jobs:
- name: Setup Golang
uses: actions/setup-go@v1
with:
go-version: '1.14.2'
go-version: '1.14.12'
- name: Create symlink in GOPATH
run: |
mkdir -p ~/go/src/github.com/argoproj
cp -a ../argo-cd ~/go/src/github.com/argoproj
- name: Add /usr/local/bin to PATH
run: echo "::add-path::/usr/local/bin"
- name: Add ~/go/bin to PATH
run: echo "::add-path::/home/runner/go/bin"
run: |
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Download & vendor dependencies
run: |
# We need to vendor go modules for codegen yet
@@ -292,7 +297,7 @@ jobs:
- name: Setup Golang
uses: actions/setup-go@v1
with:
go-version: '1.14.2'
go-version: '1.14.12'
- name: Install K3S
env:
INSTALL_K3S_VERSION: v0.5.0
@@ -309,10 +314,12 @@ jobs:
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
- name: Add /usr/local/bin to PATH
run: echo "::add-path::/usr/local/bin"
- name: Add ~/go/bin to PATH
run: echo "::add-path::/home/runner/go/bin"
run: |
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Download Go dependencies
run: |
go mod download
@@ -328,7 +335,7 @@ jobs:
run: |
docker pull quay.io/dexidp/dex:v2.22.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:5.0.8-alpine
docker pull redis:5.0.10-alpine
- name: Create target directory for binaries in the build-process
run: |
mkdir -p dist

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/setup-go@v1
with:
go-version: '1.14.1'
go-version: '1.14.12'
- uses: actions/checkout@master
with:
path: src/github.com/argoproj/argo-cd
@@ -47,4 +47,4 @@ jobs:
git config --global user.name 'CI'
git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ steps.image.outputs.tag }}' && git push)
working-directory: argoproj-deployments/argocd
# TODO: clean up old images once github supports it: https://github.community/t5/How-to-use-Git-and-GitHub/Deleting-images-from-Github-Package-Registry/m-p/41202/thread-id/9811
# TODO: clean up old images once github supports it: https://github.community/t5/How-to-use-Git-and-GitHub/Deleting-images-from-Github-Package-Registry/m-p/41202/thread-id/9811

View File

@@ -46,7 +46,7 @@ jobs:
# Target version must match major.minor.patch and optional -rcX suffix
# where X must be a number.
TARGET_VERSION=${SOURCE_TAG#*release-v}
if ! echo ${TARGET_VERSION} | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
if ! echo "${TARGET_VERSION}" | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
echo "::error::Target version '${TARGET_VERSION}' is malformed, refusing to continue." >&2
exit 1
fi
@@ -78,10 +78,10 @@ jobs:
fi
# Make the variables available in follow-up steps
echo "::set-env name=TARGET_VERSION::${TARGET_VERSION}"
echo "::set-env name=TARGET_BRANCH::${TARGET_BRANCH}"
echo "::set-env name=RELEASE_TAG::${RELEASE_TAG}"
echo "::set-env name=PRE_RELEASE::${PRE_RELEASE}"
echo "TARGET_VERSION=${TARGET_VERSION}" >> $GITHUB_ENV
echo "TARGET_BRANCH=${TARGET_BRANCH}" >> $GITHUB_ENV
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
echo "PRE_RELEASE=${PRE_RELEASE}" >> $GITHUB_ENV
- name: Check if our release tag has a correct annotation
run: |
@@ -103,16 +103,16 @@ jobs:
# Whatever is in commit history for the tag, we only want that
# annotation from our tag. We discard everything else.
if test "$begin" = "false"; then
if echo $line | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
if echo "$line" | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
continue
fi
if test "$prefix" = "true"; then
if test -z "$line"; then prefix=false; fi
else
if echo $line | egrep -q '^commit [0-9a-f]+'; then
if echo "$line" | egrep -q '^commit [0-9a-f]+'; then
break
fi
echo $line >> ${RELEASE_NOTES}
echo "$line" >> ${RELEASE_NOTES}
fi
done
@@ -136,12 +136,12 @@ jobs:
# We store path to temporary release notes file for later reading, we
# need it when creating release.
echo "::set-env name=RELEASE_NOTES::$RELEASE_NOTES"
echo "RELEASE_NOTES=${RELEASE_NOTES}" >> $GITHUB_ENV
- name: Setup Golang
uses: actions/setup-go@v1
with:
go-version: '1.14.2'
go-version: '1.14.12'
- name: Setup Git author information
run: |
@@ -286,4 +286,4 @@ jobs:
run: |
set -ue
git push --delete origin ${SOURCE_TAG}
if: ${{ always() }}
if: ${{ always() }}

View File

@@ -4,7 +4,7 @@ ARG BASE_IMAGE=debian:10-slim
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
# Also used as the image in CI jobs so needs all dependencies
####################################################################################################
FROM golang:1.14.1 as builder
FROM golang:1.14.12 as builder
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
@@ -103,7 +103,7 @@ RUN NODE_ENV='production' yarn build
####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries
####################################################################################################
FROM golang:1.14.1 as argocd-build
FROM golang:1.14.12 as argocd-build
COPY --from=builder /usr/local/bin/packr /usr/local/bin/packr

View File

@@ -1,7 +1,7 @@
controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --staticassets ui/dist/app"
dex: sh -c "go run github.com/argoproj/argo-cd/cmd/argocd-util gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.22.0 serve /dex.yaml"
redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.8-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}
dex: sh -c "go run github.com/argoproj/argo-cd/cmd/argocd-util gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.27.0 serve /dex.yaml"
redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.10-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh

View File

@@ -1 +1 @@
1.7.7
1.7.14

View File

@@ -1097,6 +1097,12 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
}
for key, local := range localObjs {
if key.Kind == kube.SecretKind && key.Group == "" {
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
delete(localObjs, key)
continue
}
items = append(items, struct {
key kube.ResourceKey
live *unstructured.Unstructured

View File

@@ -5,6 +5,7 @@ import (
"crypto/sha256"
"encoding/base64"
"fmt"
"html"
"net/http"
"os"
"strconv"
@@ -25,6 +26,7 @@ import (
settingspkg "github.com/argoproj/argo-cd/pkg/apiclient/settings"
"github.com/argoproj/argo-cd/util/cli"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
"github.com/argoproj/argo-cd/util/localconfig"
oidcutil "github.com/argoproj/argo-cd/util/oidc"
"github.com/argoproj/argo-cd/util/rand"
@@ -161,13 +163,13 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
}
func userDisplayName(claims jwt.MapClaims) string {
if email, ok := claims["email"]; ok && email != nil {
return email.(string)
if email := jwtutil.StringField(claims, "email"); email != "" {
return email
}
if name, ok := claims["name"]; ok && name != nil {
return name.(string)
if name := jwtutil.StringField(claims, "name"); name != "" {
return name
}
return claims["sub"].(string)
return jwtutil.StringField(claims, "sub")
}
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and
@@ -190,7 +192,7 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
var refreshToken string
handleErr := func(w http.ResponseWriter, errMsg string) {
http.Error(w, errMsg, http.StatusBadRequest)
http.Error(w, html.EscapeString(errMsg), http.StatusBadRequest)
completionChan <- errMsg
}
@@ -205,7 +207,7 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
log.Debugf("Callback: %s", r.URL)
if formErr := r.FormValue("error"); formErr != "" {
handleErr(w, formErr+": "+r.FormValue("error_description"))
handleErr(w, fmt.Sprintf("%s: %s", formErr, r.FormValue("error_description")))
return
}

View File

@@ -0,0 +1,31 @@
package commands
import (
"testing"
"github.com/dgrijalva/jwt-go"
"github.com/stretchr/testify/assert"
)
//
func Test_userDisplayName_email(t *testing.T) {
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "email": "firstname.lastname@example.com", "groups": []string{"baz"}}
actualName := userDisplayName(claims)
expectedName := "firstname.lastname@example.com"
assert.Equal(t, expectedName, actualName)
}
func Test_userDisplayName_name(t *testing.T) {
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "name": "Firstname Lastname", "groups": []string{"baz"}}
actualName := userDisplayName(claims)
expectedName := "Firstname Lastname"
assert.Equal(t, expectedName, actualName)
}
func Test_userDisplayName_sub(t *testing.T) {
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "groups": []string{"baz"}}
actualName := userDisplayName(claims)
expectedName := "foo"
assert.Equal(t, expectedName, actualName)
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/argoproj/gitops-engine/pkg/utils/errors"
"github.com/argoproj/gitops-engine/pkg/utils/io"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
jsonpatch "github.com/evanphx/json-patch"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/semaphore"
v1 "k8s.io/api/core/v1"
@@ -836,6 +837,11 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), &retryAfter)
return
} else {
// retrying operation. remove previous failure time in app since it is used as a trigger
// that previous failed and operation should be retried
state.FinishedAt = nil
ctrl.setOperationState(app, state)
// Get rid of sync results and null out previous operation completion time
state.SyncResult = nil
}
} else {
@@ -926,6 +932,13 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
if err != nil {
return err
}
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
if err != nil {
return err
}
}
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace)
_, err = appClient.Patch(context.Background(), app.Name, types.MergePatchType, patchJSON, metav1.PatchOptions{})
if err != nil {
@@ -1325,20 +1338,6 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
}
if app.Spec.SyncPolicy.Automated.Prune {
bAllNeedPrune := true
for _, r := range resources {
if !r.RequiresPruning {
bAllNeedPrune = false
}
}
if bAllNeedPrune {
message := fmt.Sprintf("Skipping sync attempt to %s: auto-sync will wipe out all resourses", desiredCommitSHA)
logCtx.Warnf(message)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: message}
}
}
appIf := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
_, err := argo.SetAppOperation(appIf, app.Name, &op)
if err != nil {

View File

@@ -284,7 +284,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
c.metricsServer.IncClusterEventsCount(cluster.Server, gvk.Group, gvk.Kind)
})
c.clusters[cluster.Server] = clusterCache
c.clusters[server] = clusterCache
return clusterCache, nil
}

View File

@@ -70,6 +70,11 @@ non-preferred version and causes performance issues.
The `argocd-server` is stateless and probably least likely to cause issues. You might consider increasing number of replicas to 3 or more to ensure there is no downtime during upgrades.
**settings:**
* The `ARGOCD_GRPC_MAX_SIZE_MB` environment variable allows specifying the max size of the server response message in megabytes.
The default value is 200. You might need to increase for an Argo CD instance that manages 3000+ applications.
### argocd-dex-server, argocd-redis
The `argocd-dex-server` uses an in-memory database, and two or more instances would have inconsistent data. `argocd-redis` is pre-configured with the understanding of only three total redis servers/sentinels.

View File

@@ -1,7 +1,7 @@
#!/bin/sh
# The checksum of this file is used as cache key in our integration toolchain
#
helm2_version=2.15.2
helm2_version=2.17.0
helm3_version=3.2.0
jq_version=1.6
ksonnet_version=0.13.1

View File

@@ -26,7 +26,7 @@ spec:
name: static-files
containers:
- name: dex
image: quay.io/dexidp/dex:v2.22.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
command: [/shared/argocd-util, rundex]
ports:

View File

@@ -12,4 +12,4 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.7.7
newTag: v1.7.14

View File

@@ -22,7 +22,7 @@ spec:
runAsNonRoot: true
containers:
- name: redis
image: redis:5.0.8
image: redis:5.0.10-alpine
imagePullPolicy: Always
args:
- "--save"

View File

@@ -18,4 +18,4 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.7.7
newTag: v1.7.14

View File

@@ -647,7 +647,7 @@ spec:
serviceAccountName: argocd-redis-ha
initContainers:
- name: config-init
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
resources:
{}
@@ -674,7 +674,7 @@ spec:
mountPath: /data
containers:
- name: redis
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
@@ -694,7 +694,7 @@ spec:
- mountPath: /data
name: data
- name: sentinel
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
command:
- redis-sentinel

View File

@@ -8,4 +8,4 @@ redis-ha:
haproxy:
enabled: true
image:
tag: 5.0.8-alpine
tag: 5.0.10-alpine

View File

@@ -3083,7 +3083,7 @@ spec:
- "10"
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3123,7 +3123,7 @@ spec:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.22.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -3139,7 +3139,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3189,7 +3189,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -3264,7 +3264,7 @@ spec:
env:
- name: ARGOCD_API_SERVER_REPLICAS
value: "2"
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-server
ports:
@@ -3333,7 +3333,7 @@ spec:
- /data/conf/redis.conf
command:
- redis-server
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 15
@@ -3351,7 +3351,7 @@ spec:
- /data/conf/sentinel.conf
command:
- redis-sentinel
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 15
@@ -3377,7 +3377,7 @@ spec:
value: 896627000a81c7bdad8dbdcffd39728c9c17b309
- name: SENTINEL_ID_2
value: 3acbca861108bc47379b71b1d87d1c137dce591f
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
name: config-init
resources: {}

View File

@@ -2998,7 +2998,7 @@ spec:
- "10"
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3038,7 +3038,7 @@ spec:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.22.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -3054,7 +3054,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3104,7 +3104,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -3179,7 +3179,7 @@ spec:
env:
- name: ARGOCD_API_SERVER_REPLICAS
value: "2"
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-server
ports:
@@ -3248,7 +3248,7 @@ spec:
- /data/conf/redis.conf
command:
- redis-server
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 15
@@ -3266,7 +3266,7 @@ spec:
- /data/conf/sentinel.conf
command:
- redis-sentinel
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 15
@@ -3292,7 +3292,7 @@ spec:
value: 896627000a81c7bdad8dbdcffd39728c9c17b309
- name: SENTINEL_ID_2
value: 3acbca861108bc47379b71b1d87d1c137dce591f
image: redis:5.0.8-alpine
image: redis:5.0.10-alpine
imagePullPolicy: IfNotPresent
name: config-init
resources: {}

View File

@@ -2583,7 +2583,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2623,7 +2623,7 @@ spec:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.22.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -2639,7 +2639,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2673,7 +2673,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:5.0.8
image: redis:5.0.10-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -2708,7 +2708,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -2763,7 +2763,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-server
ports:

View File

@@ -2498,7 +2498,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2538,7 +2538,7 @@ spec:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.22.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -2554,7 +2554,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2588,7 +2588,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:5.0.8
image: redis:5.0.10-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -2623,7 +2623,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -2678,7 +2678,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:v1.7.7
image: argoproj/argocd:v1.7.14
imagePullPolicy: Always
name: argocd-server
ports:

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"io/ioutil"
"math"
"net"
"net/http"
"os"
@@ -42,6 +43,7 @@ import (
versionpkg "github.com/argoproj/argo-cd/pkg/apiclient/version"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/env"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/localconfig"
oidcutil "github.com/argoproj/argo-cd/util/oidc"
@@ -54,8 +56,13 @@ const (
EnvArgoCDServer = "ARGOCD_SERVER"
// EnvArgoCDAuthToken is the environment variable to look for an Argo CD auth token
EnvArgoCDAuthToken = "ARGOCD_AUTH_TOKEN"
// EnvArgoCDgRPCMaxSizeMB is the environment variable to look for a max gRPC message size
EnvArgoCDgRPCMaxSizeMB = "ARGOCD_GRPC_MAX_SIZE_MB"
)
var (
// MaxGRPCMessageSize contains max grpc message size
MaxGRPCMessageSize = 100 * 1024 * 1024
MaxGRPCMessageSize = env.ParseNumFromEnv(EnvArgoCDgRPCMaxSizeMB, 200, 0, math.MaxInt32) * 1024 * 1024
)
// Client defines an interface for interaction with an Argo CD server.

View File

@@ -2326,13 +2326,19 @@ func (proj AppProject) IsGroupKindPermitted(gk schema.GroupKind, namespaced bool
res := metav1.GroupKind{Group: gk.Group, Kind: gk.Kind}
if namespaced {
isWhiteListed = len(proj.Spec.NamespaceResourceWhitelist) == 0 || isResourceInList(res, proj.Spec.NamespaceResourceWhitelist)
isBlackListed = isResourceInList(res, proj.Spec.NamespaceResourceBlacklist)
namespaceWhitelist := proj.Spec.NamespaceResourceWhitelist
namespaceBlacklist := proj.Spec.NamespaceResourceBlacklist
isWhiteListed = namespaceWhitelist == nil || len(namespaceWhitelist) != 0 && isResourceInList(res, namespaceWhitelist)
isBlackListed = len(namespaceBlacklist) != 0 && isResourceInList(res, namespaceBlacklist)
return isWhiteListed && !isBlackListed
}
isWhiteListed = len(proj.Spec.ClusterResourceWhitelist) == 0 || isResourceInList(res, proj.Spec.ClusterResourceWhitelist)
isBlackListed = isResourceInList(res, proj.Spec.ClusterResourceBlacklist)
clusterWhitelist := proj.Spec.ClusterResourceWhitelist
clusterBlacklist := proj.Spec.ClusterResourceBlacklist
isWhiteListed = len(clusterWhitelist) != 0 && isResourceInList(res, clusterWhitelist)
isBlackListed = len(clusterBlacklist) != 0 && isResourceInList(res, clusterBlacklist)
return isWhiteListed && !isBlackListed
}
@@ -2569,8 +2575,11 @@ func (proj *AppProject) NormalizeJWTTokens() bool {
}
func syncJWTTokenBetweenStatusAndSpec(proj *AppProject) bool {
existingRole := map[string]bool{}
needSync := false
for roleIndex, role := range proj.Spec.Roles {
existingRole[role.Name] = true
tokensInSpec := role.JWTTokens
tokensInStatus := []JWTToken{}
if proj.Status.JWTTokensByRole == nil {
@@ -2593,8 +2602,16 @@ func syncJWTTokenBetweenStatusAndSpec(proj *AppProject) bool {
proj.Spec.Roles[roleIndex].JWTTokens = tokens
proj.Status.JWTTokensByRole[role.Name] = JWTTokens{Items: tokens}
}
if proj.Status.JWTTokensByRole != nil {
for role := range proj.Status.JWTTokensByRole {
if !existingRole[role] {
delete(proj.Status.JWTTokensByRole, role)
needSync = true
}
}
}
return needSync
}

View File

@@ -134,7 +134,7 @@ func TestAppProject_IsGroupKindPermitted(t *testing.T) {
NamespaceResourceBlacklist: []metav1.GroupKind{{Group: "apps", Kind: "Deployment"}},
},
}
assert.True(t, proj.IsGroupKindPermitted(schema.GroupKind{Group: "apps", Kind: "ReplicaSet"}, true))
assert.False(t, proj.IsGroupKindPermitted(schema.GroupKind{Group: "apps", Kind: "ReplicaSet"}, true))
assert.False(t, proj.IsGroupKindPermitted(schema.GroupKind{Group: "apps", Kind: "Deployment"}, true))
proj2 := AppProject{
@@ -161,6 +161,21 @@ func TestAppProject_IsGroupKindPermitted(t *testing.T) {
}
assert.False(t, proj4.IsGroupKindPermitted(schema.GroupKind{Group: "", Kind: "Namespace"}, false))
assert.True(t, proj4.IsGroupKindPermitted(schema.GroupKind{Group: "apps", Kind: "Action"}, true))
proj5 := AppProject{
Spec: AppProjectSpec{
ClusterResourceWhitelist: []metav1.GroupKind{},
NamespaceResourceWhitelist: []metav1.GroupKind{{Group: "*", Kind: "*"}},
},
}
assert.False(t, proj5.IsGroupKindPermitted(schema.GroupKind{Group: "", Kind: "Namespace"}, false))
assert.True(t, proj5.IsGroupKindPermitted(schema.GroupKind{Group: "apps", Kind: "Action"}, true))
proj6 := AppProject{
Spec: AppProjectSpec{},
}
assert.False(t, proj6.IsGroupKindPermitted(schema.GroupKind{Group: "", Kind: "Namespace"}, false))
assert.True(t, proj6.IsGroupKindPermitted(schema.GroupKind{Group: "apps", Kind: "Action"}, true))
}
func TestAppProject_GetRoleByName(t *testing.T) {

View File

@@ -69,7 +69,9 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach
// CreateGRPC creates new configured grpc server
func (a *ArgoCDRepoServer) CreateGRPC() *grpc.Server {
server := grpc.NewServer(a.opts...)
versionpkg.RegisterVersionServiceServer(server, &version.Server{})
versionpkg.RegisterVersionServiceServer(server, version.NewServer(nil, func() (bool, error) {
return true, nil
}))
manifestService := repository.NewService(a.metricsServer, a.cache, a.parallelismLimit)
apiclient.RegisterRepoServerServiceServer(server, manifestService)

View File

@@ -18,6 +18,7 @@ import (
"github.com/argoproj/argo-cd/pkg/apiclient/account"
sessionpkg "github.com/argoproj/argo-cd/pkg/apiclient/session"
"github.com/argoproj/argo-cd/server/session"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/password"
"github.com/argoproj/argo-cd/util/rbac"
sessionutil "github.com/argoproj/argo-cd/util/session"
@@ -63,7 +64,7 @@ func newTestAccountServerExt(ctx context.Context, enforceFn rbac.ClaimsEnforcerF
}
kubeclientset := fake.NewSimpleClientset(cm, secret)
settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
sessionMgr := sessionutil.NewSessionManager(settingsMgr, "", sessionutil.NewInMemoryUserStateStorage())
sessionMgr := sessionutil.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", sessionutil.NewInMemoryUserStateStorage())
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
enforcer.SetClaimsEnforcerFunc(enforceFn)

View File

@@ -889,6 +889,10 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
manifest, err := s.kubectl.PatchResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace, types.PatchType(q.PatchType), []byte(q.Patch))
if err != nil {
// don't expose real error for secrets since it might contain secret data
if res.Kind == kube.SecretKind && res.Group == "" {
return nil, fmt.Errorf("failed to patch Secret %s/%s", res.Namespace, res.Name)
}
return nil, err
}
manifest, err = replaceSecretValues(manifest)

View File

@@ -6,6 +6,7 @@ import (
"strings"
"testing"
jwt "github.com/dgrijalva/jwt-go"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
@@ -15,13 +16,12 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
jwt "github.com/dgrijalva/jwt-go"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apiclient/project"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
"github.com/argoproj/argo-cd/server/rbacpolicy"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/assets"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
@@ -71,7 +71,7 @@ func TestProjectServer(t *testing.T) {
policyTemplate := "p, proj:%s:%s, applications, %s, %s/%s, %s"
t.Run("TestNormalizeProj", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
roleName := "roleName"
role1 := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
@@ -308,7 +308,7 @@ func TestProjectServer(t *testing.T) {
id := "testId"
t.Run("TestCreateTokenDenied", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
@@ -317,7 +317,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTokenSuccessfullyUsingGroup", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName, Groups: []string{"my-group"}}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
@@ -328,11 +328,13 @@ func TestProjectServer(t *testing.T) {
_ = enforcer.SetBuiltinPolicy(`p, role:admin, projects, update, *, allow`)
t.Run("TestCreateTokenSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1})
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 100})
assert.NoError(t, err)
claims, err := sessionMgr.Parse(tokenResponse.Token)
assert.NoError(t, err)
@@ -346,10 +348,12 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTokenWithIDSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
assert.NoError(t, err)
claims, err := sessionMgr.Parse(tokenResponse.Token)
@@ -364,10 +368,12 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTokenWithSameIdDeny", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, util.NewKeyLock(), sessionMgr, policyEnf)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
assert.NoError(t, err)
@@ -389,7 +395,7 @@ func TestProjectServer(t *testing.T) {
_ = enforcer.SetBuiltinPolicy(`p, *, *, *, *, deny`)
t.Run("TestDeleteTokenDenied", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -402,7 +408,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestDeleteTokenSuccessfullyWithGroup", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -418,7 +424,7 @@ func TestProjectServer(t *testing.T) {
p, role:admin, projects, update, *, allow`)
t.Run("TestDeleteTokenSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -439,7 +445,7 @@ p, role:admin, projects, update, *, allow`)
p, role:admin, projects, update, *, allow`)
t.Run("TestDeleteTokenByIdSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -462,7 +468,7 @@ p, role:admin, projects, update, *, allow`)
enforcer = newEnforcer(kubeclientset)
t.Run("TestCreateTwoTokensInRoleSuccess", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
tokenName := "testToken"
token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
@@ -627,7 +633,7 @@ p, role:admin, projects, update, *, allow`)
})
t.Run("TestSyncWindowsActive", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithSyncWindows := existingProj.DeepCopy()
projectWithSyncWindows.Spec.SyncWindows = v1alpha1.SyncWindows{}
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
@@ -640,7 +646,7 @@ p, role:admin, projects, update, *, allow`)
})
t.Run("TestGetSyncWindowsStateCannotGetProjectDetails", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithSyncWindows := existingProj.DeepCopy()
projectWithSyncWindows.Spec.SyncWindows = v1alpha1.SyncWindows{}
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
@@ -659,7 +665,7 @@ p, role:admin, projects, update, *, allow`)
// nolint:staticcheck
ctx := context.WithValue(context.Background(), "claims", &jwt.MapClaims{"groups": []string{"my-group"}})
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithSyncWindows := existingProj.DeepCopy()
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
projectWithSyncWindows.Spec.SyncWindows = append(projectWithSyncWindows.Spec.SyncWindows, win)

View File

@@ -82,7 +82,16 @@ func (p *RBACPolicyEnforcer) GetScopes() []string {
}
func IsProjectSubject(subject string) bool {
return strings.HasPrefix(subject, "proj:")
_, _, ok := GetProjectRoleFromSubject(subject)
return ok
}
func GetProjectRoleFromSubject(subject string) (string, string, bool) {
parts := strings.Split(subject, ":")
if len(parts) == 3 && parts[0] == "proj" {
return parts[1], parts[2], true
}
return "", "", false
}
// EnforceClaims is an RBAC claims enforcer specific to the Argo CD API server
@@ -92,14 +101,14 @@ func (p *RBACPolicyEnforcer) EnforceClaims(claims jwt.Claims, rvals ...interface
return false
}
subject := jwtutil.GetField(mapClaims, "sub")
subject := jwtutil.StringField(mapClaims, "sub")
// Check if the request is for an application resource. We have special enforcement which takes
// into consideration the project's token and group bindings
var runtimePolicy string
proj := p.getProjectFromRequest(rvals...)
if proj != nil {
if IsProjectSubject(subject) {
return p.enforceProjectToken(subject, mapClaims, proj, rvals...)
return p.enforceProjectToken(subject, proj, rvals...)
}
runtimePolicy = proj.ProjectPoliciesString()
}
@@ -158,31 +167,17 @@ func (p *RBACPolicyEnforcer) getProjectFromRequest(rvals ...interface{}) *v1alph
}
// enforceProjectToken will check to see the valid token has not yet been revoked in the project
func (p *RBACPolicyEnforcer) enforceProjectToken(subject string, claims jwt.MapClaims, proj *v1alpha1.AppProject, rvals ...interface{}) bool {
func (p *RBACPolicyEnforcer) enforceProjectToken(subject string, proj *v1alpha1.AppProject, rvals ...interface{}) bool {
subjectSplit := strings.Split(subject, ":")
if len(subjectSplit) != 3 {
return false
}
projName, roleName := subjectSplit[1], subjectSplit[2]
projName, _ := subjectSplit[1], subjectSplit[2]
if projName != proj.Name {
// this should never happen (we generated a project token for a different project)
return false
}
var iat int64 = -1
jti, err := jwtutil.GetID(claims)
if err != nil || jti == "" {
iat, err = jwtutil.GetIssuedAt(claims)
if err != nil {
return false
}
}
_, _, err = proj.GetJWTToken(roleName, iat, jti)
if err != nil {
// if we get here the token is still valid, but has been revoked (no longer exists in the project)
return false
}
vals := append([]interface{}{subject}, rvals[1:]...)
return p.enf.EnforceRuntimePolicy(proj.ProjectPoliciesString(), vals...)

View File

@@ -67,12 +67,6 @@ func TestEnforceAllPolicies(t *testing.T) {
claims = jwt.MapClaims{"sub": "cathy"}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
claims = jwt.MapClaims{"sub": "proj:my-proj:my-role"}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
claims = jwt.MapClaims{"sub": "proj:my-proj:other-role", "iat": 1234}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
claims = jwt.MapClaims{"groups": []string{"my-org:other-group"}}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
// AWS cognito returns its groups in cognito:groups
rbacEnf.SetScopes([]string{"cognito:groups"})

View File

@@ -188,9 +188,12 @@ func initializeDefaultProject(opts ArgoCDServerOpts) error {
},
}
_, err := opts.AppClientset.ArgoprojV1alpha1().AppProjects(opts.Namespace).Create(context.Background(), defaultProj, metav1.CreateOptions{})
if apierrors.IsAlreadyExists(err) {
return nil
_, err := opts.AppClientset.ArgoprojV1alpha1().AppProjects(opts.Namespace).Get(context.Background(), defaultProj.Name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
_, err = opts.AppClientset.ArgoprojV1alpha1().AppProjects(opts.Namespace).Create(context.Background(), defaultProj, metav1.CreateOptions{})
if apierrors.IsAlreadyExists(err) {
return nil
}
}
return err
}
@@ -203,8 +206,6 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
err = initializeDefaultProject(opts)
errors.CheckError(err)
sessionMgr := util_session.NewSessionManager(settingsMgr, opts.DexServerAddr, opts.Cache)
factory := appinformer.NewFilteredSharedInformerFactory(opts.AppClientset, 0, opts.Namespace, func(options *metav1.ListOptions) {})
projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
projLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(opts.Namespace)
@@ -212,6 +213,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
appLister := factory.Argoproj().V1alpha1().Applications().Lister().Applications(opts.Namespace)
sessionMgr := util_session.NewSessionManager(settingsMgr, projLister, opts.DexServerAddr, opts.Cache)
enf := rbac.NewEnforcer(opts.KubeClientset, opts.Namespace, common.ArgoCDRBACConfigMapName, nil)
enf.EnableEnforce(!opts.DisableAuth)
err = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
@@ -539,7 +541,16 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf)
certificateService := certificate.NewServer(a.RepoClientset, db, a.enf)
gpgkeyService := gpgkey.NewServer(a.RepoClientset, db, a.enf)
versionpkg.RegisterVersionServiceServer(grpcS, &version.Server{})
versionpkg.RegisterVersionServiceServer(grpcS, version.NewServer(a, func() (bool, error) {
if a.DisableAuth {
return true, nil
}
sett, err := a.settingsMgr.GetSettings()
if err != nil {
return false, err
}
return sett.AnonymousUserEnabled, err
}))
clusterpkg.RegisterClusterServiceServer(grpcS, clusterService)
applicationpkg.RegisterApplicationServiceServer(grpcS, applicationService)
repositorypkg.RegisterRepositoryServiceServer(grpcS, repoService)
@@ -813,6 +824,7 @@ func (server *ArgoCDServer) newStaticAssetsHandler(dir string, baseHRef string)
if server.XFrameOptions != "" {
w.Header().Set("X-Frame-Options", server.XFrameOptions)
}
w.Header().Set("X-XSS-Protection", "1")
// serve index.html for non file requests to support HTML5 History API
if acceptHTML && !fileRequest && (r.Method == "GET" || r.Method == "HEAD") {

View File

@@ -356,13 +356,6 @@ func TestRevokedToken(t *testing.T) {
claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt}
assert.True(t, s.enf.Enforce(claims, "projects", "get", existingProj.ObjectMeta.Name))
assert.True(t, s.enf.Enforce(claims, "applications", "get", defaultTestObject))
// Now revoke the token by deleting the token
existingProj.Spec.Roles[0].JWTTokens = nil
existingProj.Status.JWTTokensByRole = nil
_, _ = s.AppClientset.ArgoprojV1alpha1().AppProjects(test.FakeArgoCDNamespace).Update(context.Background(), &existingProj, metav1.UpdateOptions{})
time.Sleep(200 * time.Millisecond) // this lets the informer get synced
assert.False(t, s.enf.Enforce(claims, "projects", "get", existingProj.ObjectMeta.Name))
assert.False(t, s.enf.Enforce(claims, "applications", "get", defaultTestObject))
}
func TestCertsAreNotGeneratedInInsecureMode(t *testing.T) {
@@ -633,3 +626,62 @@ func TestTranslateGrpcCookieHeader(t *testing.T) {
})
}
func TestInitializeDefaultProject_ProjectDoesNotExist(t *testing.T) {
argoCDOpts := ArgoCDServerOpts{
Namespace: test.FakeArgoCDNamespace,
KubeClientset: fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret()),
AppClientset: apps.NewSimpleClientset(),
}
err := initializeDefaultProject(argoCDOpts)
if !assert.NoError(t, err) {
return
}
proj, err := argoCDOpts.AppClientset.ArgoprojV1alpha1().
AppProjects(test.FakeArgoCDNamespace).Get(context.Background(), common.DefaultAppProjectName, metav1.GetOptions{})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, proj.Spec, v1alpha1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
ClusterResourceWhitelist: []metav1.GroupKind{{Group: "*", Kind: "*"}},
})
}
func TestInitializeDefaultProject_ProjectAlreadyInitialized(t *testing.T) {
existingDefaultProject := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: common.DefaultAppProjectName,
Namespace: test.FakeArgoCDNamespace,
},
Spec: v1alpha1.AppProjectSpec{
SourceRepos: []string{"some repo"},
Destinations: []v1alpha1.ApplicationDestination{{Server: "some cluster", Namespace: "*"}},
},
}
argoCDOpts := ArgoCDServerOpts{
Namespace: test.FakeArgoCDNamespace,
KubeClientset: fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret()),
AppClientset: apps.NewSimpleClientset(&existingDefaultProject),
}
err := initializeDefaultProject(argoCDOpts)
if !assert.NoError(t, err) {
return
}
proj, err := argoCDOpts.AppClientset.ArgoprojV1alpha1().
AppProjects(test.FakeArgoCDNamespace).Get(context.Background(), common.DefaultAppProjectName, metav1.GetOptions{})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, proj.Spec, existingDefaultProject.Spec)
}

View File

@@ -7,21 +7,38 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apiclient/version"
"github.com/argoproj/argo-cd/server/settings"
"github.com/argoproj/argo-cd/util/helm"
ksutil "github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/kustomize"
sessionmgr "github.com/argoproj/argo-cd/util/session"
)
type Server struct {
type server struct {
ksonnetVersion string
kustomizeVersion string
helmVersion string
kubectlVersion string
authenticator settings.Authenticator
disableAuth func() (bool, error)
}
func NewServer(authenticator settings.Authenticator, disableAuth func() (bool, error)) *server {
return &server{authenticator: authenticator, disableAuth: disableAuth}
}
// Version returns the version of the API server
func (s *Server) Version(context.Context, *empty.Empty) (*version.VersionMessage, error) {
func (s *server) Version(ctx context.Context, _ *empty.Empty) (*version.VersionMessage, error) {
vers := common.GetVersion()
disableAuth, err := s.disableAuth()
if err != nil {
return nil, err
}
if !sessionmgr.LoggedIn(ctx) && !disableAuth {
return &version.VersionMessage{Version: vers.Version}, nil
}
if s.ksonnetVersion == "" {
ksonnetVersion, err := ksutil.Version()
if err == nil {
@@ -72,6 +89,10 @@ func (s *Server) Version(context.Context, *empty.Empty) (*version.VersionMessage
}
// AuthFuncOverride allows the version to be returned without auth
func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
func (s *server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
if s.authenticator != nil {
// this authenticates the user, but ignores any error, so that we have claims populated
ctx, _ = s.authenticator.Authenticate(ctx)
}
return ctx, nil
}

View File

@@ -1,8 +1,8 @@
FROM redis:5.0.8 as redis
FROM redis:5.0.10 as redis
FROM node:11.15.0 as node
FROM golang:1.14.1 as golang
FROM golang:1.14.12 as golang
FROM debian:10-slim
@@ -93,4 +93,4 @@ RUN mkdir -p /home/user && chmod 777 /home/user && \
ln -s /opt/yarn-v1.15.2/bin/yarn /usr/local/bin/yarn && \
ln -s /opt/yarn-v1.15.2/bin/yarnpkg /usr/local/bin/yarnpkg
ENTRYPOINT ["/usr/local/bin/uid_entrypoint.sh"]
ENTRYPOINT ["/usr/local/bin/uid_entrypoint.sh"]

View File

@@ -1,6 +1,6 @@
controller: su --pty -m default -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: su --pty -m default -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --staticassets ui/dist/app"
dex: su --pty -m default -c "test \"$ARGOCD_IN_CI\" = \"true\" && exit 0; go run github.com/argoproj/argo-cd/cmd/argocd-util gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.22.0 serve /dex.yaml"
dex: su --pty -m default -c "test \"$ARGOCD_IN_CI\" = \"true\" && exit 0; go run github.com/argoproj/argo-cd/cmd/argocd-util gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.27.0 serve /dex.yaml"
redis: su --pty -m default -c "/usr/local/bin/redis-server --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}"
repo-server: su --pty -m default -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
ui: su --pty -m default -c "test \"$ARGOCD_IN_CI\" = \"true\" && exit 0; cd ui && ARGOCD_E2E_YARN_HOST=0.0.0.0 ${ARGOCD_E2E_YARN_CMD:-yarn} start"

View File

@@ -21,6 +21,7 @@ import (
"github.com/argoproj/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
networkingv1beta "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -427,6 +428,15 @@ func TestAppWithSecrets(t *testing.T) {
diffOutput := FailOnErr(RunCli("app", "diff", app.Name)).(string)
assert.Empty(t, diffOutput)
// make sure resource update error does not print secret details
_, err = RunCli("app", "patch-resource", "test-app-with-secrets", "--resource-name", "test-secret",
"--kind", "Secret", "--patch", `{"op": "add", "path": "/data", "value": "hello"}'`,
"--patch-type", "application/json-patch+json")
require.Error(t, err)
assert.Contains(t, err.Error(), fmt.Sprintf("failed to patch Secret %s/test-secret", DeploymentNamespace()))
assert.NotContains(t, err.Error(), "username")
assert.NotContains(t, err.Error(), "password")
// patch secret and make sure app is out of sync and diff detects the change
FailOnErr(KubeClientset.CoreV1().Secrets(DeploymentNamespace()).Patch(context.Background(),
"test-secret", types.JSONPatchType, []byte(`[
@@ -462,6 +472,20 @@ func TestAppWithSecrets(t *testing.T) {
And(func(app *Application) {
diffOutput := FailOnErr(RunCli("app", "diff", app.Name)).(string)
assert.Empty(t, diffOutput)
}).
// verify not committed secret also ignore during diffing
When().
WriteFile("secret3.yaml", `
apiVersion: v1
kind: Secret
metadata:
name: test-secret3
stringData:
username: test-username`).
Then().
And(func(app *Application) {
diffOutput := FailOnErr(RunCli("app", "diff", app.Name, "--local", "testdata/secrets")).(string)
assert.Empty(t, diffOutput)
})
}
@@ -1356,10 +1380,11 @@ func TestNamespaceAutoCreation(t *testing.T) {
func TestFailedSyncWithRetry(t *testing.T) {
Given(t).
Path(guestbookPath).
Path("hook").
When().
// app should be attempted to auto-synced once and marked with error after failed attempt detected
PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": "badValue"}]`).
PatchFile("hook.yaml", `[{"op": "replace", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/hook": "PreSync"}}]`).
// make hook fail
PatchFile("hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/command", "value": ["false"]}]`).
Create().
IgnoreErrors().
Sync("--retry-limit=1", "--retry-backoff-duration=1s").

View File

@@ -46,6 +46,12 @@ func (a *Actions) DeleteFile(file string) *Actions {
return a
}
func (a *Actions) WriteFile(fileName, fileContents string) *Actions {
a.context.t.Helper()
fixture.WriteFile(a.context.path+"/"+fileName, fileContents)
return a
}
func (a *Actions) AddFile(fileName, fileContents string) *Actions {
a.context.t.Helper()
fixture.AddFile(a.context.path+"/"+fileName, fileContents)

View File

@@ -433,11 +433,15 @@ func Delete(path string) {
FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "delete"))
}
func AddFile(path, contents string) {
func WriteFile(path, contents string) {
log.WithFields(log.Fields{"path": path}).Info("adding")
CheckError(ioutil.WriteFile(filepath.Join(repoDirectory(), path), []byte(contents), 0644))
}
func AddFile(path, contents string) {
WriteFile(path, contents)
FailOnErr(Run(repoDirectory(), "git", "diff"))
FailOnErr(Run(repoDirectory(), "git", "add", "."))
@@ -445,9 +449,8 @@ func AddFile(path, contents string) {
}
func AddSignedFile(path, contents string) {
log.WithFields(log.Fields{"path": path}).Info("adding")
WriteFile(path, contents)
CheckError(ioutil.WriteFile(filepath.Join(repoDirectory(), path), []byte(contents), 0644))
prevGnuPGHome := os.Getenv("GNUPGHOME")
os.Setenv("GNUPGHOME", TmpDir+"/gpg")
FailOnErr(Run(repoDirectory(), "git", "diff"))

View File

@@ -1,15 +1,19 @@
package test
import (
"context"
"github.com/argoproj/gitops-engine/pkg/utils/testing"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/gitops-engine/pkg/utils/testing"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
appclient "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
appinformer "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
applister "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
)
@@ -115,6 +119,30 @@ func NewFakeSecret(policy ...string) *apiv1.Secret {
return &secret
}
type interfaceLister struct {
appProjects appclient.AppProjectInterface
}
func (l interfaceLister) List(selector labels.Selector) ([]*v1alpha1.AppProject, error) {
res, err := l.appProjects.List(context.Background(), metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
}
items := make([]*v1alpha1.AppProject, len(res.Items))
for i := range res.Items {
items[i] = &res.Items[i]
}
return items, nil
}
func (l interfaceLister) Get(name string) (*v1alpha1.AppProject, error) {
return l.appProjects.Get(context.Background(), name, metav1.GetOptions{})
}
func NewFakeProjListerFromInterface(appProjects appclient.AppProjectInterface) applister.AppProjectNamespaceLister {
return &interfaceLister{appProjects: appProjects}
}
func NewFakeProjLister(objects ...runtime.Object) applister.AppProjectNamespaceLister {
fakeAppClientset := apps.NewSimpleClientset(objects...)
factory := appinformer.NewFilteredSharedInformerFactory(fakeAppClientset, 0, "", func(options *metav1.ListOptions) {})

View File

@@ -330,9 +330,10 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
nodeByKey.set(treeNodeKey(node), resourceNode);
});
const nodes = Array.from(nodeByKey.values());
let roots: ResourceTreeNode[] = null;
let roots: ResourceTreeNode[] = [];
const childrenByParentKey = new Map<string, ResourceTreeNode[]>();
if (props.useNetworkingHierarchy) {
// Network view
const hasParents = new Set<string>();
const networkNodes = nodes.filter(node => node.networkingInfo);
networkNodes.forEach(parent => {
@@ -344,27 +345,6 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
});
});
roots = networkNodes.filter(node => !hasParents.has(treeNodeKey(node)));
} else {
const managedKeys = new Set(props.app.status.resources.map(nodeKey));
nodes.forEach(child => {
(child.parentRefs || []).forEach(parent => {
const children = childrenByParentKey.get(treeNodeKey(parent)) || [];
children.push(child);
childrenByParentKey.set(treeNodeKey(parent), children);
});
});
roots = nodes.filter(node => (node.parentRefs || []).length === 0 || managedKeys.has(nodeKey(node))).sort(compareNodes);
}
function processNode(node: ResourceTreeNode, root: ResourceTreeNode, colors?: string[]) {
graph.setNode(treeNodeKey(node), {...node, width: NODE_WIDTH, height: NODE_HEIGHT, root});
(childrenByParentKey.get(treeNodeKey(node)) || []).sort(compareNodes).forEach(child => {
graph.setEdge(treeNodeKey(node), treeNodeKey(child), {colors});
processNode(child, root, colors);
});
}
if (props.useNetworkingHierarchy) {
const externalRoots = roots.filter(root => (root.networkingInfo.ingress || []).length > 0).sort(compareNodes);
const internalRoots = roots.filter(root => (root.networkingInfo.ingress || []).length === 0).sort(compareNodes);
const colorsBySource = new Map<string, string>();
@@ -413,14 +393,44 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
filterGraph(props.app, externalRoots.length > 0 ? EXTERNAL_TRAFFIC_NODE : INTERNAL_TRAFFIC_NODE, graph, props.nodeFilter);
}
} else {
roots.sort(compareNodes).forEach(node => processNode(node, node));
// Tree view
const managedKeys = new Set(props.app.status.resources.map(nodeKey));
const orphans: ResourceTreeNode[] = [];
nodes.forEach(node => {
if ((node.parentRefs || []).length === 0 || managedKeys.has(nodeKey(node))) {
roots.push(node);
} else {
orphans.push(node);
node.parentRefs.forEach(parent => {
const children = childrenByParentKey.get(treeNodeKey(parent)) || [];
children.push(node);
childrenByParentKey.set(treeNodeKey(parent), children);
});
}
});
roots.sort(compareNodes).forEach(node => {
processNode(node, node);
graph.setEdge(appNodeKey(props.app), treeNodeKey(node));
});
orphans.sort(compareNodes).forEach(node => {
processNode(node, node);
});
graph.setNode(appNodeKey(props.app), {...appNode, width: NODE_WIDTH, height: NODE_HEIGHT});
roots.forEach(root => graph.setEdge(appNodeKey(props.app), treeNodeKey(root)));
if (props.nodeFilter) {
filterGraph(props.app, appNodeKey(props.app), graph, props.nodeFilter);
}
}
function processNode(node: ResourceTreeNode, root: ResourceTreeNode, colors?: string[]) {
graph.setNode(treeNodeKey(node), {...node, width: NODE_WIDTH, height: NODE_HEIGHT, root});
(childrenByParentKey.get(treeNodeKey(node)) || []).sort(compareNodes).forEach(child => {
if (treeNodeKey(child) === treeNodeKey(root)) {
return;
}
graph.setEdge(treeNodeKey(node), treeNodeKey(child), {colors});
processNode(child, root, colors);
});
}
dagre.layout(graph);
const edges: {from: string; to: string; lines: Line[]; backgroundImage?: string}[] = [];

View File

@@ -142,7 +142,7 @@ function filterApps(applications: models.Application[], pref: AppsListPreference
(pref.reposFilter.length === 0 || pref.reposFilter.includes(app.spec.source.repoURL)) &&
(pref.syncFilter.length === 0 || pref.syncFilter.includes(app.status.sync.status)) &&
(pref.healthFilter.length === 0 || pref.healthFilter.includes(app.status.health.status)) &&
(pref.namespacesFilter.length === 0 || pref.namespacesFilter.some(ns => minimatch(app.spec.destination.namespace, ns))) &&
(pref.namespacesFilter.length === 0 || pref.namespacesFilter.some(ns => app.spec.destination.namespace && minimatch(app.spec.destination.namespace, ns))) &&
(pref.clustersFilter.length === 0 || pref.clustersFilter.some(server => minimatch(app.spec.destination.server || app.spec.destination.name, server))) &&
(pref.labelsFilter.length === 0 || pref.labelsFilter.every(selector => LabelSelector.match(selector, app.metadata.labels)))
);

View File

@@ -53,6 +53,7 @@ func GenerateDexConfigYAML(settings *settings.ArgoCDSettings) ([]byte, error) {
"public": true,
"redirectURIs": []string{
"http://localhost",
"http://localhost:8085/auth/callback",
},
}

View File

@@ -30,13 +30,15 @@ func Run(cmd *exec.Cmd) (string, error) {
}
func RunWithRedactor(cmd *exec.Cmd, redactor func(text string) string) (string, error) {
opts := argoexec.CmdOpts{Timeout: timeout}
span := tracing.StartSpan(fmt.Sprintf("exec %v", cmd.Args[0]))
span.SetBaggageItem("dir", fmt.Sprintf("%v", cmd.Dir))
span.SetBaggageItem("args", fmt.Sprintf("%v", cmd.Args))
defer span.Finish()
opts := argoexec.CmdOpts{Timeout: timeout}
if redactor != nil {
span.SetBaggageItem("args", redactor(fmt.Sprintf("%v", cmd.Args)))
opts.Redactor = redactor
} else {
span.SetBaggageItem("args", fmt.Sprintf("%v", cmd.Args))
}
defer span.Finish()
return argoexec.RunCommandExt(cmd, opts)
}

View File

@@ -3,6 +3,7 @@ package exec
import (
"os"
"os/exec"
"regexp"
"testing"
"time"
@@ -27,3 +28,14 @@ func TestRun(t *testing.T) {
assert.NoError(t, err)
assert.NotEmpty(t, out)
}
func TestHideUsernamePassword(t *testing.T) {
_, err := RunWithRedactor(exec.Command("helm registry login https://charts.bitnami.com/bitnami", "--username", "foo", "--password", "bar"), nil)
assert.NotEmpty(t, err)
var redactor = func(text string) string {
return regexp.MustCompile("(--username|--password) [^ ]*").ReplaceAllString(text, "$1 ******")
}
_, err = RunWithRedactor(exec.Command("helm registry login https://charts.bitnami.com/bitnami", "--username", "foo", "--password", "bar"), redactor)
assert.NotEmpty(t, err)
}

View File

@@ -35,6 +35,9 @@ var verificationStartMatch = regexp.MustCompile(`^gpg: Signature made ([a-zA-Z0-
// Regular expression to match the key ID of a commit signature verification
var verificationKeyIDMatch = regexp.MustCompile(`^gpg:\s+using\s([A-Za-z]+)\skey\s([a-zA-Z0-9]+)$`)
// Regular expression to match possible additional fields of a commit signature verification
var verificationAdditionalFields = regexp.MustCompile(`^gpg:\s+issuer\s.+$`)
// Regular expression to match the signature status of a commit signature verification
var verificationStatusMatch = regexp.MustCompile(`^gpg: ([a-zA-Z]+) signature from "([^"]+)" \[([a-zA-Z]+)\]$`)
@@ -588,6 +591,15 @@ func ParseGitCommitVerification(signature string) (PGPVerifyResult, error) {
linesParsed += 1
// Skip additional fields
for verificationAdditionalFields.MatchString(scanner.Text()) {
if !scanner.Scan() {
return PGPVerifyResult{}, fmt.Errorf("Unexpected end-of-file while parsing commit verification output.")
}
linesParsed += 1
}
if strings.HasPrefix(scanner.Text(), "gpg: Can't check signature: ") {
result.Result = VerifyResultInvalid
result.Identity = "unknown"

View File

@@ -316,7 +316,22 @@ func Test_GPG_ParseGitCommitVerification(t *testing.T) {
// Signature with unknown key - considered invalid
{
c, err := ioutil.ReadFile("testdata/unknown_signature.txt")
c, err := ioutil.ReadFile("testdata/unknown_signature1.txt")
if err != nil {
panic(err.Error())
}
res, err := ParseGitCommitVerification(string(c))
assert.NoError(t, err)
assert.Equal(t, "4AEE18F83AFDEB23", res.KeyID)
assert.Equal(t, "RSA", res.Cipher)
assert.Equal(t, TrustUnknown, res.Trust)
assert.Equal(t, "Mon Aug 26 20:59:48 2019 CEST", res.Date)
assert.Equal(t, VerifyResultInvalid, res.Result)
}
// Signature with unknown key and additional fields - considered invalid
{
c, err := ioutil.ReadFile("testdata/unknown_signature2.txt")
if err != nil {
panic(err.Error())
}

View File

@@ -0,0 +1,4 @@
gpg: Signature made Mon Aug 26 20:59:48 2019 CEST
gpg: using RSA key 4AEE18F83AFDEB23
gpg: issuer "j.doe@example.com"
gpg: Can't check signature: No public key

View File

@@ -1,13 +1,11 @@
package grpc
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gogo/protobuf/proto"
grpc_logging "github.com/grpc-ecosystem/go-grpc-middleware/logging"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
ctx_logrus "github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
@@ -31,11 +29,11 @@ type jsonpbMarshalleble struct {
}
func (j *jsonpbMarshalleble) MarshalJSON() ([]byte, error) {
b := &bytes.Buffer{}
if err := grpc_logrus.JsonPbMarshaller.Marshal(b, j.Message); err != nil {
b, err := proto.Marshal(j.Message)
if err != nil {
return nil, fmt.Errorf("jsonpb serializer failed: %v", err)
}
return b.Bytes(), nil
return b, nil
}
type loggingServerStream struct {

View File

@@ -1,6 +1,6 @@
dependencies:
- name: mariadb
repository: https://kubernetes-charts.storage.googleapis.com/
version: 4.3.1
digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26
generated: 2018-08-02T22:07:51.905271776Z
repository: https://charts.helm.sh/stable
version: 4.4.2
digest: sha256:c07f89818ebcd92a4dbaff719ebedc14e2569ee27447d3abf739e2dd11d13fcb
generated: "2020-11-03T09:04:00.781031-08:00"

View File

@@ -1,7 +1,7 @@
dependencies:
- name: mariadb
version: 4.x.x
repository: https://kubernetes-charts.storage.googleapis.com/
repository: https://charts.helm.sh/stable
condition: mariadb.enabled
tags:
- wordpress-database

View File

@@ -2,7 +2,9 @@ package jwt
import (
"encoding/json"
"errors"
"fmt"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
)
@@ -21,8 +23,8 @@ func MapClaims(claims jwtgo.Claims) (jwtgo.MapClaims, error) {
return mapClaims, nil
}
// GetField extracts a field from the claims as a string
func GetField(claims jwtgo.MapClaims, fieldName string) string {
// StringField extracts a field from the claims as a string
func StringField(claims jwtgo.MapClaims, fieldName string) string {
if fieldIf, ok := claims[fieldName]; ok {
if field, ok := fieldIf.(string); ok {
return field
@@ -31,6 +33,16 @@ func GetField(claims jwtgo.MapClaims, fieldName string) string {
return ""
}
// Float64Field extracts a field from the claims as a float64
func Float64Field(claims jwtgo.MapClaims, fieldName string) float64 {
if fieldIf, ok := claims[fieldName]; ok {
if field, ok := fieldIf.(float64); ok {
return field
}
}
return 0
}
// GetScopeValues extracts the values of specified scopes from the claims
func GetScopeValues(claims jwtgo.MapClaims, scopes []string) []string {
groups := make([]string, 0)
@@ -67,9 +79,13 @@ func GetID(m jwtgo.MapClaims) (string, error) {
return "", fmt.Errorf("jti '%v' is not a string", m["jti"])
}
// GetIssuedAt returns the issued at as an int64
func GetIssuedAt(m jwtgo.MapClaims) (int64, error) {
switch iat := m["iat"].(type) {
// IssuedAt returns the issued at as an int64
func IssuedAt(m jwtgo.MapClaims) (int64, error) {
iatField, ok := m["iat"]
if !ok {
return 0, errors.New("token does not have iat claim")
}
switch iat := iatField.(type) {
case float64:
return int64(iat), nil
case json.Number:
@@ -81,6 +97,12 @@ func GetIssuedAt(m jwtgo.MapClaims) (int64, error) {
}
}
// IssuedAtTime returns the issued at as a time.Time
func IssuedAtTime(m jwtgo.MapClaims) (time.Time, error) {
iat, err := IssuedAt(m)
return time.Unix(iat, 0), err
}
func Claims(in interface{}) jwtgo.Claims {
claims, ok := in.(jwtgo.Claims)
if ok {

View File

@@ -1,7 +1,9 @@
package jwt
import (
"fmt"
"testing"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/stretchr/testify/assert"
@@ -36,3 +38,25 @@ func TestGetGroups(t *testing.T) {
assert.Empty(t, GetGroups(jwt.MapClaims{}, []string{"groups"}))
assert.Equal(t, []string{"foo"}, GetGroups(jwt.MapClaims{"groups": []string{"foo"}}, []string{"groups"}))
}
func TestIssuedAtTime_Int64(t *testing.T) {
// Tuesday, 1 December 2020 14:00:00
claims := jwt.MapClaims{"iat": int64(1606831200)}
issuedAt, err := IssuedAtTime(claims)
assert.Nil(t, err)
str := fmt.Sprint(issuedAt.UTC().Format("Mon Jan _2 15:04:05 2006"))
assert.Equal(t, "Tue Dec 1 14:00:00 2020", str)
}
func TestIssuedAtTime_Error_NoInt(t *testing.T) {
claims := jwt.MapClaims{"iat": 1606831200}
_, err := IssuedAtTime(claims)
assert.NotNil(t, err)
}
func TestIssuedAtTime_Error_Missing(t *testing.T) {
claims := jwt.MapClaims{}
iat, err := IssuedAtTime(claims)
assert.NotNil(t, err)
assert.Equal(t, time.Unix(0, 0), iat)
}

View File

@@ -132,19 +132,12 @@ func (e *Enforcer) EnforceErr(rvals ...interface{}) error {
if err != nil {
break
}
sub := jwtutil.GetField(claims, "sub")
if sub != "" {
if sub := jwtutil.StringField(claims, "sub"); sub != "" {
rvalsStrs = append(rvalsStrs, fmt.Sprintf("sub: %s", sub))
}
iatField, ok := claims["iat"]
if !ok {
break
if issuedAtTime, err := jwtutil.IssuedAtTime(claims); err == nil {
rvalsStrs = append(rvalsStrs, fmt.Sprintf("iat: %s", issuedAtTime.Format(time.RFC3339)))
}
iat, ok := iatField.(float64)
if !ok {
break
}
rvalsStrs = append(rvalsStrs, fmt.Sprintf("iat: %s", time.Unix(int64(iat), 0).Format(time.RFC3339)))
}
errMsg = fmt.Sprintf("%s: %s", errMsg, strings.Join(rvalsStrs, ", "))
}

View File

@@ -412,6 +412,7 @@ func TestEnforceErrorMessage(t *testing.T) {
assert.Error(t, err)
assert.Equal(t, "rpc error: code = PermissionDenied desc = permission denied", err.Error())
// nolint:staticcheck
ctx := context.WithValue(context.Background(), "claims", &jwt.StandardClaims{Subject: "proj:default:admin"})
err = enf.EnforceErr(ctx.Value("claims"), "project")
assert.Error(t, err)
@@ -419,16 +420,19 @@ func TestEnforceErrorMessage(t *testing.T) {
iat := time.Unix(int64(1593035962), 0).Format(time.RFC3339)
exp := fmt.Sprintf("rpc error: code = PermissionDenied desc = permission denied: project, sub: proj:default:admin, iat: %s", iat)
// nolint:staticcheck
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{Subject: "proj:default:admin", IssuedAt: 1593035962})
err = enf.EnforceErr(ctx.Value("claims"), "project")
assert.Error(t, err)
assert.Equal(t, exp, err.Error())
// nolint:staticcheck
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{ExpiresAt: 1})
err = enf.EnforceErr(ctx.Value("claims"), "project")
assert.Error(t, err)
assert.Equal(t, "rpc error: code = PermissionDenied desc = permission denied: project", err.Error())
// nolint:staticcheck
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{Subject: "proj:default:admin", IssuedAt: 0})
err = enf.EnforceErr(ctx.Value("claims"), "project")
assert.Error(t, err)

View File

@@ -17,6 +17,7 @@ import (
"google.golang.org/grpc/status"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/server/rbacpolicy"
"github.com/argoproj/argo-cd/util/cache/appstate"
"github.com/argoproj/argo-cd/util/dex"
@@ -31,6 +32,7 @@ import (
// SessionManager generates and validates JWT tokens for login sessions.
type SessionManager struct {
settingsMgr *settings.SettingsManager
projectsLister v1alpha1.AppProjectNamespaceLister
client *http.Client
prov oidcutil.Provider
storage UserStateStorage
@@ -74,10 +76,11 @@ const (
SessionManagerClaimsIssuer = "argocd"
// invalidLoginError, for security purposes, doesn't say whether the username or password was invalid. This does not mitigate the potential for timing attacks to determine which is which.
invalidLoginError = "Invalid username or password"
blankPasswordError = "Blank passwords are not allowed"
accountDisabled = "Account %s is disabled"
usernameTooLongError = "Username is too long (%d bytes max)"
invalidLoginError = "Invalid username or password"
blankPasswordError = "Blank passwords are not allowed"
accountDisabled = "Account %s is disabled"
usernameTooLongError = "Username is too long (%d bytes max)"
userDoesNotHaveCapability = "Account %s does not have %s capability"
)
const (
@@ -126,11 +129,12 @@ func getLoginFailureWindow() time.Duration {
}
// NewSessionManager creates a new session manager from Argo CD settings
func NewSessionManager(settingsMgr *settings.SettingsManager, dexServerAddr string, storage UserStateStorage) *SessionManager {
func NewSessionManager(settingsMgr *settings.SettingsManager, projectsLister v1alpha1.AppProjectNamespaceLister, dexServerAddr string, storage UserStateStorage) *SessionManager {
s := SessionManager{
settingsMgr: settingsMgr,
storage: storage,
sleep: time.Sleep,
projectsLister: projectsLister,
verificationDelayNoiseEnabled: true,
}
settings, err := settingsMgr.GetSettings()
@@ -202,7 +206,7 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
var claims jwt.MapClaims
settings, err := mgr.settingsMgr.GetSettings()
argoCDSettings, err := mgr.settingsMgr.GetSettings()
if err != nil {
return nil, err
}
@@ -211,14 +215,30 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return settings.ServerSignature, nil
return argoCDSettings.ServerSignature, nil
})
if err != nil {
return nil, err
}
subject := jwtutil.GetField(claims, "sub")
if rbacpolicy.IsProjectSubject(subject) {
issuedAt, err := jwtutil.IssuedAtTime(claims)
if err != nil {
return nil, err
}
subject := jwtutil.StringField(claims, "sub")
id := jwtutil.StringField(claims, "jti")
if projName, role, ok := rbacpolicy.GetProjectRoleFromSubject(subject); ok {
proj, err := mgr.projectsLister.Get(projName)
if err != nil {
return nil, err
}
_, _, err = proj.GetJWTToken(role, issuedAt.Unix(), id)
if err != nil {
return nil, err
}
return token.Claims, nil
}
@@ -227,11 +247,24 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
return nil, err
}
if id := jwtutil.GetField(claims, "jti"); id != "" && account.TokenIndex(id) == -1 {
if !account.Enabled {
return nil, fmt.Errorf("account %s is disabled", subject)
}
var capability settings.AccountCapability
if id != "" {
capability = settings.AccountCapabilityApiKey
} else {
capability = settings.AccountCapabilityLogin
}
if !account.HasCapability(capability) {
return nil, fmt.Errorf("account %s does not have '%s' capability", subject, capability)
}
if id != "" && account.TokenIndex(id) == -1 {
return nil, fmt.Errorf("account %s does not have token with id %s", subject, id)
}
issuedAt := time.Unix(int64(claims["iat"].(float64)), 0)
if account.PasswordMtime != nil && issuedAt.Before(*account.PasswordMtime) {
return nil, fmt.Errorf("Account password has changed since token issued")
}
@@ -399,15 +432,20 @@ func (mgr *SessionManager) VerifyUsernamePassword(username string, password stri
_, _ = passwordutil.HashPassword("for_consistent_response_time")
return err
}
if !account.Enabled {
return status.Errorf(codes.Unauthenticated, accountDisabled, username)
}
valid, _ := passwordutil.VerifyPassword(password, account.PasswordHash)
if !valid {
mgr.updateFailureCount(username, true)
return InvalidLoginErr
}
if !account.Enabled {
return status.Errorf(codes.Unauthenticated, accountDisabled, username)
}
if !account.HasCapability(settings.AccountCapabilityLogin) {
return status.Errorf(codes.Unauthenticated, userDoesNotHaveCapability, username, settings.AccountCapabilityLogin)
}
mgr.updateFailureCount(username, false)
return nil
}
@@ -468,11 +506,11 @@ func Username(ctx context.Context) string {
if !ok {
return ""
}
switch jwtutil.GetField(mapClaims, "iss") {
switch jwtutil.StringField(mapClaims, "iss") {
case SessionManagerClaimsIssuer:
return jwtutil.GetField(mapClaims, "sub")
return jwtutil.StringField(mapClaims, "sub")
default:
return jwtutil.GetField(mapClaims, "email")
return jwtutil.StringField(mapClaims, "email")
}
}
@@ -481,7 +519,7 @@ func Iss(ctx context.Context) string {
if !ok {
return ""
}
return jwtutil.GetField(mapClaims, "iss")
return jwtutil.StringField(mapClaims, "iss")
}
func Iat(ctx context.Context) (time.Time, error) {
@@ -489,16 +527,7 @@ func Iat(ctx context.Context) (time.Time, error) {
if !ok {
return time.Time{}, errors.New("unable to extract token claims")
}
iatField, ok := mapClaims["iat"]
if !ok {
return time.Time{}, errors.New("token does not have iat claim")
}
if iat, ok := iatField.(float64); !ok {
return time.Time{}, errors.New("iat token field has unexpected type")
} else {
return time.Unix(int64(iat), 0), nil
}
return jwtutil.IssuedAtTime(mapClaims)
}
func Sub(ctx context.Context) string {
@@ -506,7 +535,7 @@ func Sub(ctx context.Context) string {
if !ok {
return ""
}
return jwtutil.GetField(mapClaims, "sub")
return jwtutil.StringField(mapClaims, "sub")
}
func Groups(ctx context.Context, scopes []string) []string {

View File

@@ -6,28 +6,46 @@ import (
"math"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/argoproj/gitops-engine/pkg/utils/errors"
"github.com/dgrijalva/jwt-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/common"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
"github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/password"
"github.com/argoproj/argo-cd/util/settings"
)
func getKubeClient(pass string, enabled bool) *fake.Clientset {
func getProjLister(objects ...runtime.Object) v1alpha1.AppProjectNamespaceLister {
return test.NewFakeProjListerFromInterface(apps.NewSimpleClientset(objects...).ArgoprojV1alpha1().AppProjects("argocd"))
}
func getKubeClient(pass string, enabled bool, capabilities ...settings.AccountCapability) *fake.Clientset {
const defaultSecretKey = "Hello, world!"
bcrypt, err := password.HashPassword(pass)
errors.CheckError(err)
if len(capabilities) == 0 {
capabilities = []settings.AccountCapability{settings.AccountCapabilityLogin, settings.AccountCapabilityApiKey}
}
var capabilitiesStr []string
for i := range capabilities {
capabilitiesStr = append(capabilitiesStr, string(capabilities[i]))
}
return fake.NewSimpleClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
@@ -38,6 +56,7 @@ func getKubeClient(pass string, enabled bool) *fake.Clientset {
},
},
Data: map[string]string{
"admin": strings.Join(capabilitiesStr, ","),
"admin.enabled": strconv.FormatBool(enabled),
},
}, &corev1.Secret{
@@ -52,18 +71,18 @@ func getKubeClient(pass string, enabled bool) *fake.Clientset {
})
}
func newSessionManager(settingsMgr *settings.SettingsManager, storage UserStateStorage) *SessionManager {
mgr := NewSessionManager(settingsMgr, "", storage)
func newSessionManager(settingsMgr *settings.SettingsManager, projectLister v1alpha1.AppProjectNamespaceLister, storage UserStateStorage) *SessionManager {
mgr := NewSessionManager(settingsMgr, projectLister, "", storage)
mgr.verificationDelayNoiseEnabled = false
return mgr
}
func TestSessionManager(t *testing.T) {
func TestSessionManager_AdminToken(t *testing.T) {
const (
defaultSubject = "admin"
)
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("pass", true), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr := newSessionManager(settingsMgr, getProjLister(), NewInMemoryUserStateStorage())
token, err := mgr.Create(defaultSubject, 0, "")
if err != nil {
@@ -82,6 +101,80 @@ func TestSessionManager(t *testing.T) {
}
}
func TestSessionManager_AdminToken_Deactivated(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("pass", false), "argocd")
mgr := newSessionManager(settingsMgr, getProjLister(), NewInMemoryUserStateStorage())
token, err := mgr.Create("admin", 0, "")
if err != nil {
t.Errorf("Could not create token: %v", err)
}
_, err = mgr.Parse(token)
require.Error(t, err)
assert.Contains(t, err.Error(), "account admin is disabled")
}
func TestSessionManager_AdminToken_LoginCapabilityDisabled(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("pass", true, settings.AccountCapabilityLogin), "argocd")
mgr := newSessionManager(settingsMgr, getProjLister(), NewInMemoryUserStateStorage())
token, err := mgr.Create("admin", 0, "abc")
if err != nil {
t.Errorf("Could not create token: %v", err)
}
_, err = mgr.Parse(token)
require.Error(t, err)
assert.Contains(t, err.Error(), "account admin does not have 'apiKey' capability")
}
func TestSessionManager_ProjectToken(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("pass", true), "argocd")
t.Run("Valid Token", func(t *testing.T) {
proj := appv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "argocd",
},
Spec: appv1.AppProjectSpec{Roles: []appv1.ProjectRole{{Name: "test"}}},
Status: appv1.AppProjectStatus{JWTTokensByRole: map[string]appv1.JWTTokens{
"test": {
Items: []appv1.JWTToken{{ID: "abc", IssuedAt: time.Now().Unix(), ExpiresAt: 0}},
},
}},
}
mgr := newSessionManager(settingsMgr, getProjLister(&proj), NewInMemoryUserStateStorage())
jwtToken, err := mgr.Create("proj:default:test", 100, "abc")
require.NoError(t, err)
_, err = mgr.Parse(jwtToken)
assert.NoError(t, err)
})
t.Run("Token Revoked", func(t *testing.T) {
proj := appv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "argocd",
},
Spec: appv1.AppProjectSpec{Roles: []appv1.ProjectRole{{Name: "test"}}},
}
mgr := newSessionManager(settingsMgr, getProjLister(&proj), NewInMemoryUserStateStorage())
jwtToken, err := mgr.Create("proj:default:test", 10, "")
require.NoError(t, err)
_, err = mgr.Parse(jwtToken)
require.Error(t, err)
assert.Contains(t, err.Error(), "does not exist in project 'default'")
})
}
var loggedOutContext = context.Background()
// nolint:staticcheck
@@ -153,7 +246,7 @@ func TestVerifyUsernamePassword(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient(password, !tc.disabled), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr := newSessionManager(settingsMgr, getProjLister(), NewInMemoryUserStateStorage())
err := mgr.VerifyUsernamePassword(tc.userName, tc.password)
@@ -236,7 +329,7 @@ func TestCacheValueGetters(t *testing.T) {
func TestRandomPasswordVerificationDelay(t *testing.T) {
var sleptFor time.Duration
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr := newSessionManager(settingsMgr, test.NewFakeProjLister(), NewInMemoryUserStateStorage())
mgr.verificationDelayNoiseEnabled = true
mgr.sleep = func(d time.Duration) {
sleptFor = d
@@ -257,7 +350,7 @@ func TestLoginRateLimiter(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
storage := NewInMemoryUserStateStorage()
mgr := newSessionManager(settingsMgr, storage)
mgr := newSessionManager(settingsMgr, getProjLister(), storage)
t.Run("Test login delay valid user", func(t *testing.T) {
for i := 0; i < getMaxLoginFailures(); i++ {
@@ -296,7 +389,7 @@ func TestMaxUsernameLength(t *testing.T) {
username += "a"
}
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr := newSessionManager(settingsMgr, getProjLister(), NewInMemoryUserStateStorage())
err := mgr.VerifyUsernamePassword(username, "password")
assert.Error(t, err)
assert.Contains(t, err.Error(), fmt.Sprintf(usernameTooLongError, maxUsernameLength))
@@ -304,7 +397,7 @@ func TestMaxUsernameLength(t *testing.T) {
func TestMaxCacheSize(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr := newSessionManager(settingsMgr, getProjLister(), NewInMemoryUserStateStorage())
invalidUsers := []string{"invalid1", "invalid2", "invalid3", "invalid4", "invalid5", "invalid6", "invalid7"}
// Temporarily decrease max cache size
@@ -320,7 +413,7 @@ func TestMaxCacheSize(t *testing.T) {
func TestFailedAttemptsExpiry(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr := newSessionManager(settingsMgr, getProjLister(), NewInMemoryUserStateStorage())
invalidUsers := []string{"invalid1", "invalid2", "invalid3", "invalid4", "invalid5", "invalid6", "invalid7"}