1package acceptance_test 2 3import ( 4 "crypto/tls" 5 "fmt" 6 "io" 7 "net/http" 8 "net/http/httptest" 9 "net/url" 10 "os" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/require" 15) 16 17func TestArtifactProxyRequest(t *testing.T) { 18 transport := (TestHTTPSClient.Transport).(*http.Transport).Clone() 19 transport.ResponseHeaderTimeout = 5 * time.Second 20 21 content := "<!DOCTYPE html><html><head><title>Title of the document</title></head><body></body></html>" 22 contentLength := int64(len(content)) 23 testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 24 switch r.URL.RawPath { 25 case "/api/v4/projects/group%2Fproject/jobs/1/artifacts/delayed_200.html": 26 time.Sleep(2 * time.Second) 27 fallthrough 28 case "/api/v4/projects/group%2Fproject/jobs/1/artifacts/200.html", 29 "/api/v4/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/200.html": 30 w.Header().Set("Content-Type", "text/html; charset=utf-8") 31 fmt.Fprint(w, content) 32 case "/api/v4/projects/group%2Fproject/jobs/1/artifacts/500.html": 33 w.Header().Set("Content-Type", "text/html; charset=utf-8") 34 w.WriteHeader(http.StatusInternalServerError) 35 fmt.Fprint(w, content) 36 default: 37 t.Logf("Unexpected r.URL.RawPath: %q", r.URL.RawPath) 38 w.Header().Set("Content-Type", "text/html; charset=utf-8") 39 w.WriteHeader(http.StatusNotFound) 40 fmt.Fprint(w, content) 41 } 42 })) 43 44 keyFile, certFile := CreateHTTPSFixtureFiles(t) 45 cert, err := tls.LoadX509KeyPair(certFile, keyFile) 46 require.NoError(t, err) 47 48 testServer.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} 49 testServer.StartTLS() 50 51 t.Cleanup(func() { 52 os.Remove(keyFile) 53 os.Remove(certFile) 54 testServer.Close() 55 }) 56 57 tests := []struct { 58 name string 59 host string 60 path string 61 status int 62 content string 63 length int64 64 cacheControl string 65 contentType string 66 }{ 67 { 68 name: "basic proxied request", 69 host: "group.gitlab-example.com", 70 path: "/-/project/-/jobs/1/artifacts/200.html", 71 status: http.StatusOK, 72 content: content, 73 length: contentLength, 74 cacheControl: "max-age=3600", 75 contentType: "text/html; charset=utf-8", 76 }, 77 { 78 name: "basic proxied request for subgroup", 79 host: "group.gitlab-example.com", 80 path: "/-/subgroup/project/-/jobs/1/artifacts/200.html", 81 status: http.StatusOK, 82 content: content, 83 length: contentLength, 84 cacheControl: "max-age=3600", 85 contentType: "text/html; charset=utf-8", 86 }, 87 { 88 name: "502 error while attempting to proxy", 89 host: "group.gitlab-example.com", 90 path: "/-/project/-/jobs/1/artifacts/delayed_200.html", 91 status: http.StatusBadGateway, 92 content: "", 93 length: 0, 94 cacheControl: "", 95 contentType: "text/html; charset=utf-8", 96 }, 97 { 98 name: "Proxying 404 from server", 99 host: "group.gitlab-example.com", 100 path: "/-/project/-/jobs/1/artifacts/404.html", 101 status: http.StatusNotFound, 102 content: "", 103 length: 0, 104 cacheControl: "", 105 contentType: "text/html; charset=utf-8", 106 }, 107 { 108 name: "Proxying 500 from server", 109 host: "group.gitlab-example.com", 110 path: "/-/project/-/jobs/1/artifacts/500.html", 111 status: http.StatusInternalServerError, 112 content: "", 113 length: 0, 114 cacheControl: "", 115 contentType: "text/html; charset=utf-8", 116 }, 117 } 118 119 // Ensure the IP address is used in the URL, as we're relying on IP SANs to 120 // validate 121 artifactServerURL := testServer.URL + "/api/v4" 122 t.Log("Artifact server URL", artifactServerURL) 123 124 args := []string{"-artifacts-server=" + artifactServerURL, "-artifacts-server-timeout=1"} 125 126 RunPagesProcess(t, 127 withListeners([]ListenSpec{httpListener}), 128 withArguments(args), 129 withEnv([]string{"SSL_CERT_FILE=" + certFile}), 130 ) 131 132 for _, tt := range tests { 133 tt := tt 134 t.Run(tt.name, func(t *testing.T) { 135 t.Parallel() 136 137 resp, err := GetPageFromListener(t, httpListener, tt.host, tt.path) 138 require.NoError(t, err) 139 defer resp.Body.Close() 140 141 require.Equal(t, tt.status, resp.StatusCode) 142 require.Equal(t, tt.contentType, resp.Header.Get("Content-Type")) 143 144 if tt.status == http.StatusOK { 145 body, err := io.ReadAll(resp.Body) 146 require.NoError(t, err) 147 require.Equal(t, tt.content, string(body)) 148 require.Equal(t, tt.length, resp.ContentLength) 149 require.Equal(t, tt.cacheControl, resp.Header.Get("Cache-Control")) 150 } 151 }) 152 } 153} 154 155func TestPrivateArtifactProxyRequest(t *testing.T) { 156 setupTransport(t) 157 158 testServer := makeGitLabPagesAccessStub(t) 159 160 keyFile, certFile := CreateHTTPSFixtureFiles(t) 161 cert, err := tls.LoadX509KeyPair(certFile, keyFile) 162 require.NoError(t, err) 163 164 testServer.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} 165 testServer.StartTLS() 166 167 t.Cleanup(func() { 168 os.Remove(keyFile) 169 os.Remove(certFile) 170 testServer.Close() 171 }) 172 173 tests := []struct { 174 name string 175 host string 176 path string 177 status int 178 }{ 179 { 180 name: "basic proxied request for private project", 181 host: "group.gitlab-example.com", 182 path: "/-/private/-/jobs/1/artifacts/200.html", 183 status: http.StatusOK, 184 }, 185 { 186 name: "basic proxied request for subgroup", 187 host: "group.gitlab-example.com", 188 path: "/-/subgroup/private/-/jobs/1/artifacts/200.html", 189 status: http.StatusOK, 190 }, 191 { 192 name: "502 error while attempting to proxy", 193 host: "group.gitlab-example.com", 194 path: "/-/private/-/jobs/1/artifacts/delayed_200.html", 195 status: http.StatusBadGateway, 196 }, 197 { 198 name: "Proxying 404 from server", 199 host: "group.gitlab-example.com", 200 path: "/-/private/-/jobs/1/artifacts/404.html", 201 status: http.StatusNotFound, 202 }, 203 { 204 name: "Proxying 500 from server", 205 host: "group.gitlab-example.com", 206 path: "/-/private/-/jobs/1/artifacts/500.html", 207 status: http.StatusInternalServerError, 208 }, 209 } 210 211 // Ensure the IP address is used in the URL, as we're relying on IP SANs to 212 // validate 213 artifactServerURL := testServer.URL + "/api/v4" 214 t.Log("Artifact server URL", artifactServerURL) 215 216 configFile := defaultConfigFileWith(t, 217 "gitlab-server="+testServer.URL, 218 "artifacts-server="+artifactServerURL, 219 "auth-redirect-uri=https://projects.gitlab-example.com/auth", 220 "artifacts-server-timeout=1") 221 222 RunPagesProcess(t, 223 withListeners([]ListenSpec{httpsListener}), 224 withArguments([]string{ 225 "-config=" + configFile, 226 }), 227 withEnv([]string{"SSL_CERT_FILE=" + certFile}), 228 ) 229 230 for _, tt := range tests { 231 tt := tt 232 t.Run(tt.name, func(t *testing.T) { 233 t.Parallel() 234 235 resp, err := GetRedirectPage(t, httpsListener, tt.host, tt.path) 236 require.NoError(t, err) 237 defer resp.Body.Close() 238 239 require.Equal(t, http.StatusFound, resp.StatusCode) 240 241 cookie := resp.Header.Get("Set-Cookie") 242 243 // Redirects to the projects under gitlab pages domain for authentication flow 244 url, err := url.Parse(resp.Header.Get("Location")) 245 require.NoError(t, err) 246 require.Equal(t, "projects.gitlab-example.com", url.Host) 247 require.Equal(t, "/auth", url.Path) 248 state := url.Query().Get("state") 249 250 resp, err = GetRedirectPage(t, httpsListener, url.Host, url.Path+"?"+url.RawQuery) 251 252 require.NoError(t, err) 253 defer resp.Body.Close() 254 255 require.Equal(t, http.StatusFound, resp.StatusCode) 256 pagesDomainCookie := resp.Header.Get("Set-Cookie") 257 258 // Go to auth page with correct state will cause fetching the token 259 authrsp, err := GetRedirectPageWithCookie(t, httpsListener, "projects.gitlab-example.com", "/auth?code=1&state="+ 260 state, pagesDomainCookie) 261 262 require.NoError(t, err) 263 defer authrsp.Body.Close() 264 265 // Will redirect auth callback to correct host 266 url, err = url.Parse(authrsp.Header.Get("Location")) 267 require.NoError(t, err) 268 require.Equal(t, tt.host, url.Host) 269 require.Equal(t, "/auth", url.Path) 270 271 // Request auth callback in project domain 272 authrsp, err = GetRedirectPageWithCookie(t, httpsListener, url.Host, url.Path+"?"+url.RawQuery, cookie) 273 require.NoError(t, err) 274 defer authrsp.Body.Close() 275 276 // server returns the ticket, user will be redirected to the project page 277 require.Equal(t, http.StatusFound, authrsp.StatusCode) 278 cookie = authrsp.Header.Get("Set-Cookie") 279 resp, err = GetRedirectPageWithCookie(t, httpsListener, tt.host, tt.path, cookie) 280 281 require.Equal(t, tt.status, resp.StatusCode) 282 283 require.NoError(t, err) 284 defer resp.Body.Close() 285 }) 286 } 287} 288