// 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() network.InitializeFromConfig(cfg) 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) }
// 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 }
// 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) } for i, entity := range arg.Entities.Entities { // NOTE: entity.Tag isn't a tag, but a username. if !api.canRead(entity.Tag) { results[i].Error = common.ServerError(common.ErrPerm) continue } // All keys are global, no need to look up the user. if configErr == nil { results[i].Result = keyInfo } results[i].Error = common.ServerError(configErr) } 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) } for i, entity := range arg.Entities.Entities { if !api.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 } if configErr == nil { results[i].Result = keyInfo } results[i].Error = common.ServerError(configErr) } return params.StringsResults{Results: results}, nil }
func (s *systemSSHKeySuiteBase) assertHasPublicKeyInAuth(c *gc.C, privateKey string) { publicKey, err := ssh.PublicKey([]byte(privateKey), config.JujuSystemKey) c.Assert(err, jc.ErrorIsNil) // Check the public key from the auth keys config. cfg, err := s.JujuConnSuite.State.EnvironConfig() c.Assert(err, jc.ErrorIsNil) 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]+"\n", gc.Equals, publicKey) }
// 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 }
// AddSSHAuthorizedKeys is defined on the SSHKeysConfig interface. func (cfg *cloudConfig) AddSSHAuthorizedKeys(rawKeys string) { cfgKeys, _ := cfg.attrs["ssh_authorized_keys"].([]string) keys := ssh.SplitAuthorisedKeys(rawKeys) for _, key := range keys { // ensure our keys have "Juju:" prepended in order to differenciate // Juju-managed keys and externally added ones jujuKey := ssh.EnsureJujuComment(key) cfgKeys = append(cfgKeys, jujuKey) } cfg.SetAttr("ssh_authorized_keys", cfgKeys) }
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) { 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 }
// 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 { tag, err := names.ParseTag(entity.Tag) if err != nil { results[i].Error = common.ServerError(err) continue } // 1. Check permissions if !canRead(tag) { results[i].Error = common.ServerError(common.ErrPerm) continue } // 2. Check entity exists if _, err := api.state.FindEntity(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 if configErr == nil { results[i].Result = keys } else { err = configErr } results[i].Error = common.ServerError(err) } return params.StringsResults{Results: results}, 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) } }
// 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 BootstrapParams) error { cfg := environ.Config() network.InitializeFromConfig(cfg) if secret := cfg.AdminSecret(); secret == "" { return errors.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 errors.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return errors.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return errors.Errorf("environment configuration has no ca-private-key") } // Set default tools metadata source, add image metadata source, // then verify constraints. Providers may rely on image metadata // for constraint validation. var imageMetadata []*imagemetadata.ImageMetadata if args.MetadataDir != "" { var err error imageMetadata, err = setPrivateMetadataSources(environ, args.MetadataDir) if err != nil { return err } } if err := validateConstraints(environ, args.Constraints); err != nil { return err } _, supportsNetworking := environs.SupportsNetworking(environ) ctx.Infof("Bootstrapping environment %q", cfg.Name()) logger.Debugf("environment %q supports service/machine networks: %v", cfg.Name(), supportsNetworking) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement) availableTools, err := findAvailableTools(environ, args.AgentVersion, args.Constraints.Arch, args.UploadTools) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return err } if lxcMTU, ok := cfg.LXCDefaultMTU(); ok { logger.Debugf("using MTU %v for all created LXC containers' network interfaces", lxcMTU) } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishInstanceConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. agentVersion := version.Current if args.AgentVersion != nil { agentVersion = *args.AgentVersion } if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": agentVersion.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Infof("Starting new instance for initial state server") arch, series, finalizer, err := environ.Bootstrap(ctx, environs.BootstrapParams{ Constraints: args.Constraints, Placement: args.Placement, AvailableTools: availableTools, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: arch, Series: series, }) if err != nil { return err } selectedTools, err := setBootstrapTools(environ, matchingTools) if err != nil { return err } if selectedTools.URL == "" { if !args.UploadTools { logger.Warningf("no prepackaged tools available") } ctx.Infof("Building tools to upload (%s)", selectedTools.Version) builtTools, err := sync.BuildToolsTarball(&selectedTools.Version.Number, cfg.AgentStream()) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } defer os.RemoveAll(builtTools.Dir) filename := filepath.Join(builtTools.Dir, builtTools.StorageName) selectedTools.URL = fmt.Sprintf("file://%s", filename) selectedTools.Size = builtTools.Size selectedTools.SHA256 = builtTools.Sha256Hash } ctx.Infof("Installing Juju agent on bootstrap instance") instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(args.Constraints, series) if err != nil { return err } instanceConfig.Tools = selectedTools instanceConfig.CustomImageMetadata = imageMetadata if err := finalizer(ctx, instanceConfig); err != nil { return err } ctx.Infof("Bootstrap agent installed") return 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 BootstrapParams) error { cfg := environ.Config() network.InitializeFromConfig(cfg) if secret := cfg.AdminSecret(); secret == "" { return errors.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 errors.Errorf("environment configuration has no authorized-keys") } if _, hasCACert := cfg.CACert(); !hasCACert { return errors.Errorf("environment configuration has no ca-cert") } if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { return errors.Errorf("environment configuration has no ca-private-key") } // Write out the bootstrap-init file, and confirm storage is writeable. if err := environsVerifyStorage(environ.Storage()); err != nil { return err } ctx.Infof("Bootstrapping environment %q", cfg.Name()) logger.Debugf("environment %q supports service/machine networks: %v", cfg.Name(), environ.SupportNetworks()) disableNetworkManagement, _ := cfg.DisableNetworkManagement() logger.Debugf("network management by juju enabled: %v", disableNetworkManagement) availableTools, err := findAvailableTools(environ, args.Constraints.Arch, args.UploadTools) if errors.IsNotFound(err) { return errors.New(noToolsMessage) } else if err != nil { return err } // If we're uploading, we must override agent-version; // if we're not uploading, we want to ensure we have an // agent-version set anyway, to appease FinishMachineConfig. // In the latter case, setBootstrapTools will later set // agent-version to the correct thing. if cfg, err = cfg.Apply(map[string]interface{}{ "agent-version": version.Current.Number.String(), }); err != nil { return err } if err = environ.SetConfig(cfg); err != nil { return err } ctx.Infof("Starting new instance for initial state server") arch, series, finalizer, err := environ.Bootstrap(ctx, environs.BootstrapParams{ Constraints: args.Constraints, Placement: args.Placement, AvailableTools: availableTools, }) if err != nil { return err } matchingTools, err := availableTools.Match(coretools.Filter{ Arch: arch, Series: series, }) if err != nil { return err } selectedTools, err := setBootstrapTools(environ, matchingTools) if err != nil { return err } if selectedTools.URL == "" { if !args.UploadTools { logger.Warningf("no prepackaged tools available") } ctx.Infof("Building tools to upload (%s)", selectedTools.Version) builtTools, err := sync.BuildToolsTarball(&selectedTools.Version.Number) if err != nil { return errors.Annotate(err, "cannot upload bootstrap tools") } filename := filepath.Join(builtTools.Dir, builtTools.StorageName) selectedTools.URL = fmt.Sprintf("file://%s", filename) selectedTools.Size = builtTools.Size selectedTools.SHA256 = builtTools.Sha256Hash } ctx.Infof("Installing Juju agent on bootstrap instance") machineConfig, err := environs.NewBootstrapMachineConfig(args.Constraints, series) if err != nil { return err } machineConfig.Tools = selectedTools if err := finalizer(ctx, machineConfig); err != nil { return err } ctx.Infof("Bootstrap complete") return nil }