OpenLMI SELinux Design Document

Table of Contents

1 Model

The model is designed to be as close to the actual SELinux implementation as possible, without introducing additional layers of complexity that are usually found in the overly general CIM standards. Hopefully, this effort will make it clear and easier to implement.

1.1 UML

selinux.png

Figure 1: SELinux CIM model

1.2 MOF

/*
 * Copyright (C) 2014 Red Hat, Inc.  All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors: Jan Synacek <jsynacek@redhat.com>
 */

[ Version("0.0.1"),
  Description("Common superclass for all SELinux classes.") ]
class LMI_SELinuxElement: CIM_ManagedElement
{
};

[ Version("0.0.1"),
  Description("Class representing an SELinux policy boolean.") ]
class LMI_SELinuxBoolean : LMI_SELinuxElement
{
  [ Description("Current state.") ]
    boolean State;

  [ Description("State on next system boot.") ]
    boolean DefaultState;
};

[ Version("0.0.1"),
  Description(
    "Class representing an SELinux port. It can encompass multiple "
    "individual network ports, or even their ranges.") ]
class LMI_SELinuxPort : LMI_SELinuxElement
{
  [ Description("Current SELinux context.") ]
    string SELinuxCurrentContext;

  [ Description("Expected SELinux context.") ]
    string SELinuxExpectedContext;

  [ Description("Protocol type. Only UDP and TCP are supported."),
    ValueMap {"0", "1"},
    Values {"UDP", "TCP"} ]
    uint16 Protocol;

  [ Description(
      "Array of open ports that the SELinux port corresponds to.\n"
      "Individual values can be specified either as a single number, or a range.\n"
      "The range would be represented as '<port_low>-<port_high>', e.g. '1024-2048'."
      "Note that a network port can be labeled with multiple labels at the same time.") ]
    string Ports[];
};

[ Version("0.0.1"),
  Description(
    "SELinux on the managed system.\n"
    "SELinux can be in the following states:\n"
    "   Enforcing - SELinux security policy is enforced.\n"
    "   Permissive - SELinux prints warnings instead of enforcing.\n"
    "   Disabled - No SELinux policy is loaded.\n") ]
class LMI_SELinuxService : CIM_Service
{
  [ Description("Current system-wide state of SELinux."),
    ValueMap {"0", "1", "2"},
    Values {"Disabled", "Permissive", "Enforcing"} ]
    uint16 SELinuxState;

  [ Description("SELinux system-wide state on next system boot."),
    ValueMap {"0", "1", "2"},
    Values {"Disabled", "Permissive", "Enforcing"} ]
    uint16 SELinuxDefaultState;

  [ Description("SELinux policy type.") ]
    string PolicyType;

  [ Description("Current version of the SELinux system policy.") ]
    uint32 PolicyVersion;

  [ Description(
    "Set SELinux state."),
    ValueMap {"0", "1", "2", "3", "4", "5", "6"},
    Values {"Job Completed with No Error", "Not Supported",
            "Unknown", "Timeout", "Failed", "Invalid Parameter",
            "In Use"} ]
    uint32 SetSELinuxState(
      [ IN, Description("New state value."),
        ValueMap {"0", "1", "2"},
        Values {"Disabled", "Permissive", "Enforcing"} ]
      uint16 NewState,

      [ IN, Description("If set to True, makes the new state persistent.") ]
      boolean MakeDefault,

      [ IN(false), OUT ]
      LMI_SELinuxJob REF Job
    );

  [ Description(
    "Set label on an SELinux port."),
    ValueMap {"0", "1", "2", "3", "4", "5", "6"},
    Values {"Job Completed with No Error", "Not Supported",
            "Unknown", "Timeout", "Failed", "Invalid Parameter",
            "In Use"} ]
    uint32 SetPortLabel(
      [ IN, OUT, Description("An SELinux port to change.") ]
      LMI_SELinuxPort REF Target,

      [ IN, Description(
        "Network ports to change. Can be specified as a single "
        "port or as range, for example 1024-2048'.") ]
      string PortRange,

      [ IN(false), OUT ]
      LMI_SELinuxJob REF Job
    );

  [ Description(
    "Set label on an SELinux file."),
    ValueMap {"0", "1", "2", "3", "4", "5", "6"},
    Values {"Job Completed with No Error", "Not Supported",
            "Unknown", "Timeout", "Failed", "Invalid Parameter",
            "In Use"} ]
    uint32 SetFileLabel(
      [ IN, OUT, Description("An SELinux file to change.") ]
      LMI_UnixFile REF Target,

      [ IN, Description("New label.") ]
      string Label,

      [ IN(false), OUT ]
      LMI_SELinuxJob REF Job
    );

  [ Description(
    "Set a new value of an SELinux boolean."),
    ValueMap {"0", "1", "2", "3", "4", "5", "6"},
    Values {"Job Completed with No Error", "Not Supported",
            "Unknown", "Timeout", "Failed", "Invalid Parameter",
            "In Use"} ]
    uint32 SetBoolean(
      [ IN, OUT, Description("An SELinux boolean to change.") ]
      LMI_SELinuxBoolean REF Target,

      [ IN, Description("New value.") ]
      boolean Value,

      [ IN, Description("If True, makes the new state persistent.") ]
      boolean MakeDefault,

      [ IN(false), OUT ]
      LMI_SELinuxJob REF Job
    );

  [ Description(
    "Restore default SELinux security contexts on files.\n"
    "There are two actions that can be taken on the specified files:\n"
    " Report: List files whose SELinux label is different than the one specified by the policy.\n"
    " Restore: Restore SELinux label on files to the respective values specified by the policy.\n"),
    ValueMap {"0", "1", "2", "3", "4", "5", "6"},
    Values {"Job Completed with No Error", "Not Supported",
            "Unknown", "Timeout", "Failed", "Invalid Parameter",
            "In Use"} ]
    uint32 RestoreLabels(
      [ IN, OUT, Description("An SELinux file to change.") ]
      LMI_UnixFile REF Target,

      [ IN, Description(""),
        ValueMap {"0", "1", ".."},
        Values {"Report", "Restore", "OpenLMI Reserved"} ]
      uint16 Action,

      [ IN, Description(
        "If True, restore labels recursively in case Target is a directory. "
        "If Target is not a directory, this value is ignored.") ]
      boolean Recursively,

      [ IN(false), OUT ]
      LMI_SELinuxJob REF Job
    );
};


[ Version("0.0.1"),
  Description("Association class the connects the SELinux system service with its elements."),
  Association ]
class LMI_SELinuxServiceHasElement : CIM_Dependency
{
  [ Description("The SELinux element.") ]
    LMI_SELinuxElement REF Antecedent;

  [ Description("The SELinux system service.") ]    
    LMI_SELinuxService REF Dependent;
};

[ Version("0.0.1"),
  Association ]
class LMI_AffectedSELinuxJobElement : CIM_AffectedJobElement
{
};

[ Version("0.0.1"),
  Association ]
class LMI_HostedSELinuxService : CIM_HostedService
{
};

source

2 Example class instances

selinux-instances.png

Figure 2: SELinux class instances

3 Use cases

All the use cases are described using a pseudo language very similar to what the actual LMIShell looks like.

3.1 Booleans

3.1.1 get selinux boolean

LMI_SELinuxBoolean.first_instance({"InstanceID":"LMI:SELinuxBoolean:use_nfs_home_dirs"})

3.1.2 set selinux boolean

s = LMI_SELinuxService.first_instance()
i = LMI_SELinuxBoolean.first_instance({"InstanceID":"LMI:SELinuxBoolean:use_nfs_home_dirs"})

# set boolean without making it permanent
s.SetBoolean(ref i, True, False)

# set boolean permanently
s.SetBoolean(ref i, True, True)

3.2 Ports

3.2.1 show local ports and their labels

LMI_SELinuxPort.EnumerateInstance()

3.2.2 set label on a port

s = LMI_SELinuxService.first_instance()
i = LMI_SELinuxPort.first_instance({"InstanceID":"LMI:SELinuxPort:TCP:virt_port_t"})

s.SetPortLabel(ref i, "new_label_t")

3.3 Service

For all upcoming operations, the service instance is needed.

s = LMI_SELinuxService.first_instance()

Also, all service methods return job instances with affected elements associated to them.

3.3.1 get selinux state

# getenforce
s.State

# get persistent state (/etc/sysconfig/selinux)
s.DefaultState

3.3.2 set selinux state

# setenforce
s.SetSELinuxState(Permissive)

# set persistent state (/etc/sysconfig/selinux)
s.SetSELinuxState(Enforcing, True)

3.3.3 show policy version

s.PolicyVersion

3.3.4 show current mode (enforcing/permissive)

s.SELinuxState

3.3.5 show default mode (from /etc/sysconfig/selinux; enforcing/permissive/disabled)

s.SELinuxDefaultState

3.3.6 restore a file label (optionally with subdirectories)

# restore label on a file
# the 'Recursively' argument has no effect on non-directories
i = LMI_UnixFile.get_instance({"Name":"/home/jsynacek/.bashrc"})
s.RestoreLabels(ref i, 1<"Restore">)

# restore labels in  /etc recursively
# this may potentially take a while
i = LMI_UnixFile.get_instance({"Name":"/etc"})
s.RestoreLabels(ref i, 1<"Restore">, True)

3.3.7 scan a directory and report mislabelled files

# get mislabeled files in /var
i = LMI_UnixFile.get_instance({"Name":"/var"})
s.RestoreLabels(ref i, 0<"Report">, False)

# get mislabeled files in /var recursively
# this may potentially take a while
s.RestoreLabels(ref i, 0<"Report">, True)

4 Additional use cases

If there are any, they will be documented here.

5 Design

Important questions and design desicions will be recorded here, together with a timestamp and version of this document, in which they were made.

Q Question
A Answer
D Desicion

5.1 Q: Should LMI_SELinuxSystem properties by set via ModifyInstance() or by LMI_SELinuxService?   v1

A: Use LMI_SELinuxService. Additionally, a job should be spawned, because some SELinux operations may take a while.

<2014-04-07 Mon>

5.2 D: Put SELinux related methods and general properties under one class (LMI_SELinuxService).   v1

<2014-04-07 Mon>

5.3 D: SELinux files will be called LMI_SELinuxFile.   v1

and will have an identity association with LMI_UnixFile.

<2014-04-07 Mon>

5.4 Q: How should be LMI_SELinuxPort handle InstanceIDs, labels and ports?   v1

Based on the original idea, the InstanceID would be specified as LMI:SELinuxPort:<type>. However, this brings some ambiguity, since there can be two ports open, both with the same label but using different protocols.

selinux-ports.png

Figure 3: SELinux ports

In Figure 3, SELinuxPort_original_1 and SELinuxPort_original_2 represent the original model instances. They have the same InstanceID, yet they represent two different objects. This is not acceptable, since InstanceID must be unique.

This can be resolved in multiple ways:

  1. Put protocol into InstanceID. (objects SELinuxPort_solution_1_1 and 1_2)

    pros: no other change needed

    cons: introduces some redundancy

  2. Leave InstanceID as it is and remodel SELinuxPort. (object SELinuxPort_solution_2)

    pros: only one instance is needed

    cons: brings unneeded complexity

  3. Put the whole label into InstanceID.

    In theory, if SELinux labels on ports were guaranteed to be unique, putting the whole label into InstanceID would solve the problem. But that doesn't have to hold true.

A: Solution 1 is, in my opinion, the best way to solve this problem, so I will go with that.

<2014-04-08 Tue>

5.5 D: There is no intention to enable the modification of policy.   v1

<2014-04-14 Mon>

5.6 Q: There is already LMI_UnixFile with context properties. Why additional SELinux class for that?   v2

A: I wanted the file class to fit into the whole model – inherit from LMI_SELinuxElement. I know about the contexts in LMI_UnixFile and they should be removed from there. The ideal solution would probably be if LMI_UnixFile inherited from LMI_SELinuxElement, which would be multiple inheritance and that is not allowed.

Since the path to a file is a primary key (we care about Unixy systems), it is common sense and the simplest solution to use paths to get references. InstanceID can be used for that. It may seem like a hack at first, and certainly not the CIM way, but, in my opinion, is the best solution.

As a bonus, if there is no SELinux on the managed machine, no SELinux* classes will be present and everything will fit.

<2014-04-17 Thu>

5.7 D: LMI_SELinuxService will use a string for its policy type property.   v2

Although there are some common names for policy types, like Targeted, Strict, MLS, it is probably better to use simple string, instead of an enumeration type. It isn't clear whether the common names may change or custom names can be used.

<2014-04-17 Thu>

5.8 Q: Why not make LMI_SElinuxService.SetSELinuxState() take NewState and NewDefaultState instead?   v2

A: It may appear as a better solution, but if the method is called with both set, and for some reason, setting of only one of them fails, it is not clear what should happen. Should the state be rolled back? Should the method return a special return code saying that it was possible to set only one them?

It is more simple to just call the current method twice and have the operation "atomic".

<2014-04-17 Thu>

5.9 D: The method for restoring/listing mislabeled files shall be called RestoreLabels   v3

Since it functionally resembles the 'restorecon' command.

<2014-04-24 Thu>

5.10 D: LMI_SELinuxFile will be removed from the model   v3

There is already LMI_UnixFile. Although it is a bit of an outlier, it is special anyway, because it can't be enumerated.

<2014-04-24 Thu>

5.11 Q: Shouldn't LMI_SELinuxService.RestoreLabels() be somehow restrained, so it doesn't DoS the server   v3

A: No. Anybody with ssh access to the server may run 'restorecon' on his, possibly huge, directory structure as well.

<2014-04-24 Thu>

6 Older versions

6.1 v1

org / html (diff)

6.2 v2

org / html (diff)
Created by Jan Synáček <jsynacek@redhat.com>, 2014-04-24 Thu 12:58

source