github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/utils/ssh/clientkeys.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ssh 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 14 "code.google.com/p/go.crypto/ssh" 15 "github.com/juju/utils" 16 "github.com/juju/utils/set" 17 ) 18 19 const clientKeyName = "juju_id_rsa" 20 21 // PublicKeySuffix is the file extension for public key files. 22 const PublicKeySuffix = ".pub" 23 24 var ( 25 clientKeysMutex sync.Mutex 26 27 // clientKeys is a cached map of private key filenames 28 // to ssh.Signers. The private keys are those loaded 29 // from the client key directory, passed to LoadClientKeys. 30 clientKeys map[string]ssh.Signer 31 ) 32 33 // LoadClientKeys loads the client SSH keys from the 34 // specified directory, and caches them as a process-wide 35 // global. If the directory does not exist, it is created; 36 // if the directory did not exist, or contains no keys, it 37 // is populated with a new key pair. 38 // 39 // If the directory exists, then all pairs of files where one 40 // has the same name as the other + ".pub" will be loaded as 41 // private/public key pairs. 42 // 43 // Calls to LoadClientKeys will clear the previously loaded 44 // keys, and recompute the keys. 45 func LoadClientKeys(dir string) error { 46 clientKeysMutex.Lock() 47 defer clientKeysMutex.Unlock() 48 dir, err := utils.NormalizePath(dir) 49 if err != nil { 50 return err 51 } 52 if _, err := os.Stat(dir); err == nil { 53 keys, err := loadClientKeys(dir) 54 if err != nil { 55 return err 56 } else if len(keys) > 0 { 57 clientKeys = keys 58 return nil 59 } 60 // Directory exists but contains no keys; 61 // fall through and create one. 62 } 63 if err := os.MkdirAll(dir, 0700); err != nil { 64 return err 65 } 66 keyfile, key, err := generateClientKey(dir) 67 if err != nil { 68 os.RemoveAll(dir) 69 return err 70 } 71 clientKeys = map[string]ssh.Signer{keyfile: key} 72 return nil 73 } 74 75 // ClearClientKeys clears the client keys cached in memory. 76 func ClearClientKeys() { 77 clientKeysMutex.Lock() 78 defer clientKeysMutex.Unlock() 79 clientKeys = nil 80 } 81 82 func generateClientKey(dir string) (keyfile string, key ssh.Signer, err error) { 83 private, public, err := GenerateKey("juju-client-key") 84 if err != nil { 85 return "", nil, err 86 } 87 clientPrivateKey, err := ssh.ParsePrivateKey([]byte(private)) 88 if err != nil { 89 return "", nil, err 90 } 91 privkeyFilename := filepath.Join(dir, clientKeyName) 92 if err = ioutil.WriteFile(privkeyFilename, []byte(private), 0600); err != nil { 93 return "", nil, err 94 } 95 if err := ioutil.WriteFile(privkeyFilename+PublicKeySuffix, []byte(public), 0600); err != nil { 96 os.Remove(privkeyFilename) 97 return "", nil, err 98 } 99 return privkeyFilename, clientPrivateKey, nil 100 } 101 102 func loadClientKeys(dir string) (map[string]ssh.Signer, error) { 103 publicKeyFiles, err := publicKeyFiles(dir) 104 if err != nil { 105 return nil, err 106 } 107 keys := make(map[string]ssh.Signer, len(publicKeyFiles)) 108 for _, filename := range publicKeyFiles { 109 filename = filename[:len(filename)-len(PublicKeySuffix)] 110 data, err := ioutil.ReadFile(filename) 111 if err != nil { 112 return nil, err 113 } 114 keys[filename], err = ssh.ParsePrivateKey(data) 115 if err != nil { 116 return nil, fmt.Errorf("parsing key file %q: %v", filename, err) 117 } 118 } 119 return keys, nil 120 } 121 122 // privateKeys returns the private keys loaded by LoadClientKeys. 123 func privateKeys() (signers []ssh.Signer) { 124 clientKeysMutex.Lock() 125 defer clientKeysMutex.Unlock() 126 for _, key := range clientKeys { 127 signers = append(signers, key) 128 } 129 return signers 130 } 131 132 // PrivateKeyFiles returns the filenames of private SSH keys loaded by 133 // LoadClientKeys. 134 func PrivateKeyFiles() []string { 135 clientKeysMutex.Lock() 136 defer clientKeysMutex.Unlock() 137 keyfiles := make([]string, 0, len(clientKeys)) 138 for f := range clientKeys { 139 keyfiles = append(keyfiles, f) 140 } 141 return keyfiles 142 } 143 144 // PublicKeyFiles returns the filenames of public SSH keys loaded by 145 // LoadClientKeys. 146 func PublicKeyFiles() []string { 147 privkeys := PrivateKeyFiles() 148 pubkeys := make([]string, len(privkeys)) 149 for i, priv := range privkeys { 150 pubkeys[i] = priv + PublicKeySuffix 151 } 152 return pubkeys 153 } 154 155 // publicKeyFiles returns the filenames of public SSH keys 156 // in the specified directory (all the files ending with .pub). 157 func publicKeyFiles(clientKeysDir string) ([]string, error) { 158 if clientKeysDir == "" { 159 return nil, nil 160 } 161 var keys []string 162 dir, err := os.Open(clientKeysDir) 163 if err != nil { 164 return nil, err 165 } 166 names, err := dir.Readdirnames(-1) 167 dir.Close() 168 if err != nil { 169 return nil, err 170 } 171 candidates := set.NewStrings(names...) 172 for _, name := range names { 173 if !strings.HasSuffix(name, PublicKeySuffix) { 174 continue 175 } 176 // If the private key filename also exists, add the file. 177 priv := name[:len(name)-len(PublicKeySuffix)] 178 if candidates.Contains(priv) { 179 keys = append(keys, filepath.Join(dir.Name(), name)) 180 } 181 } 182 return keys, nil 183 }