Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / db / migrations / 0066_add_iv.py @ 8d5795b4

History | View | Annotate | Download (18.2 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
        # Cannot decrtypt password
73
        raise CorruptedPassword("Cannot 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
        # Cannot decrtypt password
112
        raise CorruptedPassword("Cannot 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_hash = backend.password_hash
127
            if len(old_hash.split(":")) == 2:
128
                old_pass = decrypt_db_charfield_old(old_hash)
129
                new_hash = encrypt_db_charfield(old_pass)
130
                # Bypass save method!
131
                orm.Backend.objects.filter(id=backend.id).update(password_hash=new_hash)
132

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

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

    
306
    complete_apps = ['db']