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