github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/migration/encryption_test.go (about)

     1  package migration_test
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"database/sql"
     7  	"fmt"
     8  
     9  	"code.cloudfoundry.org/lager"
    10  
    11  	"github.com/pf-qiu/concourse/v6/atc/db/encryption"
    12  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    13  	"github.com/pf-qiu/concourse/v6/atc/db/migration"
    14  	"github.com/pf-qiu/concourse/v6/atc/db/migration/migrationfakes"
    15  
    16  	. "github.com/onsi/ginkgo"
    17  	. "github.com/onsi/gomega"
    18  )
    19  
    20  var _ = Describe("Encryption", func() {
    21  	var (
    22  		err         error
    23  		db          *sql.DB
    24  		lockDB      *sql.DB
    25  		lockFactory lock.LockFactory
    26  		bindata     *migrationfakes.FakeBindata
    27  		fakeLogFunc = func(logger lager.Logger, id lock.LockID) {}
    28  	)
    29  
    30  	BeforeEach(func() {
    31  		db, err = sql.Open("postgres", postgresRunner.DataSourceName())
    32  		Expect(err).NotTo(HaveOccurred())
    33  
    34  		lockDB, err = sql.Open("postgres", postgresRunner.DataSourceName())
    35  		Expect(err).NotTo(HaveOccurred())
    36  
    37  		lockFactory = lock.NewLockFactory(lockDB, fakeLogFunc, fakeLogFunc)
    38  
    39  		bindata = new(migrationfakes.FakeBindata)
    40  		bindata.AssetStub = asset
    41  	})
    42  
    43  	AfterEach(func() {
    44  		_ = db.Close()
    45  		_ = lockDB.Close()
    46  	})
    47  
    48  	Context("starting with unencrypted DB", func() {
    49  		var (
    50  			key *encryption.Key
    51  		)
    52  		BeforeEach(func() {
    53  			key = createKey("AES256Key-32Characters1234567890")
    54  		})
    55  
    56  		It("encrypts the database", func() {
    57  			migrator := migration.NewMigrator(db, lockFactory)
    58  
    59  			err := migrator.Up(nil, nil)
    60  			Expect(err).ToNot(HaveOccurred())
    61  
    62  			insertIntoEncryptedColumn(db, encryption.NewNoEncryption(), "test")
    63  
    64  			err = migrator.Up(key, nil)
    65  			Expect(err).NotTo(HaveOccurred())
    66  			Expect(isEncryptedWith(db, key, "test")).To(BeTrue())
    67  		})
    68  	})
    69  
    70  	Context("starting with encrypted DB", func() {
    71  		var (
    72  			key1 *encryption.Key
    73  			key2 *encryption.Key
    74  		)
    75  
    76  		BeforeEach(func() {
    77  			key1 = createKey("AES256Key-32Characters1234567890")
    78  			key2 = createKey("AES256Key-32Characters0987654321")
    79  		})
    80  
    81  		Context("removing the encryption key", func() {
    82  			It("decrypts the database", func() {
    83  				migrator := migration.NewMigrator(db, lockFactory)
    84  
    85  				err := migrator.Up(key1, nil)
    86  				Expect(err).ToNot(HaveOccurred())
    87  
    88  				insertIntoEncryptedColumn(db, key1, "test")
    89  
    90  				err = migrator.Up(nil, key1)
    91  				Expect(err).NotTo(HaveOccurred())
    92  				Expect(isEncryptedWith(db, encryption.NewNoEncryption(), "test")).To(BeTrue())
    93  			})
    94  		})
    95  
    96  		Context("rotating the encryption key", func() {
    97  			It("re-encrypts the database with the new key", func() {
    98  				migrator := migration.NewMigrator(db, lockFactory)
    99  
   100  				err := migrator.Up(key2, nil)
   101  				Expect(err).ToNot(HaveOccurred())
   102  
   103  				insertIntoEncryptedColumn(db, key2, "test")
   104  
   105  				err = migrator.Up(key1, key2)
   106  				Expect(err).NotTo(HaveOccurred())
   107  				Expect(isEncryptedWith(db, key1, "test")).To(BeTrue())
   108  			})
   109  
   110  			It("rotates the key while doing a migration", func() {
   111  				migrator := migration.NewMigrator(db, lockFactory)
   112  
   113  				// do all the necessary schema migrations to this particular version
   114  				err = migrator.Migrate(nil, nil, 1513895878)
   115  				Expect(err).ToNot(HaveOccurred())
   116  
   117  				insertIntoEncryptedColumnLegacy(db, key1, "test")
   118  
   119  				// the migration after this one, 1516643303 needs to re-encrypt the auth column
   120  				err := migrator.Up(key2, key1)
   121  				Expect(err).NotTo(HaveOccurred())
   122  				Expect(isEncryptedWith(db, key2, "test")).To(BeTrue())
   123  			})
   124  		})
   125  	})
   126  
   127  	Context("starting with partially encrypted DB", func() {
   128  		var (
   129  			key1     *encryption.Key
   130  			key2     *encryption.Key
   131  			migrator migration.Migrator
   132  		)
   133  
   134  		BeforeEach(func() {
   135  			key1 = createKey("AES256Key-32Characters1234567890")
   136  			key2 = createKey("AES256Key-32Characters0987654321")
   137  			migrator = migration.NewMigrator(db, lockFactory)
   138  
   139  			err := migrator.Up(nil, nil)
   140  			Expect(err).ToNot(HaveOccurred())
   141  
   142  			insertIntoEncryptedColumn(db, encryption.NewNoEncryption(), "row1")
   143  			insertIntoEncryptedColumn(db, key1, "row2")
   144  		})
   145  
   146  		Context("adding the encryption key", func() {
   147  			It("encrypts the database", func() {
   148  				err = migrator.Up(key1, nil)
   149  				Expect(err).NotTo(HaveOccurred())
   150  				Expect(isEncryptedWith(db, key1, "row1")).To(BeTrue())
   151  				Expect(isEncryptedWith(db, key1, "row2")).To(BeTrue())
   152  			})
   153  		})
   154  
   155  		Context("removing the encryption key", func() {
   156  			It("decrypts the database", func() {
   157  				err = migrator.Up(nil, key1)
   158  				Expect(err).NotTo(HaveOccurred())
   159  				Expect(isEncryptedWith(db, encryption.NewNoEncryption(), "row1")).To(BeTrue())
   160  				Expect(isEncryptedWith(db, encryption.NewNoEncryption(), "row2")).To(BeTrue())
   161  			})
   162  		})
   163  
   164  		Context("rotating the encryption key", func() {
   165  			It("re-encrypts the database with the new key", func() {
   166  				err = migrator.Up(key2, key1)
   167  
   168  				Expect(err).NotTo(HaveOccurred())
   169  				Expect(isEncryptedWith(db, key2, "row1")).To(BeTrue())
   170  				Expect(isEncryptedWith(db, key2, "row2")).To(BeTrue())
   171  			})
   172  		})
   173  
   174  		Context("rotating to the same key", func() {
   175  			It("doesn't break", func() {
   176  				err = migrator.Up(key1, key1)
   177  
   178  				Expect(err).NotTo(HaveOccurred())
   179  				Expect(isEncryptedWith(db, key1, "row1")).To(BeTrue())
   180  				Expect(isEncryptedWith(db, key1, "row2")).To(BeTrue())
   181  			})
   182  		})
   183  	})
   184  })
   185  
   186  // used to test database versions before the column got renamed
   187  func insertIntoEncryptedColumnLegacy(db *sql.DB, strategy encryption.Strategy, name string) {
   188  	ciphertext, nonce, err := strategy.Encrypt([]byte("{}"))
   189  	Expect(err).ToNot(HaveOccurred())
   190  	var teamID int
   191  	err = db.QueryRow(`INSERT INTO teams(name, auth, nonce) VALUES($1, $2, $3) RETURNING id`, name, ciphertext, nonce).Scan(&teamID)
   192  	Expect(err).ToNot(HaveOccurred())
   193  
   194  	_, err = db.Exec(fmt.Sprintf(`CREATE TABLE team_build_events_%d () INHERITS (build_events)`, teamID))
   195  	Expect(err).ToNot(HaveOccurred())
   196  }
   197  
   198  func insertIntoEncryptedColumn(db *sql.DB, strategy encryption.Strategy, name string) {
   199  	ciphertext, nonce, err := strategy.Encrypt([]byte("{}"))
   200  	Expect(err).ToNot(HaveOccurred())
   201  	_, err = db.Exec(`INSERT INTO teams(name, legacy_auth, nonce) VALUES($1, $2, $3)`, name, ciphertext, nonce)
   202  	Expect(err).ToNot(HaveOccurred())
   203  }
   204  
   205  func isEncryptedWith(db *sql.DB, strategy encryption.Strategy, name string) bool {
   206  	var (
   207  		ciphertext string
   208  		nonce      *string
   209  	)
   210  	row := db.QueryRow(`SELECT legacy_auth, nonce FROM teams WHERE name = $1`, name)
   211  	err := row.Scan(&ciphertext, &nonce)
   212  	Expect(err).ToNot(HaveOccurred())
   213  
   214  	_, err = strategy.Decrypt(ciphertext, nonce)
   215  	return err == nil
   216  }
   217  
   218  // createKey generates an encryption.Key from a 32 characters key
   219  func createKey(key string) *encryption.Key {
   220  	k := []byte(key)
   221  
   222  	block, err := aes.NewCipher(k)
   223  	Expect(err).ToNot(HaveOccurred())
   224  
   225  	aesgcm, err := cipher.NewGCM(block)
   226  	Expect(err).ToNot(HaveOccurred())
   227  
   228  	return encryption.NewKey(aesgcm)
   229  }