gke_environment_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package e2enode
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "os/exec"
  20. "regexp"
  21. "strconv"
  22. "strings"
  23. "k8s.io/kubernetes/test/e2e/framework"
  24. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  25. imageutils "k8s.io/kubernetes/test/utils/image"
  26. "github.com/blang/semver"
  27. "github.com/onsi/ginkgo"
  28. )
  29. // checkProcess checks whether there's a process whose command line contains
  30. // the specified pattern and whose parent process id is ppid using the
  31. // pre-built information in cmdToProcessMap.
  32. func checkProcess(pattern string, ppid int, cmdToProcessMap map[string][]process) error {
  33. for cmd, processes := range cmdToProcessMap {
  34. if !strings.Contains(cmd, pattern) {
  35. continue
  36. }
  37. for _, p := range processes {
  38. if p.ppid == ppid {
  39. return nil
  40. }
  41. }
  42. }
  43. return fmt.Errorf("failed to find the process whose cmdline contains %q with ppid = %d", pattern, ppid)
  44. }
  45. // checkIPTables checks whether the functionality required by kube-proxy works
  46. // in iptables.
  47. func checkIPTables() (err error) {
  48. cmds := [][]string{
  49. {"iptables", "-N", "KUBE-PORTALS-HOST", "-t", "nat"},
  50. {"iptables", "-I", "OUTPUT", "-t", "nat", "-m", "comment", "--comment", "ClusterIPs", "-j", "KUBE-PORTALS-HOST"},
  51. {"iptables", "-A", "KUBE-PORTALS-HOST", "-t", "nat", "-m", "comment", "--comment", "test-1:", "-p", "tcp", "-m", "tcp", "--dport", "443", "-d", "10.0.0.1/32", "-j", "DNAT", "--to-destination", "10.240.0.1:11111"},
  52. {"iptables", "-C", "KUBE-PORTALS-HOST", "-t", "nat", "-m", "comment", "--comment", "test-1:", "-p", "tcp", "-m", "tcp", "--dport", "443", "-d", "10.0.0.1/32", "-j", "DNAT", "--to-destination", "10.240.0.1:11111"},
  53. {"iptables", "-A", "KUBE-PORTALS-HOST", "-t", "nat", "-m", "comment", "--comment", "test-2:", "-p", "tcp", "-m", "tcp", "--dport", "80", "-d", "10.0.0.1/32", "-j", "REDIRECT", "--to-ports", "22222"},
  54. {"iptables", "-C", "KUBE-PORTALS-HOST", "-t", "nat", "-m", "comment", "--comment", "test-2:", "-p", "tcp", "-m", "tcp", "--dport", "80", "-d", "10.0.0.1/32", "-j", "REDIRECT", "--to-ports", "22222"},
  55. }
  56. cleanupCmds := [][]string{
  57. {"iptables", "-F", "KUBE-PORTALS-HOST", "-t", "nat"},
  58. {"iptables", "-D", "OUTPUT", "-t", "nat", "-m", "comment", "--comment", "ClusterIPs", "-j", "KUBE-PORTALS-HOST"},
  59. {"iptables", "-X", "KUBE-PORTALS-HOST", "-t", "nat"},
  60. }
  61. defer func() {
  62. for _, cmd := range cleanupCmds {
  63. if _, cleanupErr := runCommand(cmd...); cleanupErr != nil && err == nil {
  64. err = cleanupErr
  65. return
  66. }
  67. }
  68. }()
  69. for _, cmd := range cmds {
  70. if _, err := runCommand(cmd...); err != nil {
  71. return err
  72. }
  73. }
  74. return
  75. }
  76. // checkPublicGCR checks the access to the public Google Container Registry by
  77. // pulling the busybox image.
  78. func checkPublicGCR() error {
  79. const image = "k8s.gcr.io/busybox"
  80. output, err := runCommand("docker", "images", "-q", image)
  81. if err != nil {
  82. return err
  83. }
  84. if len(output) != 0 {
  85. if _, err := runCommand("docker", "rmi", "-f", image); err != nil {
  86. return err
  87. }
  88. }
  89. output, err = runCommand("docker", "pull", image)
  90. if err != nil {
  91. return err
  92. }
  93. if len(output) == 0 {
  94. return fmt.Errorf("failed to pull %s", image)
  95. }
  96. if _, err := runCommand("docker", "rmi", "-f", image); err != nil {
  97. return err
  98. }
  99. return nil
  100. }
  101. // checkDockerConfig runs docker's check-config.sh script and ensures that all
  102. // expected kernel configs are enabled.
  103. func checkDockerConfig() error {
  104. var (
  105. re = regexp.MustCompile("\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]")
  106. bins = []string{
  107. "/usr/share/docker.io/contrib/check-config.sh",
  108. "/usr/share/docker/contrib/check-config.sh",
  109. }
  110. whitelist = map[string]bool{
  111. "CONFIG_MEMCG_SWAP_ENABLED": true,
  112. "CONFIG_RT_GROUP_SCHED": true,
  113. "CONFIG_EXT3_FS": true,
  114. "CONFIG_EXT3_FS_XATTR": true,
  115. "CONFIG_EXT3_FS_POSIX_ACL": true,
  116. "CONFIG_EXT3_FS_SECURITY": true,
  117. "/dev/zfs": true,
  118. "zfs command": true,
  119. "zpool command": true,
  120. }
  121. missing = map[string]bool{}
  122. )
  123. // Whitelists CONFIG_DEVPTS_MULTIPLE_INSTANCES (meaning allowing it to be
  124. // absent) if the kernel version is >= 4.8, because this option has been
  125. // removed from the 4.8 kernel.
  126. kernelVersion, err := getKernelVersion()
  127. if err != nil {
  128. return err
  129. }
  130. if kernelVersion.GTE(semver.MustParse("4.8.0")) {
  131. whitelist["CONFIG_DEVPTS_MULTIPLE_INSTANCES"] = true
  132. }
  133. for _, bin := range bins {
  134. if _, err := os.Stat(bin); os.IsNotExist(err) {
  135. continue
  136. }
  137. // We don't check the return code because it's OK if the script returns
  138. // a non-zero exit code just because the configs in the whitelist are
  139. // missing.
  140. output, _ := runCommand(bin)
  141. for _, line := range strings.Split(output, "\n") {
  142. if !strings.Contains(line, "missing") {
  143. continue
  144. }
  145. line = re.ReplaceAllString(line, "")
  146. fields := strings.Split(line, ":")
  147. if len(fields) != 2 {
  148. continue
  149. }
  150. key := strings.TrimFunc(fields[0], func(c rune) bool {
  151. return c == ' ' || c == '-'
  152. })
  153. if _, found := whitelist[key]; !found {
  154. missing[key] = true
  155. }
  156. }
  157. if len(missing) != 0 {
  158. return fmt.Errorf("missing docker config: %v", missing)
  159. }
  160. break
  161. }
  162. return nil
  163. }
  164. // checkDockerNetworkClient checks client networking by pinging an external IP
  165. // address from a container.
  166. func checkDockerNetworkClient() error {
  167. imageName := imageutils.GetE2EImage(imageutils.BusyBox)
  168. output, err := runCommand("docker", "run", "--rm", imageName, "sh", "-c", "ping -w 5 -q google.com")
  169. if err != nil {
  170. return err
  171. }
  172. if !strings.Contains(output, `0% packet loss`) {
  173. return fmt.Errorf("failed to ping from container: %s", output)
  174. }
  175. return nil
  176. }
  177. // checkDockerNetworkServer checks server networking by running an echo server
  178. // within a container and accessing it from outside.
  179. func checkDockerNetworkServer() error {
  180. const (
  181. imageName = "k8s.gcr.io/nginx:1.7.9"
  182. hostAddr = "127.0.0.1"
  183. hostPort = "8088"
  184. containerPort = "80"
  185. containerID = "nginx"
  186. message = "Welcome to nginx!"
  187. )
  188. var (
  189. portMapping = fmt.Sprintf("%s:%s", hostPort, containerPort)
  190. host = fmt.Sprintf("http://%s:%s", hostAddr, hostPort)
  191. )
  192. runCommand("docker", "rm", "-f", containerID)
  193. if _, err := runCommand("docker", "run", "-d", "--name", containerID, "-p", portMapping, imageName); err != nil {
  194. return err
  195. }
  196. output, err := runCommand("curl", host)
  197. if err != nil {
  198. return err
  199. }
  200. if !strings.Contains(output, message) {
  201. return fmt.Errorf("failed to connect to container")
  202. }
  203. // Clean up
  204. if _, err = runCommand("docker", "rm", "-f", containerID); err != nil {
  205. return err
  206. }
  207. if _, err = runCommand("docker", "rmi", imageName); err != nil {
  208. return err
  209. }
  210. return nil
  211. }
  212. // checkDockerAppArmor checks whether AppArmor is enabled and has the
  213. // "docker-default" profile.
  214. func checkDockerAppArmor() error {
  215. buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
  216. if err != nil {
  217. return err
  218. }
  219. if string(buf) != "Y\n" {
  220. return fmt.Errorf("apparmor module is not loaded")
  221. }
  222. // Checks that the "docker-default" profile is loaded and enforced.
  223. buf, err = ioutil.ReadFile("/sys/kernel/security/apparmor/profiles")
  224. if err != nil {
  225. return err
  226. }
  227. if !strings.Contains(string(buf), "docker-default (enforce)") {
  228. return fmt.Errorf("'docker-default' profile is not loaded and enforced")
  229. }
  230. // Checks that the `apparmor_parser` binary is present.
  231. _, err = exec.LookPath("apparmor_parser")
  232. if err != nil {
  233. return fmt.Errorf("'apparmor_parser' is not in directories named by the PATH env")
  234. }
  235. return nil
  236. }
  237. // checkDockerSeccomp checks whether the Docker supports seccomp.
  238. func checkDockerSeccomp() error {
  239. const (
  240. seccompProfileFileName = "/tmp/no_mkdir.json"
  241. seccompProfile = `{
  242. "defaultAction": "SCMP_ACT_ALLOW",
  243. "syscalls": [
  244. {
  245. "name": "mkdir",
  246. "action": "SCMP_ACT_ERRNO"
  247. }
  248. ]}`
  249. image = "gcr.io/google-appengine/debian8:2017-06-07-171918"
  250. )
  251. if err := ioutil.WriteFile(seccompProfileFileName, []byte(seccompProfile), 0644); err != nil {
  252. return err
  253. }
  254. // Starts a container with no seccomp profile and ensures that unshare
  255. // succeeds.
  256. _, err := runCommand("docker", "run", "--rm", "-i", "--security-opt", "seccomp=unconfined", image, "unshare", "-r", "whoami")
  257. if err != nil {
  258. return err
  259. }
  260. // Starts a container with the default seccomp profile and ensures that
  261. // unshare (a blacklisted system call in the default profile) fails.
  262. cmd := []string{"docker", "run", "--rm", "-i", image, "unshare", "-r", "whoami"}
  263. _, err = runCommand(cmd...)
  264. if err == nil {
  265. return fmt.Errorf("%q did not fail as expected", strings.Join(cmd, " "))
  266. }
  267. // Starts a container with a custom seccomp profile that blacklists mkdir
  268. // and ensures that unshare succeeds.
  269. _, err = runCommand("docker", "run", "--rm", "-i", "--security-opt", fmt.Sprintf("seccomp=%s", seccompProfileFileName), image, "unshare", "-r", "whoami")
  270. if err != nil {
  271. return err
  272. }
  273. // Starts a container with a custom seccomp profile that blacklists mkdir
  274. // and ensures that mkdir fails.
  275. cmd = []string{"docker", "run", "--rm", "-i", "--security-opt", fmt.Sprintf("seccomp=%s", seccompProfileFileName), image, "mkdir", "-p", "/tmp/foo"}
  276. _, err = runCommand(cmd...)
  277. if err == nil {
  278. return fmt.Errorf("%q did not fail as expected", strings.Join(cmd, " "))
  279. }
  280. return nil
  281. }
  282. // checkDockerStorageDriver checks whether the current storage driver used by
  283. // Docker is overlay.
  284. func checkDockerStorageDriver() error {
  285. output, err := runCommand("docker", "info")
  286. if err != nil {
  287. return err
  288. }
  289. for _, line := range strings.Split(string(output), "\n") {
  290. if !strings.Contains(line, "Storage Driver:") {
  291. continue
  292. }
  293. if !strings.Contains(line, "overlay") {
  294. return fmt.Errorf("storage driver is not 'overlay': %s", line)
  295. }
  296. return nil
  297. }
  298. return fmt.Errorf("failed to find storage driver")
  299. }
  300. var _ = framework.KubeDescribe("GKE system requirements [NodeConformance][Feature:GKEEnv][NodeFeature:GKEEnv]", func() {
  301. ginkgo.BeforeEach(func() {
  302. e2eskipper.RunIfSystemSpecNameIs("gke")
  303. })
  304. ginkgo.It("The required processes should be running", func() {
  305. cmdToProcessMap, err := getCmdToProcessMap()
  306. framework.ExpectNoError(err)
  307. for _, p := range []struct {
  308. cmd string
  309. ppid int
  310. }{
  311. {"google_accounts_daemon", 1},
  312. {"google_clock_skew_daemon", 1},
  313. {"google_ip_forwarding_daemon", 1},
  314. } {
  315. framework.ExpectNoError(checkProcess(p.cmd, p.ppid, cmdToProcessMap))
  316. }
  317. })
  318. ginkgo.It("The iptable rules should work (required by kube-proxy)", func() {
  319. framework.ExpectNoError(checkIPTables())
  320. })
  321. ginkgo.It("The GCR is accessible", func() {
  322. framework.ExpectNoError(checkPublicGCR())
  323. })
  324. ginkgo.It("The docker configuration validation should pass", func() {
  325. e2eskipper.RunIfContainerRuntimeIs("docker")
  326. framework.ExpectNoError(checkDockerConfig())
  327. })
  328. ginkgo.It("The docker container network should work", func() {
  329. e2eskipper.RunIfContainerRuntimeIs("docker")
  330. framework.ExpectNoError(checkDockerNetworkServer())
  331. framework.ExpectNoError(checkDockerNetworkClient())
  332. })
  333. ginkgo.It("The docker daemon should support AppArmor and seccomp", func() {
  334. e2eskipper.RunIfContainerRuntimeIs("docker")
  335. framework.ExpectNoError(checkDockerAppArmor())
  336. framework.ExpectNoError(checkDockerSeccomp())
  337. })
  338. ginkgo.It("The docker storage driver should work", func() {
  339. e2eskipper.Skipf("GKE does not currently require overlay")
  340. framework.ExpectNoError(checkDockerStorageDriver())
  341. })
  342. })
  343. // getPPID returns the PPID for the pid.
  344. func getPPID(pid int) (int, error) {
  345. statusFile := "/proc/" + strconv.Itoa(pid) + "/status"
  346. content, err := ioutil.ReadFile(statusFile)
  347. if err != nil {
  348. return 0, err
  349. }
  350. for _, line := range strings.Split(string(content), "\n") {
  351. if !strings.HasPrefix(line, "PPid:") {
  352. continue
  353. }
  354. s := strings.TrimSpace(strings.TrimPrefix(line, "PPid:"))
  355. ppid, err := strconv.Atoi(s)
  356. if err != nil {
  357. return 0, err
  358. }
  359. return ppid, nil
  360. }
  361. return 0, fmt.Errorf("no PPid in %s", statusFile)
  362. }
  363. // process contains a process ID and its parent's process ID.
  364. type process struct {
  365. pid int
  366. ppid int
  367. }
  368. // getCmdToProcessMap returns a mapping from the process command line to its
  369. // process ids.
  370. func getCmdToProcessMap() (map[string][]process, error) {
  371. root, err := os.Open("/proc")
  372. if err != nil {
  373. return nil, err
  374. }
  375. defer root.Close()
  376. dirs, err := root.Readdirnames(0)
  377. if err != nil {
  378. return nil, err
  379. }
  380. result := make(map[string][]process)
  381. for _, dir := range dirs {
  382. pid, err := strconv.Atoi(dir)
  383. if err != nil {
  384. continue
  385. }
  386. ppid, err := getPPID(pid)
  387. if err != nil {
  388. continue
  389. }
  390. content, err := ioutil.ReadFile("/proc/" + dir + "/cmdline")
  391. if err != nil || len(content) == 0 {
  392. continue
  393. }
  394. cmd := string(bytes.Replace(content, []byte("\x00"), []byte(" "), -1))
  395. result[cmd] = append(result[cmd], process{pid, ppid})
  396. }
  397. return result, nil
  398. }
  399. // getKernelVersion returns the kernel version in the semantic version format.
  400. func getKernelVersion() (*semver.Version, error) {
  401. output, err := runCommand("uname", "-r")
  402. if err != nil {
  403. return nil, err
  404. }
  405. // An example 'output' could be "4.13.0-1001-gke".
  406. v := strings.TrimSpace(strings.Split(output, "-")[0])
  407. kernelVersion, err := semver.Make(v)
  408. if err != nil {
  409. return nil, fmt.Errorf("failed to convert %q to semantic version: %s", v, err)
  410. }
  411. return &kernelVersion, nil
  412. }