1package testdata 2 3import ( 4 "crypto/sha256" 5 "encoding/hex" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/stretchr/testify/require" 13 14 "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api" 15) 16 17type responseFn func(*testing.T, string) api.VirtualDomain 18 19// DomainResponses holds the predefined API responses for certain domains 20// that can be used with the GitLab API stub in acceptance tests 21// Assume the working dir is inside shared/pages/ 22var DomainResponses = map[string]responseFn{ 23 "zip-from-disk.gitlab.io": customDomain(projectConfig{ 24 projectID: 123, 25 pathOnDisk: "@hashed/zip-from-disk.gitlab.io", 26 }), 27 "zip-from-disk-not-found.gitlab.io": customDomain(projectConfig{}), 28 // outside of working dir 29 "zip-not-allowed-path.gitlab.io": customDomain(projectConfig{pathOnDisk: "../../../../"}), 30 "group.gitlab-example.com": generateVirtualDomainFromDir("group", "group.gitlab-example.com", nil), 31 "CapitalGroup.gitlab-example.com": generateVirtualDomainFromDir("CapitalGroup", "CapitalGroup.gitlab-example.com", nil), 32 "group.404.gitlab-example.com": generateVirtualDomainFromDir("group.404", "group.404.gitlab-example.com", map[string]projectConfig{ 33 "/private_project": { 34 projectID: 1300, 35 accessControl: true, 36 }, 37 "/private_unauthorized": { 38 projectID: 2000, 39 accessControl: true, 40 }, 41 }), 42 "group.https-only.gitlab-example.com": generateVirtualDomainFromDir("group.https-only", "group.https-only.gitlab-example.com", map[string]projectConfig{ 43 "/project1": { 44 projectID: 1000, 45 https: true, 46 }, 47 "/project2": { 48 projectID: 1100, 49 https: false, 50 }, 51 }), 52 "domain.404.com": customDomain(projectConfig{ 53 projectID: 1000, 54 pathOnDisk: "group.404/domain.404", 55 }), 56 "withacmechallenge.domain.com": customDomain(projectConfig{ 57 projectID: 1234, 58 pathOnDisk: "group.acme/with.acme.challenge", 59 }), 60 "group.redirects.gitlab-example.com": generateVirtualDomainFromDir("group.redirects", "group.redirects.gitlab-example.com", nil), 61 "redirects.custom-domain.com": customDomain(projectConfig{ 62 projectID: 1001, 63 pathOnDisk: "group.redirects/custom-domain", 64 }), 65 "test.my-domain.com": customDomain(projectConfig{ 66 projectID: 1002, 67 https: true, 68 pathOnDisk: "group.https-only/project3", 69 }), 70 "test2.my-domain.com": customDomain(projectConfig{ 71 projectID: 1003, 72 https: false, 73 pathOnDisk: "group.https-only/project4", 74 }), 75 "no.cert.com": customDomain(projectConfig{ 76 projectID: 1004, 77 https: true, 78 pathOnDisk: "group.https-only/project5", 79 }), 80 "group.auth.gitlab-example.com": generateVirtualDomainFromDir("group.auth", "group.auth.gitlab-example.com", map[string]projectConfig{ 81 "/": { 82 projectID: 1005, 83 accessControl: true, 84 }, 85 "/private.project": { 86 projectID: 1006, 87 accessControl: true, 88 }, 89 "/private.project.1": { 90 projectID: 2006, 91 accessControl: true, 92 }, 93 "/private.project.2": { 94 projectID: 3006, 95 accessControl: true, 96 }, 97 "/subgroup/private.project": { 98 projectID: 1007, 99 accessControl: true, 100 }, 101 "/subgroup/private.project.1": { 102 projectID: 2007, 103 accessControl: true, 104 }, 105 "/subgroup/private.project.2": { 106 projectID: 3007, 107 accessControl: true, 108 }, 109 }), 110 "private.domain.com": customDomain(projectConfig{ 111 projectID: 1007, 112 accessControl: true, 113 pathOnDisk: "group.auth/private.project", 114 }), 115 // NOTE: before adding more domains here, generate the zip archive by running (per project) 116 // make zip PROJECT_SUBDIR=group/serving 117 // make zip PROJECT_SUBDIR=group/project2 118} 119 120// generateVirtualDomainFromDir walks the subdirectory inside of shared/pages/ to find any zip archives. 121// It works for subdomains of pages-domain but not for custom domains (yet) 122func generateVirtualDomainFromDir(dir, rootDomain string, perPrefixConfig map[string]projectConfig) responseFn { 123 return func(t *testing.T, wd string) api.VirtualDomain { 124 t.Helper() 125 126 var foundZips []string 127 128 // walk over dir and save any paths containing a `.zip` file 129 // $(GITLAB_PAGES_DIR)/shared/pages + "/" + group 130 131 cleanDir := filepath.Join(wd, dir) 132 133 // make sure resolved path inside dir is under wd to avoid https://securego.io/docs/rules/g304.html 134 require.Truef(t, strings.HasPrefix(cleanDir, wd), "path %q outside of wd %q", cleanDir, wd) 135 136 filepath.Walk(cleanDir, func(path string, info os.FileInfo, err error) error { 137 require.NoError(t, err) 138 139 if strings.HasSuffix(info.Name(), ".zip") { 140 project := strings.TrimPrefix(path, wd+"/"+dir) 141 foundZips = append(foundZips, project) 142 } 143 144 return nil 145 }) 146 147 lookupPaths := make([]api.LookupPath, 0, len(foundZips)) 148 // generate lookup paths 149 for _, project := range foundZips { 150 // if project = "group/subgroup/project/public.zip 151 // trim prefix group and suffix /public.zip 152 // so prefix = "/subgroup/project" 153 prefix := strings.TrimPrefix(project, dir) 154 prefix = strings.TrimSuffix(prefix, "/"+filepath.Base(project)) 155 156 // use / as prefix when the current prefix matches the rootDomain, e.g. 157 // if request is group.gitlab-example.com/ and group/group.gitlab-example.com/public.zip exists 158 if prefix == "/"+rootDomain { 159 prefix = "/" 160 } 161 162 cfg, ok := perPrefixConfig[prefix] 163 if !ok { 164 cfg = projectConfig{} 165 } 166 167 sourcePath := fmt.Sprintf("file://%s", wd+"/"+dir+project) 168 sum := sha256.Sum256([]byte(sourcePath)) 169 sha := hex.EncodeToString(sum[:]) 170 171 lookupPath := api.LookupPath{ 172 ProjectID: cfg.projectID, 173 AccessControl: cfg.accessControl, 174 HTTPSOnly: cfg.https, 175 // gitlab.Resolve logic expects prefix to have ending slash 176 Prefix: ensureEndingSlash(prefix), 177 Source: api.Source{ 178 Type: "zip", 179 Path: sourcePath, 180 SHA256: sha, 181 }, 182 } 183 184 lookupPaths = append(lookupPaths, lookupPath) 185 } 186 187 return api.VirtualDomain{ 188 LookupPaths: lookupPaths, 189 } 190 } 191} 192 193type projectConfig struct { 194 // refer to makeGitLabPagesAccessStub for custom HTTP responses per projectID 195 projectID int 196 accessControl bool 197 https bool 198 pathOnDisk string 199} 200 201// customDomain with per project config 202func customDomain(config projectConfig) responseFn { 203 return func(t *testing.T, wd string) api.VirtualDomain { 204 t.Helper() 205 206 sourcePath := fmt.Sprintf("file://%s/%s/public.zip", wd, config.pathOnDisk) 207 sum := sha256.Sum256([]byte(sourcePath)) 208 sha := hex.EncodeToString(sum[:]) 209 210 return api.VirtualDomain{ 211 Certificate: "", 212 Key: "", 213 LookupPaths: []api.LookupPath{ 214 { 215 ProjectID: config.projectID, 216 AccessControl: config.accessControl, 217 HTTPSOnly: config.https, 218 // prefix should always be `/` for custom domains, otherwise `resolvePath` will try 219 // to look for files under public/prefix/ when serving content instead of just public/ 220 // see internal/serving/disk/ for details 221 Prefix: "/", 222 Source: api.Source{ 223 Type: "zip", 224 SHA256: sha, 225 Path: sourcePath, 226 }, 227 }, 228 }, 229 } 230 } 231} 232 233func ensureEndingSlash(path string) string { 234 if strings.HasSuffix(path, "/") { 235 return path 236 } 237 238 return path + "/" 239} 240