commands

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 9, 2023 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Decrypt = &cobra.Command{
	Use:   "decrypt",
	Short: "Decrypt key(s) and print to stdout",
	RunE: func(cmd *cobra.Command, args []string) error {
		config, err := config.GetConfig()
		if err != nil {
			return err
		}

		keeper, err := crypt.NewKeeper(config.Encryption.Type, config.Encryption.KeyPath)
		if err != nil {
			return err
		}

		var envKeys []string
		switch allKeys {
		case true:
			envKeys = config.Env.Keys()
		default:
			err = validateEnv(config.Env, args)
			if err != nil {
				return err
			}

			envKeys = args
		}

		for _, key := range envKeys {
			value, err := keeper.Decrypt(key, config.Env[key])
			if err != nil {
				log.
					WithField("err", err.Error()).
					Warn("failed to decrypt value")
			}

			if withKey {
				fmt.Printf("%s=%s\n", key, value)
			} else {
				fmt.Printf("%s\n", value)
			}
		}

		return nil
	},
}
View Source
var Direnv = &cobra.Command{
	Use:       "direnv",
	Short:     "Integrate with direnv",
	Args:      cobra.ExactArgs(1),
	ValidArgs: []string{"bash", "zsh", "fish"},
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg, err := config.GetConfig()
		if err != nil {
			return err
		}

		sh := shell.Detect(args[0])
		envrcPath := direnv.EnvrcPath()

		var integrate bool
		switch {
		case yesPrompt:
			if len(envrcPath) > 0 {
				integrate = true
			} else {
				logrus.Println("No .envrc file detected. Skipping direnv integration.")
			}
		case len(envrcPath) > 0:
			output := promptUserf(".envrc file detected at %s. Would you like to integrate with direnv? [Y/n]: ", envrcPath)
			if output == "y" || output == "yes" {
				integrate = true
			}
		}

		if integrate {
			if !direnv.IsInstalled() {
				fmt.Println(`"direnv" is not installed. Install "direnv" and try again`)
				os.Exit(1)
			}

			exists, err := fileutils.TextExistsInFile(envrcPath, fmt.Sprintf(`eval "$(cryptkeeper export %s)"`, sh.Shell()))
			if err != nil || !exists {
				fmt.Printf("Add the following to your .envrc file:\n\n%s\n\n", direnv.EvalStatement(sh.Shell()))
			}

			_ = direnv.Reload()

			cfg.Mode = config.DirenvMode
		} else {
			return nil
		}

		err = config.Write(cfg)
		if err != nil {
			return fmt.Errorf("error writing config: %w", err)
		}

		return nil
	},
}
View Source
var Env = &cobra.Command{
	Use:       "env",
	Short:     "Export or unset decrypted environment variables",
	Hidden:    true,
	Args:      cobra.ExactArgs(1),
	ValidArgs: []string{"bash", "zsh", "fish"},
	Run: func(cmd *cobra.Command, args []string) {
		sh := shell.Detect(args[0])

		cfg, err := config.GetConfig()
		if err != nil {
			pathErr := new(*os.PathError)
			if (errors.Is(err, fileutils.ErrFileNotFound) || errors.As(err, pathErr)) && envVarExists(config.CKWatchEnvKey) && envVarExists(config.CKRevertEnvKey) {
				fmt.Print(unloadDiff(config.CKRevertEnvKey, sh))
			}
			return
		}

		if watchPath, ok := os.LookupEnv(config.CKWatchEnvKey); ok && watchPath != cfg.Path {
			diffString := unloadDiff(config.CKRevertEnvKey, sh)
			log.WithFields(log.Fields{
				"watch_path":  watchPath,
				"config_path": cfg.Path,
				"diff":        diffString,
			}).Debug("reverting env")

			fmt.Print(diffString)
		}

		if cfg.Mode == config.DirenvMode {

			fmt.Print(sh.Unset(config.CKRevertEnvKey) + sh.Unset(config.CKLastEnvKey))
			return
		}

		keeper, err := crypt.NewKeeper(cfg.Encryption.Type, cfg.Encryption.KeyPath)
		if err != nil {
			return
		}

		cwd, err := os.Getwd()
		if err != nil {
			return
		}

		exportEnv, err := fileutils.IsChildDirOrSame(cwd, filepath.Dir(cfg.Path))
		if err != nil {
			return
		}

		if !exportEnv {
			fmt.Print(unloadDiff(config.CKRevertEnvKey, sh))
			return
		}

		lastEnv, err := envdiff.FetchLastEnv(config.CKLastEnvKey, keeper)
		if err != nil {
			log.WithError(err).Debug("failed to fetch last env")
			return
		}

		revertEnv, err := envdiff.FetchRevert(config.CKRevertEnvKey)
		if err != nil {
			log.WithError(err).Debug("failed to fetch revert enkv")
			return
		}

		currentEnv := cfg.Env
		err = currentEnv.Decrypt(keeper)
		if err != nil {
			log.WithError(err).Debug("failed to decrypt env")
			return
		}

		var firstLoad bool
		if loading() {
			log.Info("cryptkeeper: loading")
			firstLoad = true
		}

		log.WithFields(log.Fields{
			"CURRENT_ENV": currentEnv,
			"LAST_ENV":    lastEnv,
			"REVERT_ENV":  revertEnv,
		}).Debug("envs")

		if sameEnv(lastEnv, currentEnv) && len(revertEnv) == len(lastEnv) {
			log.Debug("cryptkeeper: no changes")
			if firstLoad {
				diffString := exportAllEnvs(cfg.Path, currentEnv, revertEnv, sh, keeper)
				log.WithField("diff", diffString).Debug("exporting")
				fmt.Print(diffString)
			}
			return
		}

		if out := diffStatus(envdiff.BuildEnvDiff(lastEnv, currentEnv)); out != "" {
			log.Infof("cryptkeeper: export %s", out)
		}

		newLast := lastEnv.Copy()
		for k, v := range currentEnv {
			if !utils.In(k, revertEnv) && !utils.In(k, lastEnv) {
				val, ok := os.LookupEnv(k)
				if ok {
					revertEnv[k] = utils.ToPtr(val)
				} else {
					revertEnv[k] = nil
				}
			}

			newLast[k] = v
		}

		for k := range lastEnv {
			if !utils.In(k, currentEnv) {
				delete(newLast, k)
			}
		}

		newRevertEnv := lo.MapValues(revertEnv, func(value *string, _ string) string {
			if value == nil {
				return ""
			}
			return *value
		})

		log.WithField("env", newRevertEnv).Debug("new revert env")

		diffString := envdiff.BuildEnvDiff(newRevertEnv, newLast).ToShell(sh)

		for k := range newRevertEnv {
			if !utils.In(k, newLast) {
				delete(revertEnv, k)
			}
		}

		diffString += exportAllEnvs(cfg.Path, currentEnv, revertEnv, sh, keeper)

		log.Debugf("env diff %s", diffString)
		fmt.Print(diffString)
	},
}
View Source
var Export = &cobra.Command{
	Use:       "export",
	Short:     "Export decrypted environment variables",
	Hidden:    true,
	Args:      cobra.ExactArgs(1),
	ValidArgs: []string{"bash", "zsh", "fish"},
	Run: func(cmd *cobra.Command, args []string) {
		cfg, err := config.GetConfig()
		if err != nil {
			return
		}

		target := args[0]

		sh := shell.Detect(target)

		if target == "fish" {
			sh = shell.Bash
		}

		keeper, err := crypt.NewKeeper(cfg.Encryption.Type, cfg.Encryption.KeyPath)
		if err != nil {
			return
		}

		exported := ""
		for key, cipher := range cfg.Env {
			decryptedValue, err := keeper.Decrypt(key, cipher)
			if err != nil {
				log.
					WithField("err", err.Error()).
					Warn("failed to decrypt value")
			}

			exported += sh.Export(key, decryptedValue)
		}

		exported += sh.Export(config.CKWatchEnvKey, cfg.Path)

		if _, ok := os.LookupEnv(config.CKWatchEnvKey); !ok {
			log.Info("cryptkeeper: loading")
		}

		fmt.Print(exported)
	},
}
View Source
var Hook = &cobra.Command{
	Use:       "hook",
	Short:     "Prints the shell hook to stdout",
	Hidden:    true,
	Args:      cobra.ExactArgs(1),
	ValidArgs: []string{"bash", "zsh", "fish"},
	RunE: func(cmd *cobra.Command, args []string) error {
		selfPath, err := os.Executable()
		if err != nil {
			return err
		}

		selfPath = strings.Replace(selfPath, "\\", "/", -1)

		ctx := hookContext{
			SelfPath: selfPath,
		}

		sh := shell.Detect(args[0])

		hookTemplate, err := template.New("hook").Parse(sh.Hook())
		if err != nil {
			return err
		}

		err = hookTemplate.Execute(os.Stdout, ctx)
		if err != nil {
			return err
		}

		return nil
	},
}
View Source
var Init = &cobra.Command{
	Use:       "init",
	Short:     "Initialize cryptkeeper",
	Args:      cobra.ExactArgs(1),
	ValidArgs: []string{"bash", "zsh", "fish"},
	RunE: func(cmd *cobra.Command, args []string) error {
		keyPath = fileutils.Clean(keyPath)
		configPath := fileutils.Clean(config.FileName())

		if fileutils.FileExists(keyPath) {
			return fmt.Errorf("key file already exists at %s", keyPath)
		}
		if fileutils.FileExists(configPath) {
			return fmt.Errorf("config file already exists at %s", fileutils.Clean(config.FileName()))
		}

		var encType crypt.EncryptionType
		var err error
		switch strings.ToLower(encryption) {
		case "aes", "aes256", "aes-256":
			encType = crypt.AES256
		case "rsa", "rsa2048", "rsa-2048":
			encType = crypt.RSA2048
		case "ecc", "ecc256", "ecc-256":
			encType = crypt.ECC256
		case "serpent", "serpent256", "serpent-256":
			encType = crypt.Serpent256
		default:
			return crypt.ErrUnknownEncryptionType
		}

		err = crypt.GenerateKeys(encType, keyPath)
		if err != nil {
			return err
		}

		cfg := &config.Config{
			Encryption: config.Encryption{
				KeyPath: keyPath,
				Type:    encType,
			},
			Env:  make(config.Env),
			Path: configPath,
		}

		envrcPath := direnv.EnvrcPath()
		sh := shell.Detect(args[0])

		var integrate bool
		switch {
		case standalone:
			fmt.Println("Running in standalone mode. Skipping direnv integration.")
		default:
			integrate = true
		}

		if integrate {
			if !direnv.IsInstalled() {
				fmt.Println("direnv is not installed. Please install direnv before adding secrets.")
			}

			exists, err := fileutils.TextExistsInFile(envrcPath, fmt.Sprintf(`eval "$(cryptkeeper export %s)"`, sh.Shell()))
			if err != nil || !exists {
				fmt.Printf("Add this to your .envrc file:\n\n%s\n\n", direnv.EvalStatement(sh.Shell()))
			}

			cfg.Mode = config.DirenvMode
		} else {
			rcPath, err := fileutils.FindPathTo(sh.RCFile())

			var exists bool
			if len(rcPath) > 0 {
				exists, err = fileutils.TextExistsInFile(rcPath, fmt.Sprintf(`eval "$(cryptkeeper hook %s)"`, sh.Shell()))
			}
			if err != nil || !exists {
				fmt.Printf("Add this to your %s file:\n\neval \"$(cryptkeeper hook %s)\"\n\n", sh.RCFile(), sh.Shell())
			}

			cfg.Mode = config.StandaloneMode
		}

		err = config.Init(cfg)
		if err != nil {
			return fmt.Errorf("error writing config: %w", err)
		}

		fmt.Printf("Initialized config in %s and key in %s\n", fileutils.Clean(config.FileName()), keyPath)

		return nil
	},
}
View Source
var Remove = &cobra.Command{
	Use:   "remove",
	Short: "Remove one or more key-value pairs from the config",
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg, err := config.GetConfig()
		if err != nil {
			return err
		}

		envKeys := args
		if allKeys {
			envKeys = cfg.Env.Keys()
		}

		for _, key := range envKeys {
			delete(cfg.Env, key)
		}

		if cfg.Encryption.Type == crypt.ECC256 {
			keeper, err := crypt.NewKeeper(cfg.Encryption.Type, cfg.Encryption.KeyPath)
			if err != nil {
				logrus.
					WithError(err).
					Debug("failed to remove encryption keys")
			}

			if keeper != nil {
				for _, envKey := range envKeys {
					err = keeper.RemoveKey(envKey)
					if err != nil {
						logrus.
							WithError(err).
							Debugf("failed to remove encryption key for %s", envKey)
					}
				}
			}
		}

		err = config.Write(cfg)
		if err != nil {
			return err
		}

		if cfg.IsDirenvIntegrated() {
			return direnv.ReloadEnv()
		}

		return nil
	},
}
View Source
var Set = &cobra.Command{
	Use:     "set",
	Aliases: []string{"add"},
	Short:   "Set a new key-value pair",
	Args:    cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		key, value := args[0], ""

		cfg, err := config.GetConfig()
		if err != nil {
			return errors.New("failed to get config")
		}

		switch {
		case useClipboard:
			value, err = clipboard.ReadAll()
			if err != nil {
				logrus.
					WithError(err).
					Warn("failed to get value from clipboard")
			}
		case isInputPiped():
			reader := bufio.NewReader(os.Stdin)
			value, err = reader.ReadString('\n')
			if err != nil {
				logrus.
					WithError(err).
					Warn("failed to get piped in value")
			}
		}

		if value == "" {
			fmt.Print("Enter value (it won't be displayed):\n")
			byteValue, err := term.ReadPassword(int(os.Stdin.Fd()))
			if err != nil {
				return fmt.Errorf("error reading value: %w", err)
			}

			value = string(byteValue)
		}

		value = strings.TrimSuffix(value, "\n")

		keeper, err := crypt.NewKeeper(cfg.Encryption.Type, cfg.Encryption.KeyPath)
		if err != nil {
			return err
		}

		encryptedValue, err := keeper.Encrypt(key, value)
		if err != nil {
			return fmt.Errorf("failed to encrypt value: %w", err)
		}

		if cfg.Env == nil {
			cfg.Env = make(map[string]string)
		}

		cfg.Env[key] = encryptedValue

		err = config.Write(cfg)
		if err != nil {
			return errors.New("failed to write config")
		}

		if cfg.IsDirenvIntegrated() {
			return direnv.ReloadEnv()
		}

		return nil
	},
}
View Source
var Verify = &cobra.Command{
	Use:     "verify",
	Aliases: []string{"check"},
	Short:   "Verifies the encrypted value of a specified secret key",
	Long:    "Verifies if the provided value matches the encrypted value of a specified secret key, without revealing the actual secret. Useful for confirming the integrity or correctness of a stored secret.",
	Args:    cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		envKey, expectedValue := args[0], ""

		cfg, err := config.GetConfig()
		if err != nil {
			return errors.New("failed to get config")
		}

		cipher, ok := cfg.Env[envKey]
		if !ok {
			return fmt.Errorf("secret %s does not exist", envKey)
		}

		switch {
		case useClipboard:
			expectedValue, err = clipboard.ReadAll()
			if err != nil {
				logrus.WithError(err).Debug("failed to get value from clipboard")
			}
		case isInputPiped():
			reader := bufio.NewReader(os.Stdin)
			expectedValue, err = reader.ReadString('\n')
			if err != nil {
				logrus.WithError(err).Debug("failed to get piped in value")
			}

			expectedValue = expectedValue[:len(expectedValue)-1]
		}

		if expectedValue == "" {
			fmt.Print("Enter expected value (it won't be displayed): ")
			byteValue, err := term.ReadPassword(int(os.Stdin.Fd()))
			if err != nil {
				return fmt.Errorf("error reading value: %w", err)
			}

			expectedValue = string(byteValue)
		}

		keeper, err := crypt.NewKeeper(cfg.Encryption.Type, cfg.Encryption.KeyPath)
		if err != nil {
			return err
		}

		decryptedValue, err := keeper.Decrypt(envKey, cipher)
		if err != nil {
			return fmt.Errorf("failed to encrypt value: %w", err)
		}

		if subtle.ConstantTimeCompare([]byte(decryptedValue), []byte(expectedValue)) == 1 {
			fmt.Print("equal")
		} else {
			fmt.Print("not-equal")
		}

		return nil
	},
}
View Source
var Version = &cobra.Command{
	Use:     "version",
	Aliases: []string{"v"},
	Short:   "Prints the version number of cryptkeeper",
	Long:    "Prints the version number of cryptkeeper",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println(version.Version)
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL