[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