Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / db / migrations / 0066_add_iv.py @ 3dbd9457

History | View | Annotate | Download (18.1 kB)

1
# encoding: utf-8
2
import datetime
3
from south.db import db
4
from south.v2 import DataMigration
5
from django.db import models
6
from binascii import b2a_base64, a2b_base64
7
from Crypto.Cipher import AES
8
from Crypto import Random
9
from random import choice
10
from string import letters, digits
11
from synnefo.settings import SECRET_ENCRYPTION_KEY
12

    
13
DB_ENCRYPTED_FIELD_PREFIX = 'encrypted'
14
SALT_LEN = 8
15

    
16

    
17
def _pad_secret(secret, blocksize=32, padding='}'):
18
    len_secret = len(secret)
19
    if len_secret > 32:
20
        raise ValueError('Encryption key must be smaller than 32 bytes')
21
    if not len_secret in (16, 24, 32):
22
        return secret + (blocksize - len(secret)) * padding
23
    return secret
24

    
25

    
26
def encrypt(s, iv=None):
27
    if iv is None:
28
        obj = AES.new(_pad_secret(SECRET_ENCRYPTION_KEY), AES.MODE_CFB)
29
    else:
30
        obj = AES.new(_pad_secret(SECRET_ENCRYPTION_KEY), AES.MODE_CFB, iv)
31
    return obj.encrypt(s)
32

    
33

    
34
def decrypt(s, iv=None):
35
    if iv is None:
36
        obj = AES.new(_pad_secret(SECRET_ENCRYPTION_KEY), AES.MODE_CFB)
37
    else:
38
        obj = AES.new(_pad_secret(SECRET_ENCRYPTION_KEY), AES.MODE_CFB, iv)
39
    return obj.decrypt(s)
40

    
41

    
42
def encrypt_db_charfield_old(plaintext):
43
    if not plaintext:
44
        return plaintext
45
    salt = "".join([choice(letters + digits) for i in xrange(SALT_LEN)])
46

    
47
    plaintext = "%s%s" % (salt, plaintext)
48
    # Encrypt and convert to binary
49
    ciphertext = b2a_base64(encrypt(plaintext))
50
    # Append prefix,salt and return encoded value
51
    final = '%s:%s$%s' % (DB_ENCRYPTED_FIELD_PREFIX, salt, ciphertext)
52
    return final.encode('utf8')
53

    
54

    
55
def decrypt_db_charfield_old(ciphertext):
56
    if not ciphertext:
57
        return ciphertext
58
    has_prefix = ciphertext.startswith(DB_ENCRYPTED_FIELD_PREFIX + ':')
59
    if not has_prefix:  # Non-encoded value
60
        return ciphertext
61
    else:
62
        _, ciphertext = ciphertext.split(':')
63

    
64
    pure_salt, encrypted = ciphertext.split('$')
65

    
66
    plaintext = decrypt(a2b_base64(encrypted))
67

    
68
    salt = plaintext[:SALT_LEN]
69
    plaintext = plaintext[SALT_LEN:]
70

    
71
    if salt != pure_salt:
72
        # Can not decrtypt password
73
        raise CorruptedPassword("Can not decrypt password. Check the key")
74
    else:
75
        return plaintext
76

    
77

    
78
def encrypt_db_charfield(plaintext):
79
    if not plaintext:
80
        return plaintext
81
    salt = "".join([choice(letters + digits) for i in xrange(SALT_LEN)])
82

    
83
    iv = Random.get_random_bytes(16)
84
    plaintext = "%s%s" % (salt, plaintext)
85
    # Encrypt and convert to binary
86
    ciphertext = b2a_base64(encrypt(plaintext, iv))
87
    iv = b2a_base64(iv)
88
    # Append prefix,salt and return encoded value
89
    final = '%s:%s:%s$%s' % (DB_ENCRYPTED_FIELD_PREFIX, iv, salt, ciphertext)
90
    return final.encode('utf8')
91

    
92

    
93
def decrypt_db_charfield(ciphertext):
94
    if not ciphertext:
95
        return ciphertext
96
    has_prefix = ciphertext.startswith(DB_ENCRYPTED_FIELD_PREFIX + ':')
97
    if not has_prefix:  # Non-encoded value
98
        return ciphertext
99
    else:
100
        _, iv, ciphertext = ciphertext.split(':')
101

    
102
    pure_salt, encrypted = ciphertext.split('$')
103
    iv = a2b_base64(iv)
104

    
105
    plaintext = decrypt(a2b_base64(encrypted), iv)
106

    
107
    salt = plaintext[:SALT_LEN]
108
    plaintext = plaintext[SALT_LEN:]
109

    
110
    if salt != pure_salt:
111
        # Can not decrtypt password
112
        raise CorruptedPassword("Can not decrypt password. Check the key")
113
    else:
114
        return plaintext
115

    
116

    
117
class CorruptedPassword(Exception):
118
    pass
119

    
120

    
121
class Migration(DataMigration):
122

    
123
    def forwards(self, orm):
124
        "Write your forwards methods here."
125
        for backend in orm.Backend.objects.all():
126
            old_pass = decrypt_db_charfield_old(backend.password_hash)
127
            new_hash = encrypt_db_charfield(old_pass)
128
            # Bypass save method!
129
            orm.Backend.objects.filter(id=backend.id).update(password_hash=new_hash)
130

    
131

    
132
    def backwards(self, orm):
133
        "Write your backwards methods here."
134
        for backend in orm.Backend.objects.all():
135
            old_pass = decrypt_db_charfield(backend.password_hash)
136
            new_hash = encrypt_db_charfield_old(old_pass)
137
            orm.Backend.objects.filter(id=backend.id).update(password_hash=new_hash)
138

    
139

    
140
    models = {
141
        'db.backend': {
142
            'Meta': {'object_name': 'Backend'},
143
            'clustername': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
144
            'ctotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
145
            'dfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
146
            'drained': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
147
            'dtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
148
            'hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
149
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
150
            'index': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'unique': 'True'}),
151
            'mfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
152
            'mtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
153
            'offline': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
154
            'password_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
155
            'pinst_cnt': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
156
            'port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5080'}),
157
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
158
            'username': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'})
159
        },
160
        'db.backendnetwork': {
161
            'Meta': {'unique_together': "(('network', 'backend'),)", 'object_name': 'BackendNetwork'},
162
            'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'networks'", 'to': "orm['db.Backend']"}),
163
            'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
164
            'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
165
            'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
166
            'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
167
            'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
168
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
169
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
170
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
171
            'mac_prefix': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
172
            'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'backend_networks'", 'to': "orm['db.Network']"}),
173
            'operstate': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '30'}),
174
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
175
        },
176
        'db.bridgepooltable': {
177
            'Meta': {'object_name': 'BridgePoolTable'},
178
            'available_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
179
            'base': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
180
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
181
            'offset': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
182
            'reserved_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
183
            'size': ('django.db.models.fields.IntegerField', [], {})
184
        },
185
        'db.flavor': {
186
            'Meta': {'unique_together': "(('cpu', 'ram', 'disk', 'disk_template'),)", 'object_name': 'Flavor'},
187
            'cpu': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
188
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
189
            'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
190
            'disk_template': ('django.db.models.fields.CharField', [], {'default': "'plain'", 'max_length': '32'}),
191
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
192
            'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'})
193
        },
194
        'db.ippooltable': {
195
            'Meta': {'object_name': 'IPPoolTable'},
196
            'available_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
197
            'base': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
198
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
199
            'offset': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
200
            'reserved_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
201
            'size': ('django.db.models.fields.IntegerField', [], {})
202
        },
203
        'db.macprefixpooltable': {
204
            'Meta': {'object_name': 'MacPrefixPoolTable'},
205
            'available_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
206
            'base': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
207
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208
            'offset': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
209
            'reserved_map': ('django.db.models.fields.TextField', [], {'default': "''"}),
210
            'size': ('django.db.models.fields.IntegerField', [], {})
211
        },
212
        'db.network': {
213
            'Meta': {'object_name': 'Network'},
214
            'action': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '32', 'null': 'True'}),
215
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
216
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
217
            'dhcp': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
218
            'flavor': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
219
            'gateway': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
220
            'gateway6': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
221
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
222
            'link': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
223
            'mac_prefix': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
224
            'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'through': "orm['db.NetworkInterface']", 'symmetrical': 'False'}),
225
            'mode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True'}),
226
            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
227
            'pool': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'network'", 'unique': 'True', 'null': 'True', 'to': "orm['db.IPPoolTable']"}),
228
            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
229
            'serial': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'network'", 'null': 'True', 'to': "orm['db.QuotaHolderSerial']"}),
230
            'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '32'}),
231
            'subnet': ('django.db.models.fields.CharField', [], {'default': "'10.0.0.0/24'", 'max_length': '32'}),
232
            'subnet6': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
233
            'tags': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
234
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
235
            'userid': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'db_index': 'True'})
236
        },
237
        'db.networkinterface': {
238
            'Meta': {'object_name': 'NetworkInterface'},
239
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
240
            'dirty': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
241
            'firewall_profile': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
242
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243
            'index': ('django.db.models.fields.IntegerField', [], {}),
244
            'ipv4': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}),
245
            'ipv6': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
246
            'mac': ('django.db.models.fields.CharField', [], {'max_length': '32', 'unique': 'True', 'null': 'True'}),
247
            'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.VirtualMachine']"}),
248
            'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.Network']"}),
249
            'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '32'}),
250
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
251
        },
252
        'db.quotaholderserial': {
253
            'Meta': {'object_name': 'QuotaHolderSerial'},
254
            'accept': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
255
            'pending': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
256
            'resolved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
257
            'serial': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True', 'db_index': 'True'})
258
        },
259
        'db.virtualmachine': {
260
            'Meta': {'object_name': 'VirtualMachine'},
261
            'action': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
262
            'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'virtual_machines'", 'null': 'True', 'to': "orm['db.Backend']"}),
263
            'backend_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
264
            'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
265
            'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
266
            'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
267
            'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
268
            'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
269
            'buildpercentage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
270
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
271
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
272
            'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}),
273
            'hostid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
274
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275
            'imageid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
276
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
277
            'operstate': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
278
            'serial': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'virtual_machine'", 'null': 'True', 'to': "orm['db.QuotaHolderSerial']"}),
279
            'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
280
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
281
            'userid': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'})
282
        },
283
        'db.virtualmachinediagnostic': {
284
            'Meta': {'object_name': 'VirtualMachineDiagnostic'},
285
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
286
            'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
287
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
288
            'level': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
289
            'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'diagnostics'", 'to': "orm['db.VirtualMachine']"}),
290
            'message': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
291
            'source': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
292
            'source_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'})
293
        },
294
        'db.virtualmachinemetadata': {
295
            'Meta': {'unique_together': "(('meta_key', 'vm'),)", 'object_name': 'VirtualMachineMetadata'},
296
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
297
            'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
298
            'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
299
            'vm': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'metadata'", 'to': "orm['db.VirtualMachine']"})
300
        }
301
    }
302

    
303
    complete_apps = ['db']