veyron/services/identity: Refactoring to allow identityd_test to be run
in a module.

Four refactorings are included in this CL:

1) A server.Listen() function has been created, which starts the veyron
blesser service and http listener, and does not block.  Server.Serve()
calls server.Listen(), and contains the cleanup and blocking logic.

2) profiles/static.go is imported in main.go, and the static.ListenSpec
is threaded through the calls to Serve and Listen.  We shouldn't be
importing a profile in non-main packages.  This was causing the module
to panic.

3) One  utility functions (writeKeyAndCert) needed by identitd_test has
been moved out of main so that it can be used from the module without
needing to duplicate the code.

4) The flag definitons have been moved out of server.go and into
main.go.  The flag values are passed into server.Serve().

Change-Id: I641ea91bdbd1afdb47ea171650b4af4c2254a197
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index 10af65a..ba35304 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -14,6 +14,7 @@
 
 	"v.io/core/veyron2/vlog"
 
+	"v.io/core/veyron/profiles/static"
 	"v.io/core/veyron/services/identity/auditor"
 	"v.io/core/veyron/services/identity/blesser"
 	"v.io/core/veyron/services/identity/caveats"
@@ -31,6 +32,11 @@
 	googleConfigChrome  = flag.String("google_config_chrome", "", "Path to the JSON-encoded OAuth client configuration for Chrome browser applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
 	googleConfigAndroid = flag.String("google_config_android", "", "Path to the JSON-encoded OAuth client configuration for Android applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
 	googleDomain        = flag.String("google_domain", "", "An optional domain name. When set, only email addresses from this domain are allowed to authenticate via Google OAuth")
+
+	// Flags controlling the HTTP server
+	host      = flag.String("host", defaultHost(), "Hostname the HTTP server listens on. This can be the name of the host running the webserver, but if running behind a NAT or load balancer, this should be the host name that clients will connect to. For example, if set to 'x.com', Veyron identities will have the IssuerName set to 'x.com' and clients can expect to find the root name and public key of the signer at 'x.com/blessing-root'.")
+	httpaddr  = flag.String("httpaddr", "localhost:8125", "Address on which the HTTP server listens on.")
+	tlsconfig = flag.String("tlsconfig", "", "Comma-separated list of TLS certificate and private key files. This must be provided.")
 )
 
 func main() {
@@ -65,20 +71,21 @@
 		vlog.Fatalf("Failed to start RevocationManager: %v", err)
 	}
 
-	server.NewIdentityServer(
+	s := server.NewIdentityServer(
 		googleoauth,
 		auditor,
 		reader,
 		revocationManager,
 		oauthBlesserGoogleParams(revocationManager),
-		caveats.NewBrowserCaveatSelector()).Serve()
+		caveats.NewBrowserCaveatSelector())
+	s.Serve(&static.ListenSpec, *host, *httpaddr, *tlsconfig)
 }
 
 func usage() {
 	fmt.Fprintf(os.Stderr, `%s starts an HTTP server that brokers blessings after authenticating through OAuth.
 
 To generate TLS certificates so the HTTP server can use SSL:
-go run $GOROOT/src/crypto/tls/generate_cert.go --host <IP address>
+go run $(go list -f {{.Dir}} "crypto/tls")/generate_cert.go --host <IP address>
 
 To use Google as an OAuth provider the --google_config_* flags must be set to point to
 the a JSON file obtained after registering the application with the Google Developer Console
@@ -134,3 +141,11 @@
 	}
 	return clientID, nil
 }
+
+func defaultHost() string {
+	host, err := os.Hostname()
+	if err != nil {
+		vlog.Fatalf("os.Hostname() failed: %v", err)
+	}
+	return host
+}
diff --git a/services/identity/identityd_test/main.go b/services/identity/identityd_test/main.go
index 4531b63..e2f8058 100644
--- a/services/identity/identityd_test/main.go
+++ b/services/identity/identityd_test/main.go
@@ -5,45 +5,63 @@
 	"flag"
 	"fmt"
 	"os"
-	"os/exec"
 	"time"
 
 	"v.io/core/veyron2/vlog"
 
+	"v.io/core/veyron/profiles/static"
 	"v.io/core/veyron/services/identity/auditor"
 	"v.io/core/veyron/services/identity/blesser"
 	"v.io/core/veyron/services/identity/caveats"
 	"v.io/core/veyron/services/identity/oauth"
 	"v.io/core/veyron/services/identity/revocation"
 	"v.io/core/veyron/services/identity/server"
+	"v.io/core/veyron/services/identity/util"
 )
 
 var (
 	googleDomain = flag.String("google_domain", "", "An optional domain name. When set, only email addresses from this domain are allowed to authenticate via Google OAuth")
+
+	// Flags controlling the HTTP server
+	host      = flag.String("host", "localhost", "Hostname the HTTP server listens on. This can be the name of the host running the webserver, but if running behind a NAT or load balancer, this should be the host name that clients will connect to. For example, if set to 'x.com', Veyron identities will have the IssuerName set to 'x.com' and clients can expect to find the root name and public key of the signer at 'x.com/blessing-root'.")
+	httpaddr  = flag.String("httpaddr", "localhost:8125", "Address on which the HTTP server listens on.")
+	tlsconfig = flag.String("tlsconfig", "", "Comma-separated list of TLS certificate and private key files. This must be provided.")
 )
 
 func main() {
 	flag.Usage = usage
 	flag.Parse()
 
-	auditor, reader := auditor.NewMockBlessingAuditor()
-	revocationManager := revocation.NewMockRevocationManager()
+	// Duration to use for tls cert and blessing duration.
+	duration := 365 * 24 * time.Hour
 
 	// If no tlsconfig has been provided, write and use our own.
 	if flag.Lookup("tlsconfig").Value.String() == "" {
-		writeCertAndKey()
+		if err := util.WriteCertAndKey(*host, duration); err != nil {
+			vlog.Fatal(err)
+		}
 		if err := flag.Set("tlsconfig", "./cert.pem,./key.pem"); err != nil {
 			vlog.Fatal(err)
 		}
 	}
 
-	server.NewIdentityServer(
+	auditor, reader := auditor.NewMockBlessingAuditor()
+	revocationManager := revocation.NewMockRevocationManager()
+
+	googleParams := blesser.GoogleParams{
+		BlessingDuration:  duration,
+		DomainRestriction: *googleDomain,
+		RevocationManager: revocationManager,
+	}
+
+	s := server.NewIdentityServer(
 		oauth.NewMockOAuth(),
 		auditor,
 		reader,
 		revocationManager,
-		oauthBlesserGoogleParams(revocationManager),
-		caveats.NewMockCaveatSelector()).Serve()
+		googleParams,
+		caveats.NewMockCaveatSelector())
+	s.Serve(&static.ListenSpec, *host, *httpaddr, *tlsconfig)
 }
 
 func usage() {
@@ -51,32 +69,9 @@
 mocks out oauth, auditing, and revocation.
 
 To generate TLS certificates so the HTTP server can use SSL:
-go run $GOROOT/src/crypto/tls/generate_cert.go --host <IP address>
+go run $(go list -f {{.Dir}} "crypto/tls")/generate_cert.go --host <IP address>
 
 Flags:
 `, os.Args[0])
 	flag.PrintDefaults()
 }
-
-func writeCertAndKey() {
-	goroot := os.Getenv("GOROOT")
-	if goroot == "" {
-		vlog.Fatal("GOROOT not set")
-	}
-	generateCertFile := goroot + "/src/crypto/tls/generate_cert.go"
-	host := flag.Lookup("host").Value.String()
-	duration := 1 * time.Hour
-	if err := exec.Command("go", "run", generateCertFile, "--host", host, "--duration", duration.String()).Run(); err != nil {
-		vlog.Fatalf("Could not generate key and cert: %v", err)
-	}
-}
-
-func oauthBlesserGoogleParams(revocationManager revocation.RevocationManager) blesser.GoogleParams {
-	googleParams := blesser.GoogleParams{
-		BlessingDuration:  365 * 24 * time.Hour,
-		DomainRestriction: *googleDomain,
-		RevocationManager: revocationManager,
-	}
-	// TODO(suharshs): Figure out the test for this.
-	return googleParams
-}
diff --git a/services/identity/server/identityd.go b/services/identity/server/identityd.go
index c388d60..2ad5dcb 100644
--- a/services/identity/server/identityd.go
+++ b/services/identity/server/identityd.go
@@ -3,12 +3,10 @@
 
 import (
 	"crypto/rand"
-	"flag"
 	"fmt"
 	"html/template"
 	"net"
 	"net/http"
-	"os"
 	"reflect"
 	"strings"
 
@@ -31,15 +29,6 @@
 	"v.io/core/veyron/services/identity/revocation"
 	services "v.io/core/veyron/services/security"
 	"v.io/core/veyron/services/security/discharger"
-
-	"v.io/core/veyron/profiles/static"
-)
-
-var (
-	// Flags controlling the HTTP server
-	httpaddr  = flag.String("httpaddr", "localhost:8125", "Address on which the HTTP server listens on.")
-	tlsconfig = flag.String("tlsconfig", "", "Comma-separated list of TLS certificate and private key files. This must be provided.")
-	host      = flag.String("host", defaultHost(), "Hostname the HTTP server listens on. This can be the name of the host running the webserver, but if running behind a NAT or load balancer, this should be the host name that clients will connect to. For example, if set to 'x.com', Veyron identities will have the IssuerName set to 'x.com' and clients can expect to find the root name and public key of the signer at 'x.com/blessing-root'.")
 )
 
 const (
@@ -73,9 +62,7 @@
 	}
 }
 
-func (s *identityd) Serve() {
-	flag.Parse()
-
+func (s *identityd) Serve(listenSpec *ipc.ListenSpec, host, httpaddr, tlsconfig string) {
 	p, r := providerPrincipal(s.auditor)
 	defer r.Cleanup()
 
@@ -85,6 +72,13 @@
 	}
 	defer runtime.Cleanup()
 
+	ipcServer, _, _ := s.Listen(runtime, listenSpec, host, httpaddr, tlsconfig)
+	defer ipcServer.Stop()
+
+	<-signals.ShutdownOnSignals(runtime)
+}
+
+func (s *identityd) Listen(runtime veyron2.Runtime, listenSpec *ipc.ListenSpec, host, httpaddr, tlsconfig string) (ipc.Server, []string, string) {
 	// Setup handlers
 	http.Handle("/blessing-root", handlers.BlessingRoot{runtime.Principal()}) // json-encoded public key and blessing names of this server
 
@@ -93,17 +87,18 @@
 		vlog.Fatalf("macaroonKey generation failed: %v", err)
 	}
 
-	ipcServer, published, err := s.setupServices(runtime, macaroonKey)
+	ipcServer, published, err := s.setupServices(runtime, listenSpec, macaroonKey)
 	if err != nil {
 		vlog.Fatalf("Failed to setup veyron services for blessing: %v", err)
 	}
-	defer ipcServer.Stop()
+
+	externalHttpaddr := httpaddress(host, httpaddr)
 
 	n := "/google/"
 	h, err := oauth.NewHandler(oauth.HandlerArgs{
 		R:                       runtime,
 		MacaroonKey:             macaroonKey,
-		Addr:                    fmt.Sprintf("%s%s", httpaddress(), n),
+		Addr:                    fmt.Sprintf("%s%s", externalHttpaddr, n),
 		BlessingLogReader:       s.blessingLogReader,
 		RevocationManager:       s.revocationManager,
 		DischargerLocation:      naming.JoinAddressName(published[0], dischargerService),
@@ -138,9 +133,9 @@
 			vlog.Info("Failed to render template:", err)
 		}
 	})
-	vlog.Infof("Running HTTP server at: %v", httpaddress())
-	go runHTTPSServer(*httpaddr)
-	<-signals.ShutdownOnSignals(runtime)
+	vlog.Infof("Running HTTP server at: %v", externalHttpaddr)
+	go runHTTPSServer(httpaddr, tlsconfig)
+	return ipcServer, published, externalHttpaddr
 }
 
 func appendSuffixTo(objectname []string, suffix string) []string {
@@ -152,19 +147,19 @@
 }
 
 // Starts the blessing services and the discharging service on the same port.
-func (s *identityd) setupServices(r veyron2.Runtime, macaroonKey []byte) (ipc.Server, []string, error) {
-	server, err := r.NewServer()
+func (s *identityd) setupServices(runtime veyron2.Runtime, listenSpec *ipc.ListenSpec, macaroonKey []byte) (ipc.Server, []string, error) {
+	server, err := runtime.NewServer()
 	if err != nil {
 		return nil, nil, fmt.Errorf("failed to create new ipc.Server: %v", err)
 	}
-	eps, err := server.Listen(static.ListenSpec)
+	eps, err := server.Listen(*listenSpec)
 	if err != nil {
-		return nil, nil, fmt.Errorf("server.Listen(%v) failed: %v", static.ListenSpec, err)
+		return nil, nil, fmt.Errorf("server.Listen(%v) failed: %v", *listenSpec, err)
 	}
 	ep := eps[0]
 
 	dispatcher := newDispatcher(macaroonKey, oauthBlesserParams(s.oauthBlesserParams, s.revocationManager, ep))
-	objectname := naming.Join("identity", fmt.Sprintf("%v", r.Principal().BlessingStore().Default()))
+	objectname := naming.Join("identity", fmt.Sprintf("%v", runtime.Principal().BlessingStore().Default()))
 	if err := server.ServeDispatcher(objectname, dispatcher); err != nil {
 		return nil, nil, fmt.Errorf("failed to start Veyron services: %v", err)
 	}
@@ -206,11 +201,11 @@
 	return inputParams
 }
 
-func runHTTPSServer(addr string) {
-	if len(*tlsconfig) == 0 {
+func runHTTPSServer(addr, tlsconfig string) {
+	if len(tlsconfig) == 0 {
 		vlog.Fatal("Please set the --tlsconfig flag")
 	}
-	paths := strings.Split(*tlsconfig, ",")
+	paths := strings.Split(tlsconfig, ",")
 	if len(paths) != 2 {
 		vlog.Fatalf("Could not parse --tlsconfig. Must have exactly two components, separated by a comma")
 	}
@@ -220,14 +215,6 @@
 	}
 }
 
-func defaultHost() string {
-	host, err := os.Hostname()
-	if err != nil {
-		vlog.Fatalf("Failed to get hostname: %v", err)
-	}
-	return host
-}
-
 // providerPrincipal returns the Principal to use for the identity provider (i.e., this program).
 //
 // TODO(ataly, suharhs, mattr): HACK!!! This method also returns the runtime that it creates
@@ -246,12 +233,12 @@
 	return audit.NewPrincipal(r.Principal(), auditor), r
 }
 
-func httpaddress() string {
-	_, port, err := net.SplitHostPort(*httpaddr)
+func httpaddress(host, httpaddr string) string {
+	_, port, err := net.SplitHostPort(httpaddr)
 	if err != nil {
-		vlog.Fatalf("Failed to parse %q: %v", *httpaddr, err)
+		vlog.Fatalf("Failed to parse %q: %v", httpaddr, err)
 	}
-	return fmt.Sprintf("https://%s:%v", *host, port)
+	return fmt.Sprintf("https://%s:%v", host, port)
 }
 
 var tmpl = template.Must(template.New("main").Parse(`<!doctype html>
diff --git a/services/identity/util/certs.go b/services/identity/util/certs.go
new file mode 100644
index 0000000..697e9e2
--- /dev/null
+++ b/services/identity/util/certs.go
@@ -0,0 +1,24 @@
+package util
+
+import (
+	"fmt"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+// WriteCertAndKey creates a certificate and private key for a given host and
+// duration and writes them to cert.pem and key.pem.
+func WriteCertAndKey(host string, duration time.Duration) error {
+	listCmd := exec.Command("go", "list", "-f", "{{.Dir}}", "crypto/tls")
+	output, err := listCmd.Output()
+	if err != nil {
+		return fmt.Errorf("%s failed: %v", strings.Join(listCmd.Args, " "), err)
+	}
+	generateCertFile := filepath.Join(strings.TrimSpace(string(output)), "generate_cert.go")
+	if err := exec.Command("go", "run", generateCertFile, "--host", host, "--duration", duration.String()).Run(); err != nil {
+		return fmt.Errorf("Could not generate key and cert: %v", err)
+	}
+	return nil
+}