Move TLS setup to config file, not flags. Use jsonconfig for root config.

This commit is contained in:
Brad Fitzpatrick 2011-05-09 11:49:02 -07:00
parent 0d3f7305cb
commit 17a804b7c1
8 changed files with 101 additions and 28 deletions

View File

@ -1,6 +1,10 @@
{ "_for-emacs": "-*- mode: js2;-*-", { "_for-emacs": "-*- mode: js2;-*-",
"baseURL": ["_env", "http://localhost:${CAMLI_PORT}"], "baseURL": ["_env", "http://localhost:${CAMLI_PORT}"],
"password": ["_env", "${CAMLI_PASSWORD}"], "password": ["_env", "${CAMLI_PASSWORD}"],
"TLSCertFile": ["_env", "${CAMLI_TLS_CRT_FILE}"],
"TLSKeyFile": ["_env", "${CAMLI_TLS_KEY_FILE}"],
"prefixes": { "prefixes": {
"/": { "/": {
"handler": "filesystem", "handler": "filesystem",

View File

@ -0,0 +1,13 @@
{ "_for-emacs": "-*- mode: js2;-*-",
"baseURL": ["_env", "http://localhost:${CAMLI_PORT}"],
"password": ["_env", "${CAMLI_PASSWORD}"],
"prefixes": {
"/": {
"handler": "filesystem",
"handlerArgs": {
"path": ["_env", "${CAMLI_ROOT}"]
}
}
}
}

12
config/dev-tls.crt Normal file
View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBwTCCASugAwIBAgIBADALBgkqhkiG9w0BAQUwADAeFw0xMTAzMzEyMDI1MDda
Fw00OTEyMzEyMzU5NTlaMAAwggCdMAsGCSqGSIb3DQEBAQOCAIwAMIIAhwKCAIB6
oy4iT42G6qk+GGn5VL5JlnJT6ZG5cqaMNFaNGlIxNb6CPUZLKq2sM3gRaimsktIw
nNAcNwQGHpe1tZo+J/Pl04JTt71Y/TTAxy7OX27aZf1Rpt0SjdZ7vTPnFDPNsHGe
KBKvPt55l2+YKjkZmV7eRevsVbpkNvNGB+T5d4Ge/wIBA6NPME0wDgYDVR0PAQH/
BAQDAgCgMA0GA1UdDgQGBAQBAgMEMA8GA1UdIwQIMAaABAECAwQwGwYDVR0RBBQw
EoIJMTI3LjAuMC4xggVbOjoxXTALBgkqhkiG9w0BAQUDggCBAHC3gbdvc44vs+wD
g2kONiENnx8WKc0UTGg/TOXS3gaRb+CUIQtHWja65l8rAfclEovjHgZ7gx8brO0W
JuC6p3MUAKsgOssIrrRIx2rpnfcmFVMzguCmrMNVmKUAalw18Yp0F72xYAIitVQl
kJrLdIhBajcJRYu/YGltHQRaXuVt
-----END CERTIFICATE-----

11
config/dev-tls.key Normal file
View File

@ -0,0 +1,11 @@
-----BEGIN RSA PRIVATE KEY-----
MIIBkgIBAQKCAIB6oy4iT42G6qk+GGn5VL5JlnJT6ZG5cqaMNFaNGlIxNb6CPUZL
Kq2sM3gRaimsktIwnNAcNwQGHpe1tZo+J/Pl04JTt71Y/TTAxy7OX27aZf1Rpt0S
jdZ7vTPnFDPNsHGeKBKvPt55l2+YKjkZmV7eRevsVbpkNvNGB+T5d4Ge/wIBAwKC
AIBRwh7Bil5Z8cYpZZv7jdQxDvbim7Z7ocRdeDmzZuF2I9RW04QyHHPIIlALnBvI
YeF1veASz1gEFGUjzmbUGqKYSbCoTzXoev+F4bmbRxcX9sOmtslqvhMSHRSzA5NH
aDVI3Hn4wvBVD8gePu8ACWqvPGbCiql11OKCMfjlPn2uuwJAx/24/F5DjXZ6hQQ7
HxScOxKrpx5WnA9r1wZTltOTZkhRRzuLc21WJeE3M15QUdWi3zZxCKRFoth65HEs
jy9YHQJAnPueRI44tz79b5QqVbeaOMUr7ZCb1Kp0uo6G+ANPLdlfliAupwij2eIz
mHRJOWk0jBtXfRft1McH2H51CpXAyw==
-----END RSA PRIVATE KEY-----

View File

@ -5,12 +5,15 @@ use FindBin qw($Bin);
use Getopt::Long; use Getopt::Long;
sub usage { sub usage {
die "Usage: dev-blobserver [--wipe] <portnumber> -- [other_blobserver_opts]"; die "Usage: dev-blobserver [--wipe] [--tls] <portnumber> -- [other_blobserver_opts]";
} }
my $opt_wipe; my $opt_wipe;
GetOptions("wipe" => \$opt_wipe) my $opt_tls;
or usage(); GetOptions(
"wipe" => \$opt_wipe,
"tls" => \$opt_tls,
) or usage();
my $port = shift || "3179"; my $port = shift || "3179";
usage() unless $port =~ /^\d+$/; usage() unless $port =~ /^\d+$/;
@ -31,7 +34,10 @@ print "Starting blobserver on http://localhost:$port/ in $root\n";
$ENV{CAMLI_PASSWORD} = "pass$port"; $ENV{CAMLI_PASSWORD} = "pass$port";
$ENV{CAMLI_PORT} = $port; $ENV{CAMLI_PORT} = $port;
$ENV{CAMLI_ROOT} = $root; $ENV{CAMLI_ROOT} = $root;
$ENV{CAMLI_TLS_CRT_FILE} = $opt_tls ? "$Bin/config/dev-tls.crt" : "";
$ENV{CAMLI_TLS_KEY_FILE} = $opt_tls ? "$Bin/config/dev-tls.key" : "";
exec("$FindBin::Bin/server/go/camlistored/camlistored", exec("$FindBin::Bin/server/go/camlistored/camlistored",
"-configfile=$Bin/config/dev-blobserver-config.json", "-configfile=$Bin/config/dev-blobserver-config.json",
"-listen=:$port", "-listen=:$port",
@ARGV); @ARGV);

View File

@ -24,10 +24,25 @@ import (
type Obj map[string]interface{} type Obj map[string]interface{}
func (jc Obj) RequiredObject(key string) Obj {
jc.noteKnownKey(key)
ei, ok := jc[key]
if !ok {
jc.appendError(fmt.Errorf("Missing required config key %q (object)", key))
return make(Obj)
}
m, ok := ei.(map[string]interface{})
if !ok {
jc.appendError(fmt.Errorf("Expected config key %q to be an object, not %T", key, ei))
return make(Obj)
}
return Obj(m)
}
func (jc Obj) RequiredString(key string) string { func (jc Obj) RequiredString(key string) string {
jc.noteKnownKey(key) jc.noteKnownKey(key)
ei, ok := jc[key] ei, ok := jc[key]
if !ok { if !ok {
jc.appendError(fmt.Errorf("Missing required config key %q (string)", key)) jc.appendError(fmt.Errorf("Missing required config key %q (string)", key))
return "" return ""
} }

View File

@ -32,17 +32,15 @@ import (
var Listen = flag.String("listen", "0.0.0.0:2856", "host:port to listen on, or :0 to auto-select") var Listen = flag.String("listen", "0.0.0.0:2856", "host:port to listen on, or :0 to auto-select")
var flagSelfUrlBase = flag.String("self-base-url", "", "If empty, automatic. Else of form https://foo.com (no trailing slash)")
var flagTLS = flag.Bool("tls", false, "Use TLS")
var flagCertFile = flag.String("tls-crt", "", "If using TLS, path to cert (public key) file.")
var flagKeyFile = flag.String("tls-key", "", "If using TLS, path to private key file.")
type HandlerPicker func(req *http.Request) (http.HandlerFunc, bool) type HandlerPicker func(req *http.Request) (http.HandlerFunc, bool)
type Server struct { type Server struct {
premux []HandlerPicker premux []HandlerPicker
mux *http.ServeMux mux *http.ServeMux
listener net.Listener listener net.Listener
enableTLS bool
tlsCertFile, tlsKeyFile string
} }
func New() *Server { func New() *Server {
@ -52,15 +50,21 @@ func New() *Server {
} }
} }
func (s *Server) SetTLS(certFile, keyFile string) {
s.enableTLS = true
s.tlsCertFile = certFile
s.tlsKeyFile = keyFile
}
func (s *Server) BaseURL() string { func (s *Server) BaseURL() string {
if *flagSelfUrlBase != "" { scheme := "http"
// TODO: be automatic for TLS certs? find host name of cert inside it. if s.enableTLS {
return *flagSelfUrlBase scheme = "https"
} }
if strings.HasPrefix(*Listen, ":") { if strings.HasPrefix(*Listen, ":") {
return "http://127.0.0.1" + *Listen return scheme + "://127.0.0.1" + *Listen
} }
return "http://" + strings.Replace(*Listen, "0.0.0.0:", "127.0.0.1:", 1) return scheme + "://" + strings.Replace(*Listen, "0.0.0.0:", "127.0.0.1:", 1)
} }
// Register conditional handler-picker functions which get run before // Register conditional handler-picker functions which get run before
@ -91,7 +95,7 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
func (s *Server) Serve() { func (s *Server) Serve() {
if os.Getenv("TESTING_PORT_WRITE_FD") == "" { // Don't make noise during unit tests if os.Getenv("TESTING_PORT_WRITE_FD") == "" { // Don't make noise during unit tests
log.Printf("Starting to listen on http://%v/\n", *Listen) log.Printf("Starting to listen on %s\n", s.BaseURL())
} }
var err os.Error var err os.Error
@ -100,14 +104,14 @@ func (s *Server) Serve() {
log.Fatalf("Failed to listen on %s: %v", *Listen, err) log.Fatalf("Failed to listen on %s: %v", *Listen, err)
} }
if *flagTLS { if s.enableTLS {
config := &tls.Config{ config := &tls.Config{
Rand: rand.Reader, Rand: rand.Reader,
Time: time.Seconds, Time: time.Seconds,
NextProtos: []string{"http/1.1"}, NextProtos: []string{"http/1.1"},
} }
config.Certificates = make([]tls.Certificate, 1) config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(*flagCertFile, *flagKeyFile) config.Certificates[0], err = tls.LoadX509KeyPair(s.tlsCertFile, s.tlsKeyFile)
if err != nil { if err != nil {
log.Fatalf("Failed to load TLS cert: %v", err) log.Fatalf("Failed to load TLS cert: %v", err)
} }

View File

@ -204,8 +204,8 @@ func configFileMain() {
} }
defer f.Close() defer f.Close()
dj := json.NewDecoder(f) dj := json.NewDecoder(f)
config := make(map[string]interface{}) rootjson := make(map[string]interface{})
if err = dj.Decode(&config); err != nil { if err = dj.Decode(&rootjson); err != nil {
extra := "" extra := ""
if serr, ok := err.(*json.SyntaxError); ok { if serr, ok := err.(*json.SyntaxError); ok {
if _, serr := f.Seek(0, os.SEEK_SET); serr != nil { if _, serr := f.Seek(0, os.SEEK_SET); serr != nil {
@ -218,26 +218,34 @@ func configFileMain() {
exitFailure("error parsing JSON object in config file %s%s\n%v", exitFailure("error parsing JSON object in config file %s%s\n%v",
osutil.UserServerConfigPath(), extra, err) osutil.UserServerConfigPath(), extra, err)
} }
if err := jsonconfig.EvaluateExpressions(config); err != nil { if err := jsonconfig.EvaluateExpressions(rootjson); err != nil {
exitFailure("error expanding JSON config expressions in %s: %v", configPath, err) exitFailure("error expanding JSON config expressions in %s: %v", configPath, err)
} }
ws := webserver.New() ws := webserver.New()
baseURL := ws.BaseURL() baseURL := ws.BaseURL()
if password, ok := config["password"].(string); ok { // Root configuration
auth.AccessPassword = password config := jsonconfig.Obj(rootjson)
{
cert, key := config.OptionalString("TLSCertFile", ""), config.OptionalString("TLSKeyFile", "")
if (cert != "") != (key != "") {
exitFailure("TLSCertFile and TLSKeyFile must both be either present or absent")
}
if cert != "" {
ws.SetTLS(cert, key)
}
} }
if url, ok := config["baseURL"].(string); ok { auth.AccessPassword = config.OptionalString("password", "")
if url := config.OptionalString("baseURL", ""); url != "" {
baseURL = url baseURL = url
} }
prefixes := config.RequiredObject("prefixes")
prefixes, ok := config["prefixes"].(map[string]interface{}) if err := config.Validate(); err != nil {
if !ok { exitFailure("configuration error in root object's keys in %s: %v", configPath, err)
exitFailure("No top-level \"prefixes\": {...} in %s", osutil.UserServerConfigPath)
} }
createdHandlers := make(map[string]interface{}) createdHandlers := make(map[string]interface{})
for prefix, vei := range prefixes { for prefix, vei := range prefixes {