WEBVTT

00:00:01.440 --> 00:00:04.590
Before we dive in, I just want to say that as a blue teamer,

00:00:04.590 --> 00:00:08.490
having a high‑level understanding of automation is crucial.

00:00:08.500 --> 00:00:10.740
If security alerting is done correctly,

00:00:10.770 --> 00:00:13.530
there's usually going to be automation involved.

00:00:13.530 --> 00:00:15.870
For you to fully investigate alerts,

00:00:15.870 --> 00:00:19.560
it is pivotal to understand the context in which they are generated.

00:00:20.040 --> 00:00:23.250
We are going to continue our automation journey within the script.

00:00:24.040 --> 00:00:26.440
I'm not going to rehash the summary because we just saw

00:00:26.440 --> 00:00:28.260
it within the previous workflow file.

00:00:29.040 --> 00:00:31.460
Much of the assumptions have already been covered,

00:00:31.840 --> 00:00:35.310
but if needed, please pause the video and look over them again.

00:00:35.320 --> 00:00:38.490
One important thing to highlight is that this script

00:00:38.590 --> 00:00:41.140
expects the following path convention,

00:00:41.140 --> 00:00:44.840
and this is shared with the previous docker‑scan script that

00:00:44.840 --> 00:00:47.360
we saw to fuel the pull request process.

00:00:49.440 --> 00:00:50.760
Now we see the inputs.

00:00:51.240 --> 00:00:54.250
Docker tags are a way to version Docker images.

00:00:54.250 --> 00:00:58.100
In this demo, we build Docker images with the "latest" tag,

00:00:58.100 --> 00:00:59.940
and similar, the DOCKERHUB_USER.

00:00:59.940 --> 00:01:03.520
In this demo, it's simply zachroofsec.

00:01:03.520 --> 00:01:07.260
The functions, we'll come back to this a little bit later.

00:01:09.440 --> 00:01:10.780
All right, the main logic.

00:01:10.780 --> 00:01:13.060
First we do some miscellaneous Git setup.

00:01:13.840 --> 00:01:16.660
Then we start to iterate through the Docker configurations.

00:01:17.140 --> 00:01:19.860
This uses the path assumptions that we just went over.

00:01:20.540 --> 00:01:23.960
First, we get the absolute path for the Docker configurations.

00:01:25.140 --> 00:01:28.950
Then we get the Docker Registry repo name from our path convention.

00:01:29.440 --> 00:01:31.860
Then we start generating some Docker image names.

00:01:34.940 --> 00:01:38.160
We download the remote image within the Docker Registry

00:01:38.170 --> 00:01:40.100
during the integrity checking process.

00:01:40.100 --> 00:01:42.440
We'll explore this more in a little bit.

00:01:42.440 --> 00:01:44.750
Here, we build the Docker image.

00:01:45.340 --> 00:01:49.450
Let's do a high‑level overview of the tampering checks within this script.

00:01:49.840 --> 00:01:52.740
If an attacker changes the registry's Docker image,

00:01:52.750 --> 00:01:55.230
they subvert all preexisting controls.

00:01:55.250 --> 00:01:59.030
This includes the Trivy scan, the pull request approval process,

00:01:59.040 --> 00:02:00.060
pretty much everything.

00:02:00.540 --> 00:02:04.560
We need to ensure that a malicious Docker image has not been placed

00:02:04.570 --> 00:02:08.560
within the Docker Registry. For additional tampering checks, you should

00:02:08.560 --> 00:02:10.820
use something called Docker Content Trust.

00:02:10.830 --> 00:02:13.650
However, that's outside of the scope of this tutorial.

00:02:14.640 --> 00:02:16.760
Let's start with our tampering check logic.

00:02:17.640 --> 00:02:21.860
We first need to deduce, is this the first build of the Docker image?

00:02:22.440 --> 00:02:25.510
If so, we won't check for image tampering because there

00:02:25.510 --> 00:02:27.360
isn't an image in the remote registry.

00:02:29.340 --> 00:02:33.300
So let's assume this is the first run. We use Google's

00:02:33.300 --> 00:02:37.480
container‑diff tool to generate the Docker image signature. On

00:02:37.480 --> 00:02:41.040
subsequent image builds, we'll validate that the remote Docker

00:02:41.040 --> 00:02:43.460
image signature matches the signature.

00:02:44.340 --> 00:02:46.650
This is an example of what the signature looks like.

00:02:47.140 --> 00:02:50.350
Let's go to the function generate_signature_and_upload_image.

00:02:52.140 --> 00:02:55.160
As we just stated, we generate the Docker image signature.

00:02:55.470 --> 00:02:58.860
Then we tag the Docker image and push it to the registry.

00:03:01.740 --> 00:03:05.060
Now, we need to commit the signature into the main branch.

00:03:06.240 --> 00:03:10.810
Let's check out that code. We are just doing some very simple git

00:03:10.810 --> 00:03:17.470
operations here. Obviously, this is the first time building this Docker

00:03:17.470 --> 00:03:21.240
image, so we skip the integrity checks, and then we just continue on to

00:03:21.240 --> 00:03:23.440
the next set of Docker configurations.

00:03:23.510 --> 00:03:26.650
Remember, we are within a for loop that is iterating through all of

00:03:26.650 --> 00:03:29.350
our Docker configurations and building those images.

00:03:32.040 --> 00:03:33.860
This point in the execution path,

00:03:34.100 --> 00:03:37.160
this is not the first time this Docker image has been built.

00:03:37.540 --> 00:03:39.850
Thus, we need to check for image tampering.

00:03:40.540 --> 00:03:43.860
First we download the remote version of the Docker image.

00:03:45.240 --> 00:03:49.820
Then we get the signature of the remote Docker image. Then we get the

00:03:49.820 --> 00:03:52.560
signature of the Docker image that was previously built.

00:03:53.240 --> 00:03:56.060
Remember, we can get this within the Git repository.

00:03:57.700 --> 00:04:01.260
The remote signature does not match the previous build signature.

00:04:01.740 --> 00:04:05.160
Docker image tampering might be present within Docker Hub.

00:04:05.840 --> 00:04:08.740
So after that we just try to do some analysis that

00:04:08.740 --> 00:04:11.300
might help within an investigation.

00:04:11.380 --> 00:04:14.750
First, we ensure that Trivy does not scan the cached image.

00:04:15.140 --> 00:04:18.100
For simplicity, we're going to use our previous scan settings.

00:04:18.140 --> 00:04:18.940
In other words,

00:04:18.950 --> 00:04:24.450
we're going to copy the same severity values and the ignored unfixed flag.

00:04:24.460 --> 00:04:27.540
We are doing this so the output isn't as verbose.

00:04:27.560 --> 00:04:30.990
However, if you are doing this within a production environment,

00:04:31.000 --> 00:04:34.320
my suggestion would be to remove these flags. For an

00:04:34.320 --> 00:04:37.530
investigation, you would want to see all output and all

00:04:37.530 --> 00:04:39.350
vulnerabilities that are within the image.

00:04:41.340 --> 00:04:45.740
Next, we use Google's container‑diff tool to look at the differences

00:04:45.740 --> 00:04:48.760
between the Docker images. More on this later.

00:04:49.140 --> 00:04:52.430
At this point, we're not going to upload the new Docker image.

00:04:52.440 --> 00:04:55.310
We're just going to keep everything the way it is, exit

00:04:55.310 --> 00:04:57.790
out of this script, and alert our SIEM.

00:04:57.870 --> 00:05:00.750
A forensic investigation really needs to occur.

00:05:02.940 --> 00:05:05.160
If this portion of the script is executed,

00:05:05.170 --> 00:05:10.160
then woo‑hoo! the signatures do match and no Docker image tampering is present.

00:05:10.940 --> 00:05:11.630
At that point,

00:05:11.640 --> 00:05:15.100
we simply generate the signature and upload the Docker image and

00:05:15.100 --> 00:05:17.260
then commit everything into source control.
