In the previous post, I discussed several anti-patterns around the operation of a continuous integration (CI) system. Even if these are taken into consideration, the CI is not an isolated system; it has to interact with other systems to perform its functions. This post will discuss some additional anti-patterns related to how CI systems are often used.
I classify the points discussed here broadly under “build integrity”. The outputs of a CI system are build artifacts. Whether it is a Docker container, JAR, other executable, or an archive, it should be every team’s goal to ensure that the artifact that gets built is the same one that gets deployed. In this post are several configuration and operational missteps that I have seen in the past that could lead to compromising build integrity.
Artifact repository not using TLS
Once an artifact it built, it needs to be stored somewhere for later use - an artifact repository. Often the storage is on a different system, meaning that the artifacts need to be transferred over the network. When uploads to the artifact repository do not use TLS, an active network attacker can modify the upload in transit. The modified version would then be deployed since the modified version is stored in the repository.
Further, if the artifact repository requires authentication but does not use TLS, repo credentials can be sniffed by a passive network attacker and later used to upload modified artifacts. No build integrity for you!
Just like the CI system needs to use TLS, so does the artifact repository.
Artifact repository not using least privilege
Similar to limiting access to the CI system itself based on least privilege, access to the artifact repository should be limited. Often artifact repositories are behind a perimeter firewall so developers feel that the artifact repository can have a lower security posture. Unfortunately, if the artifact repository allows unauthenticated uploads, there is no guarantee that the artifact that gets deployed is from the CI system. Malicious attackers aside, even developers could abuse this out of convenience by manually uploading a local build to deploy in development, staging, or production instead of going through the CI system. Only the CI system should have access to upload artifacts that get deployed. If developers need a convenient repo, set up a separate playground repo that they have write access to.
Not having audit logs
Teams should be able to trace the origin of any artifact deployed in production back through the promotion process to the artifact repo and the CI build. The integrity of the artifact should be verified at each step. To facilitate this tracing, both the build logs and the repo logs should at the very least contain the artifact hash and details of who uploaded it.
Insecure repository credentials
Use a password manager to generate strong passwords. Do not use leetspeak or other human generated passwords masquerading as strong. Obligatory xkcd: https://www.xkcd.com/936/
Widely distributed repository credentials
If the repository credentials are widely distributed, it is much easier for an attacker to gain access to them and upload a modified artifact. Bypassing the CI system entirely with this method is a very easy way to undermine build integrity. Access to artifact repository credentials should be limited based on least privilege.
One of the main benefits of a CI system is having consistently built artifacts. Unfortunately, there are steps beyond simply having a CI system that development teams need to be aware of. Failure to consider the impact of how the CI system interacts with other systems can compromise build integrity, often by allowing attackers to bypass the CI system. Development teams should strongly consider the security impact of their usage of CI and its related sytems.