feat: add backup encryption using public key and decryption using private key
This commit is contained in:
18
go.mod
18
go.mod
@@ -3,21 +3,29 @@ module github.com/jkaninda/pg-bkup
|
||||
go 1.21.0
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.5
|
||||
github.com/aws/aws-sdk-go v1.55.3
|
||||
github.com/bramvdbogaerde/go-scp v1.5.0
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/crypto v0.18.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.55.3 // indirect
|
||||
github.com/bramvdbogaerde/go-scp v1.5.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hpcloud/tail v1.0.0 // indirect
|
||||
github.com/jlaffaye/ftp v0.2.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
||||
57
go.sum
57
go.sum
@@ -1,9 +1,19 @@
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
|
||||
github.com/aws/aws-sdk-go v1.55.3 h1:0B5hOX+mIx7I5XPOrjrHlKSDQV/+ypFZpIHOx5LOk3E=
|
||||
github.com/aws/aws-sdk-go v1.55.3/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/bramvdbogaerde/go-scp v1.5.0 h1:a9BinAjTfQh273eh7vd3qUgmBC+bx+3TRDtkZWmIpzM=
|
||||
github.com/bramvdbogaerde/go-scp v1.5.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
@@ -35,6 +45,8 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
|
||||
github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
@@ -46,23 +58,68 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -166,9 +166,10 @@ func localBackup(db *dbConfig, config *BackupConfig) {
|
||||
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||
finalFileName := config.backupFileName
|
||||
if config.encryption {
|
||||
encryptBackup(config.backupFileName, config.passphrase)
|
||||
encryptBackup(config)
|
||||
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, gpgExtension)
|
||||
}
|
||||
|
||||
utils.Info("Backup name is %s", finalFileName)
|
||||
moveToBackup(finalFileName, storagePath)
|
||||
//Send notification
|
||||
@@ -189,7 +190,7 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
|
||||
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||
finalFileName := config.backupFileName
|
||||
if config.encryption {
|
||||
encryptBackup(config.backupFileName, config.passphrase)
|
||||
encryptBackup(config)
|
||||
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
|
||||
}
|
||||
utils.Info("Uploading backup archive to remote storage S3 ... ")
|
||||
@@ -226,7 +227,7 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
|
||||
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||
finalFileName := config.backupFileName
|
||||
if config.encryption {
|
||||
encryptBackup(config.backupFileName, config.passphrase)
|
||||
encryptBackup(config)
|
||||
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
|
||||
}
|
||||
utils.Info("Uploading backup archive to remote storage ... ")
|
||||
@@ -261,7 +262,7 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
|
||||
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||
finalFileName := config.backupFileName
|
||||
if config.encryption {
|
||||
encryptBackup(config.backupFileName, config.passphrase)
|
||||
encryptBackup(config)
|
||||
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
|
||||
}
|
||||
utils.Info("Uploading backup archive to the remote FTP server ... ")
|
||||
@@ -291,10 +292,18 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
|
||||
deleteTemp()
|
||||
}
|
||||
|
||||
func encryptBackup(backupFileName, gpqPassphrase string) {
|
||||
err := Encrypt(filepath.Join(tmpPath, backupFileName), gpqPassphrase)
|
||||
if err != nil {
|
||||
utils.Fatal("Error during encrypting backup %v", err)
|
||||
func encryptBackup(config *BackupConfig) {
|
||||
if config.usingKey {
|
||||
err := encrypt(filepath.Join(tmpPath, config.backupFileName), config.publicKey)
|
||||
if err != nil {
|
||||
utils.Fatal("Error during encrypting backup %v", err)
|
||||
}
|
||||
} else if config.passphrase != "" {
|
||||
err := Encrypt(filepath.Join(tmpPath, config.backupFileName), config.passphrase)
|
||||
if err != nil {
|
||||
utils.Fatal("Error during encrypting backup %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,9 +40,11 @@ type BackupConfig struct {
|
||||
backupRetention int
|
||||
disableCompression bool
|
||||
prune bool
|
||||
encryption bool
|
||||
remotePath string
|
||||
encryption bool
|
||||
usingKey bool
|
||||
passphrase string
|
||||
publicKey string
|
||||
storage string
|
||||
cronExpression string
|
||||
}
|
||||
@@ -163,10 +165,14 @@ func initBackupConfig(cmd *cobra.Command) *BackupConfig {
|
||||
_ = utils.GetEnv(cmd, "path", "AWS_S3_PATH")
|
||||
cronExpression := os.Getenv("BACKUP_CRON_EXPRESSION")
|
||||
|
||||
if passphrase != "" {
|
||||
publicKeyFile, err := checkPubKeyFile(os.Getenv("GPG_PUBLIC_KEY"))
|
||||
if err == nil {
|
||||
encryption = true
|
||||
usingKey = true
|
||||
} else if passphrase != "" {
|
||||
encryption = true
|
||||
usingKey = false
|
||||
}
|
||||
|
||||
//Initialize backup configs
|
||||
config := BackupConfig{}
|
||||
config.backupRetention = backupRetention
|
||||
@@ -176,17 +182,21 @@ func initBackupConfig(cmd *cobra.Command) *BackupConfig {
|
||||
config.encryption = encryption
|
||||
config.remotePath = remotePath
|
||||
config.passphrase = passphrase
|
||||
config.publicKey = publicKeyFile
|
||||
config.usingKey = usingKey
|
||||
config.cronExpression = cronExpression
|
||||
return &config
|
||||
}
|
||||
|
||||
type RestoreConfig struct {
|
||||
s3Path string
|
||||
remotePath string
|
||||
storage string
|
||||
file string
|
||||
bucket string
|
||||
gpqPassphrase string
|
||||
s3Path string
|
||||
remotePath string
|
||||
storage string
|
||||
file string
|
||||
bucket string
|
||||
usingKey bool
|
||||
passphrase string
|
||||
privateKey string
|
||||
}
|
||||
|
||||
func initRestoreConfig(cmd *cobra.Command) *RestoreConfig {
|
||||
@@ -199,7 +209,14 @@ func initRestoreConfig(cmd *cobra.Command) *RestoreConfig {
|
||||
storage = utils.GetEnv(cmd, "storage", "STORAGE")
|
||||
file = utils.GetEnv(cmd, "file", "FILE_NAME")
|
||||
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
|
||||
gpqPassphrase := os.Getenv("GPG_PASSPHRASE")
|
||||
passphrase := os.Getenv("GPG_PASSPHRASE")
|
||||
privateKeyFile, err := checkPrKeyFile(os.Getenv("GPG_PRIVATE_KEY"))
|
||||
if err == nil {
|
||||
usingKey = true
|
||||
} else if passphrase != "" {
|
||||
usingKey = false
|
||||
}
|
||||
|
||||
//Initialize restore configs
|
||||
rConfig := RestoreConfig{}
|
||||
rConfig.s3Path = s3Path
|
||||
@@ -208,7 +225,9 @@ func initRestoreConfig(cmd *cobra.Command) *RestoreConfig {
|
||||
rConfig.bucket = bucket
|
||||
rConfig.file = file
|
||||
rConfig.storage = storage
|
||||
rConfig.gpqPassphrase = gpqPassphrase
|
||||
rConfig.passphrase = passphrase
|
||||
rConfig.usingKey = usingKey
|
||||
rConfig.privateKey = privateKeyFile
|
||||
return &rConfig
|
||||
}
|
||||
func initTargetDbConfig() *targetDbConfig {
|
||||
|
||||
118
pkg/encrypt.go
118
pkg/encrypt.go
@@ -7,14 +7,19 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/jkaninda/pg-bkup/utils"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Decrypt decrypts backup file using a passphrase
|
||||
func Decrypt(inputFile string, passphrase string) error {
|
||||
utils.Info("Decrypting backup file: %s...", inputFile)
|
||||
utils.Info("Decrypting backup using passphrase...")
|
||||
|
||||
//Create gpg home dir
|
||||
err := utils.MakeDirAll(gpgHome)
|
||||
if err != nil {
|
||||
@@ -29,13 +34,15 @@ func Decrypt(inputFile string, passphrase string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Info("Decrypting backup using passphrase...done")
|
||||
utils.Info("Backup file decrypted successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts backup using a passphrase
|
||||
func Encrypt(inputFile string, passphrase string) error {
|
||||
utils.Info("Encrypting backup...")
|
||||
utils.Info("Encrypting backup using passphrase...")
|
||||
|
||||
//Create gpg home dir
|
||||
err := utils.MakeDirAll(gpgHome)
|
||||
if err != nil {
|
||||
@@ -50,11 +57,114 @@ func Encrypt(inputFile string, passphrase string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Info("Encrypting backup using passphrase...done")
|
||||
utils.Info("Backup file encrypted successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// encrypt encrypts backup using a public key
|
||||
func encrypt(inputFile string, publicKey string) error {
|
||||
utils.Info("Encrypting backup using public key...")
|
||||
// Read the public key
|
||||
pubKeyBytes, err := os.ReadFile(publicKey)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error reading public key: %s", err))
|
||||
}
|
||||
// Create a new keyring with the public key
|
||||
publicKeyObj, err := crypto.NewKeyFromArmored(string(pubKeyBytes))
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error parsing public key: %s", err))
|
||||
}
|
||||
|
||||
keyRing, err := crypto.NewKeyRing(publicKeyObj)
|
||||
if err != nil {
|
||||
|
||||
return errors.New(fmt.Sprintf("Error creating key ring: %v", err))
|
||||
}
|
||||
|
||||
// Read the file to encrypt
|
||||
fileContent, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error reading file: %v", err))
|
||||
}
|
||||
|
||||
// Encrypt the file
|
||||
message := crypto.NewPlainMessage(fileContent)
|
||||
encMessage, err := keyRing.Encrypt(message, nil)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error encrypting file: %v", err))
|
||||
}
|
||||
|
||||
// Save the encrypted file
|
||||
err = os.WriteFile(fmt.Sprintf("%s.%s", inputFile, gpgExtension), encMessage.GetBinary(), 0644)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error saving encrypted file: %v", err))
|
||||
}
|
||||
utils.Info("Encrypting backup using public key...done")
|
||||
utils.Info("Backup file encrypted successful!")
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// decrypt decrypts backup file using a private key and passphrase.
|
||||
// privateKey GPG private key
|
||||
// passphrase GPG passphrase
|
||||
func decrypt(inputFile, privateKey, passphrase string) error {
|
||||
utils.Info("Encrypting backup using private key...")
|
||||
|
||||
// Read the private key
|
||||
priKeyBytes, err := os.ReadFile(privateKey)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error reading private key: %s", err))
|
||||
}
|
||||
|
||||
// Read the password for the private key (if it’s password-protected)
|
||||
password := []byte(passphrase)
|
||||
|
||||
// Create a key object from the armored private key
|
||||
privateKeyObj, err := crypto.NewKeyFromArmored(string(priKeyBytes))
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error parsing private key: %s", err))
|
||||
}
|
||||
|
||||
// Unlock the private key with the password
|
||||
if passphrase != "" {
|
||||
// Unlock the private key with the password
|
||||
_, err = privateKeyObj.Unlock(password)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error unlocking private key: %s", err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create a new keyring with the private key
|
||||
keyRing, err := crypto.NewKeyRing(privateKeyObj)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error creating key ring: %v", err))
|
||||
}
|
||||
|
||||
// Read the encrypted file
|
||||
encFileContent, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error reading encrypted file: %s", err))
|
||||
}
|
||||
|
||||
// Decrypt the file
|
||||
encryptedMessage := crypto.NewPGPMessage(encFileContent)
|
||||
message, err := keyRing.Decrypt(encryptedMessage, nil, 0)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error decrypting file: %s", err))
|
||||
}
|
||||
|
||||
// Save the decrypted file
|
||||
err = os.WriteFile(RemoveLastExtension(inputFile), message.GetBinary(), 0644)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error saving decrypted file: %s", err))
|
||||
}
|
||||
utils.Info("Encrypting backup using public key...done")
|
||||
fmt.Println("File successfully decrypted!")
|
||||
return nil
|
||||
}
|
||||
func RemoveLastExtension(filename string) string {
|
||||
if idx := strings.LastIndex(filename, "."); idx != -1 {
|
||||
return filename[:idx]
|
||||
|
||||
@@ -8,13 +8,16 @@ package pkg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/jkaninda/pg-bkup/utils"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// copyToTmp copy file to temporary directory
|
||||
func copyToTmp(sourcePath string, backupFileName string) {
|
||||
//Copy backup from storage to /tmp
|
||||
err := utils.CopyFile(filepath.Join(sourcePath, backupFileName), filepath.Join(tmpPath, backupFileName))
|
||||
@@ -137,3 +140,64 @@ 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
|
||||
keyFiles := []string{filepath.Join(gpgHome, "public_key.asc"), filepath.Join(gpgHome, "public_key.gpg"), pubKey}
|
||||
|
||||
// Loop through key file names and check if they exist
|
||||
for _, keyFile := range keyFiles {
|
||||
if _, err := os.Stat(keyFile); err == nil {
|
||||
// File exists
|
||||
return keyFile, nil
|
||||
} else if os.IsNotExist(err) {
|
||||
// File does not exist, continue to the next one
|
||||
continue
|
||||
} else {
|
||||
// An unexpected error occurred
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if neither file exists
|
||||
return "", fmt.Errorf("no public key file found")
|
||||
}
|
||||
func checkPrKeyFile(prKey string) (string, error) {
|
||||
// Define possible key file names
|
||||
keyFiles := []string{filepath.Join(gpgHome, "private_key.asc"), filepath.Join(gpgHome, "private_key.gpg"), prKey}
|
||||
|
||||
// Loop through key file names and check if they exist
|
||||
for _, keyFile := range keyFiles {
|
||||
if _, err := os.Stat(keyFile); err == nil {
|
||||
// File exists
|
||||
return keyFile, nil
|
||||
} else if os.IsNotExist(err) {
|
||||
// File does not exist, continue to the next one
|
||||
continue
|
||||
} else {
|
||||
// An unexpected error occurred
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if neither file exists
|
||||
return "", fmt.Errorf("no public key file found")
|
||||
}
|
||||
|
||||
@@ -30,11 +30,13 @@ func StartMigration(cmd *cobra.Command) {
|
||||
|
||||
//Generate file name
|
||||
backupFileName := fmt.Sprintf("%s_%s.sql", dbConf.dbName, time.Now().Format("20060102_150405"))
|
||||
conf := &RestoreConfig{}
|
||||
conf.file = backupFileName
|
||||
//Backup source Database
|
||||
BackupDatabase(dbConf, backupFileName, true)
|
||||
//Restore source database into target database
|
||||
utils.Info("Restoring [%s] database into [%s] database...", dbConf.dbName, targetDbConf.targetDbName)
|
||||
RestoreDatabase(&newDbConfig, backupFileName)
|
||||
RestoreDatabase(&newDbConfig, conf)
|
||||
utils.Info("[%s] database has been restored into [%s] database", dbConf.dbName, targetDbConf.targetDbName)
|
||||
utils.Info("Database migration completed.")
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jkaninda/pg-bkup/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
@@ -24,75 +23,76 @@ func StartRestore(cmd *cobra.Command) {
|
||||
case "local":
|
||||
utils.Info("Restore database from local")
|
||||
copyToTmp(storagePath, restoreConf.file)
|
||||
RestoreDatabase(dbConf, restoreConf.file)
|
||||
RestoreDatabase(dbConf, restoreConf)
|
||||
case "s3", "S3":
|
||||
restoreFromS3(dbConf, restoreConf.file, restoreConf.bucket, restoreConf.s3Path)
|
||||
restoreFromS3(dbConf, restoreConf)
|
||||
case "ssh", "SSH", "remote":
|
||||
restoreFromRemote(dbConf, restoreConf.file, restoreConf.remotePath)
|
||||
restoreFromRemote(dbConf, restoreConf)
|
||||
case "ftp", "FTP":
|
||||
restoreFromFTP(dbConf, restoreConf.file, restoreConf.remotePath)
|
||||
restoreFromFTP(dbConf, restoreConf)
|
||||
default:
|
||||
utils.Info("Restore database from local")
|
||||
copyToTmp(storagePath, restoreConf.file)
|
||||
RestoreDatabase(dbConf, restoreConf.file)
|
||||
RestoreDatabase(dbConf, restoreConf)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreFromS3(db *dbConfig, file, bucket, s3Path string) {
|
||||
func restoreFromS3(db *dbConfig, conf *RestoreConfig) {
|
||||
utils.Info("Restore database from s3")
|
||||
err := DownloadFile(tmpPath, file, bucket, s3Path)
|
||||
err := DownloadFile(tmpPath, conf.file, conf.bucket, conf.s3Path)
|
||||
if err != nil {
|
||||
utils.Fatal("Error download file from s3 %s %v ", file, err)
|
||||
utils.Fatal("Error download file from s3 %s %v ", conf.file, err)
|
||||
}
|
||||
RestoreDatabase(db, file)
|
||||
RestoreDatabase(db, conf)
|
||||
}
|
||||
func restoreFromRemote(db *dbConfig, file, remotePath string) {
|
||||
func restoreFromRemote(db *dbConfig, conf *RestoreConfig) {
|
||||
utils.Info("Restore database from remote server")
|
||||
err := CopyFromRemote(file, remotePath)
|
||||
err := CopyFromRemote(conf.file, conf.remotePath)
|
||||
if err != nil {
|
||||
utils.Fatal("Error download file from remote server: %s %v", filepath.Join(remotePath, file), err)
|
||||
utils.Fatal("Error download file from remote server: %s %v", filepath.Join(conf.remotePath, conf.file), err)
|
||||
}
|
||||
RestoreDatabase(db, file)
|
||||
RestoreDatabase(db, conf)
|
||||
}
|
||||
func restoreFromFTP(db *dbConfig, file, remotePath string) {
|
||||
func restoreFromFTP(db *dbConfig, conf *RestoreConfig) {
|
||||
utils.Info("Restore database from FTP server")
|
||||
err := CopyFromFTP(file, remotePath)
|
||||
err := CopyFromFTP(conf.file, conf.remotePath)
|
||||
if err != nil {
|
||||
utils.Fatal("Error download file from FTP server: %s %v", filepath.Join(remotePath, file), err)
|
||||
utils.Fatal("Error download file from FTP server: %s %v", filepath.Join(conf.remotePath, conf.file), err)
|
||||
}
|
||||
RestoreDatabase(db, file)
|
||||
RestoreDatabase(db, conf)
|
||||
}
|
||||
|
||||
// RestoreDatabase restore database
|
||||
func RestoreDatabase(db *dbConfig, file string) {
|
||||
gpgPassphrase := os.Getenv("GPG_PASSPHRASE")
|
||||
if file == "" {
|
||||
func RestoreDatabase(db *dbConfig, conf *RestoreConfig) {
|
||||
if conf.file == "" {
|
||||
utils.Fatal("Error, file required")
|
||||
}
|
||||
extension := filepath.Ext(fmt.Sprintf("%s/%s", tmpPath, file))
|
||||
extension := filepath.Ext(filepath.Join(tmpPath, conf.file))
|
||||
if extension == ".gpg" {
|
||||
if gpgPassphrase == "" {
|
||||
utils.Fatal("Error: GPG passphrase is required, your file seems to be a GPG file.\nYou need to provide GPG keys. GPG_PASSPHRASE environment variable is required.")
|
||||
|
||||
} else {
|
||||
//Decrypt file
|
||||
err := Decrypt(filepath.Join(tmpPath, file), gpgPassphrase)
|
||||
if conf.usingKey {
|
||||
err := decrypt(filepath.Join(tmpPath, conf.file), conf.privateKey, conf.passphrase)
|
||||
if err != nil {
|
||||
utils.Fatal("Error decrypting file %s %v", file, err)
|
||||
utils.Fatal("Error during decrypting backup %v", err)
|
||||
}
|
||||
} else {
|
||||
if conf.passphrase == "" {
|
||||
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 {
|
||||
//Decrypt file
|
||||
err := Decrypt(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)
|
||||
}
|
||||
//Update file name
|
||||
file = RemoveLastExtension(file)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err := utils.CheckEnvVars(dbHVars)
|
||||
if err != nil {
|
||||
utils.Error("Please make sure all required environment variables for database are set")
|
||||
utils.Fatal("Error checking environment variables: %s", err)
|
||||
}
|
||||
|
||||
if utils.FileExists(fmt.Sprintf("%s/%s", tmpPath, file)) {
|
||||
if utils.FileExists(filepath.Join(tmpPath, conf.file)) {
|
||||
|
||||
err := os.Setenv("PGPASSWORD", db.dbPassword)
|
||||
if err != nil {
|
||||
@@ -101,10 +101,10 @@ func RestoreDatabase(db *dbConfig, file string) {
|
||||
testDatabaseConnection(db)
|
||||
utils.Info("Restoring database...")
|
||||
|
||||
extension := filepath.Ext(file)
|
||||
extension := filepath.Ext(conf.file)
|
||||
// Restore from compressed file / .sql.gz
|
||||
if extension == ".gz" {
|
||||
str := "zcat " + filepath.Join(tmpPath, file) + " | psql -h " + db.dbHost + " -p " + db.dbPort + " -U " + db.dbUserName + " -v -d " + db.dbName
|
||||
str := "zcat " + filepath.Join(tmpPath, conf.file) + " | psql -h " + db.dbHost + " -p " + db.dbPort + " -U " + db.dbUserName + " -v -d " + db.dbName
|
||||
_, err := exec.Command("sh", "-c", str).Output()
|
||||
if err != nil {
|
||||
utils.Fatal("Error, in restoring the database %v", err)
|
||||
@@ -130,6 +130,6 @@ func RestoreDatabase(db *dbConfig, file string) {
|
||||
}
|
||||
|
||||
} else {
|
||||
utils.Fatal("File not found in %s", fmt.Sprintf("%s/%s", tmpPath, file))
|
||||
utils.Fatal("File not found in %s", filepath.Join(tmpPath, conf.file))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ var (
|
||||
storagePath = "/backup"
|
||||
disableCompression = false
|
||||
encryption = false
|
||||
usingKey = false
|
||||
)
|
||||
|
||||
// dbHVars Required environment variables for database
|
||||
|
||||
Reference in New Issue
Block a user