github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/common/authkeys.go (about) 1 // Copyright 2012-2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "bytes" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 "github.com/juju/schema" 15 "github.com/juju/utils" 16 "github.com/juju/utils/ssh" 17 18 "github.com/juju/juju/environs/config" 19 ) 20 21 var ErrNoAuthorizedKeys = errors.New("no public ssh keys found") 22 23 // FinalizeAuthorizedKeys takes a set of configuration attributes and 24 // ensures that it has an authorized-keys setting, or returns 25 // ErrNoAuthorizedKeys if it cannot. 26 // 27 // If the attributes contains a non-empty value for "authorized-keys", 28 // then it is left alone. If there is an "authorized-keys-path" setting, 29 // its contents will be loaded into "authorized-keys". Otherwise, the 30 // contents of standard public keys will be used: ~/.ssh/id_dsa.pub, 31 // ~/.ssh/id_rsa.pub, and ~/.ssh/identity.pub. 32 func FinalizeAuthorizedKeys(ctx *cmd.Context, attrs map[string]interface{}) error { 33 const authorizedKeysPathKey = "authorized-keys-path" 34 checker := schema.FieldMap(schema.Fields{ 35 config.AuthorizedKeysKey: schema.String(), 36 authorizedKeysPathKey: schema.String(), 37 }, schema.Defaults{ 38 config.AuthorizedKeysKey: schema.Omit, 39 authorizedKeysPathKey: schema.Omit, 40 }) 41 coerced, err := checker.Coerce(attrs, nil) 42 if err != nil { 43 return errors.Trace(err) 44 } 45 coercedAttrs := coerced.(map[string]interface{}) 46 47 authorizedKeys, haveAuthorizedKeys := coercedAttrs[config.AuthorizedKeysKey].(string) 48 authorizedKeysPath, haveAuthorizedKeysPath := coercedAttrs[authorizedKeysPathKey].(string) 49 if haveAuthorizedKeys && haveAuthorizedKeysPath { 50 return errors.Errorf( 51 "%q and %q may not both be specified", 52 config.AuthorizedKeysKey, authorizedKeysPathKey, 53 ) 54 } 55 if haveAuthorizedKeys { 56 // We have authorized-keys already; nothing to do. 57 return nil 58 } 59 60 authorizedKeys, err = ReadAuthorizedKeys(ctx, authorizedKeysPath) 61 if err != nil { 62 return errors.Annotate(err, "reading authorized-keys") 63 } 64 if haveAuthorizedKeysPath { 65 delete(attrs, authorizedKeysPathKey) 66 } 67 attrs[config.AuthorizedKeysKey] = authorizedKeys 68 return nil 69 } 70 71 // ReadAuthorizedKeys implements the standard juju behaviour for finding 72 // authorized_keys. It returns a set of keys in in authorized_keys format 73 // (see sshd(8) for a description). If path is non-empty, it names the 74 // file to use; otherwise the user's .ssh directory will be searched. 75 // Home directory expansion will be performed on the path if it starts with 76 // a ~; if the expanded path is relative, it will be interpreted relative 77 // to $HOME/.ssh. 78 // 79 // The result of utils/ssh.PublicKeyFiles will always be prepended to the 80 // result. In practice, this means ReadAuthorizedKeys never returns an 81 // error when the call originates in the CLI. 82 // 83 // If no SSH keys are found, ReadAuthorizedKeys returns 84 // ErrNoAuthorizedKeys. 85 func ReadAuthorizedKeys(ctx *cmd.Context, path string) (string, error) { 86 files := ssh.PublicKeyFiles() 87 if path == "" { 88 files = append(files, "id_dsa.pub", "id_rsa.pub", "identity.pub") 89 } else { 90 files = append(files, path) 91 } 92 var firstError error 93 var keyData []byte 94 for _, f := range files { 95 f, err := utils.NormalizePath(f) 96 if err != nil { 97 if firstError == nil { 98 firstError = err 99 } 100 continue 101 } 102 if !filepath.IsAbs(f) { 103 f = filepath.Join(utils.Home(), ".ssh", f) 104 } 105 data, err := ioutil.ReadFile(f) 106 if err != nil { 107 if firstError == nil && !os.IsNotExist(err) { 108 firstError = err 109 } 110 continue 111 } 112 keyData = append(keyData, bytes.Trim(data, "\n")...) 113 keyData = append(keyData, '\n') 114 ctx.Verbosef("Adding contents of %q to authorized-keys", f) 115 } 116 if len(keyData) == 0 { 117 if firstError == nil { 118 firstError = ErrNoAuthorizedKeys 119 } 120 return "", firstError 121 } 122 return string(keyData), nil 123 124 }