PageRenderTime 67ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/github.com/getlantern/flashlight/config/config.go

https://gitlab.com/zanderwong/lantern
Go | 374 lines | 270 code | 50 blank | 54 comment | 66 complexity | 61fffb33d8b38362998d3e1dc233f2dc MD5 | raw file
  1. package config
  2. import (
  3. "crypto/x509"
  4. "encoding/base64"
  5. "fmt"
  6. "net/url"
  7. "os"
  8. "path/filepath"
  9. "regexp"
  10. "sort"
  11. "time"
  12. "code.google.com/p/go-uuid/uuid"
  13. "github.com/getlantern/appdir"
  14. "github.com/getlantern/detour"
  15. "github.com/getlantern/fronted"
  16. "github.com/getlantern/golog"
  17. "github.com/getlantern/keyman"
  18. "github.com/getlantern/proxiedsites"
  19. "github.com/getlantern/yaml"
  20. "github.com/getlantern/yamlconf"
  21. "github.com/getlantern/flashlight/client"
  22. "github.com/getlantern/flashlight/proxied"
  23. )
  24. const (
  25. cloudfront = "cloudfront"
  26. // DefaultUpdateServerURL is the URL to fetch updates from.
  27. DefaultUpdateServerURL = "https://update.getlantern.org"
  28. )
  29. var (
  30. log = golog.LoggerFor("flashlight.config")
  31. m *yamlconf.Manager
  32. r = regexp.MustCompile("\\d+\\.\\d+")
  33. )
  34. // Config contains general configuration for Lantern either set globally via
  35. // the cloud, in command line flags, or in local customizations during
  36. // development.
  37. type Config struct {
  38. configDir string
  39. Version int
  40. CloudConfig string
  41. CloudConfigCA string
  42. FrontedCloudConfig string
  43. CPUProfile string
  44. MemProfile string
  45. UpdateServerURL string
  46. Client *client.ClientConfig
  47. ProxiedSites *proxiedsites.Config // List of proxied site domains that get routed through Lantern rather than accessed directly
  48. TrustedCAs []*fronted.CA
  49. }
  50. // Fetcher is an interface for fetching config updates.
  51. type Fetcher interface {
  52. pollForConfig(ycfg yamlconf.Config, sticky bool) (mutate func(yamlconf.Config) error, waitTime time.Duration, err error)
  53. }
  54. // StartPolling starts the process of polling for new configuration files.
  55. func StartPolling() {
  56. // Force detour to whitelist chained domain
  57. u, err := url.Parse(chainedCloudConfigURL)
  58. if err != nil {
  59. log.Fatalf("Unable to parse chained cloud config URL: %v", err)
  60. }
  61. detour.ForceWhitelist(u.Host)
  62. // No-op if already started.
  63. m.StartPolling()
  64. }
  65. // validateConfig checks whether the given config is valid and returns an error
  66. // if it isn't.
  67. func validateConfig(_cfg yamlconf.Config) error {
  68. cfg, ok := _cfg.(*Config)
  69. if !ok {
  70. return fmt.Errorf("Config is not a flashlight config!")
  71. }
  72. nc := len(cfg.Client.ChainedServers)
  73. log.Debugf("Found %v chained servers in config on disk", nc)
  74. for _, v := range cfg.Client.ChainedServers {
  75. log.Debugf("chained server: %v", v)
  76. }
  77. // The config will have more than one but fewer than 10 chained servers
  78. // if it has been given a custom config with a custom chained server
  79. // list
  80. if nc <= 0 || nc > 10 {
  81. return fmt.Errorf("Inappropriate number of custom chained servers found: %d", nc)
  82. }
  83. return nil
  84. }
  85. func majorVersion(version string) string {
  86. return r.FindString(version)
  87. }
  88. // Init initializes the configuration system.
  89. //
  90. // version - the version of lantern
  91. // stickyConfig - if true, we ignore cloud updates
  92. // flags - map of flags (generally from command-line) that always get applied
  93. // to the config.
  94. func Init(userConfig UserConfig, version string, configDir string, stickyConfig bool, flags map[string]interface{}) (*Config, error) {
  95. // Request the config via either chained servers or direct fronted servers.
  96. cf := proxied.ParallelPreferChained()
  97. fetcher := NewFetcher(userConfig, cf)
  98. file := "lantern-" + version + ".yaml"
  99. _, configPath, err := inConfigDir(configDir, file)
  100. if err != nil {
  101. log.Errorf("Could not get config path? %v", err)
  102. return nil, err
  103. }
  104. m = &yamlconf.Manager{
  105. FilePath: configPath,
  106. ValidateConfig: validateConfig,
  107. DefaultConfig: MakeInitialConfig,
  108. EmptyConfig: func() yamlconf.Config {
  109. return &Config{configDir: configDir}
  110. },
  111. PerSessionSetup: func(ycfg yamlconf.Config) error {
  112. cfg := ycfg.(*Config)
  113. return cfg.applyFlags(flags)
  114. },
  115. CustomPoll: func(ycfg yamlconf.Config) (mutate func(yamlconf.Config) error, waitTime time.Duration, err error) {
  116. return fetcher.pollForConfig(ycfg, stickyConfig)
  117. },
  118. // Obfuscate on-disk contents of YAML file
  119. Obfuscate: flags["readableconfig"] == nil || !flags["readableconfig"].(bool),
  120. }
  121. initial, err := m.Init()
  122. var cfg *Config
  123. if err != nil {
  124. log.Errorf("Error initializing config: %v", err)
  125. } else {
  126. cfg = initial.(*Config)
  127. }
  128. log.Debug("Returning config")
  129. return cfg, err
  130. }
  131. // Run runs the configuration system.
  132. func Run(updateHandler func(updated *Config)) error {
  133. for {
  134. next := m.Next()
  135. nextCfg := next.(*Config)
  136. updateHandler(nextCfg)
  137. }
  138. }
  139. // Update updates the configuration using the given mutator function.
  140. func Update(mutate func(cfg *Config) error) error {
  141. return m.Update(func(ycfg yamlconf.Config) error {
  142. return mutate(ycfg.(*Config))
  143. })
  144. }
  145. func inConfigDir(configDir string, filename string) (string, string, error) {
  146. cdir := configDir
  147. if cdir == "" {
  148. cdir = appdir.General("Lantern")
  149. }
  150. log.Debugf("Using config dir %v", cdir)
  151. if _, err := os.Stat(cdir); err != nil {
  152. if os.IsNotExist(err) {
  153. // Create config dir
  154. if err := os.MkdirAll(cdir, 0750); err != nil {
  155. return "", "", fmt.Errorf("Unable to create configdir at %s: %s", cdir, err)
  156. }
  157. }
  158. }
  159. return cdir, filepath.Join(cdir, filename), nil
  160. }
  161. func (cfg *Config) GetTrustedCACerts() (pool *x509.CertPool, err error) {
  162. certs := make([]string, 0, len(cfg.TrustedCAs))
  163. for _, ca := range cfg.TrustedCAs {
  164. certs = append(certs, ca.Cert)
  165. }
  166. pool, err = keyman.PoolContainingCerts(certs...)
  167. if err != nil {
  168. log.Errorf("Could not create pool %v", err)
  169. }
  170. return
  171. }
  172. // GetVersion implements the method from interface yamlconf.Config
  173. func (cfg *Config) GetVersion() int {
  174. return cfg.Version
  175. }
  176. // SetVersion implements the method from interface yamlconf.Config
  177. func (cfg *Config) SetVersion(version int) {
  178. cfg.Version = version
  179. }
  180. // applyFlags updates this Config from any command-line flags that were passed
  181. // in.
  182. func (updated *Config) applyFlags(flags map[string]interface{}) error {
  183. if updated.Client == nil {
  184. updated.Client = &client.ClientConfig{}
  185. }
  186. var visitErr error
  187. // Visit all flags that have been set and copy to config
  188. for key, value := range flags {
  189. switch key {
  190. // General
  191. case "cloudconfig":
  192. updated.CloudConfig = value.(string)
  193. case "cloudconfigca":
  194. updated.CloudConfigCA = value.(string)
  195. case "frontedconfig":
  196. updated.FrontedCloudConfig = value.(string)
  197. case "instanceid":
  198. updated.Client.DeviceID = value.(string)
  199. case "cpuprofile":
  200. updated.CPUProfile = value.(string)
  201. case "memprofile":
  202. updated.MemProfile = value.(string)
  203. }
  204. }
  205. if visitErr != nil {
  206. return visitErr
  207. }
  208. return nil
  209. }
  210. // ApplyDefaults implements the method from interface yamlconf.Config
  211. //
  212. // ApplyDefaults populates default values on a Config to make sure that we have
  213. // a minimum viable config for running. As new settings are added to
  214. // flashlight, this function should be updated to provide sensible defaults for
  215. // those settings.
  216. func (cfg *Config) ApplyDefaults() {
  217. if cfg.UpdateServerURL == "" {
  218. cfg.UpdateServerURL = "https://update.getlantern.org"
  219. }
  220. if cfg.CloudConfig == "" {
  221. cfg.CloudConfig = chainedCloudConfigURL
  222. }
  223. if cfg.FrontedCloudConfig == "" {
  224. cfg.FrontedCloudConfig = frontedCloudConfigURL
  225. }
  226. if cfg.Client == nil {
  227. cfg.Client = &client.ClientConfig{}
  228. }
  229. cfg.applyClientDefaults()
  230. if cfg.ProxiedSites == nil {
  231. log.Debugf("Adding empty proxiedsites")
  232. cfg.ProxiedSites = &proxiedsites.Config{
  233. Delta: &proxiedsites.Delta{
  234. Additions: []string{},
  235. Deletions: []string{},
  236. },
  237. Cloud: []string{},
  238. }
  239. }
  240. if cfg.ProxiedSites.Cloud == nil || len(cfg.ProxiedSites.Cloud) == 0 {
  241. log.Debugf("Loading default cloud proxiedsites")
  242. cfg.ProxiedSites.Cloud = defaultProxiedSites
  243. }
  244. if cfg.TrustedCAs == nil || len(cfg.TrustedCAs) == 0 {
  245. cfg.TrustedCAs = fronted.DefaultTrustedCAs
  246. }
  247. }
  248. func (cfg *Config) applyClientDefaults() {
  249. // Make sure we always have at least one masquerade set
  250. if cfg.Client.MasqueradeSets == nil {
  251. cfg.Client.MasqueradeSets = make(map[string][]*fronted.Masquerade)
  252. }
  253. if len(cfg.Client.MasqueradeSets) == 0 {
  254. cfg.Client.MasqueradeSets[cloudfront] = fronted.DefaultCloudfrontMasquerades
  255. }
  256. // Always make sure we have a map of ChainedServers
  257. if cfg.Client.ChainedServers == nil {
  258. cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo)
  259. }
  260. // Make sure we always have at least one server
  261. if len(cfg.Client.ChainedServers) == 0 {
  262. cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo, len(fallbacks))
  263. for key, fb := range fallbacks {
  264. cfg.Client.ChainedServers[key] = fb
  265. }
  266. }
  267. if cfg.Client.ProxiedCONNECTPorts == nil {
  268. cfg.Client.ProxiedCONNECTPorts = []int{
  269. // Standard HTTP(S) ports
  270. 80, 443,
  271. // Common unprivileged HTTP(S) ports
  272. 8080, 8443,
  273. // XMPP
  274. 5222, 5223, 5224,
  275. // Android
  276. 5228, 5229,
  277. // udpgw
  278. 7300,
  279. // Google Hangouts TCP Ports (see https://support.google.com/a/answer/1279090?hl=en)
  280. 19305, 19306, 19307, 19308, 19309,
  281. }
  282. }
  283. if cfg.Client.DeviceID == "" {
  284. // There is no true privacy or security in instance ID. For that, we rely on
  285. // transport security. Hashing MAC would buy us nothing, since the space of
  286. // MACs is trivially mapped, especially since the salt would be known
  287. cfg.Client.DeviceID = base64.StdEncoding.EncodeToString(uuid.NodeID())
  288. }
  289. }
  290. // updateFrom creates a new Config by 'merging' the given yaml into this Config.
  291. // The masquerade sets, the collections of servers, and the trusted CAs in the
  292. // update yaml completely replace the ones in the original Config.
  293. func (updated *Config) updateFrom(updateBytes []byte) error {
  294. // XXX: does this need a mutex, along with everyone that uses the config?
  295. oldDeviceID := updated.Client.DeviceID
  296. oldChainedServers := updated.Client.ChainedServers
  297. oldMasqueradeSets := updated.Client.MasqueradeSets
  298. oldTrustedCAs := updated.TrustedCAs
  299. updated.Client.ChainedServers = map[string]*client.ChainedServerInfo{}
  300. updated.Client.MasqueradeSets = map[string][]*fronted.Masquerade{}
  301. updated.TrustedCAs = []*fronted.CA{}
  302. err := yaml.Unmarshal(updateBytes, updated)
  303. if err != nil {
  304. updated.Client.ChainedServers = oldChainedServers
  305. updated.Client.MasqueradeSets = oldMasqueradeSets
  306. updated.TrustedCAs = oldTrustedCAs
  307. return fmt.Errorf("Unable to unmarshal YAML for update: %s", err)
  308. }
  309. // Deduplicate global proxiedsites
  310. if len(updated.ProxiedSites.Cloud) > 0 {
  311. wlDomains := make(map[string]bool)
  312. for _, domain := range updated.ProxiedSites.Cloud {
  313. wlDomains[domain] = true
  314. }
  315. updated.ProxiedSites.Cloud = make([]string, 0, len(wlDomains))
  316. for domain := range wlDomains {
  317. updated.ProxiedSites.Cloud = append(updated.ProxiedSites.Cloud, domain)
  318. }
  319. sort.Strings(updated.ProxiedSites.Cloud)
  320. }
  321. // Ignore DeviceID from yaml
  322. updated.Client.DeviceID = oldDeviceID
  323. return nil
  324. }