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