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(®ister.Test{ 29 Run: SelinuxEnforce, 30 ClusterSize: 1, 31 Name: "coreos.selinux.enforce", 32 Distros: []string{"cl", "fcos", "rhcos"}, 33 }) 34 register.Register(®ister.Test{ 35 Run: SelinuxBoolean, 36 ClusterSize: 1, 37 Name: "coreos.selinux.boolean", 38 Distros: []string{"cl", "fcos", "rhcos"}, 39 }) 40 register.Register(®ister.Test{ 41 Run: SelinuxBooleanPersist, 42 ClusterSize: 1, 43 Name: "rhcos.selinux.boolean.persist", 44 Distros: []string{"fcos", "rhcos"}, 45 }) 46 register.Register(®ister.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 }