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