diff --git a/docker/Dockerfile b/docker/Dockerfile index 7f2432c..7ae797a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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 diff --git a/docs/how-tos/encrypt-backup.md b/docs/how-tos/encrypt-backup.md index 788bd1b..41b5e51 100644 --- a/docs/how-tos/encrypt-backup.md +++ b/docs/how-tos/encrypt-backup.md @@ -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` @@ -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 @@ -56,4 +61,3 @@ services: networks: web: ``` -## Using GPG public key diff --git a/go.mod b/go.mod index 876dd6d..8ae07ba 100644 --- a/go.mod +++ b/go.mod @@ -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 ( diff --git a/pkg/backup.go b/pkg/backup.go index 1fdc619..abca1ac 100644 --- a/pkg/backup.go +++ b/pkg/backup.go @@ -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) } } diff --git a/pkg/encrypt.go b/pkg/encrypt.go index 97ea654..cf5a7ef 100644 --- a/pkg/encrypt.go +++ b/pkg/encrypt.go @@ -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!") @@ -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 { @@ -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 { diff --git a/pkg/helper.go b/pkg/helper.go index 2444814..850aae5 100644 --- a/pkg/helper.go +++ b/pkg/helper.go @@ -10,7 +10,6 @@ import ( "bytes" "fmt" "github.com/jkaninda/pg-bkup/utils" - "gopkg.in/yaml.v3" "os" "os/exec" "path/filepath" @@ -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 diff --git a/pkg/restore.go b/pkg/restore.go index a4b166c..7f5811f 100644 --- a/pkg/restore.go +++ b/pkg/restore.go @@ -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) } } diff --git a/utils/constant.go b/utils/constant.go index 1bf1437..4cf7fe6 100644 --- a/utils/constant.go +++ b/utils/constant.go @@ -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"