Skip to content

Commit 9c1937b

Browse files
chadsmith12Chad Smith
andauthored
Add Push Command (#1)
* add reading of secrets from user-secrets list * add tests for reading secrets buffer and parsing to secrets * add api to create resource in a folder * fix issue with password being set as the key when creating resource * update folder searching to get folders by exact name to fix issues with false positives * add ability to update secrets in folder * add env file support for pushing * add foldername arg to the usage * add a secrets fetcher interface that is used to fetch secrets from dotnet or a env file * add CommandContext to quickly spin up and get information about the command being ran * refactor secrets into different package * update readme with updated documentation --------- Co-authored-by: Chad Smith <smith.chad12@gmail.com>
1 parent dd08dd7 commit 9c1937b

File tree

17 files changed

+611
-123
lines changed

17 files changed

+611
-123
lines changed

README.md

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11

22
# Overview
33

4-
Dotsec is a command-line interface (CLI) tool written in Go to simplify the process of downloading secrets from Passbolt during development for your project. It is designed to streamline the sharing of secrets within your team, supporting either `dotnet user-secrets` or a `.env` file as the storage mechanism.
4+
Dotsec is a command-line interface (CLI) tool written in Go to simplify the process of downloading secrets from a password/secretmanager during development for your project. It is designed to streamline the sharing of secrets within your team, supporting either `dotnet user-secrets` or a `.env` file as the storage mechanism.
55

66
## Installation
77

8-
You can install Dotsec by downloading the latest release from the [Releases](https://github.com/chadsmith12/dotsec/releases) or by building it from source. Make sure to add the Dotsec binary to your system's PATH for easy access.
8+
You can install Dotsec by downloading the latest release from the [Releases](https://github.com/chadsmith12/dotsec/releases) or by building it from source. Make sure to add the `dotsec` binary to your system's PATH for easy access.
99

1010
```shell
1111
# Install Dotsec from source
@@ -15,35 +15,60 @@ go build
1515
mv dotsec /usr/local/bin/ # Move to a directory in your PATH
1616
```
1717

18+
## Supported Secret/Password Managers
19+
20+
Currently `dotsec` only supports Passbolt, though it could be expanded in the future to support other secret/password managers.
21+
1822
## Configuration
1923

20-
Before using Dotsec, you need to configure it by providing the Passbolt server details, your private key file, and an optional password. You can configure Dotsec using the following command:
24+
Before using `dotsec`, you need to configure it by providing the Passbolt server details, your private key file, and an optional password. You can configure `dotsec` using the following command:
2125

2226
```shell
2327
dotsec configure
2428
```
2529

26-
This command will prompt you for the Passbolt server URL, private key file path, and password. If you leave the password blank, Dotsec will prompt you for it each time it is required.
30+
This command will prompt you for the Passbolt server URL, private key file path, and password. If you leave the password blank, `dotsec` will prompt you for it each time it is required.
2731

2832
## Basic Usage
2933

30-
The basic usage of Dotsec involves using the `pull` command to retrieve secrets and save them to either a `dotnet user-secrets` file or a `.env` file. Here's a simple example:
34+
`dotsec` has two main commands/usages that you will use to manager your secrets for development. When interacting with Passbolt your secrets must be stored in a folder.
35+
36+
* `pull` - This command will retrieve secrets from your secret/password manager and save them to the current secret type you're using (`dotsec user-secrets` or a `.env` file).
37+
* `push` - This command will push secrets from your secret type (`dotnet user-secrets` or `.env` file) to the secret/password manager. Use it to add/update secrets in your secret manager.
38+
39+
### Basic `pull`
40+
41+
The basic usage of `dotsec` involves using the `pull` command to retrieve secrets and save them to either a `dotnet user-secrets` file or a `.env` file. Here's a simple example:
3142

3243
```shell
3344
dotsec pull "mysecrets"
3445
```
3546

36-
This command pulls secrets from the "mysecrets" folder from Passbolt and then runs `dotnet user-secrets set` for each secret found.
47+
This command pulls secrets from the "mysecrets" folder from Passbolt and then runs `dotnet user-secrets set` for each secret found. Not supplying a flag for the type will default to `dotnet user-secrets`
48+
49+
### Basic `push`
50+
51+
The basic usage of `dotsec` and the `push` command allows you to quickly push up newly created or updated secrets to your secret manager. Just like the `pull` command it will default to `dotnet user-secrets`.
52+
53+
```shell
54+
dotsec push "mysecrets"
55+
```
3756

3857
## Advanced Usage
3958

4059
### Pull Command
4160

42-
The `pull` command retrieves secrets from Passbolt and saves them to the env type. It accepts the following arguments and flags:
61+
The `pull` command retrieves secrets from Passbolt and saves them to the env type. It takes the folder name in your secret manager as the first argument:
62+
63+
- `folder name` (required): The name of the Passbolt folder containing the secrets you want to retrieve.
64+
65+
### Push Command
66+
67+
The `push` command create or updates secrets inside of your secret manager from your current secrets type. It takes the folder name in your secret manager as the first argument:
4368

4469
- `folder name` (required): The name of the Passbolt folder containing the secrets you want to retrieve.
4570

46-
Flags:
71+
Both the `pull` and `push` commands take the following flags:
4772

4873
- `--project (-p)` (optional): The path to the dotnet project where you want to sync the secrets. Defaults to the current directory. This flag is only valid with `--type dotnet`.
4974

@@ -69,4 +94,10 @@ dotsec pull "mysecrets" --file .env.development --type env
6994

7095
This command retrieves secrets from the "mysecrets" folder in Passbolt and saves them to the custom `.env` file named ".env.development" in the current directory.
7196

97+
#### Push secrets from a dotnet project:
98+
99+
```shell
100+
dotsec push "mysecrets" --project /path/to/dotnet/project --type dotnet
101+
```
72102

103+
This command will update the secrets inside of the "mysecrets" folder from the secrets that the dotnet project, in the project directory specified.

cmd/pull.go

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,10 @@ import (
66
"os"
77
"time"
88

9-
"github.com/chadsmith12/dotsec/dotnet"
10-
"github.com/chadsmith12/dotsec/env"
11-
"github.com/chadsmith12/dotsec/input"
12-
"github.com/chadsmith12/dotsec/passbolt"
9+
"github.com/chadsmith12/dotsec/cmdcontext"
1310
"github.com/spf13/cobra"
14-
"github.com/spf13/viper"
1511
)
1612

17-
var validEnvironments = []string {"dotnet", "env"}
18-
1913
// pullCmd represents the sync command
2014
var pullCmd = &cobra.Command{
2115
Use: "pull",
@@ -51,75 +45,13 @@ func pullRun(cmd *cobra.Command, args []string) {
5145
folderName := args[0]
5246
ctx, cancel := context.WithTimeout(context.Background(), 30 * time.Second)
5347
defer cancel()
54-
server, keyFile, password := getConfiguration()
55-
keyData, err := os.ReadFile(keyFile)
56-
if err != nil {
57-
fmt.Fprintln(os.Stderr, "Failed to read key file: ", err)
58-
os.Exit(1)
59-
}
48+
cmdContext := cmdcontext.NewCommandContext(cmd)
6049

61-
client, err := passbolt.NewClient(ctx, server, string(keyData), password)
62-
if err != nil {
63-
fmt.Fprintln(os.Stderr, "Failed to create api client: ", err)
64-
os.Exit(1)
65-
}
66-
67-
err = client.Login()
68-
if err != nil {
69-
fmt.Fprintln(os.Stderr, "Failed to login to Passbolt: ", err)
70-
os.Exit(1)
71-
}
72-
73-
secrets, err := client.GetSecretsByFolder(folderName);
50+
secrets, err := cmdContext.UserClient(ctx).GetSecretsByFolder(folderName);
7451
if err != nil {
7552
fmt.Fprintln(os.Stderr, "Failed to retrieve folder: ", err)
7653
os.Exit(1)
7754
}
7855

79-
envType, _ := cmd.Flags().GetString("type")
80-
setSecrectsForType(cmd, envType, secrets)
81-
}
82-
83-
func setSecrectsForType(cmd *cobra.Command, envType string, secrets []passbolt.SecretData ) {
84-
if envType == "dotnet" {
85-
project, _ := cmd.Flags().GetString("project")
86-
if err := dotnet.InitSecrets(project); err != nil {
87-
os.Exit(1)
88-
}
89-
for _, secret := range secrets {
90-
dotnet.SetSecret(project, secret.Key, secret.Value)
91-
}
92-
} else if (envType == "env") {
93-
envFile, _ := cmd.Flags().GetString("file")
94-
err := env.SetSecrets(envFile, secrets)
95-
if err != nil {
96-
fmt.Fprintln(os.Stderr, "Failed to set secrets: ", err)
97-
os.Exit(1)
98-
}
99-
} else {
100-
fmt.Println("Invalid type detected. The current valid environments supported are dotnet, and env.")
101-
os.Exit(1)
102-
}
103-
}
104-
105-
func getConfiguration() (string, string, string) {
106-
server:= viper.GetViper().GetString("server")
107-
if server == "" {
108-
fmt.Fprint(os.Stderr, "Server is not configured. Run the configure command, use the --server flag, or environment variable to set the server\n")
109-
os.Exit(1)
110-
}
111-
112-
privateKey := viper.GetViper().GetString("privateKey")
113-
if privateKey == "" {
114-
fmt.Fprint(os.Stderr, "privateKey is not configured. Run the configure command, use the --privateKey flag, or environment variable to set it to a valid private key file to load.\n")
115-
os.Exit(1)
116-
}
117-
118-
password := viper.GetViper().GetString("password")
119-
if password == "" {
120-
password, _ = input.PromptUser("Master Password: ", true)
121-
fmt.Printf("\n")
122-
}
123-
124-
return server, privateKey, password
56+
cmdContext.SecretsSetter().SetSecrets(secrets)
12557
}

cmd/push.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"time"
8+
9+
"github.com/chadsmith12/dotsec/cmdcontext"
10+
"github.com/chadsmith12/dotsec/passbolt"
11+
"github.com/chadsmith12/dotsec/secrets"
12+
"github.com/passbolt/go-passbolt/api"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
// pushCmd represents the push command
17+
var pushCmd = &cobra.Command{
18+
Use: "push foldername",
19+
Short: "Pushes alll the secrets into your file to the secret manager",
20+
Long: `Pushes the secrets from the folder specified and saves them to your secret manager folder. There are two types: dotnet or env.
21+
dotnet - Uses dotnet user-secrets to set the secrets in your dotnet projects secrets.json file.
22+
env - Saves the secrets to the .env file.
23+
24+
If you do not specify the --project flag, then it will attempt to use your current working directory.
25+
You can specify the project directory for the secrets to try to be read `,
26+
Example: "dotsec push FolderName --project ./api",
27+
Run: pushRun,
28+
}
29+
30+
func init() {
31+
rootCmd.AddCommand(pushCmd)
32+
pushCmd.Flags().StringP("project", "p", "", "The path to the dotnet project to sync the secrets to. Default to the current directory. Only valid with --type dotnet.")
33+
pushCmd.Flags().StringP("file", "f", ".env", "The env file you want to save the secrets to. Default to .env in the current directory. Only valid with --type env.")
34+
pushCmd.Flags().String("type", "dotnet", "The type of secrets file you want to use. dotnet to use dotnet user-secrets or env to use a .env file.")
35+
}
36+
37+
func pushRun(cmd *cobra.Command, args []string) {
38+
if len(args) == 0 {
39+
fmt.Println("Specify a folder to download secrets from")
40+
os.Exit(1)
41+
}
42+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
43+
defer cancel()
44+
45+
cmdCtx := cmdcontext.NewCommandContext(cmd)
46+
client := cmdCtx.UserClient(ctx)
47+
folderName := args[0]
48+
folder, err := client.GetFolderWithResources(folderName)
49+
if err != nil {
50+
fmt.Fprintf(os.Stderr, "Error - Using folder: %s - %v\n", folderName, err)
51+
os.Exit(1)
52+
}
53+
54+
secretsData, err := cmdCtx.SecretsFetcher().FetchSecrets()
55+
if err != nil {
56+
fmt.Fprintf(os.Stderr, "Error - Fetching Secrets: %v\n", err)
57+
}
58+
pushSecrets(secretsData, client, folder)
59+
}
60+
61+
func pushSecrets(secretsData []secrets.SecretData, client *passbolt.PassboltApi, folder api.Folder) {
62+
for _, value := range secretsData {
63+
if id, ok := containsSecret(folder, value.Key); ok {
64+
client.UpdateSecret(id, value)
65+
} else {
66+
client.CreateSecretInFolder(folder.ID, value)
67+
}
68+
}
69+
}
70+
71+
func containsSecret(folder api.Folder, key string) (string, bool) {
72+
for _, resource := range folder.ChildrenResources {
73+
if resource.Name == key {
74+
return resource.ID, true
75+
}
76+
}
77+
78+
return "", false
79+
}
80+

cmd/root.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
/*
2-
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3-
*/
41
package cmd
52

63
import (

cmd/test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
/*
2-
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3-
*/
41
package cmd
52

63
import (

0 commit comments

Comments
 (0)