Пример #1
0
func (e idsFromEtc) IDs() (uid int, gid int, err error) {
	uid, gid = -1, -1

	uid, err = passwd.LookupUidFromFile(
		e.username,
		filepath.Join(e.rootPath, "etc/passwd"),
	)

	if e.username != "" && err != nil {
		return
	}

	gid, err = group.LookupGidFromFile(
		e.group,
		filepath.Join(e.rootPath, "etc/group"),
	)

	if e.group != "" && err != nil {
		return
	}

	return uid, gid, nil
}
Пример #2
0
// 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
}
Пример #3
0
// 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
}