Skip to content

Commit

Permalink
encrypt and decrypt files with age
Browse files Browse the repository at this point in the history
Add options (cli and conf) to encrypt and decrypt the files produced by
a run with age. Age is a file encryption tool that supports keys and
passphrases. To keep things simple, we only support passphrase. The
passphrase can be on the command line, in the config file or set by the
PGBK_PASSPHRASE environment variable.
  • Loading branch information
orgrim committed Dec 2, 2021
1 parent d61b0b0 commit f6f9590
Show file tree
Hide file tree
Showing 8 changed files with 625 additions and 40 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ with `--post-backup-hook`. The commands are executed directly, not by a shell,
respecting single and double quoted values. Even if some operation fails, the
post backup hook is executed when present.

All the files procuded by a run of pg_back can be encrypted using age
(<https://age-encryption.org/> an easy to use tool that does authenticated
encryption of files). To keep things simple, encryption is done using a
passphrase. To encrypt files, use the `--encrypt` option along with the
`--cipher-pass` option or `PGBK_PASSPHRASE` environment variable to specify the
passphrase. When `encrypt` is set to true in the configuration file, the
`--no-encrypt` option allows to disable encryption on the command line.

Encrypted files can be decrypted with the correct passphrase and the
`--decrypt` option. When `--decrypt` is present on the command line, dumps are
not performed, instead files are decrypted. Files can also be decrypted with
the `age` tool, independently. Decryption of multiple files can be parallelized
with the `-j` option.

## Managing the configuration file

The previous v1 configuration files are not compatible with pg_back v2.
Expand Down
102 changes: 77 additions & 25 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,31 +46,34 @@ var defaultCfg string

// options struct holds command line and configuration file options
type options struct {
BinDirectory string
Directory string
Host string
Port int
Username string
ConnDb string
ExcludeDbs []string
Dbnames []string
WithTemplates bool
Format rune
DirJobs int
CompressLevel int
Jobs int
PauseTimeout int
PurgeInterval time.Duration
PurgeKeep int
SumAlgo string
PreHook string
PostHook string
PgDumpOpts []string
PerDbOpts map[string]*dbOpts
CfgFile string
TimeFormat string
Verbose bool
Quiet bool
BinDirectory string
Directory string
Host string
Port int
Username string
ConnDb string
ExcludeDbs []string
Dbnames []string
WithTemplates bool
Format rune
DirJobs int
CompressLevel int
Jobs int
PauseTimeout int
PurgeInterval time.Duration
PurgeKeep int
SumAlgo string
PreHook string
PostHook string
PgDumpOpts []string
PerDbOpts map[string]*dbOpts
CfgFile string
TimeFormat string
Verbose bool
Quiet bool
Encrypt bool
CipherPassphrase string
Decrypt bool
}

func defaultOptions() options {
Expand Down Expand Up @@ -185,6 +188,12 @@ func parseCli(args []string) (options, []string, error) {
pflag.StringVarP(&purgeKeep, "purge-min-keep", "K", "0", "minimum number of dumps to keep when purging or 'all' to keep everything\n")
pflag.StringVar(&opts.PreHook, "pre-backup-hook", "", "command to run before taking dumps")
pflag.StringVar(&opts.PostHook, "post-backup-hook", "", "command to run after taking dumps\n")

pflag.BoolVar(&opts.Encrypt, "encrypt", false, "encrypt the dumps")
NoEncrypt := pflag.Bool("no-encrypt", false, "do not encrypt the dumps")
pflag.BoolVar(&opts.Decrypt, "decrypt", false, "decrypt files in the backup directory")
pflag.StringVar(&opts.CipherPassphrase, "cipher-pass", "", "cipher passphrase for encryption and decryption\n")

pflag.StringVarP(&opts.Host, "host", "h", "", "database server host or socket directory")
pflag.IntVarP(&opts.Port, "port", "p", 0, "database server port number")
pflag.StringVarP(&opts.Username, "username", "U", "", "connect as specified database user")
Expand Down Expand Up @@ -217,6 +226,13 @@ func parseCli(args []string) (options, []string, error) {
changed = append(changed, "with-templates")
}

// To override cipher = true from the config file on the command line,
// have MergeCliAndConfigOptions() use the false value
if *NoEncrypt {
opts.Encrypt = false
changed = append(changed, "encrypt")
}

// When --help or --version is given print and tell the caller
// through the error to exit
if pce.ShowHelp {
Expand Down Expand Up @@ -270,6 +286,30 @@ func parseCli(args []string) (options, []string, error) {

opts.Format = []rune(format)[0]

if opts.Encrypt && opts.Decrypt {
return opts, changed, fmt.Errorf("options --encrypt and --decrypt are mutually exclusive")
}

// Ensure a non-empty passphrase is set when asking for encryption
if (opts.Encrypt || opts.Decrypt) && len(opts.CipherPassphrase) == 0 {
oncli := false
for _, v := range changed {
if v == "cipher-pass" {
oncli = true
break
}
}

// Fallback on the environment
if !oncli {
opts.CipherPassphrase, _ = os.LookupEnv("PGBK_PASSPHRASE")
}

if len(opts.CipherPassphrase) == 0 {
return opts, changed, fmt.Errorf("cannot use an empty passphrase for encryption")
}
}

return opts, changed, nil
}

Expand Down Expand Up @@ -308,6 +348,8 @@ func loadConfigurationFile(path string) (options, error) {
opts.SumAlgo = s.Key("checksum_algorithm").MustString("none")
opts.PreHook = s.Key("pre_backup_hook").MustString("")
opts.PostHook = s.Key("post_backup_hook").MustString("")
opts.Encrypt = s.Key("encrypt").MustBool(false)
opts.CipherPassphrase = s.Key("cipher_passphrase").MustString("")

// Validate purge keep and time limit
keep, err := validatePurgeKeepValue(purgeKeep)
Expand All @@ -331,6 +373,10 @@ func loadConfigurationFile(path string) (options, error) {
}
opts.Format = []rune(format)[0]

if opts.Encrypt && len(opts.CipherPassphrase) == 0 {
return opts, fmt.Errorf("cannot use an empty passphrase for encryption")
}

// Validate the value of the timestamp format. Force the use of legacy
// on windows to avoid failure when creating filenames with the
// timestamp
Expand Down Expand Up @@ -491,6 +537,12 @@ func mergeCliAndConfigOptions(cliOpts options, configOpts options, onCli []strin
opts.PreHook = cliOpts.PreHook
case "post-backup-hook":
opts.PostHook = cliOpts.PostHook
case "encrypt":
opts.Encrypt = cliOpts.Encrypt
case "cipher-pass":
opts.CipherPassphrase = cliOpts.CipherPassphrase
case "decrypt":
opts.Decrypt = cliOpts.Decrypt
case "host":
opts.Host = cliOpts.Host
case "port":
Expand Down
Loading

0 comments on commit f6f9590

Please sign in to comment.