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 }