diff --git a/internal/boxcli/auth.go b/internal/boxcli/auth.go index 5a4c87679f4..044742de7f4 100644 --- a/internal/boxcli/auth.go +++ b/internal/boxcli/auth.go @@ -5,14 +5,11 @@ package boxcli import ( "fmt" - "os" "strings" "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" "go.jetify.com/devbox/internal/build" - "go.jetify.com/devbox/internal/devbox" - "go.jetify.com/devbox/internal/devbox/devopt" "go.jetify.com/devbox/internal/devbox/providers/identity" "go.jetify.com/devbox/internal/ux" "go.jetify.com/pkg/api" @@ -26,7 +23,6 @@ func authCmd() *cobra.Command { cmd.AddCommand(loginCmd()) cmd.AddCommand(logoutCmd()) - cmd.AddCommand(whoAmICmd()) cmd.AddCommand(authNewTokenCommand()) return cmd @@ -77,47 +73,6 @@ func logoutCmd() *cobra.Command { return cmd } -type whoAmICmdFlags struct { - showTokens bool -} - -func whoAmICmd() *cobra.Command { - flags := &whoAmICmdFlags{} - cmd := &cobra.Command{ - Use: "whoami", - Short: "Show the current user", - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - wd, err := os.Getwd() - if err != nil { - return err - } - box, err := devbox.Open(&devopt.Opts{Dir: wd, Stderr: cmd.ErrOrStderr()}) - if err != nil { - return err - } - // TODO: WhoAmI should be a function in opensource/pkg/auth that takes in a session. - // That way we don't need to handle failed refresh token errors here. - err = box.UninitializedSecrets(cmd.Context()). - WhoAmI(cmd.Context(), cmd.OutOrStdout(), flags.showTokens) - if identity.IsRefreshTokenError(err) { - ux.Fwarningf(cmd.ErrOrStderr(), "Your session is expired. Please login again.\n") - return loginCmd().RunE(cmd, args) - } - return err - }, - } - - cmd.Flags().BoolVar( - &flags.showTokens, - "show-tokens", - false, - "Show the access, id, and refresh tokens", - ) - - return cmd -} - func authNewTokenCommand() *cobra.Command { tokensCmd := &cobra.Command{ Use: "tokens", diff --git a/internal/boxcli/root.go b/internal/boxcli/root.go index 40571594ed0..02126786917 100644 --- a/internal/boxcli/root.go +++ b/internal/boxcli/root.go @@ -59,7 +59,6 @@ func RootCmd() *cobra.Command { command.AddCommand(authCmd()) } command.AddCommand(createCmd()) - command.AddCommand(secretsCmd()) command.AddCommand(generateCmd()) command.AddCommand(globalCmd()) command.AddCommand(infoCmd()) diff --git a/internal/boxcli/secrets.go b/internal/boxcli/secrets.go deleted file mode 100644 index 623036fd51e..00000000000 --- a/internal/boxcli/secrets.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2024 Jetify Inc. and contributors. All rights reserved. -// Use of this source code is governed by the license in the LICENSE file. - -package boxcli - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" - "go.jetify.com/devbox/internal/devbox" - "go.jetify.com/devbox/internal/devbox/devopt" - "go.jetify.com/devbox/internal/fileutil" - "go.jetify.com/devbox/internal/ux" - "go.jetify.com/envsec/pkg/envsec" -) - -type secretsFlags struct { - config configFlags -} - -func (f *secretsFlags) envsec(cmd *cobra.Command) (*envsec.Envsec, error) { - box, err := devbox.Open(&devopt.Opts{ - Dir: f.config.path, - Environment: f.config.environment, - Stderr: cmd.ErrOrStderr(), - }) - if err != nil { - return nil, errors.WithStack(err) - } - - return box.Secrets(cmd.Context()) -} - -type secretsInitCmdFlags struct { - force bool -} - -type secretsListFlags struct { - show bool - format string -} - -type secretsDownloadFlags struct { - format string -} - -type secretsUploadFlags struct { - format string -} - -func secretsCmd() *cobra.Command { - flags := &secretsFlags{} - cmd := &cobra.Command{ - Use: "secrets", - Aliases: []string{"envsec"}, - Short: "Interact with devbox secrets in jetify cloud.", - PersistentPreRunE: ensureNixInstalled, - } - cmd.AddCommand(secretsDownloadCmd(flags)) - cmd.AddCommand(secretsInitCmd(flags)) - cmd.AddCommand(secretsListCmd(flags)) - cmd.AddCommand(secretsRemoveCmd(flags)) - cmd.AddCommand(secretsSetCmd(flags)) - cmd.AddCommand(secretsUploadCmd(flags)) - - flags.config.registerPersistent(cmd) - - return cmd -} - -func secretsInitCmd(secretsFlags *secretsFlags) *cobra.Command { - flags := secretsInitCmdFlags{} - cmd := &cobra.Command{ - Use: "init", - Short: "Initialize secrets management with jetify cloud", - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - return secretsInitFunc(cmd, flags, secretsFlags) - }, - } - - cmd.Flags().BoolVarP( - &flags.force, - "force", - "f", - false, - "Force initialization even if already initialized", - ) - - return cmd -} - -func secretsSetCmd(flags *secretsFlags) *cobra.Command { - return &cobra.Command{ - Use: "set = [=]...", - Short: "Securely store one or more environment variables", - Long: "Securely store one or more environment variables. To test contents of a file as a secret use set=@", - Args: cobra.MinimumNArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { - return envsec.ValidateSetArgs(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - secrets, err := flags.envsec(cmd) - if err != nil { - return errors.WithStack(err) - } - - return secrets.SetFromArgs(cmd.Context(), args) - }, - } -} - -func secretsRemoveCmd(flags *secretsFlags) *cobra.Command { - return &cobra.Command{ - Use: "remove []...", - Short: "Remove one or more environment variables", - Aliases: []string{"rm"}, - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - secrets, err := flags.envsec(cmd) - if err != nil { - return errors.WithStack(err) - } - - return secrets.DeleteAll(cmd.Context(), args...) - }, - } -} - -func secretsListCmd(commonFlags *secretsFlags) *cobra.Command { - flags := secretsListFlags{} - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all secrets", - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - secrets, err := commonFlags.envsec(cmd) - if err != nil { - return errors.WithStack(err) - } - - vars, err := secrets.List(cmd.Context()) - if err != nil { - return err - } - - return envsec.PrintEnvVar( - cmd.OutOrStdout(), secrets.EnvID, vars, flags.show, flags.format) - }, - } - - cmd.Flags().BoolVarP( - &flags.show, - "show", - "s", - false, - "Display secret values in plaintext", - ) - cmd.Flags().StringVarP( - &flags.format, - "format", - "f", - "table", - "Display the key values of each secret in the specified format, one of: table | dotenv | json.", - ) - return cmd -} - -func secretsDownloadCmd(commonFlags *secretsFlags) *cobra.Command { - flags := secretsDownloadFlags{} - command := &cobra.Command{ - Use: "download ", - Short: "Download environment variables into the specified file", - Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { - return envsec.ValidateFormat(flags.format) - }, - RunE: func(cmd *cobra.Command, args []string) error { - secrets, err := commonFlags.envsec(cmd) - if err != nil { - return errors.WithStack(err) - } - absPaths, err := fileutil.EnsureAbsolutePaths(args) - if err != nil { - return errors.WithStack(err) - } - return secrets.Download(cmd.Context(), absPaths[0], flags.format) - }, - } - - command.Flags().StringVarP( - &flags.format, "format", "f", "", "file format: dotenv or json") - - return command -} - -func secretsUploadCmd(commonFlags *secretsFlags) *cobra.Command { - flags := &secretsUploadFlags{} - command := &cobra.Command{ - Use: "upload []...", - Short: "Upload variables defined in one or more .env files.", - Args: cobra.MinimumNArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { - return envsec.ValidateFormat(flags.format) - }, - RunE: func(cmd *cobra.Command, paths []string) error { - secrets, err := commonFlags.envsec(cmd) - if err != nil { - return errors.WithStack(err) - } - absPaths, err := fileutil.EnsureAbsolutePaths(paths) - if err != nil { - return errors.WithStack(err) - } - return secrets.Upload(cmd.Context(), absPaths, flags.format) - }, - } - - command.Flags().StringVarP( - &flags.format, "format", "f", "", "File format: dotenv or json") - - return command -} - -func secretsInitFunc( - cmd *cobra.Command, - flags secretsInitCmdFlags, - secretsFlags *secretsFlags, -) error { - ctx := cmd.Context() - box, err := devbox.Open(&devopt.Opts{ - Dir: secretsFlags.config.path, - Stderr: cmd.ErrOrStderr(), - }) - if err != nil { - return errors.WithStack(err) - } - - // devbox.Secrets() by default assumes project is initialized (and shows - // error if not). So we use UninitializedSecrets() here instead. - secrets := box.UninitializedSecrets(ctx) - - if _, err := secrets.ProjectConfig(); err == nil && - !box.Config().IsEnvsecEnabled() { - // Handle edge case where directory is already set up, but devbox.json is - // not configured to use jetpack-cloud. - ux.Finfof( - cmd.ErrOrStderr(), - "Secrets already initialized. Adding to devbox config.\n", - ) - } else if err := secrets.NewProject(ctx, flags.force); err != nil { - return errors.WithStack(err) - } - box.Config().Root.SetStringField("EnvFrom", "jetpack-cloud") - return box.Config().Root.SaveTo(box.ProjectDir()) -} diff --git a/internal/devbox/devbox.go b/internal/devbox/devbox.go index 4c3f82421ce..10323d90ccc 100644 --- a/internal/devbox/devbox.go +++ b/internal/devbox/devbox.go @@ -32,7 +32,6 @@ import ( "go.jetify.com/devbox/internal/devbox/envpath" "go.jetify.com/devbox/internal/devbox/generate" "go.jetify.com/devbox/internal/devconfig" - "go.jetify.com/devbox/internal/devconfig/configfile" "go.jetify.com/devbox/internal/devpkg" "go.jetify.com/devbox/internal/devpkg/pkgtype" "go.jetify.com/devbox/internal/envir" @@ -758,7 +757,7 @@ func (d *Devbox) computeEnv( env["DEVBOX_PACKAGES_DIR"] = d.projectDir + "/" + nix.ProfilePath // Include env variables in devbox.json - configEnv, err := d.configEnvs(ctx, env) + configEnv, err := d.configEnvs(env) if err != nil { return nil, err } @@ -996,36 +995,17 @@ func (d *Devbox) checkOldEnvrc() error { // allow env variables from outside the shell to be referenced so // no leaked variables are caused by this function. func (d *Devbox) configEnvs( - ctx context.Context, existingEnv map[string]string, ) (map[string]string, error) { defer debug.FunctionTimer().End() env := map[string]string{} - if d.cfg.IsEnvsecEnabled() { - secrets, err := d.Secrets(ctx) - // TODO: replace this with error.Is check once envsec exports it. - if err != nil && !strings.Contains(err.Error(), "project not initialized") { - return nil, err - } else if err != nil { - ux.Fwarningf( - d.stderr, - "Ignoring env_from directive. jetify cloud secrets is not "+ - "initialized. Run `devbox secrets init` to initialize it.\n", - ) - } else { - cloudSecrets, err := secrets.List(ctx) - if err != nil { - ux.Fwarningf( - os.Stderr, - "Error reading secrets from jetify cloud: %s\n\n", - err, - ) - } else { - for _, secret := range cloudSecrets { - env[secret.Name] = secret.Value - } - } - } + if d.cfg.IsJetifyCloudEnvFrom() { + ux.Fwarningf( + d.stderr, + "Ignoring env_from = %q. Jetify Cloud secrets are no longer "+ + "supported by Devbox.\n", + d.cfg.Root.EnvFrom, + ) } else if d.cfg.Root.IsdotEnvEnabled() { // if env_from points to a .env file, parse and add it parsedEnvs, err := d.cfg.Root.ParseEnvsFromDotEnv() @@ -1043,9 +1023,8 @@ func (d *Devbox) configEnvs( } } else if d.cfg.Root.EnvFrom != "" { return nil, usererr.New( - "unknown env_from value: %s. Supported values are: \"%q\" or a path to a file ending in \".env\"", + "unknown env_from value: %s. It must be a path to a file ending in \".env\"", d.cfg.Root.EnvFrom, - configfile.JetifyCloudEnvFromValue, ) } for k, v := range d.cfg.Env() { diff --git a/internal/devbox/secrets.go b/internal/devbox/secrets.go deleted file mode 100644 index 611f696acbd..00000000000 --- a/internal/devbox/secrets.go +++ /dev/null @@ -1,45 +0,0 @@ -package devbox - -import ( - "context" - - "go.jetify.com/devbox/internal/build" - "go.jetify.com/envsec/pkg/envsec" - "go.jetify.com/envsec/pkg/stores/jetstore" - "go.jetify.com/pkg/envvar" -) - -func (d *Devbox) UninitializedSecrets(ctx context.Context) *envsec.Envsec { - return &envsec.Envsec{ - APIHost: build.JetpackAPIHost(), - Auth: envsec.AuthConfig{ - ClientID: envvar.Get("ENVSEC_CLIENT_ID", build.ClientID()), - Issuer: envvar.Get("ENVSEC_ISSUER", build.Issuer()), - }, - IsDev: build.IsDev, - Stderr: d.stderr, - Store: &jetstore.JetpackAPIStore{}, - WorkingDir: d.ProjectDir(), - } -} - -func (d *Devbox) Secrets(ctx context.Context) (*envsec.Envsec, error) { - envsecInstance := d.UninitializedSecrets(ctx) - - project, err := envsecInstance.ProjectConfig() - if err != nil { - return nil, err - } - - envsecInstance.EnvID = envsec.EnvID{ - EnvName: d.environment, - OrgID: project.OrgID.String(), - ProjectID: project.ProjectID.String(), - } - - if _, err := envsecInstance.InitForUser(ctx); err != nil { - return nil, err - } - - return envsecInstance, nil -} diff --git a/internal/devconfig/config.go b/internal/devconfig/config.go index c7e38d02965..1151deb14a3 100644 --- a/internal/devconfig/config.go +++ b/internal/devconfig/config.go @@ -407,13 +407,13 @@ func (c *Config) Hash() (string, error) { return cachehash.Bytes(data), nil } -func (c *Config) IsEnvsecEnabled() bool { +func (c *Config) IsJetifyCloudEnvFrom() bool { for _, i := range c.included { - if i.IsEnvsecEnabled() { + if i.IsJetifyCloudEnvFrom() { return true } } - return c.Root.IsEnvsecEnabled() + return c.Root.IsJetifyCloudEnvFrom() } func createIncludableFromPluginConfig(pluginConfig *plugin.Config) *Config { diff --git a/internal/devconfig/configfile/env.go b/internal/devconfig/configfile/env.go index 5effb5b933f..19bccdc6752 100644 --- a/internal/devconfig/configfile/env.go +++ b/internal/devconfig/configfile/env.go @@ -8,11 +8,13 @@ import ( "github.com/hashicorp/go-envparse" ) -var JetifyCloudEnvFromValue = "jetify-cloud" - -func (c *ConfigFile) IsEnvsecEnabled() bool { - // envsec for legacy. jetpack-cloud for legacy - return c.EnvFrom == "envsec" || c.EnvFrom == "jetpack-cloud" || c.EnvFrom == JetifyCloudEnvFromValue +// IsJetifyCloudEnvFrom reports whether env_from points at Jetify Cloud +// secrets. That feature has been removed, but configs in the wild still set it, +// so we recognize the value in order to ignore it with a warning rather than +// fail on it. +func (c *ConfigFile) IsJetifyCloudEnvFrom() bool { + // envsec and jetpack-cloud are legacy spellings of jetify-cloud. + return c.EnvFrom == "envsec" || c.EnvFrom == "jetpack-cloud" || c.EnvFrom == "jetify-cloud" } func (c *ConfigFile) IsdotEnvEnabled() bool { diff --git a/internal/devconfig/configfile/file.go b/internal/devconfig/configfile/file.go index 490eee0ccc4..6857f037ce7 100644 --- a/internal/devconfig/configfile/file.go +++ b/internal/devconfig/configfile/file.go @@ -39,7 +39,7 @@ type ConfigFile struct { // Env allows specifying env variables Env map[string]string `json:"env,omitempty"` - // Only allows "envsec" for now + // EnvFrom is the path to a .env file to load env variables from. EnvFrom string `json:"env_from,omitempty"` // Shell configures the devbox shell environment. diff --git a/testscripts/run/envfrom_jetify_cloud.test.txt b/testscripts/run/envfrom_jetify_cloud.test.txt new file mode 100644 index 00000000000..2a2f03e631d --- /dev/null +++ b/testscripts/run/envfrom_jetify_cloud.test.txt @@ -0,0 +1,41 @@ +# Jetify Cloud secrets were removed. Configs that still set env_from to +# jetify-cloud (or its legacy spellings) should warn and keep working instead of +# failing. + +exec devbox run test +stderr 'Jetify Cloud secrets are no longer supported' +stdout 'BAR' + +# An env_from value that is neither a .env file nor a legacy cloud value is +# still an error. +cp devbox.json.unknown devbox.json +! exec devbox run test +stderr 'unknown env_from value' + +-- devbox.json -- +{ + "packages": [], + "env": { + "FOO": "BAR" + }, + "shell": { + "scripts": { + "test": "echo $FOO" + } + }, + "env_from": "jetify-cloud" +} + +-- devbox.json.unknown -- +{ + "packages": [], + "env": { + "FOO": "BAR" + }, + "shell": { + "scripts": { + "test": "echo $FOO" + } + }, + "env_from": "nonsense" +}