github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/upgrades/systemsshkey.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package upgrades 5 6 import ( 7 "io/ioutil" 8 "os" 9 10 "github.com/juju/errors" 11 "gopkg.in/mgo.v2/txn" 12 13 "github.com/juju/juju/api/keymanager" 14 "github.com/juju/juju/environs/config" 15 "github.com/juju/juju/state" 16 "github.com/juju/juju/utils/ssh" 17 ) 18 19 func ensureSystemSSHKey(context Context) error { 20 privateKey, publicKey, err := readOrMakeSystemIdentity(context) 21 if err != nil { 22 return errors.Trace(err) 23 } 24 if publicKey == "" { 25 // privateKey was read from disk, so it exists. 26 return nil 27 } 28 if err := updateAuthorizedKeys(context, publicKey); err != nil { 29 return errors.Trace(err) 30 } 31 if err := writeSystemIdentity(context, privateKey); err != nil { 32 return errors.Trace(err) 33 } 34 return nil 35 } 36 37 // Somewhere in the 1.20 cycle the system-identity was added to the 38 // state serving info collection in the database, however no migration 39 // step was added to take the identity file from disk and put it into the 40 // new value in the database. 41 func ensureSystemSSHKeyRedux(context Context) error { 42 // If there is a system-identity in the database already, we don't need to 43 // do anything. 44 stateInfo, err := context.State().StateServingInfo() 45 if err != nil { 46 logger.Errorf("failed to read state serving info: %v", err) 47 return errors.Trace(err) 48 } 49 if stateInfo.SystemIdentity != "" { 50 logger.Infof("state serving info has a system identity already, all good") 51 // We are good. One exists already. 52 // Make sure that the agent thinks that it is the same. 53 return updateSystemIdentityInAgentConfig(context, stateInfo.SystemIdentity) 54 } 55 56 privateKey, publicKey, err := readOrMakeSystemIdentity(context) 57 if err != nil { 58 logger.Errorf("failed to read or make system identity: %v", err) 59 return errors.Trace(err) 60 } 61 62 if err := state.SetSystemIdentity(context.State(), privateKey); err != nil { 63 if errors.Cause(err) == txn.ErrAborted { 64 logger.Errorf("someone else has set system identity already") 65 // Another state server upgrading concurrently has updated 66 // the system identity so it is no longer empty. So discard 67 // anything that was created, reread the system info and write 68 // out the file. We also assume that the other upgrade has 69 // updated the authorized keys already. 70 stateInfo, err := context.State().StateServingInfo() 71 if err != nil { 72 logger.Errorf("failed to read state serving info: %v", err) 73 return errors.Trace(err) 74 } 75 if stateInfo.SystemIdentity == "" { 76 logger.Errorf("but the transaction said it would be there...") 77 return errors.New("system identity is not set") 78 } 79 if err := writeSystemIdentity(context, stateInfo.SystemIdentity); err != nil { 80 logger.Errorf("failed to write the system identity file: %v", err) 81 return errors.Trace(err) 82 } 83 return updateSystemIdentityInAgentConfig(context, stateInfo.SystemIdentity) 84 } 85 86 logger.Errorf("failed to set system identity: %v", err) 87 return errors.Annotate(err, "cannot set state serving info") 88 } 89 90 if publicKey != "" { 91 if err := writeSystemIdentity(context, privateKey); err != nil { 92 return errors.Trace(err) 93 } 94 } 95 return updateSystemIdentityInAgentConfig(context, privateKey) 96 } 97 98 // updateAuthorizedKeysForSystemIdentity makes sure that the authorized keys 99 // list is up to date with the system identity. Due to changes in the way 100 // upgrades are done in 1.22, this part, which uses the API had to be split 101 // from the first part which used the state connection. 102 func updateAuthorizedKeysForSystemIdentity(context Context) error { 103 agentInfo, ok := context.AgentConfig().StateServingInfo() 104 if !ok { 105 return errors.New("missing state serving info for the agent") 106 } 107 publicKey, err := ssh.PublicKey([]byte(agentInfo.SystemIdentity), config.JujuSystemKey) 108 if err != nil { 109 return errors.Trace(err) 110 } 111 return errors.Trace(updateAuthorizedKeys(context, publicKey)) 112 } 113 114 func updateAuthorizedKeys(context Context, publicKey string) error { 115 // Look for an existing authorized key. 116 logger.Infof("setting new authorized key for %q", publicKey) 117 keyManager := keymanager.NewClient(context.APIState()) 118 119 result, err := keyManager.ListKeys(ssh.FullKeys, config.JujuSystemKey) 120 if err != nil { 121 return errors.Trace(err) 122 } 123 if result[0].Error != nil { 124 return errors.Trace(result[0].Error) 125 } 126 keys := result[0].Result 127 128 // Loop through the keys. If we find a key that matches the publicKey 129 // then we are good, and done. If the comment on the key is for the system identity 130 // but it is not the same, remove it. 131 var keysToRemove []string 132 for _, key := range keys { 133 // The list of keys returned don't have carriage returns, but the 134 // publicKey does, so add one one before testing for equality. 135 if (key + "\n") == publicKey { 136 logger.Infof("system identity key already in authorized list") 137 return nil 138 } 139 140 fingerprint, comment, err := ssh.KeyFingerprint(key) 141 if err != nil { 142 // Log the error, but it doesn't stop us doing what we need to do. 143 logger.Errorf("bad key in authorized keys: %v", err) 144 } else if comment == config.JujuSystemKey { 145 keysToRemove = append(keysToRemove, fingerprint) 146 } 147 } 148 if keysToRemove != nil { 149 logger.Infof("removing %d keys", len(keysToRemove)) 150 results, err := keyManager.DeleteKeys(config.JujuSystemKey, keysToRemove...) 151 if err != nil { 152 // Log the error but continue. 153 logger.Errorf("failed to remove keys: %v", err) 154 } else { 155 for _, err := range results { 156 if err.Error != nil { 157 // Log the error but continue. 158 logger.Errorf("failed to remove key: %v", err.Error) 159 } 160 } 161 } 162 } 163 164 errResults, err := keyManager.AddKeys(config.JujuSystemKey, publicKey) 165 if err != nil { 166 return errors.Annotate(err, "failed to update authorised keys with new system key") 167 } 168 if err := errResults[0].Error; err != nil { 169 return errors.Annotate(err, "failed to update authorised keys with new system key") 170 } 171 return nil 172 } 173 174 func updateSystemIdentityInAgentConfig(context Context, systemIdentity string) error { 175 agentInfo, ok := context.AgentConfig().StateServingInfo() 176 if !ok { 177 return errors.New("missing state serving info for the agent") 178 } 179 if agentInfo.SystemIdentity != systemIdentity { 180 agentInfo.SystemIdentity = systemIdentity 181 context.AgentConfig().SetStateServingInfo(agentInfo) 182 } 183 return nil 184 } 185 186 func readOrMakeSystemIdentity(context Context) (privateKey, publicKey string, err error) { 187 identityFile := context.AgentConfig().SystemIdentityPath() 188 // Don't generate a key unless we have to. 189 keyExists, err := systemKeyExists(identityFile) 190 if err != nil { 191 return "", "", errors.Annotate(err, "failed to check system key exists") 192 } 193 if keyExists { 194 logger.Infof("key exists, reading contents") 195 196 // Read the contents. 197 contents, err := ioutil.ReadFile(identityFile) 198 if err != nil { 199 return "", "", errors.Trace(err) 200 } 201 // If we are just reading the private key, 202 return string(contents), "", nil 203 } 204 205 logger.Infof("generating new key") 206 privateKey, publicKey, err = ssh.GenerateKey(config.JujuSystemKey) 207 if err != nil { 208 return "", "", errors.Annotate(err, "failed to create system key") 209 } 210 return privateKey, publicKey, nil 211 } 212 213 func writeSystemIdentity(context Context, privateKey string) error { 214 identityFile := context.AgentConfig().SystemIdentityPath() 215 logger.Infof("writing system identity to %q", identityFile) 216 if err := ioutil.WriteFile(identityFile, []byte(privateKey), 0600); err != nil { 217 return errors.Annotate(err, "failed to write identity file") 218 } 219 return nil 220 } 221 222 func systemKeyExists(identityFile string) (bool, error) { 223 _, err := os.Stat(identityFile) 224 if err == nil { 225 logger.Infof("identity file %q exists", identityFile) 226 return true, nil 227 } 228 if !os.IsNotExist(err) { 229 logger.Infof("error looking for identity file %q: %v", identityFile, err) 230 return false, err 231 } 232 logger.Infof("identity file %q does not exist", identityFile) 233 return false, nil 234 }