# DevSecOps and AI

## Introduction

DevSecOps is the practice of integrating security at every stage of the the software development lifecycle. In this series of posts, I will be exploring AI from a DevSecOps lens, focusing on how AI could be used to improve efficiency, but also how AI-Generated code introduces new challenges. This post will outline the steps I took to set up a self-hosted GitLab server and integrate a Semgrep scan into the CI/CD pipeline.

## Setting up GitLab

I chose to use GitLab because I could self-host it, and because it offers a pretty robust CI/CD solution.

This is the `docker-compose.yml` file I used to set up the GitLab server and a GitLab Runner on my Linux server:

```yaml
services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    container_name: gitlab
    restart: always
    hostname: 'localhost'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://localhost:8929'
        gitlab_rails['gitlab_shell_ssh_port'] = 2424
        nginx['enable'] = true
        nginx['redirect_http_to_https'] = true
        nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab.crt"
        nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/    gitlab.key"
        gitlab_rails['registry_enabled'] = true
        registry_external_url 'https://localhost:5050'
        registry_nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab.crt"
        registry_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab.key"
    ports:
      - '8929:8929'
      - '2424:22'
      - '5050:5050'
     volumes:
      - gitlab_config:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
      - registry_data:/var/opt/gitlab/gitlab-rails/shared/registry
    shm_size: '256m'

  gitlab-runner:
    image: gitlab/gitlab-runner:latest
    container_name: gitlab-runner
    restart: always
    depends_on:
     - gitlab
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./gitlab-runner/config:/etc/gitlab-runner
    

volumes:
  gitlab_config:
  gitlab_logs:
  gitlab_data:
  registry_data:
```

### Explanation of the docker-compose.yml file

`GITLAB_OMNIBUS_CONFIG`:

*   configures the URL to access the GitLab server
    
*   defines the port used for Git SSH operations
    
*   turns on GitLab's built-in web server `nginx`, forces secure HTTPS connections instead of regular HTTP, and points to SSL security certificates (see "**Generating a self-signed SSL certificate**" below).
    
*   turns on the Container Registry (allowing you to store Docker images inside your GitLab), assigns it a web address and secures it with the same SSL certificates.
    

`ports` connects ports from the host to ports inside the container.

`volumes` saves important data outside the container so you don't lose your work if the container updates or stops. It maps named Docker volumes to internal folders for configuration, logs, application data, and container registry files.

`shm_size: '256m'` allocates 256 megabytes of shared memory (`/dev/shm`), which GitLab recommends to help its background worker run smoothly without running out of memory.

Run `docker compose up -d` in the directory containing the docker compose YAML file to create and start the Gitlab container. The first time you access it, you will need the initial password, which can be accessed via `docker compose exec -t gitlab cat /etc/gitlab/initial_root_password`.

## Generating a Self-Signed SSL certificate

Because I was hosting the Gitlab server on my local network, I chose to use self-signed certificates. Alternatively, for servers connected to the internet with a domain pointing to its public IP, Gitlab can automatically obtain and renew SSL certificates from [Let's Encrypt.](https://docs.gitlab.com/omnibus/settings/ssl/)

1.  Create a folder in the `gitlab_config` directory for the SSL keys.
    
    ```shell
    sudo mkdir -p ./gitlab_config/_data/ssl
    ```
    
2.  Generate an SSL key pair with `openssl` and store it inside the `gitlab_config` directory.
    

```shell
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout ./gitlab_config/_data/ssl/localhost.key   
-out ./gitlab_config/_data/ssl/localhost.crt   -subj "/CN=localhost"
```

3.  To allow GitLab runners to verify the certificate, copy the `localhost.key` and `localhost.crt` files to the runner configuration directory `./gitlab-runner/config`
    
4.  To access the container registry on port 5050, we will also need to allow the Docker daemon to trust the self-signed SSL certificate.
    
    1.  Create the certificate directory for the registry: `sudo mkdir -p /etc/docker/certs.d/localhost:5050`
        
    2.  Copy the `localhost.crt` file into the directory and rename it `ca.crt` (Docker expects this name)
        
    3.  Restart the Docker service: `sudo systemctl restart docker`
        

### Registering a runner

To test out SAST scanning, I used the [OWASP crAPI](https://github.com/OWASP/crAPI) repo, which is an intentionally vulnerable API based application created by OWASP to help users learn about important security risks in APIs.

Once you've imported the repo into GitLab, you need to register a runner with the following steps:

1.  In the Project side bar, go to Settings > CI/CD > Runners
    
    ![](https://cdn.hashnode.com/uploads/covers/6a8fbc1e2d6d3b8cfd4be8a2/c7c15dd7-dcfd-489c-8f89-cafda07e109c.png align="center")
    
2.  Create a new runner to obtain an authentication token, then run the command below which tells Docker to register a runner inside the "gitlab-runner" container that we created in the `docker-compose.yml` file.
    
    ```shell
    docker exec -it gitlab-runner gitlab-runner register --url "http://<YOUR_GITLAB_IP_OR_DOMAIN>" --token "YOUR_TOKEN_HERE"
    ```
    
3.  When prompted, choose `docker` as the executor and `alpine:latest` as the default docker image.
    

### Integrating Semgrep scanning into the pipeline

To run a Semgrep SAST scan on the repo, we need to add it to the `.gitlab-ci.yml` file

```yaml
stages:
  - scan

semgrep-sast:
  stage: scan
  image: semgrep/semgrep:latest
  script:
    - semgrep scan --config p/ci . --gitlab-sast --gitlab-sast-output=gl-sast-report.json  
  artifacts:
    when: always
    reports:
      sast: gl-sast-report.json # GitLab formatted report
    expire_in: 30 days
```

At the moment, there is only a scan stage defined for the pipeline, which contains the job `semgrep-sast` and uses the `semgrep/semgrep:latest` image inside an isolated container to perform the scan. The `script` runs the Semgrep scanner across the current directory (`.`) using the `ci` rule set, formats the output specifically for GitLab (`--gitlab-sast`), and saves the results into a file named `gl-sast-report.json`.

According to the Semgrep [documentation](https://semgrep.dev/p/ci), the `ci` ruleset is intended to produce low false positives, and is safe for use in CI/CD pipelines. We will look into rule sets and customisation later.

Once the `.gitlab-ci.yml` file is created, you can commit the changes and the runner should pick up the job. Below is the log output for the completed job, and we can see that an artifact containing the report was generated.

![](https://cdn.hashnode.com/uploads/covers/6a8fbc1e2d6d3b8cfd4be8a2/f1f635b4-3a49-4227-9ca5-f223478beaa8.png align="center")

### Next steps

Now that my initial setup is complete, in my next post I will be learning about threat vectors introduced by AI-Generated code and how SAST tools like Semgrep can be used to identify these security flaws.
