diff --git a/.github/workflows/macos-pr-build-test.yml b/.github/workflows/macos-build.yml similarity index 94% rename from .github/workflows/macos-pr-build-test.yml rename to .github/workflows/macos-build.yml index de4d027..48cd59f 100644 --- a/.github/workflows/macos-pr-build-test.yml +++ b/.github/workflows/macos-build.yml @@ -1,6 +1,9 @@ name: macOS Build on: + push: + branches: + - main pull_request: branches: - main diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml new file mode 100644 index 0000000..d36f78c --- /dev/null +++ b/.github/workflows/ubuntu-build.yml @@ -0,0 +1,35 @@ +name: Ubuntu Build + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test-mac: + name: Build and Test Ubuntu + runs-on: ubuntu-latest + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.19 + id: go + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Build + run: | + mkdir $HOME/.terraform.d + go build . + env: + HOME: /home/runner + - name: Test + run: | + export PATH=$PATH:$PWD + cd cmd + go test -v -cover \ No newline at end of file diff --git a/.github/workflows/win-pr-build-test.yml b/.github/workflows/windows-build.yml similarity index 95% rename from .github/workflows/win-pr-build-test.yml rename to .github/workflows/windows-build.yml index b6363b6..55c1f3f 100644 --- a/.github/workflows/win-pr-build-test.yml +++ b/.github/workflows/windows-build.yml @@ -1,6 +1,9 @@ name: Windows Build on: + push: + branches: + - main pull_request: branches: - main diff --git a/README.md b/README.md index e352f30..1998ef0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![macOS Build](https://github.com/tonedefdev/terracreds/actions/workflows/macos-pr-build-test.yml/badge.svg?branch=hotfix)](https://github.com/tonedefdev/terracreds/actions/workflows/macos-pr-build-test.yml) [![Windows Build](https://github.com/tonedefdev/terracreds/actions/workflows/win-pr-build-test.yml/badge.svg?branch=hotfix)](https://github.com/tonedefdev/terracreds/actions/workflows/win-pr-build-test.yml) +[![macOS Build](https://github.com/tonedefdev/terracreds/actions/workflows/macos-build.yml/badge.svg?branch=main)](https://github.com/tonedefdev/terracreds/actions/workflows/macos-build.yml) [![Ubuntu Build](https://github.com/tonedefdev/terracreds/actions/workflows/ubuntu-build.yml/badge.svg?branch=main)](https://github.com/tonedefdev/terracreds/actions/workflows/ubuntu-build.yml) [![Windows Build](https://github.com/tonedefdev/terracreds/actions/workflows/windows-build.yml/badge.svg?branch=main)](https://github.com/tonedefdev/terracreds/actions/workflows/window-build.yml) @@ -78,10 +78,16 @@ brew install tonedefdev/terracreds/terracreds You'll need to download the latest binary from our release page and place it anywhere on `$PATH` of your system. You can also copy and run the following commands: ```bash -wget https://github.com/tonedefdev/terracreds/releases/download/v2.1.3/terracreds_2.1.3_linux_amd64.tar.gz && \ -tar -xvf terracreds_2.1.3_linux_amd64.tar.gz && \ +VERSION="" \ +wget https://github.com/tonedefdev/terracreds/releases/download/v${VERSION}/terracreds_${VERSION}_linux_amd64.tar.gz && \ +tar -xvf terracreds_${VERSION}_linux_amd64.tar.gz && \ sudo mv -f terracreds /usr/bin/terracreds && \ -rm -f terracreds_2.1.3_linux_amd64.tar.gz README.md +rm -f terracreds_${VERSION}_linux_amd64.tar.gz README.md +``` + +You can also use `homebrew` to install on `Ubuntu` machines where `brew` is available: +```bash +brew install tonedefdev/terracreds/terracreds ``` The `terracreds` Linux implementation uses `gnome-keyring` in conjunction with `gnome-keyring-daemon` @@ -132,12 +138,12 @@ go install -v Navigate to the root of the project directory and you should see the `terracreds.exe` binary for Windows or `terracreds` for macOS and Linux. On Windows, copy the `.exe` to any directory of your choosing. Be sure to add the directory on `$env:PATH` for Windows to make using the application easier. On macOS and Linux we recommend you place the binary in `/usr/bin` as this directory should already be on the `$PATH` environment variable. ## Upgrading -If you're upgrading to the latest version of `terracreds` from a previous version use one of the methods above to install the latest binary. Once successfully installed on your system you just need to run `terracreds generate` to copy the latest version to the correct `plugins` directory for your operating system. +If you're upgrading to the latest version of `terracreds` from a previous version use one of the methods above to either install the latest binary or use the package manager for your specific operating system. Once successfully installed on your system you just need to run `terracreds generate` to copy the latest version to the correct `plugins` directory for your operating system. ## Initial Configuration In order for `terracreds` to act as your credential provider you'll need to generate the binary and the plugin directory in the default location that Terraform looks for plugins. Specifically, for credential helpers, and for Windows, the directory is `%APPDATA%\terraform.d\plugins` and for macOS and Linux `$HOME/.terraform.d/.terraformrc`. -To make things as simple as possible we created a helper command to generate everthing needed to use the app. All you need to do is run the following command in `terracreds` to generate the plugin directory, and the correctly formatted binary that Terraform will use: +To make things as simple as possible we created a helper command to generate everything needed to use the app. All you need to do is run the following command in `terracreds` to generate the plugin directory, and the correctly formatted binary that Terraform will use: ```bash terracreds generate ``` @@ -262,7 +268,7 @@ The above example would maintain the dash `[-]` in the output of the formatted T Additionally, you can use `--as-json` to return the secret names and values as a JSON string. This is printed to standard output so you can make use of shell pipes and other commands to ingest the data. ## Setting Up a Vault Provider -> We have example [terraform](https://github.com/tonedefdev/terracreds/tree/main/terraform) code you can reference in order to setup your `AWS` or `Azure` VMs to use `terracreds` for a CI/CD piepline agent or a development workstation. +> We have example [terraform](https://github.com/tonedefdev/terracreds/tree/main/terraform) code you can reference in order to setup your `AWS` or `Azure` VMs to use `terracreds` for a CI/CD pipeline agent or a development workstation. > New in version `2.1.0` diff --git a/cmd/config_test.go b/cmd/config_test.go index f988f6d..6314285 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -8,6 +8,7 @@ import ( "github.com/tonedefdev/terracreds/api" "github.com/urfave/cli/v2" + "github.com/zalando/go-keyring" ) func app() *cli.App { @@ -23,7 +24,11 @@ func config() Config { ConfigFile: ConfigFile{ Path: path, }, - TerraCreds: NewTerraCreds(runtime.GOOS), + TerraCreds: NewTerraCreds(), + } + + if runtime.GOOS == "linux" { + keyring.MockInit() } return config diff --git a/cmd/create.go b/cmd/create.go index 7eb6309..ac47d1d 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -1,11 +1,10 @@ package cmd import ( - "fmt" "os" "os/user" - "github.com/fatih/color" + "github.com/tonedefdev/terracreds/pkg/errors" "github.com/tonedefdev/terracreds/pkg/helpers" "github.com/urfave/cli/v2" ) @@ -42,8 +41,13 @@ func (cmd *Config) NewCommandCreate() *cli.Command { // newCommandActionCreate creates the secret based on the OS and type of vault func (cmd *Config) newCommandActionCreate(c *cli.Context) error { if len(os.Args) == 2 { - fmt.Fprintf(color.Output, "%s: No secret name or secret was specified. Use 'terracreds create -h' to print help info\n", color.RedString("ERROR")) - return nil + err := &errors.CustomError{ + Message: "No secret name or secret was specified. Use 'terracreds create -h' to print help info", + Level: "ERROR", + } + + helpers.Logging(cmd.Cfg, err.Message, err.Level) + return err } terraVault := cmd.NewTerraVault(c.String("name")) diff --git a/cmd/delete.go b/cmd/delete.go index c65b13f..51fe425 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/fatih/color" + "github.com/tonedefdev/terracreds/pkg/errors" "github.com/tonedefdev/terracreds/pkg/helpers" "github.com/urfave/cli/v2" ) @@ -36,8 +37,13 @@ func (cmd *Config) NewCommandDelete() *cli.Command { // newCommandActionDelete deletes the secret based on the type of vault func (cmd *Config) newCommandActionDelete(c *cli.Context) error { if len(os.Args) == 2 { - fmt.Fprintf(color.Output, "%s: No secret name was specified. Use 'terracreds delete -h' for help info\n", color.RedString("ERROR")) - return nil + err := &errors.CustomError{ + Message: "No secret name was specified. Use 'terracreds delete -h' to print help info", + Level: "ERROR", + } + + helpers.Logging(cmd.Cfg, err.Message, err.Level) + return err } if !strings.Contains(os.Args[2], "-n") && !strings.Contains(os.Args[2], "--name") { diff --git a/cmd/delete_test.go b/cmd/delete_test.go index fb6df5c..b4a2ec6 100644 --- a/cmd/delete_test.go +++ b/cmd/delete_test.go @@ -11,10 +11,15 @@ func TestNewCommandActionDelete(t *testing.T) { terracreds := config() app := app() app.Commands = []*cli.Command{ + terracreds.NewCommandCreate(), terracreds.NewCommandDelete(), } args := os.Args[0:1] + args = append(args, "create", "--name=test", "--secret=password") + app.Run(args) + + args = os.Args[0:1] args = append(args, "delete", "--name=test") app.Run(args) } diff --git a/cmd/forget.go b/cmd/forget.go index d0529fa..d979031 100644 --- a/cmd/forget.go +++ b/cmd/forget.go @@ -1,11 +1,10 @@ package cmd import ( - "fmt" "os" "os/user" - "github.com/fatih/color" + "github.com/tonedefdev/terracreds/pkg/errors" "github.com/tonedefdev/terracreds/pkg/helpers" "github.com/urfave/cli/v2" ) @@ -26,8 +25,13 @@ func (cmd *Config) NewCommandForget() *cli.Command { // newCommandActionForget deletes the requested secret in the vault when called by 'terraform logout' func (cmd *Config) newCommandActionForget(c *cli.Context) error { if len(os.Args) == 2 { - fmt.Fprintf(color.Output, "%s: No secret name or secret was specified. Use 'terracreds create -h' to print help info\n", color.RedString("ERROR")) - return nil + err := &errors.CustomError{ + Message: "No secret name or secret was specified. Use 'terracreds forget -h' to print help info", + Level: "ERROR", + } + + helpers.Logging(cmd.Cfg, err.Message, err.Level) + return err } terraVault := cmd.NewTerraVault(os.Args[2]) diff --git a/cmd/forget_test.go b/cmd/forget_test.go index 003dc16..10b640a 100644 --- a/cmd/forget_test.go +++ b/cmd/forget_test.go @@ -7,26 +7,19 @@ import ( "github.com/urfave/cli/v2" ) -func TestNewCommandActionCreateTemp(t *testing.T) { +func TestNewCommandActionForget(t *testing.T) { terracreds := config() app := app() app.Commands = []*cli.Command{ terracreds.NewCommandCreate(), + terracreds.NewCommandForget(), } args := os.Args[0:1] args = append(args, "create", "--name=test", "--secret=password") app.Run(args) -} -func TestNewCommandActionForget(t *testing.T) { - terracreds := config() - app := app() - app.Commands = []*cli.Command{ - terracreds.NewCommandForget(), - } - - args := os.Args[0:1] + args = os.Args[0:1] args = append(args, "forget", "test") app.Run(args) } diff --git a/cmd/get.go b/cmd/get.go index 4c101f7..3fe54e7 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -5,7 +5,7 @@ import ( "os" "os/user" - "github.com/fatih/color" + "github.com/tonedefdev/terracreds/pkg/errors" "github.com/tonedefdev/terracreds/pkg/helpers" "github.com/urfave/cli/v2" ) @@ -42,8 +42,11 @@ func (cmd *Config) newCommandActionGet() error { return err } - msg := "A secret name was expected after the 'get' command but no argument was provided" - helpers.Logging(cmd.Cfg, msg, "ERROR") - fmt.Fprintf(color.Output, "%s: %s\n", color.RedString("ERROR"), msg) - return nil + err := &errors.CustomError{ + Message: "A secret name was expected after the 'get' command but no argument was provided", + Level: "ERROR", + } + + helpers.Logging(cmd.Cfg, err.Message, err.Level) + return err } diff --git a/cmd/list.go b/cmd/list.go index 6661307..6bca0bf 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -7,7 +7,7 @@ import ( "os/user" "strings" - "github.com/fatih/color" + "github.com/tonedefdev/terracreds/pkg/errors" "github.com/tonedefdev/terracreds/pkg/helpers" "github.com/urfave/cli/v2" ) @@ -63,8 +63,13 @@ func (cmd *Config) NewCommandList() *cli.Command { // newCommandActionList returns the secret names from the vault either as a string, TF_VARs, or JSON func (cmd *Config) newCommandActionList(c *cli.Context) error { if len(os.Args) == 2 { - fmt.Fprintf(color.Output, "%s: No list command was specified. Use 'terracreds list -h' to print help info\n", color.RedString("ERROR")) - return nil + err := &errors.CustomError{ + Message: "No list command was specified. Use 'terracreds list -h' to print help info", + Level: "ERROR", + } + + helpers.Logging(cmd.Cfg, err.Message, err.Level) + return err } if len(os.Args) > 1 { @@ -79,9 +84,13 @@ func (cmd *Config) newCommandActionList(c *cli.Context) error { } if len(cmd.Cfg.Secrets) < 1 && c.String("secret-names") == "" { - verbiage := "A list of secrets must be provided. Use '--secret-names' and pass it a comma separated list of secrets, or setup the 'secrets' block in the terracreds config file to use this command\n" - fmt.Fprintf(color.Output, "%s: %s", color.RedString("ERROR"), verbiage) - return nil + err := &errors.CustomError{ + Message: "A list of secrets must be provided. Use '--secret-names' and pass it a comma separated list of secrets, or setup the 'secrets' block in the terracreds config file to use this command", + Level: "ERROR", + } + + helpers.Logging(cmd.Cfg, err.Message, err.Level) + return err } user, err := user.Current() diff --git a/cmd/store.go b/cmd/store.go index 0eb694d..63e54a0 100644 --- a/cmd/store.go +++ b/cmd/store.go @@ -1,11 +1,10 @@ package cmd import ( - "fmt" "os" "os/user" - "github.com/fatih/color" + "github.com/tonedefdev/terracreds/pkg/errors" "github.com/tonedefdev/terracreds/pkg/helpers" "github.com/urfave/cli/v2" ) @@ -27,8 +26,13 @@ func (cmd *Config) NewCommandStore() *cli.Command { // newCommandActionStore creates the secret in the vault when 'terraform login' is called func (cmd *Config) newCommandActionStore(c *cli.Context) error { if len(os.Args) == 2 { - fmt.Fprintf(color.Output, "%s: No hostname was specified. Use 'terracreds store -h' to print help info\n", color.RedString("ERROR")) - return nil + err := &errors.CustomError{ + Message: "No hostname was specified. Use 'terracreds store -h' to print help info", + Level: "ERROR", + } + + helpers.Logging(cmd.Cfg, err.Message, err.Level) + return err } terraVault := cmd.NewTerraVault(os.Args[2]) diff --git a/cmd/store_test.go b/cmd/store_test.go index cd9c22e..f071cc3 100644 --- a/cmd/store_test.go +++ b/cmd/store_test.go @@ -4,21 +4,24 @@ import ( "bytes" "os" "os/exec" + "runtime" "testing" ) func TestNewCommandActionStore(t *testing.T) { - store := exec.Command("terracreds", "store", "test") + if runtime.GOOS != "linux" { + store := exec.Command("terracreds", "store", "test") - buffer := bytes.Buffer{} - buffer.Write([]byte("{\"token\":\"test\"}")) - store.Stdin = &buffer + buffer := bytes.Buffer{} + buffer.Write([]byte("{\"token\":\"test\"}")) + store.Stdin = &buffer - store.Stdout = os.Stdout - store.Stderr = os.Stderr + store.Stdout = os.Stdout + store.Stderr = os.Stderr - err := store.Run() - if err != nil { - t.Fatal(err) + err := store.Run() + if err != nil { + t.Fatal(err) + } } } diff --git a/cmd/terracreds.go b/cmd/terracreds.go index 02e07b6..8639fd4 100644 --- a/cmd/terracreds.go +++ b/cmd/terracreds.go @@ -164,17 +164,8 @@ func (cmd *Config) LoadConfig(path string) error { } // NewTerraCreds is the constructor to create a TerraCreds interface -func NewTerraCreds(os string) TerraCreds { - switch os { - case "darwin": - return &platform.Mac{} - case "linux": - return &platform.Linux{} - case "windows": - return &platform.Windows{} - default: - return nil - } +func NewTerraCreds() TerraCreds { + return &platform.Platform{} } // NewTerrVault is the constructor to create a TerraVault interface for the vault provider defined in the Cfg diff --git a/go.mod b/go.mod index e11f57e..9c26563 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.7.1 github.com/MakeNowJust/heredoc v1.0.0 github.com/aws/aws-sdk-go v1.41.0 - github.com/danieljoos/wincred v1.1.0 github.com/fatih/color v1.9.0 github.com/hashicorp/vault/api v1.1.1 github.com/urfave/cli/v2 v2.2.0 @@ -20,6 +19,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect + github.com/danieljoos/wincred v1.1.0 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/google/uuid v1.1.2 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -60,8 +60,8 @@ require ( golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.3.8 // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect google.golang.org/api v0.74.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index a7efb62..2d351e5 100644 --- a/go.sum +++ b/go.sum @@ -644,8 +644,8 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -656,8 +656,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/main.go b/main.go index aeccfc1..dbe3854 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "os" - "runtime" "github.com/tonedefdev/terracreds/cmd" "github.com/urfave/cli/v2" @@ -11,8 +10,8 @@ import ( func main() { terracreds := &cmd.Config{ DefaultReplaceString: "_", - TerraCreds: cmd.NewTerraCreds(runtime.GOOS), - Version: "2.1.3", + TerraCreds: cmd.NewTerraCreds(), + Version: "2.1.4", ConfigFile: cmd.ConfigFile{ EnvironmentValue: os.Getenv("TC_CONFIG_PATH"), @@ -40,5 +39,9 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + print(err.Error()) + os.Exit(1) + } } diff --git a/pkg/errors/custom_errors.go b/pkg/errors/custom_errors.go new file mode 100644 index 0000000..7ae844c --- /dev/null +++ b/pkg/errors/custom_errors.go @@ -0,0 +1,18 @@ +package errors + +import ( + "fmt" + + "github.com/fatih/color" +) + +// CustomError implements a custom error interface +type CustomError struct { + Message string + Level string +} + +// Error returns a custom formatted error message +func (ce *CustomError) Error() string { + return fmt.Sprintf("%s: %s\n", color.RedString(ce.Level), ce.Message) +} diff --git a/pkg/platform/mac.go b/pkg/platform/mac.go deleted file mode 100644 index a238787..0000000 --- a/pkg/platform/mac.go +++ /dev/null @@ -1,197 +0,0 @@ -package platform - -import ( - "encoding/json" - "fmt" - "os" - "os/user" - "strings" - - "github.com/fatih/color" - "github.com/urfave/cli/v2" - "github.com/zalando/go-keyring" - - "github.com/tonedefdev/terracreds/api" - "github.com/tonedefdev/terracreds/pkg/helpers" - "github.com/tonedefdev/terracreds/pkg/vault" -) - -type Mac struct{} - -// Create stores or updates a Terraform API token in MacOS Keyring -func (m *Mac) Create(cfg *api.Config, hostname string, token any, user *user.User, vault vault.TerraVault) error { - var method string - method = "Updated" - - if token == nil { - var response api.CredentialResponse - err := json.NewDecoder(os.Stdin).Decode(&response) - if err != nil { - helpers.CheckError(err) - } - - token = response.Token - } - - if vault != nil { - _, err := vault.Get() - if err != nil { - method = "Created" - } - - secretValue := fmt.Sprintf("%v", token) - err = vault.Create(secretValue, method) - if err != nil { - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - return err - } - - fmt.Fprintf(color.Output, "%s: %s the credential object '%s'\n", color.GreenString("SUCCESS"), method, hostname) - return err - } - - _, err := keyring.Get(hostname, string(user.Username)) - if err != nil { - method = "Created" - } - - str := fmt.Sprintf("%v", token) - err = keyring.Set(hostname, string(user.Username), str) - - if err != nil && token != nil { - fmt.Fprintf(color.Output, "%s: You do not have permission to modify this credential\n", color.RedString("ERROR")) - return err - } - - if err != nil { - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - return nil - } - - msg := fmt.Sprintf("- %s the credential object %s", strings.ToLower(method), hostname) - helpers.Logging(cfg, msg, "SUCCESS") - - if token != nil { - fmt.Fprintf(color.Output, "%s: %s the credential object '%s'\n", color.GreenString("SUCCESS"), method, hostname) - return err - } - - return err -} - -// Delete removes or forgets a Terraform API token in MacOS Keyring -func (m *Mac) Delete(cfg *api.Config, command string, hostname string, user *user.User, vault vault.TerraVault) error { - if vault != nil { - err := vault.Delete() - if err != nil { - return err - } - - msg := fmt.Sprintf("- the credential object '%s' has been removed", hostname) - helpers.Logging(cfg, msg, "INFO") - - if command == "delete" { - msg := fmt.Sprintf("The credential object '%s' has been removed", hostname) - fmt.Fprintf(color.Output, "%s: %s\n", color.GreenString("SUCCESS"), msg) - } - - return err - } - - err := keyring.Delete(hostname, string(user.Username)) - if err == nil { - msg := fmt.Sprintf("- the credential object '%s' has been removed", hostname) - helpers.Logging(cfg, msg, "INFO") - - if command == "delete" { - msg := fmt.Sprintf("The credential object '%s' has been removed", hostname) - fmt.Fprintf(color.Output, "%s: %s\n", color.GreenString("SUCCESS"), msg) - } - return err - } - - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - if command == "delete" { - fmt.Fprintf(color.Output, "%s: You do not have permission to modify this credential\n", color.RedString("ERROR")) - } - - return nil -} - -// Get retrives a Terraform API token in MacOS Keyring -func (m *Mac) Get(cfg *api.Config, hostname string, user *user.User, vault vault.TerraVault) ([]byte, error) { - if cfg.Logging.Enabled == true { - msg := fmt.Sprintf("- terraform server: %s", hostname) - helpers.Logging(cfg, msg, "INFO") - msg = fmt.Sprintf("- user requesting access: %s", string(user.Username)) - helpers.Logging(cfg, msg, "INFO") - } - - if vault != nil { - token, err := vault.Get() - if err != nil { - helpers.CheckError(err) - } - - response := &api.CredentialResponse{ - Token: string(token), - } - - token, err = json.Marshal(response) - return token, err - } - - secret, err := keyring.Get(hostname, string(user.Username)) - if err == nil { - response := &api.CredentialResponse{ - Token: secret, - } - token, err := json.Marshal(response) - - if cfg.Logging.Enabled == true && err == nil { - msg := fmt.Sprintf("- token was retrieved for: %s", hostname) - helpers.Logging(cfg, msg, "INFO") - } - - return token, err - } - - if cfg.Logging.Enabled == true { - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - } - - fmt.Fprintf(color.Output, "%s: You do not have permission to view this credential\n", color.RedString("ERROR")) - return nil, err -} - -func (m *Mac) List(c *cli.Context, cfg *api.Config, secretNames []string, user *user.User, vault vault.TerraVault) ([]string, error) { - var secretValues []string - if cfg.Logging.Enabled == true { - msg := fmt.Sprintf("- user requesting access: %s", string(user.Username)) - helpers.Logging(cfg, msg, "INFO") - } - - if vault != nil { - secrets, err := vault.List(secretNames) - if err != nil { - return nil, err - } - - return secrets, nil - } - - for _, secret := range secretNames { - if cfg.Logging.Enabled == true { - msg := fmt.Sprintf("- secret name requested: %s", secret) - helpers.Logging(cfg, msg, "INFO") - } - - cred, err := keyring.Get(secret, string(user.Username)) - if err == nil { - value := string(cred) - secretValues = append(secretValues, value) - } - } - - return secretValues, nil -} diff --git a/pkg/platform/linux.go b/pkg/platform/platform.go similarity index 74% rename from pkg/platform/linux.go rename to pkg/platform/platform.go index c5efbcf..c7de124 100644 --- a/pkg/platform/linux.go +++ b/pkg/platform/platform.go @@ -12,14 +12,15 @@ import ( "github.com/zalando/go-keyring" "github.com/tonedefdev/terracreds/api" + "github.com/tonedefdev/terracreds/pkg/errors" "github.com/tonedefdev/terracreds/pkg/helpers" "github.com/tonedefdev/terracreds/pkg/vault" ) -type Linux struct{} +type Platform struct{} -// Create stores or updates a Terraform API token in Gnome Keyring or an external vault provider -func (l *Linux) Create(cfg *api.Config, hostname string, token any, user *user.User, vault vault.TerraVault) error { +// Creates stores or updates a secret in in a vault +func (platform *Platform) Create(cfg *api.Config, hostname string, token any, user *user.User, vault vault.TerraVault) error { var method string method = "Updated" @@ -79,8 +80,8 @@ func (l *Linux) Create(cfg *api.Config, hostname string, token any, user *user.U return err } -// Delete removes or forgets a Terraform API token in Gnome Keyring or an external vault provider -func (l *Linux) Delete(cfg *api.Config, command string, hostname string, user *user.User, vault vault.TerraVault) error { +// Deletes removes or forgets a secret in a vault +func (platform *Platform) Delete(cfg *api.Config, command string, hostname string, user *user.User, vault vault.TerraVault) error { if vault != nil { err := vault.Delete() if err != nil { @@ -107,21 +108,25 @@ func (l *Linux) Delete(cfg *api.Config, command string, hostname string, user *u msg := fmt.Sprintf("The credential object '%s' has been removed", hostname) fmt.Fprintf(color.Output, "%s: %s\n", color.GreenString("SUCCESS"), msg) } - return err } helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") if command == "delete" { - fmt.Fprintf(color.Output, "%s: You do not have permission to modify this credential\n", color.RedString("ERROR")) + err = &errors.CustomError{ + Message: "You do not have permission to modify this credential", + Level: "ERROR", + } + + return err } return nil } -// Get retrieves a Terraform API token in Gnome Keyring or an external vault provider -func (l *Linux) Get(cfg *api.Config, hostname string, user *user.User, vault vault.TerraVault) ([]byte, error) { - if cfg.Logging.Enabled == true { +// Get retrieves a secret from a vault +func (platform *Platform) Get(cfg *api.Config, hostname string, user *user.User, vault vault.TerraVault) ([]byte, error) { + if cfg.Logging.Enabled { msg := fmt.Sprintf("- terraform server: %s", hostname) helpers.Logging(cfg, msg, "INFO") msg = fmt.Sprintf("- user requesting access: %s", string(user.Username)) @@ -149,7 +154,7 @@ func (l *Linux) Get(cfg *api.Config, hostname string, user *user.User, vault vau } token, err := json.Marshal(response) - if cfg.Logging.Enabled == true && err == nil { + if cfg.Logging.Enabled && err == nil { msg := fmt.Sprintf("- token was retrieved for: %s", hostname) helpers.Logging(cfg, msg, "INFO") } @@ -157,17 +162,22 @@ func (l *Linux) Get(cfg *api.Config, hostname string, user *user.User, vault vau return token, err } - if cfg.Logging.Enabled == true { + if cfg.Logging.Enabled { helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") } - fmt.Fprintf(color.Output, "%s: You do not have permission to view this credential\n", color.RedString("ERROR")) + err = &errors.CustomError{ + Message: "You do not have permission to view this credential", + Level: "ERROR", + } + return nil, err } -func (l *Linux) List(c *cli.Context, cfg *api.Config, secretNames []string, user *user.User, vault vault.TerraVault) ([]string, error) { +// List returns a list of secrets from a vault in a specified format +func (platform *Platform) List(c *cli.Context, cfg *api.Config, secretNames []string, user *user.User, vault vault.TerraVault) ([]string, error) { var secretValues []string - if cfg.Logging.Enabled == true { + if cfg.Logging.Enabled { msg := fmt.Sprintf("- user requesting access: %s", string(user.Username)) helpers.Logging(cfg, msg, "INFO") } @@ -182,16 +192,18 @@ func (l *Linux) List(c *cli.Context, cfg *api.Config, secretNames []string, user } for _, secret := range secretNames { - if cfg.Logging.Enabled == true { + if cfg.Logging.Enabled { msg := fmt.Sprintf("- secret name requested: %s", secret) helpers.Logging(cfg, msg, "INFO") } cred, err := keyring.Get(secret, string(user.Username)) - if err == nil { - value := string(cred) - secretValues = append(secretValues, value) + if err != nil { + return nil, err } + + value := string(cred) + secretValues = append(secretValues, value) } return secretValues, nil diff --git a/pkg/platform/windows.go b/pkg/platform/windows.go deleted file mode 100644 index 1e4cbed..0000000 --- a/pkg/platform/windows.go +++ /dev/null @@ -1,205 +0,0 @@ -package platform - -import ( - "encoding/json" - "fmt" - "os" - "os/user" - "strings" - - "github.com/danieljoos/wincred" - "github.com/fatih/color" - "github.com/urfave/cli/v2" - - "github.com/tonedefdev/terracreds/api" - "github.com/tonedefdev/terracreds/pkg/helpers" - "github.com/tonedefdev/terracreds/pkg/vault" -) - -type Windows struct{} - -// Create stores or updates a Terafform API token in Windows Credential Manager or a specified Cloud Vault -func (w *Windows) Create(cfg *api.Config, hostname string, token any, user *user.User, vault vault.TerraVault) error { - var method string - method = "Updated" - - if token == nil { - var response api.CredentialResponse - err := json.NewDecoder(os.Stdin).Decode(&response) - if err != nil { - helpers.CheckError(err) - } - - token = response.Token - } - - if vault != nil { - _, err := vault.Get() - if err != nil { - method = "Created" - } - - secretValue := fmt.Sprintf("%v", token) - err = vault.Create(secretValue, method) - if err != nil { - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - return err - } - - fmt.Fprintf(color.Output, "%s: %s the credential object '%s'\n", color.GreenString("SUCCESS"), method, hostname) - return err - } - - _, err := wincred.GetGenericCredential(hostname) - if err != nil { - method = "Created" - } - - cred := wincred.NewGenericCredential(hostname) - str := fmt.Sprintf("%v", token) - cred.CredentialBlob = []byte(str) - cred.UserName = string(user.Username) - err = cred.Write() - - if err != nil && token != nil { - fmt.Fprintf(color.Output, "%s: You do not have permission to modify this credential\n", color.RedString("ERROR")) - return err - } - - if err != nil { - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - return err - } - - msg := fmt.Sprintf("- %s the credential object %s", strings.ToLower(method), hostname) - helpers.Logging(cfg, msg, "SUCCESS") - - if token != nil { - fmt.Fprintf(color.Output, "%s: %s the credential object '%s'\n", color.GreenString("SUCCESS"), method, hostname) - return err - } - - return err -} - -// Delete removes or forgets a Terraform API token from the Windows Credential Manager -func (w *Windows) Delete(cfg *api.Config, command string, hostname string, user *user.User, vault vault.TerraVault) error { - if vault != nil { - err := vault.Delete() - if err != nil { - return err - } - - msg := fmt.Sprintf("- the credential object '%s' has been removed", hostname) - helpers.Logging(cfg, msg, "INFO") - - if command == "delete" { - msg := fmt.Sprintf("The credential object '%s' has been removed", hostname) - fmt.Fprintf(color.Output, "%s: %s\n", color.GreenString("SUCCESS"), msg) - } - - return err - } - - cred, err := wincred.GetGenericCredential(hostname) - if err == nil && cred.UserName == user.Username { - cred.Delete() - - msg := fmt.Sprintf("- the credential object '%s' has been removed", hostname) - helpers.Logging(cfg, msg, "INFO") - - if command == "delete" { - msg := fmt.Sprintf("The credential object '%s' has been removed", hostname) - fmt.Fprintf(color.Output, "%s: %s\n", color.GreenString("SUCCESS"), msg) - } - - return err - } - - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - if command == "delete" { - fmt.Fprintf(color.Output, "%s: You do not have permission to modify this credential\n", color.RedString("ERROR")) - } - - return nil -} - -// Get retrieves a Terraform API token in Windows Credential Manager -func (w *Windows) Get(cfg *api.Config, hostname string, user *user.User, vault vault.TerraVault) ([]byte, error) { - if cfg.Logging.Enabled == true { - msg := fmt.Sprintf("- secret name requested: %s", hostname) - helpers.Logging(cfg, msg, "INFO") - msg = fmt.Sprintf("- user requesting access: %s", string(user.Username)) - helpers.Logging(cfg, msg, "INFO") - } - - if vault != nil { - token, err := vault.Get() - if err != nil { - helpers.CheckError(err) - } - - response := &api.CredentialResponse{ - Token: string(token), - } - - token, err = json.Marshal(response) - return token, err - } - - cred, err := wincred.GetGenericCredential(hostname) - if err == nil && cred.UserName == user.Username { - response := &api.CredentialResponse{ - Token: string(cred.CredentialBlob), - } - - token, err := json.Marshal(response) - if cfg.Logging.Enabled == true { - msg := fmt.Sprintf("- secret was retrieved for: %s", hostname) - helpers.Logging(cfg, msg, "INFO") - } - - return token, err - } - - if cfg.Logging.Enabled == true { - helpers.Logging(cfg, fmt.Sprintf("- %s", err), "ERROR") - } - - fmt.Fprintf(color.Output, "%s: You do not have permission to view this credential\n", color.RedString("ERROR")) - return nil, err -} - -func (w *Windows) List(c *cli.Context, cfg *api.Config, secretNames []string, user *user.User, vault vault.TerraVault) ([]string, error) { - var secretValues []string - if cfg.Logging.Enabled == true { - msg := fmt.Sprintf("- user requesting access: %s", string(user.Username)) - helpers.Logging(cfg, msg, "INFO") - } - - if vault != nil { - secrets, err := vault.List(secretNames) - if err != nil { - return nil, err - } - - return secrets, nil - } - - for _, secret := range secretNames { - if cfg.Logging.Enabled == true { - msg := fmt.Sprintf("- secret name requested: %s", secret) - helpers.Logging(cfg, msg, "INFO") - } - - cred, err := wincred.GetGenericCredential(secret) - if err == nil && cred.UserName == user.Username { - value := string(cred.CredentialBlob) - secretValues = append(secretValues, value) - } else { - return nil, err - } - } - - return secretValues, nil -}