Skip to content

Commit

Permalink
feat: add encrypt backup using public key, migrate gpg to go gpg depe…
Browse files Browse the repository at this point in the history
…ndency
  • Loading branch information
jkaninda committed Oct 8, 2024
1 parent dbed77a commit 2b58998
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 61 deletions.
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ ENV VERSION=${appVersion}
LABEL author="Jonas Kaninda"
LABEL version=${appVersion}

RUN apk --update add --no-cache postgresql-client gnupg tzdata
RUN apk --update add --no-cache postgresql-client tzdata
RUN mkdir $WORKDIR
RUN mkdir $BACKUPDIR
RUN mkdir -p $BACKUP_TMP_DIR
Expand Down
12 changes: 8 additions & 4 deletions docs/how-tos/encrypt-backup.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ The image supports encrypting backups using one of two available methods: GPG wi

## Using GPG passphrase

The image supports encrypting backups using GPG out of the box. In case a `GPG_PASSPHRASE` environment variable is set, the backup archive will be encrypted using the given key and saved as a sql.gpg file instead or sql.gz.gpg.
The image supports encrypting backups using GPG out of the box. In case a `GPG_PASSPHRASE` or `GPG_PUBLIC_KEY` environment variable is set, the backup archive will be encrypted using the given key and saved as a sql.gpg file instead or sql.gz.gpg.

{: .warning }
To restore an encrypted backup, you need to provide the same GPG passphrase used during backup process.
Or

- GPG home directory `/config/gnupg`
- Cipher algorithm `aes256`

{: .note }
The backup encrypted using `GPG passphrase` method can be restored automatically, no need to decrypt it before restoration.


To decrypt manually, you need to install `gnupg`

Expand All @@ -27,7 +29,10 @@ gpg --batch --passphrase "my-passphrase" \
--output database_20240730_044201.sql.gz \
--decrypt database_20240730_044201.sql.gz.gpg
```

Using your private key
```shell
gpg --output database_20240730_044201.sql.gz --decrypt database_20240730_044201.sql.gz.gpg
```
### Backup

```yml
Expand Down Expand Up @@ -56,4 +61,3 @@ services:
networks:
web:
```
## Using GPG public key
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.0
golang.org/x/crypto v0.28.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand Down
6 changes: 3 additions & 3 deletions pkg/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,12 +296,12 @@ func encryptBackup(config *BackupConfig) {
if config.usingKey {
err := encryptWithGPGPublicKey(filepath.Join(tmpPath, config.backupFileName), config.publicKey)
if err != nil {
utils.Fatal("Error during encrypting backup %v", err)
utils.Fatal("error during encrypting backup %v", err)
}
} else if config.passphrase != "" {
err := encryptWithGPGSymmetric(filepath.Join(tmpPath, config.backupFileName), config.passphrase)
err := encryptWithGPG(filepath.Join(tmpPath, config.backupFileName), config.passphrase)
if err != nil {
utils.Fatal("Error during encrypting backup %v", err)
utils.Fatal("error during encrypting backup %v", err)
}

}
Expand Down
63 changes: 36 additions & 27 deletions pkg/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,59 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/jkaninda/pg-bkup/utils"
"os"
"os/exec"
"strings"
)

// decryptWithGPGSymmetric decrypts backup file using a passphrase
func decryptWithGPGSymmetric(inputFile string, passphrase string) error {
// decryptWithGPG decrypts backup file using a passphrase
func decryptWithGPG(inputFile string, passphrase string) error {
utils.Info("Decrypting backup using passphrase...")

//Create gpg home dir
err := utils.MakeDirAll(gpgHome)
// Read the encrypted file
encFileContent, err := os.ReadFile(inputFile)
if err != nil {
return errors.New(fmt.Sprintf("Error reading encrypted file: %s", err))
}
// Define the passphrase used to encrypt the file
_passphrase := []byte(passphrase)
// Create a PGP message object from the encrypted file content
encryptedMessage := crypto.NewPGPMessage(encFileContent)
// Decrypt the message using the passphrase
plainMessage, err := crypto.DecryptMessageWithPassword(encryptedMessage, _passphrase)
if err != nil {
return err
return errors.New(fmt.Sprintf("Error decrypting file: %s", err))
}
utils.SetEnv("GNUPGHOME", gpgHome)
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--output", RemoveLastExtension(inputFile), "--decrypt", inputFile)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

err = cmd.Run()
// Save the decrypted file (restore it)
err = os.WriteFile(RemoveLastExtension(inputFile), plainMessage.GetBinary(), 0644)
if err != nil {
return err
return errors.New(fmt.Sprintf("Error saving decrypted file: %s", err))
}
utils.Info("Decrypting backup using passphrase...done")
utils.Info("Backup file decrypted successful!")
return nil
}

// encryptWithGPGSymmetric encrypts backup using a passphrase
func encryptWithGPGSymmetric(inputFile string, passphrase string) error {
// encryptWithGPG encrypts backup using a passphrase
func encryptWithGPG(inputFile string, passphrase string) error {
utils.Info("Encrypting backup using passphrase...")

//Create gpg home dir
err := utils.MakeDirAll(gpgHome)
// Read the file to be encrypted
plainFileContent, err := os.ReadFile(inputFile)
if err != nil {
return err
return errors.New(fmt.Sprintf("Error reading file: %s", err))
}
utils.SetEnv("GNUPGHOME", gpgHome)
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--symmetric", "--cipher-algo", algorithm, inputFile)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Define the passphrase to encrypt the file
_passphrase := []byte(passphrase)

err = cmd.Run()
// Create a message object from the file content
message := crypto.NewPlainMessage(plainFileContent)
// Encrypt the message using the passphrase
encryptedMessage, err := crypto.EncryptMessageWithPassword(message, _passphrase)
if err != nil {
return errors.New(fmt.Sprintf("Error encrypting backup file: %s", err))
}
// Save the encrypted .tar file
err = os.WriteFile(fmt.Sprintf("%s.%s", inputFile, gpgExtension), encryptedMessage.GetBinary(), 0644)
if err != nil {
return err
return errors.New(fmt.Sprintf("Error saving encrypted filee: %s", err))
}
utils.Info("Encrypting backup using passphrase...done")
utils.Info("Backup file encrypted successful!")
Expand Down Expand Up @@ -88,7 +97,7 @@ func encryptWithGPGPublicKey(inputFile string, publicKey string) error {
return errors.New(fmt.Sprintf("Error reading file: %v", err))
}

// encryptWithGPGSymmetric the file
// encryptWithGPG the file
message := crypto.NewPlainMessage(fileContent)
encMessage, err := keyRing.Encrypt(message, nil)
if err != nil {
Expand Down Expand Up @@ -149,7 +158,7 @@ func decryptWithGPGPrivateKey(inputFile, privateKey, passphrase string) error {
return errors.New(fmt.Sprintf("Error reading encrypted file: %s", err))
}

// decryptWithGPGSymmetric the file
// decryptWithGPG the file
encryptedMessage := crypto.NewPGPMessage(encFileContent)
message, err := keyRing.Decrypt(encryptedMessage, nil, 0)
if err != nil {
Expand Down
19 changes: 0 additions & 19 deletions pkg/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"bytes"
"fmt"
"github.com/jkaninda/pg-bkup/utils"
"gopkg.in/yaml.v3"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -140,24 +139,6 @@ func testDatabaseConnection(db *dbConfig) {
utils.Info("Successfully connected to %s database", db.dbName)

}
func readConf(filename string) (*Config, error) {
configFile := filepath.Join("", filename)
if utils.FileExists(configFile) {
buf, err := os.ReadFile(configFile)
if err != nil {
return nil, err
}

c := &Config{}
err = yaml.Unmarshal(buf, c)
if err != nil {
return nil, fmt.Errorf("in file %q: %w", filename, err)
}

return c, err
}
return nil, fmt.Errorf("config file %q not found", filename)
}
func checkPubKeyFile(pubKey string) (string, error) {
utils.Info("Checking file %s ...", pubKey)
// Define possible key file names
Expand Down
6 changes: 3 additions & 3 deletions pkg/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ func RestoreDatabase(db *dbConfig, conf *RestoreConfig) {
utils.Error("Error, passphrase or private key required")
utils.Fatal("Your file seems to be a GPG file.\nYou need to provide GPG keys. GPG_PASSPHRASE or GPG_PRIVATE_KEY environment variable is required.")
} else {
//decryptWithGPGSymmetric file
err := decryptWithGPGSymmetric(filepath.Join(tmpPath, conf.file), conf.passphrase)
//decryptWithGPG file
err := decryptWithGPG(filepath.Join(tmpPath, conf.file), conf.passphrase)
if err != nil {
utils.Fatal("Error decrypting file %s %v", file, err)
}
//Update file name
file = RemoveLastExtension(file)
conf.file = RemoveLastExtension(file)
}
}

Expand Down
6 changes: 3 additions & 3 deletions utils/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
**/
package utils

const RestoreExample = "pg-bkup restore --dbname database --file db_20231219_022941.sql.gz\n" +
const RestoreExample = "restore --dbname database --file db_20231219_022941.sql.gz\n" +
"restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz"
const BackupExample = "pg-bkup backup --dbname database --disable-compression\n" +
const BackupExample = "backup --dbname database --disable-compression\n" +
"backup --dbname database --storage s3 --path /custom-path --disable-compression"

const MainExample = "pg-bkup backup --dbname database --disable-compression\n" +
const MainExample = "backup --dbname database --disable-compression\n" +
"backup --dbname database --storage s3 --path /custom-path\n" +
"restore --dbname database --file db_20231219_022941.sql.gz"

0 comments on commit 2b58998

Please sign in to comment.