Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4594d18b7 |
@@ -22,6 +22,16 @@ From 0.39.1 on, a heading marks a version *not published* or *held* once
|
|||||||
that is settled. The newest entries may not be published yet; the manifest
|
that is settled. The newest entries may not be published yet; the manifest
|
||||||
says which version is. Older entries are not marked.
|
says which version is. Older entries are not marked.
|
||||||
|
|
||||||
|
## 0.61.1
|
||||||
|
|
||||||
|
- **An op that creates a folder runs on every Linux kernel.** Where the
|
||||||
|
kernel's overlay keeps an index (or has `redirect_dir` or `metacopy` on, as
|
||||||
|
Azure's kernel does), it marks each new folder opaque, and the bastion took
|
||||||
|
that for a replaced folder: every op that created one was refused as "a path
|
||||||
|
outside the effects in disguise". A new folder is now read as created, with
|
||||||
|
what it holds; a folder that replaces one the machine holds is still
|
||||||
|
refused.
|
||||||
|
|
||||||
## 0.61.0
|
## 0.61.0
|
||||||
|
|
||||||
- **An agent on the machine reads the rest of it.** `agents show <pid|session>`
|
- **An agent on the machine reads the rest of it.** `agents show <pid|session>`
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@@ -648,7 +649,12 @@ func opUpperChanges(upper, hostRoot string) ([]opChange, error) {
|
|||||||
out = append(out, opChange{Path: real, Kind: "special"})
|
out = append(out, opChange{Path: real, Kind: "special"})
|
||||||
return nil
|
return nil
|
||||||
case mode.IsDir():
|
case mode.IsDir():
|
||||||
if opaque, _ := opXattrOpaque(p); opaque {
|
// Opaque hides what the lower holds at this path: a replaced
|
||||||
|
// tree, refused. With nothing there it hides nothing. A kernel
|
||||||
|
// whose overlay has index, redirect_dir or metacopy on marks every
|
||||||
|
// new folder under an existing one opaque, so such a folder is
|
||||||
|
// created, and its contents are read like any other.
|
||||||
|
if opaque, _ := opXattrOpaque(p); opaque && !errors.Is(lowerErr, fs.ErrNotExist) {
|
||||||
out = append(out, opChange{Path: real, Kind: "opaque"})
|
out = append(out, opChange{Path: real, Kind: "opaque"})
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── the upper-dir reader (no sandbox needed) ────────────────────────────────
|
// ── the upper-dir reader (no sandbox needed) ────────────────────────────────
|
||||||
@@ -102,6 +104,42 @@ func TestUpperChangesReadCreatedModifiedAndLinks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A kernel whose overlay has index, redirect_dir or metacopy on marks each
|
||||||
|
// new folder under an existing one opaque. Over nothing the machine holds,
|
||||||
|
// such a folder is created and what it holds is read; over a folder the
|
||||||
|
// machine holds, opaque is a replaced tree, and refused.
|
||||||
|
func TestAnOpaqueNewFolderIsCreatedAndAReplacedOneIsRefused(t *testing.T) {
|
||||||
|
lower := t.TempDir()
|
||||||
|
upper := t.TempDir()
|
||||||
|
_ = os.MkdirAll(filepath.Join(lower, "srv/kept"), 0o755)
|
||||||
|
_ = os.WriteFile(filepath.Join(lower, "srv/kept/old.txt"), []byte("old\n"), 0o644)
|
||||||
|
for _, d := range []string{"srv/new", "srv/kept"} {
|
||||||
|
_ = os.MkdirAll(filepath.Join(upper, d), 0o755)
|
||||||
|
if err := unix.Setxattr(filepath.Join(upper, d), "trusted.overlay.opaque", []byte("y"), 0); err != nil {
|
||||||
|
t.Skipf("cannot set an overlay attribute here (%v): needs root on a filesystem with trusted attributes", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = os.WriteFile(filepath.Join(upper, "srv/new/hello.txt"), []byte("hi\n"), 0o644)
|
||||||
|
changes, err := opUpperChanges(upper, lower)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := map[string]string{}
|
||||||
|
for _, c := range changes {
|
||||||
|
got[strings.TrimPrefix(c.Path, lower)] = c.Kind
|
||||||
|
}
|
||||||
|
if got["/srv/new"] != "created_dir" || got["/srv/new/hello.txt"] != "created" {
|
||||||
|
t.Fatalf("an opaque folder over nothing is created, with what it holds: %v", got)
|
||||||
|
}
|
||||||
|
if got["/srv/kept"] != "opaque" {
|
||||||
|
t.Fatalf("an opaque folder over one the machine holds is a replaced tree: %v", got)
|
||||||
|
}
|
||||||
|
m := &opManifest{Effects: opEffects{Files: []string{lower + "/srv/"}}}
|
||||||
|
if why := opEffectsViolation(m, &opReport{Changes: changes}); !strings.Contains(why, "opaque") {
|
||||||
|
t.Fatalf("the replaced tree is refused even inside the effects: %q", why)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHostMountsAndEffectPrefix(t *testing.T) {
|
func TestHostMountsAndEffectPrefix(t *testing.T) {
|
||||||
mounts := []opHostMount{{"/", "ext4"}, {"/srv", "xfs"}, {"/srv/share", "nfs4"}, {"/proc", "proc"}, {"/home", "ext4"}}
|
mounts := []opHostMount{{"/", "ext4"}, {"/srv", "xfs"}, {"/srv/share", "nfs4"}, {"/proc", "proc"}, {"/home", "ext4"}}
|
||||||
for p, want := range map[string]string{
|
for p, want := range map[string]string{
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
0.61.0
|
0.61.1
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ says so. Those parts are enforced only by the code as written.
|
|||||||
| | `ops_linux_test.go`: `TestOpDryRunShowsTheChangeAndApplyCommitsExactlyThat` | A dry run commits nothing; an apply must name the dry run's change list and carry an approval bound to that op, its parameters, the machine and that change list. Skipped where the sandbox is unavailable. |
|
| | `ops_linux_test.go`: `TestOpDryRunShowsTheChangeAndApplyCommitsExactlyThat` | A dry run commits nothing; an apply must name the dry run's change list and carry an approval bound to that op, its parameters, the machine and that change list. Skipped where the sandbox is unavailable. |
|
||||||
| | `ops_test.go`: `TestOpTermsMutationsAreEachRefused` | Each altered field of signed op terms is refused. |
|
| | `ops_test.go`: `TestOpTermsMutationsAreEachRefused` | Each altered field of signed op terms is refused. |
|
||||||
| | `ops_linux_test.go`: `TestOpSandboxShowsOnlyTheBaseAndWhatTheOpDeclared` | In a real sandbox, an op reads the file it declared and the system base; a file beside its own folder, a folder beside it and `/etc/shadow` read as empty; a declared path that has since become a link onto `/etc/ssh` refuses the run. Skipped where the sandbox cannot run. |
|
| | `ops_linux_test.go`: `TestOpSandboxShowsOnlyTheBaseAndWhatTheOpDeclared` | In a real sandbox, an op reads the file it declared and the system base; a file beside its own folder, a folder beside it and `/etc/shadow` read as empty; a declared path that has since become a link onto `/etc/ssh` refuses the run. Skipped where the sandbox cannot run. |
|
||||||
|
| | `ops_linux_test.go`: `TestAnOpaqueNewFolderIsCreatedAndAReplacedOneIsRefused` | A folder an op left opaque over one the machine holds (a replaced tree) is refused, inside its effects or not; a new folder the kernel marks opaque is a created folder, and what it holds is read. |
|
||||||
| | `ops_taskcap_linux_test.go`: `TestARootOpIsStoppedAtItsTaskCap` | A root op starting 100 background tasks is stopped at its scope's cap, and its report says the cap held. The scope is made by a stand-in for `systemd-run` that creates the cgroup directly. Skipped where the sandbox, systemd as init or a pids controller is unavailable. |
|
| | `ops_taskcap_linux_test.go`: `TestARootOpIsStoppedAtItsTaskCap` | A root op starting 100 background tasks is stopped at its scope's cap, and its report says the cap held. The scope is made by a stand-in for `systemd-run` that creates the cgroup directly. Skipped where the sandbox, systemd as init or a pids controller is unavailable. |
|
||||||
| | `ops_view_test.go`: `TestOpSensitivePathsAreRefusedAtUnderOrAboveAndThroughLinks` | A declared path at, under or above a credential store, as written or through a link, is refused; a malformed `sensitive_paths` refuses every op. |
|
| | `ops_view_test.go`: `TestOpSensitivePathsAreRefusedAtUnderOrAboveAndThroughLinks` | A declared path at, under or above a credential store, as written or through a link, is refused; a malformed `sensitive_paths` refuses every op. |
|
||||||
| | `fuzz_paths_unix_test.go`: `FuzzAnOpEffectNeverCoversTheMachinesOwnRecord` | No accepted effect covers the bastion's own records or the system directories the sandbox masks. |
|
| | `fuzz_paths_unix_test.go`: `FuzzAnOpEffectNeverCoversTheMachinesOwnRecord` | No accepted effect covers the bastion's own records or the system directories the sandbox masks. |
|
||||||
|
|||||||
Reference in New Issue
Block a user