// TransportForConfig returns a transport for the client, setting the correct // Proxy, Dial, and TLSClientConfig if needed. It does not mutate c. // It is the caller's responsibility to then use that transport to set // the client's httpClient with SetHTTPClient. func (c *Client) TransportForConfig(tc *TransportConfig) http.RoundTripper { if c == nil { return nil } tlsConfig, err := c.TLSConfig() if err != nil { log.Fatalf("Error while configuring TLS for client: %v", err) } var transport http.RoundTripper proxy := http.ProxyFromEnvironment if tc != nil && tc.Proxy != nil { proxy = tc.Proxy } transport = &http.Transport{ Dial: c.DialFunc(), TLSClientConfig: tlsConfig, Proxy: proxy, } httpStats := &httputil.StatsTransport{ Transport: transport, } if tc != nil { httpStats.VerboseLog = tc.Verbose } transport = httpStats if android.OnAndroid() { transport = &android.StatsTransport{transport} } return transport }
func parseConfig() { if android.OnAndroid() { return } configPath := osutil.UserClientConfigPath() if _, err := os.Stat(configPath); os.IsNotExist(err) { errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } conf, err := jsonconfig.ReadFile(configPath) if err != nil { log.Fatal(err.Error()) } cfg := jsonconfig.Obj(conf) config = &clientConfig{ auth: cfg.OptionalString("auth", ""), server: cfg.OptionalString("server", ""), identity: cfg.OptionalString("identity", ""), identitySecretRing: cfg.OptionalString("identitySecretRing", osutil.IdentitySecretRing()), trustedCerts: cfg.OptionalList("trustedCerts"), ignoredFiles: cfg.OptionalList("ignoredFiles"), } if err := cfg.Validate(); err != nil { printConfigChangeHelp(cfg) log.Fatalf("Error in config file: %v", err) } }
// SetupAuth sets the client's authMode. It tries from the environment first if we're on android or in dev mode, and then from the client configuration. func (c *Client) SetupAuth() error { // env var takes precedence, but only if we're in dev mode or on android. // Too risky otherwise. if android.OnAndroid() || os.Getenv("CAMLI_DEV_CAMLI_ROOT") != "" { authMode, err := auth.FromEnv() if err == nil { c.authMode = authMode return nil } if err != auth.ErrNoAuth { return fmt.Errorf("Could not set up auth from env var CAMLI_AUTH: %v", err) } } if c.server == "" { return fmt.Errorf("No server defined for this client: can not set up auth.") } authConf := serverAuth(c.server) if authConf == "" { c.authErr = fmt.Errorf("could not find auth key for server %q in config, defaulting to no auth", c.server) c.authMode = auth.None{} return nil } var err error c.authMode, err = auth.FromConfig(authConf) return err }
// DialFunc returns the adequate dial function, depending on // whether SSL is required, the client's config has some trusted // certs, and we're on android. // If the client's config has some trusted certs, the server's // certificate will be checked against those in the config after // the TLS handshake. func (c *Client) DialFunc() func(network, addr string) (net.Conn, error) { trustedCerts := c.getTrustedCerts() if !c.useTLS() || (!c.InsecureTLS && len(trustedCerts) == 0) { // No TLS, or TLS with normal/full verification if android.OnAndroid() { return func(network, addr string) (net.Conn, error) { return android.Dial(network, addr) } } return nil } return func(network, addr string) (net.Conn, error) { var conn *tls.Conn var err error if android.OnAndroid() { con, err := android.Dial(network, addr) if err != nil { return nil, err } conn = tls.Client(con, &tls.Config{InsecureSkipVerify: true}) if err = conn.Handshake(); err != nil { return nil, err } } else { conn, err = tls.Dial(network, addr, &tls.Config{InsecureSkipVerify: true}) if err != nil { return nil, err } } if c.InsecureTLS { return conn, nil } certs := conn.ConnectionState().PeerCertificates if certs == nil || len(certs) < 1 { return nil, errors.New("Could not get server's certificate from the TLS connection.") } sig := misc.SHA1Prefix(certs[0].Raw) for _, v := range trustedCerts { if v == sig { return conn, nil } } return nil, fmt.Errorf("Server's certificate %v is not in the trusted list", sig) } }
// TLSConfig returns the correct tls.Config depending on whether // SSL is required, the client's config has some trusted certs, // and we're on android. func (c *Client) TLSConfig() (*tls.Config, error) { if !c.useTLS() { return nil, nil } trustedCerts := c.getTrustedCerts() if len(trustedCerts) > 0 { return &tls.Config{InsecureSkipVerify: true}, nil } if !android.OnAndroid() { return nil, nil } return android.TLSConfig() }
// SecretRingFile returns the filename to the user's GPG secret ring. // The value comes from either the --secret-keyring flag, the // CAMLI_SECRET_RING environment variable, the client config file's // "identitySecretRing" value, or the operating system default location. func (c *Client) SecretRingFile() string { if secretRing, ok := osutil.ExplicitSecretRingFile(); ok { return secretRing } if android.OnAndroid() { panic("on android, so CAMLI_SECRET_RING should have been defined, or --secret-keyring used.") } configOnce.Do(parseConfig) if config.IdentitySecretRing == "" { return osutil.SecretRingFile() } return config.IdentitySecretRing }
// initSecretRing sets c.secretRing. It tries, in this order, the --secret-keyring flag, // the CAMLI_SECRET_RING env var, then defaults to the operating system dependent location // otherwise. // It returns an error if the file does not exist. func (c *initCmd) initSecretRing() error { if secretRing, ok := osutil.ExplicitSecretRingFile(); ok { c.secretRing = secretRing } else { if android.OnAndroid() { panic("on android, so CAMLI_SECRET_RING should have been defined, or --secret-keyring used.") } c.secretRing = osutil.SecretRingFile() } if _, err := os.Stat(c.secretRing); err != nil { hint := "\nA GPG key is required, please use 'camput init --newkey'.\n\nOr if you know what you're doing, you can set the global camput flag --secret-keyring, or the CAMLI_SECRET_RING env var, to use your own GPG ring. And --gpgkey=<pubid> or GPGKEY to select which key ID to use." return fmt.Errorf("Could not use secret ring file %v: %v.\n%v", c.secretRing, err, hint) } return nil }
func (c *Client) initIgnoredFiles() { defer func() { c.ignoreChecker = newIgnoreChecker(c.ignoredFiles) }() if e := os.Getenv("CAMLI_IGNORED_FILES"); e != "" { c.ignoredFiles = strings.Split(e, ",") return } c.ignoredFiles = []string{} if android.OnAndroid() { return } configOnce.Do(parseConfig) c.ignoredFiles = config.IgnoredFiles }
// SecretRingFile returns the filename to the user's GPG secret ring. // The value comes from either a command-line flag, the // CAMLI_SECRET_RING environment variable, the client config file's // "identitySecretRing" value, or the operating system default location. func (c *Client) SecretRingFile() string { if flagSecretRing != "" { return flagSecretRing } if e := os.Getenv("CAMLI_SECRET_RING"); e != "" { return e } if android.OnAndroid() { panic("CAMLI_SECRET_RING should have been defined when on android") } configOnce.Do(parseConfig) if config.IdentitySecretRing == "" { return osutil.IdentitySecretRing() } return config.IdentitySecretRing }
// SecretRingFile returns the filename to the user's GPG secret ring. // The value comes from either the --secret-keyring flag, the // CAMLI_SECRET_RING environment variable, the client config file's // "identitySecretRing" value, or the operating system default location. func (c *Client) SecretRingFile() string { if secretRing, ok := osutil.ExplicitSecretRingFile(); ok { return secretRing } if android.OnAndroid() { panic("on android, so CAMLI_SECRET_RING should have been defined, or --secret-keyring used.") } if c.paramsOnly { log.Print("client: paramsOnly set; cannot get secret ring file from config or env vars.") return "" } if configDisabled { panic("Need a secret ring, and config file disabled") } configOnce.Do(parseConfig) if config.IdentitySecretRing == "" { return osutil.SecretRingFile() } return config.IdentitySecretRing }
func parseConfig() { if android.OnAndroid() { return } configPath := osutil.UserClientConfigPath() if _, err := os.Stat(configPath); os.IsNotExist(err) { errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } var err error if config, err = jsonconfig.ReadFile(configPath); err != nil { log.Fatal(err.Error()) return } }
func (c *Client) initTrustedCerts() { if e := os.Getenv("CAMLI_TRUSTED_CERT"); e != "" { c.trustedCerts = strings.Split(e, ",") return } c.trustedCerts = []string{} if android.OnAndroid() { return } if c.server == "" { log.Printf("No server defined: can not define trustedCerts for this client.") return } trustedCerts := serverTrustedCerts(c.server) if trustedCerts == nil { return } for _, trustedCert := range trustedCerts { c.trustedCerts = append(c.trustedCerts, strings.ToLower(trustedCert)) } }
// SetupAuth sets the client's authMode. It tries from the environment first if we're on android or in dev mode, and then from the client configuration. func (c *Client) SetupAuth() error { if c.paramsOnly { if c.authMode != nil { if _, ok := c.authMode.(*auth.None); !ok { return nil } } return errors.New("client: paramsOnly set; auth should not be configured from config or env vars.") } // env var takes precedence, but only if we're in dev mode or on android. // Too risky otherwise. if android.OnAndroid() || env.IsDev() || configDisabled { authMode, err := auth.FromEnv() if err == nil { c.authMode = authMode return nil } if err != auth.ErrNoAuth { return fmt.Errorf("Could not set up auth from env var CAMLI_AUTH: %v", err) } } if c.server == "" { return fmt.Errorf("No server defined for this client: can not set up auth.") } authConf := serverAuth(c.server) if authConf == "" { c.authErr = fmt.Errorf("could not find auth key for server %q in config, defaulting to no auth", c.server) c.authMode = auth.None{} return nil } var err error c.authMode, err = auth.FromConfig(authConf) return err }
// lazy config parsing when there's a known client already. // The client c may be nil. func (c *Client) parseConfig() { if android.OnAndroid() { panic("parseConfig should never have been called on Android") } if configDisabled { panic("parseConfig should never have been called with CAMLI_DISABLE_CLIENT_CONFIG_FILE set") } configPath := osutil.UserClientConfigPath() if _, err := wkfs.Stat(configPath); os.IsNotExist(err) { if c != nil && c.isSharePrefix { return } errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } // TODO: instead of using jsonconfig, we could read the file, and unmarshall into the structs that we now have in pkg/types/clientconfig. But we'll have to add the old fields (before the name changes, and before the multi-servers change) to the structs as well for our gracefull conversion/error messages to work. conf, err := osutil.NewJSONConfigParser().ReadFile(configPath) if err != nil { log.Fatal(err.Error()) } cfg := jsonconfig.Obj(conf) if singleServerAuth := cfg.OptionalString("auth", ""); singleServerAuth != "" { newConf, err := convertToMultiServers(cfg) if err != nil { log.Print(err) } else { cfg = newConf } } config = &clientconfig.Config{ Identity: cfg.OptionalString("identity", ""), IdentitySecretRing: cfg.OptionalString("identitySecretRing", ""), IgnoredFiles: cfg.OptionalList("ignoredFiles"), } serversList := make(map[string]*clientconfig.Server) servers := cfg.OptionalObject("servers") for alias, vei := range servers { // An alias should never be confused with a host name, // so we forbid anything looking like one. if isURLOrHostPort(alias) { log.Fatalf("Server alias %q looks like a hostname; \".\" or \";\" are not allowed.", alias) } serverMap, ok := vei.(map[string]interface{}) if !ok { log.Fatalf("entry %q in servers section is a %T, want an object", alias, vei) } serverConf := jsonconfig.Obj(serverMap) server := &clientconfig.Server{ Server: cleanServer(serverConf.OptionalString("server", "")), Auth: serverConf.OptionalString("auth", ""), IsDefault: serverConf.OptionalBool("default", false), TrustedCerts: serverConf.OptionalList("trustedCerts"), } if err := serverConf.Validate(); err != nil { log.Fatalf("Error in servers section of config file for server %q: %v", alias, err) } serversList[alias] = server } config.Servers = serversList if err := cfg.Validate(); err != nil { printConfigChangeHelp(cfg) log.Fatalf("Error in config file: %v", err) } }