Esempio n. 1
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
}
Esempio n. 2
0
// currentKeyDataForDelete gathers data used when deleting ssh keys.
func (api *KeyManagerAPI) currentKeyDataForDelete() (
	currentKeys []string, byFingerprint map[string]string, byComment map[string]string, err error) {

	cfg, err := api.state.ModelConfig()
	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.
	currentKeys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys())

	// Make two maps that index keys by fingerprint and by comment for fast
	// lookup of keys to delete which may be given as either.
	byFingerprint = make(map[string]string)
	byComment = make(map[string]string)
	for _, key := range currentKeys {
		fingerprint, comment, err := ssh.KeyFingerprint(key)
		if err != nil {
			logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err)
			continue
		}
		byFingerprint[fingerprint] = key
		if comment != "" {
			byComment[comment] = key
		}
	}
	return currentKeys, byFingerprint, byComment, nil
}
Esempio n. 3
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
}
Esempio n. 4
0
func annotateKeys(rawKeys string) []string {
	cfgKeys := []string{}
	keys := ssh.SplitAuthorisedKeys(rawKeys)
	for _, key := range keys {
		// ensure our keys have "Juju:" prepended to differentiate
		// Juju-managed keys and externally added ones
		jujuKey := ssh.EnsureJujuComment(key)
		cfgKeys = append(cfgKeys, jujuKey)
	}
	return cfgKeys
}
Esempio n. 5
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
}
Esempio n. 6
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
}
Esempio n. 7
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)
	}
}
Esempio n. 8
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.SetPreferIPv6(cfg.PreferIPv6())
	if secret := cfg.AdminSecret(); secret == "" {
		return errors.Errorf("model 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("model configuration has no authorized-keys")
	}
	if _, hasCACert := cfg.CACert(); !hasCACert {
		return errors.Errorf("model configuration has no ca-cert")
	}
	if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey {
		return errors.Errorf("model 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 customImageMetadata []*imagemetadata.ImageMetadata
	if args.MetadataDir != "" {
		var err error
		customImageMetadata, err = setPrivateMetadataSources(environ, args.MetadataDir)
		if err != nil {
			return err
		}
	}
	if err := validateConstraints(environ, args.ModelConstraints); err != nil {
		return err
	}
	if err := validateConstraints(environ, args.BootstrapConstraints); err != nil {
		return err
	}

	constraintsValidator, err := environ.ConstraintsValidator()
	if err != nil {
		return err
	}
	bootstrapConstraints, err := constraintsValidator.Merge(
		args.ModelConstraints, args.BootstrapConstraints,
	)
	if err != nil {
		return err
	}

	_, supportsNetworking := environs.SupportsNetworking(environ)

	var bootstrapSeries *string
	if args.BootstrapSeries != "" {
		bootstrapSeries = &args.BootstrapSeries
	}

	ctx.Infof("Bootstrapping model %q", cfg.Name())
	logger.Debugf("model %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, bootstrapConstraints.Arch,
		bootstrapSeries, args.UploadTools, args.BuildToolsTarball != nil,
	)
	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)
	}

	imageMetadata, err := bootstrapImageMetadata(
		environ, availableTools,
		args.BootstrapImage,
		&customImageMetadata,
	)
	if err != nil {
		return errors.Trace(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 FinishInstanceConfig.
	// In the latter case, setBootstrapTools will later set
	// agent-version to the correct thing.
	agentVersion := jujuversion.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 controller")
	result, err := environ.Bootstrap(ctx, environs.BootstrapParams{
		ModelConstraints:     args.ModelConstraints,
		BootstrapConstraints: args.BootstrapConstraints,
		BootstrapSeries:      args.BootstrapSeries,
		Placement:            args.Placement,
		AvailableTools:       availableTools,
		ImageMetadata:        imageMetadata,
	})
	if err != nil {
		return err
	}

	matchingTools, err := availableTools.Match(coretools.Filter{
		Arch:   result.Arch,
		Series: result.Series,
	})
	if err != nil {
		return err
	}
	selectedToolsList, err := setBootstrapTools(environ, matchingTools)
	if err != nil {
		return err
	}
	havePrepackaged := false
	for i, selectedTools := range selectedToolsList {
		if selectedTools.URL != "" {
			havePrepackaged = true
			continue
		}
		ctx.Infof("Building tools to upload (%s)", selectedTools.Version)
		builtTools, err := args.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
		selectedToolsList[i] = selectedTools
	}
	if !havePrepackaged && !args.UploadTools {
		// There are no prepackaged agents, so we must upload
		// even though the user didn't ask for it. We only do
		// this when the image-stream is not "released" and
		// the agent version hasn't been specified.
		logger.Warningf("no prepackaged tools available")
	}

	ctx.Infof("Installing Juju agent on bootstrap instance")
	publicKey, err := userPublicSigningKey()
	if err != nil {
		return err
	}
	instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(
		args.BootstrapConstraints, args.ModelConstraints, result.Series, publicKey,
	)
	if err != nil {
		return err
	}
	if err := instanceConfig.SetTools(selectedToolsList); err != nil {
		return errors.Trace(err)
	}
	instanceConfig.CustomImageMetadata = customImageMetadata
	instanceConfig.HostedModelConfig = args.HostedModelConfig

	instanceConfig.GUI = guiArchive(args.GUIDataSourceBaseURL, func(msg string) {
		ctx.Infof(msg)
	})

	if err := result.Finalize(ctx, instanceConfig); err != nil {
		return err
	}
	ctx.Infof("Bootstrap agent installed")
	return nil
}
Esempio n. 9
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 {
	if err := args.Validate(); err != nil {
		return errors.Annotate(err, "validating bootstrap parameters")
	}

	cfg := environ.Config()
	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("model configuration has no authorized-keys")
	}

	_, supportsNetworking := environs.SupportsNetworking(environ)
	logger.Debugf("model %q supports service/machine networks: %v", cfg.Name(), supportsNetworking)
	disableNetworkManagement, _ := cfg.DisableNetworkManagement()
	logger.Debugf("network management by juju enabled: %v", !disableNetworkManagement)

	// Set default tools metadata source, add image metadata source,
	// then verify constraints. Providers may rely on image metadata
	// for constraint validation.
	var customImageMetadata []*imagemetadata.ImageMetadata
	if args.MetadataDir != "" {
		var err error
		customImageMetadata, err = setPrivateMetadataSources(args.MetadataDir)
		if err != nil {
			return err
		}
	}

	var bootstrapSeries *string
	if args.BootstrapSeries != "" {
		bootstrapSeries = &args.BootstrapSeries
	}

	var bootstrapArchForImageSearch string
	if args.BootstrapConstraints.Arch != nil {
		bootstrapArchForImageSearch = *args.BootstrapConstraints.Arch
	} else if args.ModelConstraints.Arch != nil {
		bootstrapArchForImageSearch = *args.ModelConstraints.Arch
	} else {
		bootstrapArchForImageSearch = arch.HostArch()
		// We no longer support i386.
		if bootstrapArchForImageSearch == arch.I386 {
			bootstrapArchForImageSearch = arch.AMD64
		}
	}

	ctx.Verbosef("Loading image metadata")
	imageMetadata, err := bootstrapImageMetadata(environ,
		bootstrapSeries,
		bootstrapArchForImageSearch,
		args.BootstrapImage,
		&customImageMetadata,
	)
	if err != nil {
		return errors.Trace(err)
	}

	// We want to determine a list of valid architectures for which to pick tools and images.
	// This includes architectures from custom and other available image metadata.
	architectures := set.NewStrings()
	if len(customImageMetadata) > 0 {
		for _, customMetadata := range customImageMetadata {
			architectures.Add(customMetadata.Arch)
		}
	}
	if len(imageMetadata) > 0 {
		for _, iMetadata := range imageMetadata {
			architectures.Add(iMetadata.Arch)
		}
	}

	constraintsValidator, err := environ.ConstraintsValidator()
	if err != nil {
		return err
	}
	constraintsValidator.UpdateVocabulary(constraints.Arch, architectures.SortedValues())

	bootstrapConstraints, err := constraintsValidator.Merge(
		args.ModelConstraints, args.BootstrapConstraints,
	)
	if err != nil {
		return errors.Trace(err)
	}

	// The arch we use to find tools isn't the boostrapConstraints arch.
	// We copy the constraints arch to a separate variable and
	// update it from the host arch if not specified.
	// (axw) This is still not quite right:
	// For e.g. if there is a MAAS with only ARM64 machines,
	// on an AMD64 client, we're going to look for only AMD64 tools,
	// limiting what the provider can bootstrap anyway.
	var bootstrapArch string
	if bootstrapConstraints.Arch != nil {
		bootstrapArch = *bootstrapConstraints.Arch
	} else {
		// If no arch is specified as a constraint, we'll bootstrap
		// on the same arch as the client used to bootstrap.
		bootstrapArch = arch.HostArch()
		// We no longer support controllers on i386.
		// If we are bootstrapping from an i386 client,
		// we'll look for amd64 tools.
		if bootstrapArch == arch.I386 {
			bootstrapArch = arch.AMD64
		}
	}

	var availableTools coretools.List
	if !args.BuildAgent {
		ctx.Infof("Looking for packaged Juju agent version %s for %s", args.AgentVersion, bootstrapArch)
		availableTools, err = findPackagedTools(environ, args.AgentVersion, &bootstrapArch, bootstrapSeries)
		if err != nil && !errors.IsNotFound(err) {
			return err
		}
	}
	// If there are no prepackaged tools and a specific version has not been
	// requested, look for or build a local binary.
	var builtTools *sync.BuiltAgent
	if len(availableTools) == 0 && (args.AgentVersion == nil || isCompatibleVersion(*args.AgentVersion, jujuversion.Current)) {
		if args.BuildAgentTarball == nil {
			return errors.New("cannot build agent binary to upload")
		}
		if err := validateUploadAllowed(environ, &bootstrapArch, bootstrapSeries, constraintsValidator); err != nil {
			return err
		}
		if args.BuildAgent {
			ctx.Infof("Building local Juju agent binary version %s for %s", args.AgentVersion, bootstrapArch)
		} else {
			ctx.Infof("No packaged binary found, preparing local Juju agent binary")
		}
		var forceVersion version.Number
		availableTools, forceVersion = locallyBuildableTools(bootstrapSeries)
		builtTools, err = args.BuildAgentTarball(args.BuildAgent, &forceVersion, cfg.AgentStream())
		if err != nil {
			return errors.Annotate(err, "cannot package bootstrap agent binary")
		}
		defer os.RemoveAll(builtTools.Dir)
		for i, tool := range availableTools {
			if tool.URL != "" {
				continue
			}
			filename := filepath.Join(builtTools.Dir, builtTools.StorageName)
			tool.URL = fmt.Sprintf("file://%s", filename)
			tool.Size = builtTools.Size
			tool.SHA256 = builtTools.Sha256Hash
			availableTools[i] = tool
		}
	}
	if len(availableTools) == 0 {
		return errors.New(noToolsMessage)
	}

	// 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 := jujuversion.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.Verbosef("Starting new instance for initial controller")

	result, err := environ.Bootstrap(ctx, environs.BootstrapParams{
		CloudName:            args.CloudName,
		CloudRegion:          args.CloudRegion,
		ControllerConfig:     args.ControllerConfig,
		ModelConstraints:     args.ModelConstraints,
		BootstrapConstraints: bootstrapConstraints,
		BootstrapSeries:      args.BootstrapSeries,
		Placement:            args.Placement,
		AvailableTools:       availableTools,
		ImageMetadata:        imageMetadata,
	})
	if err != nil {
		return err
	}

	matchingTools, err := availableTools.Match(coretools.Filter{
		Arch:   result.Arch,
		Series: result.Series,
	})
	if err != nil {
		return err
	}
	selectedToolsList, err := getBootstrapToolsVersion(matchingTools)
	if err != nil {
		return err
	}
	// We set agent-version to the newest version, so the agent will immediately upgrade itself.
	// Note that this only is relevant if a specific agent version has not been requested, since
	// in that case the specific version will be the only version available.
	newestVersion, _ := matchingTools.Newest()
	if err := setBootstrapToolsVersion(environ, newestVersion); err != nil {
		return err
	}

	logger.Infof("Installing Juju agent on bootstrap instance")
	publicKey, err := userPublicSigningKey()
	if err != nil {
		return err
	}
	instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(
		args.ControllerConfig,
		bootstrapConstraints,
		args.ModelConstraints,
		result.Series,
		publicKey,
	)
	if err != nil {
		return err
	}
	if err := instanceConfig.SetTools(selectedToolsList); err != nil {
		return errors.Trace(err)
	}
	// Make sure we have the most recent environ config as the specified
	// tools version has been updated there.
	cfg = environ.Config()
	if err := finalizeInstanceBootstrapConfig(ctx, instanceConfig, args, cfg, customImageMetadata); err != nil {
		return errors.Annotate(err, "finalizing bootstrap instance config")
	}
	if err := result.Finalize(ctx, instanceConfig, args.DialOpts); err != nil {
		return err
	}
	ctx.Infof("Bootstrap agent now started")
	return nil
}