Пример #1
0
// 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)
}
Пример #2
0
// 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
}
Пример #3
0
// 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
}
Пример #4
0
// 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
}
Пример #5
0
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)
}
Пример #6
0
// 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
}
Пример #7
0
// 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)
}
Пример #8
0
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)
}
Пример #9
0
// 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
}
Пример #10
0
// 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
}
Пример #11
0
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")
}
Пример #12
0
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)
	}
}
Пример #13
0
// 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
}
Пример #14
0
// 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
}