Example #1
0
func umountCmd(args ...string) {
	cmd := exec.Command("/sbin/umount", args...)
	gologit.Debugln(append([]string{"/sbin/umount"}, args...))
	out, err := cmd.CombinedOutput()
	for _, line := range strings.Split(string(out), "\n") {
		if line != "" {
			gologit.Debugln(line)
		}
	}
	if err != nil {
		// some mounts are not present, so just fail
		// do not log exit status 1 unless debug logging
		gologit.Debugf("%s\n", err)
	}
}
Example #2
0
File: url.go Project: r38y/go-camo
// B64DecodeURL ensures the url is properly verified via HMAC, and then
// unencodes the url, returning the url (if valid) and whether the
// HMAC was verified.
func B64DecodeURL(hmackey []byte, encdig string, encURL string) (string, bool) {
	urlBytes, err := b64decode(encURL)
	if err != nil {
		gologit.Debugln("Bad B64 Decode of URL", encURL)
		return "", false
	}
	macBytes, err := b64decode(encdig)
	if err != nil {
		gologit.Debugln("Bad B64 Decode of MAC", encURL)
		return "", false
	}

	if ok := validateURL(&hmackey, &macBytes, &urlBytes); !ok {
		return "", false
	}
	return string(urlBytes), true
}
Example #3
0
File: url.go Project: r38y/go-camo
// HexDecodeURL ensures the url is properly verified via HMAC, and then
// unencodes the url, returning the url (if valid) and whether the
// HMAC was verified.
func HexDecodeURL(hmackey []byte, hexdig string, hexURL string) (string, bool) {
	urlBytes, err := hex.DecodeString(hexURL)
	if err != nil {
		gologit.Debugln("Bad Hex Decode of URL", hexURL)
		return "", false
	}
	macBytes, err := hex.DecodeString(hexdig)
	if err != nil {
		gologit.Debugln("Bad Hex Decode of MAC", hexURL)
		return "", false
	}

	if ok := validateURL(&hmackey, &macBytes, &urlBytes); !ok {
		return "", false
	}
	return string(urlBytes), true
}
Example #4
0
func stopCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to stop\n")
	}

	jail, err := core.FindJail(args[0])
	if err != nil {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	if !jail.IsRunning() {
		gologit.Fatalf("Jail is not running!\n")
	}

	// create file
	f, err := os.OpenFile(jail.GetLogPath(), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		gologit.Fatal(err)
	}
	defer f.Close()

	fmt.Printf("* Stopping %s (%s)\n", jail.HostUUID, jail.Tag)
	fmt.Printf("  + Removing jail process\n")

	file, err := ioutil.TempFile(os.TempDir(), "rollcage.")
	defer os.Remove(file.Name())

	jailConfig := jail.JailConfig()
	gologit.Debugln(jailConfig)
	file.WriteString(jailConfig)
	file.Close()

	excmd := exec.Command(
		"/usr/sbin/jail",
		"-f", file.Name(),
		"-r", fmt.Sprintf("ioc-%s", jail.HostUUID))
	excmd.Stdout = f
	excmd.Stderr = f
	err = excmd.Run()
	if err != nil {
		gologit.Fatal(err)
	}

	// mostly for safety...
	fmt.Printf("  + Tearing down mounts\n")
	umountCmd("-afvF", path.Join(jail.Mountpoint, "fstab"))
	umountCmd(path.Join(jail.Mountpoint, "root/dev/fd"))
	umountCmd(path.Join(jail.Mountpoint, "root/dev"))
	umountCmd(path.Join(jail.Mountpoint, "root/proc"))

	// TODO: basejail here?
	// TODO: rctl stuff here...
}
Example #5
0
File: url.go Project: r38y/go-camo
// DecodeURL ensures the url is properly verified via HMAC, and then
// unencodes the url, returning the url (if valid) and whether the
// HMAC was verified. Tries to HexDecode the url, then B64Decode if that fails.
func DecodeURL(hmackey []byte, encdig string, encURL string) (string, bool) {
	var decoder func([]byte, string, string) (string, bool)
	if len(encdig) == 40 {
		decoder = HexDecodeURL
	} else {
		decoder = B64DecodeURL
	}

	urlBytes, ok := decoder(hmackey, encdig, encURL)
	if !ok {
		gologit.Debugln("Bad Decode of URL", encURL)
		return "", false
	}
	return string(urlBytes), true
}
Example #6
0
func stopCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to stop\n")
	}

	jail, err := core.FindJail(args[0])
	if err != nil {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	if !jail.IsRunning() {
		gologit.Fatalf("Jail is not running!\n")
	}

	propertyList := []string{
		"mountpoint",
		"org.freebsd.iocage:type",
		"org.freebsd.iocage:tag",
		"org.freebsd.iocage:prestop",
		"org.freebsd.iocage:exec_stop",
		"org.freebsd.iocage:poststop",
		"org.freebsd.iocage:vnet",
		"org.freebsd.iocage:ip4",
	}

	lines := core.SplitOutput(core.ZFSMust(
		fmt.Errorf("Error listing properties"),
		"list", "-H", "-o", strings.Join(propertyList, ","), jail.Path))
	if len(lines) < 1 {
		gologit.Fatalf("No output from property fetch\n")
	}

	prop_mountpoint := removeDash(lines[0][0])
	//prop_type := removeDash(lines[0][1])
	prop_tag := removeDash(lines[0][2])
	prop_prestop := removeDash(lines[0][3])
	prop_exec_stop := removeDash(lines[0][4])
	prop_poststop := removeDash(lines[0][5])
	prop_vnet := removeDash(lines[0][6])
	prop_ip4 := removeDash(lines[0][7])

	// set a default path
	environ := []string{
		"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
	}

	fmt.Printf("* Stopping %s (%s)\n", jail.HostUUID, prop_tag)
	if prop_prestop != "" {
		fmt.Printf("  + Running pre-stop\n")
		preStop := core.SplitFieldsQuoteSafe(prop_prestop)
		excmd := exec.Command(preStop[0], preStop[1:]...)
		excmd.Env = environ
		err := excmd.Run()
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	fmt.Printf("  + Stopping services\n")
	jexec := []string{"/usr/sbin/jexec"}
	jexec = append(jexec, fmt.Sprintf("ioc-%s", jail.HostUUID))
	jexec = append(jexec, core.SplitFieldsQuoteSafe(prop_exec_stop)...)
	out, err := exec.Command(jexec[0], jexec[1:]...).CombinedOutput()
	gologit.Debugln(string(out))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	if prop_vnet == "on" {
		fmt.Printf("  + Tearing down VNET\n")
		// stop VNET networking
	} else if prop_ip4 != "inherit" {
		// stop standard networking (legacy?)
		lines := core.SplitOutput(core.ZFSMust(
			fmt.Errorf("Error listing jails"),
			"list", "-H", "-o", "org.freebsd.iocage:ip4_addr,org.freebsd.iocage:ip6_addr", jail.Path))
		for _, ip_config := range lines[0] {
			if ip_config == "none" {
				continue
			}
			for _, addr := range strings.Split(ip_config, ",") {
				item := strings.Split(addr, "|")
				gologit.Debugln("/sbin/ifconfig", item[0], item[1], "-alias")
				out, err := exec.Command("/sbin/ifconfig",
					item[0], item[1], "-alias").CombinedOutput()
				gologit.Debugln(string(out))
				if err != nil {
					gologit.Printf("%s\n", err)
				}
			}
		}
	}

	fmt.Printf("  + Removing jail process\n")
	jrexec := []string{"/usr/sbin/jail", "-r", fmt.Sprintf("ioc-%s", jail.HostUUID)}
	out, err = exec.Command(jrexec[0], jrexec[1:]...).CombinedOutput()
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	if prop_poststop != "" {
		fmt.Printf("  + Running post-stop\n")
		postStop := core.SplitFieldsQuoteSafe(prop_poststop)
		excmd := exec.Command(postStop[0], postStop[1:]...)
		excmd.Env = environ
		err := excmd.Run()
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	fmt.Printf("  + Tearing down mounts\n")
	umountCmd("-afvF", path.Join(prop_mountpoint, "fstab"))
	umountCmd(path.Join(prop_mountpoint, "root/dev/fd"))
	umountCmd(path.Join(prop_mountpoint, "root/dev"))
	umountCmd(path.Join(prop_mountpoint, "root/proc"))

	// TODO: basejail here?
	// TODO: rctl stuff here...
}
Example #7
0
func startCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to stop\n")
	}

	jail, err := core.FindJail(args[0])
	if err != nil {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	if jail.IsRunning() {
		gologit.Fatalf("Jail is already running!\n")
	}

	props := jail.GetProperties()

	// set a default path
	environ := []string{
		"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
	}

	fmt.Printf("* Starting %s (%s)\n", jail.HostUUID, jail.Tag)
	// mount procfs
	if props.GetIOC("mount_procfs") == "1" {
		fmt.Printf("  + mounting procfs\n")
		procpath := path.Join(jail.Mountpoint, "root/proc")
		excmd := exec.Command("/sbin/mount", "-t", "procfs", "proc", procpath)
		excmd.Env = environ
		err := excmd.Run()
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	// prepare jail zfs dataset if enabled
	if props.GetIOC("jail_zfs") == "on" {
		fmt.Printf("  + jailing zfs dataset\n")
		setprops := core.ZFSProperties{
			"org.freebsd.iocage:allow_mount":     "1",
			"org.freebsd.iocage:allow_mount_zfs": "1",
			"org.freebsd.iocage:enforce_statfs":  "1",
		}
		jail.SetProperties(setprops)
		core.ZFSMust(
			fmt.Errorf("Error setting property"),
			"set", "jailed=on",
			path.Join(core.GetZFSRootPath(), props.GetIOC("jail_zfs_dataset")))
	}

	// copy resolv conf
	err = core.CopyFile(
		"/etc/resolv.conf",
		path.Join(jail.Mountpoint, "root/etc/resolv.conf"))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	// create log file
	logfile, err := os.OpenFile(jail.GetLogPath(), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		gologit.Fatal(err)
	}
	defer logfile.Close()

	file, err := ioutil.TempFile(os.TempDir(), "rollcage.")
	defer os.Remove(file.Name())

	jailConfig := jail.JailConfig()
	gologit.Debugln(jailConfig)
	file.WriteString(jailConfig)
	file.Close()

	excmd := exec.Command(
		"/usr/sbin/jail",
		"-f", file.Name(),
		"-c", fmt.Sprintf("ioc-%s", jail.HostUUID))
	excmd.Stdout = logfile
	excmd.Stderr = logfile
	err = excmd.Run()
	if err != nil {
		gologit.Fatal(err)
	}

	// rctl_limits?
	// cpuset?

	// jail zfs
	if props.GetIOC("jail_zfs") == "on" {
		core.ZFSMust(
			fmt.Errorf("Error setting property"),
			"jail", fmt.Sprintf("ioc-%s", jail.HostUUID),
			path.Join(core.GetZFSRootPath(), props.GetIOC("jail_zfs_dataset")))
		out, err := exec.Command(
			"/usr/sbin/jexec",
			fmt.Sprintf("ioc-%s", jail.HostUUID),
			"zfs", "mount", "-a").CombinedOutput()
		gologit.Debugln(string(out))
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	// set last_started property
	t := time.Now()
	core.ZFSMust(
		fmt.Errorf("Error setting property"), "set",
		fmt.Sprintf(
			"org.freebsd.iocage:last_started=%s",
			t.Format("2006-01-02_15:04:05")),
		jail.Path)
}
Example #8
0
// ServerHTTP handles the client request, validates the request is validly
// HMAC signed, filters based on the Allow list, and then proxies
// valid requests to the desired endpoint. Responses are filtered for
// proper image content types.
func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	gologit.Debugln("Request:", req.URL)
	if p.metrics != nil {
		go p.metrics.AddServed()
	}

	if p.config.DisableKeepAlivesFE {
		w.Header().Set("Connection", "close")
	}

	if req.Header.Get("Via") == p.config.ServerName {
		http.Error(w, "Request loop failure", http.StatusNotFound)
		return
	}

	// split path and get components
	components := strings.Split(req.URL.Path, "/")
	if len(components) < 3 {
		http.Error(w, "Malformed request path", http.StatusNotFound)
		return
	}
	sigHash, encodedURL := components[1], components[2]

	sURL, ok := encoding.DecodeURL(p.config.HMACKey, sigHash, encodedURL)
	if !ok {
		http.Error(w, "Bad Signature", http.StatusForbidden)
		return
	}
	gologit.Debugln("URL:", sURL)
	gologit.Debugln("Client request:", req)

	u, err := url.Parse(sURL)
	if err != nil {
		gologit.Debugln("url parse error:", err)
		http.Error(w, "Bad url", http.StatusBadRequest)
		return
	}

	u.Host = strings.ToLower(u.Host)
	if u.Host == "" || localhostRegex.MatchString(u.Host) {
		http.Error(w, "Bad url host", http.StatusNotFound)
		return
	}

	// if allowList is set, require match
	matchFound := true
	if len(p.allowList) > 0 {
		matchFound = false
		for _, rgx := range p.allowList {
			if rgx.MatchString(u.Host) {
				matchFound = true
			}
		}
	}
	if !matchFound {
		http.Error(w, "Allowlist host failure", http.StatusNotFound)
		return
	}

	// filter out rfc1918 hosts
	ip := net.ParseIP(u.Host)
	if ip != nil {
		if addr1918PrefixRegex.MatchString(ip.String()) {
			http.Error(w, "Denylist host failure", http.StatusNotFound)
			return
		}
	}

	nreq, err := http.NewRequest(req.Method, sURL, nil)
	if err != nil {
		gologit.Debugln("Could not create NewRequest", err)
		http.Error(w, "Error Fetching Resource", http.StatusBadGateway)
		return
	}

	// filter headers
	p.copyHeader(&nreq.Header, &req.Header, &ValidReqHeaders)
	if req.Header.Get("X-Forwarded-For") == "" {
		host, _, err := net.SplitHostPort(req.RemoteAddr)
		if err == nil && !addr1918PrefixRegex.MatchString(host) {
			nreq.Header.Add("X-Forwarded-For", host)
		}
	}

	// add an accept header if the client didn't send one
	if nreq.Header.Get("Accept") == "" {
		nreq.Header.Add("Accept", "image/*")
	}

	nreq.Header.Add("User-Agent", p.config.ServerName)
	nreq.Header.Add("Via", p.config.ServerName)

	gologit.Debugln("Built outgoing request:", nreq)

	resp, err := p.client.Do(nreq)
	if err != nil {
		gologit.Debugln("Could not connect to endpoint", err)
		// this is a bit janky, but better than peeling off the
		// 3 layers of wrapped errors and trying to get to net.OpErr and
		// still having to rely on string comparison to find out if it is
		// a net.errClosing or not.
		errString := err.Error()
		if strings.Contains(errString, "timeout") {
			http.Error(w, "Error Fetching Resource", http.StatusGatewayTimeout)
		} else if strings.Contains(errString, "use of closed") {
			http.Error(w, "Error Fetching Resource", http.StatusBadGateway)
		} else {
			// some other error. call it a not found (camo compliant)
			http.Error(w, "Error Fetching Resource", http.StatusNotFound)
		}
		return
	}
	defer resp.Body.Close()
	gologit.Debugln("Response from upstream:", resp)

	// check for too large a response
	if resp.ContentLength > p.config.MaxSize {
		gologit.Debugln("Content length exceeded", sURL)
		http.Error(w, "Content length exceeded", http.StatusNotFound)
		return
	}

	switch resp.StatusCode {
	case 200:
		// check content type
		if !strings.HasPrefix(resp.Header.Get("Content-Type"), "image/") {
			gologit.Debugln("Non-Image content-type returned", u)
			http.Error(w, "Non-Image content-type returned",
				http.StatusBadRequest)
			return
		}
	case 300:
		gologit.Debugln("Multiple choices not supported")
		http.Error(w, "Multiple choices not supported", http.StatusNotFound)
		return
	case 301, 302, 303, 307:
		// if we get a redirect here, we either disabled following,
		// or followed until max depth and still got one (redirect loop)
		http.Error(w, "Not Found", http.StatusNotFound)
		return
	case 304:
		h := w.Header()
		p.copyHeader(&h, &resp.Header, &ValidRespHeaders)
		w.WriteHeader(304)
		return
	case 404:
		http.Error(w, "Not Found", http.StatusNotFound)
		return
	case 500, 502, 503, 504:
		// upstream errors should probably just 502. client can try later.
		http.Error(w, "Error Fetching Resource", http.StatusBadGateway)
		return
	default:
		http.Error(w, "Not Found", http.StatusNotFound)
		return
	}

	h := w.Header()
	p.copyHeader(&h, &resp.Header, &ValidRespHeaders)
	w.WriteHeader(resp.StatusCode)

	// since this uses io.Copy from the respBody, it is streaming
	// from the request to the response. This means it will nearly
	// always end up with a chunked response.
	bW, err := io.Copy(w, resp.Body)
	if err != nil {
		if opErr, ok := err.(*net.OpError); ok {
			switch opErr.Err {
			case syscall.EPIPE, syscall.ECONNRESET:
				// broken pipe - endpoint terminated the conn
				// connection reset by peer - endpoint terminated the conn
				// log as debug only.
				gologit.Debugln("OpError writing response:", err)
			default:
				// log anything else normally
				gologit.Println("OpError writing response:", err)
			}
		} else {
			// unknown error and not an OpError.
			gologit.Println("Error writing response:", err)
		}
		return
	}

	if p.metrics != nil {
		go p.metrics.AddBytes(bW)
	}
	gologit.Debugln("Response to client:", w)
}
Example #9
0
func updateCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to snapremove\n")
	}

	jail, err := core.FindJail(args[0])
	if err != nil {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	zfsArgs := []string{
		"get", "-Ho", "value", "org.freebsd.iocage:release,mountpoint",
		jail.Path}
	out := strings.Split(
		core.ZFSMust(fmt.Errorf("Error getting properties"), zfsArgs...), "\n")
	release := out[0]
	mountpoint := out[1]

	resolvconf := path.Join(mountpoint, "root/etc/resolv.conf")
	if _, err := os.Stat(resolvconf); os.IsNotExist(err) {
		data, err := ioutil.ReadFile("/etc/resolv.conf")
		if err != nil {
			gologit.Fatalln("/etc/resolv.conf not present or not readable")
		}

		err = ioutil.WriteFile(resolvconf, data, 0755)
		if err != nil {
			gologit.Fatalf("Could not copy contents to '%s'\n", resolvconf)
		}
	}

	fmt.Println("* Creating back out snapshot")
	snappath := fmt.Sprintf(
		"%s/root@%s",
		jail.Path,
		fmt.Sprintf(
			"ioc-update-%s",
			time.Now().Format("2006-01-02_15:04:05")))
	core.ZFSMust(
		fmt.Errorf("Error taking snapshot"),
		"snapshot", snappath)

	devroot := path.Join(mountpoint, "root/dev")
	ecmd := exec.Command("/sbin/mount", "-t", "devfs", "devfs", devroot)
	gologit.Debugln(ecmd.Args)
	eout, err := ecmd.CombinedOutput()
	if err != nil {
		gologit.Fatalf("Error mounting devfs: %s\n", err)
	}
	gologit.Debugln(string(eout))

	defer func() {
		ecmd := exec.Command("/sbin/umount", devroot)
		gologit.Debugln(ecmd.Args)
		err := ecmd.Run()
		if err != nil {
			gologit.Fatalf("Error unmounting devfs: %s\n", err)
		}
	}()

	fmt.Println("* Updating jail...")
	root := path.Join(mountpoint, "root")
	ecmd = exec.Command("/usr/sbin/chroot", root,
		"/usr/sbin/freebsd-update", "--not-running-from-cron",
		"fetch", "install")
	ecmd.Env = []string{
		"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
		fmt.Sprintf("UNAME_r=%s", release),
		"PAGER=/bin/cat",
	}
	gologit.Debugln(ecmd.Args)
	ecmd.Stdout = os.Stdout
	ecmd.Stderr = os.Stderr
	ecmd.Run()

	fmt.Println("* update finished")
	fmt.Println("  Once verified, don't forget to remove the snapshot!")
}
Example #10
0
func releaseUpdateCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to update\n")
	}

	release, err := core.FindRelease(args[0])
	if err != nil {
		gologit.Fatalf("Release '%s' not found!\n", args[0])
	}

	mountpoint := release.Mountpoint
	resolvconf := path.Join(mountpoint, "root/etc/resolv.conf")
	if _, err := os.Stat(resolvconf); os.IsNotExist(err) {
		data, err := ioutil.ReadFile("/etc/resolv.conf")
		if err != nil {
			gologit.Fatalln("/etc/resolv.conf not present or not readable")
		}

		err = ioutil.WriteFile(resolvconf, data, 0755)
		if err != nil {
			gologit.Fatalf("Could not copy contents to '%s'\n", resolvconf)
		}
	}

	devroot := path.Join(mountpoint, "root/dev")
	ecmd := exec.Command("/sbin/mount", "-t", "devfs", "devfs", devroot)
	gologit.Debugln(ecmd.Args)
	eout, err := ecmd.CombinedOutput()
	if err != nil {
		gologit.Fatalf("Error mounting devfs: %s\n", err)
	}
	gologit.Debugln(string(eout))

	defer func() {
		ecmd := exec.Command("/sbin/umount", devroot)
		gologit.Debugln(ecmd.Args)
		err := ecmd.Run()
		if err != nil {
			gologit.Fatalf("Error unmounting devfs: %s\n", err)
		}
	}()

	fmt.Println("* Updating release...")
	root := path.Join(mountpoint, "root")

	exargs := []string{root, "/usr/sbin/freebsd-update"}
	if release.Name != "9.3-RELEASE" && release.Name != "10.1-RELEASE" {
		exargs = append(exargs, "--not-running-from-cron")
	}
	exargs = append(exargs, "fetch", "install")
	ecmd = exec.Command("/usr/sbin/chroot", exargs...)
	unamer := release.Name
	if release.Patchlevel != "" {
		unamer = release.Patchlevel
	}
	ecmd.Env = []string{
		"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
		fmt.Sprintf("UNAME_r=%s", unamer),
		"PAGER=/bin/cat",
	}
	gologit.Debugln(ecmd.Args)
	ecmd.Stdout = os.Stdout
	ecmd.Stderr = os.Stderr
	ecmd.Stdin = os.Stdin
	ecmd.Run()
	fmt.Println("* update finished")
}