// SetUp is defined on the worker.NotifyWatchHandler interface. func (kw *keyupdaterWorker) SetUp() (watcher.NotifyWatcher, error) { // Record the keys Juju knows about. // TODO(dfc) jujuKeys, err := kw.st.AuthorisedKeys(kw.tag.String()) if err != nil { return nil, errors.LoggedErrorf(logger, "reading Juju ssh keys for %q: %v", kw.tag, err) } kw.jujuKeys = set.NewStrings(jujuKeys...) // Read the keys currently in ~/.ssh/authorised_keys. sshKeys, err := ssh.ListKeys(SSHUser, ssh.FullKeys) if err != nil { return nil, errors.LoggedErrorf(logger, "reading ssh authorized keys for %q: %v", kw.tag, err) } // Record any keys not added by Juju. for _, key := range sshKeys { _, comment, err := ssh.KeyFingerprint(key) // Also record keys which we cannot parse. if err != nil || !strings.HasPrefix(comment, ssh.JujuCommentPrefix) { kw.nonJujuKeys = append(kw.nonJujuKeys, key) } } // Write out the ssh authorised keys file to match the current state of the world. if err := kw.writeSSHKeys(jujuKeys); err != nil { return nil, errors.LoggedErrorf(logger, "adding current Juju keys to ssh authorised keys: %v", err) } w, err := kw.st.WatchAuthorisedKeys(kw.tag.String()) if err != nil { return nil, errors.LoggedErrorf(logger, "starting key updater worker: %v", err) } logger.Infof("%q key updater worker started", kw.tag) return w, nil }
// runSSHKeyImport uses ssh-import-id to find the ssh keys for the specified key ids. func runSSHKeyImport(keyIds []string) map[string][]importedSSHKey { importResults := make(map[string][]importedSSHKey, len(keyIds)) for _, keyId := range keyIds { keyInfo := []importedSSHKey{} output, err := RunSSHImportId(keyId) if err != nil { keyInfo = append(keyInfo, importedSSHKey{err: err}) continue } lines := strings.Split(output, "\n") hasKey := false for _, line := range lines { if !strings.HasPrefix(line, "ssh-") { continue } hasKey = true // ignore key comment (e.g., user@host) fingerprint, _, err := ssh.KeyFingerprint(line) keyInfo = append(keyInfo, importedSSHKey{ key: line, fingerprint: fingerprint, err: errors.Annotatef(err, "invalid ssh key for %s", keyId), }) } if !hasKey { keyInfo = append(keyInfo, importedSSHKey{ err: errors.Errorf("invalid ssh key id: %s", keyId), }) } importResults[keyId] = keyInfo } return importResults }
// currentKeyDataForDelete gathers data used when deleting ssh keys. func (api *KeyManagerAPI) currentKeyDataForDelete() ( keys map[string]string, invalidKeys []string, comments map[string]string, err error) { cfg, err := api.state.EnvironConfig() if err != nil { return nil, nil, nil, fmt.Errorf("reading current key data: %v", err) } // For now, authorised keys are global, common to all users. existingSSHKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) // Build up a map of keys indexed by fingerprint, and fingerprints indexed by comment // so we can easily get the key represented by each keyId, which may be either a fingerprint // or comment. keys = make(map[string]string) comments = make(map[string]string) for _, key := range existingSSHKeys { fingerprint, comment, err := ssh.KeyFingerprint(key) if err != nil { logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err) invalidKeys = append(invalidKeys, key) continue } keys[fingerprint] = key if comment != "" { comments[comment] = fingerprint } } return keys, invalidKeys, comments, nil }
func updateAuthorizedKeys(context Context, publicKey string) error { // Look for an existing authorized key. logger.Infof("setting new authorized key for %q", publicKey) keyManager := keymanager.NewClient(context.APIState()) result, err := keyManager.ListKeys(ssh.FullKeys, config.JujuSystemKey) if err != nil { return errors.Trace(err) } if result[0].Error != nil { return errors.Trace(result[0].Error) } keys := result[0].Result // Loop through the keys. If we find a key that matches the publicKey // then we are good, and done. If the comment on the key is for the system identity // but it is not the same, remove it. var keysToRemove []string for _, key := range keys { // The list of keys returned don't have carriage returns, but the // publicKey does, so add one one before testing for equality. if (key + "\n") == publicKey { logger.Infof("system identity key already in authorized list") return nil } fingerprint, comment, err := ssh.KeyFingerprint(key) if err != nil { // Log the error, but it doesn't stop us doing what we need to do. logger.Errorf("bad key in authorized keys: %v", err) } else if comment == config.JujuSystemKey { keysToRemove = append(keysToRemove, fingerprint) } } if keysToRemove != nil { logger.Infof("removing %d keys", len(keysToRemove)) results, err := keyManager.DeleteKeys(config.JujuSystemKey, keysToRemove...) if err != nil { // Log the error but continue. logger.Errorf("failed to remove keys: %v", err) } else { for _, err := range results { if err.Error != nil { // Log the error but continue. logger.Errorf("failed to remove key: %v", err.Error) } } } } errResults, err := keyManager.AddKeys(config.JujuSystemKey, publicKey) if err != nil { return errors.Annotate(err, "failed to update authorised keys with new system key") } if err := errResults[0].Error; err != nil { return errors.Annotate(err, "failed to update authorised keys with new system key") } return nil }
func (s *FingerprintSuite) TestKeyFingerprint(c *gc.C) { keys := []sshtesting.SSHKey{ sshtesting.ValidKeyOne, sshtesting.ValidKeyTwo, sshtesting.ValidKeyThree, } for _, k := range keys { fingerprint, _, err := ssh.KeyFingerprint(k.Key) c.Assert(err, gc.IsNil) c.Assert(fingerprint, gc.Equals, k.Fingerprint) } }
// currentKeyDataForAdd gathers data used when adding ssh keys. func (api *KeyManagerAPI) currentKeyDataForAdd() (keys []string, fingerprints set.Strings, err error) { fingerprints = make(set.Strings) cfg, err := api.state.EnvironConfig() if err != nil { return nil, nil, fmt.Errorf("reading current key data: %v", err) } keys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) for _, key := range keys { fingerprint, _, err := ssh.KeyFingerprint(key) if err != nil { logger.Warningf("ignoring invalid ssh key %q: %v", key, err) } fingerprints.Add(fingerprint) } return keys, fingerprints, nil }
func parseKeys(keys []string, mode ssh.ListMode) (keyInfo []string) { for _, key := range keys { fingerprint, comment, err := ssh.KeyFingerprint(key) if err != nil { keyInfo = append(keyInfo, fmt.Sprintf("Invalid key: %v", key)) } else { if mode == ssh.FullKeys { keyInfo = append(keyInfo, key) } else { shortKey := fingerprint if comment != "" { shortKey += fmt.Sprintf(" (%s)", comment) } keyInfo = append(keyInfo, shortKey) } } } return keyInfo }
// AddKeys adds new authorised ssh keys for the specified user. func (api *KeyManagerAPI) AddKeys(arg params.ModifyUserSSHKeys) (params.ErrorResults, error) { result := params.ErrorResults{ Results: make([]params.ErrorResult, len(arg.Keys)), } if len(arg.Keys) == 0 { return result, nil } canWrite, err := api.getCanWrite() if err != nil { return params.ErrorResults{}, common.ServerError(err) } if !canWrite(arg.User) { return params.ErrorResults{}, common.ServerError(common.ErrPerm) } // For now, authorised keys are global, common to all users. sshKeys, currentFingerprints, err := api.currentKeyDataForAdd() if err != nil { return params.ErrorResults{}, common.ServerError(fmt.Errorf("reading current key data: %v", err)) } // Ensure we are not going to add invalid or duplicate keys. result.Results = make([]params.ErrorResult, len(arg.Keys)) for i, key := range arg.Keys { fingerprint, _, err := ssh.KeyFingerprint(key) if err != nil { result.Results[i].Error = common.ServerError(fmt.Errorf("invalid ssh key: %s", key)) continue } if currentFingerprints.Contains(fingerprint) { result.Results[i].Error = common.ServerError(fmt.Errorf("duplicate ssh key: %s", key)) continue } sshKeys = append(sshKeys, key) } err = api.writeSSHKeys(sshKeys) if err != nil { return params.ErrorResults{}, common.ServerError(err) } return result, nil }
// runSSHKeyImport uses ssh-import-id to find the ssh keys for the specified key ids. func runSSHKeyImport(keyIds []string) []importedSSHKey { keyInfo := make([]importedSSHKey, len(keyIds)) for i, keyId := range keyIds { output, err := RunSSHImportId(keyId) if err != nil { keyInfo[i].err = err continue } lines := strings.Split(output, "\n") for _, line := range lines { if !strings.HasPrefix(line, "ssh-") { continue } keyInfo[i].fingerprint, _, keyInfo[i].err = ssh.KeyFingerprint(line) if err == nil { keyInfo[i].key = line } } if keyInfo[i].key == "" { keyInfo[i].err = fmt.Errorf("invalid ssh key id: %s", keyId) } } return keyInfo }
func (s *FingerprintSuite) TestKeyFingerprintError(c *gc.C) { _, _, err := ssh.KeyFingerprint("invalid key") c.Assert(err, gc.ErrorMatches, `generating key fingerprint: invalid authorized_key "invalid key"`) }