2024-03-15 11:42:22 +01:00
|
|
|
// Copyright 2024 - offen.software <hioffen@posteo.de>
|
2024-02-13 19:21:57 +01:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-08-11 10:11:23 +02:00
|
|
|
"bytes"
|
|
|
|
"errors"
|
2024-02-13 19:21:57 +01:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
|
2024-08-11 10:11:23 +02:00
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
2024-02-13 19:21:57 +01:00
|
|
|
openpgp "github.com/ProtonMail/go-crypto/openpgp/v2"
|
2024-02-16 15:35:42 +01:00
|
|
|
"github.com/offen/docker-volume-backup/internal/errwrap"
|
2024-02-13 19:21:57 +01:00
|
|
|
)
|
|
|
|
|
2024-08-11 10:11:23 +02:00
|
|
|
func (s *script) encryptAsymmetrically(outFile *os.File) (io.WriteCloser, func() error, error) {
|
|
|
|
|
|
|
|
entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(s.c.GpgPublicKeyRing)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errwrap.Wrap(err, "error parsing armored keyring")
|
|
|
|
}
|
|
|
|
|
|
|
|
armoredWriter, err := armor.Encode(outFile, "PGP MESSAGE", nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errwrap.Wrap(err, "error preparing encryption")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, name := path.Split(s.file)
|
|
|
|
dst, err := openpgp.Encrypt(armoredWriter, entityList, nil, nil, &openpgp.FileHints{
|
|
|
|
FileName: name,
|
|
|
|
}, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dst, func() error {
|
|
|
|
if err := dst.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return armoredWriter.Close()
|
|
|
|
}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *script) encryptSymmetrically(outFile *os.File) (io.WriteCloser, func() error, error) {
|
|
|
|
|
|
|
|
_, name := path.Split(s.file)
|
|
|
|
dst, err := openpgp.SymmetricallyEncrypt(outFile, []byte(s.c.GpgPassphrase), &openpgp.FileHints{
|
|
|
|
FileName: name,
|
|
|
|
}, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dst, dst.Close, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// encryptArchive encrypts the backup file using PGP and the configured passphrase or publickey(s).
|
|
|
|
// In case no passphrase or publickey is given it returns early, leaving the backup file
|
2024-02-13 19:21:57 +01:00
|
|
|
// untouched.
|
|
|
|
func (s *script) encryptArchive() error {
|
2024-08-11 10:11:23 +02:00
|
|
|
|
|
|
|
var encrypt func(outFile *os.File) (io.WriteCloser, func() error, error)
|
|
|
|
var cleanUpErr error
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case s.c.GpgPassphrase != "" && s.c.GpgPublicKeyRing != "":
|
|
|
|
return errwrap.Wrap(nil, "error in selecting asymmetric and symmetric encryption methods: conflicting env vars are set")
|
|
|
|
case s.c.GpgPassphrase != "":
|
|
|
|
encrypt = s.encryptSymmetrically
|
|
|
|
case s.c.GpgPublicKeyRing != "":
|
|
|
|
encrypt = s.encryptAsymmetrically
|
|
|
|
default:
|
2024-02-13 19:21:57 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
gpgFile := fmt.Sprintf("%s.gpg", s.file)
|
|
|
|
s.registerHook(hookLevelPlumbing, func(error) error {
|
|
|
|
if err := remove(gpgFile); err != nil {
|
2024-02-16 15:35:42 +01:00
|
|
|
return errwrap.Wrap(err, "error removing gpg file")
|
2024-02-13 19:21:57 +01:00
|
|
|
}
|
|
|
|
s.logger.Info(
|
|
|
|
fmt.Sprintf("Removed GPG file `%s`.", gpgFile),
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
outFile, err := os.Create(gpgFile)
|
|
|
|
if err != nil {
|
2024-02-16 15:35:42 +01:00
|
|
|
return errwrap.Wrap(err, "error opening out file")
|
2024-02-13 19:21:57 +01:00
|
|
|
}
|
2024-08-11 10:11:23 +02:00
|
|
|
defer func() {
|
|
|
|
if err := outFile.Close(); err != nil {
|
|
|
|
cleanUpErr = errors.Join(cleanUpErr, errwrap.Wrap(err, "error closing out file"))
|
|
|
|
}
|
|
|
|
}()
|
2024-02-13 19:21:57 +01:00
|
|
|
|
2024-08-11 10:11:23 +02:00
|
|
|
dst, dstCloseCallback, err := encrypt(outFile)
|
2024-02-13 19:21:57 +01:00
|
|
|
if err != nil {
|
2024-02-16 15:35:42 +01:00
|
|
|
return errwrap.Wrap(err, "error encrypting backup file")
|
2024-02-13 19:21:57 +01:00
|
|
|
}
|
2024-08-11 10:11:23 +02:00
|
|
|
defer func() {
|
|
|
|
if err := dstCloseCallback(); err != nil {
|
|
|
|
cleanUpErr = errors.Join(cleanUpErr, errwrap.Wrap(err, "error closing encrypted backup file"))
|
|
|
|
}
|
|
|
|
}()
|
2024-02-13 19:21:57 +01:00
|
|
|
|
|
|
|
src, err := os.Open(s.file)
|
|
|
|
if err != nil {
|
2024-02-16 15:35:42 +01:00
|
|
|
return errwrap.Wrap(err, fmt.Sprintf("error opening backup file `%s`", s.file))
|
2024-02-13 19:21:57 +01:00
|
|
|
}
|
2024-08-11 10:11:23 +02:00
|
|
|
defer func() {
|
|
|
|
if err := src.Close(); err != nil {
|
|
|
|
cleanUpErr = errors.Join(cleanUpErr, errwrap.Wrap(err, "error closing backup file"))
|
|
|
|
}
|
|
|
|
}()
|
2024-02-13 19:21:57 +01:00
|
|
|
|
|
|
|
if _, err := io.Copy(dst, src); err != nil {
|
2024-02-16 15:35:42 +01:00
|
|
|
return errwrap.Wrap(err, "error writing ciphertext to file")
|
2024-02-13 19:21:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
s.file = gpgFile
|
|
|
|
s.logger.Info(
|
2024-08-11 10:11:23 +02:00
|
|
|
fmt.Sprintf("Encrypted backup using gpg, saving as `%s`.", s.file),
|
2024-02-13 19:21:57 +01:00
|
|
|
)
|
2024-08-11 10:11:23 +02:00
|
|
|
return cleanUpErr
|
2024-02-13 19:21:57 +01:00
|
|
|
}
|