[security] enforce process limits at a per-container level to avoid abusive clients impacting other instances
This commit is contained in:
parent
c0063d2c61
commit
e0078eee0a
|
@ -55,6 +55,12 @@ type DockerConfiguration struct {
|
||||||
// utilizes host memory for this value, and that we do not keep track of the space used here
|
// utilizes host memory for this value, and that we do not keep track of the space used here
|
||||||
// so avoid allocating too much to a server.
|
// so avoid allocating too much to a server.
|
||||||
TmpfsSize uint `default:"100" json:"tmpfs_size" yaml:"tmpfs_size"`
|
TmpfsSize uint `default:"100" json:"tmpfs_size" yaml:"tmpfs_size"`
|
||||||
|
|
||||||
|
// ContainerPidLimit sets the total number of processes that can be active in a container
|
||||||
|
// at any given moment. This is a security concern in shared-hosting environments where a
|
||||||
|
// malicious process could create enough processes to cause the host node to run out of
|
||||||
|
// available pids and crash.
|
||||||
|
ContainerPidLimit int64 `default:"256" json:"container_pid_limit" yaml:"container_pid_limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistryConfiguration defines the authentication credentials for a given
|
// RegistryConfiguration defines the authentication credentials for a given
|
||||||
|
|
|
@ -486,6 +486,7 @@ func (e *Environment) convertMounts() []mount.Mount {
|
||||||
|
|
||||||
func (e *Environment) resources() container.Resources {
|
func (e *Environment) resources() container.Resources {
|
||||||
l := e.Configuration.Limits()
|
l := e.Configuration.Limits()
|
||||||
|
pids := l.ProcessLimit()
|
||||||
|
|
||||||
return container.Resources{
|
return container.Resources{
|
||||||
Memory: l.BoundedMemoryLimit(),
|
Memory: l.BoundedMemoryLimit(),
|
||||||
|
@ -497,5 +498,6 @@ func (e *Environment) resources() container.Resources {
|
||||||
BlkioWeight: l.IoWeight,
|
BlkioWeight: l.IoWeight,
|
||||||
OomKillDisable: &l.OOMDisabled,
|
OomKillDisable: &l.OOMDisabled,
|
||||||
CpusetCpus: l.Threads,
|
CpusetCpus: l.Threads,
|
||||||
|
PidsLimit: &pids,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mount struct {
|
type Mount struct {
|
||||||
|
@ -28,8 +29,8 @@ type Mount struct {
|
||||||
ReadOnly bool `json:"read_only"`
|
ReadOnly bool `json:"read_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The build settings for a given server that impact docker container creation and
|
// Limits is the build settings for a given server that impact docker container
|
||||||
// resource limits for a server instance.
|
// creation and resource limits for a server instance.
|
||||||
type Limits struct {
|
type Limits struct {
|
||||||
// The total amount of memory in megabytes that this server is allowed to
|
// The total amount of memory in megabytes that this server is allowed to
|
||||||
// use on the host system.
|
// use on the host system.
|
||||||
|
@ -56,9 +57,9 @@ type Limits struct {
|
||||||
OOMDisabled bool `json:"oom_disabled"`
|
OOMDisabled bool `json:"oom_disabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts the CPU limit for a server build into a number that can be better understood
|
// ConvertedCpuLimit converts the CPU limit for a server build into a number
|
||||||
// by the Docker environment. If there is no limit set, return -1 which will indicate to
|
// that can be better understood by the Docker environment. If there is no limit
|
||||||
// Docker that it has unlimited CPU quota.
|
// set, return -1 which will indicate to Docker that it has unlimited CPU quota.
|
||||||
func (r *Limits) ConvertedCpuLimit() int64 {
|
func (r *Limits) ConvertedCpuLimit() int64 {
|
||||||
if r.CpuLimit == 0 {
|
if r.CpuLimit == 0 {
|
||||||
return -1
|
return -1
|
||||||
|
@ -67,9 +68,10 @@ func (r *Limits) ConvertedCpuLimit() int64 {
|
||||||
return r.CpuLimit * 1000
|
return r.CpuLimit * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the hard limit for memory usage to be 5% more than the amount of memory assigned to
|
// MemoryOverheadMultiplier sets the hard limit for memory usage to be 5% more
|
||||||
// the server. If the memory limit for the server is < 4G, use 10%, if less than 2G use
|
// than the amount of memory assigned to the server. If the memory limit for the
|
||||||
// 15%. This avoids unexpected crashes from processes like Java which run over the limit.
|
// server is < 4G, use 10%, if less than 2G use 15%. This avoids unexpected
|
||||||
|
// crashes from processes like Java which run over the limit.
|
||||||
func (r *Limits) MemoryOverheadMultiplier() float64 {
|
func (r *Limits) MemoryOverheadMultiplier() float64 {
|
||||||
if r.MemoryLimit <= 2048 {
|
if r.MemoryLimit <= 2048 {
|
||||||
return 1.15
|
return 1.15
|
||||||
|
@ -84,9 +86,9 @@ func (r *Limits) BoundedMemoryLimit() int64 {
|
||||||
return int64(math.Round(float64(r.MemoryLimit) * r.MemoryOverheadMultiplier() * 1_000_000))
|
return int64(math.Round(float64(r.MemoryLimit) * r.MemoryOverheadMultiplier() * 1_000_000))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the amount of swap available as a total in bytes. This is returned as the amount
|
// ConvertedSwap returns the amount of swap available as a total in bytes. This
|
||||||
// of memory available to the server initially, PLUS the amount of additional swap to include
|
// is returned as the amount of memory available to the server initially, PLUS
|
||||||
// which is the format used by Docker.
|
// the amount of additional swap to include which is the format used by Docker.
|
||||||
func (r *Limits) ConvertedSwap() int64 {
|
func (r *Limits) ConvertedSwap() int64 {
|
||||||
if r.Swap < 0 {
|
if r.Swap < 0 {
|
||||||
return -1
|
return -1
|
||||||
|
@ -95,12 +97,19 @@ func (r *Limits) ConvertedSwap() int64 {
|
||||||
return (r.Swap * 1_000_000) + r.BoundedMemoryLimit()
|
return (r.Swap * 1_000_000) + r.BoundedMemoryLimit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessLimit returns the process limit for a container. This is currently
|
||||||
|
// defined at a system level and not on a per-server basis.
|
||||||
|
func (r *Limits) ProcessLimit() int64 {
|
||||||
|
return config.Get().Docker.ContainerPidLimit
|
||||||
|
}
|
||||||
|
|
||||||
type Variables map[string]interface{}
|
type Variables map[string]interface{}
|
||||||
|
|
||||||
// Ugly hacky function to handle environment variables that get passed through as not-a-string
|
// Get is an ugly hacky function to handle environment variables that get passed
|
||||||
// from the Panel. Ideally we'd just say only pass strings, but that is a fragile idea and if a
|
// through as not-a-string from the Panel. Ideally we'd just say only pass
|
||||||
// string wasn't passed through you'd cause a crash or the server to become unavailable. For now
|
// strings, but that is a fragile idea and if a string wasn't passed through
|
||||||
// try to handle the most likely values from the JSON and hope for the best.
|
// you'd cause a crash or the server to become unavailable. For now try to
|
||||||
|
// handle the most likely values from the JSON and hope for the best.
|
||||||
func (v Variables) Get(key string) string {
|
func (v Variables) Get(key string) string {
|
||||||
val, ok := v[key]
|
val, ok := v[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user