Compare commits

...

12 Commits

Author SHA1 Message Date
Steven Kang dbeccc4e1e bump version to 2.27.8 (#824) 2025-06-25 09:54:28 +12:00
Cara Ryan 7fd5b96130 fix(kubernetes): Namespace access permission changes role bindings not created [R8S-366] (#807)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: Malcolm Lockyer <segfault88@users.noreply.github.com>
2025-06-25 09:24:18 +12:00
Steven Kang ee6d33365e bump version to 2.27.7 (#804) 2025-06-17 09:43:15 +12:00
Steven Kang e115055a1b security: cve-2025-22874 & cve-2025-22871 bump go to 1.23.10 (#799) 2025-06-12 17:30:49 +12:00
Devon Steenberg 384cb53c64 fix(proxy): whitelist headers for proxy to forward [BE-11819] (#760) 2025-05-30 11:49:41 +12:00
Oscar Zhou 4240cbf029 fix(csrf): skip trustedorigin for http request and check x-forwarded-proto for reverse proxy [BE-11832] (#713) 2025-05-09 13:45:33 +12:00
Steven Kang eb28dd4f4e chore: bump version to 2.27.6 (#720) 2025-05-09 09:57:27 +12:00
Steven Kang 78127f8f3d chore: bump version to 2.27.5 (#704) 2025-05-02 10:08:56 +12:00
Oscar Zhou c474322889 fix(dependencies): downgrade gorilla/csrf to v1.7.2 [BE-11832] (#689) 2025-04-24 12:14:18 +12:00
Oscar Zhou 83527da1a8 fix: cve-2025-22871 [BE-11825] (#677) 2025-04-22 21:29:18 +12:00
Oscar Zhou 7c8bef84b1 feat(docker): backport --pull-limit-check-disabled cli flag [BE-11820] (#658) 2025-04-16 19:28:43 +12:00
Steven Kang 5b3dba130b chore: bump version to 2.27.4 (#645) 2025-04-15 10:24:20 +12:00
18 changed files with 346 additions and 70 deletions
+1
View File
@@ -60,6 +60,7 @@ func CLIFlags() *portainer.CLIFlags {
LogLevel: kingpin.Flag("log-level", "Set the minimum logging level to show").Default("INFO").Enum("DEBUG", "INFO", "WARN", "ERROR"),
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("NOCOLOR", "PRETTY", "JSON"),
KubectlShellImage: kingpin.Flag("kubectl-shell-image", "Kubectl shell image").Envar(portainer.KubectlShellImageEnvVar).Default(portainer.DefaultKubectlShellImage).String(),
PullLimitCheckDisabled: kingpin.Flag("pull-limit-check-disabled", "Pull limit check").Envar(portainer.PullLimitCheckDisabledEnvVar).Default(defaultPullLimitCheckDisabled).Bool(),
}
}
+17 -16
View File
@@ -4,20 +4,21 @@
package cli
const (
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultPullLimitCheckDisabled = "false"
)
+18 -17
View File
@@ -1,21 +1,22 @@
package cli
const (
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultSnapshotInterval = "5m"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultBindAddress = ":9000"
defaultHTTPSBindAddress = ":9443"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultHTTPDisabled = "false"
defaultHTTPEnabled = "false"
defaultSSL = "false"
defaultSnapshotInterval = "5m"
defaultBaseURL = "/"
defaultSecretKeyName = "portainer"
defaultPullLimitCheckDisabled = "false"
)
+1
View File
@@ -575,6 +575,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
AdminCreationDone: adminCreationDone,
PendingActionsService: pendingActionsService,
PlatformService: platformService,
PullLimitCheckDisabled: *flags.PullLimitCheckDisabled,
}
}
@@ -610,7 +610,7 @@
"RequiredPasswordLength": 12
},
"KubeconfigExpiry": "0",
"KubectlShellImage": "portainer/kubectl-shell:2.27.3",
"KubectlShellImage": "portainer/kubectl-shell:2.27.8",
"LDAPSettings": {
"AnonymousMode": true,
"AutoCreateUsers": true,
@@ -943,7 +943,7 @@
}
],
"version": {
"VERSION": "{\"SchemaVersion\":\"2.27.3\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
"VERSION": "{\"SchemaVersion\":\"2.27.8\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
},
"webhooks": null
}
@@ -80,6 +80,13 @@ func (handler *Handler) endpointDockerhubStatus(w http.ResponseWriter, r *http.R
}
}
if handler.PullLimitCheckDisabled {
return response.JSON(w, &dockerhubStatusResponse{
Limit: 10,
Remaining: 10,
})
}
httpClient := client.NewHTTPClient()
token, err := getDockerHubToken(httpClient, registry)
if err != nil {
@@ -75,7 +75,7 @@ func (handler *Handler) listRegistries(tx dataservices.DataStoreTx, r *http.Requ
return nil, httperror.InternalServerError("Unable to retrieve registries from the database", err)
}
registries, handleError := handler.filterRegistriesByAccess(r, registries, endpoint, user, securityContext.UserMemberships)
registries, handleError := handler.filterRegistriesByAccess(tx, r, registries, endpoint, user, securityContext.UserMemberships)
if handleError != nil {
return nil, handleError
}
@@ -87,15 +87,15 @@ func (handler *Handler) listRegistries(tx dataservices.DataStoreTx, r *http.Requ
return registries, err
}
func (handler *Handler) filterRegistriesByAccess(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User, memberships []portainer.TeamMembership) ([]portainer.Registry, *httperror.HandlerError) {
func (handler *Handler) filterRegistriesByAccess(tx dataservices.DataStoreTx, r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User, memberships []portainer.TeamMembership) ([]portainer.Registry, *httperror.HandlerError) {
if !endpointutils.IsKubernetesEndpoint(endpoint) {
return security.FilterRegistries(registries, user, memberships, endpoint.ID), nil
}
return handler.filterKubernetesEndpointRegistries(r, registries, endpoint, user, memberships)
return handler.filterKubernetesEndpointRegistries(tx, r, registries, endpoint, user, memberships)
}
func (handler *Handler) filterKubernetesEndpointRegistries(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User, memberships []portainer.TeamMembership) ([]portainer.Registry, *httperror.HandlerError) {
func (handler *Handler) filterKubernetesEndpointRegistries(tx dataservices.DataStoreTx, r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User, memberships []portainer.TeamMembership) ([]portainer.Registry, *httperror.HandlerError) {
namespaceParam, _ := request.RetrieveQueryParameter(r, "namespace", true)
isAdmin, err := security.IsAdmin(r)
if err != nil {
@@ -116,7 +116,7 @@ func (handler *Handler) filterKubernetesEndpointRegistries(r *http.Request, regi
return registries, nil
}
return handler.filterKubernetesRegistriesByUserRole(r, registries, endpoint, user)
return handler.filterKubernetesRegistriesByUserRole(tx, r, registries, endpoint, user)
}
func (handler *Handler) isNamespaceAuthorized(endpoint *portainer.Endpoint, namespace string, userId portainer.UserID, memberships []portainer.TeamMembership, isAdmin bool) (bool, error) {
@@ -169,7 +169,7 @@ func registryAccessPoliciesContainsNamespace(registryAccess portainer.RegistryAc
return false
}
func (handler *Handler) filterKubernetesRegistriesByUserRole(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User) ([]portainer.Registry, *httperror.HandlerError) {
func (handler *Handler) filterKubernetesRegistriesByUserRole(tx dataservices.DataStoreTx, r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User) ([]portainer.Registry, *httperror.HandlerError) {
err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if errors.Is(err, security.ErrAuthorizationRequired) {
return nil, httperror.Forbidden("User is not authorized", err)
@@ -178,7 +178,7 @@ func (handler *Handler) filterKubernetesRegistriesByUserRole(r *http.Request, re
return nil, httperror.InternalServerError("Unable to retrieve info from request context", err)
}
userNamespaces, err := handler.userNamespaces(endpoint, user)
userNamespaces, err := handler.userNamespaces(tx, endpoint, user)
if err != nil {
return nil, httperror.InternalServerError("unable to retrieve user namespaces", err)
}
@@ -186,7 +186,7 @@ func (handler *Handler) filterKubernetesRegistriesByUserRole(r *http.Request, re
return filterRegistriesByNamespaces(registries, endpoint.ID, userNamespaces), nil
}
func (handler *Handler) userNamespaces(endpoint *portainer.Endpoint, user *portainer.User) ([]string, error) {
func (handler *Handler) userNamespaces(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint, user *portainer.User) ([]string, error) {
kcl, err := handler.K8sClientFactory.GetPrivilegedKubeClient(endpoint)
if err != nil {
return nil, err
@@ -197,7 +197,7 @@ func (handler *Handler) userNamespaces(endpoint *portainer.Endpoint, user *porta
return nil, err
}
userMemberships, err := handler.DataStore.TeamMembership().TeamMembershipsByUserID(user.ID)
userMemberships, err := tx.TeamMembership().TeamMembershipsByUserID(user.ID)
if err != nil {
return nil, err
}
+14 -13
View File
@@ -26,19 +26,20 @@ func hideFields(endpoint *portainer.Endpoint) {
// Handler is the HTTP handler used to handle environment(endpoint) operations.
type Handler struct {
*mux.Router
requestBouncer security.BouncerService
DataStore dataservices.DataStore
FileService portainer.FileService
ProxyManager *proxy.Manager
ReverseTunnelService portainer.ReverseTunnelService
SnapshotService portainer.SnapshotService
K8sClientFactory *cli.ClientFactory
ComposeStackManager portainer.ComposeStackManager
AuthorizationService *authorization.Service
DockerClientFactory *dockerclient.ClientFactory
BindAddress string
BindAddressHTTPS string
PendingActionsService *pendingactions.PendingActionsService
requestBouncer security.BouncerService
DataStore dataservices.DataStore
FileService portainer.FileService
ProxyManager *proxy.Manager
ReverseTunnelService portainer.ReverseTunnelService
SnapshotService portainer.SnapshotService
K8sClientFactory *cli.ClientFactory
ComposeStackManager portainer.ComposeStackManager
AuthorizationService *authorization.Service
DockerClientFactory *dockerclient.ClientFactory
BindAddress string
BindAddressHTTPS string
PendingActionsService *pendingactions.PendingActionsService
PullLimitCheckDisabled bool
}
// NewHandler creates a handler to manage environment(endpoint) operations.
+1 -1
View File
@@ -81,7 +81,7 @@ type Handler struct {
}
// @title PortainerCE API
// @version 2.27.3
// @version 2.27.8
// @description.markdown api-description.md
// @termsOfService
@@ -0,0 +1,36 @@
package middlewares
import (
"net/http"
"slices"
"github.com/gorilla/csrf"
)
var (
// Idempotent (safe) methods as defined by RFC7231 section 4.2.2.
safeMethods = []string{"GET", "HEAD", "OPTIONS", "TRACE"}
)
type plainTextHTTPRequestHandler struct {
next http.Handler
}
func (h *plainTextHTTPRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if slices.Contains(safeMethods, r.Method) {
h.next.ServeHTTP(w, r)
return
}
req := r
// If original request was HTTPS (via proxy), keep CSRF checks.
if xfproto := r.Header.Get("X-Forwarded-Proto"); xfproto != "https" {
req = csrf.PlaintextHTTPRequest(r)
}
h.next.ServeHTTP(w, req)
}
func PlaintextHTTPRequest(next http.Handler) http.Handler {
return &plainTextHTTPRequestHandler{next: next}
}
+27 -2
View File
@@ -7,12 +7,31 @@ import (
"strings"
)
// Note that we discard any non-canonical headers by design
var allowedHeaders = map[string]struct{}{
"Accept": {},
"Accept-Encoding": {},
"Accept-Language": {},
"Cache-Control": {},
"Content-Length": {},
"Content-Type": {},
"Private-Token": {},
"User-Agent": {},
"X-Portaineragent-Target": {},
"X-Portainer-Volumename": {},
"X-Registry-Auth": {},
}
// newSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy
// from golang.org/src/net/http/httputil/reverseproxy.go and merely sets the Host
// HTTP header, which NewSingleHostReverseProxy deliberately preserves.
func newSingleHostReverseProxyWithHostHeader(target *url.URL) *httputil.ReverseProxy {
return &httputil.ReverseProxy{Director: createDirector(target)}
}
func createDirector(target *url.URL) func(*http.Request) {
targetQuery := target.RawQuery
director := func(req *http.Request) {
return func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
@@ -26,8 +45,14 @@ func newSingleHostReverseProxyWithHostHeader(target *url.URL) *httputil.ReverseP
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
for k := range req.Header {
if _, ok := allowedHeaders[k]; !ok {
// We use delete here instead of req.Header.Del because we want to delete non canonical headers.
delete(req.Header, k)
}
}
}
return &httputil.ReverseProxy{Director: director}
}
// singleJoiningSlash from golang.org/src/net/http/httputil/reverseproxy.go
@@ -0,0 +1,190 @@
package factory
import (
"net/http"
"net/url"
"testing"
"github.com/google/go-cmp/cmp"
portainer "github.com/portainer/portainer/api"
)
func Test_createDirector(t *testing.T) {
testCases := []struct {
name string
target *url.URL
req *http.Request
expectedReq *http.Request
}{
{
name: "base case",
target: createURL(t, "https://portainer.io/api/docker?a=5&b=6"),
req: createRequest(
t,
"GET",
"https://agent-portainer.io/test?c=7",
map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json", "User-Agent": "something"},
true,
),
expectedReq: createRequest(
t,
"GET",
"https://portainer.io/api/docker/test?a=5&b=6&c=7",
map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json", "User-Agent": "something"},
true,
),
},
{
name: "no User-Agent",
target: createURL(t, "https://portainer.io/api/docker?a=5&b=6"),
req: createRequest(
t,
"GET",
"https://agent-portainer.io/test?c=7",
map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json"},
true,
),
expectedReq: createRequest(
t,
"GET",
"https://portainer.io/api/docker/test?a=5&b=6&c=7",
map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json", "User-Agent": ""},
true,
),
},
{
name: "Sensitive Headers",
target: createURL(t, "https://portainer.io/api/docker?a=5&b=6"),
req: createRequest(
t,
"GET",
"https://agent-portainer.io/test?c=7",
map[string]string{
"Authorization": "secret",
"Proxy-Authorization": "secret",
"Cookie": "secret",
"X-Csrf-Token": "secret",
"X-Api-Key": "secret",
"Accept": "application/json",
"Accept-Encoding": "gzip",
"Accept-Language": "en-GB",
"Cache-Control": "None",
"Content-Length": "100",
"Content-Type": "application/json",
"Private-Token": "test-private-token",
"User-Agent": "test-user-agent",
"X-Portaineragent-Target": "test-agent-1",
"X-Portainer-Volumename": "test-volume-1",
"X-Registry-Auth": "test-registry-auth",
},
true,
),
expectedReq: createRequest(
t,
"GET",
"https://portainer.io/api/docker/test?a=5&b=6&c=7",
map[string]string{
"Accept": "application/json",
"Accept-Encoding": "gzip",
"Accept-Language": "en-GB",
"Cache-Control": "None",
"Content-Length": "100",
"Content-Type": "application/json",
"Private-Token": "test-private-token",
"User-Agent": "test-user-agent",
"X-Portaineragent-Target": "test-agent-1",
"X-Portainer-Volumename": "test-volume-1",
"X-Registry-Auth": "test-registry-auth",
},
true,
),
},
{
name: "Non canonical Headers",
target: createURL(t, "https://portainer.io/api/docker?a=5&b=6"),
req: createRequest(
t,
"GET",
"https://agent-portainer.io/test?c=7",
map[string]string{
"Accept": "application/json",
"Accept-Encoding": "gzip",
"Accept-Language": "en-GB",
"Cache-Control": "None",
"Content-Length": "100",
"Content-Type": "application/json",
"Private-Token": "test-private-token",
"User-Agent": "test-user-agent",
portainer.PortainerAgentTargetHeader: "test-agent-1",
"X-Portainer-VolumeName": "test-volume-1",
"X-Registry-Auth": "test-registry-auth",
},
false,
),
expectedReq: createRequest(
t,
"GET",
"https://portainer.io/api/docker/test?a=5&b=6&c=7",
map[string]string{
"Accept": "application/json",
"Accept-Encoding": "gzip",
"Accept-Language": "en-GB",
"Cache-Control": "None",
"Content-Length": "100",
"Content-Type": "application/json",
"Private-Token": "test-private-token",
"User-Agent": "test-user-agent",
"X-Registry-Auth": "test-registry-auth",
},
true,
),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
director := createDirector(tc.target)
director(tc.req)
if diff := cmp.Diff(tc.req, tc.expectedReq, cmp.Comparer(compareRequests)); diff != "" {
t.Fatalf("requests are different: \n%s", diff)
}
})
}
}
func createURL(t *testing.T, urlString string) *url.URL {
parsedURL, err := url.Parse(urlString)
if err != nil {
t.Fatalf("Failed to create url: %s", err)
}
return parsedURL
}
func createRequest(t *testing.T, method, url string, headers map[string]string, canonicalHeaders bool) *http.Request {
req, err := http.NewRequest(method, url, nil)
if err != nil {
t.Fatalf("Failed to create http request: %s", err)
} else {
for k, v := range headers {
if canonicalHeaders {
req.Header.Add(k, v)
} else {
req.Header[k] = []string{v}
}
}
}
return req
}
func compareRequests(a, b *http.Request) bool {
methodEqual := a.Method == b.Method
urlEqual := cmp.Diff(a.URL, b.URL) == ""
hostEqual := a.Host == b.Host
protoEqual := a.Proto == b.Proto && a.ProtoMajor == b.ProtoMajor && a.ProtoMinor == b.ProtoMinor
headersEqual := cmp.Diff(a.Header, b.Header) == ""
return methodEqual && urlEqual && hostEqual && protoEqual && headersEqual
}
+3 -1
View File
@@ -112,6 +112,7 @@ type Server struct {
AdminCreationDone chan struct{}
PendingActionsService *pendingactions.PendingActionsService
PlatformService platform.Service
PullLimitCheckDisabled bool
}
// Start starts the HTTP server
@@ -181,6 +182,7 @@ func (server *Server) Start() error {
endpointHandler.BindAddress = server.BindAddress
endpointHandler.BindAddressHTTPS = server.BindAddressHTTPS
endpointHandler.PendingActionsService = server.PendingActionsService
endpointHandler.PullLimitCheckDisabled = server.PullLimitCheckDisabled
var endpointEdgeHandler = endpointedge.NewHandler(requestBouncer, server.DataStore, server.FileService, server.ReverseTunnelService)
@@ -347,7 +349,7 @@ func (server *Server) Start() error {
log.Info().Str("bind_address", server.BindAddress).Msg("starting HTTP server")
httpServer := &http.Server{
Addr: server.BindAddress,
Handler: handler,
Handler: middlewares.PlaintextHTTPRequest(handler),
ErrorLog: errorLogger,
}
+8
View File
@@ -87,6 +87,14 @@ func (factory *ClientFactory) ClearClientCache() {
// Remove the cached kube client so a new one can be created
func (factory *ClientFactory) RemoveKubeClient(endpointID portainer.EndpointID) {
factory.endpointProxyClients.Delete(strconv.Itoa(int(endpointID)))
endpointPrefix := strconv.Itoa(int(endpointID)) + "."
for key := range factory.endpointProxyClients.Items() {
if strings.HasPrefix(key, endpointPrefix) {
factory.endpointProxyClients.Delete(key)
}
}
}
// GetPrivilegedKubeClient checks if an existing client is already registered for the environment(endpoint) and returns it if one is found.
+4 -1
View File
@@ -134,6 +134,7 @@ type (
LogLevel *string
LogMode *string
KubectlShellImage *string
PullLimitCheckDisabled *bool
}
// CustomTemplateVariableDefinition
@@ -1637,7 +1638,7 @@ type (
const (
// APIVersion is the version number of the Portainer API
APIVersion = "2.27.3"
APIVersion = "2.27.8"
// Support annotation for the API version ("STS" for Short-Term Support or "LTS" for Long-Term Support)
APIVersionSupport = "LTS"
// Edition is what this edition of Portainer is called
@@ -1689,6 +1690,8 @@ const (
PortainerCacheHeader = "X-Portainer-Cache"
// KubectlShellImageEnvVar is the environment variable used to override the default kubectl shell image
KubectlShellImageEnvVar = "KUBECTL_SHELL_IMAGE"
// PullLimitCheckDisabledEnvVar is the environment variable used to disable the pull limit check
PullLimitCheckDisabledEnvVar = "PULL_LIMIT_CHECK_DISABLED"
)
// List of supported features
+3 -3
View File
@@ -1,6 +1,6 @@
module github.com/portainer/portainer
go 1.23.5
go 1.23.10
require (
github.com/Masterminds/semver v1.5.0
@@ -27,7 +27,7 @@ require (
github.com/gofrs/uuid v4.2.0+incompatible
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/google/go-cmp v0.6.0
github.com/gorilla/csrf v1.7.2
github.com/gorilla/csrf v1.7.3
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/go-version v1.7.0
@@ -242,7 +242,7 @@ require (
go.opentelemetry.io/otel/trace v1.25.0 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
+4 -4
View File
@@ -311,8 +311,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0=
github.com/gorilla/csrf v1.7.3/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
@@ -715,8 +715,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+1 -1
View File
@@ -2,7 +2,7 @@
"author": "Portainer.io",
"name": "portainer",
"homepage": "http://portainer.io",
"version": "2.27.3",
"version": "2.27.8",
"repository": {
"type": "git",
"url": "git@github.com:portainer/portainer.git"