github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/apps/host/host.go (about) 1 // Copyright (c) 2014, Kevin Walsh. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package host exposes the functionality of a linux_host implementation as a 16 // library. 17 package host 18 19 import ( 20 "crypto/x509" 21 "crypto/x509/pkix" 22 "errors" 23 "flag" 24 "fmt" 25 "io/ioutil" 26 "net" 27 "os" 28 "os/exec" 29 "os/signal" 30 "path" 31 "syscall" 32 "text/tabwriter" 33 34 "github.com/golang/protobuf/proto" 35 "github.com/jlmucb/cloudproxy/go/tao" 36 "github.com/jlmucb/cloudproxy/go/util" 37 "github.com/jlmucb/cloudproxy/go/util/options" 38 // "github.com/golang/crypto/ssh/terminal" 39 "golang.org/x/crypto/ssh/terminal" 40 ) 41 42 var opts = []options.Option{ 43 // Flags for all/most commands 44 {"tao_domain", "", "<dir>", "Tao domain configuration directory", "all"}, 45 {"host", "", "<dir>", "Host configuration, relative to domain directory or absolute", "all"}, 46 {"quiet", false, "", "Be more quiet", "all"}, 47 {"domain_pass", "", "<password>", "Password for domain policy key", "all"}, 48 49 // Flags for init (and start) command 50 {"root", false, "", "Create a root host, not backed by any parent Tao", "init,start"}, 51 {"stacked", false, "", "Create a stacked host, backed by a parent Tao", "init,start"}, 52 // TODO(kwalsh) hosted program type should be selectable at time of 53 // tao_launch. A single host should be able to host all types concurrently. 54 {"hosting", "", "<type>", "Hosted program type: process, docker, kvm_coreos or kvm_custom", "init"}, 55 {"socket_dir", "", "<dir>", "Hosted program socket directory, relative to host directory or absolute", "init"}, 56 57 // Flags for start command 58 {"foreground", false, "", "Run in the foreground", "start"}, 59 // Using setsid (1) and shell redirection is an alternative -daemon: 60 // sh$ setsid tao host start ... </dev/null >/dev/null 2>&1 61 // sh$ setsid linux_host start ... </dev/null >/dev/null 2>&1 62 {"daemon", false, "", "Detach from tty, close stdio, and run as a daemon", "start"}, 63 64 // Flags for root 65 {"pass", "", "<password>", "Host password for root hosts (for testing only!)", "root"}, 66 67 // Flags for stacked 68 {"parent_type", "", "<type>", "Type of channel to parent Tao: TPM, TPM2, pipe, file, or unix", "stacked"}, 69 {"parent_spec", "", "<spec>", "Spec for channel to parent Tao", "stacked"}, 70 71 // Flags for QEMU/KVM CoreOS init 72 {"kvm_coreos_img", "", "<path>", "Path to CoreOS.img file, relative to domain or absolute", "kvm"}, 73 {"kvm_coreos_vm_memory", 0, "SIZE", "The amount of RAM (in KB) to give VM", "kvm"}, 74 // TODO(kwalsh) shouldn't keys be generated randomly within the host? 75 // Otherwise, we need to trust whoever holds the keys, no? 76 {"kvm_coreos_ssh_auth_keys", "", "<path>", "An authorized_keys file for SSH to CoreOS guest, relative to domain or absolute", "kvm"}, 77 78 // Flags for QEMU/KVM init with custom kernel and initram 79 {"kvm_custom_vm_memory", 1024, "SIZE", "The amount of RAM (in KB) to give VM", "kvm_custom"}, 80 } 81 82 func init() { 83 options.Add(opts...) 84 } 85 86 func help() { 87 w := new(tabwriter.Writer) 88 w.Init(os.Stderr, 4, 0, 2, ' ', 0) 89 av0 := path.Base(os.Args[0]) 90 91 fmt.Fprintf(w, "Linux Tao Host\n") 92 fmt.Fprintf(w, "Usage:\n") 93 fmt.Fprintf(w, " %s init [options]\t Initialize a new host\n", av0) 94 fmt.Fprintf(w, " %s show [options]\t Show host principal name\n", av0) 95 fmt.Fprintf(w, " %s start [options]\t Start the host\n", av0) 96 fmt.Fprintf(w, " %s stop [options]\t Request the host stop\n", av0) 97 fmt.Fprintf(w, "\n") 98 99 categories := []options.Category{ 100 {"all", "Basic options for most commands"}, 101 {"init", "Options for 'init' command"}, 102 {"start", "Options for 'start' command"}, 103 {"root", "Options for root hosts"}, 104 {"stacked", "Options for stacked hosts"}, 105 {"kvm", "Options for hosting QEMU/KVM CoreOS"}, 106 {"kvm_custom", "Options for hosting QEMU/KVM instance with custom kernel and initram"}, 107 {"logging", "Options to control log output"}, 108 } 109 options.ShowRelevant(w, categories...) 110 111 w.Flush() 112 } 113 114 var noise = ioutil.Discard 115 116 // Main provides the main functionality of linux_host. This is provided as a 117 // separate function to allow other code to register other Tao implementations 118 // (with tao.Register) before starting the code. 119 func Main() { 120 flag.Usage = help 121 122 // Get options before the command verb 123 flag.Parse() 124 // Get command verb 125 cmd := "help" 126 if flag.NArg() > 0 { 127 cmd = flag.Arg(0) 128 } 129 // Get options after the command verb 130 if flag.NArg() > 1 { 131 flag.CommandLine.Parse(flag.Args()[1:]) 132 } 133 134 if !*options.Bool["quiet"] { 135 noise = os.Stdout 136 } 137 // Load the domain. 138 domain, err := tao.LoadDomain(domainConfigPath(), nil) 139 140 // Set $TAO_DOMAIN so it will be inherited by hosted programs 141 os.Unsetenv("TAO_DOMAIN") 142 err = os.Setenv("TAO_DOMAIN", domainPath()) 143 options.FailIf(err, "Can't set $TAO_DOMAIN") 144 145 switch cmd { 146 case "help": 147 help() 148 case "init": 149 initHost(domain) 150 case "show": 151 showHost(domain) 152 case "start": 153 startHost(domain) 154 case "stop", "shutdown": 155 stopHost(domain) 156 default: 157 options.Usage("Unrecognized command: %s", cmd) 158 } 159 } 160 161 func domainPath() string { 162 if path := *options.String["tao_domain"]; path != "" { 163 return path 164 } 165 if path := os.Getenv("TAO_DOMAIN"); path != "" { 166 return path 167 } 168 options.Usage("Must supply -tao_domain or set $TAO_DOMAIN") 169 return "" 170 } 171 172 func domainConfigPath() string { 173 return path.Join(domainPath(), "tao.config") 174 } 175 176 func hostPath() string { 177 hostPath := *options.String["host"] 178 if hostPath == "" { 179 // options.Usage("Must supply a -host path") 180 hostPath = "linux_tao_host" 181 } 182 if !path.IsAbs(hostPath) { 183 hostPath = path.Join(domainPath(), hostPath) 184 } 185 return hostPath 186 } 187 188 func hostConfigPath() string { 189 return path.Join(hostPath(), "host.config") 190 } 191 192 // Update configuration based on command-line options. Does very little sanity checking. 193 func configureFromOptions(cfg *tao.LinuxHostConfig) { 194 if *options.Bool["root"] && *options.Bool["stacked"] { 195 options.Usage("Can supply only one of -root and -stacked") 196 } else if *options.Bool["root"] { 197 cfg.Type = proto.String("root") 198 } else if *options.Bool["stacked"] { 199 cfg.Type = proto.String("stacked") 200 } else if cfg.Type == nil { 201 options.Usage("Must supply one of -root and -stacked") 202 } 203 if s := *options.String["hosting"]; s != "" { 204 cfg.Hosting = proto.String(s) 205 } 206 if s := *options.String["parent_type"]; s != "" { 207 cfg.ParentType = proto.String(s) 208 } 209 if s := *options.String["parent_spec"]; s != "" { 210 cfg.ParentSpec = proto.String(s) 211 } 212 if s := *options.String["socket_dir"]; s != "" { 213 cfg.SocketDir = proto.String(s) 214 } 215 if s := *options.String["kvm_coreos_img"]; s != "" { 216 cfg.KvmCoreosImg = proto.String(s) 217 } 218 if i := *options.Int["kvm_coreos_vm_memory"]; i != 0 { 219 cfg.KvmCoreosVmMemory = proto.Int32(int32(i)) 220 } 221 if s := *options.String["kvm_coreos_ssh_auth_keys"]; s != "" { 222 cfg.KvmCoreosSshAuthKeys = proto.String(s) 223 } 224 if i := *options.Int["kvm_custom_vm_memory"]; i != 0 { 225 cfg.KvmCustomVmMemory = proto.Int32(int32(i)) 226 } 227 } 228 229 func configureFromFile() *tao.LinuxHostConfig { 230 d, err := ioutil.ReadFile(hostConfigPath()) 231 if err != nil { 232 options.Fail(err, "Can't read linux host configuration") 233 } 234 var cfg tao.LinuxHostConfig 235 if err := proto.UnmarshalText(string(d), &cfg); err != nil { 236 options.Fail(err, "Can't parse linux host configuration") 237 } 238 return &cfg 239 } 240 241 func loadHost(domain *tao.Domain, cfg *tao.LinuxHostConfig) (*tao.LinuxHost, error) { 242 var tc tao.Config 243 244 // Decide host type 245 switch cfg.GetType() { 246 case "root": 247 tc.HostType = tao.Root 248 case "stacked": 249 tc.HostType = tao.Stacked 250 case "": 251 options.Usage("Must supply -hosting flag") 252 default: 253 options.Usage("Invalid host type: %s", cfg.GetType()) 254 } 255 256 // Decide hosting type 257 switch cfg.GetHosting() { 258 case "process": 259 tc.HostedType = tao.ProcessPipe 260 case "docker": 261 tc.HostedType = tao.DockerUnix 262 case "kvm_coreos": 263 tc.HostedType = tao.KVMCoreOSFile 264 case "kvm_custom": 265 tc.HostedType = tao.KVMCustom 266 case "": 267 options.Usage("Must supply -hosting flag") 268 default: 269 options.Usage("Invalid hosting type: %s", cfg.GetHosting()) 270 } 271 272 // For stacked hosts, figure out the channel type: TPM, TPM2, pipe, file, or unix 273 if tc.HostType == tao.Stacked { 274 switch cfg.GetParentType() { 275 case "TPM": 276 tc.HostChannelType = "tpm" 277 case "TPM2": 278 tc.HostChannelType = "tpm2" 279 case "pipe": 280 tc.HostChannelType = "pipe" 281 case "file": 282 tc.HostChannelType = "file" 283 case "unix": 284 tc.HostChannelType = "unix" 285 case "": 286 options.Usage("Must supply -parent_type for stacked hosts") 287 default: 288 options.Usage("Invalid parent type: '%s'", cfg.GetParentType()) 289 } 290 291 // For stacked hosts on anything but a TPM, we also need parent spec 292 if tc.HostChannelType != "tpm" && tc.HostChannelType != "tpm2" { 293 tc.HostSpec = cfg.GetParentSpec() 294 if tc.HostSpec == "" { 295 options.Usage("Must supply -parent_spec for non-TPM stacked hosts") 296 } 297 } else if tc.HostChannelType == "tpm" { 298 // For stacked hosts on a TPM, we also need info from domain config 299 if domain.Config.TpmInfo == nil { 300 options.Usage("Must provide TPM configuration in the domain to use a TPM") 301 } 302 tc.TPMAIKPath = path.Join(domainPath(), domain.Config.TpmInfo.GetAikPath()) 303 tc.TPMPCRs = domain.Config.TpmInfo.GetPcrs() 304 tc.TPMDevice = domain.Config.TpmInfo.GetTpmPath() 305 tc.TPMAIKCertPath = path.Join(domainPath(), domain.Config.TpmInfo.GetAikCertPath()) 306 } else if tc.HostChannelType == "tpm2" { 307 // For stacked hosts on a TPM2, we also need info from domain config 308 if domain.Config.Tpm2Info == nil { 309 options.Usage("Must provide TPM2 configuration in the domain to use a TPM2") 310 } 311 312 tc.TPM2InfoDir = domainPath() 313 tc.TPM2PCRs = domain.Config.Tpm2Info.GetTpm2Pcrs() 314 tc.TPM2Device = domain.Config.Tpm2Info.GetTpm2Device() 315 } 316 } 317 318 rulesPath := "" 319 if p := domain.RulesPath(); p != "" { 320 rulesPath = path.Join(domainPath(), p) 321 } 322 323 // Create the hosted program factory 324 socketPath := hostPath() 325 if subPath := cfg.GetSocketDir(); subPath != "" { 326 if path.IsAbs(subPath) { 327 socketPath = subPath 328 } else { 329 socketPath = path.Join(socketPath, subPath) 330 } 331 } 332 333 // TODO(cjpatton) How do the NewLinuxDockerContainterFactory and the 334 // NewLinuxKVMCoreOSFactory need to be modified to support the new 335 // CachedGuard? They probably don't. 336 var childFactory tao.HostedProgramFactory 337 switch tc.HostedType { 338 case tao.ProcessPipe: 339 childFactory = tao.NewLinuxProcessFactory("pipe", socketPath) 340 case tao.DockerUnix: 341 childFactory = tao.NewLinuxDockerContainerFactory(socketPath, rulesPath) 342 case tao.KVMCoreOSFile: 343 sshFile := cfg.GetKvmCoreosSshAuthKeys() 344 if sshFile == "" { 345 options.Usage("Must specify -kvm_coreos_ssh_auth_keys for hosting QEMU/KVM CoreOS") 346 } 347 if !path.IsAbs(sshFile) { 348 sshFile = path.Join(domainPath(), sshFile) 349 } 350 sshKeysCfg, err := tao.CloudConfigFromSSHKeys(sshFile) 351 options.FailIf(err, "Can't read ssh keys") 352 353 coreOSImage := cfg.GetKvmCoreosImg() 354 if coreOSImage == "" { 355 options.Usage("Must specify -kvm_coreos_image for hosting QEMU/KVM CoreOS") 356 } 357 if !path.IsAbs(coreOSImage) { 358 coreOSImage = path.Join(domainPath(), coreOSImage) 359 } 360 361 vmMemory := cfg.GetKvmCoreosVmMemory() 362 if vmMemory == 0 { 363 vmMemory = 1024 364 } 365 366 cfg := &tao.CoreOSConfig{ 367 ImageFile: coreOSImage, 368 Memory: int(vmMemory), 369 RulesPath: rulesPath, 370 SSHKeysCfg: sshKeysCfg, 371 } 372 childFactory, err = tao.NewLinuxKVMCoreOSFactory(socketPath, cfg) 373 options.FailIf(err, "Can't create KVM CoreOS factory") 374 case tao.KVMCustom: 375 vmMemory := cfg.GetKvmCustomVmMemory() 376 if vmMemory == 0 { 377 vmMemory = 1024 378 } 379 cfg := &tao.VmConfig{ 380 Memory: int(vmMemory), 381 SocketPath: socketPath, 382 } 383 childFactory = tao.NewLinuxKVMCustomFactory(cfg) 384 } 385 386 if tc.HostType == tao.Root { 387 pwd := getKey("root host key password", "pass") 388 lh, err := tao.NewRootLinuxHost(hostPath(), domain.Guard, pwd, childFactory) 389 if err != nil { 390 return nil, err 391 } 392 // Load cert 393 rootHost, ok := lh.Host.(*tao.RootHost) 394 if !ok { 395 return nil, errors.New("Type assertion on newly created root host fails") 396 } 397 var cert *x509.Certificate 398 rawCert, err := ioutil.ReadFile(path.Join(hostPath(), "soft_tao_cert")) 399 if err != nil { 400 // Create cert signed by policy key 401 pwd = getKey("Password for domain policy key", "domain_pass") 402 // Load the domain. 403 domain, err := tao.LoadDomain(domainConfigPath(), pwd) 404 if err != nil { 405 return nil, err 406 } 407 if domain.Keys.SigningKey == nil { 408 return nil, errors.New("Domain policy key missing signing key") 409 } 410 keyName := "Soft Tao Key" 411 universalBytes, err := domain.Keys.SigningKey.UniversalKeyNameFromSigner() 412 if err != nil { 413 return nil, errors.New("Can't get universal name " + err.Error()) 414 } 415 universalKeyName := fmt.Sprintf("key([%x])", universalBytes) 416 // TODO: Check with Kevin on the right way to do this 417 subject := &pkix.Name{ 418 Organization: []string{universalKeyName}, 419 CommonName: keyName, 420 } 421 keyType := tao.SignerTypeFromSuiteName(tao.TaoCryptoSuite) 422 if keyType == nil { 423 return nil, errors.New("Bad key type") 424 } 425 pkAlg := tao.PublicKeyAlgFromSignerAlg(*keyType) 426 sigAlg := tao.SignatureAlgFromSignerAlg(*keyType) 427 if pkAlg < 0 || sigAlg < 0 { 428 return nil, errors.New("Bad Alg type") 429 } 430 verifier := rootHost.GetVerifier() 431 if verifier == nil { 432 return nil, errors.New("Verifier is nil in loadHost") 433 } 434 cert, err = domain.Keys.SigningKey.CreateSignedX509(domain.Keys.Cert, 1, 435 verifier, pkAlg, sigAlg, subject) 436 if err != nil { 437 return nil, err 438 } 439 if err = ioutil.WriteFile(path.Join(hostPath(), "soft_tao_cert"), 440 cert.Raw, os.ModePerm); err != nil { 441 return nil, err 442 } 443 } else { 444 cert, err = x509.ParseCertificate(rawCert) 445 if err != nil { 446 return nil, err 447 } 448 } 449 rootHost.LoadCert(cert) 450 return lh, nil 451 } else { 452 parent := tao.ParentFromConfig(tc) 453 if parent == nil { 454 options.Usage("No host tao available, verify -parent_type or $%s\n", tao.HostChannelTypeEnvVar) 455 } 456 return tao.NewStackedLinuxHost(hostPath(), domain.Guard, tao.ParentFromConfig(tc), childFactory) 457 } 458 } 459 460 func initHost(domain *tao.Domain) { 461 var cfg tao.LinuxHostConfig 462 463 configureFromOptions(&cfg) 464 _, err := loadHost(domain, &cfg) 465 options.FailIf(err, "Can't create host") 466 467 // If we get here, keys were created and flags must be ok. 468 469 file, err := util.CreatePath(hostConfigPath(), 0777, 0666) 470 options.FailIf(err, "Can't create host configuration") 471 cs := proto.MarshalTextString(&cfg) 472 fmt.Fprint(file, cs) 473 file.Close() 474 } 475 476 func showHost(domain *tao.Domain) { 477 cfg := configureFromFile() 478 configureFromOptions(cfg) 479 host, err := loadHost(domain, cfg) 480 options.FailIf(err, "Can't create host") 481 fmt.Printf("%v\n", host.HostName()) 482 } 483 484 func isBoolFlagSet(name string) bool { 485 f := flag.Lookup(name) 486 if f == nil { 487 return false 488 } 489 v, ok := f.Value.(flag.Getter).Get().(bool) 490 return ok && v 491 } 492 493 func daemonize() { 494 // For our purposes, "daemon" means being a session leader. 495 sid, _, errno := syscall.Syscall(syscall.SYS_GETSID, 0, 0, 0) 496 var err error 497 if errno != 0 { 498 err = errno 499 } 500 options.FailIf(err, "Can't get process SID") 501 if int(sid) != syscall.Getpid() { 502 // Go does not support daemonize(), and we can't simply call setsid 503 // because PID may be equal to GID. Using exec.Cmd with the Setsid=true 504 // will fork, ensuring that PID differs from GID, then call setsid, then 505 // exec ourself again in the new session. 506 path, err := os.Readlink("/proc/self/exe") 507 options.FailIf(err, "Can't get path to self executable") 508 // special case: keep stderr if -logtostderr or -alsologtostderr 509 stderr := os.Stderr 510 if !isBoolFlagSet("logtostderr") && !isBoolFlagSet("alsologtostderr") { 511 stderr = nil 512 } 513 spa := &syscall.SysProcAttr{ 514 Setsid: true, // Create session. 515 } 516 daemon := exec.Cmd{ 517 Path: path, 518 Args: os.Args, 519 Stderr: stderr, 520 SysProcAttr: spa, 521 } 522 err = daemon.Start() 523 options.FailIf(err, "Can't become daemon") 524 fmt.Fprintf(noise, "Linux Tao Host running as daemon\n") 525 os.Exit(0) 526 } else { 527 fmt.Fprintf(noise, "Already a session leader?\n") 528 } 529 } 530 531 func startHost(domain *tao.Domain) { 532 533 if *options.Bool["daemon"] && *options.Bool["foreground"] { 534 options.Usage("Can supply only one of -daemon and -foreground") 535 } 536 if *options.Bool["daemon"] { 537 daemonize() 538 } 539 540 cfg := configureFromFile() 541 configureFromOptions(cfg) 542 host, err := loadHost(domain, cfg) 543 options.FailIf(err, "Can't create host") 544 545 sockPath := path.Join(hostPath(), "admin_socket") 546 // Set the socketPath directory go+rx so tao_launch can access sockPath and 547 // connect to this linux host, even when tao_launch is run as non-root. 548 err = os.Chmod(path.Dir(sockPath), 0755) 549 options.FailIf(err, "Can't change permissions") 550 uaddr, err := net.ResolveUnixAddr("unix", sockPath) 551 options.FailIf(err, "Can't resolve unix socket") 552 sock, err := net.ListenUnix("unix", uaddr) 553 options.FailIf(err, "Can't create admin socket") 554 defer sock.Close() 555 err = os.Chmod(sockPath, 0666) 556 if err != nil { 557 sock.Close() 558 options.Fail(err, "Can't change permissions on admin socket") 559 } 560 561 go func() { 562 fmt.Fprintf(noise, "Linux Tao Service (%s) started and waiting for requests\n", host.HostName()) 563 err = tao.NewLinuxHostAdminServer(host).Serve(sock) 564 fmt.Fprintf(noise, "Linux Tao Service finished\n") 565 sock.Close() 566 options.FailIf(err, "Error serving admin requests") 567 os.Exit(0) 568 }() 569 570 c := make(chan os.Signal, 1) 571 signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM) 572 <-c 573 fmt.Fprintf(noise, "Linux Tao Service shutting down\n") 574 err = shutdown() 575 if err != nil { 576 sock.Close() 577 options.Fail(err, "Can't shut down admin socket") 578 } 579 580 // The above goroutine will normally end by calling os.Exit(), so we 581 // can block here indefinitely. But if we get a second kill signal, 582 // let's abort. 583 fmt.Fprintf(noise, "Waiting for shutdown....\n") 584 <-c 585 options.Fail(nil, "Could not shut down linux_host") 586 } 587 588 func stopHost(domain *tao.Domain) { 589 err := shutdown() 590 if err != nil { 591 options.Usage("Couldn't connect to linux_host: %s", err) 592 } 593 } 594 595 func shutdown() error { 596 sockPath := path.Join(hostPath(), "admin_socket") 597 conn, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: sockPath, Net: "unix"}) 598 if err != nil { 599 return err 600 } 601 defer conn.Close() 602 return tao.NewLinuxHostAdminClient(conn).Shutdown() 603 } 604 605 func getKey(prompt, name string) []byte { 606 if input := *options.String[name]; input != "" { 607 fmt.Fprintf(os.Stderr, "Warning: Passwords on the command line are not secure. Use -%s option only for testing.\n", name) 608 return []byte(input) 609 } else { 610 // Get the password from the user. 611 fmt.Print(prompt + ": ") 612 pwd, err := terminal.ReadPassword(syscall.Stdin) 613 options.FailIf(err, "Can't get password") 614 fmt.Println() 615 return pwd 616 } 617 }