Skip to content

Instantly share code, notes, and snippets.

@skurfuerst
Last active October 11, 2022 09:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save skurfuerst/efc1df4971d969797397c6bb76f0eb50 to your computer and use it in GitHub Desktop.
Save skurfuerst/efc1df4971d969797397c6bb76f0eb50 to your computer and use it in GitHub Desktop.
GitLab and Rancher 2 Authentication Proxy
#!/bin/bash
set -ex
go build -o rancher_gitlab_proxy main.go
GOOS=linux go build -o rancher_gitlab_proxy_linux main.go
# in GitLab config - gitlab.rb
nginx['custom_gitlab_server_config'] = "
# CONNECTION TO rancher-gitlab-proxy BEGIN
location /login/oauth/authorize {
proxy_pass http://127.0.0.1:8888;
}
location /login/oauth/access_token {
proxy_pass http://127.0.0.1:8888;
}
location /api/v3/user {
proxy_pass http://127.0.0.1:8888;
}
location /api/v3/teams/ {
proxy_pass http://127.0.0.1:8888;
}
location /api/v3/search/users {
proxy_pass http://127.0.0.1:8888;
}
# CONNECTION TO rancher-gitlab-proxy END
"
module sandstorm.de/rancher-gitlab-proxy
go 1.15
require (
github.com/julienschmidt/httprouter v1.3.0
github.com/xanzy/go-gitlab v0.40.2
)
package main
import (
"encoding/json"
"fmt"
"github.com/julienschmidt/httprouter"
"github.com/xanzy/go-gitlab"
"net/http"
"strconv"
"strings"
)
// !!! ADJUST
const gitlab_url = "https://gitlab.com"
const rancher_url = "https://rancher-url.de"
// !!! ADJUST
///////////////// MAIN
func main() {
router := httprouter.New()
router.GET("/login/oauth/authorize", oauthAuthorize)
router.POST("/login/oauth/access_token", oauthAccessToken)
router.GET("/api/v3/user", apiV3User)
router.GET("/api/v3/user/:id", apiV3UserId)
router.GET("/api/v3/teams/:id", apiV3TeamsId)
router.GET("/api/v3/search/users", apiV3SearchUsers)
fmt.Println("Listening to 127.0.0.1:8888")
if err := http.ListenAndServe("127.0.0.1:8888", router); err != nil {
panic(err)
}
}
///////////////// API
func oauthAuthorize(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
v := req.URL.Query()
v.Add("response_type", "code")
v.Add("scope", "read_api")
target := gitlab_url + "/oauth/authorize?" + v.Encode()
http.Redirect(w, req, target, http.StatusTemporaryRedirect)
}
func oauthAccessToken(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
v := req.URL.Query()
v.Add("grant_type", "authorization_code")
v.Add("redirect_uri", rancher_url + "/verify-auth")
target := gitlab_url + "/oauth/token?" + v.Encode()
http.Redirect(w, req, target, http.StatusTemporaryRedirect)
}
func apiV3User(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
gitlabClient := createGitlabClient(req)
gitlabUser, _, err := gitlabClient.Users.CurrentUser()
if err != nil {
panic(err)
}
githubAccount := convertGitlabUserToAccount(gitlabUser)
jsonStr, _ := json.Marshal(githubAccount)
w.Write(jsonStr)
}
func apiV3UserId(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
// Workaround to deal with routing library
if ps.ByName("id") == "orgs" {
apiV3UserOrgs(w, req, ps)
return
}
if ps.ByName("id") == "teams" {
apiV3UserTeams(w, req, ps)
return
}
gitlabClient := createGitlabClient(req)
id, _ := strconv.Atoi(ps.ByName("id"))
// user
gitlabUser, _, err := gitlabClient.Users.GetUser(id)
if err != nil {
panic(err)
}
githubAccount := convertGitlabUserToAccount(gitlabUser)
jsonStr, _ := json.Marshal(githubAccount)
w.Write(jsonStr)
}
func apiV3UserOrgs(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
result := make([]string, 0)
jsonStr, _ := json.Marshal(result)
w.Write(jsonStr)
}
func apiV3UserTeams(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
gitlabClient := createGitlabClient(req)
allAvailable := false
result := make([]Team, 0, 0)
listGroupsOptions := &gitlab.ListGroupsOptions{
ListOptions: gitlab.ListOptions{
},
// here, we only want to search for groups WHICH WE ARE MEMBER OF!!!
AllAvailable: &allAvailable,
}
for {
gitlabGroups, resp, err := gitlabClient.Groups.ListGroups(listGroupsOptions)
if err != nil {
panic(err)
}
for _, gitlabGroup := range gitlabGroups {
team := convertGitlabGroupToTeam(gitlabGroup)
result = append(result, *team)
}
// Exit the loop when we've seen all pages.
if resp.CurrentPage >= resp.TotalPages {
break
}
// Update the page number to get the next page.
listGroupsOptions.ListOptions.Page = resp.NextPage
}
jsonStr, _ := json.Marshal(result)
w.Write(jsonStr)
}
func apiV3TeamsId(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
gitlabClient := createGitlabClient(req)
id, _ := strconv.Atoi(ps.ByName("id"))
gitlabGroup, _, err := gitlabClient.Groups.GetGroup(id)
if err != nil {
panic(err)
}
team := convertGitlabGroupToTeam(gitlabGroup)
jsonStr, _ := json.Marshal(team)
w.Write(jsonStr)
}
type searchResult struct {
Items []*Account `json:"items"`
}
func apiV3SearchUsers(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
query := req.URL.Query().Get("q")
gitlabClient := createGitlabClient(req)
searchResult := &searchResult{
Items: make([]*Account, 0),
}
shouldSearchUsers := true
shouldSearchOrgs := true
if strings.Contains(query, "type:org") {
shouldSearchUsers = false
shouldSearchOrgs = true
query = strings.ReplaceAll(query, "type:org", "")
}
if shouldSearchOrgs {
allAvailable := true
gitlabGroups, _, err := gitlabClient.Groups.ListGroups(&gitlab.ListGroupsOptions{
Search: &query,
// we want to find ALL groups (which are not fully private)
AllAvailable: &allAvailable,
})
if err != nil {
panic(err)
}
for _, gitlabGroup := range gitlabGroups {
githubOrg := convertGitlabGroupToAccount(gitlabGroup)
searchResult.Items = append(searchResult.Items, githubOrg)
}
}
if shouldSearchUsers {
gitlabUsers, _, err := gitlabClient.Users.ListUsers(&gitlab.ListUsersOptions{
Search: &query,
})
if err != nil {
panic(err)
}
for _, gitlabUser := range gitlabUsers {
githubAccount := convertGitlabUserToAccount(gitlabUser)
searchResult.Items = append(searchResult.Items, githubAccount)
}
}
jsonStr, _ := json.Marshal(searchResult)
w.Write(jsonStr)
}
///////////////// HELPERS
// https://docs.github.com/en/free-pro-team@latest/rest/reference/users#get-the-authenticated-user
// copied from https://github.com/rancher/rancher/blob/2506427ba7bd31edf12f7110b7fdb8b2defe8eb3/pkg/auth/providers/github/github_account.go#L12
type Account struct {
ID int `json:"id,omitempty"`
Login string `json:"login,omitempty"`
Name string `json:"name,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
// "Type" must be "user", "team", oder "org"
Type string `json:"type,omitempty"`
}
//Team defines properties a team on github has
type Team struct {
ID int `json:"id,omitempty"`
Organization map[string]interface{} `json:"organization,omitempty"`
Name string `json:"name,omitempty"`
Slug string `json:"slug,omitempty"`
}
func createGitlabClient(req *http.Request) *gitlab.Client {
authorizationHeader := req.Header.Get("Authorization")
t := strings.Split(authorizationHeader, " ")
token := t[1]
gitlabClient, err := gitlab.NewOAuthClient(token, gitlab.WithBaseURL(gitlab_url + "/api/v4"))
if err != nil {
panic(err)
}
return gitlabClient
}
func convertGitlabUserToAccount(gitlabUser *gitlab.User) *Account {
return &Account{
ID: gitlabUser.ID,
Login: gitlabUser.Username,
Name: gitlabUser.Name,
AvatarURL: gitlabUser.AvatarURL,
HTMLURL: "",
Type: "user",
}
}
func convertGitlabGroupToAccount(gitlabGroup *gitlab.Group) *Account {
return &Account{
ID: gitlabGroup.ID,
Login: gitlabGroup.Path,
Name: gitlabGroup.Name,
AvatarURL: gitlabGroup.AvatarURL,
HTMLURL: "",
Type: "team",
}
}
func convertGitlabGroupToTeam(gitlabGroup *gitlab.Group) *Team {
org := make(map[string]interface{})
org["login"] = gitlabGroup.Path
org["avatar_url"] = gitlabGroup.AvatarURL
return &Team{
ID: gitlabGroup.ID,
Organization: org,
Name: gitlabGroup.Name,
Slug: gitlabGroup.Path,
}
}
[Unit]
Description=rancher-gitlab-proxy
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=gitlab-www
Group=gitlab-www
ExecStart=/usr/local/bin/rancher_gitlab_proxy
SyslogIdentifier=rancher-gitlab-proxy
Restart=always
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment