diff --git a/CHANGELOG.md b/CHANGELOG.md index 629c72fa..64a09453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -Coming soon! Please document any work in progress here as part of your PR. It will be moved to the next tag when released. +- Add provider for GitLab, and support for using `teamWhiteList` to restrict access based on GitLab group/repository access. ## v0.37.0 diff --git a/config/config.yml_example_gitlab b/config/config.yml_example_gitlab new file mode 100644 index 00000000..0f1dfa1d --- /dev/null +++ b/config/config.yml_example_gitlab @@ -0,0 +1,40 @@ + +# Vouch Proxy configuration +# bare minimum to get Vouch Proxy running with gitlab + +vouch: + # domains: + # valid domains that the jwt cookies can be set into + # the callback_urls will be to these domains + # for github that's only one domain since they only allow one callback URL + # https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#redirect-urls + # each of these domains must serve the url https://login.$domains[0] https://login.$domains[1] ... + domains: + - yourothersite.io + + # set allowAllUsers: true to use Vouch Proxy to just accept anyone who can authenticate at GitHub + # allowAllUsers: true + + cookie: + # allow the jwt/cookie to be set into http://yourdomain.com (defaults to true, requiring https://yourdomain.com) + secure: false + # vouch.cookie.domain must be set when enabling allowAllUsers + # domain: yourdomain.com + + # set teamWhitelist: to list of GitLab group/repositories + # The user is authorized if they have access to the given group or repository. + # teamWhitelist: + # - myGroup + # - myGroup/myRepository + +oauth: + # remember to create a new OAuth application in GitLab + provider: gitlab + client_id: xxxxxxxxxxxxxxxxxxxx + client_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + callback_url: http://vouch.yourdomain.com:9090/auth + + # if running a self-hosted instance of GitLab, set the following urls to point to the correct domain + # auth_url: https://{yourGitLabDomain}/oauth/authorize + # token_url: https://{yourGitLabDomain}/oauth/token + # user_info_url: https://{yourGitLabDomain}/oauth/userinfo diff --git a/handlers/handlers.go b/handlers/handlers.go index 6dae94e3..297a6936 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -24,6 +24,7 @@ import ( "github.com/vouch/vouch-proxy/pkg/providers/azure" "github.com/vouch/vouch-proxy/pkg/providers/common" "github.com/vouch/vouch-proxy/pkg/providers/github" + "github.com/vouch/vouch-proxy/pkg/providers/gitlab" "github.com/vouch/vouch-proxy/pkg/providers/google" "github.com/vouch/vouch-proxy/pkg/providers/homeassistant" "github.com/vouch/vouch-proxy/pkg/providers/indieauth" @@ -88,6 +89,8 @@ func getProvider() Provider { return openid.Provider{} case cfg.Providers.Alibaba: return alibaba.Provider{} + case cfg.Providers.GitLab: + return gitlab.Provider{} default: // shouldn't ever reach this since cfg checks for a properly configure `oauth.provider` log.Fatal("oauth.provider appears to be misconfigured, please check your config") diff --git a/pkg/cfg/oauth.go b/pkg/cfg/oauth.go index 56442d64..fd5580a3 100644 --- a/pkg/cfg/oauth.go +++ b/pkg/cfg/oauth.go @@ -44,6 +44,7 @@ var ( OpenStax: "openstax", Nextcloud: "nextcloud", Alibaba: "alibaba", + GitLab: "gitlab", } ) @@ -59,6 +60,7 @@ type OAuthProviders struct { OpenStax string Nextcloud string Alibaba string + GitLab string } // oauth config items endoint for access @@ -122,7 +124,8 @@ func oauthBasicTest() error { GenOAuth.Provider != Providers.OIDC && GenOAuth.Provider != Providers.OpenStax && GenOAuth.Provider != Providers.Nextcloud && - GenOAuth.Provider != Providers.Alibaba { + GenOAuth.Provider != Providers.Alibaba && + GenOAuth.Provider != Providers.GitLab { return errors.New("configuration error: Unknown oauth provider: " + GenOAuth.Provider) } // OAuthconfig Checks @@ -188,6 +191,9 @@ func setProviderDefaults() { } else if GenOAuth.Provider == Providers.IndieAuth { GenOAuth.CodeChallengeMethod = "S256" configureOAuthClient() + } else if GenOAuth.Provider == Providers.GitLab { + setDefaultsGitLab() + configureOAuthClient() } else { // OIDC, OpenStax, Nextcloud configureOAuthClient() @@ -270,6 +276,22 @@ func setDefaultsGitHub() { GenOAuth.CodeChallengeMethod = "S256" } +func setDefaultsGitLab() { + if GenOAuth.AuthURL == "" { + GenOAuth.AuthURL = "https://gitlab.com/oauth/authorize" + } + if GenOAuth.TokenURL == "" { + GenOAuth.TokenURL = "https://gitlab.com/oauth/token" + } + if GenOAuth.UserInfoURL == "" { + GenOAuth.UserInfoURL = "https://gitlab.com/oauth/userinfo" + } + if len(GenOAuth.Scopes) == 0 { + GenOAuth.Scopes = []string{"openid"} + } + GenOAuth.CodeChallengeMethod = "S256" +} + func configureOAuthClient() { log.Infof("configuring %s OAuth with Endpoint %s", GenOAuth.Provider, GenOAuth.AuthURL) OAuthClient = &oauth2.Config{ diff --git a/pkg/providers/gitlab/gitlab.go b/pkg/providers/gitlab/gitlab.go new file mode 100644 index 00000000..c5c48fc9 --- /dev/null +++ b/pkg/providers/gitlab/gitlab.go @@ -0,0 +1,46 @@ +package gitlab + +import ( + "encoding/json" + "github.com/vouch/vouch-proxy/pkg/cfg" + "github.com/vouch/vouch-proxy/pkg/providers/common" + "github.com/vouch/vouch-proxy/pkg/structs" + "golang.org/x/oauth2" + "io" + "net/http" +) + +type Provider struct{} + +func (Provider) Configure() { +} + +func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *structs.CustomClaims, ptokens *structs.PTokens, opts ...oauth2.AuthCodeOption) (rerr error) { + client, _, err := common.PrepareTokensAndClient(r, ptokens, true, opts...) + if err != nil { + return err + } + userinfo, err := client.Get(cfg.GenOAuth.UserInfoURL) + if err != nil { + return err + } + defer func() { + if err := userinfo.Body.Close(); err != nil { + rerr = err + } + }() + data, _ := io.ReadAll(userinfo.Body) + cfg.Logging.Logger.Infof("GitLab userinfo body: %s", string(data)) + if err = common.MapClaims(data, customClaims); err != nil { + cfg.Logging.Logger.Error(err) + return err + } + var glUser structs.GitLabUser + if err = json.Unmarshal(data, &glUser); err != nil { + cfg.Logging.Logger.Error(err) + return err + } + glUser.PrepareUserData() + *user = glUser.User + return nil +} diff --git a/pkg/structs/structs.go b/pkg/structs/structs.go index bccc0180..b96dacd0 100644 --- a/pkg/structs/structs.go +++ b/pkg/structs/structs.go @@ -10,7 +10,9 @@ OR CONDITIONS OF ANY KIND, either express or implied. package structs -import "strconv" +import ( + "strconv" +) // CustomClaims Temporary struct storing custom claims until JWT creation. type CustomClaims struct { @@ -148,7 +150,7 @@ type Contact struct { Verified bool `json:"is_verified"` } -//OpenStaxUser is a retrieved and authenticated user from OpenStax Accounts +// OpenStaxUser is a retrieved and authenticated user from OpenStax Accounts type OpenStaxUser struct { User Contacts []Contact `json:"contact_infos"` @@ -217,6 +219,33 @@ type AliData struct { OuName string `json:"ou_name"` } +// GitLabUser is a user provided by GitLab +// https://docs.gitlab.com/ee/integration/openid_connect_provider.html +type GitLabUser struct { + User + Sub string `json:"sub"` + AuthTime int `json:"auth_time"` + Name string `json:"name"` + Nickname string `json:"nickname"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Website string `json:"website"` + Profile string `json:"profile"` + Picture string `json:"picture"` + Groups []string `json:"groups"` + GroupsDirect []string `json:"groups_direct"` + OwnerOfGroups []string `json:"https://gitlab.org/claims/groups/owner"` + MaintainerOfGroups []string `json:"https://gitlab.org/claims/groups/maintainer"` + DeveloperOfGroups []string `json:"https://gitlab.org/claims/groups/developer"` +} + +func (g *GitLabUser) PrepareUserData() { + g.User.Name = g.Name + g.User.Username = g.Nickname + g.User.Email = g.Email + g.User.TeamMemberships = g.Groups +} + // Team has members and provides acess to sites type Team struct { Name string `json:"name" mapstructure:"name"`