feat(agent): report and refuse Compose services built from source; honour --env-file (#7)

This commit is contained in:
2026-10-12 19:32:05 +02:00
parent 69ef55837e
commit 27c4200dda
8 changed files with 534 additions and 24 deletions
+53 -11
View File
@@ -136,6 +136,16 @@ func MountRoots(ctx context.Context, cli SelfInspector, selfID string) ([]string
// override last so it wins. Every file, and the real location of its
// directory after resolving symlinks, must be inside one of roots.
func composeFiles(roots []string, configFilesLabel string) (files []string, dir string, err error) {
files, dir, err = stackFiles(roots, configFilesLabel)
if err != nil {
return nil, "", err
}
return append(files, filepath.Join(dir, OverrideName)), dir, nil
}
// stackFiles is composeFiles without the override: the operator's own config
// files, checked against roots. dir is the first file's directory.
func stackFiles(roots []string, configFilesLabel string) (files []string, dir string, err error) {
if len(roots) == 0 {
return nil, "", errNoRoots
}
@@ -150,16 +160,7 @@ func composeFiles(roots []string, configFilesLabel string) (files []string, dir
dir = filepath.Dir(files[0])
override := filepath.Join(dir, OverrideName)
// Only directories count once resolved: this drops a same-path mount of
// a single file such as /var/run/docker.sock.
var realRoots []string
for _, r := range roots {
if rr, err := filepath.EvalSymlinks(r); err == nil {
if fi, err := os.Stat(rr); err == nil && fi.IsDir() {
realRoots = append(realRoots, rr)
}
}
}
realRoots := realDirs(roots)
out := files[:0]
for _, f := range files {
if !filepath.IsAbs(f) || !insideAny(roots, f) {
@@ -176,7 +177,48 @@ func composeFiles(roots []string, configFilesLabel string) (files []string, dir
out = append(out, f)
}
}
return append(out, override), dir, nil
return out, dir, nil
}
// envFiles returns the --env-file paths from the container's
// com.docker.compose.project.environment_file label (empty label: none).
// Like config files, each must be inside one of roots, also after resolving
// symlinks of the file itself.
func envFiles(roots []string, label string) ([]string, error) {
var files []string
realRoots := realDirs(roots)
for _, f := range strings.Split(label, ",") {
if f = strings.TrimSpace(f); f == "" {
continue
}
if !filepath.IsAbs(f) || !insideAny(roots, f) {
return nil, fmt.Errorf("env file %s is not inside a directory mounted into the agent at the same path (read-write)", f)
}
real, err := filepath.EvalSymlinks(f)
if err != nil {
return nil, fmt.Errorf("resolve env file %s: %w", f, err)
}
if !insideAny(realRoots, real) {
return nil, fmt.Errorf("env file %s is not inside a directory mounted into the agent at the same path (read-write)", f)
}
files = append(files, f)
}
return files, nil
}
// realDirs returns roots with symlinks resolved. Only directories count once
// resolved: this drops a same-path mount of a single file such as
// /var/run/docker.sock.
func realDirs(roots []string) []string {
var out []string
for _, r := range roots {
if rr, err := filepath.EvalSymlinks(r); err == nil {
if fi, err := os.Stat(rr); err == nil && fi.IsDir() {
out = append(out, rr)
}
}
}
return out
}
func insideAny(roots []string, path string) bool {