Skip to content

Commit

Permalink
Implement libsodium Crypter (wal-g#512)
Browse files Browse the repository at this point in the history
* Implement libsodium Crypter

* Environment variable for possible build without libsodium

* Add explanation comment

Co-authored-by: IrinaSedova <[email protected]>
  • Loading branch information
2 people authored and g0djan committed Jan 9, 2020
1 parent f24fef5 commit fb88569
Show file tree
Hide file tree
Showing 17 changed files with 480 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
- ${CACHE_FILE_UBUNTU}
- ${CACHE_FILE_GOLANG}
- stage: test
env:
- USE_LIBSODIUM=1
script: make all_unittests
- script: make TEST="pg_backup_mark_impermanent_test" pg_integration_test
workspaces:
Expand Down
25 changes: 19 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ PKG := github.com/wal-g/wal-g
COVERAGE_FILE := coverage.out
TEST := "pg_tests"

ifeq ($(USE_LIBSODIUM),)
LIBSODIUM_TAG=""
else
LIBSODIUM_TAG=" libsodium"
endif

.PHONY: unittest fmt lint install clean

test: install deps lint unittest pg_build mysql_build redis_build mongo_build unlink_brotli pg_integration_test mysql_integration_test redis_integration_test

pg_test: install deps pg_build lint unlink_brotli pg_integration_test

pg_build: $(CMD_FILES) $(PKG_FILES)
(cd $(MAIN_PG_PATH) && go build -tags "brotli lzo" -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/pg.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/pg.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/pg.WalgVersion=`git tag -l --points-at HEAD`")
(cd $(MAIN_PG_PATH) && go build -tags "brotli lzo$(LIBSODIUM_TAG)" -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/pg.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/pg.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/pg.WalgVersion=`git tag -l --points-at HEAD`")

install_and_build_pg: install deps pg_build

Expand Down Expand Up @@ -59,7 +65,7 @@ pg_install: pg_build
mysql_test: install deps mysql_build lint unlink_brotli mysql_integration_test

mysql_build: $(CMD_FILES) $(PKG_FILES)
(cd $(MAIN_MYSQL_PATH) && go build -tags brotli -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/mysql.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/mysql.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/mysql.WalgVersion=`git tag -l --points-at HEAD`")
(cd $(MAIN_MYSQL_PATH) && go build -tags "brotli$(LIBSODIUM_TAG)" -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/mysql.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/mysql.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/mysql.WalgVersion=`git tag -l --points-at HEAD`")

load_docker_common:
@if [ "x" = "${CACHE_FILE_UBUNTU}x" ]; then\
Expand All @@ -84,7 +90,7 @@ mysql_install: mysql_build
mongo_test: install deps mongo_build lint unlink_brotli

mongo_build: $(CMD_FILES) $(PKG_FILES)
(cd $(MAIN_MONGO_PATH) && go build -tags brotli -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/mongo.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/mongo.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/mongo.WalgVersion=`git tag -l --points-at HEAD`")
(cd $(MAIN_MONGO_PATH) && go build -tags "brotli$(LIBSODIUM_TAG)" -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/mongo.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/mongo.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/mongo.WalgVersion=`git tag -l --points-at HEAD`")

mongo_install: mongo_build
mv $(MAIN_MONGO_PATH)/wal-g $(GOBIN)/wal-g
Expand All @@ -100,7 +106,7 @@ mongo_features: install deps mongo_build lint unlink_brotli
redis_test: install deps redis_build lint unlink_brotli redis_integration_test

redis_build: $(CMD_FILES) $(PKG_FILES)
(cd $(MAIN_REDIS_PATH) && go build -tags brotli -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/redis.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/redis.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/redis.WalgVersion=`git tag -l --points-at HEAD`")
(cd $(MAIN_REDIS_PATH) && go build -tags "brotli$(LIBSODIUM_TAG)" -o wal-g -ldflags "-s -w -X github.com/wal-g/wal-g/cmd/redis.BuildDate=`date -u +%Y.%m.%d_%H:%M:%S` -X github.com/wal-g/wal-g/cmd/redis.GitRevision=`git rev-parse --short HEAD` -X github.com/wal-g/wal-g/cmd/redis.WalgVersion=`git tag -l --points-at HEAD`")

redis_integration_test: load_docker_common
docker-compose build redis redis_tests
Expand All @@ -119,6 +125,9 @@ unittest:
go test -v $(TEST_MODIFIER) ./internal/compression/
go test -v $(TEST_MODIFIER) ./internal/crypto/openpgp/
go test -v $(TEST_MODIFIER) ./internal/crypto/awskms/
@if [[ ! -z "${USE_LIBSODIUM}" ]]; then\
go test -v $(TEST_MODIFIER) ./internal/crypto/libsodium/;\
fi
go test -v $(TEST_MODIFIER) ./internal/databases/mysql
go test -v $(TEST_MODIFIER) ./internal/walparser/
go test -v $(TEST_MODIFIER) ./utility
Expand All @@ -139,12 +148,16 @@ deps:
dep ensure -update github.com/cyberdelia/lzo
sed -i 's|\(#cgo LDFLAGS:\) .*|\1 -Wl,-Bstatic -llzo2 -Wl,-Bdynamic|' vendor/github.com/cyberdelia/lzo/lzo.go
./link_brotli.sh
./link_libsodium.sh

install:
go get -u github.com/golang/dep/cmd/dep
go get -u golang.org/x/lint/golint

unlink_brotli:
rm -rf vendor/github.com/google/brotli/*
mv tmp/* vendor/github.com/google/brotli/
rm -rf tmp/
mv tmp/brotli/* vendor/github.com/google/brotli/
rm -rf tmp/brotli

unlink_libsodium:
rm -rf tmp/libsodium
10 changes: 10 additions & 0 deletions PostgreSQL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Development

To compile and build the binary for Postgres:

(To build with libsodium, just set `USE_LIBSODIUM` environment variable)

```
go get github.com/wal-g/wal-g
cd $GOPATH/src/github.com/wal-g/wal-g
Expand Down Expand Up @@ -63,6 +65,14 @@ This setting allows backup automation tools to add extra information to JSON sen

If this setting is specified, during ```wal-push``` WAL-G will check the existence of WAL before uploading it. If the different file is already archived under the same name, WAL-G will return the non-zero exit code to prevent PostgreSQL from removing WAL.

* `WALG_LIBSODIUM_KEY`

To configure encryption and decryption with libsodium. WAL-G uses an [algorithm](https://download.libsodium.org/doc/secret-key_cryptography/secretstream#algorithm) that only requires a secret key.

* `WALG_LIBSODIUM_KEY_PATH`

Similar to `WALG_LIBSODIUM_KEY`, but value is the path to the key on file system. The file content will be trimmed from whitespace characters.

* `WALG_GPG_KEY_ID` (alternative form `WALE_GPG_KEY_ID`) ⚠️ **DEPRECATED**

To configure GPG key for encryption and decryption. By default, no encryption is used. Public keyring is cached in the file "/.walg_key_cache".
Expand Down
1 change: 1 addition & 0 deletions docker/mysql_tests/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ WORKDIR /go/src/github.com/wal-g/wal-g
RUN apt-get update && \
apt-get install --yes --no-install-recommends --no-install-suggests

COPY tmp/libsodium tmp/libsodium
COPY vendor/ vendor/
COPY internal/ internal/
COPY cmd/ cmd/
Expand Down
1 change: 1 addition & 0 deletions docker/pg_tests/Dockerfile_prefix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ RUN apt-get update && \
apt-get install --yes --no-install-recommends --no-install-suggests \
liblzo2-dev

COPY tmp/libsodium tmp/libsodium
COPY vendor/ vendor/
COPY internal/ internal/
COPY cmd/ cmd/
Expand Down
1 change: 1 addition & 0 deletions docker/redis_tests/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ WORKDIR /go/src/github.com/wal-g/wal-g
RUN apt-get update && \
apt-get install --yes --no-install-recommends --no-install-suggests

COPY tmp/libsodium tmp/libsodium
COPY vendor/ vendor/
COPY internal/ internal/
COPY cmd/ cmd/
Expand Down
2 changes: 2 additions & 0 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const (
TarSizeThresholdSetting = "WALG_TAR_SIZE_THRESHOLD"
CseKmsIDSetting = "WALG_CSE_KMS_ID"
CseKmsRegionSetting = "WALG_CSE_KMS_REGION"
LibsodiumKeySetting = "WALG_LIBSODIUM_KEY"
LibsodiumKeyPathSetting = "WALG_LIBSODIUM_KEY_PATH"
GpgKeyIDSetting = "GPG_KEY_ID"
PgpKeySetting = "WALG_PGP_KEY"
PgpKeyPathSetting = "WALG_PGP_KEY_PATH"
Expand Down
4 changes: 4 additions & 0 deletions internal/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ func ConfigureCrypter() crypto.Crypter {
return awskms.CrypterFromKeyID(viper.GetString(CseKmsIDSetting), viper.GetString(CseKmsRegionSetting))
}

if crypter := configureLibsodiumCrypter(); crypter != nil {
return crypter
}

return nil
}

Expand Down
22 changes: 22 additions & 0 deletions internal/configure_crypter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// +build !libsodium

package internal

// This file contains functions that should return `nil`,
// in order to be able to build wal-g without specific implementations of the crypter.
// And the configure_crypter_<crypter>.go files must have a real implementation of the function.
//
// Thus, if the tag is missing, the condition:
// if crypter := configure<crypter>Crypter(); crypter != nil {
// return crypter
// }
// will never be met.
// If there is a tag, we can configure the correct implementation of crypter.

import (
"github.com/wal-g/wal-g/internal/crypto"
)

func configureLibsodiumCrypter() crypto.Crypter {
return nil
}
21 changes: 21 additions & 0 deletions internal/configure_crypter_libsodium.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// +build libsodium

package internal

import (
"github.com/spf13/viper"
"github.com/wal-g/wal-g/internal/crypto"
"github.com/wal-g/wal-g/internal/crypto/libsodium"
)

func configureLibsodiumCrypter() crypto.Crypter {
if viper.IsSet(LibsodiumKeySetting) {
return libsodium.CrypterFromKey(viper.GetString(LibsodiumKeySetting))
}

if viper.IsSet(LibsodiumKeyPathSetting) {
return libsodium.CrypterFromKeyPath(viper.GetString(LibsodiumKeyPathSetting))
}

return nil
}
94 changes: 94 additions & 0 deletions internal/crypto/libsodium/crypter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package libsodium

// #cgo CFLAGS: -I../../../tmp/libsodium/include
// #cgo LDFLAGS: -L../../../tmp/libsodium/lib -lsodium
// #include <sodium.h>
import "C"

import (
"io"
"io/ioutil"
"strings"
"sync"

"github.com/pkg/errors"
"github.com/wal-g/wal-g/internal/crypto"
)

const (
chunkSize = 8192
)

// libsodium should always be initialised
func init() {
C.sodium_init()
}

// Crypter is libsodium Crypter implementation
type Crypter struct {
Key string
KeyPath string

mutex sync.RWMutex
}

// CrypterFromKey creates Crypter from key
func CrypterFromKey(key string) crypto.Crypter {
return &Crypter{Key: key}
}

// CrypterFromKeyPath creates Crypter from key path
func CrypterFromKeyPath(path string) crypto.Crypter {
return &Crypter{KeyPath: path}
}

func (crypter *Crypter) setup() (err error) {
crypter.mutex.RLock()

if crypter.Key == "" && crypter.KeyPath == "" {
return errors.New("libsodium Crypter must have a key or key path")
}

if crypter.Key != "" {
crypter.mutex.RUnlock()

return
}

crypter.mutex.RUnlock()

crypter.mutex.Lock()
defer crypter.mutex.Unlock()

if crypter.Key != "" {
return
}

key, err := ioutil.ReadFile(crypter.KeyPath)

if err != nil {
return
}

crypter.Key = strings.TrimSpace(string(key))

return nil
}

// Encrypt creates encryption writer from ordinary writer
func (crypter *Crypter) Encrypt(writer io.Writer) (io.WriteCloser, error) {
if err := crypter.setup(); err != nil {
return nil, err
}

return NewWriter(writer, []byte(crypter.Key))
}

// Decrypt creates decrypted reader from ordinary reader
func (crypter *Crypter) Decrypt(reader io.Reader) (io.Reader, error) {
if err := crypter.setup(); err != nil {
return nil, err
}

return NewReader(reader, []byte(crypter.Key))
}
69 changes: 69 additions & 0 deletions internal/crypto/libsodium/crypter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// +build libsodium

package libsodium

import (
"bytes"
"io/ioutil"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/wal-g/wal-g/internal/crypto"
)

const (
keyPath = "./testdata/testKey"
testKey = "TEST_LIBSODIUM_KEY"
)

func MockCrypterFromKey() *Crypter {
return CrypterFromKey(testKey).(*Crypter)
}

func MockCrypterFromKeyPath() *Crypter {
return CrypterFromKeyPath(keyPath).(*Crypter)
}

func TestMockCrypterFromKey(t *testing.T) {
assert.NoError(t, MockCrypterFromKey().setup(), "setup Crypter from key error")
}

func TestMockCrypterFromKeyPath(t *testing.T) {
assert.NoError(t, MockCrypterFromKeyPath().setup(), "setup Crypter from key path error")
}

func TestMockCrypterFromKey_ShouldReturnErrorOnEmptyKey(t *testing.T) {
assert.Error(t, CrypterFromKey("").(*Crypter).setup(), "no error on empty key")
}

func TestMockCrypterFromKeyPath_ShouldReturnErrorOnNonExistentFile(t *testing.T) {
assert.Error(t, CrypterFromKeyPath("").(*Crypter).setup(), "no error on non-existent key path")
}

func EncryptionCycle(t *testing.T, crypter crypto.Crypter) {
secret := strings.Repeat(" so very secret thing ", 1000)

buffer := new(bytes.Buffer)
encrypt, err := crypter.Encrypt(buffer)
assert.NoErrorf(t, err, "encryption error: %v", err)

encrypt.Write([]byte(secret))
encrypt.Close()

decrypt, err := crypter.Decrypt(buffer)
assert.NoErrorf(t, err, "decryption error: %v", err)

decrypted, err := ioutil.ReadAll(decrypt)
assert.NoErrorf(t, err, "decryption read error: %v", err)

assert.Equal(t, secret, string(decrypted), "decrypted text not equals to open text")
}

func TestEncryptionCycleFromKey(t *testing.T) {
EncryptionCycle(t, MockCrypterFromKey())
}

func TestEncryptionCycleFromKeyPath(t *testing.T) {
EncryptionCycle(t, MockCrypterFromKeyPath())
}
Loading

0 comments on commit fb88569

Please sign in to comment.