diff --git a/api/http/csrf/csrf.go b/api/http/csrf/csrf.go
index d7b0516b1..487a4abfb 100644
--- a/api/http/csrf/csrf.go
+++ b/api/http/csrf/csrf.go
@@ -4,6 +4,7 @@ import (
"crypto/rand"
"fmt"
"net/http"
+ "os"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
@@ -13,6 +14,13 @@ import (
)
func WithProtect(handler http.Handler) (http.Handler, error) {
+ // IsDockerDesktopExtension is used to check if we should skip csrf checks in the request bouncer (ShouldSkipCSRFCheck)
+ // DOCKER_EXTENSION is set to '1' in build/docker-extension/docker-compose.yml
+ isDockerDesktopExtension := false
+ if val, ok := os.LookupEnv("DOCKER_EXTENSION"); ok && val == "1" {
+ isDockerDesktopExtension = true
+ }
+
handler = withSendCSRFToken(handler)
token := make([]byte, 32)
@@ -27,7 +35,7 @@ func WithProtect(handler http.Handler) (http.Handler, error) {
gorillacsrf.Secure(false),
)(handler)
- return withSkipCSRF(handler), nil
+ return withSkipCSRF(handler, isDockerDesktopExtension), nil
}
func withSendCSRFToken(handler http.Handler) http.Handler {
@@ -48,10 +56,10 @@ func withSendCSRFToken(handler http.Handler) http.Handler {
})
}
-func withSkipCSRF(handler http.Handler) http.Handler {
+func withSkipCSRF(handler http.Handler, isDockerDesktopExtension bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- skip, err := security.ShouldSkipCSRFCheck(r)
+ skip, err := security.ShouldSkipCSRFCheck(r, isDockerDesktopExtension)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, err.Error(), err)
return
diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go
index 4b7095373..56edd7f20 100644
--- a/api/http/security/bouncer.go
+++ b/api/http/security/bouncer.go
@@ -528,7 +528,12 @@ func (bouncer *RequestBouncer) EdgeComputeOperation(next http.Handler) http.Hand
// - public routes
// - kubectl - a bearer token is needed, and no csrf token can be sent
// - api token
-func ShouldSkipCSRFCheck(r *http.Request) (bool, error) {
+// - docker desktop extension
+func ShouldSkipCSRFCheck(r *http.Request, isDockerDesktopExtension bool) (bool, error) {
+ if isDockerDesktopExtension {
+ return true, nil
+ }
+
cookie, _ := r.Cookie(portainer.AuthCookieKey)
hasCookie := cookie != nil && cookie.Value != ""
diff --git a/api/http/security/bouncer_test.go b/api/http/security/bouncer_test.go
index 20220e588..b202d63c9 100644
--- a/api/http/security/bouncer_test.go
+++ b/api/http/security/bouncer_test.go
@@ -386,40 +386,52 @@ func Test_apiKeyLookup(t *testing.T) {
func Test_ShouldSkipCSRFCheck(t *testing.T) {
tt := []struct {
- name string
- cookieValue string
- apiKey string
- authHeader string
- expectedResult bool
- expectedError bool
+ name string
+ cookieValue string
+ apiKey string
+ authHeader string
+ isDockerDesktopExtension bool
+ expectedResult bool
+ expectedError bool
}{
{
- name: "Should return false when cookie is present",
- cookieValue: "test-cookie",
+ name: "Should return false (not skip) when cookie is present",
+ cookieValue: "test-cookie",
+ isDockerDesktopExtension: false,
},
{
- name: "Should return true when cookie is not present",
- cookieValue: "",
- expectedResult: true,
+ name: "Should return true (skip) when cookie is present and docker desktop extension is true",
+ cookieValue: "test-cookie",
+ isDockerDesktopExtension: true,
+ expectedResult: true,
},
{
- name: "Should return true when api key is present",
- cookieValue: "",
- apiKey: "test-api-key",
- expectedResult: true,
+ name: "Should return true (skip) when cookie is not present",
+ cookieValue: "",
+ isDockerDesktopExtension: false,
+ expectedResult: true,
},
{
- name: "Should return true when auth header is present",
- cookieValue: "",
- authHeader: "test-auth-header",
- expectedResult: true,
+ name: "Should return true (skip) when api key is present",
+ cookieValue: "",
+ apiKey: "test-api-key",
+ isDockerDesktopExtension: false,
+ expectedResult: true,
},
{
- name: "Should return false and error when both api key and auth header are present",
- cookieValue: "",
- apiKey: "test-api-key",
- authHeader: "test-auth-header",
- expectedError: true,
+ name: "Should return true (skip) when auth header is present",
+ cookieValue: "",
+ authHeader: "test-auth-header",
+ isDockerDesktopExtension: false,
+ expectedResult: true,
+ },
+ {
+ name: "Should return false (not skip) and error when both api key and auth header are present",
+ cookieValue: "",
+ apiKey: "test-api-key",
+ authHeader: "test-auth-header",
+ isDockerDesktopExtension: false,
+ expectedError: true,
},
}
@@ -437,7 +449,7 @@ func Test_ShouldSkipCSRFCheck(t *testing.T) {
req.Header.Set(jwtTokenHeader, test.authHeader)
}
- result, err := ShouldSkipCSRFCheck(req)
+ result, err := ShouldSkipCSRFCheck(req, test.isDockerDesktopExtension)
is.Equal(test.expectedResult, result)
if test.expectedError {
is.Error(err)
diff --git a/app/index.html b/app/index.html
index c27b020dd..5a21bd71b 100644
--- a/app/index.html
+++ b/app/index.html
@@ -11,7 +11,8 @@