func SubmitAndInstall(keys *tao.Keys, csr *CSR) { verbose.Printf("Obtaining certificate from CA (may take a while)\n") resp, err := Submit(keys, csr) options.FailIf(err, "can't obtain X509 certificate from CA") if len(resp) == 0 { options.Fail(nil, "no x509 certificates returned from CA") } // Add the certs to our keys... keys.Cert["default"] = resp[0] for i, c := range resp { name := "ca" if i > 0 { name = fmt.Sprintf("ca-%d", i) } keys.Cert[name] = c } if keys.X509Path("default") != "" { err = keys.SaveCerts() } options.FailIf(err, "can't save X509 certificates") chain := keys.CertChain("default") verbose.Printf("Obtained certfificate chain of length %d:\n", len(chain)) for i, cert := range chain { verbose.Printf(" Cert[%d] Subject: %s\n", i, x509txt.RDNString(cert.Subject)) } if Warn { fmt.Println("Note: You may need to install root CA's key into the browser.") } }
func handle(sig os.Signal, pid int) todo { switch sig { case syscall.SIGTSTP: send(pid, syscall.SIGTSTP) verbose.Printf("[stopping]\n") syscall.Kill(syscall.Getpid(), syscall.SIGSTOP) time.Sleep(moment) verbose.Printf("[resuming]\n") send(pid, syscall.SIGCONT) return resumed case syscall.SIGHUP: // tty hangup (e.g. via disown) verbose.Set(false) os.Stdin.Close() os.Stdout.Close() os.Stderr.Close() send(pid, sig.(syscall.Signal)) // Our tty is gone, so there is little left to do. We could hang // around proxying signals (e.g. those sent via kill). But those // could be just as easily sent directly to the hosted program, // so let's not bother. return done default: send(pid, sig.(syscall.Signal)) } return cont }
func startHost(domain *tao.Domain) { if *options.Bool["daemon"] && *options.Bool["foreground"] { options.Usage("Can supply only one of -daemon and -foreground") } if *options.Bool["daemon"] { daemonize() } cfg := configureFromFile() configureFromOptions(cfg) host, err := loadHost(domain, cfg) options.FailIf(err, "Can't create host") sockPath := path.Join(hostPath(), "admin_socket") // Set the socketPath directory go+rx so tao_launch can access sockPath and // connect to this linux host, even when tao_launch is run as non-root. err = os.Chmod(path.Dir(sockPath), 0755) options.FailIf(err, "Can't change permissions") uaddr, err := net.ResolveUnixAddr("unix", sockPath) options.FailIf(err, "Can't resolve unix socket") sock, err := net.ListenUnix("unix", uaddr) options.FailIf(err, "Can't create admin socket") defer sock.Close() err = os.Chmod(sockPath, 0666) if err != nil { sock.Close() options.Fail(err, "Can't change permissions on admin socket") } go func() { verbose.Printf("Linux Tao Service (%s) started and waiting for requests\n", host.HostName()) err = tao.NewLinuxHostAdminServer(host).Serve(sock) verbose.Printf("Linux Tao Service finished\n") sock.Close() options.FailIf(err, "Error serving admin requests") os.Exit(0) }() c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM) <-c verbose.Printf("Linux Tao Service shutting down\n") err = shutdown() if err != nil { sock.Close() options.Fail(err, "Can't shut down admin socket") } // The above goroutine will normally end by calling os.Exit(), so we // can block here indefinitely. But if we get a second kill signal, // let's abort. verbose.Printf("Waiting for shutdown....\n") <-c options.Fail(nil, "Could not shut down linux_host") }
// LoadKeys loads and https key and cert from a directory. This is meant to be // called from user-facing apps. func LoadKeys(kdir string) *tao.Keys { // TODO(kwalsh) merge x509 load/save code into keys.go keys, err := tao.LoadOnDiskTaoSealedKeys(tao.Signing, tao.Parent(), kdir, tao.SealPolicyDefault) options.FailIf(err, "can't load tao-sealed HTTPS/TLS keys") chain := keys.CertChain("default") verbose.Printf("Using existing certfificate chain of length %d:\n", len(chain)) for i, cert := range chain { verbose.Printf(" Cert[%d] Subject: %s\n", i, x509txt.RDNString(cert.Subject)) } return keys }
func doError(ms util.MessageStream, err error, status rendezvous.ResponseStatus, detail string) { if err != nil { fmt.Printf("error handling request: %s\n", err) } verbose.Printf("sending error response: status=%s detail=%q\n", status, detail) resp := &rendezvous.Response{ Status: &status, ErrorDetail: proto.String(detail), } sendResponse(ms, resp) }
func doResponse(conn *tao.Conn) { defer conn.Close() if conn.Peer() == nil { verbose.Printf("netlog: connection from anonymous\n") } else { verbose.Printf("netlog: connection from peer %s\n", *conn.Peer()) } for { req, err := conn.ReadString() if err == io.EOF { fmt.Fprintf(os.Stderr, "netlog: connection closed\n") break } if err != nil { fmt.Fprintf(os.Stderr, "netlog: can't read: %s\n", err) break } verbose.Printf("netlog: got %s request\n", req) if req == "POST" { if conn.Peer() == nil { conn.WriteString("DENIED") break } verbose.Printf("netlog: peer is %s\n", *conn.Peer()) msg, err := conn.ReadString() if err != nil { conn.WriteString("BAD") break } e := &netlog.LogEntry{Prin: *conn.Peer(), Msg: msg} lock.Lock() log = append(log, e) lock.Unlock() conn.WriteString("OK") } else if req == "GET" { lock.RLock() t := log lock.RUnlock() conn.WriteString("OK") conn.WriteInt(len(t)) for _, e := range t { conn.WriteString(e.Prin.String()) conn.WriteString(e.Msg) } } else { conn.WriteString("BAD") break } } if conn.Peer() == nil { verbose.Printf("netlog: connection closed from anonymous\n") } else { verbose.Printf("netlog: connection closed from peer %s\n", *conn.Peer()) } }
func retractExecute(path, host string, domain *tao.Domain) { prin := makeHostPrin(host) subprin, err := makeProgramSubPrin(path) if err == nil { prog := prin.MakeSubprincipal(subprin) verbose.Printf("Retracting program authorization to execute:\n"+ " path: %s\n"+ " host: %s\n"+ " name: %s\n", path, prin, subprin) err := domain.Guard.Retract(prog, "Execute", nil) options.FailIf(err, "Can't retract program authorization from domain") } }
func doResponses(conn *tao.Conn) { defer conn.Close() var peer *string if conn.Peer() != nil { peer = proto.String(conn.Peer().String()) verbose.Printf("Processing connection requests for peer %s\n", *peer) netlog.Log("rendezvous: connection from peer %s", *peer) } else { verbose.Printf("Processing connection requests for anonymous peer\n") netlog.Log("rendezvous: connection from anonymous") } for { var req rendezvous.Request if err := conn.ReadMessage(&req); err != nil { if err != io.EOF { doError(conn, err, rendezvous.ResponseStatus_RENDEZVOUS_BAD_REQUEST, "failed to read request") } break } doResponse(&req, conn, peer) } lock.Lock() for k, v := range bindings { if v.expiration.IsZero() && v.conn == conn { delete(bindings, k) verbose.Printf("Expired binding upon close: %s\n", k) } } lock.Unlock() verbose.Println("Done processing connection requests") if peer == nil { netlog.Log("rendezvous: connection closed from anonymous") } else { netlog.Log("rendezvous: connection closed from peer %s", *peer) } }
func expire(now time.Time) { for k, v := range bindings { v.Age = proto.Uint64(uint64(now.Sub(v.added))) if !v.expiration.IsZero() { ttl := int64(v.expiration.Sub(now)) if ttl <= 0 { delete(bindings, k) verbose.Printf("Expired binding: %s\n", k) } else { v.Ttl = proto.Uint64(uint64(ttl)) } } } }
func daemonize() { // For our purposes, "daemon" means being a session leader. sid, _, errno := syscall.Syscall(syscall.SYS_GETSID, 0, 0, 0) var err error if errno != 0 { err = errno } options.FailIf(err, "Can't get process SID") if int(sid) != syscall.Getpid() { // Go does not support daemonize(), and we can't simply call setsid // because PID may be equal to GID. Using exec.Cmd with the Setsid=true // will fork, ensuring that PID differs from GID, then call setsid, then // exec ourself again in the new session. path, err := os.Readlink("/proc/self/exe") options.FailIf(err, "Can't get path to self executable") // special case: keep stderr if -logtostderr or -alsologtostderr stderr := os.Stderr if !isBoolFlagSet("logtostderr") && !isBoolFlagSet("alsologtostderr") { stderr = nil } spa := &syscall.SysProcAttr{ Setsid: true, // Create session. } daemon := exec.Cmd{ Path: path, Args: os.Args, Stderr: stderr, SysProcAttr: spa, } err = daemon.Start() options.FailIf(err, "Can't become daemon") verbose.Printf("Linux Tao Host running as daemon\n") os.Exit(0) } else { verbose.Printf("Already a session leader?\n") } }
func addExecute(path, host string, domain *tao.Domain) { prin := makeHostPrin(host) subprin, err := makeProgramSubPrin(path) if err == nil { prog := prin.MakeSubprincipal(subprin) verbose.Printf("Authorizing program to execute:\n"+ " path: %s\n"+ " host: %s\n"+ " name: %s\n", path, prin, subprin) err := domain.Guard.Authorize(prog, "Execute", nil) options.FailIf(err, "Can't authorize program in domain") err = domain.Save() options.FailIf(err, "Can't save domain") } }
func isCtty(fd int) bool { // One would hope there was a simple way to figure out what our controlling // tty is. Or at least check if stdin is coming from our controlling tty (as // opposed to just any old terminal). Alas, the infuriating morass that // passes for job control provides no such ability. Of no help: // stat(/dev/tty), readlink(/dev/fd/0), open(/dev/stdin), open(/dev/tty), // cat(/proc/self/fdinfo/0), stat(/proc/self/fd/0), ioctl, tty_ioctl, TIOC*, // anything to do with sid, pgrp, pgid, /dev/console, the tty command, $TTY, // $SSH_TTY. Since I am on a mission, I delved into the source for /bin/ps // to discover /proc/self/stat contains the major/minor numbers for the // controlling tty. And stat(stdin).Rdev provides the same info. If they // differ, I'm going to conclude -- oh so tentatively -- that stdin is NOT // our controlling tty. If they match, or anything goes wrong, we will // assume that stdin, if it is a terminal, is our ctty. if !terminal.IsTerminal(fd) { return false } var s syscall.Stat_t err := syscall.Fstat(fd, &s) if err != nil { verbose.Printf("[warning: fstat(%d) failed: %v]\n", fd, err) return true } name := "/proc/self/stat" f, err := os.Open(name) if err != nil { verbose.Printf("[warning: open(%q) failed: %v]\n", name, err) return true } b, err := ioutil.ReadAll(f) if err != nil { verbose.Printf("[warning: read(%q) failed: %v]\n", name, err) return true } a := strings.Split(string(b), " ") tty_nr := 6 if len(a) <= tty_nr { verbose.Printf("[warning: read(%q) borked: only %d fields]\n", name, len(a)) return true } ctty, err := strconv.Atoi(a[tty_nr]) if err != nil { verbose.Printf("[warning: read(%q) borked: tty_nr = %v]\n", name, a[tty_nr]) return true } if uint64(ctty) != s.Rdev { verbose.Printf("[warning: stdin is a tty, but not ctty]\n") return false } return true }
func runHosted(client *tao.LinuxHostAdminClient, args []string) { var err error if len(args) == 0 { options.Usage("Missing program path and arguments") } spec := new(tao.HostedProgramSpec) spec.ContainerType = "process" spec.Path = args[0] for _, prefix := range []string{"process", "docker", "kvm_coreos", "kvm_coreos_linuxhost"} { if strings.HasPrefix(spec.Path, prefix+":") { spec.ContainerType = prefix spec.Path = strings.TrimPrefix(spec.Path, prefix+":") } } switch spec.ContainerType { case "process": dirs := util.LiberalSearchPath() binary := util.FindExecutable(args[0], dirs) if binary == "" { options.Fail(nil, "Can't find `%s` on path '%s'", args[0], strings.Join(dirs, ":")) } spec.ContainerArgs = []string{spec.Path} spec.Args = args[1:] spec.Path = binary case "kvm_coreos": spec.ContainerArgs, spec.Args = split(args[1:], "--") // TODO(kwalsh) Put memory, other qemu args into container args. case "docker", "kvm_coreos_linuxhost": // args contains [ "docker:argv0", docker_args..., "--", prog_args... ] spec.ContainerArgs, spec.Args = split(args, "--") // Replace docker arg 0 with valid image name constructed from // base(argv[0]). r, _ := regexp.Compile("[^a-zA-Z0-9_.]+") spec.ContainerArgs[0] = r.ReplaceAllLiteralString(path.Base(spec.Path), "_") } spec.Dir, err = os.Getwd() options.FailIf(err, "Can't get working directory") if !path.IsAbs(spec.Path) { spec.Path = path.Join(spec.Dir, spec.Path) } pidfile := *options.String["pidfile"] var pidOut *os.File if pidfile == "-" { pidOut = os.Stdout } else if pidfile != "" { pidOut, err = os.Create(pidfile) options.FailIf(err, "Can't open pid file") } namefile := *options.String["namefile"] var nameOut *os.File if namefile == "-" { nameOut = os.Stdout } else if namefile != "" { nameOut, err = os.Create(namefile) options.FailIf(err, "Can't open name file") } daemon := *options.Bool["daemon"] disown := *options.Bool["disown"] var pr, pw *os.File proxying := false tty := isCtty(int(os.Stdin.Fd())) if daemon { // stdio is nil } else if disown { // We are assuming that if stdin is a terminal, it is our controlling // terminal. I don't know any way to verify it, but it seems likely. if tty { // stdin is nil, else they would steal input from tty } else { spec.Stdin = os.Stdin } spec.Stdout = os.Stdout spec.Stderr = os.Stderr } else { // interactive proxying = tty if proxying { pr, pw, err = os.Pipe() options.FailIf(err, "Can't pipe") spec.Stdin = pr } else { spec.Stdin = os.Stdin } spec.Stdout = os.Stdout spec.Stderr = os.Stderr verbose.Printf("[proxying stdin]\n") } // Start catching signals early, buffering a few, so we don't miss any. We // don't proxy SIGTTIN. However, we do catch it and stop ourselves, rather // than letting the OS stop us. This is necessary so that we can send // SIGCONT to the child at the right times. // Here is the easy case // we start in background // we fork (output starts going) // we are background, so leave SIGTTIN handling at the default // we read and get SIGTTIN, so are stopped // child is not stopped, it keeps outputting, as desired // upon fg, we get SIGCONT, start dropping SIGTTIN and looping for input and signals // Here is the tricky case: // we start in foreground // we fork (output starts going) // we are foreground, so catch and drop SIGTTIN (we use SIGTSTP instead) // we get SIGTSTP via ctrl-z // we send child SIGTSTP, so it stops // [we are still dropping SIGTTIN] // we send ourselves SIGSTOP, so we stop // we get SIGCONT via either bg or fg // [if bg, now furiously catching and dropping SIGTTIN] // [if fg, dropping too, but there should not be any SIGTTIN] // send child the SIGCONT // if we are foreground, so go back to top of loop // if we are background, reset SIGTTIN which causes us to stop // // The basic invariant we are trying to maintain is that when we are // foreground we catch and drop SIGTTIN, allowing us to properly handle // SIGTSTP events. There shouldn't be any SIGTTIN anyway, except for the // brief moments when we are transitioning to stopped. // And when the child is supposed to be running in the background, we should // leave the default SIGTTIN behavior, so that the OS will stop our read // loop. signals := make(chan os.Signal, 10) // some buffering signal.Notify(signals, syscall.SIGINT, // Ctrl-C syscall.SIGTERM, // SIGINT wannabe (e.g. via kill) syscall.SIGQUIT, // Ctrl-\ syscall.SIGTSTP, // Ctrl-Z syscall.SIGHUP, // tty hangup (e.g. via disown) syscall.SIGABRT, // abort (e.g. via kill) syscall.SIGUSR1, // user-defined (e.g. via kill) syscall.SIGUSR2, // user-defined (e.g. via kill) ) // Start the hosted program subprin, pid, err := client.StartHostedProgram(spec) options.FailIf(err, "Can't start hosted program") verbose.Printf("[started hosted program with pid %d]\n", pid) verbose.Printf("[subprin is %v]\n", subprin) if pidOut != nil { fmt.Fprintln(pidOut, pid) pidOut.Close() } if nameOut != nil { fmt.Fprintln(nameOut, subprin) nameOut.Close() } if disown || daemon { return } // Listen for exit status from host status := make(chan int, 1) go func() { s, _ := client.WaitHostedProgram(pid, subprin) // For short programs, we often lose the race, so // we get a "no such hosted program" error. Ignore it. status <- s }() wasForeground := false if proxying && isForeground() { verbose.Printf("[in foreground]\n") dropSIGTTIN() wasForeground = true } // Proxy stdin, if needed if proxying { pr.Close() go func() { _, err := io.Copy(pw, os.Stdin) options.WarnIf(err, "Can't copy from stdin to pipe") pw.Close() }() } // If we are proxying and (were) background, we should probably // have done a read() by now and gotten SIGTTIN and stopped. Let's // pause a moment to be sure the read() happens. time.Sleep(moment) // By this point, if we had been foreground, we might still be. Or, we might // have been foreground but just gotten SIGTSTP and are now madly dropping // SIGTTIN until we get into the loop below to handle the SIGTSTP. // // Alternatively, if we had been background, we would have been stopped by // the default SIGTTIN, so the only way we would be here is if we later got // pulled foreground via fg. We want to be dropping SIGTTIN in case we get a // SIGTSTP. if proxying && !wasForeground { dropSIGTTIN() } next := cont for next != done { select { case s := <-status: verbose.Printf("[hosted program exited, %s]\n", exitCode(s)) next = done case sig := <-signals: next = handle(sig, pid) } if next == resumed && proxying && !isForeground() { // Need to toggle SIGTTIN handling and block (that's the only way to // stop spinning on SIGTTIN), but only after handling all pending // signals (e.g. SIGCONT then SIGHUP, or SIGCONT then SIGTERM). for next == resumed { select { case s := <-status: verbose.Printf("[hosted program exited, %s]\n", exitCode(s)) next = done case sig := <-signals: next = handle(sig, pid) if next == cont { next = resumed } default: next = cont defaultSIGTTIN() time.Sleep(moment) dropSIGTTIN() } } } } signal.Stop(signals) }
func defaultSIGTTIN() { verbose.Printf("[default SIGTTIN handling]\n") signal.Stop(discard) }
func dropSIGTTIN() { verbose.Printf("[dropping SIGTTIN]\n") signal.Notify(discard, syscall.SIGTTIN) }
// Note: There is a slight race here. If pids are reused very quickly, we might // end up sending a signal to the wrong child just after the hosted program // exits. That seems unlikely. To fix it, we would have to coordinate with // linux_host, e.g. have linux_host send the signal on our behalf. func send(pid int, sig syscall.Signal) { verbose.Printf("[sending %s to hosted program]\n", sigName(sig)) syscall.Kill(pid, sig) }
func managePolicy() { // Handle queries first if query := *options.String["query"]; query != "" { queryGuard(query) return } // Load domain pwd := getKey("domain policy key password", "pass") domain, err := tao.LoadDomain(configPath(), pwd) options.FailIf(err, "Can't load domain") // Clear all the policy stored by the Guard. if *options.Bool["clear"] { domain.Guard.Clear() err := domain.Save() options.FailIf(err, "Can't save domain") } // Clear all the policy stored by the Guard. if *options.Bool["show"] { n := domain.Guard.RuleCount() fmt.Printf("Domain guard enforces %d rules:\n", n) for i := 0; i < n; i++ { fmt.Printf("rule %d: %s\n", i, domain.Guard.RuleDebugString(i)) } } // Add permissions if canExecute := *options.String["canexecute"]; canExecute != "" { host := template().GetHostName() addExecute(canExecute, host, domain) } if add := *options.String["add"]; add != "" { verbose.Printf("Adding policy rule: %s\n", add) err := domain.Guard.AddRule(add) options.FailIf(err, "Can't add rule to domain") err = domain.Save() options.FailIf(err, "Can't save domain") } if *options.Bool["add_programs"] { host := template().GetHostName() addProgramRules(host, domain) } if *options.Bool["add_containers"] { host := template().GetHostName() addContainerRules(host, domain) } if domain.Config.DomainInfo.GetGuardType() == "Datalog" { if *options.Bool["add_vms"] { addVMRules(domain) } if *options.Bool["add_linux_host"] { addLinuxHostRules(domain) } if *options.Bool["add_host"] { host := template().GetHostName() addHostRules(host, domain) } if *options.Bool["add_guard"] { addGuardRules(domain) } if *options.Bool["add_tpm"] { addTPMRules(domain) } } // Retract permissions if retract := *options.String["retract"]; retract != "" { verbose.Printf("Retracting policy rule: %s\n", retract) err := domain.Guard.RetractRule(retract) options.FailIf(err, "Can't retract rule from domain") err = domain.Save() options.FailIf(err, "Can't save domain") } if retractCanExecute := *options.String["retractcanexecute"]; retractCanExecute != "" { host := template().GetHostName() retractExecute(retractCanExecute, host, domain) } }