-
Notifications
You must be signed in to change notification settings - Fork 77
Import security updates #210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
QuintenQVD0
wants to merge
6
commits into
pelican:main
Choose a base branch
from
QuintenQVD0:security_updates
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
bcf93a5
improve cpu allocation handling & container CPU shares configurable
QuintenQVD0 6f956a4
fix: master key resets through the panel now automatically propogate …
QuintenQVD0 04a1218
SFTP: Improve request handling
QuintenQVD0 16634b7
Harden credential rotation
QuintenQVD0 866c522
SFTP: Improve request handling
QuintenQVD0 4106199
Merge branch 'main' into security_updates
lancepioch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package config | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestResolveRemoteToken(t *testing.T) { | ||
| t.Setenv("WINGS_TOKEN_ID", "") | ||
| t.Setenv("WINGS_TOKEN", "") | ||
|
|
||
| cfg := Configuration{ | ||
| AuthenticationTokenId: "panel-id", | ||
| AuthenticationToken: "panel-token", | ||
| } | ||
| if err := cfg.ResolveToken(true); err != nil { | ||
| t.Fatalf("expected remote credentials to resolve: %v", err) | ||
| } | ||
| if cfg.Token.ID != "panel-id" || cfg.Token.Token != "panel-token" { | ||
| t.Fatalf("unexpected resolved credentials: %#v", cfg.Token) | ||
| } | ||
| } | ||
|
|
||
| func TestResolveRemoteTokenRejectsIndirection(t *testing.T) { | ||
| t.Setenv("WINGS_TOKEN_ID", "") | ||
| t.Setenv("WINGS_TOKEN", "") | ||
|
|
||
| tests := []Configuration{ | ||
| {AuthenticationTokenId: "file:///tmp/id", AuthenticationToken: "panel-token"}, | ||
| {AuthenticationTokenId: "panel-id", AuthenticationToken: "$PANEL_TOKEN"}, | ||
| } | ||
| for _, cfg := range tests { | ||
| if err := cfg.ResolveToken(true); err == nil { | ||
| t.Fatal("expected remote token indirection to be rejected") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestResolveRemoteTokenRequiresEnvironmentMatch(t *testing.T) { | ||
| secret := filepath.Join(t.TempDir(), "token") | ||
| if err := os.WriteFile(secret, []byte("panel-token\n"), 0o600); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| t.Setenv("WINGS_TOKEN_ID", "panel-id") | ||
| t.Setenv("WINGS_TOKEN", "file://"+secret) | ||
|
|
||
| cfg := Configuration{ | ||
| AuthenticationTokenId: "panel-id", | ||
| AuthenticationToken: "panel-token", | ||
| } | ||
| if err := cfg.ResolveToken(true); err != nil { | ||
| t.Fatalf("expected matching environment credentials to resolve: %v", err) | ||
| } | ||
|
|
||
| cfg.AuthenticationToken = "rotated-token" | ||
| if err := cfg.ResolveToken(true); err == nil { | ||
| t.Fatal("expected mismatched environment credentials to be rejected") | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| package docker | ||
|
|
||
| import ( | ||
| "context" | ||
| "os" | ||
| "path" | ||
| "strconv" | ||
| "strings" | ||
| "sync" | ||
|
|
||
| "emperror.dev/errors" | ||
| "github.com/apex/log" | ||
| "github.com/docker/docker/client" | ||
|
|
||
| "github.com/pelican/wings/config" | ||
| ) | ||
|
|
||
| // cgroupV2 reports whether the host uses the unified cgroup v2 hierarchy. | ||
| var cgroupV2 = sync.OnceValue(func() bool { | ||
| _, err := os.Stat("/sys/fs/cgroup/cgroup.controllers") | ||
| return err == nil | ||
| }) | ||
|
|
||
| var burstWarning sync.Once | ||
|
|
||
| // cpuBurstMicroseconds returns the burst allowance in microseconds for the given | ||
| // CFS quota and configured percentage. The kernel rejects a burst larger than the | ||
| // quota, so the value is clamped to it. | ||
| func cpuBurstMicroseconds(quota int64, percent int64) int64 { | ||
| if quota <= 0 || percent <= 0 { | ||
| return 0 | ||
| } | ||
| if percent > 100 { | ||
| percent = 100 | ||
| } | ||
| return quota * percent / 100 | ||
| } | ||
|
|
||
| // resolveCgroupCpuFile parses the contents of a /proc/<pid>/cgroup file and | ||
| // returns the absolute path of the CFS burst file for that process's cgroup. | ||
| func resolveCgroupCpuFile(procCgroup string, v2 bool) (string, error) { | ||
| for _, line := range strings.Split(procCgroup, "\n") { | ||
| parts := strings.SplitN(line, ":", 3) | ||
| if len(parts) != 3 || !strings.HasPrefix(parts[2], "/") || strings.Contains(parts[2], "..") { | ||
| continue | ||
| } | ||
| if v2 { | ||
| if parts[0] == "0" && parts[1] == "" { | ||
| return path.Join("/sys/fs/cgroup", parts[2], "cpu.max.burst"), nil | ||
| } | ||
| continue | ||
| } | ||
| for _, controller := range strings.Split(parts[1], ",") { | ||
| if controller == "cpu" { | ||
| return path.Join("/sys/fs/cgroup/cpu", parts[2], "cpu.cfs_burst_us"), nil | ||
| } | ||
| } | ||
| } | ||
| return "", errors.New("environment/docker: no cpu controller found in cgroup file") | ||
| } | ||
|
|
||
| // writeCpuBurst writes a burst value in microseconds into the cpu cgroup of the | ||
| // given process. This is expected to fail on kernels older than 5.14 or when the | ||
| // cgroup hierarchy is not writable by Wings, so failures are only logged. | ||
| func writeCpuBurst(l *log.Entry, pid int, burst int64) { | ||
| if pid <= 0 { | ||
| return | ||
| } | ||
| if err := writeBurstFile(pid, burst); err != nil { | ||
| logBurstFailure(l.WithField("error", err), burst) | ||
| return | ||
| } | ||
| l.WithField("burst_us", burst).Debug("updated container cpu burst") | ||
| } | ||
|
|
||
| func writeBurstFile(pid int, burst int64) error { | ||
| b, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/cgroup") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| f, err := resolveCgroupCpuFile(string(b), cgroupV2()) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return os.WriteFile(f, []byte(strconv.FormatInt(burst, 10)), 0o644) | ||
| } | ||
|
|
||
| // logBurstFailure warns the first time a burst cannot be applied and stays at | ||
| // debug otherwise. Failed clears are always quiet since a host that never | ||
| // accepted a burst has nothing to clear. | ||
| func logBurstFailure(l *log.Entry, burst int64) { | ||
| if burst > 0 { | ||
| first := false | ||
| burstWarning.Do(func() { first = true }) | ||
| if first { | ||
| l.Warn("failed to set cpu burst, this requires Linux 5.14 or newer and a writable cgroup hierarchy") | ||
| return | ||
| } | ||
| } | ||
| l.Debug("failed to set cpu burst") | ||
| } | ||
|
|
||
| // SetCpuBurst applies the configured CFS burst to a running container based on | ||
| // the CFS quota in microseconds it was created with. This is a no-op when | ||
| // bursting is disabled or the container has no CPU limit. | ||
| func SetCpuBurst(ctx context.Context, cli *client.Client, containerID string, quota int64) { | ||
| cfg := config.Get().Docker.CpuBurst | ||
| if !cfg.Enabled || quota <= 0 { | ||
| return | ||
| } | ||
| c, err := cli.ContainerInspect(ctx, containerID) | ||
| if err != nil || c.State == nil { | ||
| return | ||
| } | ||
| writeCpuBurst(log.WithField("container_id", containerID), c.State.Pid, cpuBurstMicroseconds(quota, cfg.Percent)) | ||
| } | ||
|
|
||
| // applyCpuBurst applies the configured CFS burst to the environment's container | ||
| // using its current CPU limit. | ||
| func (e *Environment) applyCpuBurst(ctx context.Context) { | ||
| quota := e.Configuration.Limits().CpuLimit * config.Get().Docker.CpuPeriodMicroseconds() / 100 | ||
| SetCpuBurst(ctx, e.client, e.Id, quota) | ||
| } | ||
|
|
||
| // clearCpuBurst zeroes the CFS burst for the given container process. This must | ||
| // happen before a quota change is applied since the kernel rejects a quota lower | ||
| // than the current burst. It runs even when bursting is disabled so a value set | ||
| // before the feature was turned off cannot block future quota changes. | ||
| func (e *Environment) clearCpuBurst(pid int) { | ||
| writeCpuBurst(e.log(), pid, 0) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Resolve the cgroup v1 CPU mount point.
Line 55 hard-codes
/sys/fs/cgroup/cpu. A cgroup v1 host can mount the combinedcpu,cpuacctcontroller at/sys/fs/cgroup/cpu,cpuacct. In that layout,writeBurstFiletargets a missing path, logs the failure, and never enables CPU burst.Read the CPU controller mount point from mount information before constructing the burst-file path. Update
environment/docker/cgroup_burst_test.goto cover the combined-controller mount path.🤖 Prompt for AI Agents