Skip to content

Commit 365331a

Browse files
authored
Merge pull request #72 from gvallee/singularity_manifest
Create manifests for Singularity installations
2 parents a425536 + 64d3a15 commit 365331a

File tree

6 files changed

+123
-65
lines changed

6 files changed

+123
-65
lines changed

cmd/sympi/sympi.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/sylabs/singularity-mpi/internal/pkg/checker"
2222
"github.com/sylabs/singularity-mpi/internal/pkg/implem"
2323
"github.com/sylabs/singularity-mpi/internal/pkg/kv"
24+
"github.com/sylabs/singularity-mpi/internal/pkg/manifest"
2425
"github.com/sylabs/singularity-mpi/internal/pkg/sy"
2526
"github.com/sylabs/singularity-mpi/internal/pkg/sympierr"
2627
"github.com/sylabs/singularity-mpi/internal/pkg/sys"
@@ -52,8 +53,12 @@ func getSingularityInstalls(basedir string, entries []os.FileInfo) ([]string, er
5253
}
5354
if matched {
5455
// Now we check if we have an install manifest for more information
55-
installManifest := filepath.Join(basedir, entry.Name(), "install.MANIFEST")
56+
installManifest := filepath.Join(basedir, entry.Name(), "mconfig.MANIFEST")
5657
availVersion := strings.Replace(entry.Name(), sys.SingularityInstallDirPrefix, "", -1)
58+
59+
if !util.PathExists(installManifest) {
60+
installManifest = filepath.Join(basedir, entry.Name(), "install.MANIFEST")
61+
}
5762
if util.PathExists(installManifest) {
5863
data, err := ioutil.ReadFile(installManifest)
5964
// Errors are not fatal, it means we just do not extract more information
@@ -347,6 +352,16 @@ func installSingularity(id string, params []string, sysCfg *sys.Config) error {
347352
return fmt.Errorf("failed to install %s: %s", id, execRes.Err)
348353
}
349354

355+
// Create manifest for the Singularity binary
356+
syBin := filepath.Join(buildEnv.InstallDir, "bin", "singularity")
357+
manifestPath := filepath.Join(buildEnv.InstallDir, "singularity.MANIFEST")
358+
hashes := manifest.HashFiles([]string{syBin})
359+
err = manifest.Create(manifestPath, hashes)
360+
if err != nil {
361+
// This is not an error, we just log the error
362+
log.Printf("failed to create the MANIFEST for %s\n", id)
363+
}
364+
350365
return nil
351366
}
352367

@@ -579,7 +594,7 @@ func main() {
579594
if *run != "" {
580595
err := sympi.RunContainer(*run, nil, &sysCfg)
581596
if err != nil {
582-
fmt.Printf("Impossible to run container %s: %s", *run, err)
597+
fmt.Printf("Impossible to run container %s: %s\n", *run, err)
583598
os.Exit(1)
584599
}
585600

internal/pkg/buildenv/buildenv.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/sylabs/singularity-mpi/internal/pkg/implem"
2727
"github.com/sylabs/singularity-mpi/internal/pkg/persistent"
28+
"github.com/sylabs/singularity-mpi/internal/pkg/syexec"
2829
"github.com/sylabs/singularity-mpi/internal/pkg/sys"
2930
util "github.com/sylabs/singularity-mpi/internal/pkg/util/file"
3031
)
@@ -138,36 +139,35 @@ func (env *Info) RunMake(priv bool, args []string, stage string) error {
138139
return fmt.Errorf("invalid parameter(s)")
139140
}
140141

141-
var stdout, stderr bytes.Buffer
142-
142+
var makeCmd syexec.SyCmd
143+
makeCmd.ManifestName = "make"
143144
if stage != "" {
144145
args = append(args, stage)
146+
makeCmd.ManifestName = strings.Join(args, "_")
145147
}
146148

147149
args = append([]string{"-j4"}, args...)
148150
logMsg := "make " + strings.Join(args, " ")
149-
var makeCmd *exec.Cmd
150151
if !priv {
151-
makeCmd = exec.Command("make", args...)
152+
makeCmd.BinPath = "make"
152153
} else {
153154
sudoBin, err := exec.LookPath("sudo")
154155
logMsg = sudoBin + " " + logMsg
155156
if err != nil {
156157
return fmt.Errorf("failed to find the sudo binary: %s", err)
157158
}
158159
args = append([]string{"make"}, args...)
159-
makeCmd = exec.Command(sudoBin, args...)
160+
makeCmd.BinPath = sudoBin
160161
}
162+
makeCmd.CmdArgs = args
161163
log.Printf("* Executing (from %s): %s", env.SrcDir, logMsg)
162164
if len(env.Env) > 0 {
163165
makeCmd.Env = env.Env
164166
}
165-
makeCmd.Dir = env.SrcDir
166-
makeCmd.Stderr = &stderr
167-
makeCmd.Stdout = &stdout
168-
err := makeCmd.Run()
169-
if err != nil {
170-
return fmt.Errorf("command failed: %s - stdout: %s - stderr: %s", err, stdout.String(), stderr.String())
167+
makeCmd.ExecDir = env.SrcDir
168+
res := makeCmd.Run()
169+
if res.Err != nil {
170+
return fmt.Errorf("command failed: %s - stdout: %s - stderr: %s", res.Err, res.Stdout, res.Stderr)
171171
}
172172

173173
return nil

internal/pkg/manifest/manifest.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,42 @@
66
package manifest
77

88
import (
9+
"crypto/sha256"
10+
"encoding/hex"
911
"fmt"
12+
"io"
1013
"os"
1114
"strings"
1215
)
1316

17+
func getFileHash(path string) string {
18+
f, err := os.Open(path)
19+
if err != nil {
20+
return ""
21+
}
22+
defer f.Close()
23+
24+
hasher := sha256.New()
25+
_, err = io.Copy(hasher, f)
26+
if err != nil {
27+
return ""
28+
}
29+
30+
return hex.EncodeToString(hasher.Sum(nil))
31+
}
32+
33+
// Hash files returns the hash for a list of files (absolute path)
34+
func HashFiles(files []string) []string {
35+
var hashData []string
36+
37+
for _, file := range files {
38+
hash := getFileHash(file)
39+
hashData = append(hashData, file+": "+hash)
40+
}
41+
42+
return hashData
43+
}
44+
1445
// Create a new manifest
1546
func Create(filepath string, entries []string) error {
1647
f, err := os.Create(filepath)

internal/pkg/sy/sy.go

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/sylabs/singularity-mpi/internal/pkg/implem"
2424
"github.com/sylabs/singularity-mpi/internal/pkg/kv"
2525
"github.com/sylabs/singularity-mpi/internal/pkg/manifest"
26+
"github.com/sylabs/singularity-mpi/internal/pkg/syexec"
2627
"github.com/sylabs/singularity-mpi/internal/pkg/sys"
2728
util "github.com/sylabs/singularity-mpi/internal/pkg/util/file"
2829
)
@@ -243,26 +244,22 @@ func Configure(env *buildenv.Info, sysCfg *sys.Config, extraArgs []string) error
243244
return fmt.Errorf("failed to create %s: %s", env.InstallDir, err)
244245
}
245246
}
246-
singularityManifestPath := filepath.Join(env.InstallDir, "install.MANIFEST")
247-
err := manifest.Create(singularityManifestPath, []string{strings.Join(args, " ")})
248-
if err != nil {
249-
return fmt.Errorf("failed to create installation manifest: %s", err)
250-
}
251247

252248
// Run mconfig
249+
var sycmd syexec.SyCmd
250+
sycmd.Env = updateEnviron(env)
251+
sycmd.ExecDir = env.SrcDir
252+
sycmd.ManifestDir = env.InstallDir
253+
sycmd.ManifestName = "mconfig"
254+
sycmd.BinPath = "./mconfig"
255+
sycmd.CmdArgs = args
256+
sycmd.ManifestData = []string{strings.Join(args, " ")}
253257
log.Printf("-> Executing from %s: ./mconfig %s\n", env.SrcDir, strings.Join(args, " "))
254-
newEnv := updateEnviron(env)
255-
env.Env = newEnv
256-
log.Printf("-> Using env: %s\n", strings.Join(newEnv, "\n"))
257-
var stderr bytes.Buffer
258-
cmd = exec.CommandContext(ctx, "./mconfig", args...)
259-
cmd.Dir = env.SrcDir
260-
cmd.Env = newEnv
261-
cmd.Stderr = &stderr
262-
cmd.Stdout = &stdout
263-
err = cmd.Run()
264-
if err != nil {
265-
return fmt.Errorf("failed to run mconfig: %s (stderr: %s; stdout: %s)", err, stderr.String(), stdout.String())
258+
log.Printf("-> Using env: %s\n", strings.Join(sycmd.Env, "\n"))
259+
260+
res := sycmd.Run()
261+
if res.Err != nil {
262+
return fmt.Errorf("failed to run mconfig: %s (stderr: %s; stdout: %s)", res.Err, res.Stderr, res.Stdout)
266263
}
267264

268265
return nil
@@ -362,3 +359,40 @@ func GetVersion(sysCfg *sys.Config) string {
362359

363360
return stdout.String()
364361
}
362+
363+
// CheckIntegrity checks if the installation of Singularity has been compromised
364+
func CheckIntegrity(sysCfg *sys.Config) error {
365+
log.Println("* Checking intergrity of Singularity...")
366+
367+
if sysCfg.SingularityBin == "" {
368+
return fmt.Errorf("singularity bianry cannot be found")
369+
}
370+
371+
basedir := filepath.Dir(sysCfg.SingularityBin)
372+
basedir = filepath.Join(basedir, "..")
373+
installManifest := filepath.Join(basedir, "singularity.MANIFEST")
374+
375+
if util.FileExists(installManifest) {
376+
data, err := ioutil.ReadFile(installManifest)
377+
if err != nil {
378+
log.Printf("Manifest %s does not exist", installManifest)
379+
return nil // This is not a fatal error
380+
}
381+
content := string(data)
382+
lines := strings.Split(content, "\n")
383+
for _, line := range lines {
384+
if strings.Contains(line, "bin/singularity: ") {
385+
curFileHash := manifest.HashFiles([]string{sysCfg.SingularityBin})
386+
if curFileHash[0] != line {
387+
hashRecordeed := strings.Split(line, ": ")[1]
388+
actualHash := strings.Split(curFileHash[0], ": ")[1]
389+
return fmt.Errorf("hashes differ (record: %s; actual: %s)", hashRecordeed, actualHash)
390+
}
391+
}
392+
}
393+
} else {
394+
log.Printf("No manifest in %s, skipping...\n", basedir)
395+
}
396+
397+
return nil
398+
}

internal/pkg/syexec/syexec.go

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ package syexec
88
import (
99
"bytes"
1010
"context"
11-
"crypto/sha256"
12-
"encoding/hex"
13-
"io"
1411
"log"
15-
"os"
1612
"os/exec"
1713
"path/filepath"
1814
"strings"
@@ -59,6 +55,9 @@ type SyCmd struct {
5955
// CancelFn is the function to cancel the command to submit a job
6056
CancelFn context.CancelFunc
6157

58+
// ManifestName is the name of the manifest, it will default to "exec.MANIFEST" if not defined
59+
ManifestName string
60+
6261
// ManifestDir is the directory where to create the manifest related to the command execution
6362
ManifestDir string
6463

@@ -69,33 +68,6 @@ type SyCmd struct {
6968
ManifestFileHash []string
7069
}
7170

72-
func getFileHash(path string) string {
73-
f, err := os.Open(path)
74-
if err != nil {
75-
return ""
76-
}
77-
defer f.Close()
78-
79-
hasher := sha256.New()
80-
_, err = io.Copy(hasher, f)
81-
if err != nil {
82-
return ""
83-
}
84-
85-
return hex.EncodeToString(hasher.Sum(nil))
86-
}
87-
88-
func hashFiles(files []string) []string {
89-
var hashData []string
90-
91-
for _, file := range files {
92-
hash := getFileHash(file)
93-
hashData = append(hashData, file+": "+hash)
94-
}
95-
96-
return hashData
97-
}
98-
9971
// Run executes a syexec command and creates the appropriate manifest (when possible)
10072
func (c *SyCmd) Run() Result {
10173
var res Result
@@ -127,16 +99,19 @@ func (c *SyCmd) Run() Result {
12799

128100
if c.ManifestDir != "" {
129101
path := filepath.Join(c.ManifestDir, "exec.MANIFEST")
102+
if c.ManifestName != "" {
103+
path = filepath.Join(c.ManifestDir, c.ManifestName+".MANIFEST")
104+
}
130105
if !util.FileExists(path) {
131106
currentTime := time.Now()
132-
data := []string{"Command: " + c.BinPath + strings.Join(c.CmdArgs, " ") + "\n"}
107+
data := []string{"Command: " + c.BinPath + " " + strings.Join(c.CmdArgs, " ") + "\n"}
133108
data = append(data, "Execution path: "+c.ExecDir)
134109
data = append(data, "Execution time: "+currentTime.Format("2006-01-02 15:04:05"))
135110
data = append(data, c.ManifestData...)
136111

137112
filesToHash := []string{c.BinPath} // we always get the fingerprint of the binary we execute
138113
filesToHash = append(filesToHash, c.ManifestFileHash...)
139-
hashData := hashFiles(filesToHash)
114+
hashData := manifest.HashFiles(filesToHash)
140115
data = append(data, hashData...)
141116

142117
err := manifest.Create(path, data)

pkg/sympi/sympi.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/sylabs/singularity-mpi/internal/pkg/kv"
2626
"github.com/sylabs/singularity-mpi/internal/pkg/launcher"
2727
"github.com/sylabs/singularity-mpi/internal/pkg/mpi"
28+
"github.com/sylabs/singularity-mpi/internal/pkg/sy"
2829
"github.com/sylabs/singularity-mpi/internal/pkg/syexec"
2930
"github.com/sylabs/singularity-mpi/internal/pkg/sys"
3031
util "github.com/sylabs/singularity-mpi/internal/pkg/util/file"
@@ -275,8 +276,10 @@ func RunContainer(containerDesc string, args []string, sysCfg *sys.Config) error
275276
}
276277

277278
// Inspect the image and extract the metadata
278-
if sysCfg.SingularityBin == "" {
279-
log.Fatalf("singularity bin not defined")
279+
err = sy.CheckIntegrity(sysCfg)
280+
if err != nil {
281+
fmt.Printf("[WARNING] Your Singularity installation seems to be corrupted: %s\n", err)
282+
return fmt.Errorf("Compromised Singularity installation")
280283
}
281284

282285
fmt.Printf("Analyzing %s to figure out the correct configuration for execution...\n", imgPath)

0 commit comments

Comments
 (0)