func TestCopyTree(t *testing.T) { td, err := ioutil.TempDir("", tstprefix) if err != nil { t.Fatal(err) } defer os.RemoveAll(td) src := filepath.Join(td, "src") dst := filepath.Join(td, "dst") if err := os.MkdirAll(filepath.Join(td, "src"), 0755); err != nil { t.Fatal(err) } tr := []tree{ { path: "dir1", dir: true, }, { path: "dir2", dir: true, }, { path: "dir1/foo", dir: false, }, { path: "dir1/bar", dir: false, }, } createTree(t, src, tr) // absolute paths if err := CopyTree(src, dst, uid.NewBlankUidRange()); err != nil { t.Fatal(err) } checkTree(t, dst, tr) // relative paths if err := os.Chdir(td); err != nil { t.Fatal(err) } dst = "dst-rel1" if err := CopyTree("././src/", dst, uid.NewBlankUidRange()); err != nil { t.Fatal(err) } checkTree(t, dst, tr) dst = "./dst-rel2" if err := CopyTree("./src", dst, uid.NewBlankUidRange()); err != nil { t.Fatal(err) } checkTree(t, dst, tr) }
func extractTarInsecureHelperPWL(rdr io.Reader, target string, pwl PathWhitelistMap) error { editor, err := NewUidShiftingFilePermEditor(uid.NewBlankUidRange()) if err != nil { return err } return ExtractTarInsecure(tar.NewReader(rdr), target, true, pwl, editor) }
// writeEnvFile creates an environment file for given app name, the minimum // required environment variables by the appc spec will be set to sensible // defaults here if they're not provided by env. func writeEnvFile(p *stage1commontypes.Pod, env types.Environment, appName types.ACName, privateUsers string) error { ef := bytes.Buffer{} for dk, dv := range defaultEnv { if _, exists := env.Get(dk); !exists { fmt.Fprintf(&ef, "%s=%s\000", dk, dv) } } for _, e := range env { fmt.Fprintf(&ef, "%s=%s\000", e.Name, e.Value) } uidRange := uid.NewBlankUidRange() if err := uidRange.Deserialize([]byte(privateUsers)); err != nil { return err } envFilePath := EnvFilePath(p.Root, appName) if err := ioutil.WriteFile(envFilePath, ef.Bytes(), 0644); err != nil { return err } if uidRange.Shift != 0 && uidRange.Count != 0 { if err := os.Chown(envFilePath, int(uidRange.Shift), int(uidRange.Shift)); err != nil { return err } } return nil }
/*input: ocidirectory output:rktdirectrory */ func (this *oci2rkt) makeRktBundle() (err error) { //this.RktBunldePath = "/var/lib/rkt/rktbunld" stage1Path := this.RktBundlePath + "/stage1" err = os.MkdirAll(stage1Path, 0755) if err != nil { return err } //extra stage1.aci r, err := os.Open(this.Stage1Image) if err != nil { return err } defer r.Close() dr, err := aci.NewCompressedReader(r) if err != nil { return fmt.Errorf("error decompressing image: %v", err) } err = ptar.ExtractTar(dr, stage1Path, true, uid.NewBlankUidRange(), nil) if err != nil { fmt.Printf("extra stage1 image error,%v\n", err) return err } return nil }
// Write renders the ACI with the provided key in the treestore. id references // that specific tree store rendered image. // Write, to avoid having a rendered ACI with old stale files, requires that // the destination directory doesn't exist (usually Remove should be called // before Write) func (ts *TreeStore) Write(id string, key string, s *Store) error { treepath := filepath.Join(ts.path, id) fi, _ := os.Stat(treepath) if fi != nil { return fmt.Errorf("treestore: path %s already exists", treepath) } imageID, err := types.NewHash(key) if err != nil { return fmt.Errorf("treestore: cannot convert key to imageID: %v", err) } if err := os.MkdirAll(treepath, 0755); err != nil { return fmt.Errorf("treestore: cannot create treestore directory %s: %v", treepath, err) } err = aci.RenderACIWithImageID(*imageID, treepath, s, uid.NewBlankUidRange()) if err != nil { return fmt.Errorf("treestore: cannot render aci: %v", err) } hash, err := ts.Hash(id) if err != nil { return fmt.Errorf("treestore: cannot calculate tree hash: %v", err) } err = ioutil.WriteFile(filepath.Join(treepath, hashfilename), []byte(hash), 0644) if err != nil { return fmt.Errorf("treestore: cannot write hash file: %v", err) } // before creating the "rendered" flag file we need to ensure that all data is fsynced dfd, err := syscall.Open(treepath, syscall.O_RDONLY, 0) if err != nil { return err } defer syscall.Close(dfd) if err := sys.Syncfs(dfd); err != nil { return fmt.Errorf("treestore: failed to sync data: %v", err) } // Create rendered file f, err := os.Create(filepath.Join(treepath, renderedfilename)) if err != nil { return fmt.Errorf("treestore: failed to write rendered file: %v", err) } f.Close() if err := syscall.Fsync(dfd); err != nil { return fmt.Errorf("treestore: failed to sync tree store directory: %v", err) } return nil }
// PrepareMountpoints creates and sets permissions for empty volumes. // If the mountpoint comes from a Docker image and it is an implicit empty // volume, we copy files from the image to the volume, see // https://docs.docker.com/engine/userguide/containers/dockervolumes/#data-volumes func PrepareMountpoints(volPath string, targetPath string, vol *types.Volume, dockerImplicit bool) error { if vol.Kind != "empty" { return nil } diag.Printf("creating an empty volume folder for sharing: %q", volPath) m, err := strconv.ParseUint(*vol.Mode, 8, 32) if err != nil { return errwrap.Wrap(fmt.Errorf("invalid mode %q for volume %q", *vol.Mode, vol.Name), err) } mode := os.FileMode(m) Uid := *vol.UID Gid := *vol.GID if dockerImplicit { fi, err := os.Stat(targetPath) if err == nil { // the directory exists in the image, let's set the same // permissions and copy files from there to the empty volume mode = fi.Mode() Uid = int(fi.Sys().(*syscall.Stat_t).Uid) Gid = int(fi.Sys().(*syscall.Stat_t).Gid) if err := fileutil.CopyTree(targetPath, volPath, uid.NewBlankUidRange()); err != nil { return errwrap.Wrap(fmt.Errorf("error copying image files to empty volume %q", volPath), err) } } } if err := os.MkdirAll(volPath, 0770); err != nil { return errwrap.Wrap(fmt.Errorf("error creating %q", volPath), err) } if err := os.Chown(volPath, Uid, Gid); err != nil { return errwrap.Wrap(fmt.Errorf("could not change owner of %q", volPath), err) } if err := os.Chmod(volPath, mode); err != nil { return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", volPath), err) } return nil }
// createBackup backs a database up in a given directory. It basically // copies this directory into a given backups directory. The backups // directory has a simple structure - a directory inside named "0" is // the most recent backup. A directory name for oldest backup is // deduced from a given limit. For instance, for limit being 5 the // name for the oldest backup would be "4". If a backups number // exceeds the given limit then only newest ones are kept and the rest // is removed. func createBackup(dbDir, backupsDir string, limit int) error { tmpBackupDir := filepath.Join(backupsDir, "tmp") if err := os.MkdirAll(backupsDir, defaultPathPerm); err != nil { return err } if err := fileutil.CopyTree(dbDir, tmpBackupDir, uid.NewBlankUidRange()); err != nil { return err } defer os.RemoveAll(tmpBackupDir) // prune backups if err := pruneOldBackups(backupsDir, limit-1); err != nil { return err } if err := shiftBackups(backupsDir, limit-2); err != nil { return err } if err := os.Rename(tmpBackupDir, filepath.Join(backupsDir, "0")); err != nil { return err } return nil }
func runPrepare(cmd *cobra.Command, args []string) (exit int) { var err error origStdout := os.Stdout privateUsers := uid.NewBlankUidRange() if flagQuiet { if os.Stdout, err = os.Open("/dev/null"); err != nil { stderr("prepare: unable to open /dev/null") return 1 } } if flagPrivateUsers { if !common.SupportsUserNS() { stderr("prepare: --private-users is not supported, kernel compiled without user namespace support") return 1 } privateUsers.SetRandomUidRange(uid.DefaultRangeCount) } if err = parseApps(&rktApps, args, cmd.Flags(), true); err != nil { stderr("prepare: error parsing app image arguments: %v", err) return 1 } if len(flagPodManifest) > 0 && (len(flagVolumes) > 0 || len(flagPorts) > 0 || flagInheritEnv || !flagExplicitEnv.IsEmpty() || flagLocal) { stderr("prepare: conflicting flags set with --pod-manifest (see --help)") return 1 } if rktApps.Count() < 1 && len(flagPodManifest) == 0 { stderr("prepare: must provide at least one image or specify the pod manifest") return 1 } if globalFlags.Dir == "" { log.Printf("dir unset - using temporary directory") globalFlags.Dir, err = ioutil.TempDir("", "rkt") if err != nil { stderr("prepare: error creating temporary directory: %v", err) return 1 } } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("prepare: cannot open store: %v", err) return 1 } config, err := getConfig() if err != nil { stderr("prepare: cannot get configuration: %v", err) return 1 } fn := &finder{ imageActionData: imageActionData{ s: s, headers: config.AuthPerHost, dockerAuth: config.DockerCredentialsPerRegistry, insecureSkipVerify: globalFlags.InsecureSkipVerify, debug: globalFlags.Debug, }, local: flagLocal, withDeps: false, } s1img, err := getStage1Hash(s, flagStage1Image) if err != nil { stderr("prepare: %v", err) return 1 } fn.ks = getKeystore() fn.withDeps = true if err := fn.findImages(&rktApps); err != nil { stderr("%v", err) return 1 } p, err := newPod() if err != nil { stderr("prepare: error creating new pod: %v", err) return 1 } cfg := stage0.CommonConfig{ Store: s, Stage1Image: *s1img, UUID: p.uuid, Debug: globalFlags.Debug, } pcfg := stage0.PrepareConfig{ CommonConfig: cfg, UseOverlay: !flagNoOverlay && common.SupportsOverlay(), PrivateUsers: privateUsers, } if len(flagPodManifest) > 0 { pcfg.PodManifest = flagPodManifest } else { pcfg.Volumes = []types.Volume(flagVolumes) pcfg.Ports = []types.ExposedPort(flagPorts) pcfg.InheritEnv = flagInheritEnv pcfg.ExplicitEnv = flagExplicitEnv.Strings() pcfg.Apps = &rktApps } if err = stage0.Prepare(pcfg, p.path(), p.uuid); err != nil { stderr("prepare: error setting up stage0: %v", err) return 1 } if err := p.sync(); err != nil { stderr("prepare: error syncing pod data: %v", err) return 1 } if err := p.xToPrepared(); err != nil { stderr("prepare: error transitioning to prepared: %v", err) return 1 } os.Stdout = origStdout // restore output in case of --quiet stdout("%s", p.uuid.String()) return 0 }
func TestExtractTarPWL(t *testing.T) { if !sys.HasChrootCapability() { t.Skipf("chroot capability not available. Disabling test.") } entries := []*testTarEntry{ { header: &tar.Header{ Name: "folder/", Typeflag: tar.TypeDir, Mode: int64(0747), }, }, { contents: "foo", header: &tar.Header{ Name: "folder/foo.txt", Size: 3, }, }, { contents: "bar", header: &tar.Header{ Name: "folder/bar.txt", Size: 3, }, }, { header: &tar.Header{ Name: "folder/symlink.txt", Typeflag: tar.TypeSymlink, Linkname: "folder/foo.txt", }, }, } testTarPath, err := newTestTar(entries) if err != nil { t.Errorf("unexpected error: %v", err) } defer os.Remove(testTarPath) containerTar, err := os.Open(testTarPath) if err != nil { t.Errorf("unexpected error: %v", err) } defer containerTar.Close() tmpdir, err := ioutil.TempDir("", "rkt-temp-dir") if err != nil { t.Errorf("unexpected error: %v", err) } defer os.RemoveAll(tmpdir) pwl := make(PathWhitelistMap) pwl["folder/foo.txt"] = struct{}{} err = ExtractTar(containerTar, tmpdir, false, uid.NewBlankUidRange(), pwl) if err != nil { t.Errorf("unexpected error: %v", err) } matches, err := filepath.Glob(filepath.Join(tmpdir, "folder/*.txt")) if err != nil { t.Errorf("unexpected error: %v", err) } if len(matches) != 1 { t.Errorf("unexpected number of files found: %d, wanted 1", len(matches)) } }
func runRun(cmd *cobra.Command, args []string) (exit int) { privateUsers := uid.NewBlankUidRange() err := parseApps(&rktApps, args, cmd.Flags(), true) if err != nil { stderr("run: error parsing app image arguments: %v", err) return 1 } if flagStoreOnly && flagNoStore { stderr("both --store-only and --no-store specified") return 1 } if flagPrivateUsers { if !common.SupportsUserNS() { stderr("run: --private-users is not supported, kernel compiled without user namespace support") return 1 } privateUsers.SetRandomUidRange(uid.DefaultRangeCount) } if len(flagPorts) > 0 && flagNet.None() { stderr("--port flag does not work with 'none' networking") return 1 } if len(flagPorts) > 0 && flagNet.Host() { stderr("--port flag does not work with 'host' networking") return 1 } if flagMDSRegister && flagNet.None() { stderr("--mds-register flag does not work with --net=none. Please use 'host', 'default' or an equivalent network") return 1 } if len(flagPodManifest) > 0 && (len(flagPorts) > 0 || flagInheritEnv || !flagExplicitEnv.IsEmpty() || rktApps.Count() > 0 || flagStoreOnly || flagNoStore) { stderr("conflicting flags set with --pod-manifest (see --help)") return 1 } if flagInteractive && rktApps.Count() > 1 { stderr("run: interactive option only supports one image") return 1 } if rktApps.Count() < 1 && len(flagPodManifest) == 0 { stderr("run: must provide at least one image or specify the pod manifest") return 1 } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("run: cannot open store: %v", err) return 1 } config, err := getConfig() if err != nil { stderr("run: cannot get configuration: %v", err) return 1 } fn := &finder{ imageActionData: imageActionData{ s: s, headers: config.AuthPerHost, dockerAuth: config.DockerCredentialsPerRegistry, insecureSkipVerify: globalFlags.InsecureSkipVerify, debug: globalFlags.Debug, }, storeOnly: flagStoreOnly, noStore: flagNoStore, withDeps: false, } s1img, err := getStage1Hash(s, cmd) if err != nil { stderr("%v", err) return 1 } fn.ks = getKeystore() fn.withDeps = true if err := fn.findImages(&rktApps); err != nil { stderr("%v", err) return 1 } p, err := newPod() if err != nil { stderr("Error creating new pod: %v", err) return 1 } // if requested, write out pod UUID early so "rkt rm" can // clean it up even if something goes wrong if flagUUIDFileSave != "" { if err := writeUUIDToFile(p.uuid, flagUUIDFileSave); err != nil { stderr("Error saving pod UUID to file: %v", err) return 1 } } processLabel, mountLabel, err := label.InitLabels(nil) if err != nil { stderr("Error initialising SELinux: %v", err) return 1 } cfg := stage0.CommonConfig{ MountLabel: mountLabel, ProcessLabel: processLabel, Store: s, Stage1Image: *s1img, UUID: p.uuid, Debug: globalFlags.Debug, } pcfg := stage0.PrepareConfig{ CommonConfig: cfg, UseOverlay: !flagNoOverlay && common.SupportsOverlay(), PrivateUsers: privateUsers, } if len(flagPodManifest) > 0 { pcfg.PodManifest = flagPodManifest } else { pcfg.Ports = []types.ExposedPort(flagPorts) pcfg.InheritEnv = flagInheritEnv pcfg.ExplicitEnv = flagExplicitEnv.Strings() pcfg.Apps = &rktApps } if globalFlags.Debug { stage0.InitDebug() } keyLock, err := lock.SharedKeyLock(lockDir(), common.PrepareLock) if err != nil { stderr("rkt: cannot get shared prepare lock: %v", err) return 1 } err = stage0.Prepare(pcfg, p.path(), p.uuid) if err != nil { stderr("run: error setting up stage0: %v", err) keyLock.Close() return 1 } keyLock.Close() // get the lock fd for run lfd, err := p.Fd() if err != nil { stderr("Error getting pod lock fd: %v", err) return 1 } // skip prepared by jumping directly to run, we own this pod if err := p.xToRun(); err != nil { stderr("run: unable to transition to run: %v", err) return 1 } rktgid, err := common.LookupGid(common.RktGroup) if err != nil { stderr("run: group %q not found, will use default gid when rendering images", common.RktGroup) rktgid = -1 } rcfg := stage0.RunConfig{ CommonConfig: cfg, Net: flagNet, LockFd: lfd, Interactive: flagInteractive, MDSRegister: flagMDSRegister, LocalConfig: globalFlags.LocalConfigDir, RktGid: rktgid, } apps, err := p.getApps() if err != nil { stderr("run: cannot get the appList in the pod manifest: %v", err) return 1 } rcfg.Apps = apps stage0.Run(rcfg, p.path(), globalFlags.Dir) // execs, never returns return 1 }
func runPrepare(cmd *cobra.Command, args []string) (exit int) { var err error origStdout := os.Stdout privateUsers := uid.NewBlankUidRange() if flagQuiet { if os.Stdout, err = os.Open("/dev/null"); err != nil { stderr("prepare: unable to open /dev/null: %v", err) return 1 } } if flagStoreOnly && flagNoStore { stderr("both --store-only and --no-store specified") return 1 } if flagPrivateUsers { if !common.SupportsUserNS() { stderr("prepare: --private-users is not supported, kernel compiled without user namespace support") return 1 } privateUsers.SetRandomUidRange(uid.DefaultRangeCount) } if err = parseApps(&rktApps, args, cmd.Flags(), true); err != nil { stderr("prepare: error parsing app image arguments: %v", err) return 1 } if len(flagPodManifest) > 0 && (len(flagPorts) > 0 || flagInheritEnv || !flagExplicitEnv.IsEmpty() || flagStoreOnly || flagNoStore) { stderr("prepare: conflicting flags set with --pod-manifest (see --help)") return 1 } if rktApps.Count() < 1 && len(flagPodManifest) == 0 { stderr("prepare: must provide at least one image or specify the pod manifest") return 1 } s, err := store.NewStore(getDataDir()) if err != nil { stderr("prepare: cannot open store: %v", err) return 1 } config, err := getConfig() if err != nil { stderr("prepare: cannot get configuration: %v", err) return 1 } s1img, err := getStage1Hash(s, cmd) if err != nil { stderr("prepare: %v", err) return 1 } fn := &image.Finder{ S: s, Ks: getKeystore(), Headers: config.AuthPerHost, DockerAuth: config.DockerCredentialsPerRegistry, InsecureFlags: globalFlags.InsecureFlags, Debug: globalFlags.Debug, TrustKeysFromHttps: globalFlags.TrustKeysFromHttps, StoreOnly: flagStoreOnly, NoStore: flagNoStore, WithDeps: true, } if err := fn.FindImages(&rktApps); err != nil { stderr("prepare: %v", err) return 1 } p, err := newPod() if err != nil { stderr("prepare: error creating new pod: %v", err) return 1 } cfg := stage0.CommonConfig{ Store: s, Stage1Image: *s1img, UUID: p.uuid, Debug: globalFlags.Debug, } pcfg := stage0.PrepareConfig{ CommonConfig: &cfg, UseOverlay: !flagNoOverlay && common.SupportsOverlay(), PrivateUsers: privateUsers, } if len(flagPodManifest) > 0 { pcfg.PodManifest = flagPodManifest } else { pcfg.Ports = []types.ExposedPort(flagPorts) pcfg.InheritEnv = flagInheritEnv pcfg.ExplicitEnv = flagExplicitEnv.Strings() pcfg.Apps = &rktApps } if globalFlags.Debug { stage0.InitDebug() } keyLock, err := lock.SharedKeyLock(lockDir(), common.PrepareLock) if err != nil { stderr("rkt: cannot get shared prepare lock: %v", err) return 1 } if err = stage0.Prepare(pcfg, p.path(), p.uuid); err != nil { stderr("prepare: error setting up stage0: %v", err) keyLock.Close() return 1 } keyLock.Close() if err := p.sync(); err != nil { stderr("prepare: error syncing pod data: %v", err) return 1 } if err := p.xToPrepared(); err != nil { stderr("prepare: error transitioning to prepared: %v", err) return 1 } os.Stdout = origStdout // restore output in case of --quiet stdout("%s", p.uuid.String()) return 0 }
func runImageExtract(cmd *cobra.Command, args []string) (exit int) { if len(args) != 2 { cmd.Usage() return 1 } outputDir := args[1] s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("image extract: cannot open store: %v", err) return 1 } key, err := getStoreKeyFromAppOrHash(s, args[0]) if err != nil { stderr("image extract: %v", err) return 1 } aci, err := s.ReadStream(key) if err != nil { stderr("image extract: error reading ACI from the store: %v", err) return 1 } // ExtractTar needs an absolute path absOutputDir, err := filepath.Abs(outputDir) if err != nil { stderr("image extract: error converting output to an absolute path: %v", err) return 1 } if _, err := os.Stat(absOutputDir); err == nil { if !flagExtractOverwrite { stderr("image extract: output directory exists (try --overwrite)") return 1 } // don't allow the user to delete the root filesystem by mistake if absOutputDir == "/" { stderr("image extract: this would delete your root filesystem. Refusing.") return 1 } if err := os.RemoveAll(absOutputDir); err != nil { stderr("image extract: error removing existing output dir: %v", err) return 1 } } // if the user only asks for the rootfs we extract the image to a temporary // directory and then move/copy the rootfs to the output directory, if not // we just extract the image to the output directory extractDir := absOutputDir if flagExtractRootfsOnly { rktTmpDir, err := s.TmpDir() if err != nil { stderr("image extract: error creating rkt temporary directory: %v", err) return 1 } tmpDir, err := ioutil.TempDir(rktTmpDir, "rkt-image-extract-") if err != nil { stderr("image extract: error creating temporary directory: %v", err) return 1 } defer os.RemoveAll(tmpDir) extractDir = tmpDir } else { if err := os.MkdirAll(absOutputDir, 0755); err != nil { stderr("image extract: error creating output directory: %v", err) return 1 } } if err := tar.ExtractTar(aci, extractDir, false, uid.NewBlankUidRange(), nil); err != nil { stderr("image extract: error extracting ACI: %v", err) return 1 } if flagExtractRootfsOnly { rootfsDir := filepath.Join(extractDir, "rootfs") if err := os.Rename(rootfsDir, absOutputDir); err != nil { if e, ok := err.(*os.LinkError); ok && e.Err == syscall.EXDEV { // it's on a different device, fall back to copying if err := fileutil.CopyTree(rootfsDir, absOutputDir, uid.NewBlankUidRange()); err != nil { stderr("image extract: error copying ACI rootfs: %v", err) return 1 } } else { stderr("image extract: error moving ACI rootfs: %v", err) return 1 } } } return 0 }
func TestExtractTarOverwrite(t *testing.T) { if !sys.HasChrootCapability() { t.Skipf("chroot capability not available. Disabling test.") } tmpdir, err := ioutil.TempDir("", "rkt-temp-dir") if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.RemoveAll(tmpdir) entries := []*testTarEntry{ { contents: "hello", header: &tar.Header{ Name: "hello.txt", Size: 5, }, }, { header: &tar.Header{ Name: "afolder", Typeflag: tar.TypeDir, }, }, { contents: "hello", header: &tar.Header{ Name: "afolder/hello.txt", Size: 5, }, }, { contents: "hello", header: &tar.Header{ Name: "afile", Size: 5, }, }, { header: &tar.Header{ Name: "folder01", Typeflag: tar.TypeDir, }, }, { contents: "hello", header: &tar.Header{ Name: "folder01/file01", Size: 5, }, }, { contents: "hello", header: &tar.Header{ Name: "filesymlinked", Size: 5, }, }, { header: &tar.Header{ Name: "linktofile", Linkname: "filesymlinked", Typeflag: tar.TypeSymlink, }, }, { header: &tar.Header{ Name: "dirsymlinked", Typeflag: tar.TypeDir, }, }, { header: &tar.Header{ Name: "linktodir", Linkname: "dirsymlinked", Typeflag: tar.TypeSymlink, }, }, } testTarPath, err := newTestTar(entries) if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(testTarPath) containerTar1, err := os.Open(testTarPath) if err != nil { t.Fatalf("unexpected error: %v", err) } defer containerTar1.Close() err = ExtractTar(containerTar1, tmpdir, false, uid.NewBlankUidRange(), nil) if err != nil { t.Fatalf("unexpected error: %v", err) } // Now overwrite: // a file with a new file // a dir with a file entries = []*testTarEntry{ { contents: "newhello", header: &tar.Header{ Name: "hello.txt", Size: 8, }, }, // Now this is a file { contents: "nowafile", header: &tar.Header{ Name: "afolder", Typeflag: tar.TypeReg, Size: 8, }, }, // Now this is a dir { header: &tar.Header{ Name: "afile", Typeflag: tar.TypeDir, }, }, // Overwrite symlink to a file with a regular file // the linked file shouldn't be removed { contents: "filereplacingsymlink", header: &tar.Header{ Name: "linktofile", Typeflag: tar.TypeReg, Size: 20, }, }, // Overwrite symlink to a dir with a regular file // the linked directory and all its contents shouldn't be // removed { contents: "filereplacingsymlink", header: &tar.Header{ Name: "linktodir", Typeflag: tar.TypeReg, Size: 20, }, }, // folder01 already exists and shouldn't be removed (keeping folder01/file01) { header: &tar.Header{ Name: "folder01", Typeflag: tar.TypeDir, Mode: int64(0755), }, }, { contents: "hello", header: &tar.Header{ Name: "folder01/file02", Size: 5, Mode: int64(0644), }, }, } testTarPath, err = newTestTar(entries) if err != nil { t.Errorf("unexpected error: %v", err) } defer os.Remove(testTarPath) containerTar2, err := os.Open(testTarPath) if err != nil { t.Errorf("unexpected error: %v", err) } defer containerTar2.Close() err = ExtractTar(containerTar2, tmpdir, true, uid.NewBlankUidRange(), nil) expectedFiles := []*fileInfo{ &fileInfo{path: "hello.txt", typeflag: tar.TypeReg, size: 8, contents: "newhello"}, &fileInfo{path: "linktofile", typeflag: tar.TypeReg, size: 20}, &fileInfo{path: "linktodir", typeflag: tar.TypeReg, size: 20}, &fileInfo{path: "afolder", typeflag: tar.TypeReg, size: 8}, &fileInfo{path: "dirsymlinked", typeflag: tar.TypeDir}, &fileInfo{path: "afile", typeflag: tar.TypeDir}, &fileInfo{path: "filesymlinked", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "folder01", typeflag: tar.TypeDir}, &fileInfo{path: "folder01/file01", typeflag: tar.TypeReg, size: 5}, &fileInfo{path: "folder01/file02", typeflag: tar.TypeReg, size: 5}, } err = checkExpectedFiles(tmpdir, fileInfoSliceToMap(expectedFiles)) if err != nil { t.Errorf("unexpected error: %v", err) } }
func runImageRender(cmd *cobra.Command, args []string) (exit int) { if len(args) != 2 { cmd.Usage() return 1 } outputDir := args[1] s, err := store.NewStore(getDataDir()) if err != nil { stderr("image render: cannot open store: %v", err) return 1 } key, err := getStoreKeyFromAppOrHash(s, args[0]) if err != nil { stderr("image render: %v", err) return 1 } id, _, err := s.RenderTreeStore(key, false) if err != nil { stderr("image render: error rendering ACI: %v", err) return 1 } if _, err := s.CheckTreeStore(id); err != nil { stderr("image render: warning: tree cache is in a bad state. Rebuilding...") var err error if id, _, err = s.RenderTreeStore(key, true); err != nil { stderr("image render: error rendering ACI: %v", err) return 1 } } if _, err := os.Stat(outputDir); err == nil { if !flagRenderOverwrite { stderr("image render: output directory exists (try --overwrite)") return 1 } // don't allow the user to delete the root filesystem by mistake if outputDir == "/" { stderr("image extract: this would delete your root filesystem. Refusing.") return 1 } if err := os.RemoveAll(outputDir); err != nil { stderr("image render: error removing existing output dir: %v", err) return 1 } } rootfsOutDir := outputDir if !flagRenderRootfsOnly { if err := os.MkdirAll(outputDir, 0755); err != nil { stderr("image render: error creating output directory: %v", err) return 1 } rootfsOutDir = filepath.Join(rootfsOutDir, "rootfs") manifest, err := s.GetImageManifest(key) if err != nil { stderr("image render: error getting manifest: %v", err) return 1 } mb, err := json.Marshal(manifest) if err != nil { stderr("image render: error marshalling image manifest: %v", err) return 1 } if err := ioutil.WriteFile(filepath.Join(outputDir, "manifest"), mb, 0700); err != nil { stderr("image render: error writing image manifest: %v", err) return 1 } } cachedTreePath := s.GetTreeStoreRootFS(id) if err := fileutil.CopyTree(cachedTreePath, rootfsOutDir, uid.NewBlankUidRange()); err != nil { stderr("image render: error copying ACI rootfs: %v", err) return 1 } return 0 }
func TestExtractTarFolders(t *testing.T) { if !sys.HasChrootCapability() { t.Skipf("chroot capability not available. Disabling test.") } entries := []*testTarEntry{ { contents: "foo", header: &tar.Header{ Name: "deep/folder/foo.txt", Size: 3, }, }, { header: &tar.Header{ Name: "deep/folder/", Typeflag: tar.TypeDir, Mode: int64(0747), }, }, { contents: "bar", header: &tar.Header{ Name: "deep/folder/bar.txt", Size: 3, }, }, { header: &tar.Header{ Name: "deep/folder2/symlink.txt", Typeflag: tar.TypeSymlink, Linkname: "deep/folder/foo.txt", }, }, { header: &tar.Header{ Name: "deep/folder2/", Typeflag: tar.TypeDir, Mode: int64(0747), }, }, { contents: "bar", header: &tar.Header{ Name: "deep/folder2/bar.txt", Size: 3, }, }, { header: &tar.Header{ Name: "deep/deep/folder", Typeflag: tar.TypeDir, Mode: int64(0755), }, }, { header: &tar.Header{ Name: "deep/deep/", Typeflag: tar.TypeDir, Mode: int64(0747), }, }, } testTarPath, err := newTestTar(entries) if err != nil { t.Errorf("unexpected error: %v", err) } defer os.Remove(testTarPath) containerTar, err := os.Open(testTarPath) if err != nil { t.Errorf("unexpected error: %v", err) } defer containerTar.Close() tmpdir, err := ioutil.TempDir("", "rkt-temp-dir") if err != nil { t.Errorf("unexpected error: %v", err) } os.RemoveAll(tmpdir) err = os.MkdirAll(tmpdir, 0755) if err != nil { t.Errorf("unexpected error: %v", err) } defer os.RemoveAll(tmpdir) err = ExtractTar(containerTar, tmpdir, false, uid.NewBlankUidRange(), nil) if err != nil { t.Errorf("unexpected error: %v", err) } matches, err := filepath.Glob(filepath.Join(tmpdir, "deep/folder/*.txt")) if err != nil { t.Errorf("unexpected error: %v", err) } if len(matches) != 2 { t.Errorf("unexpected number of files found: %d, wanted 2", len(matches)) } matches, err = filepath.Glob(filepath.Join(tmpdir, "deep/folder2/*.txt")) if err != nil { t.Errorf("unexpected error: %v", err) } if len(matches) != 2 { t.Errorf("unexpected number of files found: %d, wanted 2", len(matches)) } dirInfo, err := os.Lstat(filepath.Join(tmpdir, "deep/folder")) if err != nil { t.Errorf("unexpected error: %v", err) } else if dirInfo.Mode().Perm() != os.FileMode(0747) { t.Errorf("unexpected dir mode: %s", dirInfo.Mode()) } dirInfo, err = os.Lstat(filepath.Join(tmpdir, "deep/deep")) if err != nil { t.Errorf("unexpected error: %v", err) } else if dirInfo.Mode().Perm() != os.FileMode(0747) { t.Errorf("unexpected dir mode: %s", dirInfo.Mode()) } }
// Write renders the ACI with the provided key in the treestore. id references // that specific tree store rendered image. // Write, to avoid having a rendered ACI with old stale files, requires that // the destination directory doesn't exist (usually Remove should be called // before Write) func (ts *TreeStore) Write(id string, key string, s *Store) (string, error) { treepath := ts.GetPath(id) fi, _ := os.Stat(treepath) if fi != nil { return "", fmt.Errorf("path %s already exists", treepath) } imageID, err := types.NewHash(key) if err != nil { return "", errwrap.Wrap(errors.New("cannot convert key to imageID"), err) } if err := os.MkdirAll(treepath, 0755); err != nil { return "", errwrap.Wrap(fmt.Errorf("cannot create treestore directory %s", treepath), err) } err = aci.RenderACIWithImageID(*imageID, treepath, s, uid.NewBlankUidRange()) if err != nil { return "", errwrap.Wrap(errors.New("cannot render aci"), err) } hash, err := ts.Hash(id) if err != nil { return "", errwrap.Wrap(errors.New("cannot calculate tree hash"), err) } err = ioutil.WriteFile(filepath.Join(treepath, hashfilename), []byte(hash), 0644) if err != nil { return "", errwrap.Wrap(errors.New("cannot write hash file"), err) } // before creating the "rendered" flag file we need to ensure that all data is fsynced dfd, err := syscall.Open(treepath, syscall.O_RDONLY, 0) if err != nil { return "", err } defer syscall.Close(dfd) if err := sys.Syncfs(dfd); err != nil { return "", errwrap.Wrap(errors.New("failed to sync data"), err) } // Create rendered file f, err := os.Create(filepath.Join(treepath, renderedfilename)) if err != nil { return "", errwrap.Wrap(errors.New("failed to write rendered file"), err) } f.Close() // Write the hash of the image that will use this tree store err = ioutil.WriteFile(filepath.Join(treepath, imagefilename), []byte(key), 0644) if err != nil { return "", errwrap.Wrap(errors.New("cannot write image file"), err) } if err := syscall.Fsync(dfd); err != nil { return "", errwrap.Wrap(errors.New("failed to sync tree store directory"), err) } treeSize, err := ts.Size(id) if err != nil { return "", err } if err := s.UpdateTreeStoreSize(key, treeSize); err != nil { return "", err } return string(hash), nil }
func TestExtractTarTimes(t *testing.T) { if !sys.HasChrootCapability() { t.Skipf("chroot capability not available. Disabling test.") } // Do not set ns as tar has second precision time1 := time.Unix(100000, 0) time2 := time.Unix(200000, 0) time3 := time.Unix(300000, 0) entries := []*testTarEntry{ { header: &tar.Header{ Name: "folder/", Typeflag: tar.TypeDir, Mode: int64(0747), ModTime: time1, }, }, { contents: "foo", header: &tar.Header{ Name: "folder/foo.txt", Size: 3, ModTime: time2, }, }, { header: &tar.Header{ Name: "folder/symlink.txt", Typeflag: tar.TypeSymlink, Linkname: "folder/foo.txt", ModTime: time3, }, }, } testTarPath, err := newTestTar(entries) if err != nil { t.Errorf("unexpected error: %v", err) } defer os.Remove(testTarPath) containerTar, err := os.Open(testTarPath) if err != nil { t.Errorf("unexpected error: %v", err) } defer containerTar.Close() tmpdir, err := ioutil.TempDir("", "rkt-temp-dir") if err != nil { t.Errorf("unexpected error: %v", err) } os.RemoveAll(tmpdir) err = os.MkdirAll(tmpdir, 0755) if err != nil { t.Errorf("unexpected error: %v", err) } err = ExtractTar(containerTar, tmpdir, false, uid.NewBlankUidRange(), nil) if err != nil { t.Errorf("unexpected error: %v", err) } err = checkTime(filepath.Join(tmpdir, "folder/"), time1) if err != nil { t.Errorf("unexpected error: %v", err) } err = checkTime(filepath.Join(tmpdir, "folder/foo.txt"), time2) if err != nil { t.Errorf("unexpected error: %v", err) } //Check only (by now) on linux if runtime.GOOS == "linux" { err = checkTime(filepath.Join(tmpdir, "folder/symlink.txt"), time3) if err != nil { t.Errorf("unexpected error: %v", err) } } }
func extractTarHelperPWL(rdr io.Reader, target string, pwl PathWhitelistMap) error { return ExtractTar(rdr, target, false, uid.NewBlankUidRange(), pwl) }
// appToSystemd transforms the provided RuntimeApp+ImageManifest into systemd units func appToSystemd(p *stage1commontypes.Pod, ra *schema.RuntimeApp, interactive bool, flavor string, privateUsers string) error { app := ra.App appName := ra.Name imgName := p.AppNameToImageName(appName) if len(app.Exec) == 0 { return fmt.Errorf(`image %q has an empty "exec" (try --exec=BINARY)`, imgName) } workDir := "/" if app.WorkingDirectory != "" { workDir = app.WorkingDirectory } env := app.Environment env.Set("AC_APP_NAME", appName.String()) if p.MetadataServiceURL != "" { env.Set("AC_METADATA_URL", p.MetadataServiceURL) } if err := writeEnvFile(p, env, appName, privateUsers); err != nil { return errwrap.Wrap(errors.New("unable to write environment file"), err) } var _uid, gid int var err error uidRange := uid.NewBlankUidRange() if err := uidRange.Deserialize([]byte(privateUsers)); err != nil { return errwrap.Wrap(errors.New("unable to deserialize uid range"), err) } if strings.HasPrefix(app.User, "/") { var stat syscall.Stat_t if err = syscall.Lstat(filepath.Join(common.AppRootfsPath(p.Root, appName), app.User), &stat); err != nil { return errwrap.Wrap(fmt.Errorf("unable to get uid from file %q", app.User), err) } uidReal, _, err := uidRange.UnshiftRange(stat.Uid, 0) if err != nil { return errwrap.Wrap(errors.New("unable to determine real uid"), err) } _uid = int(uidReal) } else { _uid, err = strconv.Atoi(app.User) if err != nil { _uid, err = passwd.LookupUidFromFile(app.User, filepath.Join(common.AppRootfsPath(p.Root, appName), "etc/passwd")) if err != nil { return errwrap.Wrap(fmt.Errorf("cannot lookup user %q", app.User), err) } } } if strings.HasPrefix(app.Group, "/") { var stat syscall.Stat_t if err = syscall.Lstat(filepath.Join(common.AppRootfsPath(p.Root, appName), app.Group), &stat); err != nil { return errwrap.Wrap(fmt.Errorf("unable to get gid from file %q", app.Group), err) } _, gidReal, err := uidRange.UnshiftRange(0, stat.Gid) if err != nil { return errwrap.Wrap(errors.New("unable to determine real gid"), err) } gid = int(gidReal) } else { gid, err = strconv.Atoi(app.Group) if err != nil { gid, err = group.LookupGidFromFile(app.Group, filepath.Join(common.AppRootfsPath(p.Root, appName), "etc/group")) if err != nil { return errwrap.Wrap(fmt.Errorf("cannot lookup group %q", app.Group), err) } } } execWrap := []string{"/appexec", common.RelAppRootfsPath(appName), workDir, RelEnvFilePath(appName), strconv.Itoa(_uid), generateGidArg(gid, app.SupplementaryGIDs), "--"} execStart := quoteExec(append(execWrap, app.Exec...)) opts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Application=%v Image=%v", appName, imgName)), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), unit.NewUnitOption("Unit", "Wants", fmt.Sprintf("reaper-%s.service", appName)), unit.NewUnitOption("Service", "Restart", "no"), unit.NewUnitOption("Service", "ExecStart", execStart), unit.NewUnitOption("Service", "User", "0"), unit.NewUnitOption("Service", "Group", "0"), } if interactive { opts = append(opts, unit.NewUnitOption("Service", "StandardInput", "tty")) opts = append(opts, unit.NewUnitOption("Service", "StandardOutput", "tty")) opts = append(opts, unit.NewUnitOption("Service", "StandardError", "tty")) } else { opts = append(opts, unit.NewUnitOption("Service", "StandardOutput", "journal+console")) opts = append(opts, unit.NewUnitOption("Service", "StandardError", "journal+console")) opts = append(opts, unit.NewUnitOption("Service", "SyslogIdentifier", filepath.Base(app.Exec[0]))) } // When an app fails, we shut down the pod opts = append(opts, unit.NewUnitOption("Unit", "OnFailure", "halt.target")) for _, eh := range app.EventHandlers { var typ string switch eh.Name { case "pre-start": typ = "ExecStartPre" case "post-stop": typ = "ExecStopPost" default: return fmt.Errorf("unrecognized eventHandler: %v", eh.Name) } exec := quoteExec(append(execWrap, eh.Exec...)) opts = append(opts, unit.NewUnitOption("Service", typ, exec)) } // Some pre-start jobs take a long time, set the timeout to 0 opts = append(opts, unit.NewUnitOption("Service", "TimeoutStartSec", "0")) var saPorts []types.Port for _, p := range app.Ports { if p.SocketActivated { saPorts = append(saPorts, p) } } for _, i := range app.Isolators { switch v := i.Value().(type) { case *types.ResourceMemory: opts, err = cgroup.MaybeAddIsolator(opts, "memory", v.Limit()) if err != nil { return err } case *types.ResourceCPU: opts, err = cgroup.MaybeAddIsolator(opts, "cpu", v.Limit()) if err != nil { return err } } } if len(saPorts) > 0 { sockopts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Application=%v Image=%v %s", appName, imgName, "socket-activated ports")), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), unit.NewUnitOption("Socket", "BindIPv6Only", "both"), unit.NewUnitOption("Socket", "Service", ServiceUnitName(appName)), } for _, sap := range saPorts { var proto string switch sap.Protocol { case "tcp": proto = "ListenStream" case "udp": proto = "ListenDatagram" default: return fmt.Errorf("unrecognized protocol: %v", sap.Protocol) } // We find the host port for the pod's port and use that in the // socket unit file. // This is so because systemd inside the pod will match based on // the socket port number, and since the socket was created on the // host, it will have the host port number. port := findHostPort(*p.Manifest, sap.Name) if port == 0 { log.Printf("warning: no --port option for socket-activated port %q, assuming port %d as specified in the manifest", sap.Name, sap.Port) port = sap.Port } sockopts = append(sockopts, unit.NewUnitOption("Socket", proto, fmt.Sprintf("%v", port))) } file, err := os.OpenFile(SocketUnitPath(p.Root, appName), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return errwrap.Wrap(errors.New("failed to create socket file"), err) } defer file.Close() if _, err = io.Copy(file, unit.Serialize(sockopts)); err != nil { return errwrap.Wrap(errors.New("failed to write socket unit file"), err) } if err = os.Symlink(path.Join("..", SocketUnitName(appName)), SocketWantPath(p.Root, appName)); err != nil { return errwrap.Wrap(errors.New("failed to link socket want"), err) } opts = append(opts, unit.NewUnitOption("Unit", "Requires", SocketUnitName(appName))) } opts = append(opts, unit.NewUnitOption("Unit", "Requires", InstantiatedPrepareAppUnitName(appName))) opts = append(opts, unit.NewUnitOption("Unit", "After", InstantiatedPrepareAppUnitName(appName))) file, err := os.OpenFile(ServiceUnitPath(p.Root, appName), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return errwrap.Wrap(errors.New("failed to create service unit file"), err) } defer file.Close() if _, err = io.Copy(file, unit.Serialize(opts)); err != nil { return errwrap.Wrap(errors.New("failed to write service unit file"), err) } if err = os.Symlink(path.Join("..", ServiceUnitName(appName)), ServiceWantPath(p.Root, appName)); err != nil { return errwrap.Wrap(errors.New("failed to link service want"), err) } if flavor == "kvm" { // bind mount all shared volumes from /mnt/volumeName (we don't use mechanism for bind-mounting given by nspawn) err := AppToSystemdMountUnits(common.Stage1RootfsPath(p.Root), appName, p.Manifest.Volumes, ra, UnitsDir) if err != nil { return errwrap.Wrap(errors.New("failed to prepare mount units"), err) } } if err = writeAppReaper(p, appName.String()); err != nil { return errwrap.Wrap(fmt.Errorf("failed to write app %q reaper service", appName), err) } return nil }
func runRun(cmd *cobra.Command, args []string) (exit int) { privateUsers := uid.NewBlankUidRange() err := parseApps(&rktApps, args, cmd.Flags(), true) if err != nil { stderr("run: error parsing app image arguments: %v", err) return 1 } if flagPrivateUsers { if !common.SupportsUserNS() { stderr("run: --private-users is not supported, kernel compiled without user namespace support") return 1 } privateUsers.SetRandomUidRange(uid.DefaultRangeCount) } if len(flagPorts) > 0 && !flagPrivateNet.Any() { stderr("--port flag requires --private-net") return 1 } if len(flagPodManifest) > 0 && (len(flagVolumes) > 0 || len(flagPorts) > 0 || flagInheritEnv || !flagExplicitEnv.IsEmpty() || rktApps.Count() > 0 || flagLocal) { stderr("conflicting flags set with --pod-manifest (see --help)") return 1 } if globalFlags.Dir == "" { log.Printf("dir unset - using temporary directory") var err error globalFlags.Dir, err = ioutil.TempDir("", "rkt") if err != nil { stderr("error creating temporary directory: %v", err) return 1 } } if flagInteractive && rktApps.Count() > 1 { stderr("run: interactive option only supports one image") return 1 } if rktApps.Count() < 1 && len(flagPodManifest) == 0 { stderr("run: must provide at least one image or specify the pod manifest") return 1 } s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("run: cannot open store: %v", err) return 1 } config, err := getConfig() if err != nil { stderr("run: cannot get configuration: %v", err) return 1 } fn := &finder{ imageActionData: imageActionData{ s: s, headers: config.AuthPerHost, dockerAuth: config.DockerCredentialsPerRegistry, insecureSkipVerify: globalFlags.InsecureSkipVerify, debug: globalFlags.Debug, }, local: flagLocal, withDeps: false, } s1img, err := getStage1Hash(s, flagStage1Image) if err != nil { stderr("%v", err) return 1 } fn.ks = getKeystore() fn.withDeps = true if err := fn.findImages(&rktApps); err != nil { stderr("%v", err) return 1 } p, err := newPod() if err != nil { stderr("Error creating new pod: %v", err) return 1 } // if requested, write out pod UUID early so "rkt rm" can // clean it up even if something goes wrong if flagUUIDFileSave != "" { if err := writeUUIDToFile(p.uuid, flagUUIDFileSave); err != nil { stderr("Error saving pod UUID to file: %v", err) return 1 } } processLabel, mountLabel, err := label.InitLabels(nil) if err != nil { stderr("Error initialising SELinux: %v", err) return 1 } cfg := stage0.CommonConfig{ MountLabel: mountLabel, ProcessLabel: processLabel, Store: s, Stage1Image: *s1img, UUID: p.uuid, Debug: globalFlags.Debug, } pcfg := stage0.PrepareConfig{ CommonConfig: cfg, UseOverlay: !flagNoOverlay && common.SupportsOverlay(), PrivateUsers: privateUsers, } if len(flagPodManifest) > 0 { pcfg.PodManifest = flagPodManifest } else { pcfg.Volumes = []types.Volume(flagVolumes) pcfg.Ports = []types.ExposedPort(flagPorts) pcfg.InheritEnv = flagInheritEnv pcfg.ExplicitEnv = flagExplicitEnv.Strings() pcfg.Apps = &rktApps } err = stage0.Prepare(pcfg, p.path(), p.uuid) if err != nil { stderr("run: error setting up stage0: %v", err) return 1 } // get the lock fd for run lfd, err := p.Fd() if err != nil { stderr("Error getting pod lock fd: %v", err) return 1 } // skip prepared by jumping directly to run, we own this pod if err := p.xToRun(); err != nil { stderr("run: unable to transition to run: %v", err) return 1 } rcfg := stage0.RunConfig{ CommonConfig: cfg, PrivateNet: flagPrivateNet, LockFd: lfd, Interactive: flagInteractive, MDSRegister: flagMDSRegister, LocalConfig: globalFlags.LocalConfigDir, } apps, err := p.getApps() if err != nil { stderr("run: cannot get the appList in the pod manifest: %v", err) return 1 } rcfg.Apps = apps stage0.Run(rcfg, p.path()) // execs, never returns return 1 }
// parseUserGroup parses the User and Group fields of an App and returns its // UID and GID. // The User and Group fields accept several formats: // 1. the hardcoded string "root" // 2. a path // 3. a number // 4. a name in reference to /etc/{group,passwod} in the image // See https://github.com/appc/spec/blob/master/spec/aci.md#image-manifest-schema func parseUserGroup(p *stage1commontypes.Pod, ra *schema.RuntimeApp, privateUsers string) (int, int, error) { app := ra.App appName := ra.Name var uid_, gid_ int var err error uidRange := uid.NewBlankUidRange() if err := uidRange.Deserialize([]byte(privateUsers)); err != nil { return -1, -1, errwrap.Wrap(errors.New("unable to deserialize uid range"), err) } switch { case app.User == "root": uid_ = 0 case strings.HasPrefix(app.User, "/"): var stat syscall.Stat_t if err = syscall.Lstat(filepath.Join(common.AppRootfsPath(p.Root, appName), app.User), &stat); err != nil { return -1, -1, errwrap.Wrap(fmt.Errorf("unable to get uid from file %q", app.User), err) } uidReal, _, err := uidRange.UnshiftRange(stat.Uid, 0) if err != nil { return -1, -1, errwrap.Wrap(errors.New("unable to determine real uid"), err) } uid_ = int(uidReal) default: uid_, err = strconv.Atoi(app.User) if err != nil { uid_, err = passwd.LookupUidFromFile(app.User, filepath.Join(common.AppRootfsPath(p.Root, appName), "etc/passwd")) if err != nil { return -1, -1, errwrap.Wrap(fmt.Errorf("cannot lookup user %q", app.User), err) } } } switch { case app.Group == "root": gid_ = 0 case strings.HasPrefix(app.Group, "/"): var stat syscall.Stat_t if err = syscall.Lstat(filepath.Join(common.AppRootfsPath(p.Root, appName), app.Group), &stat); err != nil { return -1, -1, errwrap.Wrap(fmt.Errorf("unable to get gid from file %q", app.Group), err) } _, gidReal, err := uidRange.UnshiftRange(0, stat.Gid) if err != nil { return -1, -1, errwrap.Wrap(errors.New("unable to determine real gid"), err) } gid_ = int(gidReal) default: gid_, err = strconv.Atoi(app.Group) if err != nil { gid_, err = group.LookupGidFromFile(app.Group, filepath.Join(common.AppRootfsPath(p.Root, appName), "etc/group")) if err != nil { return -1, -1, errwrap.Wrap(fmt.Errorf("cannot lookup group %q", app.Group), err) } } } return uid_, gid_, nil }