// 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 }
// Bootstrap bootstraps the given environment. The supplied constraints are // used to provision the instance, and are also set within the bootstrapped // environment. func Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args environs.BootstrapParams) error { cfg := environ.Config() if secret := cfg.AdminSecret(); secret == "" { return fmt.Errorf("environment configuration has no admin-secret") } if authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()); len(authKeys) == 0 { // Apparently this can never happen, so it's not tested. But, one day, // Config will act differently (it's pretty crazy that, AFAICT, the // authorized-keys are optional config settings... but it's impossible // to actually *create* a config without them)... and when it does, // we'll be here to catch this problem early. return fmt.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return fmt.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return fmt.Errorf("environment configuration has no ca-private-key") } // Write out the bootstrap-init file, and confirm storage is writeable. if err := environs.VerifyStorage(environ.Storage()); err != nil { return err } logger.Debugf("environment %q supports service/machine networks: %v", environ.Name(), environ.SupportNetworks()) logger.Infof("bootstrapping environment %q", environ.Name()) return environ.Bootstrap(ctx, args) }
// AddSSHAuthorizedKeys adds a set of keys in // ssh authorized_keys format (see ssh(8) for details) // that will be added to ~/.ssh/authorized_keys for the // configured user (see SetUser). func (cfg *Config) AddSSHAuthorizedKeys(keyData string) { akeys, _ := cfg.attrs["ssh_authorized_keys"].([]string) keys := ssh.SplitAuthorisedKeys(keyData) for _, key := range keys { // Ensure the key has a comment prepended with "Juju:" so we // can distinguish between Juju managed keys and those added // externally. jujuKey := ssh.EnsureJujuComment(key) akeys = append(akeys, jujuKey) } cfg.attrs["ssh_authorized_keys"] = akeys }
func (s *systemSSHKeySuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) apiState, _ := s.OpenAPIAsNewMachine(c, state.JobManageEnviron) s.ctx = &mockContext{ agentConfig: &mockAgentConfig{dataDir: s.DataDir()}, apiState: apiState, } _, err := os.Stat(s.keyFile()) c.Assert(err, jc.Satisfies, os.IsNotExist) // There's initially one authorised key for the test user. cfg, err := s.State.EnvironConfig() c.Assert(err, gc.IsNil) authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) c.Assert(authKeys, gc.HasLen, 1) }
// currentKeyDataForAdd gathers data used when adding ssh keys. func (api *KeyManagerAPI) currentKeyDataForAdd() (keys []string, fingerprints *set.Strings, err error) { fp := set.NewStrings() fingerprints = &fp 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 (s *systemSSHKeySuite) assertKeyCreation(c *gc.C) { c.Assert(s.keyFile(), jc.IsNonEmptyFile) // Check the private key from the system identify file. privateKey, err := ioutil.ReadFile(s.keyFile()) c.Assert(err, gc.IsNil) c.Check(string(privateKey), jc.HasPrefix, "-----BEGIN RSA PRIVATE KEY-----\n") c.Check(string(privateKey), jc.HasSuffix, "-----END RSA PRIVATE KEY-----\n") // Check the public key from the auth keys config. cfg, err := s.JujuConnSuite.State.EnvironConfig() c.Assert(err, gc.IsNil) authKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) // The dummy env is created with 1 fake key. We check that another has been added. c.Assert(authKeys, gc.HasLen, 2) c.Check(authKeys[1], jc.HasPrefix, "ssh-rsa ") c.Check(authKeys[1], jc.HasSuffix, " juju-system-key") }
func (s *AuthorisedKeysKeysSuite) TestSplitAuthorisedKeys(c *gc.C) { sshKey := sshtesting.ValidKeyOne.Key for _, test := range []struct { keyData string expected []string }{ {"", nil}, {sshKey, []string{sshKey}}, {sshKey + "\n", []string{sshKey}}, {sshKey + "\n\n", []string{sshKey}}, {sshKey + "\n#comment\n", []string{sshKey}}, {sshKey + "\n #comment\n", []string{sshKey}}, {sshKey + "\ninvalid\n", []string{sshKey, "invalid"}}, } { actual := ssh.SplitAuthorisedKeys(test.keyData) c.Assert(actual, gc.DeepEquals, test.expected) } }
// AuthorisedKeys reports the authorised ssh keys for the specified machines. // The current implementation relies on global authorised keys being stored in the environment config. // This will change as new user management and authorisation functionality is added. func (api *KeyUpdaterAPI) AuthorisedKeys(arg params.Entities) (params.StringsResults, error) { if len(arg.Entities) == 0 { return params.StringsResults{}, nil } results := make([]params.StringsResult, len(arg.Entities)) // For now, authorised keys are global, common to all machines. var keys []string config, configErr := api.state.EnvironConfig() if configErr == nil { keys = ssh.SplitAuthorisedKeys(config.AuthorizedKeys()) } canRead, err := api.getCanRead() if err != nil { return params.StringsResults{}, err } for i, entity := range arg.Entities { // 1. Check permissions if !canRead(entity.Tag) { results[i].Error = common.ServerError(common.ErrPerm) continue } // 2. Check entity exists if _, err := api.state.FindEntity(entity.Tag); err != nil { if errors.IsNotFound(err) { results[i].Error = common.ServerError(common.ErrPerm) } else { results[i].Error = common.ServerError(err) } continue } // 3. Get keys var err error if configErr == nil { results[i].Result = keys } else { err = configErr } results[i].Error = common.ServerError(err) } return params.StringsResults{Results: results}, nil }
// ListKeys returns the authorised ssh keys for the specified users. func (api *KeyManagerAPI) ListKeys(arg params.ListSSHKeys) (params.StringsResults, error) { if len(arg.Entities.Entities) == 0 { return params.StringsResults{}, nil } results := make([]params.StringsResult, len(arg.Entities.Entities)) // For now, authorised keys are global, common to all users. var keyInfo []string cfg, configErr := api.state.EnvironConfig() if configErr == nil { keys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) keyInfo = parseKeys(keys, arg.Mode) } canRead, err := api.getCanRead() if err != nil { return params.StringsResults{}, err } for i, entity := range arg.Entities.Entities { if !canRead(entity.Tag) { results[i].Error = common.ServerError(common.ErrPerm) continue } if _, err := api.state.User(entity.Tag); err != nil { if errors.IsNotFound(err) { results[i].Error = common.ServerError(common.ErrPerm) } else { results[i].Error = common.ServerError(err) } continue } var err error if configErr == nil { results[i].Result = keyInfo } else { err = configErr } results[i].Error = common.ServerError(err) } return params.StringsResults{Results: results}, nil }