feat: add encrypt backup using public key, migrate gpg to go gpg dependency

This commit is contained in:
Jonas Kaninda
2024-10-08 22:12:54 +02:00
parent dbed77ac8a
commit 2b58998643
8 changed files with 55 additions and 62 deletions

View File

@@ -52,7 +52,7 @@ ENV VERSION=${appVersion}
LABEL author="Jonas Kaninda" LABEL author="Jonas Kaninda"
LABEL version=${appVersion} 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 $WORKDIR
RUN mkdir $BACKUPDIR RUN mkdir $BACKUPDIR
RUN mkdir -p $BACKUP_TMP_DIR RUN mkdir -p $BACKUP_TMP_DIR

View File

@@ -10,15 +10,17 @@ The image supports encrypting backups using one of two available methods: GPG wi
## Using GPG passphrase ## 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 } {: .warning }
To restore an encrypted backup, you need to provide the same GPG passphrase used during backup process. To restore an encrypted backup, you need to provide the same GPG passphrase used during backup process.
Or
- GPG home directory `/config/gnupg` - GPG home directory `/config/gnupg`
- Cipher algorithm `aes256` - 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` To decrypt manually, you need to install `gnupg`
@@ -27,7 +29,10 @@ gpg --batch --passphrase "my-passphrase" \
--output database_20240730_044201.sql.gz \ --output database_20240730_044201.sql.gz \
--decrypt database_20240730_044201.sql.gz.gpg --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 ### Backup
```yml ```yml
@@ -56,4 +61,3 @@ services:
networks: networks:
web: web:
``` ```
## Using GPG public key

1
go.mod
View File

@@ -10,7 +10,6 @@ require (
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.28.0
gopkg.in/yaml.v3 v3.0.1
) )
require ( require (

View File

@@ -296,12 +296,12 @@ func encryptBackup(config *BackupConfig) {
if config.usingKey { if config.usingKey {
err := encryptWithGPGPublicKey(filepath.Join(tmpPath, config.backupFileName), config.publicKey) err := encryptWithGPGPublicKey(filepath.Join(tmpPath, config.backupFileName), config.publicKey)
if err != nil { if err != nil {
utils.Fatal("Error during encrypting backup %v", err) utils.Fatal("error during encrypting backup %v", err)
} }
} else if config.passphrase != "" { } 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 { if err != nil {
utils.Fatal("Error during encrypting backup %v", err) utils.Fatal("error during encrypting backup %v", err)
} }
} }

View File

@@ -12,50 +12,59 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/jkaninda/pg-bkup/utils" "github.com/jkaninda/pg-bkup/utils"
"os" "os"
"os/exec"
"strings" "strings"
) )
// decryptWithGPGSymmetric decrypts backup file using a passphrase // decryptWithGPG decrypts backup file using a passphrase
func decryptWithGPGSymmetric(inputFile string, passphrase string) error { func decryptWithGPG(inputFile string, passphrase string) error {
utils.Info("Decrypting backup using passphrase...") utils.Info("Decrypting backup using passphrase...")
// Read the encrypted file
//Create gpg home dir encFileContent, err := os.ReadFile(inputFile)
err := utils.MakeDirAll(gpgHome)
if err != nil { if err != nil {
return err return errors.New(fmt.Sprintf("Error reading encrypted file: %s", err))
} }
utils.SetEnv("GNUPGHOME", gpgHome) // Define the passphrase used to encrypt the file
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--output", RemoveLastExtension(inputFile), "--decrypt", inputFile) _passphrase := []byte(passphrase)
cmd.Stdout = os.Stdout // Create a PGP message object from the encrypted file content
cmd.Stderr = os.Stderr encryptedMessage := crypto.NewPGPMessage(encFileContent)
// Decrypt the message using the passphrase
err = cmd.Run() plainMessage, err := crypto.DecryptMessageWithPassword(encryptedMessage, _passphrase)
if err != nil { if err != nil {
return err return errors.New(fmt.Sprintf("Error decrypting file: %s", err))
}
// Save the decrypted file (restore it)
err = os.WriteFile(RemoveLastExtension(inputFile), plainMessage.GetBinary(), 0644)
if err != nil {
return errors.New(fmt.Sprintf("Error saving decrypted file: %s", err))
} }
utils.Info("Decrypting backup using passphrase...done") utils.Info("Decrypting backup using passphrase...done")
utils.Info("Backup file decrypted successful!") utils.Info("Backup file decrypted successful!")
return nil return nil
} }
// encryptWithGPGSymmetric encrypts backup using a passphrase // encryptWithGPG encrypts backup using a passphrase
func encryptWithGPGSymmetric(inputFile string, passphrase string) error { func encryptWithGPG(inputFile string, passphrase string) error {
utils.Info("Encrypting backup using passphrase...") utils.Info("Encrypting backup using passphrase...")
// Read the file to be encrypted
//Create gpg home dir plainFileContent, err := os.ReadFile(inputFile)
err := utils.MakeDirAll(gpgHome)
if err != nil { if err != nil {
return err return errors.New(fmt.Sprintf("Error reading file: %s", err))
} }
utils.SetEnv("GNUPGHOME", gpgHome) // Define the passphrase to encrypt the file
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--symmetric", "--cipher-algo", algorithm, inputFile) _passphrase := []byte(passphrase)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
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 { if err != nil {
return err 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 errors.New(fmt.Sprintf("Error saving encrypted filee: %s", err))
} }
utils.Info("Encrypting backup using passphrase...done") utils.Info("Encrypting backup using passphrase...done")
utils.Info("Backup file encrypted successful!") utils.Info("Backup file encrypted successful!")
@@ -88,7 +97,7 @@ func encryptWithGPGPublicKey(inputFile string, publicKey string) error {
return errors.New(fmt.Sprintf("Error reading file: %v", err)) return errors.New(fmt.Sprintf("Error reading file: %v", err))
} }
// encryptWithGPGSymmetric the file // encryptWithGPG the file
message := crypto.NewPlainMessage(fileContent) message := crypto.NewPlainMessage(fileContent)
encMessage, err := keyRing.Encrypt(message, nil) encMessage, err := keyRing.Encrypt(message, nil)
if err != nil { if err != nil {
@@ -149,7 +158,7 @@ func decryptWithGPGPrivateKey(inputFile, privateKey, passphrase string) error {
return errors.New(fmt.Sprintf("Error reading encrypted file: %s", err)) return errors.New(fmt.Sprintf("Error reading encrypted file: %s", err))
} }
// decryptWithGPGSymmetric the file // decryptWithGPG the file
encryptedMessage := crypto.NewPGPMessage(encFileContent) encryptedMessage := crypto.NewPGPMessage(encFileContent)
message, err := keyRing.Decrypt(encryptedMessage, nil, 0) message, err := keyRing.Decrypt(encryptedMessage, nil, 0)
if err != nil { if err != nil {

View File

@@ -10,7 +10,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/jkaninda/pg-bkup/utils" "github.com/jkaninda/pg-bkup/utils"
"gopkg.in/yaml.v3"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -140,24 +139,6 @@ func testDatabaseConnection(db *dbConfig) {
utils.Info("Successfully connected to %s database", db.dbName) 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) { func checkPubKeyFile(pubKey string) (string, error) {
utils.Info("Checking file %s ...", pubKey) utils.Info("Checking file %s ...", pubKey)
// Define possible key file names // Define possible key file names

View File

@@ -81,13 +81,13 @@ func RestoreDatabase(db *dbConfig, conf *RestoreConfig) {
utils.Error("Error, passphrase or private key required") 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.") 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 { } else {
//decryptWithGPGSymmetric file //decryptWithGPG file
err := decryptWithGPGSymmetric(filepath.Join(tmpPath, conf.file), conf.passphrase) err := decryptWithGPG(filepath.Join(tmpPath, conf.file), conf.passphrase)
if err != nil { if err != nil {
utils.Fatal("Error decrypting file %s %v", file, err) utils.Fatal("Error decrypting file %s %v", file, err)
} }
//Update file name //Update file name
file = RemoveLastExtension(file) conf.file = RemoveLastExtension(file)
} }
} }

View File

@@ -6,11 +6,11 @@
**/ **/
package utils 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" "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" "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" + "backup --dbname database --storage s3 --path /custom-path\n" +
"restore --dbname database --file db_20231219_022941.sql.gz" "restore --dbname database --file db_20231219_022941.sql.gz"