mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-04-06 00:38:47 +02:00
Compare commits
45 Commits
v1.7.7
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92b02379b9 | ||
|
|
7423c0bb20 | ||
|
|
528fb17951 | ||
|
|
cdc1aaa8a7 | ||
|
|
73d73e11f9 | ||
|
|
76ed3d6d72 | ||
|
|
f77a3cbadb | ||
|
|
2e215df16d | ||
|
|
c2e3fb96fb | ||
|
|
f2562b41a0 | ||
|
|
602df423e0 | ||
|
|
5be98f51c8 | ||
|
|
fb6b32babc | ||
|
|
a95c7ffd98 | ||
|
|
8663126343 | ||
|
|
94e6075757 | ||
|
|
fd07e1c00a | ||
|
|
2b132cbcee | ||
|
|
d0d6dae7af | ||
|
|
cf6551d29b | ||
|
|
1bf11f72a4 | ||
|
|
4ef0245ce8 | ||
|
|
9cd980bd69 | ||
|
|
97401f9bb9 | ||
|
|
2720bef5ce | ||
|
|
d8441b4292 | ||
|
|
bcb05b0c2e | ||
|
|
7248fee361 | ||
|
|
cf541c6200 | ||
|
|
f6dc8c389a | ||
|
|
26ff594063 | ||
|
|
d4e8213e28 | ||
|
|
89e28c64aa | ||
|
|
52fc8a0024 | ||
|
|
39d7891f85 | ||
|
|
fa97ddd36a | ||
|
|
2e8751c01a | ||
|
|
ad4b60aec0 | ||
|
|
ef5010c3a0 | ||
|
|
592476baff | ||
|
|
6929423f4a | ||
|
|
c277ef8442 | ||
|
|
adec0701a2 | ||
|
|
f6f96e7709 | ||
|
|
001c227222 |
41
.github/workflows/ci-build.yaml
vendored
41
.github/workflows/ci-build.yaml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/image.yaml
vendored
4
.github/workflows/image.yaml
vendored
@@ -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
|
||||
|
||||
22
.github/workflows/release.yaml
vendored
22
.github/workflows/release.yaml
vendored
@@ -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() }}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
4
Procfile
4
Procfile
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
31
cmd/argocd/commands/login_test.go
Normal file
31
cmd/argocd/commands/login_test.go
Normal 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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
2
controller/cache/cache.go
vendored
2
controller/cache/cache.go
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -12,4 +12,4 @@ bases:
|
||||
images:
|
||||
- name: argoproj/argocd
|
||||
newName: argoproj/argocd
|
||||
newTag: v1.7.7
|
||||
newTag: v1.7.14
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -18,4 +18,4 @@ bases:
|
||||
images:
|
||||
- name: argoproj/argocd
|
||||
newName: argoproj/argocd
|
||||
newTag: v1.7.7
|
||||
newTag: v1.7.14
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,4 +8,4 @@ redis-ha:
|
||||
haproxy:
|
||||
enabled: true
|
||||
image:
|
||||
tag: 5.0.8-alpine
|
||||
tag: 5.0.10-alpine
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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...)
|
||||
|
||||
|
||||
@@ -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"})
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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").
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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) {})
|
||||
|
||||
@@ -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}[] = [];
|
||||
|
||||
@@ -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)))
|
||||
);
|
||||
|
||||
@@ -53,6 +53,7 @@ func GenerateDexConfigYAML(settings *settings.ArgoCDSettings) ([]byte, error) {
|
||||
"public": true,
|
||||
"redirectURIs": []string{
|
||||
"http://localhost",
|
||||
"http://localhost:8085/auth/callback",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
4
util/gpg/testdata/unknown_signature2.txt
vendored
Normal file
4
util/gpg/testdata/unknown_signature2.txt
vendored
Normal 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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, ", "))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user