chroot: Move runUsingChrootExecMain to run_common.go

Again, this breaks the FreeBSD build and this will be addressed by
refactoring in the next commit.

Signed-off-by: Doug Rabson <dfr@rabson.org>
This commit is contained in:
Doug Rabson 2022-08-05 15:16:06 +01:00
parent 0a61e4b280
commit 4d963eb5e1
3 changed files with 202 additions and 387 deletions

View File

@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
@ -563,3 +564,204 @@ func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io
return 0, nil
}
// main() for parent subprocess. Its main job is to try to make our
// environment look like the one described by the runtime configuration blob,
// and then launch the intended command as a child.
func runUsingChrootExecMain() {
args := os.Args[1:]
var options runUsingChrootExecSubprocOptions
var err error
runtime.LockOSThread()
// Set logging.
if level := os.Getenv("LOGLEVEL"); level != "" {
if ll, err := strconv.Atoi(level); err == nil {
logrus.SetLevel(logrus.Level(ll))
}
os.Unsetenv("LOGLEVEL")
}
// Unpack our configuration.
confPipe := os.NewFile(3, "confpipe")
if confPipe == nil {
fmt.Fprintf(os.Stderr, "error reading options pipe\n")
os.Exit(1)
}
defer confPipe.Close()
if err := json.NewDecoder(confPipe).Decode(&options); err != nil {
fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err)
os.Exit(1)
}
// Set the hostname. We're already in a distinct UTS namespace and are admins in the user
// namespace which created it, so we shouldn't get a permissions error, but seccomp policy
// might deny our attempt to call sethostname() anyway, so log a debug message for that.
if options.Spec == nil || options.Spec.Process == nil {
fmt.Fprintf(os.Stderr, "invalid options spec passed in\n")
os.Exit(1)
}
if options.Spec.Hostname != "" {
if err := unix.Sethostname([]byte(options.Spec.Hostname)); err != nil {
logrus.Debugf("failed to set hostname %q for process: %v", options.Spec.Hostname, err)
}
}
// Try to chroot into the root. Do this before we potentially block the syscall via the
// seccomp profile.
var oldst, newst unix.Stat_t
if err := unix.Stat(options.Spec.Root.Path, &oldst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chdir(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing to intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chroot(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Stat("/", &newst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing current root directory: %v\n", err)
os.Exit(1)
}
if oldst.Dev != newst.Dev || oldst.Ino != newst.Ino {
fmt.Fprintf(os.Stderr, "unknown error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("chrooted into %q", options.Spec.Root.Path)
// not doing because it's still shared: creating devices
// not doing because it's not applicable: setting annotations
// not doing because it's still shared: setting sysctl settings
// not doing because cgroupfs is read only: configuring control groups
// -> this means we can use the freezer to make sure there aren't any lingering processes
// -> this means we ignore cgroups-based controls
// not doing because we don't set any in the config: running hooks
// not doing because we don't set it in the config: setting rootfs read-only
// not doing because we don't set it in the config: setting rootfs propagation
logrus.Debugf("setting apparmor profile")
if err = setApparmorProfile(options.Spec); err != nil {
fmt.Fprintf(os.Stderr, "error setting apparmor profile for process: %v\n", err)
os.Exit(1)
}
if err = setSelinuxLabel(options.Spec); err != nil {
fmt.Fprintf(os.Stderr, "error setting SELinux label for process: %v\n", err)
os.Exit(1)
}
logrus.Debugf("setting resource limits")
if err = setRlimits(options.Spec, false, false); err != nil {
fmt.Fprintf(os.Stderr, "error setting process resource limits for process: %v\n", err)
os.Exit(1)
}
// Try to change to the directory.
cwd := options.Spec.Process.Cwd
if !filepath.IsAbs(cwd) {
cwd = "/" + cwd
}
cwd = filepath.Clean(cwd)
if err := unix.Chdir("/"); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into new root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chdir(cwd); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into directory %q under root %q: %v\n", cwd, options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("changed working directory to %q", cwd)
// Drop privileges.
user := options.Spec.Process.User
if len(user.AdditionalGids) > 0 {
gids := make([]int, len(user.AdditionalGids))
for i := range user.AdditionalGids {
gids[i] = int(user.AdditionalGids[i])
}
logrus.Debugf("setting supplemental groups")
if err = syscall.Setgroups(gids); err != nil {
fmt.Fprintf(os.Stderr, "error setting supplemental groups list: %v", err)
os.Exit(1)
}
} else {
setgroups, _ := ioutil.ReadFile("/proc/self/setgroups")
if strings.Trim(string(setgroups), "\n") != "deny" {
logrus.Debugf("clearing supplemental groups")
if err = syscall.Setgroups([]int{}); err != nil {
fmt.Fprintf(os.Stderr, "error clearing supplemental groups list: %v", err)
os.Exit(1)
}
}
}
logrus.Debugf("setting gid")
if err = syscall.Setresgid(int(user.GID), int(user.GID), int(user.GID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting GID: %v", err)
os.Exit(1)
}
if err = setSeccomp(options.Spec); err != nil {
fmt.Fprintf(os.Stderr, "error setting seccomp filter for process: %v\n", err)
os.Exit(1)
}
logrus.Debugf("setting capabilities")
var keepCaps []string
if user.UID != 0 {
keepCaps = []string{"CAP_SETUID"}
}
if err := setCapabilities(options.Spec, keepCaps...); err != nil {
fmt.Fprintf(os.Stderr, "error setting capabilities for process: %v\n", err)
os.Exit(1)
}
logrus.Debugf("setting uid")
if err = syscall.Setresuid(int(user.UID), int(user.UID), int(user.UID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting UID: %v", err)
os.Exit(1)
}
// Actually run the specified command.
cmd := exec.Command(args[0], args[1:]...)
setPdeathsig(cmd)
cmd.Env = options.Spec.Process.Env
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.Dir = cwd
logrus.Debugf("Running %#v (PATH = %q)", cmd, os.Getenv("PATH"))
interrupted := make(chan os.Signal, 100)
if err = cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "process failed to start with error: %v", err)
}
go func() {
for range interrupted {
if err := cmd.Process.Signal(syscall.SIGKILL); err != nil {
logrus.Infof("%v while attempting to send SIGKILL to child process", err)
}
}
}()
signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
err = cmd.Wait()
signal.Stop(interrupted)
close(interrupted)
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if waitStatus, ok := exitError.ProcessState.Sys().(syscall.WaitStatus); ok {
if waitStatus.Exited() {
if waitStatus.ExitStatus() != 0 {
fmt.Fprintf(os.Stderr, "subprocess exited with status %d\n", waitStatus.ExitStatus())
}
os.Exit(waitStatus.ExitStatus())
} else if waitStatus.Signaled() {
fmt.Fprintf(os.Stderr, "subprocess exited on %s\n", waitStatus.Signal())
os.Exit(1)
}
}
}
fmt.Fprintf(os.Stderr, "process exited with error: %v", err)
os.Exit(1)
}
}

View File

@ -68,187 +68,6 @@ func createJail(options runUsingChrootExecSubprocOptions) error {
return nil
}
// main() for parent subprocess. Its main job is to try to make our
// environment look like the one described by the runtime configuration blob,
// and then launch the intended command as a child.
func runUsingChrootExecMain() {
args := os.Args[1:]
var options runUsingChrootExecSubprocOptions
var err error
runtime.LockOSThread()
// Set logging.
if level := os.Getenv("LOGLEVEL"); level != "" {
if ll, err := strconv.Atoi(level); err == nil {
logrus.SetLevel(logrus.Level(ll))
}
os.Unsetenv("LOGLEVEL")
}
// Unpack our configuration.
confPipe := os.NewFile(3, "confpipe")
if confPipe == nil {
fmt.Fprintf(os.Stderr, "error reading options pipe\n")
os.Exit(1)
}
defer confPipe.Close()
if err := json.NewDecoder(confPipe).Decode(&options); err != nil {
fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err)
os.Exit(1)
}
// Set the hostname. We're already in a distinct UTS namespace and are admins in the user
// namespace which created it, so we shouldn't get a permissions error, but seccomp policy
// might deny our attempt to call sethostname() anyway, so log a debug message for that.
if options.Spec == nil || options.Spec.Process == nil {
fmt.Fprintf(os.Stderr, "invalid options spec passed in\n")
os.Exit(1)
}
/*if options.Spec.Hostname != "" {
if err := unix.Sethostname([]byte(options.Spec.Hostname)); err != nil {
logrus.Debugf("failed to set hostname %q for process: %v", options.Spec.Hostname, err)
}
}*/
// Try to create a jail and if that fails, fall back to chroot
if err := createJail(options); err == nil {
logrus.Debugf("jailed into %q", options.Spec.Root.Path)
} else {
// Try to chroot into the root. Do this before we potentially block the syscall via the
// seccomp profile.
var oldst, newst unix.Stat_t
if err := unix.Stat(options.Spec.Root.Path, &oldst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chdir(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing to intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chroot(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Stat("/", &newst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing current root directory: %v\n", err)
os.Exit(1)
}
if oldst.Dev != newst.Dev || oldst.Ino != newst.Ino {
fmt.Fprintf(os.Stderr, "unknown error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("chrooted into %q", options.Spec.Root.Path)
}
// not doing because it's still shared: creating devices
// not doing because it's not applicable: setting annotations
// not doing because it's still shared: setting sysctl settings
// not doing because cgroupfs is read only: configuring control groups
// -> this means we can use the freezer to make sure there aren't any lingering processes
// -> this means we ignore cgroups-based controls
// not doing because we don't set any in the config: running hooks
// not doing because we don't set it in the config: setting rootfs read-only
// not doing because we don't set it in the config: setting rootfs propagation
logrus.Debugf("setting resource limits")
if err = setRlimits(options.Spec, false, false); err != nil {
fmt.Fprintf(os.Stderr, "error setting process resource limits for process: %v\n", err)
os.Exit(1)
}
// Try to change to the directory.
cwd := options.Spec.Process.Cwd
if !filepath.IsAbs(cwd) {
cwd = "/" + cwd
}
cwd = filepath.Clean(cwd)
if err := unix.Chdir("/"); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into new root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chdir(cwd); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into directory %q under root %q: %v\n", cwd, options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("changed working directory to %q", cwd)
// Drop privileges.
user := options.Spec.Process.User
if len(user.AdditionalGids) > 0 {
gids := make([]int, len(user.AdditionalGids))
for i := range user.AdditionalGids {
gids[i] = int(user.AdditionalGids[i])
}
logrus.Debugf("setting supplemental groups")
if err = syscall.Setgroups(gids); err != nil {
fmt.Fprintf(os.Stderr, "error setting supplemental groups list: %v", err)
os.Exit(1)
}
} else {
setgroups, _ := ioutil.ReadFile("/proc/self/setgroups")
if strings.Trim(string(setgroups), "\n") != "deny" {
logrus.Debugf("clearing supplemental groups")
if err = syscall.Setgroups([]int{}); err != nil {
fmt.Fprintf(os.Stderr, "error clearing supplemental groups list: %v", err)
os.Exit(1)
}
}
}
logrus.Debugf("setting gid")
if err = unix.Setresgid(int(user.GID), int(user.GID), int(user.GID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting GID: %v", err)
os.Exit(1)
}
logrus.Debugf("setting uid")
if err = unix.Setresuid(int(user.UID), int(user.UID), int(user.UID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting UID: %v", err)
os.Exit(1)
}
// Actually run the specified command.
cmd := exec.Command(args[0], args[1:]...)
setPdeathsig(cmd)
cmd.Env = options.Spec.Process.Env
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.Dir = cwd
logrus.Debugf("Running %#v (PATH = %q)", cmd, os.Getenv("PATH"))
interrupted := make(chan os.Signal, 100)
if err = cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "process failed to start with error: %v", err)
}
go func() {
for range interrupted {
if err := cmd.Process.Signal(syscall.SIGKILL); err != nil {
logrus.Infof("%v while attempting to send SIGKILL to child process", err)
}
}
}()
signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
err = cmd.Wait()
signal.Stop(interrupted)
close(interrupted)
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if waitStatus, ok := exitError.ProcessState.Sys().(syscall.WaitStatus); ok {
if waitStatus.Exited() {
if waitStatus.ExitStatus() != 0 {
fmt.Fprintf(os.Stderr, "subprocess exited with status %d\n", waitStatus.ExitStatus())
}
os.Exit(waitStatus.ExitStatus())
} else if waitStatus.Signaled() {
fmt.Fprintf(os.Stderr, "subprocess exited on %s\n", waitStatus.Signal())
os.Exit(1)
}
}
}
fmt.Fprintf(os.Stderr, "process exited with error: %v", err)
os.Exit(1)
}
}
// logNamespaceDiagnostics knows which namespaces we want to create.
// Output debug messages when that differs from what we're being asked to do.
func logNamespaceDiagnostics(spec *specs.Spec) {

View File

@ -4,16 +4,11 @@
package chroot
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
@ -96,207 +91,6 @@ func setPlatformUnshareOptions(spec *specs.Spec, cmd *unshare.Cmd) error {
return nil
}
// main() for parent subprocess. Its main job is to try to make our
// environment look like the one described by the runtime configuration blob,
// and then launch the intended command as a child.
func runUsingChrootExecMain() {
args := os.Args[1:]
var options runUsingChrootExecSubprocOptions
var err error
runtime.LockOSThread()
// Set logging.
if level := os.Getenv("LOGLEVEL"); level != "" {
if ll, err := strconv.Atoi(level); err == nil {
logrus.SetLevel(logrus.Level(ll))
}
os.Unsetenv("LOGLEVEL")
}
// Unpack our configuration.
confPipe := os.NewFile(3, "confpipe")
if confPipe == nil {
fmt.Fprintf(os.Stderr, "error reading options pipe\n")
os.Exit(1)
}
defer confPipe.Close()
if err := json.NewDecoder(confPipe).Decode(&options); err != nil {
fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err)
os.Exit(1)
}
// Set the hostname. We're already in a distinct UTS namespace and are admins in the user
// namespace which created it, so we shouldn't get a permissions error, but seccomp policy
// might deny our attempt to call sethostname() anyway, so log a debug message for that.
if options.Spec == nil || options.Spec.Process == nil {
fmt.Fprintf(os.Stderr, "invalid options spec passed in\n")
os.Exit(1)
}
if options.Spec.Hostname != "" {
if err := unix.Sethostname([]byte(options.Spec.Hostname)); err != nil {
logrus.Debugf("failed to set hostname %q for process: %v", options.Spec.Hostname, err)
}
}
// Try to chroot into the root. Do this before we potentially block the syscall via the
// seccomp profile.
var oldst, newst unix.Stat_t
if err := unix.Stat(options.Spec.Root.Path, &oldst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chdir(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing to intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chroot(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Stat("/", &newst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing current root directory: %v\n", err)
os.Exit(1)
}
if oldst.Dev != newst.Dev || oldst.Ino != newst.Ino {
fmt.Fprintf(os.Stderr, "unknown error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("chrooted into %q", options.Spec.Root.Path)
// not doing because it's still shared: creating devices
// not doing because it's not applicable: setting annotations
// not doing because it's still shared: setting sysctl settings
// not doing because cgroupfs is read only: configuring control groups
// -> this means we can use the freezer to make sure there aren't any lingering processes
// -> this means we ignore cgroups-based controls
// not doing because we don't set any in the config: running hooks
// not doing because we don't set it in the config: setting rootfs read-only
// not doing because we don't set it in the config: setting rootfs propagation
logrus.Debugf("setting apparmor profile")
if err = setApparmorProfile(options.Spec); err != nil {
fmt.Fprintf(os.Stderr, "error setting apparmor profile for process: %v\n", err)
os.Exit(1)
}
if err = setSelinuxLabel(options.Spec); err != nil {
fmt.Fprintf(os.Stderr, "error setting SELinux label for process: %v\n", err)
os.Exit(1)
}
logrus.Debugf("setting resource limits")
if err = setRlimits(options.Spec, false, false); err != nil {
fmt.Fprintf(os.Stderr, "error setting process resource limits for process: %v\n", err)
os.Exit(1)
}
// Try to change to the directory.
cwd := options.Spec.Process.Cwd
if !filepath.IsAbs(cwd) {
cwd = "/" + cwd
}
cwd = filepath.Clean(cwd)
if err := unix.Chdir("/"); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into new root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chdir(cwd); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into directory %q under root %q: %v\n", cwd, options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("changed working directory to %q", cwd)
// Drop privileges.
user := options.Spec.Process.User
if len(user.AdditionalGids) > 0 {
gids := make([]int, len(user.AdditionalGids))
for i := range user.AdditionalGids {
gids[i] = int(user.AdditionalGids[i])
}
logrus.Debugf("setting supplemental groups")
if err = syscall.Setgroups(gids); err != nil {
fmt.Fprintf(os.Stderr, "error setting supplemental groups list: %v", err)
os.Exit(1)
}
} else {
setgroups, _ := ioutil.ReadFile("/proc/self/setgroups")
if strings.Trim(string(setgroups), "\n") != "deny" {
logrus.Debugf("clearing supplemental groups")
if err = syscall.Setgroups([]int{}); err != nil {
fmt.Fprintf(os.Stderr, "error clearing supplemental groups list: %v", err)
os.Exit(1)
}
}
}
logrus.Debugf("setting gid")
if err = syscall.Setresgid(int(user.GID), int(user.GID), int(user.GID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting GID: %v", err)
os.Exit(1)
}
if err = setSeccomp(options.Spec); err != nil {
fmt.Fprintf(os.Stderr, "error setting seccomp filter for process: %v\n", err)
os.Exit(1)
}
logrus.Debugf("setting capabilities")
var keepCaps []string
if user.UID != 0 {
keepCaps = []string{"CAP_SETUID"}
}
if err := setCapabilities(options.Spec, keepCaps...); err != nil {
fmt.Fprintf(os.Stderr, "error setting capabilities for process: %v\n", err)
os.Exit(1)
}
logrus.Debugf("setting uid")
if err = syscall.Setresuid(int(user.UID), int(user.UID), int(user.UID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting UID: %v", err)
os.Exit(1)
}
// Actually run the specified command.
cmd := exec.Command(args[0], args[1:]...)
setPdeathsig(cmd)
cmd.Env = options.Spec.Process.Env
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.Dir = cwd
logrus.Debugf("Running %#v (PATH = %q)", cmd, os.Getenv("PATH"))
interrupted := make(chan os.Signal, 100)
if err = cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "process failed to start with error: %v", err)
}
go func() {
for range interrupted {
if err := cmd.Process.Signal(syscall.SIGKILL); err != nil {
logrus.Infof("%v while attempting to send SIGKILL to child process", err)
}
}
}()
signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
err = cmd.Wait()
signal.Stop(interrupted)
close(interrupted)
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if waitStatus, ok := exitError.ProcessState.Sys().(syscall.WaitStatus); ok {
if waitStatus.Exited() {
if waitStatus.ExitStatus() != 0 {
fmt.Fprintf(os.Stderr, "subprocess exited with status %d\n", waitStatus.ExitStatus())
}
os.Exit(waitStatus.ExitStatus())
} else if waitStatus.Signaled() {
fmt.Fprintf(os.Stderr, "subprocess exited on %s\n", waitStatus.Signal())
os.Exit(1)
}
}
}
fmt.Fprintf(os.Stderr, "process exited with error: %v", err)
os.Exit(1)
}
}
// logNamespaceDiagnostics knows which namespaces we want to create.
// Output debug messages when that differs from what we're being asked to do.
func logNamespaceDiagnostics(spec *specs.Spec) {