Software development is a dynamic process that involves multiple stages, from writing code to testing and deployment. Code quality plays a crucial role in software development, as it determines the overall performance and reliability of the software. Poor code quality can lead to bugs, crashes, and security vulnerabilities. SonarQube is a powerful code quality management tool that helps developers identify and fix code quality and security issues. In this article, we will explore how to use SonarQube on AWS Elastic Container Service (ECS).

Table of contents

Setup Virtual Private Cloud (VPC) 🌐
Create KMS encryption key πŸ”’
Setup AWS RDS database πŸ—ƒοΈ
Setup Application Load Balancer πŸ‹οΈ
Setup AWS EFS πŸ’Ύ
Setup IAM roles πŸ‘₯
Upload SonarQube image to ECR πŸš€
Create ECS Workload βš™οΈ
Connect to SonarQube πŸ”—

Setup Virtual Private Cloud (VPC) 🌐

When setting up a new AWS environment for our project, one of the first things you'll need to do is create a VPC. The VPC will consist of 6 subnets in 3 Availability Zones (AZs), with 3 private, and 3 public ones. Additionally, we need 1 Internet Gateway (IG) and 1 NAT Gateway.

Setup security groups

When setting up the VPC, it is essential to configure security groups to control inbound and outbound traffic to and from the VPC. Security groups act as virtual firewalls, allowing only authorized traffic to pass through. In total, we need to set up four security groups:

default

This security group will be used for the EFS filesystem.

GroupName Type IpProtocol Port IpRanges / UserIdGroupPairs
default Inbound/Ingress tcp 2049 sonarqube-sg

sonarqube-lb

This security group will be attached to the application load balancer.

GroupName Type IpProtocol Port IpRanges / UserIdGroupPairs
sonarqube-lb Inbound/Ingress tcp 80 <Company Network>
sonarqube-lb Outbound/Egress tcp 9000 sonarqube-sg

sonarqube-sg

This security group will be used for the ECS cluster.

GroupName Type IpProtocol Port IpRanges / UserIdGroupPairs
sonarqube-sg Inbound/Ingress tcp 9000 sonarqube-lb
sonarqube-sg Outbound/Egress tcp 5432 sonarqube-db
sonarqube-sg Outbound/Egress tcp 443 0.0.0.0/0
sonarqube-sg Outbound/Egress tcp 2049 default

sonarqube-db

This security group will be attached to the AWS RDS database.

GroupName Type IpProtocol Port IpRanges / UserIdGroupPairs
sonarqube-db Inbound/Ingress tcp 5432 sonarqube-sg

Create KMS encryption key πŸ”’

In this section, we'll cover the process of creating an AWS Key Management Service (KMS) key. This key will be used to encrypt our database credentials. Choose the symmetric encryption and name it sonarqube.

The key needs have the following key policy attached:

{
    "Id": "key-consolepolicy-3",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::0123456789:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        }
    ]
}

Setup AWS RDS database πŸ—ƒοΈ

In this section, we'll discuss how to set up an Amazon Relational Database Service (RDS) instance to host the SonarQube database. We'll go through the process of creating an RDS instance and configuring it to meet the SonarQube requirements.

Create Subnet Group

First, we create a subnet group within the previously created three private subnets. You can find this option on the left-hand side in the AWS RDS console.

Create AWS RDS Database

All supported database engines for SonarQube can be found in the official documentation:

Server installation requirements & SonarQube

In our case, we have chosen the Aurora/PostgreSQL Compatible AWS RDS database.

Database settings

In the settings section, we need to set the username as sonarqube and choose "Manage master credentials in AWS Secrets Manager". Now we can reference the encryption key that we created earlier.

Database connectivity

In the connectivity section, we chose the VPC, subnet group and security group we created.

Additional Configuration

In the "Additional Configuration" section, we will create an initial database with the name sonar . Afterward, we can choose to encrypt the database.

Join our community of cloud security professionals. πŸ”

Subscribe to our newsletter

Setup Application Load Balancer πŸ‹οΈ

In this section, we'll cover how to set up an AWS Application Load Balancer (ALB) to distribute incoming traffic to the SonarQube instances running on AWS ECS. We'll go through the process of creating an ALB and configuring target groups and listeners.

Create Target Group

Before creating the application load balancer, we must set up a target group. In the case of ECS, we need to set the target type as IP addresses and choose the VPC created earlier.

We can remove all IP addresses from the second step and create the target group.

Create Application Load Balancer

When creating the Application Load Balancer choose the correct scheme configuration depending on if it should be internet-facing or internal. In our case, we want to be internet-facing. In the case of a corporate network, it should be internal.

Make sure to choose all public subnets for routing in the Mapping section. For the security group, you choose sonarqube-lb.

For the listeners, we chose the newly created target group.

Setup AWS EFS πŸ’Ύ

In this section, we'll discuss how to use Amazon Elastic File System (EFS) to share the SonarQube data directory between multiple ECS instances. We'll go through the process of creating an EFS file system, access points, and configuring the required directory structure for storing data.

SonarQube saves plugins and other data on the EFS. For a detailed explanation of EFS check out my previous article:

Unleashing the Power of AWS EFS with Access Points: What You Need to Know
Elastic File System (EFS) πŸ“, is a scalable and fully managed cloud file storage system. EFS offers shared file storage that enables several workloads to access the same data. AWS has launched Access Points to expand the functionality of EFS. With this feature, you define points of access to your EF…

First, we create a new EFS file system in the EFS console:

The new file systems will automatically attach the default security group of the VPC. That's why we adjusted it earlier.

Next, we create three access points to the file system. Make sure to have the same setup as on the screenshot. Otherwise, it can happen that SonarQube is not able to write anything on the file system.

Setup IAM roles πŸ‘₯

In this section, we'll cover the process of creating AWS Identity and Access Management (IAM) roles for the ECS task and task execution roles. We'll go through the steps of creating the roles and defining the necessary policies. This will allow the ECS tasks to interact securely with other AWS services, such as accessing the AWS Secrets Manager secret or KMS key.

Trust relationship

Both roles need to have a trust relationship with ECS:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "ecs-tasks.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

Create SonarQubeTaskRole

The task role, which we configure in the ECS task definition, doesn't need to have any policies attached.

SonarQubeTaskExecutionRole

The second role, we need to create is the task execution role which will be configured in the ECS task definition. Here we need a custom policy that allows access to the KMS key and Secrets Manager secret.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt",
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "arn:aws:kms:<region>:<accountID>:key/<keyID>",
                "arn:aws:secretsmanager:<region>:<accountID>:secret:rds!cluster-<clusterID>"
            ]
        }
    ]
}

Additionally, you need to attach the two AWS-managed roles AmazonEC2ContainerRegistryReadOnly and AWSOpsWorksCloudWatchLogs.

Upload SonarQube image to ECR πŸš€

In this section, we'll discuss using Amazon Elastic Container Registry (ECR) to store and manage the Docker images used for running the SonarQube instances on ECS. This will simplify the management of the Docker images, enabling versioning and secure access control.

To manage the lifecycle of the SonarQube docker image more easily, just check out my previous article:

Simplifying Pushing Docker Images to ECR With CodeBuild: The Ultimate How-To
Are you tired of manually pushing Docker images to Amazon Elastic Container Registry (ECR) every time you make changes to your images? It can be a tedious and time-consuming process that can slow down your development workflow. But fear not, because CodeBuild can simplify this process for you! πŸ’ͺ In…

Otherwise, we can also manually upload the image after creating a new ECR repository:

  1. docker pull sonarqube:latest
  2. aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <accountID>.dkr.ecr.us-east-2.amazonaws.com
  3. docker tag sonarqube:latest <accountID>.dkr.ecr.<region>.amazonaws.com/sonarqube:latest
  4. docker push <accountID>.dkr.ecr.<region>.amazonaws.com/sonarqube:latest

Create ECS Workload βš™οΈ

In this section, we will create an ECS cluster to run our containerized SonarQube instance. First, we create a task definition, then the ECS cluster, and finally the ECS service.

Create ECS task definition

Below you can find a CloudFormation template that will create the task definition. As we need to set some custom values for the ulimits we require a CloudFormation template. Please exchange the database information with the writer instance of your RDS cluster and the database password with the secrets manage secret that was created for you by RDS. You can retrieve this information from the AWS Secrets Manager console.

{
   "AWSTemplateFormatVersion":"2010-09-09",
   "Description":"Template Creates a ECS task definition for SonarQube",
   "Resources":{
      "taskdefinition":{
         "Type":"AWS::ECS::TaskDefinition",
         "Properties":{
            "ContainerDefinitions":[
               {
                  "name":"sonarqube",
                  "image":"<accountID>.dkr.ecr.<region>.amazonaws.com/sonarqube:latest",
                  "portMappings":[
                     {
                        "name":"sonarqube-9000-tcp",
                        "containerPort":9000,
                        "hostPort":9000,
                        "protocol":"tcp",
                        "appProtocol":"http"
                     }
                  ],
                  "essential":true,
                  "environment":[
                     {
                        "name":"SONAR_JDBC_URL",
                        "value":"jdbc:postgresql://sonarqube.cluster-bs1928dsnm1.<region>.rds.amazonaws.com:5432/sonar"
                     },
                     {
                        "name":"SONAR_JDBC_USERNAME",
                        "value":"sonarqube"
                     },
                     {
                        "name":"SONAR_SEARCH_JAVAADDITIONALOPTS",
                        "value":"-Dnode.store.allow_mmap=false,-Ddiscovery.type=single-node"
                     }
                  ],
                  "mountPoints":[
                     {
                        "sourceVolume":"sonarqube_data",
                        "containerPath":"/opt/sonarqube/data",
                        "readOnly":false
                     },
                     {
                        "sourceVolume":"sonarqube_extensions",
                        "containerPath":"/opt/sonarqube/extensions",
                        "readOnly":false
                     },
                     {
                        "sourceVolume":"sonarqube_logs",
                        "containerPath":"/opt/sonarqube/logs",
                        "readOnly":false
                     }
                  ],
                  "secrets":[
                     {
                        "name":"SONAR_JDBC_PASSWORD",
                        "valueFrom":"arn:aws:secretsmanager:<region>:<accountID>:secret:rds!cluster-19jaksl21-IGFr6V:password::"
                     }
                  ],
                  "logConfiguration":{
                     "logDriver":"awslogs",
                     "options":{
                        "awslogs-create-group":"true",
                        "awslogs-group":"/ecs/sonarqube",
                        "awslogs-region":"<region>",
                        "awslogs-stream-prefix":"ecs"
                     }
                  },
                  "ulimits":[
                     {
                        "name":"nofile",
                        "softLimit":65535,
                        "hardLimit":65535
                     }
                  ]
               }
            ],
            "taskRoleArn":"arn:aws:iam::<accountID>:role/SonarQubeTaskRole",
            "executionRoleArn":"arn:aws:iam::<accountID>:role/SonarQubeTaskExecutionRole",
            "networkMode":"awsvpc",
            "volumes":[
               {
                  "name":"sonarqube_logs",
                  "efsVolumeConfiguration":{
                     "fileSystemId":"fs-0128vs291284fn121",
                     "rootDirectory":"/",
                     "transitEncryption":"ENABLED",
                     "authorizationConfig":{
                        "accessPointId":"fsap-<accessPointID>",
                        "iam":"DISABLED"
                     }
                  }
               },
               {
                  "name":"sonarqube_extensions",
                  "efsVolumeConfiguration":{
                     "fileSystemId":"fs-0128vs291284fn121",
                     "rootDirectory":"/",
                     "transitEncryption":"ENABLED",
                     "authorizationConfig":{
                        "accessPointId":"fsap-<accessPointID>",
                        "iam":"DISABLED"
                     }
                  }
               },
               {
                  "name":"sonarqube_data",
                  "efsVolumeConfiguration":{
                     "fileSystemId":"fs-0128vs291284fn121",
                     "rootDirectory":"/",
                     "transitEncryption":"ENABLED",
                     "authorizationConfig":{
                        "accessPointId":"fsap-<accessPointID>",
                        "iam":"DISABLED"
                     }
                  }
               }
            ],
            "family":"sonarqube",
            "requiresCompatibilities":[
               "FARGATE"
            ],
            "cpu":"1024",
            "memory":"3072",
            "runtimePlatform":{
               "cpuArchitecture":"X86_64",
               "operatingSystemFamily":"LINUX"
            }
         }
      }
   }
}

If you are running into issues with password retrieval, you can check out my detailed setup article:

How To Manage Secrets In AWS ECS Using AWS Secret Manager: A Comprehensive Guide
Managing secrets in AWS ECS (Elastic Container Service) can be challenging, as you want to ensure that sensitive information like passwords, API keys, and database credentials are secure and only accessible to the applications that need them. AWS Secret Manager is a service that helps you manage sec…

Create ECS cluster

Next up, we're diving into setting up our ECS cluster. We'll go with AWS Fargate as our infrastructure choice.

Create ECS service

Next, we create a new service for the ECS cluster. For the lunch type, we choose FARGATE.

❗
The cluster setup of SonarQube is exclusive to the Data Center Edition.

In the Networking section, you need to choose the private subnets of your SonarQube VPC. Also, deactivate to assign a public IP. This way we can ensure that the ECS tasks are running in a private subnet only.

In the Load Balancing section, you need to reference the previously created ALB.

Connect to SonarQube πŸ”—

Now we can take the DNS name of the Application Load Balancer and connect to the SonarQube instance. πŸŽ‰

Share this post