mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-28 11:28:48 +01:00
Compare commits
35 Commits
v1.8.2
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebbc1d02f5 | ||
|
|
9b0631b6c6 | ||
|
|
eb3d1fb84b | ||
|
|
e97b643526 | ||
|
|
b51ba85aae | ||
|
|
be72e7cc42 | ||
|
|
f6e9e41d7b | ||
|
|
087b8b2fd5 | ||
|
|
6dbbb18aa9 | ||
|
|
62b9b3aeb5 | ||
|
|
31110cde4d | ||
|
|
d6c5c72eb4 | ||
|
|
0b3333ef4b | ||
|
|
d0f8edfec8 | ||
|
|
b1ff29fdf9 | ||
|
|
785bb9ecce | ||
|
|
b57579c4ae | ||
|
|
6b53ac785e | ||
|
|
e38920f570 | ||
|
|
28aea3dfde | ||
|
|
fe59190a96 | ||
|
|
0a04a491d9 | ||
|
|
701ce05393 | ||
|
|
965825f752 | ||
|
|
bd73326b8a | ||
|
|
502b8944c4 | ||
|
|
f5b0db240b | ||
|
|
ce43b7a438 | ||
|
|
ebcfea64ff | ||
|
|
14c3dd2c59 | ||
|
|
4359d345a0 | ||
|
|
7081068a2d | ||
|
|
0f9c684278 | ||
|
|
3ea3c13665 | ||
|
|
13fed83ec6 |
7
.readthedocs.yml
Normal file
7
.readthedocs.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
formats: all
|
||||
mkdocs:
|
||||
fail_on_warning: false
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
47
SECURITY.md
Normal file
47
SECURITY.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Security Policy for Argo CD
|
||||
|
||||
Version: **v1.0 (2020-02-26)**
|
||||
|
||||
## Preface
|
||||
|
||||
As a deployment tool, Argo CD needs to have production access which makes
|
||||
security a very important topic. The Argoproj team takes security very
|
||||
seriously and is continuously working on improving it.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We currently support the most recent release (`N`, e.g. `1.8`) and the release
|
||||
previous to the most recent one (`N-1`, e.g. `1.7`). With the release of
|
||||
`N+1`, `N-1` drops out of support and `N` becomes `N-1`.
|
||||
|
||||
We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the
|
||||
supported versions, which will contain fixes for security vulnerabilities and
|
||||
important bugs. Prior releases might receive critical security fixes on a best
|
||||
effort basis, however, it cannot be guaranteed that security fixes get
|
||||
back-ported to these unsupported versions.
|
||||
|
||||
In rare cases, where a security fix needs complex re-design of a feature or is
|
||||
otherwise very intrusive, and there's a workaround available, we may decide to
|
||||
provide a forward-fix only, e.g. to be released the next minor release, instead
|
||||
of releasing it within a patch branch for the currently supported releases.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a security related bug in ArgoCD, we kindly ask you for responsible
|
||||
disclosure and for giving us appropriate time to react, analyze and develop a
|
||||
fix to mitigate the found security vulnerability.
|
||||
|
||||
We will do our best to react quickly on your inquiry, and to coordinate a fix
|
||||
and disclosure with you. Sometimes, it might take a little longer for us to
|
||||
react (e.g. out of office conditions), so please bear with us in these cases.
|
||||
|
||||
We will publish security advisiories using the Git Hub SA feature to keep our
|
||||
community well informed, and will credit you for your findings (unless you
|
||||
prefer to stay anonymous, of course).
|
||||
|
||||
Please report vulnerabilities by e-mail to all of the following people:
|
||||
|
||||
* jfischer@redhat.com
|
||||
* Jesse_Suen@intuit.com
|
||||
* Alexander_Matyushentsev@intuit.com
|
||||
* Edward_Lee@intuit.com
|
||||
@@ -78,6 +78,7 @@ func NewCommand() *cobra.Command {
|
||||
|
||||
cache, err := cacheSrc()
|
||||
errors.CheckError(err)
|
||||
cache.Cache.SetClient(cacheutil.NewTwoLevelClient(cache.Cache.GetClient(), 10*time.Minute))
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
|
||||
kubectl := kubeutil.NewKubectl()
|
||||
|
||||
@@ -1901,7 +1901,7 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
|
||||
selectedResourcesAreReady = checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation)
|
||||
}
|
||||
|
||||
if selectedResourcesAreReady && !operationInProgress {
|
||||
if selectedResourcesAreReady && (!operationInProgress || !watchOperation) {
|
||||
app = printFinalStatus(app)
|
||||
return app, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
grpc_util "github.com/argoproj/argo-cd/util/grpc"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
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"
|
||||
@@ -113,7 +115,7 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
}
|
||||
|
||||
parser := &jwt.Parser{
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation()),
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
|
||||
}
|
||||
claims := jwt.MapClaims{}
|
||||
_, _, err := parser.ParseUnverified(tokenString, &claims)
|
||||
@@ -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/v4"
|
||||
"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)
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
"github.com/argoproj/argo-cd/util/jwt"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -247,13 +248,10 @@ func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *c
|
||||
}
|
||||
|
||||
claims := token.Claims.(jwtgo.MapClaims)
|
||||
issuedAt := int64(claims["iat"].(float64))
|
||||
expiresAt := int64(0)
|
||||
if expires, ok := claims["exp"]; ok {
|
||||
expiresAt = int64(expires.(float64))
|
||||
}
|
||||
id := claims["jti"].(string)
|
||||
subject := claims["sub"].(string)
|
||||
issuedAt, _ := jwt.IssuedAt(claims)
|
||||
expiresAt := int64(jwt.Float64Field(claims, "exp"))
|
||||
id := jwt.StringField(claims, "jti")
|
||||
subject := jwt.StringField(claims, "sub")
|
||||
|
||||
if !outputTokenOnly {
|
||||
fmt.Printf("Create token succeeded for %s.\n", subject)
|
||||
|
||||
2
controller/cache/cache.go
vendored
2
controller/cache/cache.go
vendored
@@ -294,7 +294,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
|
||||
}
|
||||
|
||||
175
docs/assets/versions.css
Normal file
175
docs/assets/versions.css
Normal file
@@ -0,0 +1,175 @@
|
||||
.md-header__title {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dropdown-caret {
|
||||
display: inline-block !important;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.fa .fa-caret-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.rst-other-versions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rst-other-versions > dl, .rst-other-versions dt, .rst-other-versions small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rst-other-versions > dl:first-child {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
line-height: 0px !important;
|
||||
}
|
||||
|
||||
.rst-versions.shift-up .rst-other-versions {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.rst-versions .rst-other-versions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Version Warning */
|
||||
div[data-md-component=announce] {
|
||||
background-color: rgb(248, 243, 236);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
div[data-md-component=announce]>div#announce-msg{
|
||||
color: var(--md-code-hl-number-color);
|
||||
font-size: .8rem;
|
||||
text-align: center;
|
||||
margin: 15px;
|
||||
}
|
||||
div[data-md-component=announce]>div#announce-msg>a{
|
||||
color: var(--md-typeset-a-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* from https://assets.readthedocs.org/static/css/badge_only.css,
|
||||
most styles have to be overriden here */
|
||||
.rst-versions{
|
||||
position: relative !important;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100px !important;
|
||||
background: hsla(173, 100%, 24%, 1) !important;
|
||||
font-family: inherit !important;
|
||||
z-index: 0 !important;
|
||||
}
|
||||
.rst-versions a{
|
||||
color:#2980B9;
|
||||
text-decoration:none
|
||||
}
|
||||
.rst-versions .rst-badge-small{
|
||||
display:none
|
||||
}
|
||||
.rst-versions .rst-current-version{
|
||||
padding:12px;
|
||||
background: hsla(173, 100%, 24%, 1) !important;
|
||||
display:block;
|
||||
text-align:right;
|
||||
font-size:90%;
|
||||
cursor:pointer;
|
||||
color: white !important;
|
||||
*zoom:1
|
||||
}
|
||||
.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{
|
||||
display:table;content:""
|
||||
}
|
||||
.rst-versions .rst-current-version:after{
|
||||
clear:both
|
||||
}
|
||||
.rst-versions .rst-current-version .fa{
|
||||
color:#fcfcfc
|
||||
}
|
||||
.rst-versions .rst-current-version .fa-caret-down{
|
||||
display: none;
|
||||
}
|
||||
.rst-versions.shift-up .rst-other-versions{
|
||||
display:block
|
||||
}
|
||||
.rst-versions .rst-other-versions{
|
||||
font-size:90%;
|
||||
padding:12px;
|
||||
color:gray;
|
||||
display:none
|
||||
}
|
||||
.rst-versions .rst-other-versions hr{
|
||||
display: none !important;
|
||||
height: 0px !important;
|
||||
border: 0px;
|
||||
margin: 0px !important;
|
||||
padding: 0px;
|
||||
border-top: none !important;
|
||||
}
|
||||
.rst-versions .rst-other-versions dd{
|
||||
display:inline-block;
|
||||
margin:0
|
||||
}
|
||||
.rst-versions .rst-other-versions dd a{
|
||||
display:inline-block;
|
||||
padding: 1em 0em !important;
|
||||
color:#fcfcfc;
|
||||
font-size: .6rem !important;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 80px;
|
||||
}
|
||||
.rst-versions .rst-other-versions dd a:hover{
|
||||
font-size: .7rem !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
.rst-versions.rst-badge{
|
||||
display: block !important;
|
||||
width: 100px !important;
|
||||
bottom: 0px !important;
|
||||
right: 0px !important;
|
||||
left:auto;
|
||||
border:none;
|
||||
text-align: center !important;
|
||||
line-height: 0;
|
||||
}
|
||||
.rst-versions.rst-badge .icon-book{
|
||||
display: none;
|
||||
}
|
||||
.rst-versions.rst-badge .fa-book{
|
||||
display: none !important;
|
||||
}
|
||||
.rst-versions.rst-badge.shift-up .rst-current-version{
|
||||
text-align: left !important;
|
||||
}
|
||||
.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{
|
||||
display: none !important;
|
||||
}
|
||||
.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{
|
||||
display: none !important;
|
||||
}
|
||||
.rst-versions.rst-badge .rst-current-version{
|
||||
width: 70px !important;
|
||||
height: 2.4rem !important;
|
||||
line-height:2.4rem !important;
|
||||
padding: 0px 5px !important;
|
||||
display: inline-block !important;
|
||||
font-size: .6rem !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
@media screen and (max-width: 768px){
|
||||
.rst-versions{
|
||||
width:85%;
|
||||
display:none
|
||||
}
|
||||
.rst-versions.shift{
|
||||
display:block
|
||||
}
|
||||
}
|
||||
58
docs/assets/versions.js
Normal file
58
docs/assets/versions.js
Normal file
@@ -0,0 +1,58 @@
|
||||
setTimeout(function() {
|
||||
const callbackName = 'callback_' + new Date().getTime();
|
||||
window[callbackName] = function (response) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = response.html;
|
||||
document.querySelector(".md-header__inner > .md-header__title").appendChild(div);
|
||||
const container = div.querySelector('.rst-versions');
|
||||
var caret = document.createElement('div');
|
||||
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>"
|
||||
caret.classList.add('dropdown-caret')
|
||||
div.querySelector('.rst-current-version').appendChild(caret);
|
||||
div.querySelector('.rst-current-version').addEventListener('click', function() {
|
||||
const classes = container.className.split(' ');
|
||||
const index = classes.indexOf('shift-up');
|
||||
if (index === -1) {
|
||||
classes.push('shift-up');
|
||||
} else {
|
||||
classes.splice(index, 1);
|
||||
}
|
||||
container.className = classes.join(' ');
|
||||
});
|
||||
}
|
||||
|
||||
var CSSLink = document.createElement('link');
|
||||
CSSLink.rel='stylesheet';
|
||||
CSSLink.href = '/assets/versions.css';
|
||||
document.getElementsByTagName('head')[0].appendChild(CSSLink);
|
||||
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://argo-cd.readthedocs.io/_/api/v2/footer_html/?'+
|
||||
'callback=' + callbackName + '&project=argo-cd&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (window['READTHEDOCS_DATA'] || { version: 'latest' }).version;
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
}, 0);
|
||||
|
||||
// VERSION WARNINGS
|
||||
window.addEventListener("DOMContentLoaded", function() {
|
||||
var rtdData = window['READTHEDOCS_DATA'] || { version: 'latest' };
|
||||
var margin = 30;
|
||||
var headerHeight = document.getElementsByClassName("md-header")[0].offsetHeight;
|
||||
if (rtdData.version === "latest") {
|
||||
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for an unreleased version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>"
|
||||
var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin
|
||||
document.querySelector("header.md-header").style.top = bannerHeight +"px";
|
||||
document.querySelector('style').textContent +=
|
||||
"@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
|
||||
document.querySelector('style').textContent +=
|
||||
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
|
||||
}
|
||||
else if ((window['READTHEDOCS_DATA']).version !== "stable") {
|
||||
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for a previous version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>"
|
||||
var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin
|
||||
document.querySelector("header.md-header").style.top = bannerHeight +"px";
|
||||
document.querySelector('style').textContent +=
|
||||
"@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
|
||||
document.querySelector('style').textContent +=
|
||||
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}"
|
||||
}
|
||||
});
|
||||
@@ -28,7 +28,7 @@ Breaking down the permissions definition differs slightly between applications a
|
||||
|
||||
### RBAC Resources and Actions
|
||||
|
||||
Resources: `clusters`, `projects`, `applications`, `repositories`, `certificates`
|
||||
Resources: `clusters`, `projects`, `applications`, `repositories`, `certificates`, `accounts`, `gpgkeys`
|
||||
|
||||
Actions: `get`, `create`, `update`, `delete`, `sync`, `override`, `action`
|
||||
|
||||
|
||||
4
docs/requirements.txt
Normal file
4
docs/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
mkdocs==1.1.2
|
||||
mkdocs-material==7.1.7
|
||||
markdown_include==0.6.0
|
||||
pygments==2.7.4
|
||||
@@ -1,5 +1,11 @@
|
||||
# Security Considerations
|
||||
|
||||
!!!warning "Deprecation notice"
|
||||
This page is now deprecated and serves as an archive only. For up-to-date
|
||||
information, please have a look at our
|
||||
[security policy](https://github.com/argoproj/argo-cd/security/policy) and
|
||||
[published security advisories](https://github.com/argoproj/argo-cd/security/advisories).
|
||||
|
||||
As a deployment tool, Argo CD needs to have production access which makes security a very important topic.
|
||||
The Argoproj team takes security very seriously and continuously working on improving it. Learn more about security
|
||||
related features in [Security](./operator-manual/security.md) section.
|
||||
@@ -171,12 +177,6 @@ Upgrade to ArgoCD v1.5.0 or higher. No workaround available
|
||||
|
||||
## Reporting Vulnerabilities
|
||||
|
||||
If you find a security related bug in ArgoCD, we kindly ask you for responsible
|
||||
disclosure and for giving us appropriate time to react, analyze and develop a
|
||||
fix to mitigate the found security vulnerability.
|
||||
|
||||
Please report security vulnerabilities by e-mailing:
|
||||
|
||||
* [Jesse_Suen@intuit.com](mailto:Jesse_Suen@intuit.com)
|
||||
* [Alexander_Matyushentsev@intuit.com](mailto:Alexander_Matyushentsev@intuit.com)
|
||||
* [Edward_Lee@intuit.com](mailto:Edward_Lee@intuit.com)
|
||||
Please have a look at our
|
||||
[security policy](https://github.com/argoproj/argo-cd/security/policy)
|
||||
for more details on how to report security vulnerabilities for Argo CD.
|
||||
|
||||
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
|
||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible
|
||||
github.com/argoproj/gitops-engine v0.2.1
|
||||
github.com/argoproj/gitops-engine v0.2.2
|
||||
github.com/argoproj/pkg v0.2.0
|
||||
github.com/bombsimon/logrusr v1.0.0
|
||||
github.com/casbin/casbin v1.9.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -79,8 +79,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
|
||||
github.com/argoproj/gitops-engine v0.2.1 h1:iXmTCCM0m2u/YVLMhJatU21awuAscGiirscn13rYQtE=
|
||||
github.com/argoproj/gitops-engine v0.2.1/go.mod h1:OxXp8YaT73rw9gEBnGBWg55af80nkV/uIjWCbJu1Nw0=
|
||||
github.com/argoproj/gitops-engine v0.2.2 h1:islNwFTaHVVgx2pMvgXqINV93WPCVInWe64DlasFSx4=
|
||||
github.com/argoproj/gitops-engine v0.2.2/go.mod h1:OxXp8YaT73rw9gEBnGBWg55af80nkV/uIjWCbJu1Nw0=
|
||||
github.com/argoproj/pkg v0.2.0 h1:ETgC600kr8WcAi3MEVY5sA1H7H/u1/IysYOobwsZ8No=
|
||||
github.com/argoproj/pkg v0.2.0/go.mod h1:F4TZgInLUEjzsWFB/BTJBsewoEy0ucnKSq6vmQiD/yc=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: argoproj/argocd
|
||||
newName: argoproj/argocd
|
||||
newTag: v1.8.2
|
||||
newTag: v1.8.7
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -11,7 +11,7 @@ patchesStrategicMerge:
|
||||
images:
|
||||
- name: argoproj/argocd
|
||||
newName: argoproj/argocd
|
||||
newTag: v1.8.2
|
||||
newTag: v1.8.7
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/dex
|
||||
|
||||
@@ -2864,7 +2864,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd-util
|
||||
- /shared
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
@@ -3002,7 +3002,7 @@ spec:
|
||||
- argocd-repo-server
|
||||
- --redis
|
||||
- argocd-redis-ha-haproxy:6379
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -3084,7 +3084,7 @@ spec:
|
||||
env:
|
||||
- name: ARGOCD_API_SERVER_REPLICAS
|
||||
value: "2"
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -3160,7 +3160,7 @@ spec:
|
||||
- "10"
|
||||
- --redis
|
||||
- argocd-redis-ha-haproxy:6379
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -2779,7 +2779,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd-util
|
||||
- /shared
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
@@ -2917,7 +2917,7 @@ spec:
|
||||
- argocd-repo-server
|
||||
- --redis
|
||||
- argocd-redis-ha-haproxy:6379
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2999,7 +2999,7 @@ spec:
|
||||
env:
|
||||
- name: ARGOCD_API_SERVER_REPLICAS
|
||||
value: "2"
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -3075,7 +3075,7 @@ spec:
|
||||
- "10"
|
||||
- --redis
|
||||
- argocd-redis-ha-haproxy:6379
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -2453,7 +2453,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd-util
|
||||
- /shared
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
@@ -2553,7 +2553,7 @@ spec:
|
||||
- argocd-repo-server
|
||||
- --redis
|
||||
- argocd-redis:6379
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2631,7 +2631,7 @@ spec:
|
||||
- argocd-server
|
||||
- --staticassets
|
||||
- /shared/app
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2706,7 +2706,7 @@ spec:
|
||||
- "20"
|
||||
- --operation-processors
|
||||
- "10"
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -2368,7 +2368,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd-util
|
||||
- /shared
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
@@ -2468,7 +2468,7 @@ spec:
|
||||
- argocd-repo-server
|
||||
- --redis
|
||||
- argocd-redis:6379
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2546,7 +2546,7 @@ spec:
|
||||
- argocd-server
|
||||
- --staticassets
|
||||
- /shared/app
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2621,7 +2621,7 @@ spec:
|
||||
- "20"
|
||||
- --operation-processors
|
||||
- "10"
|
||||
image: argoproj/argocd:v1.8.2
|
||||
image: argoproj/argocd:v1.8.7
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -9,6 +9,12 @@ theme:
|
||||
text: 'Work Sans'
|
||||
logo: 'assets/logo.png'
|
||||
favicon: 'assets/favicon.png'
|
||||
# language: en-custom
|
||||
custom_dir: overrides
|
||||
extra_javascript:
|
||||
- assets/versions.js
|
||||
extra_css:
|
||||
- assets/versions.css
|
||||
google_analytics:
|
||||
- 'UA-105170809-2'
|
||||
- 'auto'
|
||||
|
||||
3
overrides/partials/language/en-custom.html
Normal file
3
overrides/partials/language/en-custom.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{% macro t(key) %}{{ {
|
||||
"toc.title": "Table of Contents"
|
||||
}[key] }}{% endmacro %}
|
||||
@@ -328,14 +328,14 @@ func (c *client) refreshAuthToken(localCfg *localconfig.LocalConfig, ctxName, co
|
||||
return err
|
||||
}
|
||||
parser := &jwt.Parser{
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation()),
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
|
||||
}
|
||||
var claims jwt.StandardClaims
|
||||
_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if claims.Valid(jwt.DefaultValidationHelper) == nil {
|
||||
if claims.Valid(parser.ValidationHelper) == nil {
|
||||
// token is still valid
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -943,6 +943,17 @@ type ApplicationTree struct {
|
||||
OrphanedNodes []ResourceNode `json:"orphanedNodes,omitempty" protobuf:"bytes,2,rep,name=orphanedNodes"`
|
||||
}
|
||||
|
||||
// Normalize sorts application tree nodes and hosts. The persistent order allows to
|
||||
// effectively compare previously cached app tree and allows to unnecessary Redis requests.
|
||||
func (t *ApplicationTree) Normalize() {
|
||||
sort.Slice(t.Nodes, func(i, j int) bool {
|
||||
return t.Nodes[i].FullName() < t.Nodes[j].FullName()
|
||||
})
|
||||
sort.Slice(t.OrphanedNodes, func(i, j int) bool {
|
||||
return t.OrphanedNodes[i].FullName() < t.OrphanedNodes[j].FullName()
|
||||
})
|
||||
}
|
||||
|
||||
type ApplicationSummary struct {
|
||||
// ExternalURLs holds all external URLs of application child resources.
|
||||
ExternalURLs []string `json:"externalURLs,omitempty" protobuf:"bytes,1,opt,name=externalURLs"`
|
||||
@@ -1011,6 +1022,11 @@ type ResourceNode struct {
|
||||
CreatedAt *metav1.Time `json:"createdAt,omitempty" protobuf:"bytes,8,opt,name=createdAt"`
|
||||
}
|
||||
|
||||
// FullName returns node full name
|
||||
func (n *ResourceNode) FullName() string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", n.Group, n.Kind, n.Namespace, n.Name)
|
||||
}
|
||||
|
||||
func (n *ResourceNode) GroupKindVersion() schema.GroupVersionKind {
|
||||
return schema.GroupVersionKind{
|
||||
Group: n.Group,
|
||||
@@ -1056,6 +1072,11 @@ type ResourceDiff struct {
|
||||
PredictedLiveState string `json:"predictedLiveState,omitempty" protobuf:"bytes,10,opt,name=predictedLiveState"`
|
||||
}
|
||||
|
||||
// FullName returns full name of a node that was used for diffing
|
||||
func (r *ResourceDiff) FullName() string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", r.Group, r.Kind, r.Namespace, r.Name)
|
||||
}
|
||||
|
||||
// ConnectionStatus represents connection status
|
||||
type ConnectionStatus = string
|
||||
|
||||
@@ -2443,13 +2464,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
|
||||
}
|
||||
|
||||
@@ -2707,8 +2734,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 {
|
||||
@@ -2731,8 +2761,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) {
|
||||
|
||||
@@ -79,7 +79,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.initConstants)
|
||||
apiclient.RegisterRepoServerServiceServer(server, manifestService)
|
||||
|
||||
|
||||
@@ -17,6 +17,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/errors"
|
||||
"github.com/argoproj/argo-cd/util/password"
|
||||
"github.com/argoproj/argo-cd/util/rbac"
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -899,6 +899,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)
|
||||
|
||||
@@ -85,7 +85,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
issuer := jwtutil.GetField(mapClaims, "iss")
|
||||
issuer := jwtutil.StringField(mapClaims, "iss")
|
||||
|
||||
if argoCDSettings.OIDCConfig() == nil || argoCDSettings.OIDCConfig().LogoutURL == "" || issuer == session.SessionManagerClaimsIssuer {
|
||||
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
|
||||
|
||||
@@ -9,18 +9,17 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/dgrijalva/jwt-go/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
|
||||
"github.com/argoproj/argo-cd/test"
|
||||
"github.com/argoproj/argo-cd/util/session"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
|
||||
"github.com/dgrijalva/jwt-go/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -177,7 +176,7 @@ func TestHandlerConstructLogoutURL(t *testing.T) {
|
||||
settingsManagerWithoutOIDCConfig := settings.NewSettingsManager(context.Background(), kubeClientWithoutOIDCConfig, "default")
|
||||
settingsManagerWithOIDCConfigButNoLogoutURL := settings.NewSettingsManager(context.Background(), kubeClientWithOIDCConfigButNoLogoutURL, "default")
|
||||
|
||||
sessionManager := session.NewSessionManager(settingsManagerWithOIDCConfig, "", session.NewInMemoryUserStateStorage())
|
||||
sessionManager := session.NewSessionManager(settingsManagerWithOIDCConfig, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
|
||||
|
||||
oidcHandler := NewHandler(appclientset.NewSimpleClientset(), settingsManagerWithOIDCConfig, sessionManager, "", "default")
|
||||
oidcHandler.verifyToken = func(tokenString string) (jwt.Claims, error) {
|
||||
|
||||
@@ -108,7 +108,7 @@ func (s *Server) CreateToken(ctx context.Context, q *project.ProjectTokenCreateR
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
parser := &jwt.Parser{
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation()),
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
|
||||
}
|
||||
claims := jwt.StandardClaims{}
|
||||
_, _, err = parser.ParseUnverified(jwtToken, &claims)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/pkg/sync"
|
||||
"github.com/dgrijalva/jwt-go/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -17,14 +18,13 @@ import (
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
k8scache "k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/dgrijalva/jwt-go/v4"
|
||||
|
||||
"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"
|
||||
informer "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/test"
|
||||
"github.com/argoproj/argo-cd/util/assets"
|
||||
jwtutil "github.com/argoproj/argo-cd/util/jwt"
|
||||
"github.com/argoproj/argo-cd/util/rbac"
|
||||
@@ -82,7 +82,7 @@ func TestProjectServer(t *testing.T) {
|
||||
}
|
||||
|
||||
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}}}
|
||||
@@ -319,7 +319,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, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
@@ -328,7 +328,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, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
@@ -339,11 +339,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, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
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, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
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)
|
||||
@@ -357,10 +359,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, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
clientset := apps.NewSimpleClientset(projectWithRole)
|
||||
|
||||
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
|
||||
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
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)
|
||||
@@ -375,10 +379,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, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
clientset := apps.NewSimpleClientset(projectWithRole)
|
||||
|
||||
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
|
||||
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
|
||||
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -400,7 +406,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
|
||||
@@ -413,7 +419,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
|
||||
@@ -429,7 +435,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
|
||||
@@ -450,7 +456,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
|
||||
@@ -473,7 +479,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}}}
|
||||
@@ -638,7 +644,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"}
|
||||
@@ -651,7 +657,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"}
|
||||
@@ -670,7 +676,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"})
|
||||
|
||||
@@ -209,8 +209,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)
|
||||
@@ -218,6 +216,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)
|
||||
@@ -562,7 +561,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)
|
||||
@@ -833,6 +841,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") {
|
||||
|
||||
@@ -362,13 +362,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) {
|
||||
|
||||
@@ -15,18 +15,26 @@ 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"
|
||||
"github.com/argoproj/argo-cd/util/log"
|
||||
sessionmgr "github.com/argoproj/argo-cd/util/session"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
type server struct {
|
||||
ksonnetVersion string
|
||||
kustomizeVersion string
|
||||
helmVersion string
|
||||
kubectlVersion string
|
||||
jsonnetVersion string
|
||||
authenticator settings.Authenticator
|
||||
disableAuth func() (bool, error)
|
||||
}
|
||||
|
||||
func NewServer(authenticator settings.Authenticator, disableAuth func() (bool, error)) *server {
|
||||
return &server{authenticator: authenticator, disableAuth: disableAuth}
|
||||
}
|
||||
|
||||
func getVersion() (string, error) {
|
||||
@@ -51,8 +59,17 @@ func getVersion() (string, error) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -104,6 +121,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
|
||||
}
|
||||
|
||||
@@ -19,6 +19,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"
|
||||
@@ -425,6 +426,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(`[
|
||||
@@ -1514,3 +1524,30 @@ definitions:
|
||||
assert.Equal(t, "update-status", text)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppWaitOperationInProgress(t *testing.T) {
|
||||
Given(t).
|
||||
And(func() {
|
||||
SetResourceOverrides(map[string]ResourceOverride{
|
||||
"batch/Job": {
|
||||
HealthLua: `return { status = 'Running' }`,
|
||||
},
|
||||
"apps/Deployment": {
|
||||
HealthLua: `return { status = 'Suspended' }`,
|
||||
},
|
||||
})
|
||||
}).
|
||||
Async(true).
|
||||
Path("hook-and-deployment").
|
||||
When().
|
||||
Create().
|
||||
Sync().
|
||||
Then().
|
||||
// stuck in running state
|
||||
Expect(OperationPhaseIs(OperationRunning)).
|
||||
When().
|
||||
And(func() {
|
||||
_, err := RunCli("app", "wait", Name(), "--suspended")
|
||||
errors.CheckError(err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
. "github.com/argoproj/argo-cd/test/e2e/fixture"
|
||||
. "github.com/argoproj/argo-cd/test/e2e/fixture/app"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
@@ -16,23 +17,30 @@ func TestFixingDegradedApp(t *testing.T) {
|
||||
When().
|
||||
IgnoreErrors().
|
||||
Create().
|
||||
PatchFile("pod-1.yaml", `[{"op": "replace", "path": "/spec/containers/0/image", "value": "rubbish"}]`).
|
||||
And(func() {
|
||||
SetResourceOverrides(map[string]ResourceOverride{
|
||||
"ConfigMap": {
|
||||
HealthLua: `return { status = obj.metadata.annotations and obj.metadata.annotations['health'] or 'Degraded' }`,
|
||||
},
|
||||
})
|
||||
}).
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
Expect(HealthIs(health.HealthStatusMissing)).
|
||||
Expect(ResourceResultNumbering(1)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeSynced)).
|
||||
Expect(ResourceHealthIs("Pod", "pod-1", health.HealthStatusDegraded)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod-2", SyncStatusCodeOutOfSync)).
|
||||
Expect(ResourceHealthIs("Pod", "pod-2", health.HealthStatusMissing)).
|
||||
Expect(ResourceSyncStatusIs("ConfigMap", "cm-1", SyncStatusCodeSynced)).
|
||||
Expect(ResourceHealthIs("ConfigMap", "cm-1", health.HealthStatusDegraded)).
|
||||
Expect(ResourceSyncStatusIs("ConfigMap", "cm-2", SyncStatusCodeOutOfSync)).
|
||||
Expect(ResourceHealthIs("ConfigMap", "cm-2", health.HealthStatusMissing)).
|
||||
When().
|
||||
PatchFile("pod-1.yaml", `[{"op": "replace", "path": "/spec/containers/0/image", "value": "nginx:1.17.4-alpine"}]`).
|
||||
PatchFile("cm-1.yaml", `[{"op": "replace", "path": "/metadata/annotations/health", "value": "Healthy"}]`).
|
||||
PatchFile("cm-2.yaml", `[{"op": "replace", "path": "/metadata/annotations/health", "value": "Healthy"}]`).
|
||||
// need to force a refresh here
|
||||
Refresh(RefreshTypeNormal).
|
||||
Then().
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync)).
|
||||
Expect(ResourceSyncStatusIs("ConfigMap", "cm-1", SyncStatusCodeOutOfSync)).
|
||||
When().
|
||||
Sync().
|
||||
Then().
|
||||
@@ -40,10 +48,10 @@ func TestFixingDegradedApp(t *testing.T) {
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
Expect(ResourceResultNumbering(2)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeSynced)).
|
||||
Expect(ResourceHealthIs("Pod", "pod-1", health.HealthStatusHealthy)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod-2", SyncStatusCodeSynced)).
|
||||
Expect(ResourceHealthIs("Pod", "pod-2", health.HealthStatusHealthy))
|
||||
Expect(ResourceSyncStatusIs("ConfigMap", "cm-1", SyncStatusCodeSynced)).
|
||||
Expect(ResourceHealthIs("ConfigMap", "cm-1", health.HealthStatusHealthy)).
|
||||
Expect(ResourceSyncStatusIs("ConfigMap", "cm-2", SyncStatusCodeSynced)).
|
||||
Expect(ResourceHealthIs("ConfigMap", "cm-2", health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
func TestOneProgressingDeploymentIsSucceededAndSynced(t *testing.T) {
|
||||
|
||||
6
test/e2e/testdata/sync-waves/cm-1.yaml
vendored
Normal file
6
test/e2e/testdata/sync-waves/cm-1.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-1
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
6
test/e2e/testdata/sync-waves/cm-2.yaml
vendored
Normal file
6
test/e2e/testdata/sync-waves/cm-2.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm-2
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
11
test/e2e/testdata/sync-waves/pod-1.yaml
vendored
11
test/e2e/testdata/sync-waves/pod-1.yaml
vendored
@@ -1,11 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-1
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: nginx:1.17.4-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
11
test/e2e/testdata/sync-waves/pod-2.yaml
vendored
11
test/e2e/testdata/sync-waves/pod-2.yaml
vendored
@@ -1,11 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-2
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: nginx:1.17.4-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
@@ -1,14 +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/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"
|
||||
)
|
||||
@@ -114,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) {})
|
||||
|
||||
7
util/cache/appstate/cache.go
vendored
7
util/cache/appstate/cache.go
vendored
@@ -3,6 +3,7 @@ package appstate
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
@@ -61,6 +62,9 @@ func (c *Cache) GetAppManagedResources(appName string, res *[]*appv1.ResourceDif
|
||||
}
|
||||
|
||||
func (c *Cache) SetAppManagedResources(appName string, managedResources []*appv1.ResourceDiff) error {
|
||||
sort.Slice(managedResources, func(i, j int) bool {
|
||||
return managedResources[i].FullName() < managedResources[j].FullName()
|
||||
})
|
||||
return c.SetItem(appManagedResourcesKey(appName), managedResources, c.appStateCacheExpiration, managedResources == nil)
|
||||
}
|
||||
|
||||
@@ -82,6 +86,9 @@ func (c *Cache) OnAppResourcesTreeChanged(ctx context.Context, appName string, c
|
||||
}
|
||||
|
||||
func (c *Cache) SetAppResourcesTree(appName string, resourcesTree *appv1.ApplicationTree) error {
|
||||
if resourcesTree != nil {
|
||||
resourcesTree.Normalize()
|
||||
}
|
||||
err := c.SetItem(appResourcesTreeKey(appName), resourcesTree, c.appStateCacheExpiration, resourcesTree == nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
8
util/cache/cache.go
vendored
8
util/cache/cache.go
vendored
@@ -78,6 +78,14 @@ type Cache struct {
|
||||
client CacheClient
|
||||
}
|
||||
|
||||
func (c *Cache) GetClient() CacheClient {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func (c *Cache) SetClient(client CacheClient) {
|
||||
c.client = client
|
||||
}
|
||||
|
||||
func (c *Cache) SetItem(key string, item interface{}, expiration time.Duration, delete bool) error {
|
||||
key = fmt.Sprintf("%s|%s", key, common.CacheVersion)
|
||||
if delete {
|
||||
|
||||
20
util/cache/inmemory.go
vendored
20
util/cache/inmemory.go
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gocache "github.com/patrickmn/go-cache"
|
||||
@@ -29,6 +30,25 @@ func (i *InMemoryCache) Set(item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasSame returns true if key with the same value already present in cache
|
||||
func (i *InMemoryCache) HasSame(key string, obj interface{}) (bool, error) {
|
||||
var buf bytes.Buffer
|
||||
err := gob.NewEncoder(&buf).Encode(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
bufIf, found := i.memCache.Get(key)
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
existingBuf, ok := bufIf.(bytes.Buffer)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("InMemoryCache has unexpected entry: %v", existingBuf))
|
||||
}
|
||||
return bytes.Equal(buf.Bytes(), existingBuf.Bytes()), nil
|
||||
}
|
||||
|
||||
func (i *InMemoryCache) Get(key string, obj interface{}) error {
|
||||
bufIf, found := i.memCache.Get(key)
|
||||
if !found {
|
||||
|
||||
68
util/cache/twolevelclient.go
vendored
Normal file
68
util/cache/twolevelclient.go
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// NewTwoLevelClient creates cache client that proxies requests to given external cache and tries to minimize
|
||||
// number of requests to external client by storing cache entries in local in-memory cache.
|
||||
func NewTwoLevelClient(client CacheClient, inMemoryExpiration time.Duration) *twoLevelClient {
|
||||
return &twoLevelClient{inMemoryCache: NewInMemoryCache(inMemoryExpiration), externalCache: client}
|
||||
}
|
||||
|
||||
type twoLevelClient struct {
|
||||
inMemoryCache *InMemoryCache
|
||||
externalCache CacheClient
|
||||
}
|
||||
|
||||
// Set stores the given value in both in-memory and external cache.
|
||||
// Skip storing the value in external cache if the same value already exists in memory to avoid requesting external cache.
|
||||
func (c *twoLevelClient) Set(item *Item) error {
|
||||
has, err := c.inMemoryCache.HasSame(item.Key, item.Object)
|
||||
if has {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Warnf("Failed to check key '%s' in in-memory cache: %v", item.Key, err)
|
||||
}
|
||||
err = c.inMemoryCache.Set(item)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to save key '%s' in in-memory cache: %v", item.Key, err)
|
||||
}
|
||||
return c.externalCache.Set(item)
|
||||
}
|
||||
|
||||
// Get returns cache value from in-memory cache if it present. Otherwise loads it from external cache and persists
|
||||
// in memory to avoid future requests to external cache.
|
||||
func (c *twoLevelClient) Get(key string, obj interface{}) error {
|
||||
err := c.inMemoryCache.Get(key, obj)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.externalCache.Get(key, obj)
|
||||
if err == nil {
|
||||
_ = c.inMemoryCache.Set(&Item{Key: key, Object: obj})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete deletes cache for given key in both in-memory and external cache.
|
||||
func (c *twoLevelClient) Delete(key string) error {
|
||||
err := c.inMemoryCache.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.externalCache.Delete(key)
|
||||
}
|
||||
|
||||
func (c *twoLevelClient) OnUpdated(ctx context.Context, key string, callback func() error) error {
|
||||
return c.externalCache.OnUpdated(ctx, key, callback)
|
||||
}
|
||||
|
||||
func (c *twoLevelClient) NotifyUpdated(key string) error {
|
||||
return c.externalCache.NotifyUpdated(key)
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func (sac *ServiceAccountClaims) Valid(helper *jwt.ValidationHelper) error {
|
||||
// ParseServiceAccountToken parses a Kubernetes service account token
|
||||
func ParseServiceAccountToken(token string) (*ServiceAccountClaims, error) {
|
||||
parser := &jwt.Parser{
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation()),
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
|
||||
}
|
||||
var claims ServiceAccountClaims
|
||||
_, _, err := parser.ParseUnverified(token, &claims)
|
||||
|
||||
@@ -34,13 +34,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.NewLoggingTracer(log.NewLogrusLogger(logrus.New())).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)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package jwt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go/v4"
|
||||
)
|
||||
@@ -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/v4"
|
||||
"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)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ type User struct {
|
||||
// Claims returns the standard claims from the JWT claims
|
||||
func (u *User) Claims() (*jwt.StandardClaims, error) {
|
||||
parser := &jwt.Parser{
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation()),
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
|
||||
}
|
||||
claims := jwt.StandardClaims{}
|
||||
_, _, err := parser.ParseUnverified(u.AuthToken, &claims)
|
||||
|
||||
@@ -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, ", "))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
@@ -18,6 +19,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"
|
||||
@@ -32,6 +34,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
|
||||
@@ -128,11 +131,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()
|
||||
@@ -187,14 +191,48 @@ func (mgr *SessionManager) Create(subject string, secondsBeforeExpiry int64, id
|
||||
return mgr.signClaims(claims)
|
||||
}
|
||||
|
||||
type standardClaims struct {
|
||||
Audience jwt.ClaimStrings `json:"aud,omitempty"`
|
||||
ExpiresAt int64 `json:"exp,omitempty"`
|
||||
ID string `json:"jti,omitempty"`
|
||||
IssuedAt int64 `json:"iat,omitempty"`
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
NotBefore int64 `json:"nbf,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
}
|
||||
|
||||
func unixTimeOrZero(t *jwt.Time) int64 {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
func (mgr *SessionManager) signClaims(claims jwt.Claims) (string, error) {
|
||||
log.Infof("Issuing claims: %v", claims)
|
||||
// log.Infof("Issuing claims: %v", claims)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
settings, err := mgr.settingsMgr.GetSettings()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token.SignedString(settings.ServerSignature)
|
||||
// workaround for https://github.com/argoproj/argo-cd/issues/5217
|
||||
// According to https://tools.ietf.org/html/rfc7519#section-4.1.6 "iat" and other time fields must contain
|
||||
// number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time.
|
||||
// The https://github.com/dgrijalva/jwt-go marshals time as non integer.
|
||||
return token.SignedString(settings.ServerSignature, jwt.WithMarshaller(func(ctx jwt.CodingContext, v interface{}) ([]byte, error) {
|
||||
if std, ok := v.(jwt.StandardClaims); ok {
|
||||
return json.Marshal(standardClaims{
|
||||
Audience: std.Audience,
|
||||
ExpiresAt: unixTimeOrZero(std.ExpiresAt),
|
||||
ID: std.ID,
|
||||
IssuedAt: unixTimeOrZero(std.IssuedAt),
|
||||
Issuer: std.Issuer,
|
||||
NotBefore: unixTimeOrZero(std.NotBefore),
|
||||
Subject: std.Subject,
|
||||
})
|
||||
}
|
||||
return json.Marshal(v)
|
||||
}))
|
||||
}
|
||||
|
||||
// Parse tries to parse the provided string and returns the token claims for local login.
|
||||
@@ -204,7 +242,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
|
||||
}
|
||||
@@ -213,14 +251,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
|
||||
}
|
||||
|
||||
@@ -229,11 +283,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")
|
||||
}
|
||||
@@ -423,7 +490,7 @@ func (mgr *SessionManager) VerifyUsernamePassword(username string, password stri
|
||||
// We choose how to verify based on the issuer.
|
||||
func (mgr *SessionManager) VerifyToken(tokenString string) (jwt.Claims, error) {
|
||||
parser := &jwt.Parser{
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation()),
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
|
||||
}
|
||||
var claims jwt.StandardClaims
|
||||
_, _, err := parser.ParseUnverified(tokenString, &claims)
|
||||
@@ -484,11 +551,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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +564,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) {
|
||||
@@ -505,16 +572,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 {
|
||||
@@ -522,7 +580,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 {
|
||||
|
||||
@@ -22,7 +22,7 @@ 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, getProjLister(), NewInMemoryUserStateStorage())
|
||||
mgr.verificationDelayNoiseEnabled = true
|
||||
mgr.sleep = func(d time.Duration) {
|
||||
sleptFor = d
|
||||
|
||||
@@ -6,28 +6,46 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go/v4"
|
||||
"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/errors"
|
||||
"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)
|
||||
|
||||
@@ -237,7 +330,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++ {
|
||||
@@ -276,7 +369,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))
|
||||
@@ -284,7 +377,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
|
||||
@@ -300,7 +393,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