From a5b5c1926d437a8132115bd608d2fd8141982f70 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 8 Mar 2013 13:34:33 -0500 Subject: Reporting: added support for various SELinux entries --- src/lib/Bcfg2/Reporting/models.py | 198 +++++++++++++++++++++++++++++++------- 1 file changed, 162 insertions(+), 36 deletions(-) (limited to 'src/lib/Bcfg2/Reporting/models.py') diff --git a/src/lib/Bcfg2/Reporting/models.py b/src/lib/Bcfg2/Reporting/models.py index ab2dc8418..a04203c73 100644 --- a/src/lib/Bcfg2/Reporting/models.py +++ b/src/lib/Bcfg2/Reporting/models.py @@ -11,21 +11,9 @@ except ImproperlyConfigured: from django.core.cache import cache from datetime import datetime, timedelta +from Bcfg2.Compat import cPickle + -try: - import cPickle as pickle -except: - import pickle - -KIND_CHOICES = ( - #These are the kinds of config elements - ('Package', 'Package'), - ('Path', 'directory'), - ('Path', 'file'), - ('Path', 'permissions'), - ('Path', 'symlink'), - ('Service', 'Service'), -) TYPE_GOOD = 0 TYPE_BAD = 1 TYPE_MODIFIED = 2 @@ -57,8 +45,8 @@ def hash_entry(entry_dict): for key in sorted(entry_dict.keys()): if key in ('id', 'hash_key') or key.startswith('_'): continue - dataset.append( (key, entry_dict[key]) ) - return hash(pickle.dumps(dataset)) + dataset.append((key, entry_dict[key])) + return hash(cPickle.dumps(dataset)) class Client(models.Model): @@ -121,7 +109,8 @@ class InteractionManager(models.Manager): class Interaction(models.Model): - """Models each reconfiguration operation interaction between client and server.""" + """ Models each reconfiguration operation interaction between + client and server. """ client = models.ForeignKey(Client, related_name="interactions") timestamp = models.DateTimeField(db_index=True) # Timestamp for this record state = models.CharField(max_length=32) # good/bad/modified/etc @@ -137,8 +126,21 @@ class Interaction(models.Model): packages = models.ManyToManyField("PackageEntry") paths = models.ManyToManyField("PathEntry") services = models.ManyToManyField("ServiceEntry") + sebooleans = models.ManyToManyField("SEBooleanEntry") + seports = models.ManyToManyField("SEPortEntry") + sefcontexts = models.ManyToManyField("SEFcontextEntry") + senodes = models.ManyToManyField("SENodeEntry") + selogins = models.ManyToManyField("SELoginEntry") + seusers = models.ManyToManyField("SEUserEntry") + seinterfaces = models.ManyToManyField("SEInterfaceEntry") + sepermissives = models.ManyToManyField("SEPermissiveEntry") + semodules = models.ManyToManyField("SEModuleEntry") failures = models.ManyToManyField("FailureEntry") + entry_types = ('actions', 'packages', 'paths', 'services', 'sebooleans', + 'seports', 'sefcontexts', 'senodes', 'selogins', 'seusers', + 'seinterfaces', 'sepermissives', 'semodules') + # Formerly InteractionMetadata profile = models.ForeignKey("Group", related_name="+", null=True) groups = models.ManyToManyField("Group") @@ -157,7 +159,8 @@ class Interaction(models.Model): def percentbad(self): if not self.total_count == 0: - return ((self.total_count - self.good_count) / (float(self.total_count))) * 100 + return ((self.total_count - self.good_count) / + (float(self.total_count))) * 100 else: return 0 @@ -189,7 +192,8 @@ class Interaction(models.Model): self.client.save() # save again post update def delete(self): - '''Override the default delete. Allows us to remove Performance items''' + '''Override the default delete. Allows us to remove + Performance items ''' pitems = list(self.performance_items.all()) super(Interaction, self).delete() for perf in pitems: @@ -201,19 +205,19 @@ class Interaction(models.Model): def bad(self): rv = [] - for entry in ('actions', 'packages', 'paths', 'services'): + for entry in self.entry_types: rv.extend(getattr(self, entry).filter(state=TYPE_BAD)) return rv def modified(self): rv = [] - for entry in ('actions', 'packages', 'paths', 'services'): + for entry in self.entry_types: rv.extend(getattr(self, entry).filter(state=TYPE_MODIFIED)) return rv def extra(self): rv = [] - for entry in ('actions', 'packages', 'paths', 'services'): + for entry in self.entry_types: rv.extend(getattr(self, entry).filter(state=TYPE_EXTRA)) return rv @@ -325,7 +329,6 @@ class BaseEntry(models.Model): self.hash_key = hash_entry(self.__dict__) super(BaseEntry, self).save(*args, **kwargs) - def class_name(self): return self.__class__.__name__ @@ -333,7 +336,6 @@ class BaseEntry(models.Model): """todo""" return [] - @classmethod def entry_from_name(cls, name): try: @@ -344,28 +346,26 @@ class BaseEntry(models.Model): except KeyError: raise ValueError("Invalid type %s" % name) - @classmethod def entry_from_type(cls, etype): - for entry_cls in (ActionEntry, PackageEntry, PathEntry, ServiceEntry): + for entry_cls in ENTRY_CLASSES: if etype == entry_cls.ENTRY_TYPE: return entry_cls else: raise ValueError("Invalid type %s" % etype) - @classmethod def entry_get_or_create(cls, act_dict): """Helper to quickly lookup an object""" cls_name = cls().__class__.__name__ act_hash = hash_entry(act_dict) - + # TODO - get form cache and validate act_key = "%s_%s" % (cls_name, act_hash) newact = cache.get(act_key) if newact: return newact - + acts = cls.objects.filter(hash_key=act_hash) if len(acts) > 0: for act in acts: @@ -375,20 +375,18 @@ class BaseEntry(models.Model): #match found newact = act break - + # worst case, its new if not newact: newact = cls(**act_dict) newact.save(hash_key=act_hash) - + cache.set(act_key, newact, 60 * 60) return newact - def is_failure(self): return isinstance(self, FailureEntry) - @classmethod def prune_orphans(cls): '''Remove unused entries''' @@ -397,7 +395,7 @@ class BaseEntry(models.Model): for x in cls.objects.filter(interaction__isnull=True).values("id")] i = 0 while i < len(cls_orphans): - cls.objects.filter(id__in=cls_orphans[i:i+100]).delete() + cls.objects.filter(id__in=cls_orphans[i:i + 100]).delete() i += 100 @@ -439,13 +437,137 @@ class FailureEntry(BaseEntry): class ActionEntry(SuccessEntry): - """ The new model for package information """ + """ Action entry """ status = models.CharField(max_length=128, default="check") output = models.IntegerField(default=0) ENTRY_TYPE = r"Action" +class SEBooleanEntry(SuccessEntry): + """ SELinux boolean """ + value = models.BooleanField(default=True) + + ENTRY_TYPE = r"SEBoolean" + + +class SEPortEntry(SuccessEntry): + """ SELinux port """ + selinuxtype = models.CharField(max_length=128) + current_selinuxtype = models.CharField(max_length=128, null=True) + + ENTRY_TYPE = r"SEPort" + + def selinuxtype_problem(self): + """Check for an selinux type problem.""" + if not self.current_selinuxtype: + return True + return self.selinuxtype != self.current_selinuxtype + + def short_list(self): + """Return a list of problems""" + rv = super(SEPortEntry, self).short_list() + if self.selinuxtype_problem(): + rv.append("Wrong SELinux type") + return rv + + +class SEFcontextEntry(SuccessEntry): + """ SELinux file context """ + selinuxtype = models.CharField(max_length=128) + current_selinuxtype = models.CharField(max_length=128, null=True) + filetype = models.CharField(max_length=16) + + ENTRY_TYPE = r"SEFcontext" + + def selinuxtype_problem(self): + """Check for an selinux type problem.""" + if not self.current_selinuxtype: + return True + return self.selinuxtype != self.current_selinuxtype + + def short_list(self): + """Return a list of problems""" + rv = super(SEFcontextEntry, self).short_list() + if self.selinuxtype_problem(): + rv.append("Wrong SELinux type") + return rv + + +class SENodeEntry(SuccessEntry): + """ SELinux node """ + selinuxtype = models.CharField(max_length=128) + current_selinuxtype = models.CharField(max_length=128, null=True) + proto = models.CharField(max_length=4) + + ENTRY_TYPE = r"SENode" + + def selinuxtype_problem(self): + """Check for an selinux type problem.""" + if not self.current_selinuxtype: + return True + return self.selinuxtype != self.current_selinuxtype + + def short_list(self): + """Return a list of problems""" + rv = super(SENodeEntry, self).short_list() + if self.selinuxtype_problem(): + rv.append("Wrong SELinux type") + return rv + + +class SELoginEntry(SuccessEntry): + """ SELinux login """ + selinuxuser = models.CharField(max_length=128) + current_selinuxuser = models.CharField(max_length=128, null=True) + + ENTRY_TYPE = r"SELogin" + + +class SEUserEntry(SuccessEntry): + """ SELinux user """ + roles = models.CharField(max_length=128) + current_roles = models.CharField(max_length=128, null=True) + prefix = models.CharField(max_length=128) + current_prefix = models.CharField(max_length=128, null=True) + + ENTRY_TYPE = r"SEUser" + + +class SEInterfaceEntry(SuccessEntry): + """ SELinux interface """ + selinuxtype = models.CharField(max_length=128) + current_selinuxtype = models.CharField(max_length=128, null=True) + + ENTRY_TYPE = r"SEInterface" + + def selinuxtype_problem(self): + """Check for an selinux type problem.""" + if not self.current_selinuxtype: + return True + return self.selinuxtype != self.current_selinuxtype + + def short_list(self): + """Return a list of problems""" + rv = super(SEInterfaceEntry, self).short_list() + if self.selinuxtype_problem(): + rv.append("Wrong SELinux type") + return rv + + +class SEPermissiveEntry(SuccessEntry): + """ SELinux permissive domain """ + ENTRY_TYPE = r"SEPermissive" + + +class SEModuleEntry(SuccessEntry): + """ SELinux module """ + disabled = models.BooleanField(default=False) + current_disabled = models.BooleanField(default=False) + + ENTRY_TYPE = r"SEModule" + + class PackageEntry(SuccessEntry): """ The new model for package information """ @@ -455,7 +577,7 @@ class PackageEntry(SuccessEntry): verification_details = models.TextField(default="") ENTRY_TYPE = r"Package" - #TODO - prune + # TODO - prune def version_problem(self): """Check for a version problem.""" @@ -612,3 +734,7 @@ class ServiceEntry(SuccessEntry): return rv +ENTRY_TYPES = (ActionEntry, PackageEntry, PathEntry, ServiceEntry, + SEBooleanEntry, SEPortEntry, SEFcontextEntry, SENodeEntry, + SELoginEntry, SEUserEntry, SEInterfaceEntry, SEPermissiveEntry, + SEModuleEntry) -- cgit v1.2.3-1-g7c22