Browse code

Set volume dir SELinux context if possible

Fixes #741

Andy Goldstein authored on 2015/02/04 06:40:47
Showing 2 changed files
... ...
@@ -2,9 +2,11 @@ package kubernetes
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
+	"fmt"
5 6
 	"net"
6 7
 	"net/http"
7 8
 	"os"
9
+	"os/exec"
8 10
 	"path/filepath"
9 11
 	"reflect"
10 12
 	"strconv"
... ...
@@ -17,7 +19,7 @@ import (
17 17
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy"
18 18
 	pconfig "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config"
19 19
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
20
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"
20
+	kexec "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"
21 21
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/iptables"
22 22
 
23 23
 	"github.com/fsouza/go-dockerclient"
... ...
@@ -36,6 +38,22 @@ const NodeScheme = "http"
36 36
 // NodePort is the default Kubelet port for serving information about the node.
37 37
 const NodePort = 10250
38 38
 
39
+type commandExecutor interface {
40
+	LookPath(executable string) (string, error)
41
+	Run(command string, args ...string) error
42
+}
43
+
44
+type defaultCommandExecutor struct{}
45
+
46
+func (ce defaultCommandExecutor) LookPath(executable string) (string, error) {
47
+	return exec.LookPath(executable)
48
+}
49
+
50
+func (ce defaultCommandExecutor) Run(command string, args ...string) error {
51
+	c := exec.Command(command, args...)
52
+	return c.Run()
53
+}
54
+
39 55
 // NodeConfig represents the required parameters to start the OpenShift node
40 56
 // through Kubernetes. All fields are required.
41 57
 type NodeConfig struct {
... ...
@@ -80,17 +98,32 @@ func (c *NodeConfig) EnsureDocker(docker *dockerutil.Helper) {
80 80
 // an absolute path and create the directory if it does not exist. Will exit if
81 81
 // an error is encountered.
82 82
 func (c *NodeConfig) EnsureVolumeDir() {
83
-	rootDirectory, err := filepath.Abs(c.VolumeDir)
83
+	if volumeDir, err := c.initializeVolumeDir(&defaultCommandExecutor{}, c.VolumeDir); err != nil {
84
+		glog.Fatal(err)
85
+	} else {
86
+		c.VolumeDir = volumeDir
87
+	}
88
+}
89
+
90
+func (c *NodeConfig) initializeVolumeDir(ce commandExecutor, path string) (string, error) {
91
+	rootDirectory, err := filepath.Abs(path)
84 92
 	if err != nil {
85
-		glog.Fatalf("Error converting volume directory to an absolute path: %v", err)
93
+		return "", fmt.Errorf("Error converting volume directory to an absolute path: %v", err)
86 94
 	}
87 95
 
88 96
 	if _, err := os.Stat(rootDirectory); os.IsNotExist(err) {
89 97
 		if mkdirErr := os.MkdirAll(rootDirectory, 0750); mkdirErr != nil {
90
-			glog.Fatalf("Couldn't create kubelet volume root directory '%s': %s", rootDirectory, mkdirErr)
98
+			return "", fmt.Errorf("Couldn't create kubelet volume root directory '%s': %s", rootDirectory, mkdirErr)
99
+		}
100
+		if chconPath, chconLookupErr := ce.LookPath("chcon"); chconLookupErr != nil {
101
+			glog.V(2).Infof("Couldn't locate 'chcon' to set the kubelet volume root directory context: %s", chconLookupErr)
102
+		} else {
103
+			if err := ce.Run(chconPath, "-t", "svirt_sandbox_file_t", rootDirectory); err != nil {
104
+				return "", fmt.Errorf("Error running 'chcon' to set the kubelet volume root directory context: %s", err)
105
+			}
91 106
 		}
92 107
 	}
93
-	c.VolumeDir = rootDirectory
108
+	return rootDirectory, nil
94 109
 }
95 110
 
96 111
 // RunKubelet starts the Kubelet.
... ...
@@ -192,7 +225,7 @@ func (c *NodeConfig) RunProxy() {
192 192
 	}
193 193
 
194 194
 	var proxier pconfig.ServiceConfigHandler
195
-	proxier = proxy.NewProxier(loadBalancer, ip, iptables.New(exec.New(), protocol))
195
+	proxier = proxy.NewProxier(loadBalancer, ip, iptables.New(kexec.New(), protocol))
196 196
 	if proxier == nil || reflect.ValueOf(proxier).IsNil() { // explicitly declared interfaces aren't plain nil, you must reflect inside to see if it's really nil or not
197 197
 		glog.Errorf("WARNING: Could not modify iptables.  iptables must be mutable by this process to use services.  Do you have root permissions?")
198 198
 		proxier = &service.FailingServiceConfigProxy{}
199 199
new file mode 100644
... ...
@@ -0,0 +1,95 @@
0
+package kubernetes
1
+
2
+import (
3
+	"errors"
4
+	"io/ioutil"
5
+	"os"
6
+	"path"
7
+)
8
+
9
+import "testing"
10
+
11
+type fakeCommandExecutor struct {
12
+	commandFound bool
13
+	commandErr   error
14
+	runCalled    bool
15
+	lookCalled   bool
16
+}
17
+
18
+func (f *fakeCommandExecutor) LookPath(path string) (string, error) {
19
+	f.lookCalled = true
20
+	if f.commandFound {
21
+		return path, nil
22
+	}
23
+	return "", errors.New("not found")
24
+}
25
+
26
+func (f *fakeCommandExecutor) Run(command string, args ...string) error {
27
+	f.runCalled = true
28
+	return f.commandErr
29
+}
30
+
31
+func TestInitializeVolumeDir(t *testing.T) {
32
+	parentDir, err := ioutil.TempDir("", "")
33
+	if err != nil {
34
+		t.Fatalf("Unable to create parent temp dir: %s", err)
35
+	}
36
+	if err := os.MkdirAll(parentDir, 0750); err != nil {
37
+		t.Fatalf("Error creating volume parent dir: %s", err)
38
+	}
39
+	defer os.RemoveAll(parentDir)
40
+
41
+	testCases := []struct {
42
+		chconFound      bool
43
+		chconRunErr     error
44
+		removeVolumeDir bool
45
+	}{
46
+		{chconFound: false, removeVolumeDir: true},
47
+		{chconFound: true, chconRunErr: nil, removeVolumeDir: true},
48
+		{chconFound: true, chconRunErr: errors.New("e"), removeVolumeDir: true},
49
+		{removeVolumeDir: false},
50
+	}
51
+
52
+	for i, testCase := range testCases {
53
+		ce := &fakeCommandExecutor{
54
+			commandFound: testCase.chconFound,
55
+			commandErr:   testCase.chconRunErr,
56
+		}
57
+		nc := &NodeConfig{VolumeDir: path.Join(parentDir, "somedir")}
58
+
59
+		if testCase.removeVolumeDir {
60
+			if err := os.RemoveAll(nc.VolumeDir); err != nil {
61
+				t.Fatalf("%d: Error removing volume dir: %s", i, err)
62
+			}
63
+		}
64
+
65
+		path, err := nc.initializeVolumeDir(ce, nc.VolumeDir)
66
+
67
+		if testCase.removeVolumeDir {
68
+			if !ce.lookCalled {
69
+				t.Fatalf("%d: expected look for chcon", i)
70
+			}
71
+			if !testCase.chconFound && ce.runCalled {
72
+				t.Fatalf("%d: unexpected run after chcon not found", i)
73
+			}
74
+			if testCase.chconFound && !ce.runCalled {
75
+				t.Fatalf("%d: expected chcon run", i)
76
+			}
77
+			haveErr := err != nil
78
+			expectErr := testCase.chconRunErr != nil
79
+			if expectErr != haveErr {
80
+				t.Fatalf("%d: expected chcon run err: %t, got: %t, %s", i, expectErr, haveErr, err)
81
+			}
82
+			if !haveErr && path != nc.VolumeDir {
83
+				t.Fatalf("%d:, expected path(%s) == nc.VolumeDir(%s)", i, path, nc.VolumeDir)
84
+			}
85
+		} else {
86
+			if ce.lookCalled {
87
+				t.Fatalf("%d: unexpected look for chcon with reused volume dir", i)
88
+			}
89
+			if ce.runCalled {
90
+				t.Fatalf("%d: unexpected run for chcon with reused volume dir", i)
91
+			}
92
+		}
93
+	}
94
+}