How to integrate Sonarqube with Gitlab using Oauth 2.0

How to integrate Sonarqube with Gitlab using Oauth 2.0

sonarqube_gitlab_oauth2_integration

Here in this article we will try to setup Oauth2.0 based authorization system using a SonarQube server which acts as a client application and GitLab server which acts as a Resource provider server. We will delegate the authorization request from SonarQube to GitLab Resource provider. After authorization on Resource provider server, based on the Resource owner decision to Allow or Deny access authorization is granted to Client Application to access the User resources details on the GitLab Server. Finally the Client Applications callback url is loaded successfully.

Test Environment

Fedora 37 workstation
Vagrant
libvirt/kvm

What is Oauth2.0

OAuth 2.0 provides secure delegated server resource access to client applications on behalf of a resource owner. OAuth 2 allows authorization servers to issue access tokens to third-party clients with the approval of the resource owner or the end-user.

Important Parties Involved in Oauth2.0 authorization flow

NameDescription
Client ApplicationApplication Which wants to access your credential
Resource Provider/ServerServer providing the resources like Google, Facebook, GitHub, GitLab
Authorization Provider/ServerServer providing the Authorization like Google, Facebook, GitHub, GitLab
Resource OwnerOwner of the Resource hosted on the Resource Provider or Authorization Provider Server

Here is the project structure for GitLab and SonarQube server setup using Vagrant and Ansible as the provisioner.

GitLab Server

[admin@fedser gitlab]$ tree .
.
├── ansible
│   ├── linux_provision_gitlab_server.yml
│   └── roles
│       ├── linux_docker_install_server
│       │   ├── tasks
│       │   │   └── main.yml
│       │   └── vars
│       │       └── main.yml
│       └── linux_gitlab_install_server
│           ├── tasks
│           │   └── main.yml
│           ├── templates
│           │   └── docker-compose.yml.j2
│           └── vars
│               └── main.yml
├── gitlab_oauth_config.sh
└── Vagrantfile

SonarQube Server

[admin@fedser sonarqube]$ tree .
.
├── ansible
│   ├── linux_provision_sonar_server.yml
│   └── roles
│       ├── linux_docker_install_server
│       │   ├── tasks
│       │   │   └── main.yml
│       │   └── vars
│       │       └── main.yml
│       └── linux_sonar_install_server
│           ├── tasks
│           │   └── main.yml
│           ├── templates
│           │   ├── docker-compose.yml.j2
│           │   └── sysctl.conf.j2
│           └── vars
│               └── main.yml
├── sonar_oauth_config.sh
└── Vagrantfile

If you are interested in watching the video. Here is the YouTube video on the same step by step procedure outlined below.

Procedure

Step1: Setup GitLab Server

Here is the Vagrantfile which we will use to launch the VM and provision it using “linux_provision_gitlab_server.yml” ansible playbook.

GitLab Server Vagrantfile

[admin@fedser gitlab]$ cat Vagrantfile 
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

    config.hostmanager.enabled = true
    config.hostmanager.manage_host = true
    config.hostmanager.manage_guest = true
  
    config.vm.define "gitlab" do |gitlab|
      gitlab.vm.box = "fedora/38-cloud-base"
      gitlab.vm.hostname = "fedgitlab.stack.com"
  
      gitlab.vm.synced_folder '.', '/vagrant', disabled: true
      gitlab.vm.synced_folder ".", "/home/vagrant/gitlab", type: "sshfs"
  
      gitlab.vm.provider "libvirt" do |libvirt|
        libvirt.cpus = 2
        libvirt.memory = 4096
      end
  
      # Switch to "ansible_local" to install and use ansible on guest VM
  
      gitlab.vm.provision "ansible" do |ansible|
        ansible.playbook = "ansible/linux_provision_gitlab_server.yml"
        ansible.verbose = true
      end
    end
  end

GitLab Server Ansible Playbook

[admin@fedser gitlab]$ cat ansible/linux_provision_gitlab_server.yml 
---
- name: linuxn gitlab server management
  hosts: gitlab
  become: true
  become_user: root
  roles:
  - { role: 'linux_docker_install_server' }  
  - { role: 'linux_gitlab_install_server' }

The Ansible playbook does the setup in two parts. First it installs the docker and docker-compose as a pre-requisite. Second, it launches the GitLab server using the docker-compose.yml file.

Docker Setup

[admin@fedser gitlab]$ cat ansible/roles/linux_docker_install_server/vars/main.yml 
---
docker_repository: "https://download.docker.com/linux/fedora/docker-ce.repo"
docker_compose_repository: "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64"
sonar_user: "vagrant"
[admin@fedser gitlab]$ cat ansible/roles/linux_docker_install_server/tasks/main.yml 
---
- name: Install pre-requisite packages
  dnf: name={{ item }} state=present
  with_items:
    - dnf-plugins-core

- name: Add Docker repository
  command: "dnf config-manager --add-repo {{docker_repository}}"

- name: Install Docker Engine
  dnf: name={{ item }} state=present
  with_items:
    - docker-ce
    - docker-ce-cli
    - containerd.io

- name: Ensure group "docker" exists
  group:
    name: docker
    state: present

- name: Add the user 'vagrant' to group of 'docker'
  user:
    name: "{{sonar_user}}"
    group: "{{sonar_group}}"
    groups: docker
    append: yes

- name: Reload systemd daemon
  systemd:
    daemon_reload: yes

- name: Enable and Start Docker service
  service:
    name: docker
    enabled: yes
    state: started

- name: Validate Docker installation
  command: "docker run hello-world"

- name: install docker compose
  command: "curl -SL {{docker_compose_repository}} -o /usr/local/bin/docker-compose"

- name: ensure executable perm docker-compose 
  file:
    path: /usr/local/bin/docker-compose
    mode: '0755'

GitLab Server Setup

[admin@fedser gitlab]$ cat ansible/roles/linux_gitlab_install_server/vars/main.yml 
---
gitlab_host: "fedgitlab.stack.com"
gitlab_home: "/home/vagrant/gitlab"
gitlab_config: "/etc/gitlab"
gitlab_logs: "/var/log/gitlab"
gitlab_data: "/var/opt/gitlab"
[admin@fedser gitlab]$ cat ansible/roles/linux_gitlab_install_server/templates/docker-compose.yml.j2 
version: "3"

services:
  gitlab:
    image: "gitlab/gitlab-ce:latest"
    hostname: {{gitlab_host}}
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://fedgitlab.stack.com'
        gitlab_rails['gitlab_shell_ssh_port'] = 2224
        gitlab_rails['initial_root_password'] = "vF8Cc9PikcWwnyKDn4SbfvKmk42XTD4q8RD8C+WpIuc="
        gitlab_rails['display_initial_root_password'] = true
    ports:
      - "443:443"
      - "80:80"
      - "2224:2224"
    volumes:
      - gitlab_config:{{gitlab_config}}
      - gitlab_logs:{{gitlab_logs}}
      - gitlab_data:{{gitlab_data}}
    shm_size: '256m'

volumes:
  gitlab_config:
  gitlab_logs:
  gitlab_data:
[admin@fedser gitlab]$ cat ansible/roles/linux_gitlab_install_server/tasks/main.yml 
---
- name: ensure docker compose file exists
  template:
    src: "docker-compose.yml.j2"
    dest: "{{gitlab_home}}/docker-compose.yml"

- name: ensure gitlab service up
  command: "/usr/local/bin/docker-compose -f {{gitlab_home}}/docker-compose.yml up -d"

Now its time to launch our GitLab server using vagrant as shown below.

[admin@fedser gitlab]$ vagrant up

Once the GitLab server is fully up and running. We can access it using the below.

URL - http://fedgitlab.stack.com/

Step2: Setup SonarQube Server

Here is the Vagrantfile which we will use to launch the VM and provision it using “linux_provision_sonar_server.yml” ansible playbook.

SonarQube Vagrantfile

[admin@fedser sonarqube]$ cat Vagrantfile 
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

    config.hostmanager.enabled = true
    config.hostmanager.manage_host = true
    config.hostmanager.manage_guest = true
  
    config.vm.define "sonar" do |sonar|
      sonar.vm.box = "fedora/38-cloud-base"
      sonar.vm.hostname = "fedsonar.stack.com"
  
      sonar.vm.synced_folder '.', '/vagrant', disabled: true
      sonar.vm.synced_folder ".", "/home/vagrant/sonar", type: "sshfs"
  
      sonar.vm.provider "libvirt" do |libvirt|
        libvirt.cpus = 2
        libvirt.memory = 2048
      end
  
      # Switch to "ansible_local" to install and use ansible on guest VM
  
      sonar.vm.provision "ansible" do |ansible|
        ansible.playbook = "ansible/linux_provision_sonar_server.yml"
        ansible.verbose = true
      end
    end
  end

SonarQube Ansible Playbook

[admin@fedser sonarqube]$ cat ansible/linux_provision_sonar_server.yml 
---
- name: linuxn sonar server management
  hosts: sonar
  become: true
  become_user: root
  roles:
  - { role: 'linux_docker_install_server' }  
  - { role: 'linux_sonar_install_server' }

The Ansible playbook does the setup in two parts. First it installs the docker and docker-compose as a pre-requisite. Second, it launches the SonarQube server using the docker-compose.yml file.

Docker Setup

[admin@fedser sonarqube]$ cat ansible/roles/linux_docker_install_server/vars/main.yml 
---
docker_repository: "https://download.docker.com/linux/fedora/docker-ce.repo"
docker_compose_repository: "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64"
sonar_user: "vagrant"
sonar_group: "docker"
[admin@fedser sonarqube]$ cat ansible/roles/linux_docker_install_server/tasks/main.yml 
---
  - name: Install pre-requisite packages
    dnf: name={{ item }} state=present
    with_items:
      - dnf-plugins-core

  - name: Add Docker repository
    command: "dnf config-manager --add-repo {{docker_repository}}"
  
  - name: Install Docker Engine
    dnf: name={{ item }} state=present
    with_items:
      - docker-ce
      - docker-ce-cli
      - containerd.io
  
  - name: Ensure group "docker" exists
    group:
      name: docker
      state: present
  
  - name: Add the user 'vagrant' to group of 'docker'
    user:
      name: "{{sonar_user}}"
      group: "{{sonar_group}}"
      groups: docker
      append: yes

  - name: Reload systemd daemon
    systemd:
      daemon_reload: yes

  - name: Enable and Start Docker service
    service:
      name: docker
      enabled: yes
      state: started

  - name: Validate Docker installation
    command: "docker run hello-world"
  
  - name: install docker compose
    command: "curl -SL {{docker_compose_repository}} -o /usr/local/bin/docker-compose"

  - name: ensure executable perm docker-compose 
    file:
      path: /usr/local/bin/docker-compose
      mode: '0755'

SonarQube Setup

[admin@fedser sonarqube]$ cat ansible/roles/linux_sonar_install_server/vars/main.yml 
---
sonar_home: "/home/vagrant/sonar"

[admin@fedser sonarqube]$ cat ansible/roles/linux_sonar_install_server/templates/sysctl.conf.j2 
# sysctl settings are defined through files in
# /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/.
#
# Vendors settings live in /usr/lib/sysctl.d/.
# To override a whole file, create a new file with the same in
# /etc/sysctl.d/ and put new settings there. To override
# only specific settings, add a file with a lexically later
# name in /etc/sysctl.d/ and put new settings there.
#
# For more information, see sysctl.conf(5) and sysctl.d(5).
vm.max_map_count=262144
[admin@fedser sonarqube]$ cat ansible/roles/linux_sonar_install_server/templates/docker-compose.yml.j2 
version: "3"

services:
  sonarqube:
    image: sonarqube:community
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "80:9000"
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:
[admin@fedser sonarqube]$ cat ansible/roles/linux_sonar_install_server/tasks/main.yml 
---
- name: ensure docker compose file exists
  template:
    src: "docker-compose.yml.j2"
    dest: "{{sonar_home}}/docker-compose.yml"

- name: ensure max map count set
  template:
    src: "sysctl.conf.j2"
    dest: "/etc/sysctl.conf"

- name: ensure kernel parameters reloaded
  command: "sysctl -p"

- name: ensure sonar service up
  command: "/usr/local/bin/docker-compose -f {{sonar_home}}/docker-compose.yml up -d"

Now its time to launch the SonarQube server using Vagrant as shown below.

[admin@fedser sonarqube]$ vagrant up

Once the server is up and running it can be access using the below.

URL - http://fedsonar.stack.com/

Step3: Create an Oauth Client App

Before we can create the Ouath Client using the REST API, we need to generate a personal access token for the “root” user as shown below and capture the token.

personal access token – glpat-Wp1Jf3XRW91j9BX5E_6E

Create an Instance wide Oauth2 client application. Here is the bash script using the REST API call to setup the Oauth Client.

[admin@fedser gitlab]$ cat gitlab_oauth_config.sh 
#!/bin/bash

oauthapp="sonarqube1"
gitlaburl="http://fedgitlab.stack.com"
sonarurl="http://fedsonar.stack.com"
oauthscope="api read_user email"

# Create Oauth2 Client
echo "Create Oauth2 client application"
curl --request POST --header "PRIVATE-TOKEN: glpat-Wp1Jf3XRW91j9BX5E_6E" \
     --data "name=$oauthapp&redirect_uri=$sonarurl/oauth2/callback/gitlab&scopes=$oauthscope" \
     "$gitlaburl/api/v4/applications"

Once we execute the following script with the generated personal access token it should provide us with the Application ID and Secret which can be captured from the JSON response.

Generated Details

Application ID - 11faa63fd46921f90dae1652920cd510a812a99dd36dbc11b90bda4d9d1a28f9
Secret - gloas-ce20a0e6686073f7f7773d2493ed2fc8b763a3673dc2cfe35c497fe993d63979

Here is the screenshot of the Oauth2 Client Application.

Step4: Setup Gitlab Auth in SonarQube

Here in this step we will setup the client application ie. SonarQube to leverage Gitlab as the Authentication provider. Here we will use a bash script to reset the default “admin” user password and trigger REST API calls to update the settings for Gitlab Authentication.

NOTE: Uncomment the resetpassword function if the sonarqube default password is not changed

[admin@fedser sonarqube]$ cat sonar_oauth_config.sh 
#!/bin/bash

adminuser="admin"
defaultpassword="admin"
adminpassword="admin@1234"
baseurl="http://fedsonar.stack.com"
enabled=true
gitlaburl="http://fedgitlab.stack.com"
appid="dfa0869ed0248fdf8b5a6ff193dc407bd017eba90233f4370b317e3de39e1f3f"
secret="gloas-8d424318db74bb4b65b0a2d400399e28bfdf2c6587013e2bd7ffda96f6ef1b3a"
allowsignup=true

# Reset admin password
resetpassword()
{
	echo "Reset admin password"
	curl -u $adminuser:$defaultpassword -X POST "$baseurl/api/users/change_password?login=$adminuser&previousPassword=$defaultpassword&password=$adminpassword"
}

# Configure gitlab oauth client
configureoauth()
{
	echo "Configure oauth client settings"
	curl -u $adminuser:$adminpassword -X POST "$baseurl/api/settings/set?key=sonar.core.serverBaseURL&value=$baseurl"
	curl -u $adminuser:$adminpassword -X POST "$baseurl/api/settings/set?key=sonar.auth.gitlab.enabled&value=$enabled"
	curl -u $adminuser:$adminpassword -X POST "$baseurl/api/settings/set?key=sonar.auth.gitlab.url&value=$gitlaburl"
	curl -u $adminuser:$adminpassword -X POST "$baseurl/api/settings/set?key=sonar.auth.gitlab.applicationId.secured&value=$appid"
	curl -u $adminuser:$adminpassword -X POST "$baseurl/api/settings/set?key=sonar.auth.gitlab.secret.secured&value=$secret"
	curl -u $adminuser:$adminpassword -X POST "$baseurl/api/settings/set?key=sonar.auth.gitlab.allowUsersToSignUp&value=$allowsignup"
}

### To be carried out by admin user
#resetpassword
configureoauth

Once we execute the following bash script, it will reset the default “admin” user password, update the sonarqube base url and update the setting for the gitlab authentication. Please note we are using the application id and secret that we generated in GitLab server to configure the client side.

Step5: Authenticate to SonarQube

Once the Oauth setup is completed. Its time to access the SonarQube portal and validate that it now shows us an option to “Login with GitLab” as shown below.

URL - http://fedsonar.stack.com/

Once we click on the “Login with GitLab” option it redirects us to “GitLab” authentication page as shown below.

Now we can authenticate with the “root” user on the gitlab portal and once we authentication successfully it ask us whether we want to authorize the Sonarqube client to access the user details on Gitlab as shown below.

Once we authorize the SonarQube client to access GitLab user resource details, we are redirected back to the SonarQube along with authcode using the redirectUrl. Then SonarQube contacts GitLab server along with the obtained authcode to make sure everything is okay. Then GitLab server grants access token to SonarQube client and we finally are able to access SonarQube.

If you now check the user profile details for the user with which we logged into the SonarQube portal it will be the “root” user of the GitLab server the resource which we are granted access to by Authorization server.

Hope you enjoyed reading this article. Thank you..