Skip to content
This repository was archived by the owner on May 10, 2024. It is now read-only.

Commit c072340

Browse files
authored
Merge pull request #38 from riotkit-org/issue-32-refactor-gpg-support
Refactor GPG support
2 parents f1e3602 + 8ba2626 commit c072340

File tree

30 files changed

+448
-598
lines changed

30 files changed

+448
-598
lines changed

DEVELOPMENT.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Running make & restore locally to test encryption & sending & receiving
2+
-----------------------------------------------------------------------
3+
4+
#### Server part
5+
6+
1. In backup-repository repository you need to run `make k3d skaffold-deploy`, then you will have a working Backup Repository instance in local Kubernetes.
7+
8+
2. Setup a tunnel for client connections
9+
10+
```bash
11+
kubectl port-forward svc/server-backup-repository-server -n backups 8080:8080
12+
```
13+
14+
#### Client part
15+
16+
```bash
17+
# export basic settings. Execute once in a console
18+
export BM_COLLECTION_ID=iwa-ait
19+
export BM_PASSPHRASE=riotkit
20+
export BM_AUTH_TOKEN=$(curl -s -X POST -d '{"username":"admin","password":"admin"}' -H 'Content-Type: application/json' 'http://127.0.0.1:8080/api/stable/auth/login' | jq '.data.token' -r)
21+
export BM_URL=http://127.0.0.1:8080
22+
```
23+
24+
#### Perform testing
25+
26+
```bash
27+
./.build/backup-maker make --cmd "tar -zcvf - ./" --key ./resources/test/gpg-key.asc
28+
./.build/backup-maker restore --cmd "cat - > /tmp/restore.tar.gz" --passphrase riotkit --private-key ./resources/test/gpg-key.asc
29+
```

README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export BM_PASSPHRASE="riotkit"; \
4040
backup-maker make --url https://example.org \
4141
-c "tar -zcvf - ./" \
4242
--key build/test/backup.key \
43-
--recipient test@riotkit.org \
4443
--log-level info
4544
```
4645

@@ -54,7 +53,6 @@ backup-maker restore --url $$(cat .build/test/domain.txt) \
5453
-c "cat - > /tmp/test" \
5554
--private-key .build/test/backup.key \
5655
--passphrase riotkit \
57-
--recipient test@riotkit.org \
5856
--log-level debug
5957
```
6058

@@ -65,25 +63,23 @@ please take a look at `Backup Controller` documentation.
6563

6664
**Note: GPG steps are optional**
6765

68-
1. `gpg` keyring is created in a temporary directory, keys are imported
66+
1. `gpg` keys are loaded
6967
2. Command specified in `--cmd` or in `-c` is executed
7068
3. Result of the command, it's stdout is transferred to the `gpg` process
7169
4. From `gpg` process the encoded data is buffered directly to the server
7270
5. Feedback is returned
73-
6. Temporary `gpg` keyring is deleted
7471

7572
## Restore - How it works?
7673

7774
It is very similar as in backup operation.
7875

79-
1. `gpg` keyring is created in a temporary directory, keys are imported
76+
1. `gpg` keys are loaded
8077
2. Command specified in `--cmd` or in `-c` is executed
8178
3. `gpg` process is started
8279
4. Backup download is starting
8380
5. Backup is transmitted on the fly from server to `gpg` -> our shell command
8481
6. Our shell `--cmd` / `-c` command is taking stdin and performing a restore action
8582
7. Feedback is returned
86-
8. Temporary `gpg` keyring is deleted
8783

8884
## Automated procedures
8985

@@ -96,6 +92,7 @@ together with a tool that generates Backup & Restore procedures. Those procedure
9692

9793
- Skip `--private-key` and `--passphrase` to disable GPG
9894
- Use `debug` log level to see GPG output and more verbose output at all
95+
- Increase encryption/decryption performance by disabling armoring
9996

10097

10198
## Proposed usage

client/download_test.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,11 @@ func TestDownload_SuccessWithValidGPG(t *testing.T) {
9090

9191
ctx := createExampleContext()
9292
ctx.ActionType = "download"
93-
ctx.Gpg = context.GPGOperationContext{
93+
ctx.Crypto = context.EncryptionOperationContext{
9494
PrivateKeyPath: "../resources/test/gpg-key.asc",
9595
Passphrase: "riotkit",
96-
Recipient: "test@riotkit.org",
96+
EncType: "gpg-armored",
9797
}
98-
initErr := context.InitializeGPGContext(&ctx)
99-
assert.Nil(t, initErr)
100-
defer ctx.Gpg.CleanUp()
10198

10299
_ = DownloadBackupIntoProcessStdin(ctx, "cat - > ../.build/TestDownload_SuccessWithValidGPG", client)
103100

client/upload.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func UploadFromCommandOutput(app actionCtx.Action, client HTTPClient) error {
115115
return pipeErr
116116
}
117117

118-
log.Print("Starting cmd.Run()")
118+
log.Print("Starting cmd.Encrypt()")
119119
execErr := cmd.Start()
120120
if execErr != nil {
121121
log.Println("Cannot start backup process ", execErr)
@@ -127,6 +127,7 @@ func UploadFromCommandOutput(app actionCtx.Action, client HTTPClient) error {
127127
log.Printf("Starting Upload() for PID=%v", cmd.Process.Pid)
128128
status, out, uploadErr := Upload(ctx, client, app.Url, app.CollectionId, app.AuthToken, ReadCloserWithCancellationWhenProcessFails{stdout, cmd, cancel}, app.Timeout)
129129
if uploadErr != nil {
130+
cancel()
130131
log.Errorf("Status: %v, Out: %v, Err: %v", status, out, uploadErr)
131132
return uploadErr
132133
} else {

client/upload_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestUploadFromCommandOutput_PassesOnValidResponse(t *testing.T) {
2525
ActionType: "make",
2626
VersionToRestore: "",
2727
DownloadPath: "",
28-
Gpg: context.GPGOperationContext{},
28+
Crypto: context.EncryptionOperationContext{},
2929
}, http)
3030
assert.Nil(t, err)
3131
}
@@ -47,7 +47,7 @@ func TestUploadFromCommandOutput_FailOnInvalidResponse(t *testing.T) {
4747
ActionType: "make",
4848
VersionToRestore: "",
4949
DownloadPath: "",
50-
Gpg: context.GPGOperationContext{},
50+
Crypto: context.EncryptionOperationContext{},
5151
}, http)
5252
assert.NotNil(t, err)
5353
}

cmd/backupmaker/main.go

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func addAddGenericFlags(cmd *cobra.Command, ctx *context.Action) {
2121
cmd.Flags().StringVarP(&ctx.AuthToken, "auth-token", "t", os.Getenv("BM_AUTH_TOKEN"), "Access token that allows to upload at least one file successfully, [environment variable: BM_AUTH_TOKEN]")
2222
cmd.Flags().Int64VarP(&ctx.Timeout, "timeout", "", 60*20, "Connection and read timeout in summary [environment variable: BM_TIMEOUT]")
2323
cmd.Flags().StringVarP(&ctx.LogLevelStr, "log-level", "", "info", "Verbosity level: panic|fatal|error|warn|info|debug|trace")
24-
cmd.Flags().BoolVarP(&ctx.Gpg.ShouldShowOutput, "verbose", "", false, "Increase verbosity")
24+
cmd.Flags().StringVarP(&ctx.Crypto.EncType, "encryption-type", "", getEnvOrDefault("BM_ENCRYPTION_TYPE", "gpg-armored"), "Encryption type (options: gpg-armored, gpg-binary) [environment variable: BM_ENCRYPTION_TYPE]")
2525
}
2626

2727
func createMakeCommand() *cobra.Command {
@@ -33,23 +33,19 @@ func createMakeCommand() *cobra.Command {
3333
Short: "Upload a backup to remote",
3434
SilenceUsage: true,
3535
RunE: func(cmd *cobra.Command, args []string) error {
36-
defer ctx.Gpg.CleanUp()
3736
if err := context.InitializeLogLevel(&ctx); err != nil {
3837
return err
3938
}
40-
if err := context.InitializeGPGContext(&ctx); err != nil {
41-
return err
42-
}
4339
if err := client.UploadFromCommandOutput(ctx, client.CreateHttpClient()); err != nil {
4440
return err
4541
}
4642
return nil
4743
},
4844
}
49-
makeCmd.Flags().StringVarP(&ctx.Gpg.PublicKeyPath, "key", "k", os.Getenv("BM_PUBLIC_KEY_PATH"), "GPG public or private key (required if using GPG) [environment variable: BM_PUBLIC_KEY_PATH]")
50-
makeCmd.Flags().StringVarP(&ctx.Gpg.Recipient, "recipient", "r", os.Getenv("BM_RECIPIENT"), "GPG recipient e-mail (required if using GPG). By default this e-mail SHOULD BE same as e-mail used when restoring/downloading backup [environment variable: BM_RECIPIENT]")
45+
makeCmd.Flags().StringVarP(&ctx.Crypto.PublicKeyPath, "key", "k", os.Getenv("BM_PUBLIC_KEY_PATH"), "GPG public or private key (required if using GPG) [environment variable: BM_PUBLIC_KEY_PATH]")
5146
makeCmd.Flags().StringVarP(&ctx.Command, "cmd", "c", os.Getenv("BM_CMD"), "Command to execute, which output will be captured and sent to server [environment variable: BM_CMD]")
52-
makeCmd.Flags().StringVarP(&ctx.Gpg.Passphrase, "passphrase", "", os.Getenv("BM_PASSPHRASE"), "Secret passphrase for GPG [environment variable: BM_PASSPHRASE]")
47+
makeCmd.Flags().StringVarP(&ctx.Crypto.Passphrase, "passphrase", "", os.Getenv("BM_PASSPHRASE"), "Secret passphrase for GPG [environment variable: BM_PASSPHRASE]")
48+
5349
addAddGenericFlags(&makeCmd, &ctx)
5450

5551
return &makeCmd
@@ -64,25 +60,21 @@ func createRestoreCommand() *cobra.Command {
6460
SilenceUsage: true,
6561
Short: "Restore a backup from remote to local target",
6662
RunE: func(cmd *cobra.Command, args []string) error {
67-
defer ctx.Gpg.CleanUp()
6863
if err := context.InitializeLogLevel(&ctx); err != nil {
6964
return err
7065
}
71-
if err := context.InitializeGPGContext(&ctx); err != nil {
72-
return err
73-
}
7466
if err := client.DownloadBackupIntoProcessStdin(ctx, ctx.Command, client.CreateHttpClient()); err != nil {
7567
return err
7668
}
7769
return nil
7870
},
7971
}
8072

81-
restoreCmd.Flags().StringVarP(&ctx.Gpg.PrivateKeyPath, "private-key", "p", os.Getenv("BM_PRIVATE_KEY_PATH"), "GPG private key. [environment variable: BM_PRIVATE_KEY_PATH]")
73+
restoreCmd.Flags().StringVarP(&ctx.Crypto.PrivateKeyPath, "private-key", "p", os.Getenv("BM_PRIVATE_KEY_PATH"), "GPG private key. [environment variable: BM_PRIVATE_KEY_PATH]")
8274
restoreCmd.Flags().StringVarP(&ctx.Command, "cmd", "c", os.Getenv("BM_CMD"), "Command which should take downloaded file as stdin stream e.g. some tar, unzip, psql [environment variable: BM_CMD]")
83-
restoreCmd.Flags().StringVarP(&ctx.Gpg.Passphrase, "passphrase", "", os.Getenv("BM_PASSPHRASE"), "Secret passphrase for GPG [environment variable: BM_PASSPHRASE]")
75+
restoreCmd.Flags().StringVarP(&ctx.Crypto.Passphrase, "passphrase", "", os.Getenv("BM_PASSPHRASE"), "Secret passphrase for GPG [environment variable: BM_PASSPHRASE]")
8476
restoreCmd.Flags().StringVarP(&ctx.VersionToRestore, "version", "s", getEnvOrDefault("BM_VERSION", "latest"), "Version number [environment variable: BM_VERSION]")
85-
restoreCmd.Flags().StringVarP(&ctx.Gpg.Recipient, "recipient", "r", os.Getenv("BM_RECIPIENT"), "GPG recipient e-mail (required if using GPG). By default this e-mail SHOULD BE same as e-mail used when restoring/downloading backup [environment variable: BM_RECIPIENT]")
77+
8678
addAddGenericFlags(&restoreCmd, &ctx)
8779
return &restoreCmd
8880
}
@@ -96,25 +88,21 @@ func createDownloadCommand() *cobra.Command {
9688
SilenceUsage: true,
9789
Short: "Download a remote backup and print into a local file",
9890
RunE: func(cmd *cobra.Command, args []string) error {
99-
defer ctx.Gpg.CleanUp()
10091
if err := context.InitializeLogLevel(&ctx); err != nil {
10192
return err
10293
}
103-
if err := context.InitializeGPGContext(&ctx); err != nil {
104-
return err
105-
}
10694
if err := client.DownloadIntoFile(ctx, ctx.DownloadPath, client.CreateHttpClient()); err != nil {
10795
return err
10896
}
10997
return nil
11098
},
11199
}
112100

113-
downloadCmd.Flags().StringVarP(&ctx.Gpg.PrivateKeyPath, "private-key", "p", os.Getenv("BM_PRIVATE_KEY_PATH"), "GPG private key. [environment variable: BM_PRIVATE_KEY_PATH]")
101+
downloadCmd.Flags().StringVarP(&ctx.Crypto.PrivateKeyPath, "private-key", "p", os.Getenv("BM_PRIVATE_KEY_PATH"), "GPG private key. [environment variable: BM_PRIVATE_KEY_PATH]")
114102
downloadCmd.Flags().StringVarP(&ctx.Command, "save-path", "", os.Getenv("BM_SAVE_PATH"), "Place where to save file instead of executing a restore command [environment variable: BM_SAVE_PATH]")
115-
downloadCmd.Flags().StringVarP(&ctx.Gpg.Passphrase, "passphrase", "", os.Getenv("BM_PASSPHRASE"), "Secret passphrase for GPG [environment variable: BM_PASSPHRASE]")
103+
downloadCmd.Flags().StringVarP(&ctx.Crypto.Passphrase, "passphrase", "", os.Getenv("BM_PASSPHRASE"), "Secret passphrase for GPG [environment variable: BM_PASSPHRASE]")
116104
downloadCmd.Flags().StringVarP(&ctx.VersionToRestore, "version", "s", getEnvOrDefault("BM_VERSION", "latest"), "Version number [environment variable: BM_VERSION]")
117-
downloadCmd.Flags().StringVarP(&ctx.Gpg.Recipient, "recipient", "r", os.Getenv("BM_RECIPIENT"), "GPG recipient e-mail (required if using GPG). By default this e-mail SHOULD BE same as e-mail used when restoring/downloading backup [environment variable: BM_RECIPIENT]")
105+
118106
addAddGenericFlags(&downloadCmd, &ctx)
119107
return &downloadCmd
120108
}

cmd/encryption/main.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package encryption
2+
3+
import (
4+
"github.com/pkg/errors"
5+
"github.com/riotkit-org/br-backup-maker/crypto"
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func NewEncryptionCommand() *cobra.Command {
10+
app := &App{}
11+
12+
command := &cobra.Command{
13+
Use: "encrypt",
14+
SilenceUsage: true,
15+
Short: "Encrypts a stdin and outputs as stdout",
16+
RunE: func(command *cobra.Command, args []string) error {
17+
return app.Encrypt()
18+
},
19+
}
20+
21+
command.Flags().StringVarP(&app.keyPath, "key-path", "k", "", "Path to the key file")
22+
command.Flags().StringVarP(&app.encType, "type", "t", "gpg-armored", "Encryption type (options: gpg-armored, gpg-binary)")
23+
command.Flags().StringVarP(&app.passphrase, "passphrase", "p", "", "(Optional) passphrase to decrypt the key")
24+
25+
return command
26+
}
27+
28+
func NewDecryptionCommand() *cobra.Command {
29+
app := &App{}
30+
31+
command := &cobra.Command{
32+
Use: "decrypt",
33+
SilenceUsage: true,
34+
Short: "Decrypts a stdin and outputs as stdout",
35+
RunE: func(command *cobra.Command, args []string) error {
36+
return app.Decrypt()
37+
},
38+
}
39+
40+
command.Flags().StringVarP(&app.keyPath, "key-path", "k", "", "Path to the key file")
41+
command.Flags().StringVarP(&app.encType, "type", "t", "gpg-armored", "Encryption type (options: gpg-armored, gpg-binary)")
42+
command.Flags().StringVarP(&app.passphrase, "passphrase", "p", "", "(Optional) passphrase to decrypt the key")
43+
44+
return command
45+
}
46+
47+
func NewCryptoCommand() *cobra.Command {
48+
command := &cobra.Command{
49+
Use: "crypto",
50+
SilenceUsage: true,
51+
Short: "Decrypts a stdin and outputs as stdout",
52+
RunE: func(command *cobra.Command, args []string) error {
53+
return command.Help()
54+
},
55+
}
56+
command.AddCommand(NewEncryptionCommand())
57+
command.AddCommand(NewDecryptionCommand())
58+
return command
59+
}
60+
61+
type App struct {
62+
keyPath string
63+
encType string
64+
passphrase string
65+
}
66+
67+
func (encrypt *App) createAlgo() (crypto.Service, error) {
68+
if encrypt.encType == "gpg-armored" {
69+
return crypto.GPGEncryption{Armored: true}, nil
70+
} else if encrypt.encType == "gpg-binary" {
71+
return crypto.GPGEncryption{Armored: false}, nil
72+
}
73+
return nil, errors.New("unsupported encryption type")
74+
}
75+
76+
func (encrypt *App) Encrypt() error {
77+
algo, err := encrypt.createAlgo()
78+
if err != nil {
79+
return err
80+
}
81+
return algo.Encrypt(encrypt.keyPath, encrypt.passphrase)
82+
}
83+
84+
func (encrypt *App) Decrypt() error {
85+
algo, err := encrypt.createAlgo()
86+
if err != nil {
87+
return err
88+
}
89+
return algo.Decrypt(encrypt.keyPath, encrypt.passphrase)
90+
}

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"github.com/riotkit-org/br-backup-maker/cmd/backupmaker"
55
"github.com/riotkit-org/br-backup-maker/cmd/bmg"
6+
"github.com/riotkit-org/br-backup-maker/cmd/encryption"
67
"github.com/spf13/cobra"
78
)
89

@@ -18,5 +19,7 @@ func GetRootCommand() *cobra.Command {
1819
cmd.AddCommand(subCmd)
1920
}
2021
cmd.AddCommand(bmg.CreateCommand())
22+
cmd.AddCommand(encryption.NewCryptoCommand())
23+
2124
return cmd
2225
}

context/action.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type Action struct {
1313
VersionToRestore string
1414
DownloadPath string
1515

16-
Gpg GPGOperationContext
16+
Crypto EncryptionOperationContext
1717
LogLevel uint32
1818
LogLevelStr string // todo: convert to LogLevel
1919
//logLevel, _ := log.ParseLevel(*logLevelStr)
@@ -30,21 +30,21 @@ func (that Action) CreateWrappedCommand(custom string) string {
3030
cmd = custom
3131
}
3232

33-
if !that.Gpg.Enabled(that.ActionType) {
33+
if !that.Crypto.Enabled(that.ActionType) {
3434
return cmd
3535
}
3636

3737
if that.ActionType == "make" {
38-
return cmd + " | " + that.Gpg.GetEncryptionCommand()
38+
return cmd + " | " + that.Crypto.GetEncryptionCommand()
3939
}
4040

41-
return that.Gpg.GetDecryptionCommand() + " | " + cmd
41+
return that.Crypto.GetDecryptionCommand() + " | " + cmd
4242
}
4343

4444
// GetPrintableCommand returns same command as in CreateWrappedCommand(), but with erased credentials
4545
// so the command could be logged or printed into the console
4646
func (that Action) GetPrintableCommand(custom string) string {
47-
return strings.ReplaceAll(that.CreateWrappedCommand(custom), that.Gpg.Passphrase, "***")
47+
return strings.ReplaceAll(that.CreateWrappedCommand(custom), that.Crypto.Passphrase, "***")
4848
}
4949

5050
func (that Action) ShouldShowCommandsOutput() bool {

0 commit comments

Comments
 (0)