authz_unix_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. // +build !windows
  2. // TODO Windows: This uses a Unix socket for testing. This might be possible
  3. // to port to Windows using a named pipe instead.
  4. package authorization // import "github.com/docker/docker/pkg/authorization"
  5. import (
  6. "bytes"
  7. "encoding/json"
  8. "io/ioutil"
  9. "net"
  10. "net/http"
  11. "net/http/httptest"
  12. "os"
  13. "path"
  14. "reflect"
  15. "strings"
  16. "testing"
  17. "github.com/docker/docker/pkg/plugins"
  18. "github.com/docker/go-connections/tlsconfig"
  19. "github.com/gorilla/mux"
  20. )
  21. const (
  22. pluginAddress = "authz-test-plugin.sock"
  23. )
  24. func TestAuthZRequestPluginError(t *testing.T) {
  25. server := authZPluginTestServer{t: t}
  26. server.start()
  27. defer server.stop()
  28. authZPlugin := createTestPlugin(t)
  29. request := Request{
  30. User: "user",
  31. RequestBody: []byte("sample body"),
  32. RequestURI: "www.authz.com/auth",
  33. RequestMethod: "GET",
  34. RequestHeaders: map[string]string{"header": "value"},
  35. }
  36. server.replayResponse = Response{
  37. Err: "an error",
  38. }
  39. actualResponse, err := authZPlugin.AuthZRequest(&request)
  40. if err != nil {
  41. t.Fatalf("Failed to authorize request %v", err)
  42. }
  43. if !reflect.DeepEqual(server.replayResponse, *actualResponse) {
  44. t.Fatal("Response must be equal")
  45. }
  46. if !reflect.DeepEqual(request, server.recordedRequest) {
  47. t.Fatal("Requests must be equal")
  48. }
  49. }
  50. func TestAuthZRequestPlugin(t *testing.T) {
  51. server := authZPluginTestServer{t: t}
  52. server.start()
  53. defer server.stop()
  54. authZPlugin := createTestPlugin(t)
  55. request := Request{
  56. User: "user",
  57. RequestBody: []byte("sample body"),
  58. RequestURI: "www.authz.com/auth",
  59. RequestMethod: "GET",
  60. RequestHeaders: map[string]string{"header": "value"},
  61. }
  62. server.replayResponse = Response{
  63. Allow: true,
  64. Msg: "Sample message",
  65. }
  66. actualResponse, err := authZPlugin.AuthZRequest(&request)
  67. if err != nil {
  68. t.Fatalf("Failed to authorize request %v", err)
  69. }
  70. if !reflect.DeepEqual(server.replayResponse, *actualResponse) {
  71. t.Fatal("Response must be equal")
  72. }
  73. if !reflect.DeepEqual(request, server.recordedRequest) {
  74. t.Fatal("Requests must be equal")
  75. }
  76. }
  77. func TestAuthZResponsePlugin(t *testing.T) {
  78. server := authZPluginTestServer{t: t}
  79. server.start()
  80. defer server.stop()
  81. authZPlugin := createTestPlugin(t)
  82. request := Request{
  83. User: "user",
  84. RequestURI: "something.com/auth",
  85. RequestBody: []byte("sample body"),
  86. }
  87. server.replayResponse = Response{
  88. Allow: true,
  89. Msg: "Sample message",
  90. }
  91. actualResponse, err := authZPlugin.AuthZResponse(&request)
  92. if err != nil {
  93. t.Fatalf("Failed to authorize request %v", err)
  94. }
  95. if !reflect.DeepEqual(server.replayResponse, *actualResponse) {
  96. t.Fatal("Response must be equal")
  97. }
  98. if !reflect.DeepEqual(request, server.recordedRequest) {
  99. t.Fatal("Requests must be equal")
  100. }
  101. }
  102. func TestResponseModifier(t *testing.T) {
  103. r := httptest.NewRecorder()
  104. m := NewResponseModifier(r)
  105. m.Header().Set("h1", "v1")
  106. m.Write([]byte("body"))
  107. m.WriteHeader(http.StatusInternalServerError)
  108. m.FlushAll()
  109. if r.Header().Get("h1") != "v1" {
  110. t.Fatalf("Header value must exists %s", r.Header().Get("h1"))
  111. }
  112. if !reflect.DeepEqual(r.Body.Bytes(), []byte("body")) {
  113. t.Fatalf("Body value must exists %s", r.Body.Bytes())
  114. }
  115. if r.Code != http.StatusInternalServerError {
  116. t.Fatalf("Status code must be correct %d", r.Code)
  117. }
  118. }
  119. func TestDrainBody(t *testing.T) {
  120. tests := []struct {
  121. length int // length is the message length send to drainBody
  122. expectedBodyLength int // expectedBodyLength is the expected body length after drainBody is called
  123. }{
  124. {10, 10}, // Small message size
  125. {maxBodySize - 1, maxBodySize - 1}, // Max message size
  126. {maxBodySize * 2, 0}, // Large message size (skip copying body)
  127. }
  128. for _, test := range tests {
  129. msg := strings.Repeat("a", test.length)
  130. body, closer, err := drainBody(ioutil.NopCloser(bytes.NewReader([]byte(msg))))
  131. if err != nil {
  132. t.Fatal(err)
  133. }
  134. if len(body) != test.expectedBodyLength {
  135. t.Fatalf("Body must be copied, actual length: '%d'", len(body))
  136. }
  137. if closer == nil {
  138. t.Fatal("Closer must not be nil")
  139. }
  140. modified, err := ioutil.ReadAll(closer)
  141. if err != nil {
  142. t.Fatalf("Error must not be nil: '%v'", err)
  143. }
  144. if len(modified) != len(msg) {
  145. t.Fatalf("Result should not be truncated. Original length: '%d', new length: '%d'", len(msg), len(modified))
  146. }
  147. }
  148. }
  149. func TestSendBody(t *testing.T) {
  150. var (
  151. testcases = []struct {
  152. url string
  153. contentType string
  154. expected bool
  155. }{
  156. {
  157. contentType: "application/json",
  158. expected: true,
  159. },
  160. {
  161. contentType: "Application/json",
  162. expected: true,
  163. },
  164. {
  165. contentType: "application/JSON",
  166. expected: true,
  167. },
  168. {
  169. contentType: "APPLICATION/JSON",
  170. expected: true,
  171. },
  172. {
  173. contentType: "application/json; charset=utf-8",
  174. expected: true,
  175. },
  176. {
  177. contentType: "application/json;charset=utf-8",
  178. expected: true,
  179. },
  180. {
  181. contentType: "application/json; charset=UTF8",
  182. expected: true,
  183. },
  184. {
  185. contentType: "application/json;charset=UTF8",
  186. expected: true,
  187. },
  188. {
  189. contentType: "text/html",
  190. expected: false,
  191. },
  192. {
  193. contentType: "",
  194. expected: false,
  195. },
  196. {
  197. url: "nothing.com/auth",
  198. contentType: "",
  199. expected: false,
  200. },
  201. {
  202. url: "nothing.com/auth",
  203. contentType: "application/json;charset=UTF8",
  204. expected: false,
  205. },
  206. {
  207. url: "nothing.com/auth?p1=test",
  208. contentType: "application/json;charset=UTF8",
  209. expected: false,
  210. },
  211. {
  212. url: "nothing.com/test?p1=/auth",
  213. contentType: "application/json;charset=UTF8",
  214. expected: true,
  215. },
  216. {
  217. url: "nothing.com/something/auth",
  218. contentType: "application/json;charset=UTF8",
  219. expected: true,
  220. },
  221. {
  222. url: "nothing.com/auth/test",
  223. contentType: "application/json;charset=UTF8",
  224. expected: false,
  225. },
  226. {
  227. url: "nothing.com/v1.24/auth/test",
  228. contentType: "application/json;charset=UTF8",
  229. expected: false,
  230. },
  231. {
  232. url: "nothing.com/v1/auth/test",
  233. contentType: "application/json;charset=UTF8",
  234. expected: false,
  235. },
  236. {
  237. url: "www.nothing.com/v1.24/auth/test",
  238. contentType: "application/json;charset=UTF8",
  239. expected: false,
  240. },
  241. {
  242. url: "https://www.nothing.com/v1.24/auth/test",
  243. contentType: "application/json;charset=UTF8",
  244. expected: false,
  245. },
  246. {
  247. url: "http://nothing.com/v1.24/auth/test",
  248. contentType: "application/json;charset=UTF8",
  249. expected: false,
  250. },
  251. {
  252. url: "www.nothing.com/test?p1=/auth",
  253. contentType: "application/json;charset=UTF8",
  254. expected: true,
  255. },
  256. {
  257. url: "http://www.nothing.com/test?p1=/auth",
  258. contentType: "application/json;charset=UTF8",
  259. expected: true,
  260. },
  261. {
  262. url: "www.nothing.com/something/auth",
  263. contentType: "application/json;charset=UTF8",
  264. expected: true,
  265. },
  266. {
  267. url: "https://www.nothing.com/something/auth",
  268. contentType: "application/json;charset=UTF8",
  269. expected: true,
  270. },
  271. }
  272. )
  273. for _, testcase := range testcases {
  274. header := http.Header{}
  275. header.Set("Content-Type", testcase.contentType)
  276. if testcase.url == "" {
  277. testcase.url = "nothing.com"
  278. }
  279. if b := sendBody(testcase.url, header); b != testcase.expected {
  280. t.Fatalf("sendBody failed: url: %s, content-type: %s; Expected: %t, Actual: %t", testcase.url, testcase.contentType, testcase.expected, b)
  281. }
  282. }
  283. }
  284. func TestResponseModifierOverride(t *testing.T) {
  285. r := httptest.NewRecorder()
  286. m := NewResponseModifier(r)
  287. m.Header().Set("h1", "v1")
  288. m.Write([]byte("body"))
  289. m.WriteHeader(http.StatusInternalServerError)
  290. overrideHeader := make(http.Header)
  291. overrideHeader.Add("h1", "v2")
  292. overrideHeaderBytes, err := json.Marshal(overrideHeader)
  293. if err != nil {
  294. t.Fatalf("override header failed %v", err)
  295. }
  296. m.OverrideHeader(overrideHeaderBytes)
  297. m.OverrideBody([]byte("override body"))
  298. m.OverrideStatusCode(http.StatusNotFound)
  299. m.FlushAll()
  300. if r.Header().Get("h1") != "v2" {
  301. t.Fatalf("Header value must exists %s", r.Header().Get("h1"))
  302. }
  303. if !reflect.DeepEqual(r.Body.Bytes(), []byte("override body")) {
  304. t.Fatalf("Body value must exists %s", r.Body.Bytes())
  305. }
  306. if r.Code != http.StatusNotFound {
  307. t.Fatalf("Status code must be correct %d", r.Code)
  308. }
  309. }
  310. // createTestPlugin creates a new sample authorization plugin
  311. func createTestPlugin(t *testing.T) *authorizationPlugin {
  312. pwd, err := os.Getwd()
  313. if err != nil {
  314. t.Fatal(err)
  315. }
  316. client, err := plugins.NewClient("unix:///"+path.Join(pwd, pluginAddress), &tlsconfig.Options{InsecureSkipVerify: true})
  317. if err != nil {
  318. t.Fatalf("Failed to create client %v", err)
  319. }
  320. return &authorizationPlugin{name: "plugin", plugin: client}
  321. }
  322. // AuthZPluginTestServer is a simple server that implements the authZ plugin interface
  323. type authZPluginTestServer struct {
  324. listener net.Listener
  325. t *testing.T
  326. // request stores the request sent from the daemon to the plugin
  327. recordedRequest Request
  328. // response stores the response sent from the plugin to the daemon
  329. replayResponse Response
  330. server *httptest.Server
  331. }
  332. // start starts the test server that implements the plugin
  333. func (t *authZPluginTestServer) start() {
  334. r := mux.NewRouter()
  335. l, err := net.Listen("unix", pluginAddress)
  336. if err != nil {
  337. t.t.Fatal(err)
  338. }
  339. t.listener = l
  340. r.HandleFunc("/Plugin.Activate", t.activate)
  341. r.HandleFunc("/"+AuthZApiRequest, t.auth)
  342. r.HandleFunc("/"+AuthZApiResponse, t.auth)
  343. t.server = &httptest.Server{
  344. Listener: l,
  345. Config: &http.Server{
  346. Handler: r,
  347. Addr: pluginAddress,
  348. },
  349. }
  350. t.server.Start()
  351. }
  352. // stop stops the test server that implements the plugin
  353. func (t *authZPluginTestServer) stop() {
  354. t.server.Close()
  355. os.Remove(pluginAddress)
  356. if t.listener != nil {
  357. t.listener.Close()
  358. }
  359. }
  360. // auth is a used to record/replay the authentication api messages
  361. func (t *authZPluginTestServer) auth(w http.ResponseWriter, r *http.Request) {
  362. t.recordedRequest = Request{}
  363. body, err := ioutil.ReadAll(r.Body)
  364. if err != nil {
  365. t.t.Fatal(err)
  366. }
  367. r.Body.Close()
  368. json.Unmarshal(body, &t.recordedRequest)
  369. b, err := json.Marshal(t.replayResponse)
  370. if err != nil {
  371. t.t.Fatal(err)
  372. }
  373. w.Write(b)
  374. }
  375. func (t *authZPluginTestServer) activate(w http.ResponseWriter, r *http.Request) {
  376. b, err := json.Marshal(plugins.Manifest{Implements: []string{AuthZApiImplements}})
  377. if err != nil {
  378. t.t.Fatal(err)
  379. }
  380. w.Write(b)
  381. }