Skip to content

Commit

Permalink
Enable OAuth login, make fetching users faster (#9)
Browse files Browse the repository at this point in the history
* fix: Enable OAuth login on test Grafana

* fix: Parallelize user fetching to make it faster
  • Loading branch information
davidgubler authored Jan 29, 2024
1 parent bcbef26 commit 70678ed
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 6 deletions.
22 changes: 22 additions & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ services:
image: grafana/grafana-oss:9.5.3
environment:
- GF_SECURITY_ADMIN_PASSWORD=$GF_SECURITY_ADMIN_PASSWORD
- GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true
- GF_AUTH_GENERIC_OAUTH_API_URL=https://id.test.vshn.net/auth/realms/VSHN-main-dev-realm/protocol/openid-connect/userinfo
- GF_AUTH_GENERIC_OAUTH_AUTH_URL=https://id.test.vshn.net/auth/realms/VSHN-main-dev-realm/protocol/openid-connect/auth
- GF_AUTH_GENERIC_OAUTH_CLIENT_ID=operator-dev-grafana
- GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=$GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET
- GF_AUTH_GENERIC_OAUTH_ENABLED=true
- GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH=preferred_username
- GF_AUTH_GENERIC_OAUTH_NAME=VSHN Test Keycloak
- GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH='Deny'
- GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT=false
- GF_AUTH_GENERIC_OAUTH_SKIP_ORG_ROLE_SYNC=true
- GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email
- GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://id.test.vshn.net/auth/realms/VSHN-main-dev-realm/protocol/openid-connect/token
- GF_SERVER_DOMAIN=operator-dev-grafana.apps.cloudscale-lpg-2.appuio.cloud
- GF_SERVER_ROOT_URL=https://operator-dev-grafana.apps.cloudscale-lpg-2.appuio.cloud
ports:
- "3000:3000"
labels:
Expand All @@ -15,3 +30,10 @@ services:
reservations:
cpus: "0.01"
memory: 512M
volumes:
- grafana_data:/var/lib/grafana

volumes:
grafana_data:
labels:
k8ify.singleton: true
96 changes: 90 additions & 6 deletions pkg/keycloakClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,36 @@ func (this *KeycloakClient) GetToken() (string, error) {
return fmt.Sprintf("%s", accessToken), nil
}

func (this *KeycloakClient) GetUsers(token string) ([]*KeycloakUser, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/auth/admin/realms/%s/users?max=100000", this.baseURL.String(), this.realm), nil)
func (this *KeycloakClient) getUsersCount(token string) (uint32, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/auth/admin/realms/%s/users/count", this.baseURL.String(), this.realm), nil)
if err != nil {
return 0, err
}

req.Header["Authorization"] = []string{"Bearer " + token}
req.Header["cache-control"] = []string{"no-cache"}

r, err := this.client.Do(req)
if err != nil {
return 0, err
}
defer r.Body.Close()

body, err := io.ReadAll(r.Body)
if err != nil {
return 0, err
}

var count uint32 = 0
err = json.Unmarshal(body, &count)
if err != nil {
return 0, err
}
return count, nil
}

func (this *KeycloakClient) getUsers(token string, batchSize uint32, first uint32) ([]*KeycloakUser, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/auth/admin/realms/%s/users?max=%d&first=%d", this.baseURL.String(), this.realm, batchSize, first), nil)
if err != nil {
return nil, err
}
Expand All @@ -177,6 +205,62 @@ func (this *KeycloakClient) GetUsers(token string) ([]*KeycloakUser, error) {
return keycloakUsers, nil
}

func (this *KeycloakClient) usersWorker(token string, batchSize uint32, firstChan chan uint32, results *sync.Map, errorCount *uint64, wg *sync.WaitGroup) {
defer wg.Done()

for first := range firstChan {
groups, err := this.getUsers(token, batchSize, first)
if err != nil {
atomic.AddUint64(errorCount, 1)
klog.Error(err)
}
results.Store(first, groups)
}
}

func (this *KeycloakClient) GetUsers(token string) ([]*KeycloakUser, error) {
// This could be a simple straight fetch of all users, but because of https://github.com/keycloak/keycloak/issues/10005
// we need to do parallel fetches to keep the load times reasonable
count, err := this.getUsersCount(token)
if err != nil {
return nil, err
}

results := sync.Map{}
var errorCount uint64
var batchSize uint32 = 50

firstChan := make(chan uint32)
wg := new(sync.WaitGroup)

// creating workers
for i := 0; i < 10; i++ {
wg.Add(1)
go this.usersWorker(token, batchSize, firstChan, &results, &errorCount, wg)
}

// sending batches to workers
var i uint32
for i = 0; i <= count/batchSize; i++ {
firstChan <- i * batchSize
}

close(firstChan)
wg.Wait()

if errorCount > 0 {
return nil, errors.New("Could not fetch all users")
}

users := make([]*KeycloakUser, 0)
results.Range(func(k, v interface{}) bool {
users = append(users, v.([]*KeycloakUser)...)
return true
})

return users, nil
}

func (this *KeycloakClient) GetGroups(token string) ([]*KeycloakGroup, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/auth/admin/realms/%s/groups?max=100000&briefRepresentation=false", this.baseURL.String(), this.realm), nil)
if err != nil {
Expand Down Expand Up @@ -235,7 +319,7 @@ func (this *KeycloakClient) findSubgroup(groups []*KeycloakGroup) {
}
}

func (this *KeycloakClient) GetGroupMembership(token string, user *KeycloakUser) ([]*KeycloakGroup, error) {
func (this *KeycloakClient) getGroupMembership(token string, user *KeycloakUser) ([]*KeycloakGroup, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/auth/admin/realms/%s/users/%s/groups", this.baseURL.String(), this.realm, user.Id), nil)
if err != nil {
return nil, err
Expand Down Expand Up @@ -263,11 +347,11 @@ func (this *KeycloakClient) GetGroupMembership(token string, user *KeycloakUser)
return groups, nil
}

func (this *KeycloakClient) worker(token string, userChan chan *KeycloakUser, results *sync.Map, errorCount *uint64, wg *sync.WaitGroup) {
func (this *KeycloakClient) groupMembershipWorker(token string, userChan chan *KeycloakUser, results *sync.Map, errorCount *uint64, wg *sync.WaitGroup) {
defer wg.Done()

for user := range userChan {
groups, err := this.GetGroupMembership(token, user)
groups, err := this.getGroupMembership(token, user)
if err != nil {
atomic.AddUint64(errorCount, 1)
klog.Error(err)
Expand All @@ -286,7 +370,7 @@ func (this *KeycloakClient) GetGroupMemberships(token string, users []*KeycloakU
// creating workers
for i := 0; i < 10; i++ {
wg.Add(1)
go this.worker(token, userChan, &results, &errorCount, wg)
go this.groupMembershipWorker(token, userChan, &results, &errorCount, wg)
}

// sending users to workers
Expand Down

0 comments on commit 70678ed

Please sign in to comment.