// LoadClientKeys loads the client SSH keys from the // specified directory, and caches them as a process-wide // global. If the directory does not exist, it is created; // if the directory did not exist, or contains no keys, it // is populated with a new key pair. // // If the directory exists, then all pairs of files where one // has the same name as the other + ".pub" will be loaded as // private/public key pairs. // // Calls to LoadClientKeys will clear the previously loaded // keys, and recompute the keys. func LoadClientKeys(dir string) error { clientKeysMutex.Lock() defer clientKeysMutex.Unlock() dir, err := utils.NormalizePath(dir) if err != nil { return err } if _, err := os.Stat(dir); err == nil { keys, err := loadClientKeys(dir) if err != nil { return err } else if len(keys) > 0 { clientKeys = keys return nil } // Directory exists but contains no keys; // fall through and create one. } if err := os.MkdirAll(dir, 0700); err != nil { return err } keyfile, key, err := generateClientKey(dir) if err != nil { os.RemoveAll(dir) return err } clientKeys = map[string]ssh.Signer{keyfile: key} return nil }
// maybeReadAttrFromFile sets defined[attr] to: // // 1) The content of the file defined[attr+"-path"], if that's set // 2) The value of defined[attr] if it is already set. // 3) The content of defaultPath if it exists and defined[attr] is unset // 4) Preserves the content of defined[attr], otherwise // // The defined[attr+"-path"] key is always deleted. func maybeReadAttrFromFile(defined map[string]interface{}, attr, defaultPath string) error { pathAttr := attr + "-path" path, _ := defined[pathAttr].(string) delete(defined, pathAttr) hasPath := path != "" if !hasPath { // No path and attribute is already set; leave it be. if s, _ := defined[attr].(string); s != "" { return nil } path = defaultPath } path, err := utils.NormalizePath(path) if err != nil { return err } if !filepath.IsAbs(path) { path = osenv.JujuHomePath(path) } data, err := ioutil.ReadFile(path) if err != nil { if os.IsNotExist(err) && !hasPath { // If the default path isn't found, it's // not an error. return nil } return err } if len(data) == 0 { return fmt.Errorf("file %q is empty", path) } defined[attr] = string(data) return nil }
func opensshOptions(options *Options, commandKind opensshCommandKind) []string { args := append([]string{}, opensshCommonOptions...) if options == nil { options = &Options{} } if len(options.proxyCommand) > 0 { args = append(args, "-o", "ProxyCommand "+utils.CommandString(options.proxyCommand...)) } if !options.passwordAuthAllowed { args = append(args, "-o", "PasswordAuthentication no") } // We must set ServerAliveInterval or the server may // think we've become unresponsive on long running // command executions such as "apt-get upgrade". args = append(args, "-o", "ServerAliveInterval 30") if options.allocatePTY { args = append(args, "-t", "-t") // twice to force } if options.knownHostsFile != "" { args = append(args, "-o", "UserKnownHostsFile "+utils.CommandString(options.knownHostsFile)) } identities := append([]string{}, options.identities...) if pk := PrivateKeyFiles(); len(pk) > 0 { // Add client keys as implicit identities identities = append(identities, pk...) } // If any identities are specified, the // default ones must be explicitly specified. if len(identities) > 0 { // Restrict SSH to only the explicitly provided identity files. // Otherwise we may run out of authentication attempts if the // user has many identity files. args = append(args, "-o", "IdentitiesOnly yes") for _, identity := range defaultIdentities { path, err := utils.NormalizePath(identity) if err != nil { logger.Warningf("failed to normalize path %q: %v", identity, err) continue } if _, err := os.Stat(path); err == nil { identities = append(identities, path) } } } for _, identity := range identities { args = append(args, "-i", identity) } if options.port != 0 { port := fmt.Sprint(options.port) if commandKind == scpKind { // scp uses -P instead of -p (-p means preserve). args = append(args, "-P", port) } else { args = append(args, "-p", port) } } return args }
func checkFiles(c *gc.C, obtained, expected []string) { var err error for i, e := range expected { expected[i], err = utils.NormalizePath(e) c.Assert(err, jc.ErrorIsNil) } c.Assert(obtained, jc.SameContents, expected) }
func writeAuthorisedKeys(username string, keys []string) error { keyDir := fmt.Sprintf(authKeysDir, username) keyDir, err := utils.NormalizePath(keyDir) if err != nil { return err } err = os.MkdirAll(keyDir, os.FileMode(0755)) if err != nil { return fmt.Errorf("cannot create ssh key directory: %v", err) } keyData := strings.Join(keys, "\n") + "\n" // Get perms to use on auth keys file sshKeyFile := filepath.Join(keyDir, authKeysFile) perms := os.FileMode(0644) info, err := os.Stat(sshKeyFile) if err == nil { perms = info.Mode().Perm() } logger.Debugf("writing authorised keys file %s", sshKeyFile) err = utils.AtomicWriteFile(sshKeyFile, []byte(keyData), perms) if err != nil { return err } // TODO (wallyworld) - what to do on windows (if anything) // TODO(dimitern) - no need to use user.Current() if username // is "" - it will use the current user anyway. if runtime.GOOS != "windows" { // Ensure the resulting authorised keys file has its ownership // set to the specified username. var u *user.User if username == "" { u, err = user.Current() } else { u, err = user.Lookup(username) } if err != nil { return err } // chown requires ints but user.User has strings for windows. uid, err := strconv.Atoi(u.Uid) if err != nil { return err } gid, err := strconv.Atoi(u.Gid) if err != nil { return err } err = os.Chown(sshKeyFile, uid, gid) if err != nil { return err } } return nil }
func CreateTestKey(c *gc.C) func(*gc.C) { keyFile := fmt.Sprintf("~/.ssh/%s", testKeyFileName) keyFilePath, err := utils.NormalizePath(keyFile) c.Assert(err, jc.ErrorIsNil) err = ioutil.WriteFile(keyFilePath, []byte(testPrivateKey), 400) c.Assert(err, jc.ErrorIsNil) return func(c *gc.C) { os.Remove(keyFilePath) } }
// Read returns the contents of the file. func (f *FileVar) Read(ctx *Context) ([]byte, error) { if f.Path == "" { return nil, ErrNoPath } path, err := utils.NormalizePath(f.Path) if err != nil { return nil, err } return ioutil.ReadFile(ctx.AbsPath(path)) }
// Validate implements environs.EnvironProvider.Validate. func (provider environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { // Check for valid changes for the base config values. if err := config.Validate(cfg, old); err != nil { return nil, err } validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) if err != nil { return nil, fmt.Errorf("failed to validate unknown attrs: %v", err) } localConfig := newEnvironConfig(cfg, validated) // Before potentially creating directories, make sure that the // root directory has not changed. containerType := localConfig.container() if old != nil { oldLocalConfig, err := provider.newConfig(old) if err != nil { return nil, fmt.Errorf("old config is not a valid local config: %v", old) } if containerType != oldLocalConfig.container() { return nil, fmt.Errorf("cannot change container from %q to %q", oldLocalConfig.container(), containerType) } if localConfig.rootDir() != oldLocalConfig.rootDir() { return nil, fmt.Errorf("cannot change root-dir from %q to %q", oldLocalConfig.rootDir(), localConfig.rootDir()) } if localConfig.networkBridge() != oldLocalConfig.networkBridge() { return nil, fmt.Errorf("cannot change network-bridge from %q to %q", oldLocalConfig.rootDir(), localConfig.rootDir()) } if localConfig.storagePort() != oldLocalConfig.storagePort() { return nil, fmt.Errorf("cannot change storage-port from %v to %v", oldLocalConfig.storagePort(), localConfig.storagePort()) } } // Currently only supported containers are "lxc" and "kvm". if containerType != instance.LXC && containerType != instance.KVM { return nil, fmt.Errorf("unsupported container type: %q", containerType) } dir, err := utils.NormalizePath(localConfig.rootDir()) if err != nil { return nil, err } if dir == "." { dir = osenv.JujuHomePath(cfg.Name()) } // Always assign the normalized path. localConfig.attrs["root-dir"] = dir // Apply the coerced unknown values back into the config. return cfg.Apply(localConfig.attrs) }
func authKeysDir(username string) (string, error) { homeDir, err := utils.UserHomeDir(username) if err != nil { return "", err } homeDir, err = utils.NormalizePath(homeDir) if err != nil { return "", err } return filepath.Join(homeDir, ".ssh"), nil }
// Open opens the file. func (f *FileVar) Open(ctx *Context) (io.ReadCloser, error) { if f.Path == "" { return nil, ErrNoPath } if f.IsStdin() { return ioutil.NopCloser(ctx.Stdin), nil } path, err := utils.NormalizePath(f.Path) if err != nil { return nil, err } return os.Open(ctx.AbsPath(path)) }
func (*fileSuite) TestNormalizePath(c *gc.C) { home := filepath.FromSlash(c.MkDir()) err := utils.SetHome(home) c.Assert(err, gc.IsNil) // TODO (frankban) bug 1324841: improve the isolation of this suite. currentUser, err := user.Current() c.Assert(err, gc.IsNil) for i, test := range []struct { path string expected string err string }{{ path: filepath.FromSlash("/var/lib/juju"), expected: filepath.FromSlash("/var/lib/juju"), }, { path: "~/foo", expected: filepath.Join(home, "foo"), }, { path: "~/foo//../bar", expected: filepath.Join(home, "bar"), }, { path: "~", expected: home, }, { path: "~" + currentUser.Username, expected: currentUser.HomeDir, }, { path: "~" + currentUser.Username + "/foo", expected: filepath.Join(currentUser.HomeDir, "foo"), }, { path: "~" + currentUser.Username + "/foo//../bar", expected: filepath.Join(currentUser.HomeDir, "bar"), }, { path: filepath.FromSlash("foo~bar/baz"), expected: filepath.FromSlash("foo~bar/baz"), }, { path: "~foobar/path", err: ".*" + utils.NoSuchUserErrRegexp, }} { c.Logf("test %d: %s", i, test.path) actual, err := utils.NormalizePath(test.path) if test.err != "" { c.Check(err, gc.ErrorMatches, test.err) } else { c.Check(err, gc.IsNil) c.Check(actual, gc.Equals, test.expected) } } }
func userPublicSigningKey() (string, error) { signingKeyFile := os.Getenv("JUJU_STREAMS_PUBLICKEY_FILE") signingKey := "" if signingKeyFile != "" { path, err := utils.NormalizePath(signingKeyFile) if err != nil { return "", errors.Annotatef(err, "cannot expand key file path: %s", signingKeyFile) } b, err := ioutil.ReadFile(path) if err != nil { return "", errors.Annotatef(err, "invalid public key file: %s", path) } signingKey = string(b) } return signingKey, nil }
func (s *ConfigSuite) TestPrepareWithDefaultKeyFile(c *gc.C) { ctx := coretesting.Context(c) // By default "private-key-path isn't set until after validateConfig has been called. attrs := validAttrs().Delete("private-key-path", "private-key") keyFilePath, err := utils.NormalizePath(jp.DefaultPrivateKey) c.Assert(err, gc.IsNil) err = ioutil.WriteFile(keyFilePath, []byte(testPrivateKey), 400) c.Assert(err, gc.IsNil) defer os.Remove(keyFilePath) testConfig := newConfig(c, attrs) preparedConfig, err := jp.Provider.Prepare(ctx, testConfig) c.Assert(err, gc.IsNil) attrs = preparedConfig.Config().AllAttrs() c.Check(attrs["private-key-path"], gc.Equals, jp.DefaultPrivateKey) c.Check(attrs["private-key"], gc.Equals, testPrivateKey) }
func opensshOptions(options *Options, commandKind opensshCommandKind) []string { args := append([]string{}, opensshCommonOptions...) if options == nil { options = &Options{} } if len(options.proxyCommand) > 0 { args = append(args, "-o", "ProxyCommand "+utils.CommandString(options.proxyCommand...)) } if !options.passwordAuthAllowed { args = append(args, "-o", "PasswordAuthentication no") } if options.allocatePTY { args = append(args, "-t", "-t") // twice to force } identities := append([]string{}, options.identities...) if pk := PrivateKeyFiles(); len(pk) > 0 { // Add client keys as implicit identities identities = append(identities, pk...) } // If any identities are specified, the // default ones must be explicitly specified. if len(identities) > 0 { for _, identity := range defaultIdentities { path, err := utils.NormalizePath(identity) if err != nil { logger.Warningf("failed to normalize path %q: %v", identity, err) continue } if _, err := os.Stat(path); err == nil { identities = append(identities, path) } } } for _, identity := range identities { args = append(args, "-i", identity) } if options.port != 0 { port := fmt.Sprint(options.port) if commandKind == scpKind { // scp uses -P instead of -p (-p means preserve). args = append(args, "-P", port) } else { args = append(args, "-p", port) } } return args }
// NewFileStorageReader returns a new storage reader for // a directory inside the local file system. func NewFileStorageReader(path string) (reader storage.StorageReader, err error) { var p string if p, err = utils.NormalizePath(path); err != nil { return nil, err } if p, err = filepath.Abs(p); err != nil { return nil, err } fi, err := os.Stat(p) if err != nil { return nil, err } if !fi.Mode().IsDir() { return nil, fmt.Errorf("specified source path is not a directory: %s", path) } return &fileStorageReader{p}, nil }
// ValidateFileAttrValue returns the normalised file path, so // long as the specified path is valid and not a directory. func ValidateFileAttrValue(path string) (string, error) { if !filepath.IsAbs(path) && !strings.HasPrefix(path, "~") { return "", errors.Errorf("file path must be an absolute path: %s", path) } absPath, err := utils.NormalizePath(path) if err != nil { return "", err } info, err := os.Stat(absPath) if err != nil { return "", errors.Errorf("invalid file path: %s", absPath) } if info.IsDir() { return "", errors.Errorf("file path must be a file: %s", absPath) } return absPath, nil }
// LoadClientCert generates client cert for x509 authentication // if the directory files are not already there , if they are already there // it will load them into memory func (x *X509) LoadClientCert(certFile, keyFile string) error { x.mu.Lock() defer x.mu.Unlock() b1, key := filepath.Split(keyFile) b2, cert := filepath.Split(certFile) if strings.Compare(b1, b2) != 0 { return fmt.Errorf("Cert and Key base paths dosen't match") } base, err := utils.NormalizePath(b1) if err != nil { return err } logger.Debugf("Init winrm credentials path for the module %s", base) logger.Debugf("Init winrm path key %s", keyFile) logger.Debugf("Init winrm path cert %s", certFile) if err = x.read(base, key, cert); err != nil && err != errNoClientCert && err != errNoX509Folder && err != errNoClientPrivateKey { return err } if err == errNoClientCert || err == errNoX509Folder || err == errNoClientPrivateKey { if err = os.RemoveAll(base); err != nil { return err } } if err := os.MkdirAll(base, 0700); err != nil { return err } if err = x.write(base, key, cert); err != nil { return err } return nil }
// ReadAttrs reads attributes from the specified files, and then overlays // the results with the k=v attributes. func (f *ConfigFlag) ReadAttrs(ctx *cmd.Context) (map[string]interface{}, error) { attrs := make(map[string]interface{}) for _, f := range f.files { path, err := utils.NormalizePath(f) if err != nil { return nil, errors.Trace(err) } data, err := ioutil.ReadFile(ctx.AbsPath(path)) if err != nil { return nil, errors.Trace(err) } if err := yaml.Unmarshal(data, &attrs); err != nil { return nil, err } } for k, v := range f.attrs { attrs[k] = v } return attrs, nil }
// GetCredentials returns a curated set of credential values for a given cloud. // The credential key values are read from the credentials store and the provider // finalises the values to resolve things like json files. // If region is not specified, the default credential region is used. func GetCredentials( store jujuclient.CredentialGetter, region, credentialName, cloudName, cloudType string, ) (_ *cloud.Credential, chosenCredentialName, regionName string, _ error) { credential, credentialName, defaultRegion, err := credentialByName( store, cloudName, credentialName, ) if err != nil { return nil, "", "", errors.Trace(err) } regionName = region if regionName == "" { regionName = defaultRegion } readFile := func(f string) ([]byte, error) { f, err := utils.NormalizePath(f) if err != nil { return nil, errors.Trace(err) } return ioutil.ReadFile(f) } // Finalize credential against schemas supported by the provider. provider, err := environs.Provider(cloudType) if err != nil { return nil, "", "", errors.Trace(err) } credential, err = cloud.FinalizeCredential( *credential, provider.CredentialSchemas(), readFile, ) if err != nil { return nil, "", "", errors.Annotatef( err, "validating %q credential for cloud %q", credentialName, cloudName, ) } return credential, credentialName, regionName, nil }
// ReadAuthorizedKeys implements the standard juju behaviour for finding // authorized_keys. It returns a set of keys in in authorized_keys format // (see sshd(8) for a description). If path is non-empty, it names the // file to use; otherwise the user's .ssh directory will be searched. // Home directory expansion will be performed on the path if it starts with // a ~; if the expanded path is relative, it will be interpreted relative // to $HOME/.ssh. // // The result of utils/ssh.PublicKeyFiles will always be prepended to the // result. In practice, this means ReadAuthorizedKeys never returns an // error when the call originates in the CLI. // // If no SSH keys are found, ReadAuthorizedKeys returns // ErrNoAuthorizedKeys. func ReadAuthorizedKeys(ctx *cmd.Context, path string) (string, error) { files := ssh.PublicKeyFiles() if path == "" { files = append(files, "id_dsa.pub", "id_rsa.pub", "identity.pub") } else { files = append(files, path) } var firstError error var keyData []byte for _, f := range files { f, err := utils.NormalizePath(f) if err != nil { if firstError == nil { firstError = err } continue } if !filepath.IsAbs(f) { f = filepath.Join(utils.Home(), ".ssh", f) } data, err := ioutil.ReadFile(f) if err != nil { if firstError == nil && !os.IsNotExist(err) { firstError = err } continue } keyData = append(keyData, bytes.Trim(data, "\n")...) keyData = append(keyData, '\n') ctx.Verbosef("Adding contents of %q to authorized-keys", f) } if len(keyData) == 0 { if firstError == nil { firstError = ErrNoAuthorizedKeys } return "", firstError } return string(keyData), nil }
// readFileAttr reads the contents of an attribute from a file, if the // corresponding "-path" attribute is set, or otherwise from a default // path. func readFileAttr(attrs map[string]interface{}, key, defaultPath string) (content string, userSpecified bool, _ error) { path, ok := attrs[key+"-path"].(string) if ok { userSpecified = true } else { path = defaultPath } absPath, err := utils.NormalizePath(path) if err != nil { return "", userSpecified, errors.Trace(err) } if !filepath.IsAbs(absPath) { absPath = osenv.JujuXDGDataHomePath(absPath) } data, err := ioutil.ReadFile(absPath) if err != nil { return "", userSpecified, errors.Annotatef(err, "%q not set, and could not read from %q", key, path) } if len(data) == 0 { return "", userSpecified, errors.Errorf("file %q is empty", path) } return string(data), userSpecified, nil }
func readAuthorisedKeys(username string) ([]string, error) { keyDir := fmt.Sprintf(authKeysDir, username) sshKeyFile, err := utils.NormalizePath(filepath.Join(keyDir, authKeysFile)) if err != nil { return nil, err } logger.Debugf("reading authorised keys file %s", sshKeyFile) keyData, err := ioutil.ReadFile(sshKeyFile) if os.IsNotExist(err) { return []string{}, nil } if err != nil { return nil, fmt.Errorf("reading ssh authorised keys file: %v", err) } var keys []string for _, key := range strings.Split(string(keyData), "\n") { if len(strings.Trim(key, " \r")) == 0 { continue } keys = append(keys, key) } return keys, nil }
// Validate implements environs.EnvironProvider.Validate. func (provider environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { // Check for valid changes for the base config values. if err := config.Validate(cfg, old); err != nil { return nil, err } validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) if err != nil { return nil, errors.Annotatef(err, "failed to validate unknown attrs") } localConfig := newEnvironConfig(cfg, validated) // Set correct default network bridge if needed // fix for http://pad.lv/1394450 localConfig.setDefaultNetworkBridge() // Before potentially creating directories, make sure that the // root directory has not changed. if localConfig.namespace() == "" { return nil, errors.New("missing namespace, config not prepared") } containerType := localConfig.container() if old != nil { oldLocalConfig, err := provider.newConfig(old) if err != nil { return nil, errors.Annotatef(err, "old config is not a valid local config: %v", old) } if containerType != oldLocalConfig.container() { return nil, errors.Errorf("cannot change container from %q to %q", oldLocalConfig.container(), containerType) } if localConfig.rootDir() != oldLocalConfig.rootDir() { return nil, errors.Errorf("cannot change root-dir from %q to %q", oldLocalConfig.rootDir(), localConfig.rootDir()) } if localConfig.networkBridge() != oldLocalConfig.networkBridge() { return nil, errors.Errorf("cannot change network-bridge from %q to %q", oldLocalConfig.rootDir(), localConfig.rootDir()) } if localConfig.storagePort() != oldLocalConfig.storagePort() { return nil, errors.Errorf("cannot change storage-port from %v to %v", oldLocalConfig.storagePort(), localConfig.storagePort()) } if localConfig.namespace() != oldLocalConfig.namespace() { return nil, errors.Errorf("cannot change namespace from %v to %v", oldLocalConfig.namespace(), localConfig.namespace()) } } // Currently only supported containers are "lxc" and "kvm". if containerType != instance.LXC && containerType != instance.KVM { return nil, errors.Errorf("unsupported container type: %q", containerType) } dir, err := utils.NormalizePath(localConfig.rootDir()) if err != nil { return nil, err } if dir == "." { dir = osenv.JujuHomePath(cfg.Name()) } // Always assign the normalized path. localConfig.attrs["root-dir"] = dir // If the user hasn't already specified a value, set it to the // given value. defineIfNot := func(keyName string, value interface{}) { if _, defined := cfg.AllAttrs()[keyName]; !defined { logger.Infof("lxc-clone is enabled. Switching %s to %v", keyName, value) localConfig.attrs[keyName] = value } } // If we're cloning, and the user hasn't specified otherwise, // prefer to skip update logic. if useClone, _ := localConfig.LXCUseClone(); useClone && containerType == instance.LXC { defineIfNot("enable-os-refresh-update", true) defineIfNot("enable-os-upgrade", false) } // Apply the coerced unknown values back into the config. return cfg.Apply(localConfig.attrs) }
// GetCredentials returns a curated set of credential values for a given cloud. // The credential key values are read from the credentials store and the provider // finalises the values to resolve things like json files. // If region is not specified, the default credential region is used. func GetCredentials( ctx *cmd.Context, store jujuclient.CredentialGetter, args GetCredentialsParams, ) (_ *cloud.Credential, chosenCredentialName, regionName string, _ error) { credential, credentialName, defaultRegion, err := credentialByName( store, args.CloudName, args.CredentialName, ) if err != nil { return nil, "", "", errors.Trace(err) } regionName = args.CloudRegion if regionName == "" { regionName = defaultRegion if regionName == "" && len(args.Cloud.Regions) > 0 { // No region was specified, use the first region // in the list. regionName = args.Cloud.Regions[0].Name } } cloudEndpoint := args.Cloud.Endpoint cloudIdentityEndpoint := args.Cloud.IdentityEndpoint if regionName != "" { region, err := cloud.RegionByName(args.Cloud.Regions, regionName) if err != nil { return nil, "", "", errors.Trace(err) } cloudEndpoint = region.Endpoint cloudIdentityEndpoint = region.IdentityEndpoint } readFile := func(f string) ([]byte, error) { f, err := utils.NormalizePath(f) if err != nil { return nil, errors.Trace(err) } return ioutil.ReadFile(f) } // Finalize credential against schemas supported by the provider. provider, err := environs.Provider(args.Cloud.Type) if err != nil { return nil, "", "", errors.Trace(err) } credential, err = cloud.FinalizeCredential( *credential, provider.CredentialSchemas(), readFile, ) if err != nil { return nil, "", "", errors.Annotatef( err, "finalizing %q credential for cloud %q", credentialName, args.CloudName, ) } credential, err = provider.FinalizeCredential( ctx, environs.FinalizeCredentialParams{ Credential: *credential, CloudEndpoint: cloudEndpoint, CloudIdentityEndpoint: cloudIdentityEndpoint, }, ) if err != nil { return nil, "", "", errors.Annotatef( err, "finalizing %q credential for cloud %q", credentialName, args.CloudName, ) } return credential, credentialName, regionName, nil }
func validateConfig(cfg, old *config.Config) (*environConfig, error) { // Check for valid changes for the base config values. if err := config.Validate(cfg, old); err != nil { return nil, err } newAttrs, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) if err != nil { return nil, err } envConfig := &environConfig{cfg, newAttrs} // If an old config was supplied, check any immutable fields have not changed. if old != nil { oldEnvConfig, err := validateConfig(old, nil) if err != nil { return nil, err } for _, field := range configImmutableFields { if oldEnvConfig.attrs[field] != envConfig.attrs[field] { return nil, fmt.Errorf( "%s: cannot change from %v to %v", field, oldEnvConfig.attrs[field], envConfig.attrs[field], ) } } } // Read env variables to fill in any missing fields. for field, envVar := range environmentVariables { // If field is not set, get it from env variables if fieldValue, ok := envConfig.attrs[field]; !ok || fieldValue == "" { localEnvVariable := os.Getenv(envVar) if localEnvVariable != "" { envConfig.attrs[field] = localEnvVariable } else { if field != "private-key-path" { return nil, fmt.Errorf("cannot get %s value from environment variable %s", field, envVar) } } } } // Ensure private-key-path is set - if it's not in config or an env var, use a default value. if v, ok := envConfig.attrs["private-key-path"]; !ok || v == "" { v = os.Getenv(environmentVariables["private-key-path"]) if v == "" { v = DefaultPrivateKey } envConfig.attrs["private-key-path"] = v } // Now that we've ensured private-key-path is properly set, we go back and set // up the private key - this is used to sign requests. if fieldValue, ok := envConfig.attrs["private-key"]; !ok || fieldValue == "" { keyFile, err := utils.NormalizePath(envConfig.attrs["private-key-path"].(string)) if err != nil { return nil, err } privateKey, err := ioutil.ReadFile(keyFile) if err != nil { return nil, err } envConfig.attrs["private-key"] = string(privateKey) } // Check for missing fields. for field := range configFields { if envConfig.attrs[field] == "" { return nil, fmt.Errorf("%s: must not be empty", field) } } return envConfig, nil }
func opensshOptions(options *Options, commandKind opensshCommandKind) []string { if options == nil { options = &Options{} } var args []string var hostChecks string switch options.strictHostKeyChecking { case StrictHostChecksYes: hostChecks = "yes" case StrictHostChecksNo: hostChecks = "no" case StrictHostChecksAsk: hostChecks = "ask" default: // StrictHostChecksUnset and invalid values are handled the // same way (the option doesn't get included). } if hostChecks != "" { args = append(args, "-o", "StrictHostKeyChecking "+hostChecks) } if len(options.proxyCommand) > 0 { args = append(args, "-o", "ProxyCommand "+utils.CommandString(options.proxyCommand...)) } if !options.passwordAuthAllowed { args = append(args, "-o", "PasswordAuthentication no") } // We must set ServerAliveInterval or the server may // think we've become unresponsive on long running // command executions such as "apt-get upgrade". args = append(args, "-o", "ServerAliveInterval 30") if options.allocatePTY { args = append(args, "-t", "-t") // twice to force } if options.knownHostsFile != "" { args = append(args, "-o", "UserKnownHostsFile "+utils.CommandString(options.knownHostsFile)) } identities := append([]string{}, options.identities...) if pk := PrivateKeyFiles(); len(pk) > 0 { // Add client keys as implicit identities identities = append(identities, pk...) } // If any identities are specified, the // default ones must be explicitly specified. if len(identities) > 0 { for _, identity := range defaultIdentities { path, err := utils.NormalizePath(identity) if err != nil { logger.Warningf("failed to normalize path %q: %v", identity, err) continue } if _, err := os.Stat(path); err == nil { identities = append(identities, path) } } } for _, identity := range identities { args = append(args, "-i", identity) } if options.port != 0 { port := fmt.Sprint(options.port) if commandKind == scpKind { // scp uses -P instead of -p (-p means preserve). args = append(args, "-P", port) } else { args = append(args, "-p", port) } } return args }