github.com/coreos/mantle@v0.13.0/kola/tests/misc/selinux.go (about)

     1  // Copyright 2016 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package misc
    16  
    17  import (
    18  	"fmt"
    19  	"regexp"
    20  	"strings"
    21  
    22  	"github.com/coreos/mantle/kola/cluster"
    23  	"github.com/coreos/mantle/kola/register"
    24  	"github.com/coreos/mantle/platform"
    25  )
    26  
    27  func init() {
    28  	register.Register(&register.Test{
    29  		Run:         SelinuxEnforce,
    30  		ClusterSize: 1,
    31  		Name:        "coreos.selinux.enforce",
    32  		Distros:     []string{"cl", "fcos", "rhcos"},
    33  	})
    34  	register.Register(&register.Test{
    35  		Run:         SelinuxBoolean,
    36  		ClusterSize: 1,
    37  		Name:        "coreos.selinux.boolean",
    38  		Distros:     []string{"cl", "fcos", "rhcos"},
    39  	})
    40  	register.Register(&register.Test{
    41  		Run:         SelinuxBooleanPersist,
    42  		ClusterSize: 1,
    43  		Name:        "rhcos.selinux.boolean.persist",
    44  		Distros:     []string{"fcos", "rhcos"},
    45  	})
    46  	register.Register(&register.Test{
    47  		Run:         SelinuxManage,
    48  		ClusterSize: 1,
    49  		Name:        "rhcos.selinux.manage",
    50  		Distros:     []string{"rhcos"},
    51  	})
    52  }
    53  
    54  // cmdCheckOutput is used by `testSelinuxCmds()`. It contains a
    55  // command to run, a bool indicating if output should be matched,
    56  // and the regex used for the match
    57  type cmdCheckOutput struct {
    58  	cmdline     string // command to run
    59  	checkoutput bool   // should output be checked
    60  	match       string // regex used to match output from command
    61  }
    62  
    63  // seBooleanState is used by `getSelinuxBooleanState()` to store the
    64  // original value of the boolean and the flipped value of the boolean
    65  type seBooleanState struct {
    66  	originalValue string // original boolean value from `getseboolean`
    67  	newValue      string // new, opposite boolean value
    68  }
    69  
    70  // testSelinuxCmds will run a list of commands, optionally check their output, and
    71  // ultimately reboot the host
    72  func testSelinuxCmds(c cluster.TestCluster, m platform.Machine, cmds []cmdCheckOutput) {
    73  	for _, cmd := range cmds {
    74  		output := c.MustSSH(m, cmd.cmdline)
    75  
    76  		if cmd.checkoutput {
    77  			match := regexp.MustCompile(cmd.match).MatchString(string(output))
    78  			if !match {
    79  				c.Fatalf("command %q has unexpected output: tried to match regexp %q, output was %q", cmd.cmdline, cmd.match, string(output))
    80  			}
    81  		}
    82  	}
    83  
    84  	err := m.Reboot()
    85  	if err != nil {
    86  		c.Fatalf("failed to reboot machine: %v", err)
    87  	}
    88  }
    89  
    90  // getSelinuxBooleanState checks the original value of a provided SELinux boolean
    91  // and returns a seBooleanState struct with original + opposite/new value
    92  func getSelinuxBooleanState(c cluster.TestCluster, m platform.Machine, seBool string) (seBooleanState, error) {
    93  	boolState := seBooleanState{}
    94  
    95  	// get original value of boolean
    96  	reString := seBool + " --> (on|off)"
    97  	reBool, _ := regexp.Compile(reString)
    98  	origOut, err := c.SSH(m, "getsebool "+seBool)
    99  	if err != nil {
   100  		return boolState, fmt.Errorf(`Could not get SELinux boolean: %v`, err)
   101  	}
   102  
   103  	match := reBool.FindStringSubmatch(string(origOut))
   104  
   105  	if match == nil {
   106  		return boolState, fmt.Errorf(`failed to match regexp %q, from output %q`, reString, origOut)
   107  	}
   108  
   109  	origBool := match[1]
   110  
   111  	if string(origBool) == "off" {
   112  		boolState.originalValue = "off"
   113  		boolState.newValue = "on"
   114  	} else if string(origBool) == "on" {
   115  		boolState.originalValue = "on"
   116  		boolState.newValue = "off"
   117  	} else {
   118  		return boolState, fmt.Errorf(`failed to match boolean value; expected "on" or "off", got %q`, string(origBool))
   119  	}
   120  
   121  	return boolState, nil
   122  }
   123  
   124  // SelinuxEnforce checks that some basic things work after `setenforce 1`
   125  func SelinuxEnforce(c cluster.TestCluster) {
   126  	cmdList := []cmdCheckOutput{
   127  		{"getenforce", true, "Enforcing"},
   128  		{"sudo setenforce 0", false, ""},
   129  		{"getenforce", true, "Permissive"},
   130  		{"sudo setenforce 1", false, ""},
   131  		{"getenforce", true, "Enforcing"},
   132  		{"systemctl --no-pager is-active system.slice", true, "active"},
   133  		{"sudo cp /etc/selinux/config{,.old}", false, ""},
   134  		{"sudo sed -i 's/SELINUX=permissive/SELINUX=enforcing/' /etc/selinux/config", false, ""},
   135  	}
   136  
   137  	m := c.Machines()[0]
   138  
   139  	testSelinuxCmds(c, m, cmdList)
   140  
   141  	output := c.MustSSH(m, "getenforce")
   142  
   143  	if string(output) != "Enforcing" {
   144  		c.Fatalf(`command "getenforce" has unexpected output: want %q, got %q`, "Enforcing", string(output))
   145  	}
   146  }
   147  
   148  // SelinuxBoolean checks that you can tweak a boolean in the current session
   149  func SelinuxBoolean(c cluster.TestCluster) {
   150  	seBoolean := "virt_use_nfs"
   151  
   152  	m := c.Machines()[0]
   153  
   154  	tempBoolState, err := getSelinuxBooleanState(c, m, seBoolean)
   155  	if err != nil {
   156  		c.Fatalf(`Failed to gather SELinux boolean state: %v`, err)
   157  	}
   158  
   159  	// construct a regexp that looks like ".*off" or ".*on"
   160  	tempBoolRegexp := ".*" + tempBoolState.newValue
   161  	cmdList := []cmdCheckOutput{
   162  		{fmt.Sprintf("sudo setsebool %s %s", seBoolean, tempBoolState.newValue), false, ""},
   163  		{"getsebool " + seBoolean, true, tempBoolRegexp},
   164  	}
   165  
   166  	testSelinuxCmds(c, m, cmdList)
   167  
   168  	// since we didn't persist the change, should return to default value
   169  	postOut := c.MustSSH(m, "getsebool "+seBoolean)
   170  	postBool := strings.Split(string(postOut), " ")[2]
   171  
   172  	// newBool[0] contains the original value of the boolean
   173  	if postBool != tempBoolState.originalValue {
   174  		c.Fatalf(`The SELinux boolean "%q" is incorrectly configured: wanted %q, got %q`, seBoolean, tempBoolState.originalValue, postBool)
   175  	}
   176  }
   177  
   178  // SelinuxBooleanPersist checks that you can tweak a boolean and have it
   179  // persist across reboots
   180  func SelinuxBooleanPersist(c cluster.TestCluster) {
   181  	seBoolean := "virt_use_nfs"
   182  
   183  	m := c.Machines()[0]
   184  
   185  	persistBoolState, err := getSelinuxBooleanState(c, m, seBoolean)
   186  	if err != nil {
   187  		c.Fatalf(`Failed to gather SELinux boolean state: %v`, err)
   188  	}
   189  
   190  	// construct a regexp that looks like ".*off" or ".*on"
   191  	persistBoolRegexp := ".*" + persistBoolState.newValue
   192  	cmdList := []cmdCheckOutput{
   193  		{fmt.Sprintf("sudo setsebool -P %s %s", seBoolean, persistBoolState.newValue), false, ""},
   194  		{"getsebool " + seBoolean, true, persistBoolRegexp},
   195  	}
   196  
   197  	testSelinuxCmds(c, m, cmdList)
   198  
   199  	// the change should be persisted after a reboot
   200  	postOut := c.MustSSH(m, "getsebool "+seBoolean)
   201  	postBool := strings.Split(string(postOut), " ")[2]
   202  
   203  	if postBool != persistBoolState.newValue {
   204  		c.Fatalf(`The SELinux boolean "%q" is incorrectly configured: wanted %q, got %q`, seBoolean, persistBoolState.newValue, postBool)
   205  	}
   206  }
   207  
   208  // SelinuxManage checks that you can modify an SELinux file context and
   209  // have it persist across reboots
   210  func SelinuxManage(c cluster.TestCluster) {
   211  	cmdList := []cmdCheckOutput{
   212  		{"sudo semanage fcontext -l | grep vasd", true, ".*system_u:object_r:var_auth_t:s0"},
   213  		{"sudo semanage fcontext -m -t httpd_log_t \"/var/opt/quest/vas/vasd(/.*)?\"", false, ""},
   214  		{"sudo semanage fcontext -l | grep vasd", true, ".*system_u:object_r:httpd_log_t:s0"},
   215  	}
   216  
   217  	m := c.Machines()[0]
   218  
   219  	testSelinuxCmds(c, m, cmdList)
   220  
   221  	// the change should be persisted after a reboot
   222  	output := c.MustSSH(m, "sudo semanage fcontext -l | grep vasd")
   223  
   224  	s := ".*system_u:object_r:httpd_log_t:s0"
   225  	match := regexp.MustCompile(s).MatchString(string(output))
   226  	if !match {
   227  		c.Fatalf(`The SELinux file context "/var/opt/quest/vas/vasd(/.*)?" is incorrectly configured.  Tried to match regexp %q, output was %q`, s, string(output))
   228  	}
   229  }