func writeSSHKeys()

in cmd/core_plugin/firstboot/firstboot_linux.go [83:189]


func writeSSHKeys(ctx context.Context, instanceSetup *cfg.InstanceSetup) error {
	if instanceSetup == nil {
		galog.V(2).Debug("No instance setup config, skipping SSH key generation")
		return nil
	}

	hostKeyDir := instanceSetup.HostKeyDir
	dir, err := os.Open(hostKeyDir)
	if err != nil {
		return fmt.Errorf("failed to open host key dir: %w", err)
	}
	defer dir.Close()

	files, err := dir.Readdirnames(0)
	if err != nil {
		return fmt.Errorf("failed to read host key dir: %w", err)
	}

	keytypes := make(map[string]bool)

	// Find keys present on disk, and deduce their type from filename.
	for _, file := range files {
		if !hostKeyFile(file) {
			galog.V(2).Debugf("Skipping file %q, not a key file", file)
			continue
		}

		keytype := file
		keytype = strings.TrimPrefix(keytype, hostKeyFilePrefix)
		keytype = strings.TrimSuffix(keytype, hostKeyFileSuffix)
		keytypes[keytype] = true
	}

	// List keys we should generate, according to the config.
	configKeys := instanceSetup.HostKeyTypes
	for _, keytype := range strings.Split(configKeys, ",") {
		keytypes[keytype] = true
	}

	client := metadata.New()

	// Generate new keys and upload to guest attributes.
	for keytype := range keytypes {
		keyfile := path.Join(hostKeyDir, fmt.Sprintf("%s_%s_%s", hostKeyFilePrefix, keytype, hostKeyFileSuffix))
		pubKeyFile := keyfile + ".pub"

		tmpKeyFile := keyfile + ".temp"
		tmpPubKeyFile := keyfile + ".temp.pub"

		cmd := []string{"ssh-keygen", "-t", keytype, "-f", tmpKeyFile, "-N", "", "-q"}
		opts := run.Options{Name: cmd[0], Args: cmd[1:], OutputType: run.OutputNone}
		if _, err := run.WithContext(ctx, opts); err != nil {
			galog.Warnf("Failed to generate SSH host key %q: %v", keyfile, err)
			continue
		}

		if err := os.Chmod(tmpKeyFile, 0600); err != nil {
			galog.Errorf("Failed to chmod SSH host key %q: %v", tmpKeyFile, err)
			continue
		}

		if err := os.Chmod(tmpPubKeyFile, 0644); err != nil {
			galog.Errorf("Failed to chmod SSH host key %q: %v", tmpPubKeyFile, err)
			continue
		}

		if err := os.Rename(tmpKeyFile, keyfile); err != nil {
			galog.Errorf("Failed to overwrite %q: %v", keyfile, err)
			continue
		}

		if err := os.Rename(tmpPubKeyFile, pubKeyFile); err != nil {
			galog.Errorf("Failed to overwrite %q: %v", keyfile+".pub", err)
			continue
		}

		pubKey, err := os.ReadFile(pubKeyFile)
		if err != nil {
			galog.Errorf("Can't read %s public key: %v", keytype, err)
			continue
		}

		vals := strings.Split(string(pubKey), " ")
		if len(vals) < 2 {
			galog.Warnf("Generated key(%q) is malformed, not uploading", keytype)
			continue
		}

		if err := client.WriteGuestAttributes(ctx, "hostkeys/"+vals[0], vals[1]); err != nil {
			galog.Errorf("Failed to upload %s key to guest attributes: %v", keytype, err)
		}
	}

	_, err = exec.LookPath("restorecon")
	if err != nil {
		galog.Infof("restorecon not found, skipping SELinux context restoration")
		return nil
	}

	cmd := []string{"restorecon", "-FR", hostKeyDir}
	opts := run.Options{Name: cmd[0], Args: cmd[1:], OutputType: run.OutputNone}
	if _, err := run.WithContext(ctx, opts); err != nil {
		return fmt.Errorf("failed to restore SELinux context for: %s, %w", hostKeyDir, err)
	}

	return nil
}