func setupTLS(ws *webserver.Server, config *serverinit.Config, listen string) { cert, key := config.OptionalString("TLSCertFile", ""), config.OptionalString("TLSKeyFile", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { exitf("TLSCertFile and TLSKeyFile must both be either present or absent") } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() if cert == defCert && key == defKey { _, err1 := os.Stat(cert) _, err2 := os.Stat(key) if err1 != nil || err2 != nil { if os.IsNotExist(err1) || os.IsNotExist(err2) { if err := genSelfTLS(listen); err != nil { exitf("Could not generate self-signed TLS cert: %q", err) } } else { exitf("Could not stat cert or key: %q, %q", err1, err2) } } } if cert == "" && key == "" { err := genSelfTLS(listen) if err != nil { exitf("Could not generate self signed creds: %q", err) } cert = defCert key = defKey } data, err := ioutil.ReadFile(cert) if err != nil { exitf("Failed to read pem certificate: %s", err) } block, _ := pem.Decode(data) if block == nil { exitf("Failed to decode pem certificate") } certif, err := x509.ParseCertificate(block.Bytes) if err != nil { exitf("Failed to parse certificate: %v", err) } sig := misc.SHA256Prefix(certif.Raw) log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig) ws.SetTLS(cert, key) }
// 1) We do not want to force the user to buy a cert. // 2) We still want our client (camput) to be able to // verify the cert's authenticity. // 3) We want to avoid MITM attacks and warnings in // the browser. // Using a simple self-signed won't do because of 3), // as Chrome offers no way to set a self-signed as // trusted when importing it. (same on android). // We could have created a self-signed CA (that we // would import in the browsers) and create another // cert (signed by that CA) which would be the one // used in camlistore. // We're doing even simpler: create a self-signed // CA and directly use it as a self-signed cert // (and install it as a CA in the browsers). // 2) is satisfied by doing our own checks, // See pkg/client func setupTLS(ws *webserver.Server, config *serverinit.Config, hostname string) { cert, key := config.OptionalString("httpsCert", ""), config.OptionalString("httpsKey", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { exitf("httpsCert and httpsKey must both be either present or absent") } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() const hint = "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n\"trustedCerts\": [\"%s\"]," if cert == defCert && key == defKey { _, err1 := wkfs.Stat(cert) _, err2 := wkfs.Stat(key) if err1 != nil || err2 != nil { if os.IsNotExist(err1) || os.IsNotExist(err2) { sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self-signed TLS cert: %q", err) } log.Printf(hint, sig) } else { exitf("Could not stat cert or key: %q, %q", err1, err2) } } } // Always generate new certificates if the config's httpsCert and httpsKey are empty. if cert == "" && key == "" { sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self signed creds: %q", err) } log.Printf(hint, sig) cert = defCert key = defKey } data, err := wkfs.ReadFile(cert) if err != nil { exitf("Failed to read pem certificate: %s", err) } sig, err := httputil.CertFingerprint(data) if err != nil { exitf("certificate error: %v", err) } log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig) ws.SetTLS(cert, key) }
// certFilename returns the HTTPS certificate file name func certFilename() string { return filepath.Base(osutil.DefaultTLSCert()) }
// genLowLevelConfig returns a low-level config from a high-level config. func genLowLevelConfig(conf *serverconfig.Config) (lowLevelConf *Config, err error) { obj := jsonconfig.Obj{} if conf.HTTPS { if (conf.HTTPSCert != "") != (conf.HTTPSKey != "") { return nil, errors.New("Must set both httpsCert and httpsKey (or neither to generate a self-signed cert)") } if conf.HTTPSCert != "" { obj["httpsCert"] = conf.HTTPSCert obj["httpsKey"] = conf.HTTPSKey } else { obj["httpsCert"] = osutil.DefaultTLSCert() obj["httpsKey"] = osutil.DefaultTLSKey() } } if conf.BaseURL != "" { u, err := url.Parse(conf.BaseURL) if err != nil { return nil, fmt.Errorf("Error parsing baseURL %q as a URL: %v", conf.BaseURL, err) } if u.Path != "" && u.Path != "/" { return nil, fmt.Errorf("baseURL can't have a path, only a scheme, host, and optional port.") } u.Path = "" obj["baseURL"] = u.String() } if conf.Listen != "" { obj["listen"] = conf.Listen } obj["https"] = conf.HTTPS obj["auth"] = conf.Auth username := "" if conf.DBName == "" { username = osutil.Username() if username == "" { return nil, fmt.Errorf("USER (USERNAME on windows) env var not set; needed to define dbname") } conf.DBName = "camli" + username } var indexerPath string numIndexers := numSet(conf.Mongo, conf.MySQL, conf.PostgreSQL, conf.SQLite, conf.KVFile) runIndex := conf.RunIndex.Get() switch { case runIndex && numIndexers == 0: return nil, fmt.Errorf("Unless runIndex is set to false, you must specify an index option (kvIndexFile, mongo, mysql, postgres, sqlite).") case runIndex && numIndexers != 1: return nil, fmt.Errorf("With runIndex set true, you can only pick exactly one indexer (mongo, mysql, postgres, sqlite).") case !runIndex && numIndexers != 0: return nil, fmt.Errorf("With runIndex disabled, you can't specify any of mongo, mysql, postgres, sqlite.") case conf.MySQL != "": indexerPath = "/index-mysql/" case conf.PostgreSQL != "": indexerPath = "/index-postgres/" case conf.Mongo != "": indexerPath = "/index-mongo/" case conf.SQLite != "": indexerPath = "/index-sqlite/" case conf.KVFile != "": indexerPath = "/index-kv/" } entity, err := jsonsign.EntityFromSecring(conf.Identity, conf.IdentitySecretRing) if err != nil { return nil, err } armoredPublicKey, err := jsonsign.ArmoredPublicKey(entity) if err != nil { return nil, err } nolocaldisk := conf.BlobPath == "" if nolocaldisk { if conf.S3 == "" && conf.GoogleCloudStorage == "" { return nil, errors.New("You need at least one of blobPath (for localdisk) or s3 or googlecloudstorage configured for a blobserver.") } if conf.S3 != "" && conf.GoogleCloudStorage != "" { return nil, errors.New("Using S3 as a primary storage and Google Cloud Storage as a mirror is not supported for now.") } } if conf.ShareHandler && conf.ShareHandlerPath == "" { conf.ShareHandlerPath = "/share/" } prefixesParams := &configPrefixesParams{ secretRing: conf.IdentitySecretRing, keyId: conf.Identity, indexerPath: indexerPath, blobPath: conf.BlobPath, packBlobs: conf.PackBlobs, searchOwner: blob.SHA1FromString(armoredPublicKey), shareHandlerPath: conf.ShareHandlerPath, flickr: conf.Flickr, memoryIndex: conf.MemoryIndex.Get(), } prefixes := genLowLevelPrefixes(prefixesParams, conf.OwnerName) var cacheDir string if nolocaldisk { // Whether camlistored is run from EC2 or not, we use // a temp dir as the cache when primary storage is S3. // TODO(mpl): s3CacheBucket // See http://code.google.com/p/camlistore/issues/detail?id=85 cacheDir = filepath.Join(tempDir(), "camli-cache") } else { cacheDir = filepath.Join(conf.BlobPath, "cache") } if !noMkdir { if err := os.MkdirAll(cacheDir, 0700); err != nil { return nil, fmt.Errorf("Could not create blobs cache dir %s: %v", cacheDir, err) } } published := []interface{}{} if len(conf.Publish) > 0 { if !runIndex { return nil, fmt.Errorf("publishing requires an index") } published, err = addPublishedConfig(prefixes, conf.Publish, conf.SourceRoot) if err != nil { return nil, fmt.Errorf("Could not generate config for published: %v", err) } } if runIndex { addUIConfig(prefixesParams, prefixes, "/ui/", published, conf.SourceRoot) } if conf.MySQL != "" { addMySQLConfig(prefixes, conf.DBName, conf.MySQL) } if conf.PostgreSQL != "" { addPostgresConfig(prefixes, conf.DBName, conf.PostgreSQL) } if conf.Mongo != "" { addMongoConfig(prefixes, conf.DBName, conf.Mongo) } if conf.SQLite != "" { addSQLiteConfig(prefixes, conf.SQLite) } if conf.KVFile != "" { addKVConfig(prefixes, conf.KVFile) } if conf.S3 != "" { if err := addS3Config(prefixesParams, prefixes, conf.S3); err != nil { return nil, err } } if conf.GoogleDrive != "" { if err := addGoogleDriveConfig(prefixes, conf.GoogleDrive); err != nil { return nil, err } } if conf.GoogleCloudStorage != "" { if err := addGoogleCloudStorageConfig(prefixes, conf.GoogleCloudStorage); err != nil { return nil, err } } obj["prefixes"] = (map[string]interface{})(prefixes) lowLevelConf = &Config{ Obj: obj, } return lowLevelConf, nil }
// genLowLevelConfig returns a low-level config from a high-level config. func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { var ( baseURL = conf.OptionalString("baseURL", "") listen = conf.OptionalString("listen", "") auth = conf.RequiredString("auth") keyId = conf.RequiredString("identity") secretRing = conf.RequiredString("identitySecretRing") tlsOn = conf.OptionalBool("https", false) tlsCert = conf.OptionalString("HTTPSCertFile", "") tlsKey = conf.OptionalString("HTTPSKeyFile", "") // Blob storage options blobPath = conf.OptionalString("blobPath", "") packBlobs = conf.OptionalBool("packBlobs", false) // use diskpacked instead of the default filestorage s3 = conf.OptionalString("s3", "") // "access_key_id:secret_access_key:bucket[:hostname]" googlecloudstorage = conf.OptionalString("googlecloudstorage", "") // "clientId:clientSecret:refreshToken:bucket" googledrive = conf.OptionalString("googledrive", "") // "clientId:clientSecret:refreshToken:parentId" swift = conf.OptionalString("swift", "") // "tenant:secret:container:auth_url" // Enable the share handler. If true, and shareHandlerPath is empty, // then shareHandlerPath defaults to "/share/". shareHandler = conf.OptionalBool("shareHandler", false) // URL prefix for the share handler. If set, overrides shareHandler. shareHandlerPath = conf.OptionalString("shareHandlerPath", "") // Index options memoryIndex = conf.OptionalBool("memoryIndex", true) // copy disk-based index to memory on start-up runIndex = conf.OptionalBool("runIndex", true) // if false: no search, no UI, etc. dbname = conf.OptionalString("dbname", "") // for mysql, postgres, mongo mysql = conf.OptionalString("mysql", "") postgres = conf.OptionalString("postgres", "") mongo = conf.OptionalString("mongo", "") sqliteFile = conf.OptionalString("sqlite", "") kvFile = conf.OptionalString("kvIndexFile", "") // Importer options flickr = conf.OptionalString("flickr", "") _ = conf.OptionalList("replicateTo") publish = conf.OptionalObject("publish") // alternative source tree, to override the embedded ui and/or closure resources. // If non empty, the ui files will be expected at // sourceRoot + "/server/camlistored/ui" and the closure library at // sourceRoot + "/third_party/closure/lib" // Also used by the publish handler. sourceRoot = conf.OptionalString("sourceRoot", "") ownerName = conf.OptionalString("ownerName", "") ) if err := conf.Validate(); err != nil { return nil, err } obj := jsonconfig.Obj{} if tlsOn { if (tlsCert != "") != (tlsKey != "") { return nil, errors.New("Must set both TLSCertFile and TLSKeyFile (or neither to generate a self-signed cert)") } if tlsCert != "" { obj["TLSCertFile"] = tlsCert obj["TLSKeyFile"] = tlsKey } else { obj["TLSCertFile"] = osutil.DefaultTLSCert() obj["TLSKeyFile"] = osutil.DefaultTLSKey() } } if baseURL != "" { u, err := url.Parse(baseURL) if err != nil { return nil, fmt.Errorf("Error parsing baseURL %q as a URL: %v", baseURL, err) } if u.Path != "" && u.Path != "/" { return nil, fmt.Errorf("baseURL can't have a path, only a scheme, host, and optional port.") } u.Path = "" obj["baseURL"] = u.String() } if listen != "" { obj["listen"] = listen } obj["https"] = tlsOn obj["auth"] = auth username := "" if dbname == "" { username = osutil.Username() if username == "" { return nil, fmt.Errorf("USER (USERNAME on windows) env var not set; needed to define dbname") } dbname = "camli" + username } var indexerPath string numIndexers := numSet(mongo, mysql, postgres, sqliteFile, kvFile) switch { case runIndex && numIndexers == 0: return nil, fmt.Errorf("Unless runIndex is set to false, you must specify an index option (kvIndexFile, mongo, mysql, postgres, sqlite).") case runIndex && numIndexers != 1: return nil, fmt.Errorf("With runIndex set true, you can only pick exactly one indexer (mongo, mysql, postgres, sqlite).") case !runIndex && numIndexers != 0: return nil, fmt.Errorf("With runIndex disabled, you can't specify any of mongo, mysql, postgres, sqlite.") case mysql != "": indexerPath = "/index-mysql/" case postgres != "": indexerPath = "/index-postgres/" case mongo != "": indexerPath = "/index-mongo/" case sqliteFile != "": indexerPath = "/index-sqlite/" case kvFile != "": indexerPath = "/index-kv/" } entity, err := jsonsign.EntityFromSecring(keyId, secretRing) if err != nil { return nil, err } armoredPublicKey, err := jsonsign.ArmoredPublicKey(entity) if err != nil { return nil, err } nolocaldisk := blobPath == "" if nolocaldisk { if s3 == "" && googlecloudstorage == "" { return nil, errors.New("You need at least one of blobPath (for localdisk) or s3 or googlecloudstorage configured for a blobserver.") } if s3 != "" && googlecloudstorage != "" { return nil, errors.New("Using S3 as a primary storage and Google Cloud Storage as a mirror is not supported for now.") } } if shareHandler && shareHandlerPath == "" { shareHandlerPath = "/share/" } prefixesParams := &configPrefixesParams{ secretRing: secretRing, keyId: keyId, indexerPath: indexerPath, blobPath: blobPath, packBlobs: packBlobs, searchOwner: blob.SHA1FromString(armoredPublicKey), shareHandlerPath: shareHandlerPath, flickr: flickr, memoryIndex: memoryIndex, } prefixes := genLowLevelPrefixes(prefixesParams, ownerName) var cacheDir string if nolocaldisk { // Whether camlistored is run from EC2 or not, we use // a temp dir as the cache when primary storage is S3. // TODO(mpl): s3CacheBucket // See http://code.google.com/p/camlistore/issues/detail?id=85 cacheDir = filepath.Join(tempDir(), "camli-cache") } else { cacheDir = filepath.Join(blobPath, "cache") } if !noMkdir { if err := os.MkdirAll(cacheDir, 0700); err != nil { return nil, fmt.Errorf("Could not create blobs cache dir %s: %v", cacheDir, err) } } published := []interface{}{} if len(publish) > 0 { if !runIndex { return nil, fmt.Errorf("publishing requires an index") } published, err = addPublishedConfig(prefixes, publish, sourceRoot) if err != nil { return nil, fmt.Errorf("Could not generate config for published: %v", err) } } if runIndex { addUIConfig(prefixesParams, prefixes, "/ui/", published, sourceRoot) } if mysql != "" { addMySQLConfig(prefixes, dbname, mysql) } if postgres != "" { addPostgresConfig(prefixes, dbname, postgres) } if mongo != "" { addMongoConfig(prefixes, dbname, mongo) } if sqliteFile != "" { addSQLiteConfig(prefixes, sqliteFile) } if kvFile != "" { addKVConfig(prefixes, kvFile) } if s3 != "" { if err := addS3Config(prefixesParams, prefixes, s3); err != nil { return nil, err } } if googledrive != "" { if err := addGoogleDriveConfig(prefixes, googledrive); err != nil { return nil, err } } if googlecloudstorage != "" { if err := addGoogleCloudStorageConfig(prefixes, googlecloudstorage); err != nil { return nil, err } } if swift != "" { if err := addSwiftConfig(prefixesParams, prefixes, swift); err != nil { return nil, err } } obj["prefixes"] = (map[string]interface{})(prefixes) lowLevelConf = &Config{ Obj: obj, configPath: conf.configPath, } return lowLevelConf, nil }
compute "google.golang.org/api/compute/v1" ) const ( // duration after which a progress state is dropped from the progress map progressStateExpiration = 7 * 24 * time.Hour cookieExpiration = 24 * time.Hour ) var ( helpGenCert = `A self-signed HTTPS certificate will be generated for the chosen domain name (or for "localhost" if left blank) and set as the default. You will be able to set another HTTPS certificate for Camlistore afterwards.` helpDomainName = "http://en.wikipedia.org/wiki/Fully_qualified_domain_name" helpMachineTypes = "https://cloud.google.com/compute/docs/machine-types" helpZones = "https://cloud.google.com/compute/docs/zones#available" helpSSH = "https://cloud.google.com/compute/docs/console#sshkeys" helpChangeCert = `in your project console, navigate to "Storage", "Cloud Storage", "Storage browser", "%s-camlistore", "config". Delete "` + filepath.Base(osutil.DefaultTLSCert()) + `", "` + filepath.Base(osutil.DefaultTLSKey()) + `", and replace them by uploading your own files (with the same names).` formDefaults = map[string]string{ "name": InstanceName, "machine": Machine, "zone": Zone, } machineValues = []string{ "g1-small", "n1-highcpu-2", } backupZones = map[string][]string{ "us-central1": []string{"-a", "-b", "-f"}, "europe-west1": []string{"-b", "-c", "-d"}, "asia-east1": []string{"-a", "-b", "-c"},
// 1) We do not want to force the user to buy a cert. // 2) We still want our client (camput) to be able to // verify the cert's authenticity. // 3) We want to avoid MITM attacks and warnings in // the browser. // Using a simple self-signed won't do because of 3), // as Chrome offers no way to set a self-signed as // trusted when importing it. (same on android). // We could have created a self-signed CA (that we // would import in the browsers) and create another // cert (signed by that CA) which would be the one // used in camlistore. // We're doing even simpler: create a self-signed // CA and directly use it as a self-signed cert // (and install it as a CA in the browsers). // 2) is satisfied by doing our own checks, // See pkg/client func genSelfTLS(listen string) error { priv, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { return fmt.Errorf("failed to generate private key: %s", err) } now := time.Now() hostname, _, err := net.SplitHostPort(listen) if err != nil { return fmt.Errorf("splitting listen failed: %q", err) } // TODO(mpl): if no host is specified in the listening address // (e.g ":3179") we'll end up in this case, and the self-signed // will have "localhost" as a CommonName. But I don't think // there's anything we can do about it. Maybe warn... if hostname == "" { hostname = "localhost" } template := x509.Certificate{ SerialNumber: new(big.Int).SetInt64(0), Subject: pkix.Name{ CommonName: hostname, Organization: []string{hostname}, }, NotBefore: now.Add(-5 * time.Minute).UTC(), NotAfter: now.AddDate(1, 0, 0).UTC(), SubjectKeyId: []byte{1, 2, 3, 4}, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, IsCA: true, BasicConstraintsValid: true, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return fmt.Errorf("Failed to create certificate: %s", err) } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() certOut, err := os.Create(defCert) if err != nil { return fmt.Errorf("failed to open %s for writing: %s", defCert, err) } pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certOut.Close() log.Printf("written %s\n", defCert) cert, err := x509.ParseCertificate(derBytes) if err != nil { return fmt.Errorf("Failed to parse certificate: %v", err) } sig := misc.SHA256Prefix(cert.Raw) hint := "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n" + `"trustedCerts": ["` + sig + `"],` log.Printf(hint) keyOut, err := os.OpenFile(defKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("failed to open %s for writing:", defKey, err) } pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) keyOut.Close() log.Printf("written %s\n", defKey) return nil }
configDir = "config" ConsoleURL = "https://console.developers.google.com" HelpCreateProject = "Go to " + ConsoleURL + " to create a new Google Cloud project" HelpEnableAPIs = `Enable the project APIs: in your project console, navigate to "APIs and auth", "APIs". In the list, enable "Google Cloud Storage", "Google Cloud Storage JSON API", and "Google Compute Engine".` helpDeleteInstance = `To delete an existing Compute Engine instance: in your project console, navigate to "Compute", "Compute Engine", and "VM instances". Select your instance and click "Delete".` HelpManageSSHKeys = `To manage/add SSH keys: in your project console, navigate to "Compute", "Compute Engine", and "VM instances". Click on your instance name. Scroll down to the SSH Keys section.` HelpManageHTTPCreds = `To change your login and password: in your project console, navigate to "Compute", "Compute Engine", and "VM instances". Click on your instance name. Set camlistore-username and/or camlistore-password in the custom metadata section.` ) var ( // Verbose enables more info to be printed. Verbose bool // HTTPS certificate file name certFilename = filepath.Base(osutil.DefaultTLSCert()) // HTTPS key name keyFilename = filepath.Base(osutil.DefaultTLSKey()) ) // NewOAuthConfig returns an OAuth configuration template. func NewOAuthConfig(clientID, clientSecret string) *oauth2.Config { return &oauth2.Config{ Scopes: []string{ logging.Scope, compute.DevstorageFull_controlScope, compute.ComputeScope, "https://www.googleapis.com/auth/sqlservice", "https://www.googleapis.com/auth/sqlservice.admin", }, Endpoint: google.Endpoint,
func (b *lowBuilder) build() (*Config, error) { conf, low := b.high, b.low if conf.HTTPS { if (conf.HTTPSCert != "") != (conf.HTTPSKey != "") { return nil, errors.New("Must set both httpsCert and httpsKey (or neither to generate a self-signed cert)") } if conf.HTTPSCert != "" { low["httpsCert"] = conf.HTTPSCert low["httpsKey"] = conf.HTTPSKey } else { low["httpsCert"] = osutil.DefaultTLSCert() low["httpsKey"] = osutil.DefaultTLSKey() } } if conf.BaseURL != "" { u, err := url.Parse(conf.BaseURL) if err != nil { return nil, fmt.Errorf("Error parsing baseURL %q as a URL: %v", conf.BaseURL, err) } if u.Path != "" && u.Path != "/" { return nil, fmt.Errorf("baseURL can't have a path, only a scheme, host, and optional port.") } u.Path = "" low["baseURL"] = u.String() } if conf.Listen != "" { low["listen"] = conf.Listen } if conf.PackBlobs && conf.PackRelated { return nil, errors.New("can't use both packBlobs (for 'diskpacked') and packRelated (for 'blobpacked')") } low["https"] = conf.HTTPS low["auth"] = conf.Auth numIndexers := numSet(conf.LevelDB, conf.Mongo, conf.MySQL, conf.PostgreSQL, conf.SQLite, conf.KVFile, conf.MemoryIndex) switch { case b.runIndex() && numIndexers == 0: return nil, fmt.Errorf("Unless runIndex is set to false, you must specify an index option (kvIndexFile, leveldb, mongo, mysql, postgres, sqlite, memoryIndex).") case b.runIndex() && numIndexers != 1: return nil, fmt.Errorf("With runIndex set true, you can only pick exactly one indexer (mongo, mysql, postgres, sqlite, kvIndexFile, leveldb, memoryIndex).") case !b.runIndex() && numIndexers != 0: return nil, fmt.Errorf("With runIndex disabled, you can't specify any of mongo, mysql, postgres, sqlite.") } if conf.Identity == "" { return nil, errors.New("no 'identity' in server config") } noLocalDisk := conf.BlobPath == "" if noLocalDisk { if !conf.MemoryStorage && conf.S3 == "" && conf.GoogleCloudStorage == "" { return nil, errors.New("Unless memoryStorage is set, you must specify at least one storage option for your blobserver (blobPath (for localdisk), s3, googlecloudstorage).") } if !conf.MemoryStorage && conf.S3 != "" && conf.GoogleCloudStorage != "" { return nil, errors.New("Using S3 as a primary storage and Google Cloud Storage as a mirror is not supported for now.") } } if conf.ShareHandler && conf.ShareHandlerPath == "" { conf.ShareHandlerPath = "/share/" } if conf.MemoryStorage { noMkdir = true if conf.BlobPath != "" { return nil, errors.New("memoryStorage and blobPath are mutually exclusive.") } if conf.PackRelated { return nil, errors.New("memoryStorage doesn't support packRelated.") } } if err := b.genLowLevelPrefixes(); err != nil { return nil, err } var cacheDir string if noLocalDisk { // Whether camlistored is run from EC2 or not, we use // a temp dir as the cache when primary storage is S3. // TODO(mpl): s3CacheBucket // See https://camlistore.org/issue/85 cacheDir = filepath.Join(tempDir(), "camli-cache") } else { cacheDir = filepath.Join(conf.BlobPath, "cache") } if !noMkdir { if err := os.MkdirAll(cacheDir, 0700); err != nil { return nil, fmt.Errorf("Could not create blobs cache dir %s: %v", cacheDir, err) } } if len(conf.Publish) > 0 { if !b.runIndex() { return nil, fmt.Errorf("publishing requires an index") } var tlsO *tlsOpts httpsCert, ok1 := low["httpsCert"].(string) httpsKey, ok2 := low["httpsKey"].(string) if ok1 && ok2 { tlsO = &tlsOpts{ httpsCert: httpsCert, httpsKey: httpsKey, } } if err := b.addPublishedConfig(tlsO); err != nil { return nil, fmt.Errorf("Could not generate config for published: %v", err) } } if b.runIndex() { b.addUIConfig() sto, err := b.sortedStorage("index") if err != nil { return nil, err } b.addPrefix("/index/", "storage-index", args{ "blobSource": "/bs/", "storage": sto, }) } if conf.S3 != "" { if err := b.addS3Config(conf.S3); err != nil { return nil, err } } if conf.GoogleDrive != "" { if err := b.addGoogleDriveConfig(conf.GoogleDrive); err != nil { return nil, err } } if conf.GoogleCloudStorage != "" { if err := b.addGoogleCloudStorageConfig(conf.GoogleCloudStorage); err != nil { return nil, err } } return &Config{Obj: b.low}, nil }
// If cert/key files are specified, and found, use them. // If cert/key files are specified, not found, and the default values, generate // them (self-signed CA used as a cert), and use them. // If cert/key files are not specified, use Let's Encrypt. func setupTLS(ws *webserver.Server, config *serverinit.Config, hostname string) { cert, key := config.OptionalString("httpsCert", ""), config.OptionalString("httpsKey", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { exitf("httpsCert and httpsKey must both be either present or absent") } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() const hint = "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n\"trustedCerts\": [\"%s\"]," if cert == defCert && key == defKey { _, err1 := wkfs.Stat(cert) _, err2 := wkfs.Stat(key) if err1 != nil || err2 != nil { if os.IsNotExist(err1) || os.IsNotExist(err2) { sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self-signed TLS cert: %q", err) } log.Printf(hint, sig) } else { exitf("Could not stat cert or key: %q, %q", err1, err2) } } } if cert == "" && key == "" { // Use Let's Encrypt if no files are specified, and we have a usable hostname. if netutil.IsFQDN(hostname) { m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(hostname), Cache: autocert.DirCache(osutil.DefaultLetsEncryptCache()), } log.Print("TLS enabled, with Let's Encrypt") ws.SetTLS(webserver.TLSSetup{ CertManager: m.GetCertificate, }) return } // Otherwise generate new certificates sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self signed creds: %q", err) } log.Printf(hint, sig) cert = defCert key = defKey } data, err := wkfs.ReadFile(cert) if err != nil { exitf("Failed to read pem certificate: %s", err) } sig, err := httputil.CertFingerprint(data) if err != nil { exitf("certificate error: %v", err) } log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig) ws.SetTLS(webserver.TLSSetup{ CertFile: cert, KeyFile: key, }) }