func changeUser(username string) error { currentUser, err := user.Current() if err != nil { return util.Errorf("Could not determine current user: %s", err) } uid, gid, err := p2_user.IDs(username) if err != nil { return util.Errorf("Could not retrieve uid/gid for %q: %s", username, err) } if strconv.Itoa(uid) == currentUser.Uid && strconv.Itoa(gid) == currentUser.Gid { return nil } userCstring := C.CString(username) defer C.free(unsafe.Pointer(userCstring)) ret, err := C.initgroups(userCstring, C.__gid_t(gid)) if ret != 0 && err != nil { return util.Errorf("Could not initgroups for %q (primary gid %v): %s", username, gid, err) } ret, err = C.setgid(C.__gid_t(gid)) if ret != 0 && err != nil { return util.Errorf("Could not setgid %v: %s", gid, err) } ret, err = C.setuid(C.__uid_t(uid)) if ret != 0 && err != nil { return util.Errorf("Could not setuid %v: %s", uid, err) } return nil }
func (pod *Pod) WriteCurrentManifest(manifest manifest.Manifest) (string, error) { // write the old manifest to a temporary location in case a launch fails. tmpDir, err := ioutil.TempDir("", "manifests") if err != nil { return "", util.Errorf("could not create a tempdir to write old manifest: %s", err) } lastManifest := filepath.Join(tmpDir, "last_manifest.yaml") if _, err := os.Stat(pod.currentPodManifestPath()); err == nil { podManifestURL, err := url.Parse(pod.currentPodManifestPath()) if err != nil { return "", util.Errorf("Couldn't parse manifest path '%s' as URL: %s", pod.currentPodManifestPath(), err) } err = uri.URICopy(podManifestURL, lastManifest) if err != nil && !os.IsNotExist(err) { return "", err } } f, err := os.OpenFile(pod.currentPodManifestPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { pod.logError(err, "Unable to open current manifest file") err = pod.revertCurrentManifest(lastManifest) if err != nil { pod.logError(err, "Couldn't replace old manifest as current") } return "", err } defer f.Close() err = manifest.Write(f) if err != nil { pod.logError(err, "Unable to write current manifest file") err = pod.revertCurrentManifest(lastManifest) if err != nil { pod.logError(err, "Couldn't replace old manifest as current") } return "", err } uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { pod.logError(err, "Unable to find pod UID/GID") // the write was still successful so we are not going to revert return "", err } err = f.Chown(uid, gid) if err != nil { pod.logError(err, "Unable to chown current manifest") return "", err } return lastManifest, nil }
// Executables gets a list of the runit services that will be built for this launchable. func (l *Launchable) Executables(serviceBuilder *runit.ServiceBuilder) ([]launch.Executable, error) { if !l.Installed() { return []launch.Executable{}, util.Errorf("%s is not installed", l.ServiceID_) } uid, gid, err := user.IDs(l.RunAs) if err != nil { return nil, util.Errorf("%s: unknown runas user: %s", l.ServiceID_, l.RunAs) } lspec, err := l.getSpec() if err != nil { return nil, util.Errorf("%s: loading container specification: %s", l.ServiceID_, err) } expectedPlatform := Platform{ OS: "linux", Arch: "amd64", } if lspec.Platform != expectedPlatform { return nil, util.Errorf( "%s: unsupported container platform: %#v expected %#v", l.ServiceID_, lspec.Platform, expectedPlatform, ) } if filepath.Base(lspec.Root.Path) != lspec.Root.Path { return nil, util.Errorf("%s: invalid container root: %s", l.ServiceID_, lspec.Root.Path) } luser := lspec.Process.User if uid != int(luser.UID) || gid != int(luser.GID) { return nil, util.Errorf("%s: cannot execute as %s(%d:%d): container expects %d:%d", l.ServiceID_, l.RunAs, uid, gid, luser.UID, luser.GID) } serviceName := l.ServiceID_ + "__container" return []launch.Executable{{ Service: runit.Service{ Path: filepath.Join(serviceBuilder.RunitRoot, serviceName), Name: serviceName, }, Exec: append( []string{l.P2Exec}, p2exec.P2ExecArgs{ // TODO: support environment variables NoLimits: true, WorkDir: l.InstallDir(), Command: []string{*RuncPath, "start"}, }.CommandLine()..., ), }}, nil }
func (l *Launchable) flipSymlink(newLinkPath string) error { dir, err := ioutil.TempDir(l.RootDir, l.ServiceID_) if err != nil { return util.Errorf("Couldn't create temporary directory for symlink: %s", err) } defer os.RemoveAll(dir) tempLinkPath := filepath.Join(dir, l.ServiceID_) err = os.Symlink(l.InstallDir(), tempLinkPath) if err != nil { return util.Errorf("Couldn't create symlink for OpenContainer launchable %s: %s", l.ServiceID_, err) } uid, gid, err := user.IDs(l.RunAs) if err != nil { return util.Errorf("Couldn't retrieve UID/GID for OpenContainer launchable %s user %s: %s", l.ServiceID_, l.RunAs, err) } err = os.Lchown(tempLinkPath, uid, gid) if err != nil { return util.Errorf("Couldn't lchown symlink for OpenContainer launchable %s: %s", l.ServiceID_, err) } return os.Rename(tempLinkPath, newLinkPath) }
func changeUser(username string) error { uid, gid, err := user.IDs(username) if err != nil { return util.Errorf("Could not retrieve uid/gid for %q: %s", username, err) } userCstring := C.CString(username) defer C.free(unsafe.Pointer(userCstring)) ret, err := C.initgroups(userCstring, C.int(gid)) if ret != 0 && err != nil { return util.Errorf("Could not initgroups for %q (primary gid %v): %s", username, gid, err) } ret, err = C.setgid(C.gid_t(gid)) if ret != 0 && err != nil { return util.Errorf("Could not setgid %v: %s", gid, err) } ret, err = C.setuid(C.uid_t(uid)) if ret != 0 && err != nil { return util.Errorf("Could not setuid %v: %s", uid, err) } return nil }
// Install will ensure that executables for all required services are present on the host // machine and are set up to run. In the case of Hoist artifacts (which is the only format // supported currently, this will set up runit services.). func (pod *Pod) Install(manifest *Manifest) error { podHome := pod.path uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { return util.Errorf("Could not determine pod UID/GID: %s", err) } err = util.MkdirChownAll(podHome, uid, gid, 0755) if err != nil { return util.Errorf("Could not create pod home: %s", err) } // we may need to write config files to a unique directory per pod version, depending on restart semantics. Need // to think about this more. err = pod.setupConfig(manifest) if err != nil { pod.logError(err, "Could not setup config") return util.Errorf("Could not setup config: %s", err) } launchables, err := pod.Launchables(manifest) if err != nil { return err } for _, launchable := range launchables { err := launchable.Install() if err != nil { pod.logLaunchableError(launchable.Id, err, "Unable to install launchable") return err } } pod.logInfo("Successfully installed") return nil }
// ExtractTarGz reads a gzipped tar stream and extracts all files to the destination // directory. If an owner name is specified, all files will be created to be owned by that // user; otherwise, the tar specifies ownership. // // If any file would be extracted outside of the destination directory due to relative paths // containing '..', the archive will be rejected with an error. func ExtractTarGz(owner string, fp io.Reader, dest string) (err error) { fz, err := gzip.NewReader(fp) if err != nil { return util.Errorf("error reading gzip data: %s", err) } defer fz.Close() tr := tar.NewReader(fz) var ownerUID, ownerGID int if owner != "" { ownerUID, ownerGID, err = user.IDs(owner) if err != nil { return err } } err = util.MkdirChownAll(dest, ownerUID, ownerGID, 0755) if err != nil { return util.Errorf("error creating root directory %s: %s", dest, err) } err = os.Chown(dest, ownerUID, ownerGID) if err != nil { return util.Errorf("error setting ownership of root directory %s: %s", dest, err) } for { hdr, err := tr.Next() if err == io.EOF { break } if err != nil { return util.Errorf("read error: %s", err) } fpath := filepath.Join(dest, hdr.Name) var uid, gid int if owner == "" { uid, gid = hdr.Uid, hdr.Gid } else { uid, gid = ownerUID, ownerGID } // Error on all files that would end up outside the destination directory. if !strings.HasPrefix(fpath, dest) { return util.Errorf( "cannot extract %s, as its target %s is outside the root directory %s", hdr.Name, fpath, dest, ) } parent := filepath.Dir(fpath) if err := util.MkdirChownAll(parent, ownerUID, ownerGID, 0755); err != nil { return util.Errorf( "error creating directory %s (parent of %s): %s", parent, hdr.Name, err, ) } switch hdr.Typeflag { case tar.TypeSymlink: err = os.Symlink(hdr.Linkname, fpath) if err != nil { return util.Errorf( "error creating symlink %s -> %s: %s", fpath, hdr.Linkname, err, ) } err = os.Lchown(fpath, uid, gid) if err != nil { return util.Errorf("error setting owner of %s: %s", fpath, err) } case tar.TypeLink: // If you include a file multiple times in an invocation of // Gnu tar, it stores anything after the first as a hard link // to itself. Since such a structure can't otherwise exist, we // can simply skip it. if hdr.Name == hdr.Linkname { continue } // hardlink paths are encoded relative to the tarball root, rather than // the path of the link itself, so we need to resolve that path linkTarget, err := filepath.Rel(filepath.Dir(hdr.Name), hdr.Linkname) if err != nil { return util.Errorf( "error resolving link: %s -> %s: %s", fpath, hdr.Linkname, err, ) } // we can't make the hardlink right away because the target might not // exist, so we'll just make a symlink instead err = os.Symlink(linkTarget, fpath) if err != nil { return util.Errorf( "error creating symlink %s -> %s (originally hardlink): %s", fpath, linkTarget, err, ) } case tar.TypeDir: err = os.Mkdir(fpath, hdr.FileInfo().Mode()) if err != nil && !os.IsExist(err) { return util.Errorf("error creating directory %s: %s", fpath, err) } err = os.Chown(fpath, uid, gid) if err != nil { return util.Errorf("error setting ownership of %s: %s", fpath, err) } case tar.TypeReg, tar.TypeRegA: // Extract the file inside a closure to limit the scope of its open FD err = func() (innerErr error) { f, err := os.OpenFile( fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdr.FileInfo().Mode(), ) if err != nil { return util.Errorf("error creating %s: %s", fpath, err) } // Released at end of "case" statement defer func() { if closeErr := f.Close(); innerErr == nil { innerErr = closeErr } }() err = f.Chown(uid, gid) if err != nil { return util.Errorf("error setting file ownership of %s: %s", fpath, err) } _, err = io.Copy(f, tr) if err != nil { return util.Errorf("error extracting to %s: %s", fpath, err) } return nil }() if err != nil { return err } default: return util.Errorf("unhandled type flag %q (header %v)", hdr.Typeflag, hdr) } } return nil }
// setupConfig creates two directories in the pod's home directory, called "env" and "config." // the "config" directory contains the pod's config file, named with pod's ID and the // SHA of its manifest's content. The "env" directory contains environment files // (as described in http://smarden.org/runit/chpst.8.html, with the -e option) and includes a // single file called CONFIG_PATH, which points at the file written in the "config" directory. func (pod *Pod) setupConfig(manifest *Manifest) error { uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { return util.Errorf("Could not determine pod UID/GID: %s", err) } err = util.MkdirChownAll(pod.ConfigDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create config directory for pod %s: %s", manifest.ID(), err) } configFileName, err := manifest.ConfigFileName() if err != nil { return err } configPath := filepath.Join(pod.ConfigDir(), configFileName) file, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) defer file.Close() if err != nil { return util.Errorf("Could not open config file for pod %s for writing: %s", manifest.ID(), err) } err = manifest.WriteConfig(file) if err != nil { return err } err = file.Chown(uid, gid) if err != nil { return err } platConfigFileName, err := manifest.PlatformConfigFileName() if err != nil { return err } platConfigPath := filepath.Join(pod.ConfigDir(), platConfigFileName) platFile, err := os.OpenFile(platConfigPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) defer platFile.Close() if err != nil { return util.Errorf("Could not open config file for pod %s for writing: %s", manifest.ID(), err) } err = manifest.WritePlatformConfig(platFile) if err != nil { return err } err = platFile.Chown(uid, gid) if err != nil { return err } err = util.MkdirChownAll(pod.EnvDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create the environment dir for pod %s: %s", manifest.ID(), err) } err = writeEnvFile(pod.EnvDir(), "CONFIG_PATH", configPath, uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), "PLATFORM_CONFIG_PATH", platConfigPath, uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), "POD_HOME", pod.Path(), uid, gid) if err != nil { return err } return nil }
// setupConfig does the following: // // 1) creates a directory in the pod's home directory called "config" which // contains YAML configuration files (named with pod's ID and the SHA of its // manifest's content) the path to which will be exported to a pods launchables // via the CONFIG_PATH environment variable // // 2) writes an "env" directory in the pod's home directory called "env" which // contains environment variables written as files that will be exported to all // processes started by all launchables (as described in // http://smarden.org/runit/chpst.8.html, with the -e option), including // CONFIG_PATH // // 3) writes an "env" directory for each launchable. The "env" directory // contains environment files specific to a launchable (such as // LAUNCHABLE_ROOT) // // We may wish to provide a "config" directory per launchable at some point as // well, so that launchables can have different config namespaces func (pod *Pod) setupConfig(manifest manifest.Manifest, launchables []launch.Launchable) error { uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { return util.Errorf("Could not determine pod UID/GID: %s", err) } var configData bytes.Buffer err = manifest.WriteConfig(&configData) if err != nil { return err } var platConfigData bytes.Buffer err = manifest.WritePlatformConfig(&platConfigData) if err != nil { return err } err = util.MkdirChownAll(pod.ConfigDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create config directory for pod %s: %s", manifest.ID(), err) } configFileName, err := manifest.ConfigFileName() if err != nil { return err } configPath := filepath.Join(pod.ConfigDir(), configFileName) err = writeFileChown(configPath, configData.Bytes(), uid, gid) if err != nil { return util.Errorf("Error writing config file for pod %s: %s", manifest.ID(), err) } platConfigFileName, err := manifest.PlatformConfigFileName() if err != nil { return err } platConfigPath := filepath.Join(pod.ConfigDir(), platConfigFileName) err = writeFileChown(platConfigPath, platConfigData.Bytes(), uid, gid) if err != nil { return util.Errorf("Error writing platform config file for pod %s: %s", manifest.ID(), err) } err = util.MkdirChownAll(pod.EnvDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create the environment dir for pod %s: %s", manifest.ID(), err) } err = writeEnvFile(pod.EnvDir(), ConfigPathEnvVar, configPath, uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PlatformConfigPathEnvVar, platConfigPath, uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PodHomeEnvVar, pod.Home(), uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PodIDEnvVar, pod.Id.String(), uid, gid) if err != nil { return err } err = writeEnvFile(pod.EnvDir(), PodUniqueKeyEnvVar, pod.uniqueKey.String(), uid, gid) if err != nil { return err } for _, launchable := range launchables { // we need to remove any unset env vars from a previous pod err = os.RemoveAll(launchable.EnvDir()) if err != nil { return err } err = util.MkdirChownAll(launchable.EnvDir(), uid, gid, 0755) if err != nil { return util.Errorf("Could not create the environment dir for pod %s launchable %s: %s", manifest.ID(), launchable.ServiceID(), err) } err = writeEnvFile(launchable.EnvDir(), LaunchableIDEnvVar, launchable.ID().String(), uid, gid) if err != nil { return err } err = writeEnvFile(launchable.EnvDir(), "LAUNCHABLE_ROOT", launchable.InstallDir(), uid, gid) if err != nil { return err } // last, write the user-supplied env variables to ensure priority of user-supplied values for envName, value := range launchable.EnvVars() { err = writeEnvFile(launchable.EnvDir(), envName, fmt.Sprint(value), uid, gid) if err != nil { return err } } } return nil }
// Install will ensure that executables for all required services are present on the host // machine and are set up to run. In the case of Hoist artifacts (which is the only format // supported currently, this will set up runit services.). func (pod *Pod) Install(manifest manifest.Manifest, verifier auth.ArtifactVerifier, artifactRegistry artifact.Registry) error { podHome := pod.home uid, gid, err := user.IDs(manifest.RunAsUser()) if err != nil { return util.Errorf("Could not determine pod UID/GID for %s: %s", manifest.RunAsUser(), err) } err = util.MkdirChownAll(podHome, uid, gid, 0755) if err != nil { return util.Errorf("Could not create pod home: %s", err) } launchables, err := pod.Launchables(manifest) if err != nil { return err } downloader := artifact.NewLocationDownloader(pod.Fetcher, verifier) for launchableID, stanza := range manifest.GetLaunchableStanzas() { // TODO: investigate passing in necessary fields to InstallDir() launchable, err := pod.getLaunchable(launchableID, stanza, manifest.RunAsUser()) if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") return err } if launchable.Installed() { continue } launchableURL, verificationData, err := artifactRegistry.LocationDataForLaunchable(launchableID, stanza) if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") return err } err = downloader.Download(launchableURL, verificationData, launchable.InstallDir(), manifest.RunAsUser()) if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") _ = os.Remove(launchable.InstallDir()) return err } err = launchable.PostInstall() if err != nil { pod.logLaunchableError(launchable.ServiceID(), err, "Unable to install launchable") _ = os.Remove(launchable.InstallDir()) return err } } // we may need to write config files to a unique directory per pod version, depending on restart semantics. Need // to think about this more. err = pod.setupConfig(manifest, launchables) if err != nil { pod.logError(err, "Could not setup config") return util.Errorf("Could not setup config: %s", err) } pod.logInfo("Successfully installed") return nil }