| from core.models import User,Site,Service,SingletonModel,PlCoreBase,Slice,SlicePrivilege |
| import os |
| from django.db import models |
| from django.db.models import Q |
| from django.forms.models import model_to_dict |
| from django.core.exceptions import ValidationError, ObjectDoesNotExist |
| |
| # Create your models here. |
| |
| class SyndicateService(SingletonModel,Service): |
| class Meta: |
| app_label = "syndicate_storage" |
| verbose_name = "Syndicate Service" |
| verbose_name_plural = "Syndicate Service" |
| |
| def __unicode__(self): return u'Syndicate Service' |
| |
| |
| class SyndicatePrincipal(PlCoreBase): |
| class Meta: |
| app_label = "syndicate_storage" |
| |
| # for now, this is a user email address |
| principal_id = models.TextField() |
| public_key_pem = models.TextField() |
| sealed_private_key = models.TextField() |
| |
| def __unicode__self(self): return "%s" % self.principal_id |
| |
| |
| class Volume(PlCoreBase): |
| class Meta: |
| app_label = "syndicate_storage" |
| |
| name = models.CharField(max_length=64, help_text="Human-readable, searchable name of the Volume") |
| |
| owner_id = models.ForeignKey(User, verbose_name='Owner') |
| |
| description = models.TextField(null=True, blank=True,max_length=130, help_text="Human-readable description of what this Volume is used for.") |
| blocksize = models.PositiveIntegerField(help_text="Number of bytes per block.") |
| private = models.BooleanField(default=True, help_text="Indicates if the Volume is visible to users other than the Volume Owner and Syndicate Administrators.") |
| 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.") |
| |
| cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data") |
| cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data") |
| cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data") |
| |
| slice_id = models.ManyToManyField(Slice, through="VolumeSlice") |
| |
| def __unicode__(self): return self.name |
| |
| |
| @staticmethod |
| def select_by_user(user): |
| """ |
| Only return Volumes accessible by the user. |
| Admin users can see everything. |
| """ |
| if user.is_admin: |
| qs = Volume.objects.all() |
| else: |
| qs = Volume.objects.filter( Q(owner_id=user) | Q(private=False) ) |
| |
| return qs |
| |
| |
| class VolumeAccessRight(PlCoreBase): |
| class Meta: |
| app_label = "syndicate_storage" |
| |
| owner_id = models.ForeignKey(User, verbose_name='user') |
| |
| volume = models.ForeignKey(Volume) |
| |
| cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data") |
| cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data") |
| cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data") |
| |
| |
| def __unicode__(self): return "%s-%s" % (self.owner_id.email, self.volume.name) |
| |
| |
| class ObserverSecretValue( models.TextField ): |
| class Meta: |
| app_label = "syndicate_storage" |
| |
| __metaclass__ = models.SubfieldBase |
| |
| MAGIC_PREFIX = "$SECRET$:" |
| |
| @classmethod |
| def is_encrypted( cls, secret_str ): |
| # all encrypted secrets start with MAGIC_PREFIX, which is NOT base64-encoded |
| return secret_str.startswith( cls.MAGIC_PREFIX ) |
| |
| @classmethod |
| def unserialize( cls, serialized_ciphertext ): |
| # strip prefix and return ciphertext |
| return serialized_ciphertext[len(cls.MAGIC_PREFIX):] |
| |
| @classmethod |
| def serialize( cls, ciphertext ): |
| # prepend a magic prefix so we know it's encrypted |
| return cls.MAGIC_PREFIX + ciphertext |
| |
| def to_python( self, secret_str ): |
| """ |
| Decrypt the value with the Observer key |
| """ |
| |
| # is this in the clear? |
| if not ObserverSecretValue.is_encrypted( secret_str ): |
| # nothing to do |
| return secret_str |
| |
| # otherwise, decrypt it |
| from syndicate_observer import syndicatelib |
| |
| # get observer private key |
| config = syndicatelib.get_config() |
| |
| try: |
| observer_pkey_path = config.SYNDICATE_PRIVATE_KEY |
| observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path ) |
| except: |
| raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" ) |
| |
| # deserialize |
| secret_str = ObserverSecretValue.unserialize( secret_str ) |
| |
| # decrypt |
| if secret_str is not None and len(secret_str) > 0: |
| |
| slice_secret = syndicatelib.decrypt_slice_secret( observer_pkey_pem, secret_str ) |
| |
| if slice_secret is not None: |
| return slice_secret |
| |
| else: |
| raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to decrypt slice secret value" ) |
| else: |
| return None |
| |
| |
| def pre_save( self, model_inst, add ): |
| """ |
| Encrypt the value with the Observer key |
| """ |
| |
| from syndicate_observer import syndicatelib |
| |
| # get observer private key |
| config = syndicatelib.get_config() |
| |
| try: |
| observer_pkey_path = config.SYNDICATE_PRIVATE_KEY |
| observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path ) |
| except: |
| raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" ) |
| |
| slice_secret = getattr(model_inst, self.attname ) |
| |
| if slice_secret is not None: |
| |
| # encrypt it |
| sealed_slice_secret = syndicatelib.encrypt_slice_secret( observer_pkey_pem, slice_secret ) |
| |
| return ObserverSecretValue.serialize( sealed_slice_secret ) |
| |
| else: |
| raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: No slice secret generated" ) |
| |
| |
| class SliceSecret(models.Model): # NOTE: not a PlCoreBase |
| class Meta: |
| app_label = "syndicate_storage" |
| |
| slice_id = models.ForeignKey(Slice) |
| secret = ObserverSecretValue(blank=True, help_text="Shared secret between OpenCloud and this slice's Syndicate daemons.") |
| |
| def __unicode__(self): return self.slice_id.name |
| |
| @staticmethod |
| def select_by_user(user): |
| """ |
| Only return slice secrets for slices where this user has 'admin' role. |
| Admin users can see everything. |
| """ |
| if user.is_admin: |
| qs = SliceSecret.objects.all() |
| else: |
| visible_slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user,role__role='admin')] |
| qs = SliceSecret.objects.filter(slice_id__id__in=visible_slice_ids) |
| |
| return qs |
| |
| |
| class VolumeSlice(PlCoreBase): |
| class Meta: |
| app_label = "syndicate_storage" |
| |
| volume_id = models.ForeignKey(Volume, verbose_name="Volume") |
| slice_id = models.ForeignKey(Slice, verbose_name="Slice") |
| |
| cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data") |
| cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data") |
| cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data") |
| |
| 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") |
| 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") |
| |
| credentials_blob = models.TextField(null=True, blank=True, help_text="Encrypted slice credentials, sealed with the slice secret.") |
| |
| def __unicode__(self): return "%s-%s" % (self.volume_id.name, self.slice_id.name) |
| |
| def clean(self): |
| """ |
| Verify that our fields are in order: |
| * UG_portnum and RG_portnum have to be valid port numbers between 1025 and 65534 |
| * UG_portnum and RG_portnum cannot be changed once set. |
| * UG_portnum and RG_portnum are unique |
| """ |
| |
| if self.UG_portnum == self.RG_portnum: |
| raise ValidationError( "UG and RG ports must be unique" ) |
| |
| if self.UG_portnum < 1025 or self.UG_portnum > 65534: |
| raise ValidationError( "UG port number must be between 1025 and 65534" ) |
| |
| if self.RG_portnum < 1025 or self.RG_portnum > 65534: |
| raise ValidationError( "RG port number must be between 1025 and 65534" ) |
| |
| |
| def save(self, *args, **kw): |
| """ |
| Make sure a SliceSecret exists for this slice |
| """ |
| |
| from syndicate_observer import syndicatelib |
| |
| # get observer private key |
| config = syndicatelib.get_config() |
| |
| try: |
| observer_pkey_path = config.SYNDICATE_PRIVATE_KEY |
| observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path ) |
| except: |
| raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" ) |
| |
| # get or create the slice secret |
| slice_secret = syndicatelib.get_or_create_slice_secret( observer_pkey_pem, None, slice_fk=self.slice_id ) |
| |
| if slice_secret is None: |
| raise SyndicateObserverError( "Failed to get or create slice secret for %s" % self.slice_id.name ) |
| |
| super(VolumeSlice, self).save(*args, **kw) |
| |
| |
| |