mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-01 14:08:46 +01:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a48bca03c7 | ||
|
|
6930ceb414 | ||
|
|
01b7a73922 | ||
|
|
ff2d9b918d | ||
|
|
cea91ce935 | ||
|
|
51b73096f5 | ||
|
|
2c166dac97 | ||
|
|
16c015f0cb | ||
|
|
7ca60cb957 | ||
|
|
d0c80ee0bb | ||
|
|
9f1a33865a | ||
|
|
f09c84f30a | ||
|
|
f7f7493e9f | ||
|
|
c1ddb53d29 | ||
|
|
e04288482d | ||
|
|
1f3e1ec803 | ||
|
|
c3423e8df3 | ||
|
|
5ef48c1123 | ||
|
|
e3ae286e2d | ||
|
|
7abf6713b4 | ||
|
|
0232073ccf | ||
|
|
860d8fd7e1 | ||
|
|
147ff80543 | ||
|
|
3800a1e49d |
@@ -68,7 +68,8 @@ argocd login cd.argoproj.io --core`,
|
||||
server = "kubernetes"
|
||||
} else {
|
||||
server = args[0]
|
||||
tlsTestResult, err := grpc_util.TestTLS(server)
|
||||
dialTime := 30 * time.Second
|
||||
tlsTestResult, err := grpc_util.TestTLS(server, dialTime)
|
||||
errors.CheckError(err)
|
||||
if !tlsTestResult.TLS {
|
||||
if !globalClientOpts.PlainText {
|
||||
|
||||
@@ -2,12 +2,13 @@ package cmpserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/server/version"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
// ArgoCDCMPServer is the config management plugin server implementation
|
||||
@@ -61,6 +63,11 @@ func NewServer(initConstants plugin.CMPServerInitConstants) (*ArgoCDCMPServer, e
|
||||
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)),
|
||||
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
grpc.KeepaliveEnforcementPolicy(
|
||||
keepalive.EnforcementPolicy{
|
||||
MinTime: common.GRPCKeepAliveEnforcementMinimum,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
return &ArgoCDCMPServer{
|
||||
|
||||
@@ -286,3 +286,10 @@ const (
|
||||
// AnnotationApplicationRefresh is an annotation that is added when an ApplicationSet is requested to be refreshed by a webhook. The ApplicationSet controller will remove this annotation at the end of reconcilation.
|
||||
AnnotationApplicationSetRefresh = "argocd.argoproj.io/application-set-refresh"
|
||||
)
|
||||
|
||||
// gRPC settings
|
||||
const (
|
||||
GRPCKeepAliveEnforcementMinimum = 10 * time.Second
|
||||
// Keep alive is 2x enforcement minimum to ensure network jitter does not introduce ENHANCE_YOUR_CALM errors
|
||||
GRPCKeepAliveTime = 2 * GRPCKeepAliveEnforcementMinimum
|
||||
)
|
||||
|
||||
12
controller/cache/cache.go
vendored
12
controller/cache/cache.go
vendored
@@ -176,6 +176,7 @@ func NewLiveStateCache(
|
||||
type cacheSettings struct {
|
||||
clusterSettings clustercache.Settings
|
||||
appInstanceLabelKey string
|
||||
trackingMethod appv1.TrackingMethod
|
||||
}
|
||||
|
||||
type liveStateCache struct {
|
||||
@@ -210,7 +211,7 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
|
||||
ResourceHealthOverride: lua.ResourceHealthOverrides(resourceOverrides),
|
||||
ResourcesFilter: resourcesFilter,
|
||||
}
|
||||
return &cacheSettings{clusterSettings, appInstanceLabelKey}, nil
|
||||
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr)}, nil
|
||||
}
|
||||
|
||||
func asResourceNode(r *clustercache.Resource) appv1.ResourceNode {
|
||||
@@ -354,7 +355,8 @@ func isTransientNetworkErr(err error) bool {
|
||||
}
|
||||
if strings.Contains(errorString, "net/http: TLS handshake timeout") ||
|
||||
strings.Contains(errorString, "i/o timeout") ||
|
||||
strings.Contains(errorString, "connection timed out") {
|
||||
strings.Contains(errorString, "connection timed out") ||
|
||||
strings.Contains(errorString, "connection reset by peer") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -387,7 +389,6 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server)
|
||||
}
|
||||
|
||||
trackingMethod := argo.GetTrackingMethod(c.settingsMgr)
|
||||
clusterCacheOpts := []clustercache.UpdateSettingsFunc{
|
||||
clustercache.SetListSemaphore(semaphore.NewWeighted(clusterCacheListSemaphoreSize)),
|
||||
clustercache.SetListPageSize(clusterCacheListPageSize),
|
||||
@@ -400,9 +401,12 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
clustercache.SetPopulateResourceInfoHandler(func(un *unstructured.Unstructured, isRoot bool) (interface{}, bool) {
|
||||
res := &ResourceInfo{}
|
||||
populateNodeInfo(un, res)
|
||||
c.lock.RLock()
|
||||
cacheSettings := c.cacheSettings
|
||||
c.lock.RUnlock()
|
||||
res.Health, _ = health.GetResourceHealth(un, cacheSettings.clusterSettings.ResourceHealthOverride)
|
||||
|
||||
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, trackingMethod)
|
||||
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod)
|
||||
if isRoot && appName != "" {
|
||||
res.AppName = appName
|
||||
}
|
||||
|
||||
4
controller/cache/cache_test.go
vendored
4
controller/cache/cache_test.go
vendored
@@ -111,6 +111,7 @@ func TestIsRetryableError(t *testing.T) {
|
||||
tlsHandshakeTimeoutErr net.Error = netError("net/http: TLS handshake timeout")
|
||||
ioTimeoutErr net.Error = netError("i/o timeout")
|
||||
connectionTimedout net.Error = netError("connection timed out")
|
||||
connectionReset net.Error = netError("connection reset by peer")
|
||||
)
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
assert.False(t, isRetryableError(nil))
|
||||
@@ -148,4 +149,7 @@ func TestIsRetryableError(t *testing.T) {
|
||||
t.Run("ConnectionTimeout", func(t *testing.T) {
|
||||
assert.True(t, isRetryableError(connectionTimedout))
|
||||
})
|
||||
t.Run("ConnectionReset", func(t *testing.T) {
|
||||
assert.True(t, isRetryableError(connectionReset))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -494,6 +494,8 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
}
|
||||
gvk := obj.GroupVersionKind()
|
||||
|
||||
isSelfReferencedObj := m.isSelfReferencedObj(liveObj, appLabelKey, trackingMethod)
|
||||
|
||||
resState := v1alpha1.ResourceStatus{
|
||||
Namespace: obj.GetNamespace(),
|
||||
Name: obj.GetName(),
|
||||
@@ -501,7 +503,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
Version: gvk.Version,
|
||||
Group: gvk.Group,
|
||||
Hook: hookutil.IsHook(obj),
|
||||
RequiresPruning: targetObj == nil && liveObj != nil,
|
||||
RequiresPruning: targetObj == nil && liveObj != nil && isSelfReferencedObj,
|
||||
}
|
||||
|
||||
var diffResult diff.DiffResult
|
||||
@@ -510,8 +512,11 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
} else {
|
||||
diffResult = diff.DiffResult{Modified: false, NormalizedLive: []byte("{}"), PredictedLive: []byte("{}")}
|
||||
}
|
||||
if resState.Hook || ignore.Ignore(obj) || (targetObj != nil && hookutil.Skip(targetObj)) {
|
||||
// For resource hooks or skipped resources, don't store sync status, and do not affect overall sync status
|
||||
if resState.Hook || ignore.Ignore(obj) || (targetObj != nil && hookutil.Skip(targetObj)) || !isSelfReferencedObj {
|
||||
// For resource hooks, skipped resources or objects that may have
|
||||
// been created by another controller with annotations copied from
|
||||
// the source object, don't store sync status, and do not affect
|
||||
// overall sync status
|
||||
} else if diffResult.Modified || targetObj == nil || liveObj == nil {
|
||||
// Set resource state to OutOfSync since one of the following is true:
|
||||
// * target and live resource are different
|
||||
@@ -667,3 +672,33 @@ func NewAppStateManager(
|
||||
resourceTracking: resourceTracking,
|
||||
}
|
||||
}
|
||||
|
||||
// isSelfReferencedObj returns whether the given obj is managed by the application
|
||||
// according to the values in the tracking annotation. It returns true when all
|
||||
// of the properties in the annotation (name, namespace, group and kind) match
|
||||
// the properties of the inspected object, or if the tracking method used does
|
||||
// not provide the required properties for matching.
|
||||
func (m *appStateManager) isSelfReferencedObj(obj *unstructured.Unstructured, appLabelKey string, trackingMethod v1alpha1.TrackingMethod) bool {
|
||||
if obj == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// If tracking method doesn't contain required metadata for this check,
|
||||
// we are not able to determine and just assume the object to be managed.
|
||||
if trackingMethod == argo.TrackingMethodLabel {
|
||||
return true
|
||||
}
|
||||
|
||||
// In order for us to assume obj to be managed by this application, the
|
||||
// values from the annotation have to match the properties from the live
|
||||
// object.
|
||||
appInstance := m.resourceTracking.GetAppInstance(obj, appLabelKey, trackingMethod)
|
||||
if appInstance != nil {
|
||||
return obj.GetNamespace() == appInstance.Namespace &&
|
||||
obj.GetName() == appInstance.Name &&
|
||||
obj.GetObjectKind().GroupVersionKind().Group == appInstance.Group &&
|
||||
obj.GetObjectKind().GroupVersionKind().Kind == appInstance.Kind
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
. "github.com/argoproj/gitops-engine/pkg/utils/testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "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/runtime"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
)
|
||||
|
||||
// TestCompareAppStateEmpty tests comparison when both git and live have no objects
|
||||
@@ -770,3 +772,128 @@ func TestComparisonResult_GetSyncStatus(t *testing.T) {
|
||||
|
||||
assert.Equal(t, status, res.GetSyncStatus())
|
||||
}
|
||||
|
||||
func TestIsLiveResourceManaged(t *testing.T) {
|
||||
managedObj := kube.MustToUnstructured(&corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap1",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:default/configmap1",
|
||||
},
|
||||
},
|
||||
})
|
||||
managedObjWithLabel := kube.MustToUnstructured(&corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
common.LabelKeyAppInstance: "guestbook",
|
||||
},
|
||||
},
|
||||
})
|
||||
unmanagedObjWrongName := kube.MustToUnstructured(&corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:default/configmap1",
|
||||
},
|
||||
},
|
||||
})
|
||||
unmanagedObjWrongKind := kube.MustToUnstructured(&corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: "guestbook:/Service:default/configmap2",
|
||||
},
|
||||
},
|
||||
})
|
||||
unmanagedObjWrongGroup := kube.MustToUnstructured(&corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: "guestbook:apps/ConfigMap:default/configmap2",
|
||||
},
|
||||
},
|
||||
})
|
||||
unmanagedObjWrongNamespace := kube.MustToUnstructured(&corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:fakens/configmap2",
|
||||
},
|
||||
},
|
||||
})
|
||||
ctrl := newFakeController(&fakeData{
|
||||
apps: []runtime.Object{app, &defaultProj},
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: test.FakeClusterURL,
|
||||
Revision: "abc123",
|
||||
},
|
||||
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
|
||||
kube.GetResourceKey(managedObj): managedObj,
|
||||
kube.GetResourceKey(unmanagedObjWrongName): unmanagedObjWrongName,
|
||||
kube.GetResourceKey(unmanagedObjWrongKind): unmanagedObjWrongKind,
|
||||
kube.GetResourceKey(unmanagedObjWrongGroup): unmanagedObjWrongGroup,
|
||||
kube.GetResourceKey(unmanagedObjWrongNamespace): unmanagedObjWrongNamespace,
|
||||
},
|
||||
})
|
||||
|
||||
manager := ctrl.appStateManager.(*appStateManager)
|
||||
|
||||
// Managed resource w/ annotations
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObj, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObj, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
|
||||
// Managed resource w/ label
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObjWithLabel, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
|
||||
// Wrong resource name
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
|
||||
// Wrong resource group
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
|
||||
// Wrong resource kind
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
|
||||
// Wrong resource namespace
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotationAndLabel))
|
||||
|
||||
// Nil resource
|
||||
assert.True(t, manager.isSelfReferencedObj(nil, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
}
|
||||
|
||||
BIN
docs/assets/terminal.png
Normal file
BIN
docs/assets/terminal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
@@ -174,7 +174,9 @@ argocd ... --grpc-web
|
||||
|
||||
## Why Am I Getting `x509: certificate signed by unknown authority` When Using The CLI?
|
||||
|
||||
Your not running your server with correct certs.
|
||||
The certificate created by default by Argo CD is not automatically recognised by the Argo CD CLI, in order
|
||||
to create a secure system you must follow the instructions to [install a certificate](/operator-manual/tls/)
|
||||
and configure your client OS to trust that certificate.
|
||||
|
||||
If you're not running in a production system (e.g. you're testing Argo CD out), try the `--insecure` flag:
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ kubectl create namespace argocd
|
||||
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml
|
||||
```
|
||||
|
||||
This default installation will have a self-signed certificate and cannot be accessed without a bit of extra work.
|
||||
Do one of:
|
||||
|
||||
* Follow the [instructions to configure a certificate](./operator-manual/tls) (and ensure that the client OS trusts it).
|
||||
* Configure the client OS to trust the self signed certificate.
|
||||
* Use the --insecure flag on all Argo CD CLI operations in this guide.
|
||||
|
||||
Use `argocd login --core` to [configure](./user-guide/commands/argocd_login.md) CLI access and skip steps 3-5.
|
||||
|
||||
## 2. Download Argo CD CLI
|
||||
|
||||
@@ -107,7 +107,7 @@ spec:
|
||||
```
|
||||
(*The full example can be found [here](https://github.com/argoproj/argo-cd/tree/master/examples/applicationset/git-generator-directory/excludes).*)
|
||||
|
||||
This example excludes the `exclude-helm-guestbook` directory from the list of directories scanned for this `ApplictionSet` resource.
|
||||
This example excludes the `exclude-helm-guestbook` directory from the list of directories scanned for this `ApplicationSet` resource.
|
||||
|
||||
!!! note "Exclude rules have higher priority than include rules"
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ data:
|
||||
# The URLs to download additional ArgoCD binaries (besides the Linux amd64 binary included by default)
|
||||
# for different OS architectures. If provided, additional download buttons will be displayed on the help page.
|
||||
help.download.linux-arm64: "path-or-url-to-download"
|
||||
help.download.linux-ppc64le: "path-or-url-to-download"
|
||||
help.download.linux-s390x: "path-or-url-to-download"
|
||||
help.download.darwin-amd64: "path-or-url-to-download"
|
||||
help.download.darwin-arm64: "path-or-url-to-download"
|
||||
help.download.windows-amd64: "path-or-url-to-download"
|
||||
@@ -231,6 +233,14 @@ data:
|
||||
# If omitted, Argo CD injects the app name into the label: 'app.kubernetes.io/instance'
|
||||
application.instanceLabelKey: mycompany.com/appname
|
||||
|
||||
# You can change the resource tracking method Argo CD uses by changing the
|
||||
# setting application.resourceTrackingMethod to the desired method.
|
||||
# The following methods are available:
|
||||
# - label : Uses the application.instanceLabelKey label for tracking
|
||||
# - annotation : Uses an annotation with additional metadata for tracking instead of the label
|
||||
# - annotation+label : Also uses an annotation for tracking, but additionally labels the resource with the application name
|
||||
application.resourceTrackingMethod: annotation
|
||||
|
||||
# disables admin user. Admin is enabled by default
|
||||
admin.enabled: "false"
|
||||
# add an additional local user with apiKey and login capabilities
|
||||
@@ -288,3 +298,12 @@ data:
|
||||
|
||||
# exec.enabled indicates whether the UI exec feature is enabled. It is disabled by default.
|
||||
exec.enabled: "false"
|
||||
|
||||
# exec.shells restricts which shells are allowed for `exec`, and in which order they are attempted
|
||||
exec.shells: "bash,sh,powershell,cmd"
|
||||
|
||||
# oidc.tls.insecure.skip.verify determines whether certificate verification is skipped when verifying tokens with the
|
||||
# configured OIDC provider (either external or the bundled Dex instance). Setting this to "true" will cause JWT
|
||||
# token verification to pass despite the OIDC provider having an invalid certificate. Only set to "true" if you
|
||||
# understand the risks.
|
||||
oidc.tls.insecure.skip.verify: "false"
|
||||
|
||||
@@ -59,22 +59,7 @@ also use glob patterns in the action path: `action/*` (or regex patterns if you
|
||||
`exec` is a special resource. When enabled with the `create` action, this privilege allows a user to `exec` into Pods via
|
||||
the Argo CD UI. The functionality is similar to `kubectl exec`.
|
||||
|
||||
`exec` is a powerful privilege. It allows the user to run arbitrary code on any Pod managed by an Application for which
|
||||
they have `create` privileges. If the Pod mounts a ServiceAccount token (which is the default behavior of Kubernetes),
|
||||
then the user effectively has the same privileges as that ServiceAccount.
|
||||
|
||||
The exec feature is disabled entirely by default. To enable it, set the `exec.enabled` key to "true" on the argocd-cm
|
||||
ConfigMap. You will also need to add the following to the argocd-api-server Role (if you're using Argo CD in namespaced
|
||||
mode) or ClusterRole (if you're using Argo CD in cluster mode).
|
||||
|
||||
```yaml
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/exec
|
||||
verbs:
|
||||
- create
|
||||
```
|
||||
See [Web-based Terminal](web_based_terminal.md) for more info.
|
||||
|
||||
## Tying It All Together
|
||||
|
||||
|
||||
@@ -496,3 +496,20 @@ data:
|
||||
clientSecret: $another-secret:oidc.auth0.clientSecret # Mind the ':'
|
||||
...
|
||||
```
|
||||
|
||||
### Skipping certificate verification on OIDC provider connections
|
||||
|
||||
By default, all connections made by the API server to OIDC providers (either external providers or the bundled Dex
|
||||
instance) must pass certificate validation. These connections occur when getting the OIDC provider's well-known
|
||||
configuration, when getting the OIDC provider's keys, and when exchanging an authorization code or verifying an ID
|
||||
token as part of an OIDC login flow.
|
||||
|
||||
Disabling certificate verification might make sense if:
|
||||
* You are using the bundled Dex instance **and** your Argo CD instance has TLS configured with a self-signed certificate
|
||||
**and** you understand and accept the risks of skipping OIDC provider cert verification.
|
||||
* You are using an external OIDC provider **and** that provider uses an invalid certificate **and** you cannot solve
|
||||
the problem by setting `oidcConfig.rootCA` **and** you understand and accept the risks of skipping OIDC provider cert
|
||||
verification.
|
||||
|
||||
If either of those two applies, then you can disable OIDC provider certificate verification by setting
|
||||
`oidc.tls.insecure.skip.verify` to `"true"` in the `argocd-cm` ConfigMap.
|
||||
|
||||
45
docs/operator-manual/web_based_terminal.md
Normal file
45
docs/operator-manual/web_based_terminal.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Web-based Terminal
|
||||
|
||||

|
||||
|
||||
Since v2.4, Argo CD has a web-based terminal that allows you to get a shell inside a running pod just like you would with
|
||||
`kubectl exec`. It's basically SSH from your browser, full ANSI color support and all! However, for security this feature
|
||||
is disabled by default.
|
||||
|
||||
This is a powerful privilege. It allows the user to run arbitrary code on any Pod managed by an Application for which
|
||||
they have the `exec/create` privilege. If the Pod mounts a ServiceAccount token (which is the default behavior of
|
||||
Kubernetes), then the user effectively has the same privileges as that ServiceAccount.
|
||||
|
||||
## Enabling the terminal
|
||||
|
||||
1. Set the `exec.enabled` key to `true` on the `argocd-cm` ConfigMap.
|
||||
|
||||
2. Patch the `argocd-server` Role (if using namespaced Argo) or ClusterRole (if using clustered Argo) to allow `argocd-server`
|
||||
to exec into pods
|
||||
```yaml
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/exec
|
||||
verbs:
|
||||
- create
|
||||
```
|
||||
|
||||
3. Add RBAC rules to allow your users to `create` the `exec` resource, i.e.
|
||||
```
|
||||
p, role:myrole, exec, create, */*, allow
|
||||
```
|
||||
|
||||
See [RBAC Configuration](rbac.md#exec-resource) for more info.
|
||||
|
||||
## Changing allowed shells
|
||||
|
||||
By default, Argo CD attempts to execute shells in this order:
|
||||
|
||||
1. bash
|
||||
2. sh
|
||||
3. powershell
|
||||
4. cmd
|
||||
|
||||
If none of the shells are found, the terminal session will fail. To add to or change the allowed shells, change the
|
||||
`exec.shells` key in the `argocd-cm` ConfigMap, separating them with commas.
|
||||
@@ -235,16 +235,17 @@ If you don't need to set any environment variables, you can set an empty plugin
|
||||
is 90s. So if you increase the repo server timeout greater than 90s, be sure to set `ARGOCD_EXEC_TIMEOUT` on the
|
||||
sidecar.
|
||||
|
||||
## Tarball stream filtering
|
||||
## Plugin tar stream exclusions
|
||||
|
||||
In order to increase the speed of manifest generation, certain files and folders can be excluded from being sent to your
|
||||
plugin. We recommend excluding your `.git` folder if it isn't necessary. Use Go's
|
||||
[filepatch.Match](https://pkg.go.dev/path/filepath#Match) syntax.
|
||||
|
||||
You can set it one of three ways:
|
||||
|
||||
1. The `--plugin-tar-exclude` argument on the repo server.
|
||||
2. The `reposerver.plugin.tar.exclusions` key if you are using `argocd-cmd-params-cm`
|
||||
3. Directly setting 'ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS' environment variable on the repo server.
|
||||
3. Directly setting `ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS` environment variable on the repo server.
|
||||
|
||||
For option 1, the flag can be repeated multiple times. For option 2 and 3, you can specify multiple globs by separating
|
||||
them with semicolons.
|
||||
@@ -85,3 +85,22 @@ argocd app set <appName> --kustomize-version v3.5.4
|
||||
## Build Environment
|
||||
|
||||
Kustomize does not support parameters and therefore cannot support the standard [build environment](build-environment.md).
|
||||
|
||||
## Kustomizing Helm charts
|
||||
|
||||
It's possible to [render Helm charts with Kustomize](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/chart.md).
|
||||
Doing so requires that you pass the `--enable-helm` flag to the `kustomize build` command.
|
||||
This flag is not part of the Kustomize options within Argo CD.
|
||||
If you would like to render Helm charts through Kustomize in an Argo CD application, you have two options:
|
||||
You can either create a [custom plugin](https://argo-cd.readthedocs.io/en/stable/user-guide/config-management-plugins/), or modify the `argocd-cm` ConfigMap to include the `--enable-helm` flag globally for all Kustomize applications:
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
namespace: argocd
|
||||
data:
|
||||
kustomize.buildOptions: --enable-helm
|
||||
```
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.4.4
|
||||
newTag: v2.4.6
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -9385,7 +9385,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -9615,7 +9615,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -9664,7 +9664,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -9851,7 +9851,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.4.4
|
||||
newTag: v2.4.6
|
||||
|
||||
@@ -11,7 +11,7 @@ patchesStrategicMerge:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.4.4
|
||||
newTag: v2.4.6
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -10320,7 +10320,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -10417,7 +10417,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -10457,7 +10457,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -10714,7 +10714,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -10763,7 +10763,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -11010,7 +11010,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -11218,7 +11218,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -1244,7 +1244,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1341,7 +1341,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1381,7 +1381,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1638,7 +1638,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1687,7 +1687,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1934,7 +1934,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2142,7 +2142,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -9692,7 +9692,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -9789,7 +9789,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -9829,7 +9829,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -10054,7 +10054,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -10103,7 +10103,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -10346,7 +10346,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -10548,7 +10548,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -616,7 +616,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -713,7 +713,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -753,7 +753,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -978,7 +978,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1027,7 +1027,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1270,7 +1270,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1472,7 +1472,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.4
|
||||
image: quay.io/argoproj/argocd:v2.4.6
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -44,6 +44,7 @@ nav:
|
||||
- operator-manual/custom_tools.md
|
||||
- operator-manual/custom-styles.md
|
||||
- operator-manual/metrics.md
|
||||
- operator-manual/web_based_terminal.md
|
||||
- Notification:
|
||||
- Overview: operator-manual/notifications/index.md
|
||||
- operator-manual/notifications/triggers.md
|
||||
|
||||
@@ -3,10 +3,11 @@ package reposerver
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
@@ -86,6 +88,11 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach
|
||||
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)),
|
||||
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
grpc.KeepaliveEnforcementPolicy(
|
||||
keepalive.EnforcementPolicy{
|
||||
MinTime: common.GRPCKeepAliveEnforcementMinimum,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
// We do allow for non-TLS servers to be created, in case of mTLS will be
|
||||
|
||||
@@ -33,17 +33,19 @@ type terminalHandler struct {
|
||||
enf *rbac.Enforcer
|
||||
cache *servercache.Cache
|
||||
appResourceTreeFn func(ctx context.Context, app *appv1.Application) (*appv1.ApplicationTree, error)
|
||||
allowedShells []string
|
||||
}
|
||||
|
||||
// NewHandler returns a new terminal handler.
|
||||
func NewHandler(appLister applisters.ApplicationNamespaceLister, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
|
||||
appResourceTree AppResourceTreeFn) *terminalHandler {
|
||||
appResourceTree AppResourceTreeFn, allowedShells []string) *terminalHandler {
|
||||
return &terminalHandler{
|
||||
appLister: appLister,
|
||||
db: db,
|
||||
enf: enf,
|
||||
cache: cache,
|
||||
appResourceTreeFn: appResourceTree,
|
||||
allowedShells: allowedShells,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +125,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Namespace name is not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
shell := q.Get("shell") // No need to validate. Will only buse used if it's in the allow-list.
|
||||
shell := q.Get("shell") // No need to validate. Will only be used if it's in the allow-list.
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
@@ -216,14 +218,12 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer session.Done()
|
||||
|
||||
validShells := []string{"bash", "sh", "powershell", "cmd"}
|
||||
if isValidShell(validShells, shell) {
|
||||
if isValidShell(s.allowedShells, shell) {
|
||||
cmd := []string{shell}
|
||||
err = startProcess(kubeClientset, config, namespace, podName, container, cmd, session)
|
||||
} else {
|
||||
// No shell given or it was not valid: try some shells until one succeeds or all fail
|
||||
// FIXME: if the first shell fails then the first keyboard event is lost
|
||||
for _, testShell := range validShells {
|
||||
// No shell given or the given shell was not allowed: try the configured shells until one succeeds or all fail.
|
||||
for _, testShell := range s.allowedShells {
|
||||
cmd := []string{testShell}
|
||||
if err = startProcess(kubeClientset, config, namespace, podName, container, cmd, session); err == nil {
|
||||
break
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -26,6 +27,7 @@ type terminalSession struct {
|
||||
sizeChan chan remotecommand.TerminalSize
|
||||
doneChan chan struct{}
|
||||
tty bool
|
||||
readLock sync.Mutex
|
||||
}
|
||||
|
||||
// newTerminalSession create terminalSession
|
||||
@@ -60,7 +62,9 @@ func (t *terminalSession) Next() *remotecommand.TerminalSize {
|
||||
|
||||
// Read called in a loop from remotecommand as long as the process is running
|
||||
func (t *terminalSession) Read(p []byte) (int, error) {
|
||||
t.readLock.Lock()
|
||||
_, message, err := t.wsConn.ReadMessage()
|
||||
t.readLock.Unlock()
|
||||
if err != nil {
|
||||
log.Errorf("read message err: %v", err)
|
||||
return copy(p, EndOfTransmission), err
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
// nolint:staticcheck
|
||||
golang_proto "github.com/golang/protobuf/proto"
|
||||
|
||||
netCtx "context"
|
||||
|
||||
"github.com/argoproj/pkg/sync"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
@@ -35,11 +37,11 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/soheilhy/cmux"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
netCtx "golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -607,6 +609,11 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
|
||||
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
grpc.ConnectionTimeout(300 * time.Second),
|
||||
grpc.KeepaliveEnforcementPolicy(
|
||||
keepalive.EnforcementPolicy{
|
||||
MinTime: common.GRPCKeepAliveEnforcementMinimum,
|
||||
},
|
||||
),
|
||||
}
|
||||
sensitiveMethods := map[string]bool{
|
||||
"/cluster.ClusterService/Create": true,
|
||||
@@ -801,7 +808,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
}
|
||||
mux.Handle("/api/", handler)
|
||||
|
||||
terminalHandler := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn)
|
||||
terminalHandler := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells)
|
||||
mux.HandleFunc("/terminal", func(writer http.ResponseWriter, request *http.Request) {
|
||||
argocdSettings, err := a.settingsMgr.GetSettings()
|
||||
if err != nil {
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
projectFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/project"
|
||||
repoFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/repos"
|
||||
"github.com/argoproj/argo-cd/v2/test/e2e/testdata"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
. "github.com/argoproj/argo-cd/v2/util/argo"
|
||||
. "github.com/argoproj/argo-cd/v2/util/errors"
|
||||
"github.com/argoproj/argo-cd/v2/util/io"
|
||||
@@ -947,7 +948,7 @@ func TestLocalManifestSync(t *testing.T) {
|
||||
And(func(app *Application) {
|
||||
res, _ := RunCli("app", "manifests", app.Name)
|
||||
assert.Contains(t, res, "containerPort: 80")
|
||||
assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.2")
|
||||
assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2")
|
||||
}).
|
||||
Given().
|
||||
LocalPath(guestbookPathLocal).
|
||||
@@ -958,7 +959,7 @@ func TestLocalManifestSync(t *testing.T) {
|
||||
And(func(app *Application) {
|
||||
res, _ := RunCli("app", "manifests", app.Name)
|
||||
assert.Contains(t, res, "containerPort: 81")
|
||||
assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.3")
|
||||
assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.3")
|
||||
}).
|
||||
Given().
|
||||
LocalPath("").
|
||||
@@ -969,7 +970,7 @@ func TestLocalManifestSync(t *testing.T) {
|
||||
And(func(app *Application) {
|
||||
res, _ := RunCli("app", "manifests", app.Name)
|
||||
assert.Contains(t, res, "containerPort: 80")
|
||||
assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.2")
|
||||
assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1190,7 +1191,8 @@ func TestPermissionWithScopedRepo(t *testing.T) {
|
||||
Name(projName).
|
||||
Destination("*,*").
|
||||
When().
|
||||
Create()
|
||||
Create().
|
||||
AddSource("*")
|
||||
|
||||
repoFixture.Given(t, true).
|
||||
When().
|
||||
@@ -2063,3 +2065,241 @@ func TestDisableManifestGeneration(t *testing.T) {
|
||||
assert.Equal(t, app.Status.SourceType, ApplicationSourceTypeDirectory)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSwitchTrackingMethod(t *testing.T) {
|
||||
ctx := Given(t)
|
||||
|
||||
ctx.
|
||||
SetTrackingMethod(string(argo.TrackingMethodAnnotation)).
|
||||
Path("deployment").
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
Refresh(RefreshTypeNormal).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Add resource with tracking annotation. This should put the
|
||||
// application OutOfSync.
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-configmap",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Delete resource to bring application back in sync
|
||||
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
SetTrackingMethod(string(argo.TrackingMethodLabel)).
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Add a resource with a tracking annotation. This should not
|
||||
// affect the application, because we now use the tracking method
|
||||
// "label".
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-configmap",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Add a resource with the tracking label. The app should become
|
||||
// OutOfSync.
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "extra-configmap",
|
||||
Labels: map[string]string{
|
||||
common.LabelKeyAppInstance: Name(),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Delete resource to bring application back in sync
|
||||
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "extra-configmap", metav1.DeleteOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
func TestSwitchTrackingLabel(t *testing.T) {
|
||||
ctx := Given(t)
|
||||
|
||||
ctx.
|
||||
Path("deployment").
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
Refresh(RefreshTypeNormal).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Add extra resource that carries the default tracking label
|
||||
// We expect the app to go out of sync.
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-configmap",
|
||||
Labels: map[string]string{
|
||||
common.LabelKeyAppInstance: Name(),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Delete resource to bring application back in sync
|
||||
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
// Change tracking label
|
||||
SetTrackingLabel("argocd.tracking").
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Create resource with the new tracking label, the application
|
||||
// is expected to go out of sync
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-configmap",
|
||||
Labels: map[string]string{
|
||||
"argocd.tracking": Name(),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Delete resource to bring application back in sync
|
||||
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Add extra resource that carries the default tracking label
|
||||
// We expect the app to stay in sync, because the configured
|
||||
// label is different.
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-configmap",
|
||||
Labels: map[string]string{
|
||||
common.LabelKeyAppInstance: Name(),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
func TestAnnotationTrackingExtraResources(t *testing.T) {
|
||||
ctx := Given(t)
|
||||
|
||||
SetTrackingMethod(string(argo.TrackingMethodAnnotation))
|
||||
ctx.
|
||||
Path("deployment").
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
Refresh(RefreshTypeNormal).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Add a resource with an annotation that is not referencing the
|
||||
// resource.
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "extra-configmap",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:apps/Deployment:%s/guestbook-cm", Name(), DeploymentNamespace()),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Refresh(RefreshTypeNormal).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
When().
|
||||
And(func() {
|
||||
// Add a resource with an annotation that is self-referencing the
|
||||
// resource.
|
||||
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-configmap",
|
||||
Annotations: map[string]string{
|
||||
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{}))
|
||||
}).
|
||||
Refresh(RefreshTypeNormal).
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
@@ -43,7 +43,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
|
||||
@@ -341,3 +341,13 @@ func (a *Actions) verifyAction() {
|
||||
a.Then().Expect(Success(""))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Actions) SetTrackingMethod(trackingMethod string) *Actions {
|
||||
fixture.SetTrackingMethod(trackingMethod)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Actions) SetTrackingLabel(trackingLabel string) *Actions {
|
||||
fixture.SetTrackingLabel(trackingLabel)
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -308,3 +308,8 @@ func (c *Context) HelmSkipCrds() *Context {
|
||||
c.helmSkipCrds = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) SetTrackingMethod(trackingMethod string) *Context {
|
||||
fixture.SetTrackingMethod(trackingMethod)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -160,7 +160,8 @@ func init() {
|
||||
adminUsername = GetEnvWithDefault(EnvAdminUsername, defaultAdminUsername)
|
||||
AdminPassword = GetEnvWithDefault(EnvAdminPassword, defaultAdminPassword)
|
||||
|
||||
tlsTestResult, err := grpcutil.TestTLS(apiServerAddress)
|
||||
dialTime := 30 * time.Second
|
||||
tlsTestResult, err := grpcutil.TestTLS(apiServerAddress, dialTime)
|
||||
CheckError(err)
|
||||
|
||||
ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{Insecure: true, ServerAddr: apiServerAddress, PlainText: !tlsTestResult.TLS})
|
||||
@@ -357,6 +358,13 @@ func SetTrackingMethod(trackingMethod string) {
|
||||
})
|
||||
}
|
||||
|
||||
func SetTrackingLabel(trackingLabel string) {
|
||||
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
|
||||
cm.Data["application.instanceLabelKey"] = trackingLabel
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func SetResourceOverridesSplitKeys(overrides map[string]v1alpha1.ResourceOverride) {
|
||||
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
|
||||
for k, v := range overrides {
|
||||
|
||||
@@ -15,6 +15,7 @@ type Context struct {
|
||||
timeout int
|
||||
name string
|
||||
destination string
|
||||
repos []string
|
||||
}
|
||||
|
||||
func Given(t *testing.T) *Context {
|
||||
@@ -43,6 +44,11 @@ func (c *Context) Destination(destination string) *Context {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) SourceRepositories(repos []string) *Context {
|
||||
c.repos = repos
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) And(block func()) *Context {
|
||||
block()
|
||||
return c
|
||||
|
||||
@@ -171,7 +171,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- "true"
|
||||
image: "alpine:latest"
|
||||
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never
|
||||
@@ -202,7 +202,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- "true"
|
||||
image: "alpine:latest"
|
||||
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never
|
||||
@@ -218,7 +218,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- "false"
|
||||
image: "alpine:latest"
|
||||
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never
|
||||
|
||||
2
test/e2e/multiarch-container/Dockerfile
Normal file
2
test/e2e/multiarch-container/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM docker.io/library/busybox
|
||||
CMD exec sh -c "trap : TERM INT; echo 'Hi' && tail -f /dev/null"
|
||||
11
test/e2e/multiarch-container/build.sh
Executable file
11
test/e2e/multiarch-container/build.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
VERSIONS="0.1 0.2 0.3"
|
||||
PLATFORMS="linux/amd64,linux/arm64,linux/s390x,linux/ppc64le"
|
||||
for version in $VERSIONS; do
|
||||
docker buildx build \
|
||||
-t "quay.io/argoprojlabs/argocd-e2e-container:${version}" \
|
||||
--platform "${PLATFORMS}" \
|
||||
--push \
|
||||
.
|
||||
done
|
||||
2
test/e2e/testdata/cluster-role/pod.yaml
vendored
2
test/e2e/testdata/cluster-role/pod.yaml
vendored
@@ -5,7 +5,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.17.4-alpine
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
ports:
|
||||
- containerPort: "80"
|
||||
imagePullPolicy: IfNotPresent
|
||||
2
test/e2e/testdata/deployment/deployment.yaml
vendored
2
test/e2e/testdata/deployment/deployment.yaml
vendored
@@ -16,6 +16,6 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.17.4-alpine
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: extensions-deployment
|
||||
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2"
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -5,7 +5,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
|
||||
@@ -14,13 +14,8 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: guestbook-ui
|
||||
command:
|
||||
- sh
|
||||
args:
|
||||
- -c
|
||||
- "echo \"Hi\" && tail -f /dev/null"
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.3
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.3
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 81
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: nginx:1.17.4-alpine
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
|
||||
@@ -10,7 +10,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- "true"
|
||||
image: "alpine:3.10.2"
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never
|
||||
2
test/e2e/testdata/hook/hook.yaml
vendored
2
test/e2e/testdata/hook/hook.yaml
vendored
@@ -8,7 +8,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- "true"
|
||||
image: "alpine:latest"
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never
|
||||
2
test/e2e/testdata/hook/pod.yaml
vendored
2
test/e2e/testdata/hook/pod.yaml
vendored
@@ -5,7 +5,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:latest
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
|
||||
2
test/e2e/testdata/kustomize/pod.yaml
vendored
2
test/e2e/testdata/kustomize/pod.yaml
vendored
@@ -5,7 +5,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.17.4-alpine
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -39,7 +39,7 @@ spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: [sh, -c, "sleep 10"]
|
||||
|
||||
@@ -56,6 +56,6 @@ spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: [sh, -c, "sleep 10"]
|
||||
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: helm-guestbook
|
||||
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2"
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
@@ -30,4 +30,4 @@ spec:
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
port: http
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: helm-guestbook
|
||||
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2"
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -16,6 +16,6 @@ spec:
|
||||
containers:
|
||||
- name: main
|
||||
command: ["sleep", "999"]
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
|
||||
2
test/e2e/testdata/two-nice-pods/pod-1.yaml
vendored
2
test/e2e/testdata/two-nice-pods/pod-1.yaml
vendored
@@ -5,7 +5,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
|
||||
2
test/e2e/testdata/two-nice-pods/pod-2.yaml
vendored
2
test/e2e/testdata/two-nice-pods/pod-2.yaml
vendored
@@ -5,7 +5,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: alpine:3.10.2
|
||||
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "true"
|
||||
|
||||
@@ -43,7 +43,7 @@ export const Help = () => {
|
||||
|
||||
{Object.keys(binaryUrls || {}).map(binaryName => {
|
||||
const url = binaryUrls[binaryName];
|
||||
const match = binaryName.match(/.*(darwin|windows|linux)-(amd64|arm64)/);
|
||||
const match = binaryName.match(/.*(darwin|windows|linux)-(amd64|arm64|ppc64le|s390x)/);
|
||||
const [platform, arch] = match ? match.slice(1) : ['', ''];
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -29,6 +29,7 @@ var LabelMaxLength = 63
|
||||
// ResourceTracking defines methods which allow setup and retrieve tracking information to resource
|
||||
type ResourceTracking interface {
|
||||
GetAppName(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) string
|
||||
GetAppInstance(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) *AppInstanceValue
|
||||
SetAppInstance(un *unstructured.Unstructured, key, val, namespace string, trackingMethod v1alpha1.TrackingMethod) error
|
||||
BuildAppInstanceValue(value AppInstanceValue) string
|
||||
ParseAppInstanceValue(value string) (*AppInstanceValue, error)
|
||||
@@ -54,7 +55,7 @@ func NewResourceTracking() ResourceTracking {
|
||||
// GetTrackingMethod retrieve tracking method from settings
|
||||
func GetTrackingMethod(settingsMgr *settings.SettingsManager) v1alpha1.TrackingMethod {
|
||||
tm, err := settingsMgr.GetTrackingMethod()
|
||||
if err != nil {
|
||||
if err != nil || tm == "" {
|
||||
return TrackingMethodLabel
|
||||
}
|
||||
return v1alpha1.TrackingMethod(tm)
|
||||
@@ -64,15 +65,23 @@ func IsOldTrackingMethod(trackingMethod string) bool {
|
||||
return trackingMethod == "" || trackingMethod == string(TrackingMethodLabel)
|
||||
}
|
||||
|
||||
func (rt *resourceTracking) getAppInstanceValue(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) *AppInstanceValue {
|
||||
appInstanceAnnotation := argokube.GetAppInstanceAnnotation(un, common.AnnotationKeyAppInstance)
|
||||
value, err := rt.ParseAppInstanceValue(appInstanceAnnotation)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetAppName retrieve application name base on tracking method
|
||||
func (rt *resourceTracking) GetAppName(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) string {
|
||||
retrieveAppInstanceValue := func() string {
|
||||
appInstanceAnnotation := argokube.GetAppInstanceAnnotation(un, common.AnnotationKeyAppInstance)
|
||||
value, err := rt.ParseAppInstanceValue(appInstanceAnnotation)
|
||||
if err != nil {
|
||||
return ""
|
||||
value := rt.getAppInstanceValue(un, key, trackingMethod)
|
||||
if value != nil {
|
||||
return value.ApplicationName
|
||||
}
|
||||
return value.ApplicationName
|
||||
return ""
|
||||
}
|
||||
switch trackingMethod {
|
||||
case TrackingMethodLabel:
|
||||
@@ -86,6 +95,18 @@ func (rt *resourceTracking) GetAppName(un *unstructured.Unstructured, key string
|
||||
}
|
||||
}
|
||||
|
||||
// GetAppInstance returns the representation of the app instance annotation.
|
||||
// If the tracking method does not support metadata, or the annotation could
|
||||
// not be parsed, it returns nil.
|
||||
func (rt *resourceTracking) GetAppInstance(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) *AppInstanceValue {
|
||||
switch trackingMethod {
|
||||
case TrackingMethodAnnotation, TrackingMethodAnnotationAndLabel:
|
||||
return rt.getAppInstanceValue(un, key, trackingMethod)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetAppInstance set label/annotation base on tracking method
|
||||
func (rt *resourceTracking) SetAppInstance(un *unstructured.Unstructured, key, val, namespace string, trackingMethod v1alpha1.TrackingMethod) error {
|
||||
setAppInstanceAnnotation := func() error {
|
||||
|
||||
@@ -58,6 +58,7 @@ type Client interface {
|
||||
Root() string
|
||||
Init() error
|
||||
Fetch(revision string) error
|
||||
Submodule() error
|
||||
Checkout(revision string, submoduleEnabled bool) error
|
||||
LsRefs() (*Refs, error)
|
||||
LsRemote(revision string) (string, error)
|
||||
@@ -392,6 +393,17 @@ func (m *nativeGitClient) LsLargeFiles() ([]string, error) {
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// Submodule embed other repositories into this repository
|
||||
func (m *nativeGitClient) Submodule() error {
|
||||
if err := m.runCredentialedCmd("git", "submodule", "sync", "--recursive"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.runCredentialedCmd("git", "submodule", "update", "--init", "--recursive"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checkout checkout specified revision
|
||||
func (m *nativeGitClient) Checkout(revision string, submoduleEnabled bool) error {
|
||||
if revision == "" || revision == "HEAD" {
|
||||
@@ -415,7 +427,7 @@ func (m *nativeGitClient) Checkout(revision string, submoduleEnabled bool) error
|
||||
}
|
||||
if _, err := os.Stat(m.root + "/.gitmodules"); !os.IsNotExist(err) {
|
||||
if submoduleEnabled {
|
||||
if err := m.runCredentialedCmd("git", "submodule", "update", "--init", "--recursive"); err != nil {
|
||||
if err := m.Submodule(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -92,3 +93,117 @@ func Test_nativeGitClient_Fetch_Prune(t *testing.T) {
|
||||
err = client.Fetch("")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_nativeGitClient_Submodule(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
foo := filepath.Join(tempDir, "foo")
|
||||
err = os.Mkdir(foo, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = foo
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
bar := filepath.Join(tempDir, "bar")
|
||||
err = os.Mkdir(bar, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd = exec.Command("git", "init")
|
||||
cmd.Dir = bar
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial commit", "--allow-empty")
|
||||
cmd.Dir = bar
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Embed repository bar into repository foo
|
||||
cmd = exec.Command("git", "submodule", "add", bar)
|
||||
cmd.Dir = foo
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial commit")
|
||||
cmd.Dir = foo
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
tempDir, err = os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Clone foo
|
||||
cmd = exec.Command("git", "clone", foo)
|
||||
cmd.Dir = tempDir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := NewClient(fmt.Sprintf("file://%s", foo), NopCreds{}, true, false, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Fetch("")
|
||||
assert.NoError(t, err)
|
||||
|
||||
commitSHA, err := client.LsRemote("HEAD")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Call Checkout() with submoduleEnabled=false.
|
||||
err = client.Checkout(commitSHA, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if submodule url does not exist in .git/config
|
||||
cmd = exec.Command("git", "config", "submodule.bar.url")
|
||||
cmd.Dir = client.Root()
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
assert.Error(t, err)
|
||||
|
||||
// Call Submodule() via Checkout() with submoduleEnabled=true.
|
||||
err = client.Checkout(commitSHA, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if the .gitmodule URL is reflected in .git/config
|
||||
cmd = exec.Command("git", "config", "submodule.bar.url")
|
||||
cmd.Dir = client.Root()
|
||||
result, err := cmd.Output()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bar+"\n", string(result))
|
||||
|
||||
// Change URL of submodule bar
|
||||
cmd = exec.Command("git", "config", "--file=.gitmodules", "submodule.bar.url", bar+"baz")
|
||||
cmd.Dir = client.Root()
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Call Submodule()
|
||||
err = client.Submodule()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if the URL change in .gitmodule is reflected in .git/config
|
||||
cmd = exec.Command("git", "config", "submodule.bar.url")
|
||||
cmd.Dir = client.Root()
|
||||
result, err = cmd.Output()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bar+"baz\n", string(result))
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
git "github.com/argoproj/argo-cd/v2/util/git"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Client is an autogenerated mock type for the Client type
|
||||
@@ -203,6 +202,20 @@ func (_m *Client) Root() string {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Submodule provides a mock function with given fields:
|
||||
func (_m *Client) Submodule() error {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// VerifyCommitSignature provides a mock function with given fields: _a0
|
||||
func (_m *Client) VerifyCommitSignature(_a0 string) (string, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
@@ -87,7 +88,7 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
||||
grpc.FailOnNonTempDialError(true),
|
||||
grpc.WithContextDialer(dialer),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()), // we are handling TLS, so tell grpc not to
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: 10 * time.Second}),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: common.GRPCKeepAliveTime}),
|
||||
)
|
||||
conn, err := grpc.DialContext(ctx, address, opts...)
|
||||
var res interface{}
|
||||
@@ -115,7 +116,7 @@ type TLSTestResult struct {
|
||||
InsecureErr error
|
||||
}
|
||||
|
||||
func TestTLS(address string) (*TLSTestResult, error) {
|
||||
func TestTLS(address string, dialTime time.Duration) (*TLSTestResult, error) {
|
||||
if parts := strings.Split(address, ":"); len(parts) == 1 {
|
||||
// If port is unspecified, assume the most likely port
|
||||
address += ":443"
|
||||
@@ -124,12 +125,21 @@ func TestTLS(address string) (*TLSTestResult, error) {
|
||||
var tlsConfig tls.Config
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
creds := credentials.NewTLS(&tlsConfig)
|
||||
conn, err := BlockingDial(context.Background(), "tcp", address, creds)
|
||||
|
||||
// Set timeout when dialing to the server
|
||||
// fix: https://github.com/argoproj/argo-cd/issues/9679
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dialTime)
|
||||
defer cancel()
|
||||
|
||||
conn, err := BlockingDial(ctx, "tcp", address, creds)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
testResult.TLS = true
|
||||
creds := credentials.NewTLS(&tls.Config{})
|
||||
conn, err := BlockingDial(context.Background(), "tcp", address, creds)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dialTime)
|
||||
defer cancel()
|
||||
|
||||
conn, err := BlockingDial(ctx, "tcp", address, creds)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
} else {
|
||||
@@ -142,7 +152,9 @@ func TestTLS(address string) (*TLSTestResult, error) {
|
||||
// If we get here, we were unable to connect via TLS (even with InsecureSkipVerify: true)
|
||||
// It may be because server is running without TLS, or because of real issues (e.g. connection
|
||||
// refused). Test if server accepts plain-text connections
|
||||
conn, err = BlockingDial(context.Background(), "tcp", address, nil)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), dialTime)
|
||||
defer cancel()
|
||||
conn, err = BlockingDial(ctx, "tcp", address, nil)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
testResult.TLS = false
|
||||
|
||||
@@ -28,6 +28,8 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
var InvalidRedirectURLError = fmt.Errorf("invalid return URL")
|
||||
|
||||
const (
|
||||
GrantTypeAuthorizationCode = "authorization_code"
|
||||
GrantTypeImplicit = "implicit"
|
||||
@@ -185,10 +187,18 @@ func (a *ClientApp) verifyAppState(r *http.Request, w http.ResponseWriter, state
|
||||
return "", err
|
||||
}
|
||||
cookieVal := string(val)
|
||||
returnURL := a.baseHRef
|
||||
redirectURL := a.baseHRef
|
||||
parts := strings.SplitN(cookieVal, ":", 2)
|
||||
if len(parts) == 2 && parts[1] != "" {
|
||||
returnURL = parts[1]
|
||||
if !isValidRedirectURL(parts[1], []string{a.settings.URL}) {
|
||||
sanitizedUrl := parts[1]
|
||||
if len(sanitizedUrl) > 100 {
|
||||
sanitizedUrl = sanitizedUrl[:100]
|
||||
}
|
||||
log.Warnf("Failed to verify app state - got invalid redirectURL %q", sanitizedUrl)
|
||||
return "", fmt.Errorf("failed to verify app state: %w", InvalidRedirectURLError)
|
||||
}
|
||||
redirectURL = parts[1]
|
||||
}
|
||||
if parts[0] != state {
|
||||
return "", fmt.Errorf("invalid state in '%s' cookie", common.AuthCookieName)
|
||||
@@ -201,7 +211,7 @@ func (a *ClientApp) verifyAppState(r *http.Request, w http.ResponseWriter, state
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: a.secureCookie,
|
||||
})
|
||||
return returnURL, nil
|
||||
return redirectURL, nil
|
||||
}
|
||||
|
||||
// isValidRedirectURL checks whether the given redirectURL matches on of the
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
gooidc "github.com/coreos/go-oidc"
|
||||
@@ -19,6 +22,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util"
|
||||
"github.com/argoproj/argo-cd/v2/util/crypto"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
"github.com/argoproj/argo-cd/v2/util/test"
|
||||
)
|
||||
|
||||
func TestInferGrantType(t *testing.T) {
|
||||
@@ -104,6 +108,174 @@ func TestHandleCallback(t *testing.T) {
|
||||
assert.Equal(t, "login-failed: <script>alert('hello')</script>\n", w.Body.String())
|
||||
}
|
||||
|
||||
func TestClientApp_HandleLogin(t *testing.T) {
|
||||
oidcTestServer := test.GetOIDCTestServer(t)
|
||||
t.Cleanup(oidcTestServer.Close)
|
||||
|
||||
dexTestServer := test.GetDexTestServer(t)
|
||||
t.Cleanup(dexTestServer.Close)
|
||||
|
||||
t.Run("oidc certificate checking during login should toggle on config", func(t *testing.T) {
|
||||
cdSettings := &settings.ArgoCDSettings{
|
||||
URL: "https://argocd.example.com",
|
||||
OIDCConfigRAW: fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
}
|
||||
app, err := NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("GET", "https://argocd.example.com/auth/login", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.HandleLogin(w, req)
|
||||
|
||||
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
|
||||
cdSettings.OIDCTLSInsecureSkipVerify = true
|
||||
|
||||
app, err = NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
app.HandleLogin(w, req)
|
||||
|
||||
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
|
||||
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
|
||||
})
|
||||
|
||||
t.Run("dex certificate checking during login should toggle on config", func(t *testing.T) {
|
||||
cdSettings := &settings.ArgoCDSettings{
|
||||
URL: "https://argocd.example.com",
|
||||
DexConfig: `connectors:
|
||||
- type: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: aabbccddeeff00112233
|
||||
clientSecret: aabbccddeeff00112233`,
|
||||
}
|
||||
cert, err := tls.X509KeyPair(test.Cert, test.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
cdSettings.Certificate = &cert
|
||||
|
||||
app, err := NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("GET", "https://argocd.example.com/auth/login", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.HandleLogin(w, req)
|
||||
|
||||
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
|
||||
cdSettings.OIDCTLSInsecureSkipVerify = true
|
||||
|
||||
app, err = NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
app.HandleLogin(w, req)
|
||||
|
||||
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
|
||||
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientApp_HandleCallback(t *testing.T) {
|
||||
oidcTestServer := test.GetOIDCTestServer(t)
|
||||
t.Cleanup(oidcTestServer.Close)
|
||||
|
||||
dexTestServer := test.GetDexTestServer(t)
|
||||
t.Cleanup(dexTestServer.Close)
|
||||
|
||||
t.Run("oidc certificate checking during oidc callback should toggle on config", func(t *testing.T) {
|
||||
cdSettings := &settings.ArgoCDSettings{
|
||||
URL: "https://argocd.example.com",
|
||||
OIDCConfigRAW: fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
}
|
||||
app, err := NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("GET", "https://argocd.example.com/auth/callback", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.HandleCallback(w, req)
|
||||
|
||||
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
|
||||
cdSettings.OIDCTLSInsecureSkipVerify = true
|
||||
|
||||
app, err = NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
app.HandleCallback(w, req)
|
||||
|
||||
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
|
||||
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
|
||||
})
|
||||
|
||||
t.Run("dex certificate checking during oidc callback should toggle on config", func(t *testing.T) {
|
||||
cdSettings := &settings.ArgoCDSettings{
|
||||
URL: "https://argocd.example.com",
|
||||
DexConfig: `connectors:
|
||||
- type: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: aabbccddeeff00112233
|
||||
clientSecret: aabbccddeeff00112233`,
|
||||
}
|
||||
cert, err := tls.X509KeyPair(test.Cert, test.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
cdSettings.Certificate = &cert
|
||||
|
||||
app, err := NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("GET", "https://argocd.example.com/auth/callback", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.HandleCallback(w, req)
|
||||
|
||||
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
|
||||
cdSettings.OIDCTLSInsecureSkipVerify = true
|
||||
|
||||
app, err = NewClientApp(cdSettings, dexTestServer.URL, "https://argocd.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
app.HandleCallback(w, req)
|
||||
|
||||
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
|
||||
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsValidRedirect(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
@@ -191,7 +363,7 @@ func TestGenerateAppState(t *testing.T) {
|
||||
signature, err := util.MakeSignature(32)
|
||||
require.NoError(t, err)
|
||||
expectedReturnURL := "http://argocd.example.com/"
|
||||
app, err := NewClientApp(&settings.ArgoCDSettings{ServerSignature: signature}, "", "")
|
||||
app, err := NewClientApp(&settings.ArgoCDSettings{ServerSignature: signature, URL: expectedReturnURL}, "", "")
|
||||
require.NoError(t, err)
|
||||
generateResponse := httptest.NewRecorder()
|
||||
state, err := app.generateAppState(expectedReturnURL, generateResponse)
|
||||
@@ -219,6 +391,56 @@ func TestGenerateAppState(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateAppState_XSS(t *testing.T) {
|
||||
signature, err := util.MakeSignature(32)
|
||||
require.NoError(t, err)
|
||||
app, err := NewClientApp(
|
||||
&settings.ArgoCDSettings{
|
||||
// Only return URLs starting with this base should be allowed.
|
||||
URL: "https://argocd.example.com",
|
||||
ServerSignature: signature,
|
||||
},
|
||||
"", "",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("XSS fails", func(t *testing.T) {
|
||||
// This attack assumes the attacker has compromised the server's secret key. We use `generateAppState` here for
|
||||
// convenience, but an attacker with access to the server secret could write their own code to generate the
|
||||
// malicious cookie.
|
||||
|
||||
expectedReturnURL := "javascript: alert('hi')"
|
||||
generateResponse := httptest.NewRecorder()
|
||||
state, err := app.generateAppState(expectedReturnURL, generateResponse)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
for _, cookie := range generateResponse.Result().Cookies() {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
|
||||
returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), state)
|
||||
assert.ErrorIs(t, err, InvalidRedirectURLError)
|
||||
assert.Empty(t, returnURL)
|
||||
})
|
||||
|
||||
t.Run("valid return URL succeeds", func(t *testing.T) {
|
||||
expectedReturnURL := "https://argocd.example.com/some/path"
|
||||
generateResponse := httptest.NewRecorder()
|
||||
state, err := app.generateAppState(expectedReturnURL, generateResponse)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
for _, cookie := range generateResponse.Result().Cookies() {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
|
||||
returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), state)
|
||||
assert.NoError(t, err, InvalidRedirectURLError)
|
||||
assert.Equal(t, expectedReturnURL, returnURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateAppState_NoReturnURL(t *testing.T) {
|
||||
signature, err := util.MakeSignature(32)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -123,10 +123,7 @@ func NewSessionManager(settingsMgr *settings.SettingsManager, projectsLister v1a
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tlsConfig := settings.TLSConfig()
|
||||
if tlsConfig != nil {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
tlsConfig := settings.OIDCTLSConfig()
|
||||
s.client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
|
||||
@@ -2,11 +2,9 @@ package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -31,6 +29,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
"github.com/argoproj/argo-cd/v2/util/password"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
utiltest "github.com/argoproj/argo-cd/v2/util/test"
|
||||
)
|
||||
|
||||
func getProjLister(objects ...runtime.Object) v1alpha1.AppProjectNamespaceLister {
|
||||
@@ -460,46 +459,14 @@ func TestFailedAttemptsExpiry(t *testing.T) {
|
||||
os.Setenv(envLoginFailureWindowSeconds, "")
|
||||
}
|
||||
|
||||
func oidcMockHandler(t *testing.T, url string) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.RequestURI {
|
||||
case "/.well-known/openid-configuration":
|
||||
_, err := io.WriteString(w, fmt.Sprintf(`
|
||||
{
|
||||
"issuer": "%[1]s",
|
||||
"authorization_endpoint": "%[1]s/auth",
|
||||
"token_endpoint": "%[1]s/token",
|
||||
"jwks_uri": "%[1]s/keys",
|
||||
"userinfo_endpoint": "%[1]s/userinfo",
|
||||
"device_authorization_endpoint": "%[1]s/device/code",
|
||||
"grant_types_supported": ["authorization_code"],
|
||||
"response_types_supported": ["code"],
|
||||
"subject_types_supported": ["public"],
|
||||
"id_token_signing_alg_values_supported": ["RS512"],
|
||||
"code_challenge_methods_supported": ["S256", "plain"],
|
||||
"scopes_supported": ["openid"],
|
||||
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
|
||||
"claims_supported": ["sub", "aud", "exp"]
|
||||
}`, url))
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
func getKubeClientWithConfig(config map[string]string, secretConfig map[string][]byte) *fake.Clientset {
|
||||
mergedSecretConfig := map[string][]byte{
|
||||
"server.secretkey": []byte("Hello, world!"),
|
||||
}
|
||||
for key, value := range secretConfig {
|
||||
mergedSecretConfig[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func getOIDCTestServer(t *testing.T) *httptest.Server {
|
||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Start with a placeholder. We need the server URL before setting up the real handler.
|
||||
}))
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
oidcMockHandler(t, ts.URL)(w, r)
|
||||
})
|
||||
return ts
|
||||
}
|
||||
|
||||
func getKubeClientWithConfig(config map[string]string) *fake.Clientset {
|
||||
return fake.NewSimpleClientset(&corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-cm",
|
||||
@@ -514,45 +481,18 @@ func getKubeClientWithConfig(config map[string]string) *fake.Clientset {
|
||||
Name: "argocd-secret",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"server.secretkey": []byte("Hello, world!"),
|
||||
},
|
||||
Data: mergedSecretConfig,
|
||||
})
|
||||
}
|
||||
|
||||
// privateKey is an RSA key used only for tests.
|
||||
var privateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEA2KHkp2fe0ReHqAt9BimWEec2ryWZIyg9jvB3BdP3mzFf0bOt
|
||||
WlHm1FAETFxH4h5jYASUaaWEwRNNyGlT1GhTp+jOMC4xhOSb5/SnI2dt2EITkudQ
|
||||
FKsSFUdJAndqOzkjrP2pL4fi4b7JWhuLDO36ufAP4l2m3tnAseGSSTIccWvzLFFU
|
||||
s3wsHOHxOcJGCP1Z7rizxl6mTKYL/Z+GHqN17OJslDf901uPXsUeDCYL2iigGPhD
|
||||
Ao6k8POsfbpqLG7poCDTK50FLnS5qEocjxt+J4ZjBEWTU/DOFXWYstzfbhm8OZPQ
|
||||
pSSEiBCxpg+zjtkQfCyZxXB5RQ84CY78fXOI9QIDAQABAoIBAG8jL0FLIp62qZvm
|
||||
uO9ualUo/37/lP7aaCpq50UQJ9lwjS3yNh8+IWQO4QWj2iUBXg4mi1Vf2ymKk78b
|
||||
eixgkXp1D0Lcj/8ToYBwnUami04FKDGXhhf0Y8SS27vuM4vKlqjrQd7modkangYi
|
||||
V0X82UKHDD8fuLpfkGIxzXDLypfMzjMuVpSntnWaf2YX3VR/0/66yEp9GejftF2k
|
||||
wqhGoWM6r68pN5XuCqWd5PRluSoDy/o4BAFMhYCSfp9PjgZE8aoeWHgYzlZ3gUyn
|
||||
r+HaDDNWbibhobXk/9h8lwAJ6KCZ5RZ+HFfh0HuwIxmocT9OCFgy/S0g1p+o3m9K
|
||||
VNd5AMkCgYEA5fbS5UK7FBzuLoLgr1hktmbLJhpt8y8IPHNABHcUdE+O4/1xTQNf
|
||||
pMUwkKjGG1MtrGjLOIoMGURKKn8lR1GMZueOTSKY0+mAWUGvSzl6vwtJwvJruT8M
|
||||
otEO03o0tPnRKGxbFjqxkp2b6iqJ8MxCRZ3lSidc4mdi7PHzv9lwgvsCgYEA8Siq
|
||||
7weCri9N6y+tIdORAXgRzcW54BmJyqB147c72RvbMacb6rN28KXpM3qnRXyp3Llb
|
||||
yh81TW3FH10GqrjATws7BK8lP9kkAw0Z/7kNiS1NgH3pUbO+5H2kAa/6QW35nzRe
|
||||
Jw2lyfYGWqYO4hYXH14ML1kjgS1hgd3XHOQ64M8CgYAKcjDYSzS2UC4dnMJaFLjW
|
||||
dErsGy09a7iDDnUs/r/GHMsP3jZkWi/hCzgOiiwdl6SufUAl/FdaWnjH/2iRGco3
|
||||
7nLPXC/3CFdVNp+g2iaSQRADtAFis9N+HeL/hkCYq/RtUqa8lsP0NgacF3yWnKCy
|
||||
Ct8chDc67ZlXzBHXeCgdOwKBgHHGFPbWXUHeUW1+vbiyvrupsQSanznp8oclMtkv
|
||||
Dk48hSokw9fzuU6Jh77gw9/Vk7HtxS9Tj+squZA1bDrJFPl1u+9WzkUUJZhG6xgp
|
||||
bwhj1iejv5rrKUlVOTYOlwudXeJNa4oTNz9UEeVcaLMjZt9GmIsSC90a0uDZD26z
|
||||
AlAjAoGAEoqm2DcNN7SrH6aVFzj1EVOrNsHYiXj/yefspeiEmf27PSAslP+uF820
|
||||
SDpz4h+Bov5qTKkzcxuu1QWtA4M0K8Iy6IYLwb83DZEm1OsAf4i0pODz21PY/I+O
|
||||
VHzjB10oYgaInHZgMUdyb6F571UdiYSB6a/IlZ3ngj5touy3VIM=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
func TestSessionManager_VerifyToken(t *testing.T) {
|
||||
t.Run("HS256 is supported", func(t *testing.T) {
|
||||
oidcTestServer := getOIDCTestServer(t)
|
||||
defer oidcTestServer.Close()
|
||||
oidcTestServer := utiltest.GetOIDCTestServer(t)
|
||||
t.Cleanup(oidcTestServer.Close)
|
||||
|
||||
dexTestServer := utiltest.GetDexTestServer(t)
|
||||
t.Cleanup(dexTestServer.Close)
|
||||
|
||||
t.Run("RS512 is supported", func(t *testing.T) {
|
||||
dexConfig := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
@@ -563,7 +503,7 @@ clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig), "argocd")
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig, nil), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
// Use test server's client to avoid TLS issues.
|
||||
@@ -572,7 +512,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKey)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
@@ -580,4 +520,216 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
assert.NotContains(t, err.Error(), "oidc: id token signed with unsupported algorithm")
|
||||
})
|
||||
|
||||
t.Run("oidcConfig.rootCA is respected", func(t *testing.T) {
|
||||
cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: oidcTestServer.TLS.Certificates[0].Certificate[0]})
|
||||
|
||||
dexConfig := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
rootCA: |
|
||||
%s
|
||||
`, oidcTestServer.URL, strings.Replace(string(cert), "\n", "\n ", -1)),
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig, nil), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
// If the root CA is being respected, we won't get this error. The error message is environment-dependent, so
|
||||
// we check for either of the error messages associated with a failed cert check.
|
||||
assert.NotContains(t, err.Error(), "certificate is not trusted")
|
||||
assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is Dex, TLS is configured", func(t *testing.T) {
|
||||
dexConfig := map[string]string{
|
||||
"url": dexTestServer.URL,
|
||||
"dex.config": `connectors:
|
||||
- type: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: aabbccddeeff00112233
|
||||
clientSecret: aabbccddeeff00112233`,
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), dexTestServer.URL, NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = fmt.Sprintf("%s/api/dex", dexTestServer.URL)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, TLS is configured", func(t *testing.T) {
|
||||
dexConfig := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is Dex, TLS is configured", func(t *testing.T) {
|
||||
dexConfig := map[string]string{
|
||||
"url": dexTestServer.URL,
|
||||
"dex.config": `connectors:
|
||||
- type: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: aabbccddeeff00112233
|
||||
clientSecret: aabbccddeeff00112233`,
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), dexTestServer.URL, NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = fmt.Sprintf("%s/api/dex", dexTestServer.URL)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, TLS is configured, OIDCTLSInsecureSkipVerify is true", func(t *testing.T) {
|
||||
dexConfig := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true",
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
assert.NotContains(t, err.Error(), "certificate is not trusted")
|
||||
assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, TLS is not configured, OIDCTLSInsecureSkipVerify is true", func(t *testing.T) {
|
||||
dexConfig := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true",
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(dexConfig, nil), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
// This is the error thrown when the test server's certificate _is_ being verified.
|
||||
assert.NotContains(t, err.Error(), "certificate is not trusted")
|
||||
assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,6 +99,15 @@ type ArgoCDSettings struct {
|
||||
ServerRBACLogEnforceEnable bool `json:"serverRBACLogEnforceEnable"`
|
||||
// ExecEnabled indicates whether the UI exec feature is enabled
|
||||
ExecEnabled bool `json:"execEnabled"`
|
||||
// ExecShells restricts which shells are allowed for `exec` and in which order they are tried
|
||||
ExecShells []string `json:"execShells"`
|
||||
// OIDCTLSInsecureSkipVerify determines whether certificate verification is skipped when verifying tokens with the
|
||||
// configured OIDC provider (either external or the bundled Dex instance). Setting this to `true` will cause JWT
|
||||
// token verification to pass despite the OIDC provider having an invalid certificate. Only set to `true` if you
|
||||
// understand the risks.
|
||||
OIDCTLSInsecureSkipVerify bool `json:"oidcTLSInsecureSkipVerify"`
|
||||
// TrackingMethod defines the resource tracking method to be used
|
||||
TrackingMethod string `json:"application.resourceTrackingMethod,omitempty"`
|
||||
}
|
||||
|
||||
type GoogleAnalytics struct {
|
||||
@@ -368,7 +377,7 @@ const (
|
||||
kustomizePathPrefixKey = "kustomize.path"
|
||||
// anonymousUserEnabledKey is the key which enables or disables anonymous user
|
||||
anonymousUserEnabledKey = "users.anonymous.enabled"
|
||||
// anonymousUserEnabledKey is the key which specifies token expiration duration
|
||||
// userSessionDurationKey is the key which specifies token expiration duration
|
||||
userSessionDurationKey = "users.session.duration"
|
||||
// diffOptions is the key where diff options are configured
|
||||
resourceCompareOptionsKey = "resource.compareoptions"
|
||||
@@ -406,6 +415,10 @@ const (
|
||||
helmValuesFileSchemesKey = "helm.valuesFileSchemes"
|
||||
// execEnabledKey is the key to configure whether the UI exec feature is enabled
|
||||
execEnabledKey = "exec.enabled"
|
||||
// execShellsKey is the key to configure which shells are allowed for `exec` and in what order they are tried
|
||||
execShellsKey = "exec.shells"
|
||||
// oidcTLSInsecureSkipVerifyKey is the key to configure whether TLS cert verification is skipped for OIDC connections
|
||||
oidcTLSInsecureSkipVerifyKey = "oidc.tls.insecure.skip.verify"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -1271,6 +1284,15 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.Confi
|
||||
}
|
||||
settings.InClusterEnabled = argoCDCM.Data[inClusterEnabledKey] != "false"
|
||||
settings.ExecEnabled = argoCDCM.Data[execEnabledKey] == "true"
|
||||
execShells := argoCDCM.Data[execShellsKey]
|
||||
if execShells != "" {
|
||||
settings.ExecShells = strings.Split(execShells, ",")
|
||||
} else {
|
||||
// Fall back to default. If you change this list, also change docs/operator-manual/argocd-cm.yaml.
|
||||
settings.ExecShells = []string{"bash", "sh", "powershell", "cmd"}
|
||||
}
|
||||
settings.OIDCTLSInsecureSkipVerify = argoCDCM.Data[oidcTLSInsecureSkipVerifyKey] == "true"
|
||||
settings.TrackingMethod = argoCDCM.Data[settingsResourceTrackingMethodKey]
|
||||
}
|
||||
|
||||
// validateExternalURL ensures the external URL that is set on the configmap is valid
|
||||
@@ -1640,22 +1662,28 @@ func (a *ArgoCDSettings) OAuth2ClientSecret() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// OIDCTLSConfig returns the TLS config for the OIDC provider. If an external provider is configured, returns a TLS
|
||||
// config using the root CAs (if any) specified in the OIDC config. If an external OIDC provider is not configured,
|
||||
// returns the API server TLS config, because the API server proxies requests to Dex.
|
||||
func (a *ArgoCDSettings) OIDCTLSConfig() *tls.Config {
|
||||
if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
oidcConfig := a.OIDCConfig()
|
||||
if oidcConfig != nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
if oidcConfig.RootCA != "" {
|
||||
certPool := x509.NewCertPool()
|
||||
ok := certPool.AppendCertsFromPEM([]byte(oidcConfig.RootCA))
|
||||
if !ok {
|
||||
log.Warn("invalid oidc root ca cert - returning default tls.Config instead")
|
||||
return &tls.Config{}
|
||||
}
|
||||
return &tls.Config{
|
||||
RootCAs: certPool,
|
||||
log.Warn("failed to append certificates from PEM: proceeding without custom rootCA")
|
||||
} else {
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tlsConfig = a.TLSConfig()
|
||||
}
|
||||
tlsConfig := a.TLSConfig()
|
||||
if tlsConfig != nil {
|
||||
if tlsConfig != nil && a.OIDCTLSInsecureSkipVerify {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
return tlsConfig
|
||||
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
testutil "github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -1169,3 +1172,68 @@ func TestGetHelmSettings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestArgoCDSettings_OIDCTLSConfig_OIDCTLSInsecureSkipVerify(t *testing.T) {
|
||||
certParsed, err := tls.X509KeyPair(test.Cert, test.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct{
|
||||
name string
|
||||
settings *ArgoCDSettings
|
||||
expectNilTLSConfig bool
|
||||
}{
|
||||
{
|
||||
name: "OIDC configured, no root CA",
|
||||
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
||||
issuer: aaa
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`},
|
||||
},
|
||||
{
|
||||
name: "OIDC configured, valid root CA",
|
||||
settings: &ArgoCDSettings{OIDCConfigRAW: fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: aaa
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
rootCA: |
|
||||
%s
|
||||
`, strings.Replace(string(test.Cert), "\n", "\n ", -1))},
|
||||
},
|
||||
{
|
||||
name: "OIDC configured, invalid root CA",
|
||||
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
||||
issuer: aaa
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
rootCA: "invalid"`},
|
||||
},
|
||||
{
|
||||
name: "OIDC not configured, no cert configured",
|
||||
settings: &ArgoCDSettings{},
|
||||
expectNilTLSConfig: true,
|
||||
},
|
||||
{
|
||||
name: "OIDC not configured, cert configured",
|
||||
settings: &ArgoCDSettings{Certificate: &certParsed},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
if testCase.expectNilTLSConfig {
|
||||
assert.Nil(t, testCase.settings.OIDCTLSConfig())
|
||||
} else {
|
||||
assert.False(t, testCase.settings.OIDCTLSConfig().InsecureSkipVerify)
|
||||
|
||||
testCase.settings.OIDCTLSInsecureSkipVerify = true
|
||||
|
||||
assert.True(t, testCase.settings.OIDCTLSConfig().InsecureSkipVerify)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
140
util/test/testutil.go
Normal file
140
util/test/testutil.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Cert is a certificate for tests. It was generated like this:
|
||||
// opts := tls.CertOptions{Hosts: []string{"localhost"}, Organization: "Acme"}
|
||||
// certBytes, privKey, err := tls.generatePEM(opts)
|
||||
var Cert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC8zCCAdugAwIBAgIQCSoocl6e/FR4mQy1wX6NbjANBgkqhkiG9w0BAQsFADAP
|
||||
MQ0wCwYDVQQKEwRBY21lMB4XDTIyMDYyMjE3Mjk1MloXDTIzMDYyMjE3Mjk1Mlow
|
||||
DzENMAsGA1UEChMEQWNtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
ANih5Kdn3tEXh6gLfQYplhHnNq8lmSMoPY7wdwXT95sxX9GzrVpR5tRQBExcR+Ie
|
||||
Y2AElGmlhMETTchpU9RoU6fozjAuMYTkm+f0pyNnbdhCE5LnUBSrEhVHSQJ3ajs5
|
||||
I6z9qS+H4uG+yVobiwzt+rnwD+Jdpt7ZwLHhkkkyHHFr8yxRVLN8LBzh8TnCRgj9
|
||||
We64s8ZepkymC/2fhh6jdezibJQ3/dNbj17FHgwmC9oooBj4QwKOpPDzrH26aixu
|
||||
6aAg0yudBS50uahKHI8bfieGYwRFk1PwzhV1mLLc324ZvDmT0KUkhIgQsaYPs47Z
|
||||
EHwsmcVweUUPOAmO/H1ziPUCAwEAAaNLMEkwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
|
||||
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxo
|
||||
b3N0MA0GCSqGSIb3DQEBCwUAA4IBAQA+8cGJfYRhXQxan7FATsbtC+1DwW1cPc60
|
||||
5eLOuI0jPdvXLDmtOulBEjR4KOfJ5oTKXGjs/+gR3sffP6s8gm2XFQn4+OsmxHbO
|
||||
b2RjPHgKUtJmrI4ZCN8iPGlKIar5u6Q8NZwzpeZ2XL0bpPp7RQsfHqMyhsqDinWR
|
||||
vvwQB+Bri0oIOtzW2645vWmYc2SaFMd8+8g6Ipa+PRSJezeUxIVZG12zlhsio18F
|
||||
9SHY2ONcYISjfrGTIcu4cZRGxCZGTIwMngBlb71mia+K7uH+UE6qfJy/t6KiFsCP
|
||||
yOwMb95nGQSQLDNoGr8gwgE2qPuR0kR9Z5OrWF0DoVCyL3xnxr02
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
// PrivateKey is an RSA key used only for tests.
|
||||
var PrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEA2KHkp2fe0ReHqAt9BimWEec2ryWZIyg9jvB3BdP3mzFf0bOt
|
||||
WlHm1FAETFxH4h5jYASUaaWEwRNNyGlT1GhTp+jOMC4xhOSb5/SnI2dt2EITkudQ
|
||||
FKsSFUdJAndqOzkjrP2pL4fi4b7JWhuLDO36ufAP4l2m3tnAseGSSTIccWvzLFFU
|
||||
s3wsHOHxOcJGCP1Z7rizxl6mTKYL/Z+GHqN17OJslDf901uPXsUeDCYL2iigGPhD
|
||||
Ao6k8POsfbpqLG7poCDTK50FLnS5qEocjxt+J4ZjBEWTU/DOFXWYstzfbhm8OZPQ
|
||||
pSSEiBCxpg+zjtkQfCyZxXB5RQ84CY78fXOI9QIDAQABAoIBAG8jL0FLIp62qZvm
|
||||
uO9ualUo/37/lP7aaCpq50UQJ9lwjS3yNh8+IWQO4QWj2iUBXg4mi1Vf2ymKk78b
|
||||
eixgkXp1D0Lcj/8ToYBwnUami04FKDGXhhf0Y8SS27vuM4vKlqjrQd7modkangYi
|
||||
V0X82UKHDD8fuLpfkGIxzXDLypfMzjMuVpSntnWaf2YX3VR/0/66yEp9GejftF2k
|
||||
wqhGoWM6r68pN5XuCqWd5PRluSoDy/o4BAFMhYCSfp9PjgZE8aoeWHgYzlZ3gUyn
|
||||
r+HaDDNWbibhobXk/9h8lwAJ6KCZ5RZ+HFfh0HuwIxmocT9OCFgy/S0g1p+o3m9K
|
||||
VNd5AMkCgYEA5fbS5UK7FBzuLoLgr1hktmbLJhpt8y8IPHNABHcUdE+O4/1xTQNf
|
||||
pMUwkKjGG1MtrGjLOIoMGURKKn8lR1GMZueOTSKY0+mAWUGvSzl6vwtJwvJruT8M
|
||||
otEO03o0tPnRKGxbFjqxkp2b6iqJ8MxCRZ3lSidc4mdi7PHzv9lwgvsCgYEA8Siq
|
||||
7weCri9N6y+tIdORAXgRzcW54BmJyqB147c72RvbMacb6rN28KXpM3qnRXyp3Llb
|
||||
yh81TW3FH10GqrjATws7BK8lP9kkAw0Z/7kNiS1NgH3pUbO+5H2kAa/6QW35nzRe
|
||||
Jw2lyfYGWqYO4hYXH14ML1kjgS1hgd3XHOQ64M8CgYAKcjDYSzS2UC4dnMJaFLjW
|
||||
dErsGy09a7iDDnUs/r/GHMsP3jZkWi/hCzgOiiwdl6SufUAl/FdaWnjH/2iRGco3
|
||||
7nLPXC/3CFdVNp+g2iaSQRADtAFis9N+HeL/hkCYq/RtUqa8lsP0NgacF3yWnKCy
|
||||
Ct8chDc67ZlXzBHXeCgdOwKBgHHGFPbWXUHeUW1+vbiyvrupsQSanznp8oclMtkv
|
||||
Dk48hSokw9fzuU6Jh77gw9/Vk7HtxS9Tj+squZA1bDrJFPl1u+9WzkUUJZhG6xgp
|
||||
bwhj1iejv5rrKUlVOTYOlwudXeJNa4oTNz9UEeVcaLMjZt9GmIsSC90a0uDZD26z
|
||||
AlAjAoGAEoqm2DcNN7SrH6aVFzj1EVOrNsHYiXj/yefspeiEmf27PSAslP+uF820
|
||||
SDpz4h+Bov5qTKkzcxuu1QWtA4M0K8Iy6IYLwb83DZEm1OsAf4i0pODz21PY/I+O
|
||||
VHzjB10oYgaInHZgMUdyb6F571UdiYSB6a/IlZ3ngj5touy3VIM=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
func dexMockHandler(t *testing.T, url string) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.RequestURI {
|
||||
case "/api/dex/.well-known/openid-configuration":
|
||||
_, err := io.WriteString(w, fmt.Sprintf(`
|
||||
{
|
||||
"issuer": "%[1]s/api/dex",
|
||||
"authorization_endpoint": "%[1]s/api/dex/auth",
|
||||
"token_endpoint": "%[1]s/api/dex/token",
|
||||
"jwks_uri": "%[1]s/api/dex/keys",
|
||||
"userinfo_endpoint": "%[1]s/api/dex/userinfo",
|
||||
"device_authorization_endpoint": "%[1]s/api/dex/device/code",
|
||||
"grant_types_supported": ["authorization_code"],
|
||||
"response_types_supported": ["code"],
|
||||
"subject_types_supported": ["public"],
|
||||
"id_token_signing_alg_values_supported": ["RS512"],
|
||||
"code_challenge_methods_supported": ["S256", "plain"],
|
||||
"scopes_supported": ["openid"],
|
||||
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
|
||||
"claims_supported": ["sub", "aud", "exp"]
|
||||
}`, url))
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetDexTestServer(t *testing.T) *httptest.Server {
|
||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Start with a placeholder. We need the server URL before setting up the real handler.
|
||||
}))
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
dexMockHandler(t, ts.URL)(w, r)
|
||||
})
|
||||
return ts
|
||||
}
|
||||
|
||||
func oidcMockHandler(t *testing.T, url string) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.RequestURI {
|
||||
case "/.well-known/openid-configuration":
|
||||
_, err := io.WriteString(w, fmt.Sprintf(`
|
||||
{
|
||||
"issuer": "%[1]s",
|
||||
"authorization_endpoint": "%[1]s/auth",
|
||||
"token_endpoint": "%[1]s/token",
|
||||
"jwks_uri": "%[1]s/keys",
|
||||
"userinfo_endpoint": "%[1]s/userinfo",
|
||||
"device_authorization_endpoint": "%[1]s/device/code",
|
||||
"grant_types_supported": ["authorization_code"],
|
||||
"response_types_supported": ["code"],
|
||||
"subject_types_supported": ["public"],
|
||||
"id_token_signing_alg_values_supported": ["RS512"],
|
||||
"code_challenge_methods_supported": ["S256", "plain"],
|
||||
"scopes_supported": ["openid"],
|
||||
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
|
||||
"claims_supported": ["sub", "aud", "exp"]
|
||||
}`, url))
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetOIDCTestServer(t *testing.T) *httptest.Server {
|
||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Start with a placeholder. We need the server URL before setting up the real handler.
|
||||
}))
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
oidcMockHandler(t, ts.URL)(w, r)
|
||||
})
|
||||
return ts
|
||||
}
|
||||
@@ -248,6 +248,8 @@ func generate(opts CertOptions) ([]byte, crypto.PrivateKey, error) {
|
||||
var validFor time.Duration
|
||||
if opts.ValidFor == 0 {
|
||||
validFor = 365 * 24 * time.Hour
|
||||
} else {
|
||||
validFor = opts.ValidFor
|
||||
}
|
||||
notAfter := notBefore.Add(validFor)
|
||||
|
||||
|
||||
@@ -252,6 +252,21 @@ func TestGenerate(t *testing.T) {
|
||||
assert.NotNil(t, cert)
|
||||
assert.GreaterOrEqual(t, (time.Now().Unix())+int64(1*time.Hour), cert.NotBefore.Unix())
|
||||
})
|
||||
|
||||
for _, year := range []int{1, 2, 3, 10} {
|
||||
t.Run(fmt.Sprintf("Create certificate with specified ValidFor %d year", year), func(t *testing.T) {
|
||||
validFrom, validFor := time.Now(), 365*24*time.Hour*time.Duration(year)
|
||||
opts := CertOptions{Hosts: []string{"localhost"}, Organization: "Acme", ValidFrom: validFrom, ValidFor: validFor}
|
||||
certBytes, privKey, err := generate(opts)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, privKey)
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, cert)
|
||||
t.Logf("certificate expiration time %s", cert.NotAfter)
|
||||
assert.Equal(t, validFrom.Unix()+int64(validFor.Seconds()), cert.NotAfter.Unix())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePEM(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user