blob: e2f98be967e968d8bc74e042019a77a99e93189f [file] [log] [blame]
jcnelson575a1352014-07-10 19:33:34 -04001from core.models import User,Site,Service,SingletonModel,PlCoreBase,Slice,SlicePrivilege
2import os
3from django.db import models
4from django.db.models import Q
5from django.forms.models import model_to_dict
6from django.core.exceptions import ValidationError, ObjectDoesNotExist
7
8# Create your models here.
9
10class SyndicateService(SingletonModel,Service):
11 class Meta:
12 app_label = "syndicate_storage"
13 verbose_name = "Syndicate Service"
14 verbose_name_plural = "Syndicate Service"
15
16 def __unicode__(self): return u'Syndicate Service'
17
18
19class SyndicatePrincipal(PlCoreBase):
20 class Meta:
21 app_label = "syndicate_storage"
22
23 # for now, this is a user email address
jcnelson94ce1e12014-07-16 15:32:29 -040024 principal_id = models.TextField(unique=True)
jcnelson575a1352014-07-10 19:33:34 -040025 public_key_pem = models.TextField()
26 sealed_private_key = models.TextField()
27
28 def __unicode__self(self): return "%s" % self.principal_id
29
30
31class Volume(PlCoreBase):
32 class Meta:
33 app_label = "syndicate_storage"
34
35 name = models.CharField(max_length=64, help_text="Human-readable, searchable name of the Volume")
36
37 owner_id = models.ForeignKey(User, verbose_name='Owner')
38
39 description = models.TextField(null=True, blank=True,max_length=130, help_text="Human-readable description of what this Volume is used for.")
40 blocksize = models.PositiveIntegerField(help_text="Number of bytes per block.")
41 private = models.BooleanField(default=True, help_text="Indicates if the Volume is visible to users other than the Volume Owner and Syndicate Administrators.")
42 archive = models.BooleanField(default=False, help_text="Indicates if this Volume is read-only, and only an Aquisition Gateway owned by the Volume owner (or Syndicate admin) can write to it.")
43
44 cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data")
45 cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data")
46 cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data")
47
48 slice_id = models.ManyToManyField(Slice, through="VolumeSlice")
49
50 def __unicode__(self): return self.name
51
52
53 @staticmethod
54 def select_by_user(user):
55 """
56 Only return Volumes accessible by the user.
57 Admin users can see everything.
58 """
59 if user.is_admin:
60 qs = Volume.objects.all()
61 else:
62 qs = Volume.objects.filter( Q(owner_id=user) | Q(private=False) )
63
64 return qs
65
66
67class VolumeAccessRight(PlCoreBase):
68 class Meta:
69 app_label = "syndicate_storage"
70
71 owner_id = models.ForeignKey(User, verbose_name='user')
72
73 volume = models.ForeignKey(Volume)
74
75 cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data")
76 cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data")
77 cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data")
78
79
80 def __unicode__(self): return "%s-%s" % (self.owner_id.email, self.volume.name)
81
82
83class ObserverSecretValue( models.TextField ):
84 class Meta:
85 app_label = "syndicate_storage"
86
87 __metaclass__ = models.SubfieldBase
88
89 MAGIC_PREFIX = "$SECRET$:"
90
91 @classmethod
92 def is_encrypted( cls, secret_str ):
93 # all encrypted secrets start with MAGIC_PREFIX, which is NOT base64-encoded
94 return secret_str.startswith( cls.MAGIC_PREFIX )
95
96 @classmethod
97 def unserialize( cls, serialized_ciphertext ):
98 # strip prefix and return ciphertext
99 return serialized_ciphertext[len(cls.MAGIC_PREFIX):]
100
101 @classmethod
102 def serialize( cls, ciphertext ):
103 # prepend a magic prefix so we know it's encrypted
104 return cls.MAGIC_PREFIX + ciphertext
105
106 def to_python( self, secret_str ):
107 """
108 Decrypt the value with the Observer key
109 """
110
111 # is this in the clear?
112 if not ObserverSecretValue.is_encrypted( secret_str ):
113 # nothing to do
114 return secret_str
115
116 # otherwise, decrypt it
117 from syndicate_observer import syndicatelib
118
119 # get observer private key
120 config = syndicatelib.get_config()
121
122 try:
123 observer_pkey_path = config.SYNDICATE_PRIVATE_KEY
124 observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path )
125 except:
126 raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" )
127
128 # deserialize
129 secret_str = ObserverSecretValue.unserialize( secret_str )
130
131 # decrypt
132 if secret_str is not None and len(secret_str) > 0:
133
134 slice_secret = syndicatelib.decrypt_slice_secret( observer_pkey_pem, secret_str )
135
136 if slice_secret is not None:
137 return slice_secret
138
139 else:
140 raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to decrypt slice secret value" )
141 else:
142 return None
143
144
145 def pre_save( self, model_inst, add ):
146 """
147 Encrypt the value with the Observer key
148 """
149
150 from syndicate_observer import syndicatelib
151
152 # get observer private key
153 config = syndicatelib.get_config()
154
155 try:
156 observer_pkey_path = config.SYNDICATE_PRIVATE_KEY
157 observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path )
158 except:
159 raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" )
160
161 slice_secret = getattr(model_inst, self.attname )
162
163 if slice_secret is not None:
164
165 # encrypt it
166 sealed_slice_secret = syndicatelib.encrypt_slice_secret( observer_pkey_pem, slice_secret )
167
168 return ObserverSecretValue.serialize( sealed_slice_secret )
169
170 else:
171 raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: No slice secret generated" )
172
173
174class SliceSecret(models.Model): # NOTE: not a PlCoreBase
175 class Meta:
176 app_label = "syndicate_storage"
177
178 slice_id = models.ForeignKey(Slice)
179 secret = ObserverSecretValue(blank=True, help_text="Shared secret between OpenCloud and this slice's Syndicate daemons.")
180
181 def __unicode__(self): return self.slice_id.name
182
183 @staticmethod
184 def select_by_user(user):
185 """
186 Only return slice secrets for slices where this user has 'admin' role.
187 Admin users can see everything.
188 """
189 if user.is_admin:
190 qs = SliceSecret.objects.all()
191 else:
192 visible_slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user,role__role='admin')]
193 qs = SliceSecret.objects.filter(slice_id__id__in=visible_slice_ids)
194
195 return qs
196
197
198class VolumeSlice(PlCoreBase):
199 class Meta:
200 app_label = "syndicate_storage"
201
202 volume_id = models.ForeignKey(Volume, verbose_name="Volume")
203 slice_id = models.ForeignKey(Slice, verbose_name="Slice")
204
205 cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data")
206 cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data")
207 cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data")
208
209 UG_portnum = models.PositiveIntegerField(help_text="User Gateway port. Any port above 1024 will work, but it must be available slice-wide.", verbose_name="UG port")
210 RG_portnum = models.PositiveIntegerField(help_text="Replica Gateway port. Any port above 1024 will work, but it must be available slice-wide.", verbose_name="RG port")
211
212 credentials_blob = models.TextField(null=True, blank=True, help_text="Encrypted slice credentials, sealed with the slice secret.")
213
214 def __unicode__(self): return "%s-%s" % (self.volume_id.name, self.slice_id.name)
215
216 def clean(self):
217 """
218 Verify that our fields are in order:
219 * UG_portnum and RG_portnum have to be valid port numbers between 1025 and 65534
220 * UG_portnum and RG_portnum cannot be changed once set.
221 * UG_portnum and RG_portnum are unique
222 """
223
224 if self.UG_portnum == self.RG_portnum:
225 raise ValidationError( "UG and RG ports must be unique" )
226
227 if self.UG_portnum < 1025 or self.UG_portnum > 65534:
228 raise ValidationError( "UG port number must be between 1025 and 65534" )
229
230 if self.RG_portnum < 1025 or self.RG_portnum > 65534:
231 raise ValidationError( "RG port number must be between 1025 and 65534" )
232
233
234 def save(self, *args, **kw):
235 """
236 Make sure a SliceSecret exists for this slice
237 """
238
239 from syndicate_observer import syndicatelib
240
241 # get observer private key
242 config = syndicatelib.get_config()
243
244 try:
245 observer_pkey_path = config.SYNDICATE_PRIVATE_KEY
246 observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path )
247 except:
248 raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" )
249
250 # get or create the slice secret
251 slice_secret = syndicatelib.get_or_create_slice_secret( observer_pkey_pem, None, slice_fk=self.slice_id )
252
253 if slice_secret is None:
254 raise SyndicateObserverError( "Failed to get or create slice secret for %s" % self.slice_id.name )
255
256 super(VolumeSlice, self).save(*args, **kw)
257
258
259