Compare commits
3 Commits
2.21.0-rc1
...
2.21.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 111f641979 | |||
| da370316df | |||
| f69825d859 |
+11
-3
@@ -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
|
||||
|
||||
@@ -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 != ""
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/unique"
|
||||
)
|
||||
|
||||
var ErrEdgeGroupNotFound = errors.New("edge group was not found")
|
||||
@@ -32,7 +33,7 @@ func EdgeStackRelatedEndpoints(edgeGroupIDs []portainer.EdgeGroupID, endpoints [
|
||||
edgeStackEndpoints = append(edgeStackEndpoints, EdgeGroupRelatedEndpoints(edgeGroup, endpoints, endpointGroups)...)
|
||||
}
|
||||
|
||||
return edgeStackEndpoints, nil
|
||||
return unique.Unique(edgeStackEndpoints), nil
|
||||
}
|
||||
|
||||
type EndpointRelationsConfig struct {
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@
|
||||
<meta name="robots" content="noindex" />
|
||||
<base id="base" />
|
||||
<script>
|
||||
if (window.origin == 'file://') {
|
||||
// http://localhost:49000 is a docker extension specific url (see /build/docker-extension/docker-compose.yml)
|
||||
if (window.origin == 'http://localhost:49000') {
|
||||
// we are loading the app from a local file as in docker extension
|
||||
document.getElementById('base').href = 'http://localhost:49000/';
|
||||
|
||||
|
||||
@@ -47,8 +47,12 @@ export function HomeView() {
|
||||
We could not connect your local environment to Portainer.
|
||||
<br />
|
||||
Please ensure your environment is correctly exposed. For
|
||||
help with installation visit
|
||||
<a href="https://documentation.portainer.io/quickstart/">
|
||||
help with installation visit{' '}
|
||||
<a
|
||||
href="https://documentation.portainer.io/quickstart/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://documentation.portainer.io/quickstart
|
||||
</a>
|
||||
</p>
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"docker": "v27.0.3",
|
||||
"dockerCompose": "v2.28.1",
|
||||
"helm": "v3.15.2",
|
||||
"kubectl": "v1.30.2",
|
||||
"docker": "v27.1.2",
|
||||
"dockerCompose": "v2.29.2",
|
||||
"helm": "v3.15.4",
|
||||
"kubectl": "v1.31.0",
|
||||
"mingit": "2.45.2.1"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ build-remote:
|
||||
docker buildx build -f build/linux/Dockerfile --push --builder=buildx-multi-arch --platform=windows/amd64,linux/amd64,linux/arm64 --build-arg TAG=$(VERSION) --build-arg PORTAINER_IMAGE_NAME=$(IMAGE_NAME) --tag=$(TAGGED_IMAGE_NAME) .
|
||||
|
||||
install:
|
||||
docker extension install $(TAGGED_IMAGE_NAME)
|
||||
docker extension install $(TAGGED_IMAGE_NAME) --force
|
||||
|
||||
dev:
|
||||
docker extension dev debug $(IMAGE_NAME)
|
||||
|
||||
multiarch:
|
||||
docker buildx create --name=buildx-multi-arch --driver=docker-container --driver-opt=network=host
|
||||
|
||||
@@ -20,10 +20,9 @@ Next you must install the CLI plugin to enable extension development. Please fol
|
||||
|
||||
### Build from local changes
|
||||
|
||||
1. Run `yarn` to install the project dependencies
|
||||
2. Run `yarn dev:extension` to install the extension
|
||||
3. Make your code changes
|
||||
4. Re-run `yarn dev:extension` to rebuild and re-install with your latest changes
|
||||
1. Run `make dev-extension` to install the project dependencies and start in development mode (note that this doesn't do live updates for frontend changes).
|
||||
2. Make your code changes
|
||||
3. Re-run `make dev-extension` to rebuild and re-install with your latest changes
|
||||
|
||||
## Accessing the Portainer extension
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"dashboard-tab": {
|
||||
"title": "Portainer",
|
||||
"root": "/public",
|
||||
"src": "index.html"
|
||||
"src": "http://localhost:49000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ module github.com/portainer/portainer
|
||||
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.11
|
||||
toolchain go1.21.12
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
@@ -17,7 +17,7 @@ require (
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5
|
||||
github.com/docker/cli v26.0.2+incompatible
|
||||
github.com/docker/docker v26.0.2+incompatible
|
||||
github.com/docker/docker v26.1.5+incompatible
|
||||
github.com/fvbommel/sortorder v1.0.2
|
||||
github.com/fxamacker/cbor/v2 v2.4.0
|
||||
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
|
||||
|
||||
@@ -94,8 +94,8 @@ github.com/docker/cli v26.0.2+incompatible h1:4C4U8ZqrlNDe/R1U1zFFX+YsCFiVUicJqo
|
||||
github.com/docker/cli v26.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE=
|
||||
github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g=
|
||||
github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
|
||||
Reference in New Issue
Block a user