audit_dynamic.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /*
  2. Copyright 2019 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 auth
  14. import (
  15. "fmt"
  16. "strings"
  17. "time"
  18. "github.com/onsi/ginkgo"
  19. auditregv1alpha1 "k8s.io/api/auditregistration/v1alpha1"
  20. apiv1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/types"
  24. "k8s.io/apimachinery/pkg/util/intstr"
  25. "k8s.io/apimachinery/pkg/util/wait"
  26. auditinternal "k8s.io/apiserver/pkg/apis/audit"
  27. auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
  28. clientset "k8s.io/client-go/kubernetes"
  29. restclient "k8s.io/client-go/rest"
  30. "k8s.io/kubernetes/test/e2e/framework"
  31. "k8s.io/kubernetes/test/e2e/framework/auth"
  32. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  33. "k8s.io/kubernetes/test/utils"
  34. imageutils "k8s.io/kubernetes/test/utils/image"
  35. )
  36. var _ = SIGDescribe("[Feature:DynamicAudit]", func() {
  37. f := framework.NewDefaultFramework("audit")
  38. ginkgo.It("should dynamically audit API calls", func() {
  39. namespace := f.Namespace.Name
  40. ginkgo.By("Creating a kubernetes client that impersonates an unauthorized anonymous user")
  41. config, err := framework.LoadConfig()
  42. framework.ExpectNoError(err, "failed to fetch config")
  43. config.Impersonate = restclient.ImpersonationConfig{
  44. UserName: "system:anonymous",
  45. Groups: []string{"system:unauthenticated"},
  46. }
  47. anonymousClient, err := clientset.NewForConfig(config)
  48. framework.ExpectNoError(err, "failed to create the anonymous client")
  49. _, err = f.ClientSet.CoreV1().Namespaces().Create(&apiv1.Namespace{
  50. ObjectMeta: metav1.ObjectMeta{
  51. Name: "audit",
  52. },
  53. })
  54. framework.ExpectNoError(err, "failed to create namespace")
  55. _, err = f.ClientSet.CoreV1().Pods(namespace).Create(&apiv1.Pod{
  56. ObjectMeta: metav1.ObjectMeta{
  57. Name: "audit-proxy",
  58. Labels: map[string]string{
  59. "app": "audit",
  60. },
  61. },
  62. Spec: apiv1.PodSpec{
  63. Containers: []apiv1.Container{
  64. {
  65. Name: "proxy",
  66. Image: imageutils.GetE2EImage(imageutils.AuditProxy),
  67. Ports: []apiv1.ContainerPort{
  68. {
  69. ContainerPort: 8080,
  70. },
  71. },
  72. },
  73. },
  74. },
  75. })
  76. framework.ExpectNoError(err, "failed to create proxy pod")
  77. _, err = f.ClientSet.CoreV1().Services(namespace).Create(&apiv1.Service{
  78. ObjectMeta: metav1.ObjectMeta{
  79. Name: "audit",
  80. },
  81. Spec: apiv1.ServiceSpec{
  82. Ports: []apiv1.ServicePort{
  83. {
  84. Port: 80,
  85. TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 8080},
  86. },
  87. },
  88. Selector: map[string]string{
  89. "app": "audit",
  90. },
  91. },
  92. })
  93. framework.ExpectNoError(err, "failed to create proxy service")
  94. config, err = framework.LoadConfig()
  95. framework.ExpectNoError(err, "failed to load config")
  96. var podIP string
  97. // get pod ip
  98. err = wait.Poll(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
  99. p, err := f.ClientSet.CoreV1().Pods(namespace).Get("audit-proxy", metav1.GetOptions{})
  100. if errors.IsNotFound(err) {
  101. e2elog.Logf("waiting for audit-proxy pod to be present")
  102. return false, nil
  103. } else if err != nil {
  104. return false, err
  105. }
  106. podIP = p.Status.PodIP
  107. if podIP == "" {
  108. e2elog.Logf("waiting for audit-proxy pod IP to be ready")
  109. return false, nil
  110. }
  111. return true, nil
  112. })
  113. framework.ExpectNoError(err, "timed out waiting for audit-proxy pod to be ready")
  114. podURL := fmt.Sprintf("http://%s:8080", podIP)
  115. // create audit sink
  116. sink := auditregv1alpha1.AuditSink{
  117. ObjectMeta: metav1.ObjectMeta{
  118. Name: "test",
  119. },
  120. Spec: auditregv1alpha1.AuditSinkSpec{
  121. Policy: auditregv1alpha1.Policy{
  122. Level: auditregv1alpha1.LevelRequestResponse,
  123. Stages: []auditregv1alpha1.Stage{
  124. auditregv1alpha1.StageRequestReceived,
  125. auditregv1alpha1.StageResponseStarted,
  126. auditregv1alpha1.StageResponseComplete,
  127. auditregv1alpha1.StagePanic,
  128. },
  129. },
  130. Webhook: auditregv1alpha1.Webhook{
  131. ClientConfig: auditregv1alpha1.WebhookClientConfig{
  132. URL: &podURL,
  133. },
  134. },
  135. },
  136. }
  137. _, err = f.ClientSet.AuditregistrationV1alpha1().AuditSinks().Create(&sink)
  138. framework.ExpectNoError(err, "failed to create audit sink")
  139. e2elog.Logf("created audit sink")
  140. // check that we are receiving logs in the proxy
  141. err = wait.Poll(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
  142. logs, err := framework.GetPodLogs(f.ClientSet, namespace, "audit-proxy", "proxy")
  143. if err != nil {
  144. e2elog.Logf("waiting for audit-proxy pod logs to be available")
  145. return false, nil
  146. }
  147. if logs == "" {
  148. e2elog.Logf("waiting for audit-proxy pod logs to be non-empty")
  149. return false, nil
  150. }
  151. return true, nil
  152. })
  153. framework.ExpectNoError(err, "failed to get logs from audit-proxy pod")
  154. auditTestUser = "kubernetes-admin"
  155. testCases := []struct {
  156. action func()
  157. events []utils.AuditEvent
  158. }{
  159. // Create, get, update, patch, delete, list, watch pods.
  160. // TODO(@pbarker): dedupe this with the main audit test once policy functionality is available
  161. // https://github.com/kubernetes/kubernetes/issues/70818
  162. {
  163. func() {
  164. pod := &apiv1.Pod{
  165. ObjectMeta: metav1.ObjectMeta{
  166. Name: "audit-pod",
  167. },
  168. Spec: apiv1.PodSpec{
  169. Containers: []apiv1.Container{{
  170. Name: "pause",
  171. Image: imageutils.GetPauseImageName(),
  172. }},
  173. },
  174. }
  175. updatePod := func(pod *apiv1.Pod) {}
  176. f.PodClient().CreateSync(pod)
  177. _, err := f.PodClient().Get(pod.Name, metav1.GetOptions{})
  178. framework.ExpectNoError(err, "failed to get audit-pod")
  179. podChan, err := f.PodClient().Watch(watchOptions)
  180. framework.ExpectNoError(err, "failed to create watch for pods")
  181. for range podChan.ResultChan() {
  182. }
  183. f.PodClient().Update(pod.Name, updatePod)
  184. _, err = f.PodClient().List(metav1.ListOptions{})
  185. framework.ExpectNoError(err, "failed to list pods")
  186. _, err = f.PodClient().Patch(pod.Name, types.JSONPatchType, patch)
  187. framework.ExpectNoError(err, "failed to patch pod")
  188. f.PodClient().DeleteSync(pod.Name, &metav1.DeleteOptions{}, framework.DefaultPodDeletionTimeout)
  189. },
  190. []utils.AuditEvent{
  191. {
  192. Level: auditinternal.LevelRequestResponse,
  193. Stage: auditinternal.StageResponseComplete,
  194. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace),
  195. Verb: "create",
  196. Code: 201,
  197. User: auditTestUser,
  198. Resource: "pods",
  199. Namespace: namespace,
  200. RequestObject: true,
  201. ResponseObject: true,
  202. AuthorizeDecision: "allow",
  203. }, {
  204. Level: auditinternal.LevelRequestResponse,
  205. Stage: auditinternal.StageResponseComplete,
  206. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace),
  207. Verb: "get",
  208. Code: 200,
  209. User: auditTestUser,
  210. Resource: "pods",
  211. Namespace: namespace,
  212. RequestObject: false,
  213. ResponseObject: true,
  214. AuthorizeDecision: "allow",
  215. }, {
  216. Level: auditinternal.LevelRequestResponse,
  217. Stage: auditinternal.StageResponseComplete,
  218. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace),
  219. Verb: "list",
  220. Code: 200,
  221. User: auditTestUser,
  222. Resource: "pods",
  223. Namespace: namespace,
  224. RequestObject: false,
  225. ResponseObject: true,
  226. AuthorizeDecision: "allow",
  227. }, {
  228. Level: auditinternal.LevelRequestResponse,
  229. Stage: auditinternal.StageResponseStarted,
  230. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout),
  231. Verb: "watch",
  232. Code: 200,
  233. User: auditTestUser,
  234. Resource: "pods",
  235. Namespace: namespace,
  236. RequestObject: false,
  237. ResponseObject: false,
  238. AuthorizeDecision: "allow",
  239. }, {
  240. Level: auditinternal.LevelRequestResponse,
  241. Stage: auditinternal.StageResponseComplete,
  242. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout),
  243. Verb: "watch",
  244. Code: 200,
  245. User: auditTestUser,
  246. Resource: "pods",
  247. Namespace: namespace,
  248. RequestObject: false,
  249. ResponseObject: false,
  250. AuthorizeDecision: "allow",
  251. }, {
  252. Level: auditinternal.LevelRequestResponse,
  253. Stage: auditinternal.StageResponseComplete,
  254. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace),
  255. Verb: "update",
  256. Code: 200,
  257. User: auditTestUser,
  258. Resource: "pods",
  259. Namespace: namespace,
  260. RequestObject: true,
  261. ResponseObject: true,
  262. AuthorizeDecision: "allow",
  263. }, {
  264. Level: auditinternal.LevelRequestResponse,
  265. Stage: auditinternal.StageResponseComplete,
  266. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace),
  267. Verb: "patch",
  268. Code: 200,
  269. User: auditTestUser,
  270. Resource: "pods",
  271. Namespace: namespace,
  272. RequestObject: true,
  273. ResponseObject: true,
  274. AuthorizeDecision: "allow",
  275. }, {
  276. Level: auditinternal.LevelRequestResponse,
  277. Stage: auditinternal.StageResponseComplete,
  278. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace),
  279. Verb: "delete",
  280. Code: 200,
  281. User: auditTestUser,
  282. Resource: "pods",
  283. Namespace: namespace,
  284. RequestObject: true,
  285. ResponseObject: true,
  286. AuthorizeDecision: "allow",
  287. },
  288. },
  289. },
  290. }
  291. // test authorizer annotations, RBAC is required.
  292. annotationTestCases := []struct {
  293. action func()
  294. events []utils.AuditEvent
  295. }{
  296. // get a pod with unauthorized user
  297. {
  298. func() {
  299. _, err := anonymousClient.CoreV1().Pods(namespace).Get("another-audit-pod", metav1.GetOptions{})
  300. expectForbidden(err)
  301. },
  302. []utils.AuditEvent{
  303. {
  304. Level: auditinternal.LevelRequestResponse,
  305. Stage: auditinternal.StageResponseComplete,
  306. RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/another-audit-pod", namespace),
  307. Verb: "get",
  308. Code: 403,
  309. User: auditTestUser,
  310. ImpersonatedUser: "system:anonymous",
  311. ImpersonatedGroups: "system:unauthenticated",
  312. Resource: "pods",
  313. Namespace: namespace,
  314. RequestObject: false,
  315. ResponseObject: true,
  316. AuthorizeDecision: "forbid",
  317. },
  318. },
  319. },
  320. }
  321. if auth.IsRBACEnabled(f.ClientSet.RbacV1beta1()) {
  322. testCases = append(testCases, annotationTestCases...)
  323. }
  324. expectedEvents := []utils.AuditEvent{}
  325. for _, t := range testCases {
  326. t.action()
  327. expectedEvents = append(expectedEvents, t.events...)
  328. }
  329. // The default flush timeout is 30 seconds, therefore it should be enough to retry once
  330. // to find all expected events. However, we're waiting for 5 minutes to avoid flakes.
  331. pollingInterval := 30 * time.Second
  332. pollingTimeout := 5 * time.Minute
  333. err = wait.Poll(pollingInterval, pollingTimeout, func() (bool, error) {
  334. // Fetch the logs
  335. logs, err := framework.GetPodLogs(f.ClientSet, namespace, "audit-proxy", "proxy")
  336. if err != nil {
  337. return false, err
  338. }
  339. reader := strings.NewReader(logs)
  340. missingReport, err := utils.CheckAuditLines(reader, expectedEvents, auditv1.SchemeGroupVersion)
  341. if err != nil {
  342. e2elog.Logf("Failed to observe audit events: %v", err)
  343. } else if len(missingReport.MissingEvents) > 0 {
  344. e2elog.Logf(missingReport.String())
  345. }
  346. return len(missingReport.MissingEvents) == 0, nil
  347. })
  348. framework.ExpectNoError(err, "after %v failed to observe audit events", pollingTimeout)
  349. err = f.ClientSet.AuditregistrationV1alpha1().AuditSinks().Delete("test", &metav1.DeleteOptions{})
  350. framework.ExpectNoError(err, "could not delete audit configuration")
  351. })
  352. })