Skip to content

Commit

Permalink
ORGANIC-467. Refactored authorization/authenication out of backends i…
Browse files Browse the repository at this point in the history
…nto auth.go. Got it compiled.
  • Loading branch information
thomasyip committed Nov 27, 2018
1 parent 914448d commit e70ec30
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 132 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ for GitLab:
/repos/:owner/:name/tree/
```

**Running git-gateway to test locally**
**Running `git-gateway`**
**(Do not merge this section back to the open source project)**
**(Do not deploy it to production. It is a Proof of Concept has has not been secured. See @TODO items in code.**
**(the instruction assume Okta, and github.com)**
Expand Down Expand Up @@ -58,3 +58,20 @@ for GitLab:
change `backend.name` value to `git-gateway`
change `backend.gateway_url` value to `http://localhost:8087`
14. run `content-cms` following the README.md

**Develop, Build and Run git-gateway**

1. Follow instructions 1 - 10 in previous "Running `git-gateway`" section
2. Run these commands once:
```
docker build -t netlify/git-gateway:latest .
docker run --rm --env-file my.env --net localdev -p 127.0.0.1:8087:8087 --expose 8087 -ti -v $PWD:/go/src/github.com/netlify/git-gateway --entrypoint '/bin/sh' --user root netlify/git-gateway:latest
cd /go/src/github.com/netlify/git-gateway
make deps
```
3. Run these commands after edit:
```
make build && ./git-gateway
```
4. `<ctrl> + c` to stop

12 changes: 7 additions & 5 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var bearerRegexp = regexp.MustCompile(`^(?:B|b)earer (\S+$)`)
type API struct {
handler http.Handler
db storage.Connection
auth Auth
config *conf.GlobalConfiguration
version string
}
Expand Down Expand Up @@ -58,7 +59,8 @@ func NewAPI(globalConfig *conf.GlobalConfiguration, db storage.Connection) *API

// NewAPIWithVersion creates a new REST API using the specified version
func NewAPIWithVersion(ctx context.Context, globalConfig *conf.GlobalConfiguration, db storage.Connection, version string) *API {
api := &API{config: globalConfig, db: db, version: version}
auth := NewAuthWithVersion(ctx, globalConfig, version)
api := &API{config: globalConfig, db: db, auth: *auth, version: version}

xffmw, _ := xff.Default()

Expand All @@ -75,10 +77,10 @@ func NewAPIWithVersion(ctx context.Context, globalConfig *conf.GlobalConfigurati
r.Use(api.loadJWSSignatureHeader)
r.Use(api.loadInstanceConfig)
}
r.With(api.requireAuthentication).Mount("/github", NewGitHubGateway())
r.With(api.requireAuthentication).Mount("/gitlab", NewGitLabGateway())
r.With(api.requireAuthentication).Mount("/bitbucket", NewBitBucketGateway())
r.With(api.requireAuthentication).Get("/settings", api.Settings)
r.With(api.auth.authenticate).Mount("/github", NewGitHubGateway())
r.With(api.auth.authenticate).Mount("/gitlab", NewGitLabGateway())
r.With(api.auth.authenticate).Mount("/bitbucket", NewBitBucketGateway())
r.With(api.auth.authenticate).Get("/settings", api.Settings)
})

if globalConfig.MultiInstanceMode {
Expand Down
56 changes: 52 additions & 4 deletions api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ package api

import (
"context"
"errors"
"net/http"

"github.com/netlify/git-gateway/conf"
"github.com/sirupsen/logrus"
"github.com/okta/okta-jwt-verifier-golang"
)

// requireAuthentication checks incoming requests for tokens presented using the Authorization header
func (a *API) requireAuthentication(w http.ResponseWriter, r *http.Request) (context.Context, error) {
type Auth struct {
config *conf.GlobalConfiguration
version string
}

// authenicate checks incoming requests for tokens presented using the Authorization header
func (a *Auth) authenticate(w http.ResponseWriter, r *http.Request) (context.Context, error) {
logrus.Info("Getting auth token")
token, err := a.extractBearerToken(w, r)
if err != nil {
Expand All @@ -20,7 +27,48 @@ func (a *API) requireAuthentication(w http.ResponseWriter, r *http.Request) (con
return a.parseJWTClaims(token, r)
}

func (a *API) extractBearerToken(w http.ResponseWriter, r *http.Request) (string, error) {
// authorize checks incoming requests for roles data in tokens that is parsed and verified by prior authentication step
func (a *Auth) authorize(w http.ResponseWriter, r *http.Request) (context.Context, error) {
ctx := r.Context()
claims := getClaims(ctx)
config := getConfig(ctx)

logrus.Infof("authenticate context: %v+", ctx)
if claims == nil {
return nil, errors.New("Access to endpoint not allowed: no claims found in Bearer token")
}

if !allowedRegexp.MatchString(r.URL.Path) {
return nil, errors.New("Access to endpoint not allowed: this part of GitHub's API has been restricted")
}

if len(config.Roles) == 0 {
return ctx, nil
}

roles, ok := claims.AppMetaData["roles"]
if ok {
roleStrings, _ := roles.([]interface{})
for _, data := range roleStrings {
role, _ := data.(string)
for _, adminRole := range config.Roles {
if role == adminRole {
return ctx, nil
}
}
}
}

return nil, errors.New("Access to endpoint not allowed: your role doesn't allow access")
}

func NewAuthWithVersion(ctx context.Context, globalConfig *conf.GlobalConfiguration, version string) *Auth {
auth := &Auth{config: globalConfig, version: version}

return auth
}

func (a *Auth) extractBearerToken(w http.ResponseWriter, r *http.Request) (string, error) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return "", unauthorizedError("This endpoint requires a Bearer token")
Expand All @@ -34,7 +82,7 @@ func (a *API) extractBearerToken(w http.ResponseWriter, r *http.Request) (string
return matches[1], nil
}

func (a *API) parseJWTClaims(bearer string, r *http.Request) (context.Context, error) {
func (a *Auth) parseJWTClaims(bearer string, r *http.Request) (context.Context, error) {
// Reimplemented to use Okta lib
// Original validation only work for HS256 algo,
// Okta supports RS256 only which requires public key downloading and caching (key rotation)
Expand Down
39 changes: 0 additions & 39 deletions api/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"compress/gzip"
"context"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -118,11 +117,6 @@ func (bb *BitBucketGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if err := bb.authenticate(w, r); err != nil {
handleError(unauthorizedError(err.Error()), w, r)
return
}

endpoint := config.BitBucket.Endpoint
apiURL := singleJoiningSlash(endpoint, "/repositories/"+config.BitBucket.Repo)
target, err := url.Parse(apiURL)
Expand All @@ -142,39 +136,6 @@ func (bb *BitBucketGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
bb.proxy.ServeHTTP(w, r.WithContext(ctx))
}

func (bb *BitBucketGateway) authenticate(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims := getClaims(ctx)
config := getConfig(ctx)

if claims == nil {
return errors.New("Access to endpoint not allowed: no claims found in Bearer token")
}

if !bitbucketAllowedRegexp.MatchString(r.URL.Path) {
return errors.New("Access to endpoint not allowed: this part of BitBucket's API has been restricted")
}

if len(config.Roles) == 0 {
return nil
}

roles, ok := claims.AppMetaData["roles"]
if ok {
roleStrings, _ := roles.([]interface{})
for _, data := range roleStrings {
role, _ := data.(string)
for _, adminRole := range config.Roles {
if role == adminRole {
return nil
}
}
}
}

return errors.New("Access to endpoint not allowed: your role doesn't allow access")
}

func rewriteBitBucketLink(link, endpointAPIURL, proxyAPIURL string) string {
return proxyAPIURL + strings.TrimPrefix(link, endpointAPIURL)
}
Expand Down
43 changes: 0 additions & 43 deletions api/github.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package api

import (
"errors"
"net/http"
"net/http/httputil"
"net/url"
Expand Down Expand Up @@ -60,11 +59,6 @@ func (gh *GitHubGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if err := gh.authenticate(w, r); err != nil {
handleError(unauthorizedError(err.Error()), w, r)
return
}

endpoint := config.GitHub.Endpoint
apiURL := singleJoiningSlash(endpoint, "/repos/"+config.GitHub.Repo)
target, err := url.Parse(apiURL)
Expand All @@ -80,43 +74,6 @@ func (gh *GitHubGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
gh.proxy.ServeHTTP(w, r.WithContext(ctx))
}

func (gh *GitHubGateway) authenticate(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims := getClaims(ctx)
config := getConfig(ctx)

log := getLogEntry(r)
log.Infof("authenticate context: %v+", ctx)
if claims == nil {
// @TODO? WARNING: the check should be done in auth.go, imo.
// Having the jwt in the context (and thus, sent to github.com) is not necessary
// return errors.New("Access to endpoint not allowed: no claims found in Bearer token")
}

if !allowedRegexp.MatchString(r.URL.Path) {
return errors.New("Access to endpoint not allowed: this part of GitHub's API has been restricted")
}

if len(config.Roles) == 0 {
return nil
}

roles, ok := claims.AppMetaData["roles"]
if ok {
roleStrings, _ := roles.([]interface{})
for _, data := range roleStrings {
role, _ := data.(string)
for _, adminRole := range config.Roles {
if role == adminRole {
return nil
}
}
}
}

return errors.New("Access to endpoint not allowed: your role doesn't allow access")
}

type GitHubTransport struct{}

func (t *GitHubTransport) RoundTrip(r *http.Request) (*http.Response, error) {
Expand Down
39 changes: 0 additions & 39 deletions api/gitlab.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package api

import (
"errors"
"net/http"
"net/http/httputil"
"net/url"
Expand Down Expand Up @@ -79,11 +78,6 @@ func (gl *GitLabGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if err := gl.authenticate(w, r); err != nil {
handleError(unauthorizedError(err.Error()), w, r)
return
}

endpoint := config.GitLab.Endpoint
// repos in the form of userName/repoName must be encoded as
// userName%2FrepoName
Expand All @@ -99,39 +93,6 @@ func (gl *GitLabGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
gl.proxy.ServeHTTP(w, r.WithContext(ctx))
}

func (gl *GitLabGateway) authenticate(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims := getClaims(ctx)
config := getConfig(ctx)

if claims == nil {
return errors.New("Access to endpoint not allowed: no claims found in Bearer token")
}

if !gitlabAllowedRegexp.MatchString(r.URL.Path) {
return errors.New("Access to endpoint not allowed: this part of GitLab's API has been restricted")
}

if len(config.Roles) == 0 {
return nil
}

roles, ok := claims.AppMetaData["roles"]
if ok {
roleStrings, _ := roles.([]interface{})
for _, data := range roleStrings {
role, _ := data.(string)
for _, adminRole := range config.Roles {
if role == adminRole {
return nil
}
}
}
}

return errors.New("Access to endpoint not allowed: your role doesn't allow access")
}

var gitlabLinkRegex = regexp.MustCompile("<(.*?)>")
var gitlabLinkRelRegex = regexp.MustCompile("rel=\"(.*?)\"")

Expand Down
2 changes: 1 addition & 1 deletion api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (a *API) verifyOperatorRequest(w http.ResponseWriter, req *http.Request) (c
}

func (a *API) extractOperatorRequest(w http.ResponseWriter, req *http.Request) (context.Context, string, error) {
token, err := a.extractBearerToken(w, req)
token, err := a.auth.extractBearerToken(w, req)
if err != nil {
return nil, token, err
}
Expand Down

0 comments on commit e70ec30

Please sign in to comment.