Why is peer approval critical to DevSecOps success?

Blog banner image


DevSecOps tooling can be bypassed and ignored if proper development processes are not enforced, and this is where peer approvals come in



When we think of DevSecOps, we often immediately think of the products that identify vulnerabilities in the application code and infrastructure and “shift left” security. Tools such as SCA, SAST, DAST, IAST and RASP.

We may also think of security gating, where we detect vulnerabilities early in the development cycle and block the vulnerable code from being merged into the production application. The reality of course is that this ideal is rarely ever realised, and almost never recommended.

DevSecOps is much more effective when we don’t apply security gates

DevSecOps is much more effective when we educate developers, and encourage secure development practices, without blocking the flow of development. When implemented well, developers will produce code and DevSecOps tooling will provide them with the suggestions they need to spot vulnerable code and address it immediately. To do this, we need an opportunity to present these vulnerability detections to the developer, and there are two great opportunities to do this.

SAST IDE integration

Some tools integrate directly into the developer’s IDE, providing the quickest feedback and “shifting left” as far as we can possibly get. This immediate feedback ensures that the developer can fix their vulnerable code whilst writing it.

There is a big drawback to this technology too. If the integration isn’t running, then the code isn’t being scanned. We need to enforce code scanning, and for this we need to scan via the Source Code Management tool (SCM) such as Github.

DevSecOps tooling during Pull and Merge Requests

When the developer has completed a unit of work, which is a deliberately flexible measurement but which typically occurs multiple times per day, they will “commit” the work to Source Control and push it to an SCM product. In some cases, this SCM destination may have only one version of the code but ideally it should force developers to create “branches” of the source code to store their changes separately from everyone elses changes.

When the developer has finished the feature or bug fix, they need to merge their changes into the main branch for the application source code. This is most commonly called a Pull or Merge Request and is done through the SCM web interface. This process is a great place to run DevSecOps tooling as we can prevent this Pull Request from being merged until the tooling has completed running, and it provides a comment thread where we can add our DevSecOps tooling findings.

Importantly, we can ensure these DevSecOps tools run every time and even record the output, but we also need to make sure that the results from these tools are understood and utilised.

What are Peer Approvals?

Peer approvals are simply where another developer is required to approve another developer’s code change. This simple quality gate can ensure that the code change is of an acceptable quality, follows the organisations guidelines, and meets the aim of the change. It can also provide a fantastic opportunity for peers to review and discuss the findings raised by any DevSecOps tooling.

Of course, peer reviews also hold developers to account and ensure poor-quality code does not enter the codebase.

Peer approvals allow us to introduce accountability and visibility to a developer’s work, and any DevSecOps tooling involved in this step will naturally become topics for discussion amongst peers. If we don’t enforce peer approvals, it is much easier for DevSecOps tool output to go unread, and for code quality to drop during periods of pressure.

Of course, not all peer approvals are equal and peer approvals should:

  • Be taken seriously It’s easy for a culture to develop where peer approvals are granted without the “peer” actually reviewing the code. To combat this you should ensure the importance of proper peer approval is communicated and that reasonable code changes are submitted for review. It is much better to perform more frequent but smaller peer reviews, rather than huge peer reviews following weeks of work.
  • Encourage communication Peer reviews are a great way for developers to learn optimisations and best practices from their colleagues, but this opportunity can be lost if politics or time pressures get in the way. It’s not recommended to force peer reviews from management or team leaders, instead we should entrust any peer to perform the review.
  • Allow the security team to get involved If we have DevSecOps tooling commenting on these Pull or Merge requests, we are very likely going to require occasional support from the security or appsec teams. This is great and it should be encouraged! It can be difficult to fully grasp how a security concept can lead to abuse, but that is much easier for the developer to understand when looking at their own code.

We need to enforce peer approvals!

Peer approvals can be “opt-in”, but in reality this means that they will be used fleetingly and not used when it’s convenient or if a developer knows their code would not pass scrutiny. Fortunately, the major SCM providers include the ability to apply “branch protection rules” which can enforce peer approvals prior to code being merged into the default branch. This is typically applied per “repository” but can be automated via APIs or IaC tools like terraform.

Identifying repos on Github which don’t enforce peer reviews

import requests
from requests.auth import HTTPBasicAuth

# Replace these variables with your GitHub organization and personal access token
GITHUB_ORG = "your-organization"
ACCESS_TOKEN = "your-access-token"
USERNAME = "your-username"

def get_repos(org):
    url = f"https://api.github.com/orgs/{org}/repos"
    response = requests.get(url, auth=HTTPBasicAuth(USERNAME, ACCESS_TOKEN))
    response.raise_for_status()
    return response.json()

def get_branch_protection(owner, repo, branch):
    url = f"https://api.github.com/repos/{owner}/{repo}/branches/{branch}/protection"
    response = requests.get(url, auth=HTTPBasicAuth(USERNAME, ACCESS_TOKEN))
    if response.status_code == 404:
        return None  # No branch protection found
    response.raise_for_status()
    return response.json()

def check_required_reviews(protection):
    if protection is None:
        return False
    required_reviews = protection.get('required_pull_request_reviews')
    if not required_reviews:
        return False
    return required_reviews.get('required_approving_review_count', 0) >= 1

def main():
    repos = get_repos(GITHUB_ORG)
    repos_without_reviews = []

    for repo in repos:
        repo_name = repo['name']
        default_branch = repo['default_branch']
        print(f"Checking repository: {repo_name} (default branch: {default_branch})")
        protection = get_branch_protection(GITHUB_ORG, repo_name, default_branch)
        if not check_required_reviews(protection):
            repos_without_reviews.append(repo_name)

    if repos_without_reviews:
        print("Repositories without enforced peer review:")
        for repo in repos_without_reviews:
            print(f" - {repo}")
    else:
        print("All repositories enforce at least one peer review.")

if __name__ == "__main__":
    main()

Identifying repos on Gitlab which don’t enforce peer reviews

import requests

# Replace these variables with your GitLab group and personal access token
GITLAB_GROUP_ID = "your-group-id"
ACCESS_TOKEN = "your-access-token"

def get_headers():
    return {
        "Private-Token": ACCESS_TOKEN
    }

def get_repos(group_id):
    url = f"https://gitlab.com/api/v4/groups/{group_id}/projects"
    response = requests.get(url, headers=get_headers())
    response.raise_for_status()
    return response.json()

def get_project_branch(project_id, branch_name):
    url = f"https://gitlab.com/api/v4/projects/{project_id}/protected_branches/{branch_name}"
    response = requests.get(url, headers=get_headers())
    if response.status_code == 404:
        return None  # No branch protection found
    response.raise_for_status()
    return response.json()

def check_required_reviews(protection):
    if protection is None:
        return False
    approvals_required = protection.get('approvals_before_merge', 0)
    return approvals_required >= 1

def main():
    repos = get_repos(GITLAB_GROUP_ID)
    repos_without_reviews = []

    for repo in repos:
        repo_name = repo['name']
        repo_id = repo['id']
        default_branch = repo['default_branch']
        print(f"Checking repository: {repo_name} (default branch: {default_branch})")
        protection = get_project_branch(repo_id, default_branch)
        if not check_required_reviews(protection):
            repos_without_reviews.append(repo_name)

    if repos_without_reviews:
        print("Repositories without enforced peer review:")
        for repo in repos_without_reviews:
            print(f" - {repo}")
    else:
        print("All repositories enforce at least one peer review.")

if __name__ == "__main__":
    main()
Conclusion

A well-implemented DevSecOps program will require effective use of peer approvals to drive conversation and validate code quality.

When done correctly, we provide accurate advice and guidance directly to the development teams before vulnerable code ever gets to production. This advice is then discussed between peers during the peer approval process, and ultimately drives education and conversation.

Now it doesn’t matter if the DevSecOps tools identify false positives because they still promote conversation, but importantly they aren’t preventing the flow of work!

We provide expert DevSecOps consulting and gap analysis services.

For more information, email us at [email protected] or call us on 0161 660 3545

Author

Simon Gurney

- CTO -

Simon is one of the Punk Security Directors and has over 17 years experience working within IT, primarily focused on automation and InfoSec.

read more