Skip to content

Commit

Permalink
🐛 Support self-hosted GitLab instances where base URL has a path comp…
Browse files Browse the repository at this point in the history
…onent (#3819)

* Add GL_HOST env flag

Self-hosted instances which dont use a subdomain result in broken API links.
This change may not be finished, but is intended to evaluate the solution.

Previously, self hosted instances where the instance is part of the path (foo.com/gitlab/owner/repo)
would have their API base URL registered as foo.com/api/v4/ instead of foo.com/gitlab/api/v4/

Signed-off-by: Spencer Schrock <sschrock@google.com>

* include token in gitlab project probe

Signed-off-by: Spencer Schrock <sschrock@google.com>

* consider GL_HOST when parsing gitlab repo urls

Signed-off-by: Spencer Schrock <sschrock@google.com>

* remove unneeded GL_HOST parsing

now that repoURL_parse handles GL_HOST, we dont need it elsewhere.

Signed-off-by: Spencer Schrock <sschrock@google.com>

* cleanup

Signed-off-by: Spencer Schrock <sschrock@google.com>

* mention GL_HOST in readme

Signed-off-by: Spencer Schrock <sschrock@google.com>

* fix linter

Signed-off-by: Spencer Schrock <sschrock@google.com>

* handle GL_HOST without scheme

Signed-off-by: Spencer Schrock <sschrock@google.com>

* move api-less check earlier

if we can avoid an API call, do it.

Signed-off-by: Spencer Schrock <sschrock@google.com>

* try listing projects with and without auth token

Signed-off-by: Spencer Schrock <sschrock@google.com>

* fix linter

Signed-off-by: Spencer Schrock <sschrock@google.com>

* revert passing token to list projects

the simpler the better

Signed-off-by: Spencer Schrock <sschrock@google.com>

---------

Signed-off-by: Spencer Schrock <sschrock@google.com>
  • Loading branch information
spencerschrock committed Jan 31, 2024
1 parent 83ff808 commit e10dbb1
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 12 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,17 @@ scorecard --repo gitlab.com/<org>/<project>/<subproject>

For an example of using Scorecard in GitLab CI/CD, see [here](https://gitlab.com/ossf-test/scorecard-pipeline-example).

###### Self Hosted Editions
While we focus on GitLab.com support, Scorecard also works with self-hosted GitLab installations.
If your platform is hosted at a subdomain (e.g. `gitlab.foo.com`), Scorecard should work out of the box.
If your platform is hosted at some slug (e.g. `foo.com/bar/`), you will need to set the `GL_HOST` environment variable.

```bash
export GITLAB_AUTH_TOKEN=glpat-xxxx
export GL_HOST=foo.com/bar
scorecard --repo foo.com/bar/<org>/<project>
```

##### Using GitHub Enterprise Server (GHES) based Repository

To use a GitHub Enterprise host `github.corp.com`, use the `GH_HOST` environment variable.
Expand Down
49 changes: 37 additions & 12 deletions clients/gitlabrepo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package gitlabrepo
import (
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strings"

"github.com/xanzy/go-gitlab"
Expand Down Expand Up @@ -61,14 +63,26 @@ func (r *repoURL) parse(input string) error {
t = input
}

// Allow skipping scheme for ease-of-use, default to https.
if !strings.Contains(t, "://") {
t = "https://" + t
u, err := url.Parse(withDefaultScheme(t))
if err != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("url.Parse: %v", err))
}

u, e := url.Parse(t)
if e != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("url.Parse: %v", e))
// fixup the URL, for situations where GL_HOST contains part of the path
// https://github.com/ossf/scorecard/issues/3696
if h := os.Getenv("GL_HOST"); h != "" {
hostURL, err := url.Parse(withDefaultScheme(h))
if err != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("url.Parse GL_HOST: %v", err))
}

// only modify behavior of repos which fall under GL_HOST
if hostURL.Host == u.Host {
// without the scheme and without trailing slashes
u.Host = hostURL.Host + strings.TrimRight(hostURL.Path, "/")
// remove any part of the path which belongs to the host
u.Path = strings.TrimPrefix(u.Path, hostURL.Path)
}
}

const splitLen = 2
Expand All @@ -81,6 +95,14 @@ func (r *repoURL) parse(input string) error {
return nil
}

// Allow skipping scheme for ease-of-use, default to https.
func withDefaultScheme(uri string) string {
if strings.Contains(uri, "://") {
return uri
}
return "https://" + uri
}

// URI implements Repo.URI().
func (r *repoURL) URI() string {
return fmt.Sprintf("%s/%s/%s", r.host, r.owner, r.project)
Expand All @@ -97,6 +119,10 @@ func (r *repoURL) String() string {

// IsValid implements Repo.IsValid.
func (r *repoURL) IsValid() error {
if strings.TrimSpace(r.owner) == "" || strings.TrimSpace(r.project) == "" {
return sce.WithMessage(sce.ErrorInvalidURL, "expected full project url: "+r.URI())
}

if strings.Contains(r.host, "gitlab.") {
return nil
}
Expand All @@ -105,15 +131,18 @@ func (r *repoURL) IsValid() error {
return fmt.Errorf("%w: %s", errInvalidGitlabRepoURL, r.host)
}

client, err := gitlab.NewClient("", gitlab.WithBaseURL(fmt.Sprintf("%s://%s", r.scheme, r.host)))
// intentionally pass empty token
// "When accessed without authentication, only public projects with simple fields are returned."
// https://docs.gitlab.com/ee/api/projects.html#list-all-projects
client, err := gitlab.NewClient("", gitlab.WithBaseURL(r.Host()))
if err != nil {
return sce.WithMessage(err,
fmt.Sprintf("couldn't create gitlab client for %s", r.host),
)
}

_, resp, err := client.Projects.ListProjects(&gitlab.ListProjectsOptions{})
if resp == nil || resp.StatusCode != 200 {
if resp == nil || resp.StatusCode != http.StatusOK {
return sce.WithMessage(sce.ErrRepoUnreachable,
fmt.Sprintf("couldn't reach gitlab instance at %s", r.host),
)
Expand All @@ -124,10 +153,6 @@ func (r *repoURL) IsValid() error {
)
}

if strings.TrimSpace(r.owner) == "" || strings.TrimSpace(r.project) == "" {
return sce.WithMessage(sce.ErrorInvalidURL,
fmt.Sprintf("%v. Expected the full project url", r.URI()))
}
return nil
}

Expand Down
80 changes: 80 additions & 0 deletions clients/gitlabrepo/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,83 @@ func TestRepoURL_MakeGitLabRepo(t *testing.T) {
}
}
}

//nolint:paralleltest // uses t.Setenv, can't be parallelized
func TestRepoURL_parse_GL_HOST(t *testing.T) {
tests := []struct {
name string
url string
host, owner, project string
glHost string
wantErr bool
}{
{
name: "GL_HOST ends with slash",
glHost: "https://foo.com/gitlab/",
url: "foo.com/gitlab/ssdlc/scorecard-scanner",
host: "foo.com/gitlab",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST doesn't end with slash",
glHost: "https://foo.com/gitlab",
url: "foo.com/gitlab/ssdlc/scorecard-scanner",
host: "foo.com/gitlab",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST doesn't match url",
glHost: "https://foo.com/gitlab",
url: "bar.com/gitlab/ssdlc/scorecard-scanner",
host: "bar.com",
owner: "gitlab",
project: "ssdlc/scorecard-scanner",
},
{
name: "GL_HOST has no path component",
glHost: "https://foo.com",
url: "foo.com/ssdlc/scorecard-scanner",
host: "foo.com",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST path has multiple slashes",
glHost: "https://foo.com/bar/baz/",
url: "foo.com/bar/baz/ssdlc/scorecard-scanner",
host: "foo.com/bar/baz",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST has no scheme",
glHost: "foo.com/bar/",
url: "foo.com/bar/ssdlc/scorecard-scanner",
host: "foo.com/bar",
owner: "ssdlc",
project: "scorecard-scanner",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("GL_HOST", tt.glHost)
var r repoURL
err := r.parse(tt.url)
if (err != nil) != tt.wantErr {
t.Fatalf("wanted err: %t, got: %v", tt.wantErr, err)
}
if r.host != tt.host {
t.Errorf("got host: %s, want: %s", r.host, tt.host)
}
if r.owner != tt.owner {
t.Errorf("got owner: %s, want: %s", r.owner, tt.owner)
}
if r.project != tt.project {
t.Errorf("got project: %s, want: %s", r.project, tt.project)
}
})
}
}

0 comments on commit e10dbb1

Please sign in to comment.