cani_test.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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 auth
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io/ioutil"
  18. "net/http"
  19. "strings"
  20. "testing"
  21. authorizationv1 "k8s.io/api/authorization/v1"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "k8s.io/cli-runtime/pkg/genericclioptions"
  25. restclient "k8s.io/client-go/rest"
  26. "k8s.io/client-go/rest/fake"
  27. cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
  28. "k8s.io/kubernetes/pkg/kubectl/scheme"
  29. )
  30. func TestRunAccessCheck(t *testing.T) {
  31. tests := []struct {
  32. name string
  33. o *CanIOptions
  34. args []string
  35. allowed bool
  36. serverErr error
  37. expectedBodyStrings []string
  38. }{
  39. {
  40. name: "restmapping for args",
  41. o: &CanIOptions{},
  42. args: []string{"get", "replicaset"},
  43. allowed: true,
  44. expectedBodyStrings: []string{
  45. `{"resourceAttributes":{"namespace":"test","verb":"get","group":"extensions","resource":"replicasets"}}`,
  46. },
  47. },
  48. {
  49. name: "simple success",
  50. o: &CanIOptions{},
  51. args: []string{"get", "deployments.extensions/foo"},
  52. allowed: true,
  53. expectedBodyStrings: []string{
  54. `{"resourceAttributes":{"namespace":"test","verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
  55. },
  56. },
  57. {
  58. name: "all namespaces",
  59. o: &CanIOptions{
  60. AllNamespaces: true,
  61. },
  62. args: []string{"get", "deployments.extensions/foo"},
  63. allowed: true,
  64. expectedBodyStrings: []string{
  65. `{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
  66. },
  67. },
  68. {
  69. name: "disallowed",
  70. o: &CanIOptions{
  71. AllNamespaces: true,
  72. },
  73. args: []string{"get", "deployments.extensions/foo"},
  74. allowed: false,
  75. expectedBodyStrings: []string{
  76. `{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
  77. },
  78. },
  79. {
  80. name: "forcedError",
  81. o: &CanIOptions{
  82. AllNamespaces: true,
  83. },
  84. args: []string{"get", "deployments.extensions/foo"},
  85. allowed: false,
  86. serverErr: fmt.Errorf("forcedError"),
  87. expectedBodyStrings: []string{
  88. `{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
  89. },
  90. },
  91. {
  92. name: "sub resource",
  93. o: &CanIOptions{
  94. AllNamespaces: true,
  95. Subresource: "log",
  96. },
  97. args: []string{"get", "pods"},
  98. allowed: true,
  99. expectedBodyStrings: []string{
  100. `{"resourceAttributes":{"verb":"get","resource":"pods","subresource":"log"}}`,
  101. },
  102. },
  103. {
  104. name: "nonResourceURL",
  105. o: &CanIOptions{},
  106. args: []string{"get", "/logs"},
  107. allowed: true,
  108. expectedBodyStrings: []string{
  109. `{"nonResourceAttributes":{"path":"/logs","verb":"get"}}`,
  110. },
  111. },
  112. }
  113. for _, test := range tests {
  114. t.Run(test.name, func(t *testing.T) {
  115. test.o.Out = ioutil.Discard
  116. test.o.ErrOut = ioutil.Discard
  117. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  118. defer tf.Cleanup()
  119. ns := scheme.Codecs
  120. tf.Client = &fake.RESTClient{
  121. GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
  122. NegotiatedSerializer: ns,
  123. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  124. expectPath := "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews"
  125. if req.URL.Path != expectPath {
  126. t.Errorf("%s: expected %v, got %v", test.name, expectPath, req.URL.Path)
  127. return nil, nil
  128. }
  129. bodyBits, err := ioutil.ReadAll(req.Body)
  130. if err != nil {
  131. t.Errorf("%s: %v", test.name, err)
  132. return nil, nil
  133. }
  134. body := string(bodyBits)
  135. for _, expectedBody := range test.expectedBodyStrings {
  136. if !strings.Contains(body, expectedBody) {
  137. t.Errorf("%s expecting %s in %s", test.name, expectedBody, body)
  138. }
  139. }
  140. return &http.Response{
  141. StatusCode: http.StatusOK,
  142. Body: ioutil.NopCloser(bytes.NewBufferString(
  143. fmt.Sprintf(`{"kind":"SelfSubjectAccessReview","apiVersion":"authorization.k8s.io/v1","status":{"allowed":%v}}`, test.allowed),
  144. )),
  145. },
  146. test.serverErr
  147. }),
  148. }
  149. tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}
  150. if err := test.o.Complete(tf, test.args); err != nil {
  151. t.Errorf("%s: %v", test.name, err)
  152. return
  153. }
  154. actualAllowed, err := test.o.RunAccessCheck()
  155. switch {
  156. case test.serverErr == nil && err == nil:
  157. // pass
  158. case err != nil && test.serverErr != nil && strings.Contains(err.Error(), test.serverErr.Error()):
  159. // pass
  160. default:
  161. t.Errorf("%s: expected %v, got %v", test.name, test.serverErr, err)
  162. return
  163. }
  164. if actualAllowed != test.allowed {
  165. t.Errorf("%s: expected %v, got %v", test.name, test.allowed, actualAllowed)
  166. return
  167. }
  168. })
  169. }
  170. }
  171. func TestRunAccessList(t *testing.T) {
  172. t.Run("test access list", func(t *testing.T) {
  173. options := &CanIOptions{List: true}
  174. expectedOutput := "Resources Non-Resource URLs Resource Names Verbs\n" +
  175. "job.* [] [test-resource] [get list]\n" +
  176. "pod.* [] [test-resource] [get list]\n" +
  177. " [/apis/*] [] [get]\n" +
  178. " [/version] [] [get]\n"
  179. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  180. defer tf.Cleanup()
  181. ns := scheme.Codecs
  182. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  183. tf.Client = &fake.RESTClient{
  184. GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
  185. NegotiatedSerializer: ns,
  186. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  187. switch req.URL.Path {
  188. case "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews":
  189. body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, getSelfSubjectRulesReview()))))
  190. return &http.Response{StatusCode: http.StatusOK, Body: body}, nil
  191. default:
  192. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  193. return nil, nil
  194. }
  195. }),
  196. }
  197. ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
  198. options.IOStreams = ioStreams
  199. if err := options.Complete(tf, []string{}); err != nil {
  200. t.Errorf("got unexpected error when do Complete(): %v", err)
  201. return
  202. }
  203. err := options.RunAccessList()
  204. if err != nil {
  205. t.Errorf("got unexpected error when do RunAccessList(): %v", err)
  206. } else if buf.String() != expectedOutput {
  207. t.Errorf("expected %v\n but got %v\n", expectedOutput, buf.String())
  208. }
  209. })
  210. }
  211. func getSelfSubjectRulesReview() *authorizationv1.SelfSubjectRulesReview {
  212. return &authorizationv1.SelfSubjectRulesReview{
  213. Status: authorizationv1.SubjectRulesReviewStatus{
  214. ResourceRules: []authorizationv1.ResourceRule{
  215. {
  216. Verbs: []string{"get", "list"},
  217. APIGroups: []string{"*"},
  218. Resources: []string{"pod", "job"},
  219. ResourceNames: []string{"test-resource"},
  220. },
  221. },
  222. NonResourceRules: []authorizationv1.NonResourceRule{
  223. {
  224. Verbs: []string{"get"},
  225. NonResourceURLs: []string{"/apis/*", "/version"},
  226. },
  227. },
  228. },
  229. }
  230. }