argo-cd/server/extension/extension_test.go
Matthieu MOREL 0cff632502
chore(server): Fix modernize linter (#26324)
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2026-02-09 00:47:03 -10:00

876 lines
26 KiB
Go

package extension_test
import (
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v3/util/rbac"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/server/extension"
"github.com/argoproj/argo-cd/v3/server/extension/mocks"
dbmocks "github.com/argoproj/argo-cd/v3/util/db/mocks"
"github.com/argoproj/argo-cd/v3/util/settings"
)
func TestValidateHeaders(t *testing.T) {
t.Run("will build RequestResources successfully", func(t *testing.T) {
// given
r, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://null", http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDApplicationName, "namespace:app-name")
r.Header.Add(extension.HeaderArgoCDProjectName, "project-name")
// when
rr, err := extension.ValidateHeaders(r)
// then
require.NoError(t, err)
assert.NotNil(t, rr)
assert.Equal(t, "namespace", rr.ApplicationNamespace)
assert.Equal(t, "app-name", rr.ApplicationName)
assert.Equal(t, "project-name", rr.ProjectName)
})
t.Run("will return error if application is malformatted", func(t *testing.T) {
// given
r, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://null", http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDApplicationName, "no-namespace")
// when
rr, err := extension.ValidateHeaders(r)
// then
require.Error(t, err)
assert.Nil(t, rr)
})
t.Run("will return error if application header is missing", func(t *testing.T) {
// given
r, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://null", http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDProjectName, "project-name")
// when
rr, err := extension.ValidateHeaders(r)
// then
require.Error(t, err)
assert.Nil(t, rr)
})
t.Run("will return error if project header is missing", func(t *testing.T) {
// given
r, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://null", http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDApplicationName, "namespace:app-name")
// when
rr, err := extension.ValidateHeaders(r)
// then
require.Error(t, err)
assert.Nil(t, rr)
})
t.Run("will return error if invalid namespace", func(t *testing.T) {
// given
r, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://null", http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDApplicationName, "bad%namespace:app-name")
r.Header.Add(extension.HeaderArgoCDProjectName, "project-name")
// when
rr, err := extension.ValidateHeaders(r)
// then
require.Error(t, err)
assert.Nil(t, rr)
})
t.Run("will return error if invalid app name", func(t *testing.T) {
// given
r, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://null", http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDApplicationName, "namespace:bad@app")
r.Header.Add(extension.HeaderArgoCDProjectName, "project-name")
// when
rr, err := extension.ValidateHeaders(r)
// then
require.Error(t, err)
assert.Nil(t, rr)
})
t.Run("will return error if invalid project name", func(t *testing.T) {
// given
r, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://null", http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDApplicationName, "namespace:app")
r.Header.Add(extension.HeaderArgoCDProjectName, "bad^project")
// when
rr, err := extension.ValidateHeaders(r)
// then
require.Error(t, err)
assert.Nil(t, rr)
})
}
func TestRegisterExtensions(t *testing.T) {
t.Parallel()
type fixture struct {
settingsGetterMock *mocks.SettingsGetter
manager *extension.Manager
}
setup := func() *fixture {
settMock := &mocks.SettingsGetter{}
logger, _ := test.NewNullLogger()
logEntry := logger.WithContext(t.Context())
m := extension.NewManager(logEntry, "", settMock, nil, nil, nil, nil, nil)
return &fixture{
settingsGetterMock: settMock,
manager: m,
}
}
t.Run("will register extensions successfully", func(t *testing.T) {
// given
t.Parallel()
f := setup()
settings := &settings.ArgoCDSettings{
ExtensionConfig: map[string]string{
"": getExtensionConfigString(),
"another-ext": getSingleExtensionConfigString(),
},
}
f.settingsGetterMock.EXPECT().Get().Return(settings, nil)
expectedProxyRegistries := []string{
"external-backend",
"some-backend",
"another-ext",
}
// when
err := f.manager.RegisterExtensions()
// then
require.NoError(t, err)
for _, expectedProxyRegistry := range expectedProxyRegistries {
proxyRegistry, found := f.manager.ProxyRegistry(expectedProxyRegistry)
assert.True(t, found)
assert.NotNil(t, proxyRegistry)
}
})
t.Run("will return error if extension config is invalid", func(t *testing.T) {
// given
t.Parallel()
type testCase struct {
name string
configYaml string
}
cases := []testCase{
{
name: "no name",
configYaml: getExtensionConfigNoName(),
},
{
name: "no service",
configYaml: getExtensionConfigNoService(),
},
{
name: "no URL",
configYaml: getExtensionConfigNoURL(),
},
{
name: "invalid name",
configYaml: getExtensionConfigInvalidName(),
},
{
name: "no header name",
configYaml: getExtensionConfigNoHeaderName(),
},
{
name: "no header value",
configYaml: getExtensionConfigNoHeaderValue(),
},
}
// when
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
// given
t.Parallel()
f := setup()
settings := &settings.ArgoCDSettings{
ExtensionConfig: map[string]string{
"": tc.configYaml,
},
}
f.settingsGetterMock.EXPECT().Get().Return(settings, nil)
// when
err := f.manager.RegisterExtensions()
// then
require.Error(t, err, "expected error in test %s but got nil", tc.name)
})
}
})
}
func TestCallExtension(t *testing.T) {
t.Parallel()
type fixture struct {
mux *http.ServeMux
appGetterMock *mocks.ApplicationGetter
settingsGetterMock *mocks.SettingsGetter
rbacMock *mocks.RbacEnforcer
projMock *mocks.ProjectGetter
metricsMock *mocks.ExtensionMetricsRegistry
userMock *mocks.UserGetter
manager *extension.Manager
}
defaultServerNamespace := "control-plane-ns"
defaultProjectName := "project-name"
setup := func() *fixture {
appMock := &mocks.ApplicationGetter{}
settMock := &mocks.SettingsGetter{}
rbacMock := &mocks.RbacEnforcer{}
projMock := &mocks.ProjectGetter{}
metricsMock := &mocks.ExtensionMetricsRegistry{}
userMock := &mocks.UserGetter{}
dbMock := &dbmocks.ArgoDB{}
dbMock.EXPECT().GetClusterServersByName(mock.Anything, mock.Anything).Return([]string{"cluster1"}, nil).Maybe()
dbMock.EXPECT().GetCluster(mock.Anything, mock.Anything).Return(&v1alpha1.Cluster{Server: "some-url", Name: "cluster1"}, nil).Maybe()
logger, _ := test.NewNullLogger()
logEntry := logger.WithContext(t.Context())
m := extension.NewManager(logEntry, defaultServerNamespace, settMock, appMock, projMock, dbMock, rbacMock, userMock)
m.AddMetricsRegistry(metricsMock)
mux := http.NewServeMux()
extHandler := http.HandlerFunc(m.CallExtension())
mux.Handle(extension.URLPrefix+"/", extHandler)
return &fixture{
mux: mux,
appGetterMock: appMock,
settingsGetterMock: settMock,
rbacMock: rbacMock,
projMock: projMock,
metricsMock: metricsMock,
userMock: userMock,
manager: m,
}
}
getApp := func(destName, destServer, projName string) *v1alpha1.Application {
return &v1alpha1.Application{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{},
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Name: destName,
Server: destServer,
},
Project: projName,
},
Status: v1alpha1.ApplicationStatus{
Resources: []v1alpha1.ResourceStatus{
{
Group: "apps",
Version: "v1",
Kind: "Pod",
Namespace: "default",
Name: "some-pod",
},
},
},
}
}
getProjectWithDestinations := func(prjName string, destNames []string, destURLs []string) *v1alpha1.AppProject {
destinations := []v1alpha1.ApplicationDestination{}
for _, destName := range destNames {
destination := v1alpha1.ApplicationDestination{
Name: destName,
}
destinations = append(destinations, destination)
}
for _, destURL := range destURLs {
destination := v1alpha1.ApplicationDestination{
Server: destURL,
}
destinations = append(destinations, destination)
}
return &v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: prjName,
},
Spec: v1alpha1.AppProjectSpec{
Destinations: destinations,
},
}
}
withProject := func(prj *v1alpha1.AppProject, f *fixture) {
f.projMock.EXPECT().Get(prj.GetName()).Return(prj, nil).Maybe()
}
withMetrics := func(f *fixture) {
f.metricsMock.EXPECT().IncExtensionRequestCounter(mock.Anything, mock.Anything).Maybe()
f.metricsMock.EXPECT().ObserveExtensionRequestDuration(mock.Anything, mock.Anything).Maybe()
}
withRbac := func(f *fixture, allowApp, allowExt bool) {
var appAccessError error
var extAccessError error
if !allowApp {
appAccessError = errors.New("no app permission")
}
if !allowExt {
extAccessError = errors.New("no extension permission")
}
f.rbacMock.EXPECT().EnforceErr(mock.Anything, rbac.ResourceApplications, rbac.ActionGet, mock.Anything).Return(appAccessError).Maybe()
f.rbacMock.EXPECT().EnforceErr(mock.Anything, rbac.ResourceExtensions, rbac.ActionInvoke, mock.Anything).Return(extAccessError).Maybe()
}
withUser := func(f *fixture, userId string, username string, groups []string) {
f.userMock.EXPECT().GetUserId(mock.Anything).Return(userId).Maybe()
f.userMock.EXPECT().GetUsername(mock.Anything).Return(username).Maybe()
f.userMock.EXPECT().GetGroups(mock.Anything).Return(groups).Maybe()
}
withExtensionConfig := func(configYaml string, f *fixture) {
secrets := make(map[string]string)
secrets["extension.auth.header"] = "Bearer some-bearer-token"
secrets["extension.auth.header2"] = "Bearer another-bearer-token"
settings := &settings.ArgoCDSettings{
ExtensionConfig: map[string]string{
"ephemeral": "services:\n- url: http://some-server.com",
"": configYaml,
},
Secrets: secrets,
}
f.settingsGetterMock.EXPECT().Get().Return(settings, nil).Maybe()
}
startTestServer := func(t *testing.T, f *fixture) *httptest.Server {
t.Helper()
err := f.manager.RegisterExtensions()
require.NoError(t, err, "error starting test server")
return httptest.NewServer(f.mux)
}
startBackendTestSrv := func(response string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for k, v := range r.Header {
w.Header().Add(k, strings.Join(v, ","))
}
fmt.Fprintln(w, response)
}))
}
newExtensionRequest := func(t *testing.T, method, url string) *http.Request {
t.Helper()
r, err := http.NewRequestWithContext(t.Context(), method, url, http.NoBody)
require.NoError(t, err, "error initializing request")
r.Header.Add(extension.HeaderArgoCDApplicationName, "namespace:app-name")
r.Header.Add(extension.HeaderArgoCDProjectName, defaultProjectName)
return r
}
t.Run("will call extension backend successfully", func(t *testing.T) {
// given
t.Parallel()
f := setup()
backendResponse := "some data"
backendEndpoint := "some-backend"
clusterURL := "some-url"
backendSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for k, v := range r.Header {
w.Header().Add(k, strings.Join(v, ","))
}
fmt.Fprintln(w, backendResponse)
}))
defer backendSrv.Close()
withRbac(f, true, true)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
withExtensionConfig(getExtensionConfig(backendEndpoint, backendSrv.URL), f)
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, backendEndpoint))
app := getApp("", clusterURL, defaultProjectName)
proj := getProjectWithDestinations("project-name", nil, []string{clusterURL})
f.appGetterMock.EXPECT().Get(mock.Anything, mock.Anything).Return(app, nil).Maybe()
withProject(proj, f)
var wg sync.WaitGroup
wg.Add(2)
f.metricsMock.
On("IncExtensionRequestCounter", mock.Anything, mock.Anything).
Run(func(_ mock.Arguments) {
wg.Done()
})
f.metricsMock.
On("ObserveExtensionRequestDuration", mock.Anything, mock.Anything).
Run(func(_ mock.Arguments) {
wg.Done()
})
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
actual := strings.TrimSuffix(string(body), "\n")
assert.Equal(t, backendResponse, actual)
assert.Equal(t, defaultServerNamespace, resp.Header.Get(extension.HeaderArgoCDNamespace))
assert.Equal(t, clusterURL, resp.Header.Get(extension.HeaderArgoCDTargetClusterURL))
assert.Equal(t, "Bearer some-bearer-token", resp.Header.Get("Authorization"))
assert.Equal(t, "some-user", resp.Header.Get(extension.HeaderArgoCDUsername))
assert.Equal(t, "some-user-id", resp.Header.Get(extension.HeaderArgoCDUserId))
assert.Equal(t, "group1,group2", resp.Header.Get(extension.HeaderArgoCDGroups))
// waitgroup is necessary to make sure assertions aren't executed before
// the goroutine initiated by extension.CallExtension concludes which would
// lead to flaky test.
wg.Wait()
f.metricsMock.AssertCalled(t, "IncExtensionRequestCounter", backendEndpoint, http.StatusOK)
f.metricsMock.AssertCalled(t, "ObserveExtensionRequestDuration", backendEndpoint, mock.Anything)
})
t.Run("proxy will return 404 if extension endpoint not registered", func(t *testing.T) {
// given
t.Parallel()
f := setup()
withExtensionConfig(getExtensionConfigString(), f)
withRbac(f, true, true)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
cluster1Name := "cluster1"
f.appGetterMock.EXPECT().Get("namespace", "app-name").Return(getApp(cluster1Name, "", defaultProjectName), nil).Maybe()
withProject(getProjectWithDestinations("project-name", []string{cluster1Name}, []string{"some-url"}), f)
ts := startTestServer(t, f)
defer ts.Close()
nonRegistered := "non-registered"
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, nonRegistered))
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("will route requests with 2 backends for the same extension successfully", func(t *testing.T) {
// given
t.Parallel()
f := setup()
extName := "some-extension"
response1 := "response backend 1"
cluster1Name := "cluster1"
cluster1URL := "url1"
beSrv1 := startBackendTestSrv(response1)
defer beSrv1.Close()
response2 := "response backend 2"
cluster2Name := "cluster2"
cluster2URL := "url2"
beSrv2 := startBackendTestSrv(response2)
defer beSrv2.Close()
f.appGetterMock.EXPECT().Get("ns1", "app1").Return(getApp(cluster1Name, "", defaultProjectName), nil).Maybe()
f.appGetterMock.EXPECT().Get("ns2", "app2").Return(getApp("", cluster2URL, defaultProjectName), nil).Maybe()
withRbac(f, true, true)
withExtensionConfig(getExtensionConfigWith2Backends(extName, beSrv1.URL, cluster1Name, cluster1URL, beSrv2.URL, cluster2Name, cluster2URL), f)
withProject(getProjectWithDestinations("project-name", []string{cluster1Name}, []string{cluster2URL}), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
url := fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)
req := newExtensionRequest(t, http.MethodGet, url)
req.Header.Del(extension.HeaderArgoCDApplicationName)
req1 := req.Clone(t.Context())
req1.Header.Add(extension.HeaderArgoCDApplicationName, "ns1:app1")
req2 := req.Clone(t.Context())
req2.Header.Add(extension.HeaderArgoCDApplicationName, "ns2:app2")
// when
resp1, err := http.DefaultClient.Do(req1)
require.NoError(t, err)
resp2, err := http.DefaultClient.Do(req2)
require.NoError(t, err)
// then
require.NotNil(t, resp1)
assert.Equal(t, http.StatusOK, resp1.StatusCode)
body, err := io.ReadAll(resp1.Body)
require.NoError(t, err)
actual := strings.TrimSuffix(string(body), "\n")
assert.Equal(t, response1, actual)
assert.Equal(t, "Bearer some-bearer-token", resp1.Header.Get("Authorization"))
require.NotNil(t, resp2)
assert.Equal(t, http.StatusOK, resp2.StatusCode)
body, err = io.ReadAll(resp2.Body)
require.NoError(t, err)
actual = strings.TrimSuffix(string(body), "\n")
assert.Equal(t, response2, actual)
assert.Equal(t, "Bearer another-bearer-token", resp2.Header.Get("Authorization"))
})
t.Run("will return 401 if sub has no access to get application", func(t *testing.T) {
// given
t.Parallel()
f := setup()
allowApp := false
allowExtension := true
extName := "some-extension"
withRbac(f, allowApp, allowExtension)
withExtensionConfig(getExtensionConfig(extName, "http://fake"), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName))
f.appGetterMock.EXPECT().Get(mock.Anything, mock.Anything).Return(getApp("", "", defaultProjectName), nil).Maybe()
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("will return 401 if sub has no access to invoke extension", func(t *testing.T) {
// given
t.Parallel()
f := setup()
allowApp := true
allowExtension := false
extName := "some-extension"
withRbac(f, allowApp, allowExtension)
withExtensionConfig(getExtensionConfig(extName, "http://fake"), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName))
f.appGetterMock.EXPECT().Get(mock.Anything, mock.Anything).Return(getApp("", "", defaultProjectName), nil).Maybe()
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("will return 401 if project has no access to target cluster", func(t *testing.T) {
// given
t.Parallel()
f := setup()
allowApp := true
allowExtension := true
extName := "some-extension"
noCluster := []string{}
withRbac(f, allowApp, allowExtension)
withExtensionConfig(getExtensionConfig(extName, "http://fake"), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName))
f.appGetterMock.EXPECT().Get(mock.Anything, mock.Anything).Return(getApp("", "", defaultProjectName), nil).Maybe()
proj := getProjectWithDestinations("project-name", nil, noCluster)
withProject(proj, f)
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("will return 401 if project in application does not exist", func(t *testing.T) {
// given
t.Parallel()
f := setup()
allowApp := true
allowExtension := true
extName := "some-extension"
withRbac(f, allowApp, allowExtension)
withExtensionConfig(getExtensionConfig(extName, "http://fake"), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName))
f.appGetterMock.EXPECT().Get(mock.Anything, mock.Anything).Return(getApp("", "", defaultProjectName), nil).Maybe()
f.projMock.EXPECT().Get(defaultProjectName).Return(nil, nil).Maybe()
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("will return 401 if project in application does not match with header", func(t *testing.T) {
// given
t.Parallel()
f := setup()
allowApp := true
allowExtension := true
extName := "some-extension"
differentProject := "differentProject"
withRbac(f, allowApp, allowExtension)
withExtensionConfig(getExtensionConfig(extName, "http://fake"), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName))
f.appGetterMock.EXPECT().Get(mock.Anything, mock.Anything).Return(getApp("", "", differentProject), nil).Maybe()
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("will return 401 if application defines name and server destination", func(t *testing.T) {
// This test is to validate a security risk with malicious application
// trying to gain access to execute extensions in clusters it doesn't
// have access.
// given
t.Parallel()
f := setup()
extName := "some-extension"
maliciousName := "srv1"
destinationServer := "some-valid-server"
f.appGetterMock.EXPECT().Get("ns1", "app1").Return(getApp(maliciousName, destinationServer, defaultProjectName), nil).Maybe()
withRbac(f, true, true)
withExtensionConfig(getExtensionConfigWith2Backends(extName, "url1", "cluster1Name", "cluster1URL", "url2", "cluster2Name", "cluster2URL"), f)
withProject(getProjectWithDestinations("project-name", nil, []string{"srv1", destinationServer}), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
url := fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)
req := newExtensionRequest(t, http.MethodGet, url)
req.Header.Del(extension.HeaderArgoCDApplicationName)
req1 := req.Clone(t.Context())
req1.Header.Add(extension.HeaderArgoCDApplicationName, "ns1:app1")
// when
resp1, err := http.DefaultClient.Do(req1)
require.NoError(t, err)
// then
require.NotNil(t, resp1)
assert.Equal(t, http.StatusUnauthorized, resp1.StatusCode)
body, err := io.ReadAll(resp1.Body)
require.NoError(t, err)
actual := strings.TrimSuffix(string(body), "\n")
assert.Equal(t, "Unauthorized extension request", actual)
})
t.Run("will return 400 if no extension name is provided", func(t *testing.T) {
// given
t.Parallel()
f := setup()
allowApp := true
allowExtension := true
extName := "some-extension"
differentProject := "differentProject"
withRbac(f, allowApp, allowExtension)
withExtensionConfig(getExtensionConfig(extName, "http://fake"), f)
withMetrics(f)
withUser(f, "some-user-id", "some-user", []string{"group1", "group2"})
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", ts.URL+"/extensions/")
f.appGetterMock.EXPECT().Get(mock.Anything, mock.Anything).Return(getApp("", "", differentProject), nil).Maybe()
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
}
func getExtensionConfig(name, url string) string {
cfg := `
extensions:
- name: %s
backend:
services:
- url: %s
headers:
- name: Authorization
value: '$extension.auth.header'
`
return fmt.Sprintf(cfg, name, url)
}
func getExtensionConfigWith2Backends(name, url1, clus1Name, clus1URL, url2, clus2Name, clus2URL string) string {
cfg := `
extensions:
- name: %s
backend:
services:
- url: %s
headers:
- name: Authorization
value: '$extension.auth.header'
cluster:
name: %s
server: %s
- url: %s
headers:
- name: Authorization
value: '$extension.auth.header2'
cluster:
name: %s
server: %s
- url: http://test.com
cluster:
name: cl1
- url: http://test2.com
cluster:
name: cl2
`
return fmt.Sprintf(cfg, name, url1, clus1Name, clus1URL, url2, clus2Name, clus2URL)
}
func getExtensionConfigString() string {
return `
extensions:
- name: external-backend
backend:
connectionTimeout: 10s
keepAlive: 11s
idleConnectionTimeout: 12s
maxIdleConnections: 30
services:
- url: https://httpbin.org
headers:
- name: some-header
value: '$some.secret.ref'
- name: some-backend
backend:
services:
- url: http://localhost:7777
`
}
func getSingleExtensionConfigString() string {
return `
connectionTimeout: 10s
keepAlive: 11s
idleConnectionTimeout: 12s
maxIdleConnections: 30
services:
- url: http://localhost:7777
`
}
func getExtensionConfigNoService() string {
return `
extensions:
- backend:
connectionTimeout: 2s
`
}
func getExtensionConfigNoName() string {
return `
extensions:
- backend:
services:
- url: https://httpbin.org
`
}
func getExtensionConfigInvalidName() string {
return `
extensions:
- name: invalid/name
backend:
services:
- url: https://httpbin.org
`
}
func getExtensionConfigNoURL() string {
return `
extensions:
- name: some-backend
backend:
services:
- cluster: some-cluster
`
}
func getExtensionConfigNoHeaderName() string {
return `
extensions:
- name: some-extension
backend:
services:
- url: https://httpbin.org
headers:
- value: '$some.secret.key'
`
}
func getExtensionConfigNoHeaderValue() string {
return `
extensions:
- name: some-extension
backend:
services:
- url: https://httpbin.org
headers:
- name: some-header-name
`
}