Getting image vulnerabilities

This tutorial shows you how to use Container Analysis to scan an image stored in Container Registry for vulnerabilities and then list high severity vulnerabilities for the image.

Objectives

This tutorial covers the following steps:

  1. Build a Docker image with known vulnerabilities
  2. Push the image to your project's Container Registry
  3. Poll Container Analysis for completion of the initial scan and analysis of the image
  4. View vulnerabilities identified by Container Analysis
  5. List high severity vulnerabilities for the image

Costs

This tutorial uses billable components of Google Cloud Platform. See Pricing for details about pricing.

Use the pricing calculator to generate a cost estimate based on your projected usage. New GCP users might be eligible for a free trial.

Before you begin

To use Container Analysis, you must enable and set up Container Analysis and Container Registry.

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. In the GCP Console, go to the Manage resources page and select or create a project.

    Go to the Manage resources page

  3. Comprueba que la facturación esté habilitada en tu proyecto.

    Descubre cómo puedes habilitar la facturación

  4. Habilita las Container Registry and Container Scanning API necesarias.

    Habilita las API

  5. Install and initialize the Cloud SDK.
  6. Enable required IAM permissions.
    • To view occurrences, you must have the following permission:
      containeranalysis.notes.listOccurrences

      Alternately, you can grant the following predefined IAM role, which will automatically provide the necessary permissions:

      Container Analysis Occurrences Viewer

  7. Install Docker. If you're using a Linux-based operating system, such as Ubuntu or Debian, add your username to the docker group so that you can run Docker without using sudo:
    sudo usermod -a -G docker ${USER}

    You may need to restart your system after adding yourself to the docker group.

  8. Open Docker. To ensure that Docker is running, run the following Docker command, which returns the current time and date:
  9. docker run busybox date

Building a Docker image

For this tutorial, build the following Docker image so that you have an image to push to Container Registry. You will use a Python runtime with known vulnerabilities that Container Analysis will identify. This Docker image contains a small Python web app. The app uses the Flask web framework to serve a web page which displays the message "Hello, World!"

To create the Docker image:

  1. Create a directory to store the three Docker image files.

  2. In this directory, create three files: Dockerfile, requirements.txt, and app.py. See the examples below for what you'll need to put in the contents of each file:

Dockerfile

# The Dockerfile defines the image's environment
# Import Python runtime and set up working directory
# This specified runtime has known vulnerabilities so that vulnerability
# scanning can be tested.
FROM python:2.7-slim
WORKDIR /app
ADD . /app

# Install any necessary dependencies
RUN pip install -r requirements.txt

# Open port 80 for serving the webpage
EXPOSE 80

# Run app.py when the container launches
CMD ["python", "app.py"]

requirements.txt

# This file defines the image's dependencies
Flask

app.py

# The Docker image contains the following code
from flask import Flask
import os
import socket

app = Flask(__name__)

@app.route("/")
def hello():
    html = "<h3>Hello, World!</h3>"
    return html

if __name__ == "__main__":
  app.run(host='0.0.0.0', port=80)

To build the Docker image, run the following Docker command from the directory containing the image's files:

docker build -t vulnerabilities-tutorial-image .

You've now created a Docker image on your local machine.

Add the image to Container Registry

Configure docker to use the gcloud command-line tool as a credential helper

Before you can push or pull images, you must configure Docker to use the gcloud command-line tool to authenticate requests to Container Registry. To do so, run the following command (you are only required to do this once):

gcloud auth configure-docker

Tag the image with a registry name

Before you push the Docker image to Container Registry, you need to tag it with its registry name. Tagging the Docker image with a registry name configures the docker push command to push the image to a specific location. For this quickstart, the host location is gcr.io.

To tag the Docker image, run the following command:

docker tag vulnerabilities-tutorial-image gcr.io/[PROJECT-ID]/vulnerabilities-tutorial-image:tag1

where:

  • [PROJECT-ID] is your Google Cloud Platform Console project ID, which you need to add to your command
  • gcr.io is the hostname
  • vulnerabilities-tutorial-image is the name of the Docker image
  • tag1 is a tag you're adding to the Docker image. If you didn't specify a tag, Docker will apply the default tag latest.

You are now ready to push the image to Container Registry.

Push the image to Container Registry

Once docker has been configured to use gcloud as a credential helper, and the local image is tagged with the registry name, you can push it to Container Registry.

To push the Docker image, run the following command:

docker push gcr.io/[PROJECT-ID]/vulnerabilities-tutorial-image:tag1

where [PROJECT-ID] is your Google Cloud Platform Console project ID. If your project ID contains a colon (:), see Domain-scoped projects.

When you push an image to a new host location, the service creates the underlying storage bucket unique to your project. You can view images hosted by Container Registry via the GCP Console, or by visiting the image's registry name in your web browser: http://gcr.io/[PROJECT-ID]/vulnerabilities-tutorial-image.

Polling for initial scan and analysis results

Container Analysis automatically performs an initial scan and analysis of the image. It creates a discovery occurrence that includes information collected from that initial scan. Container Analysis also updates the discovery occurrence with the current status of the scan.

The following code sample polls for the current status of the initial scan so that you know when vulnerability information is available to view.

Java

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries . For more information, see the Container Registry Java API reference documentation .

import com.google.cloud.devtools.containeranalysis.v1beta1.GrafeasV1Beta1Client;
import com.google.containeranalysis.v1beta1.ProjectName;
import io.grafeas.v1beta1.Occurrence;
import io.grafeas.v1beta1.discovery.Discovered.AnalysisStatus;
import java.io.IOException;
import java.lang.InterruptedException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class PollDiscoveryOccurrenceFinished {
  // Repeatedly query the Container Analysis API for the latest discovery occurrence until it is
  // either in a terminal state, or the timeout value has been exceeded
  public static Occurrence pollDiscoveryOccurrenceFinished(String resourceUrl, String projectId,
      long timeoutSeconds) throws IOException, TimeoutException, InterruptedException {
    // String resourceUrl = "https://gcr.io/project/image@sha256:123";
    // String projectId = "my-project-id";
    // long timeoutSeconds = 30;
    final String projectName = ProjectName.format(projectId);
    long deadline = System.currentTimeMillis() + timeoutSeconds * 1000;

    // Initialize client that will be used to send requests. After completing all of your requests,
    // call the "close" method on the client to safely clean up any remaining background resources.
    GrafeasV1Beta1Client client = GrafeasV1Beta1Client.create();

    // find the discovery occurrence using a filter string
    Occurrence discoveryOccurrence = null;
    // vulbnerability discovery occurrences are always associated with the
    // PACKAGE_VULNERABILITY note in the "goog-analysis" GCP project
    String filter =  String.format("resourceUrl=\"%s\" AND noteProjectId=\"%s\" AND noteId=\"%s\"",
        resourceUrl, "goog-analysis",  "PACKAGE_VULNERABILITY");
    while (discoveryOccurrence == null) {
      for (Occurrence o : client.listOccurrences(projectName, filter).iterateAll()) {
        if (o.getDiscovered() != null) {
          // there should be only one valid discovery occurrence returned by the given filter
          discoveryOccurrence = o;
        }
      }
      TimeUnit.SECONDS.sleep(1);
      // check for timeout
      if (System.currentTimeMillis() > deadline) {
        throw new TimeoutException("discovery occurrence not found");
      }
    }

    // wait for discovery occurrence to enter a terminal state
    AnalysisStatus status = AnalysisStatus.PENDING;
    while (status != AnalysisStatus.FINISHED_SUCCESS
        && status != AnalysisStatus.FINISHED_FAILED
        && status != AnalysisStatus.FINISHED_UNSUPPORTED) {
      // update the occurrence state
      discoveryOccurrence = client.getOccurrence(discoveryOccurrence.getName());
      status = discoveryOccurrence.getDiscovered().getDiscovered().getAnalysisStatus();
      TimeUnit.SECONDS.sleep(1);
      // check for timeout
      if (System.currentTimeMillis() > deadline) {
        throw new TimeoutException("discovery occurrence not in terminal state");
      }
    }
    return discoveryOccurrence;
  }
}

Go

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries . For more information, see the Container Registry Go API reference documentation .

import (
	"context"
	"fmt"
	"time"

	containeranalysis "cloud.google.com/go/containeranalysis/apiv1beta1"
	discovery "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/discovery"
	grafeaspb "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/grafeas"
)

// pollDiscoveryOccurrenceFinished returns the discovery occurrence for a resource once it reaches a finished state.
func pollDiscoveryOccurrenceFinished(resourceURL, projectID string, timeout time.Duration) (*grafeaspb.Occurrence, error) {
	// resourceURL := fmt.Sprintf("https://gcr.io/my-project/my-image")
	// timeout := time.Duration(5) * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	client, err := containeranalysis.NewGrafeasV1Beta1Client(ctx)
	if err != nil {
		return nil, fmt.Errorf("NewGrafeasV1Beta1Client: %v", err)
	}
	defer client.Close()

	// ticker is used to poll once per second.
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	// Find the discovery occurrence using a filter string.
	var discoveryOccurrence *grafeaspb.Occurrence
	for discoveryOccurrence == nil {
		select {
		case <-ctx.Done():
			return nil, fmt.Errorf("timeout while retrieving discovery occurrence")
		case <-ticker.C:
			req := &grafeaspb.ListOccurrencesRequest{
				Parent: fmt.Sprintf("projects/%s", projectID),
				// Vulnerability discovery occurrences are always associated with the
				// PACKAGE_VULNERABILITY note in the "goog-analysis" GCP project.
				Filter: fmt.Sprintf(`resourceUrl=%q AND noteProjectId="goog-analysis" AND noteId="PACKAGE_VULNERABILITY"`, resourceURL),
			}
			it := client.ListOccurrences(ctx, req)
			// Only one occurrence should ever be returned by ListOccurrences
			// and the given filter.
			result, err := it.Next()
			if err != nil {
				return nil, fmt.Errorf("it.Next: %v", err)
			}
			if result.GetDiscovered() != nil {
				discoveryOccurrence = result
			}
		}
	}

	// Wait for the discovery occurrence to enter a terminal state.
	for {
		select {
		case <-ctx.Done():
			return nil, fmt.Errorf("timeout waiting for terminal state")
		case <-ticker.C:
			// Update the occurrence.
			req := &grafeaspb.GetOccurrenceRequest{Name: discoveryOccurrence.GetName()}
			updated, err := client.GetOccurrence(ctx, req)
			if err != nil {
				return nil, fmt.Errorf("GetOccurrence: %v", err)
			}
			switch updated.GetDiscovered().GetDiscovered().GetAnalysisStatus() {
			case discovery.Discovered_FINISHED_SUCCESS,
				discovery.Discovered_FINISHED_FAILED,
				discovery.Discovered_FINISHED_UNSUPPORTED:
				return discoveryOccurrence, nil
			}
		}
	}
}

When the status of the scan is FINISHED_SUCCESS, you are ready to view the list of vulnerabilities.

Listing all vulnerabilities associated with an image

Known vulnerabilities are stored as notes. Container Analysis creates an occurrence for each instance of a note that it finds during analysis of the image. As Container Analysis collects new information about vulnerabilities, it updates the list of occurrences automatically.

You can retrieve information about notes and occurrences using the GCP Console, gcloud command-line tool, or the Container Analysis API. Additionally, the API supports a number of filter expressions for retrieving specific information about notes or occurrences.

The following code sample retrieves vulnerabilities found for an image.

Java

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries . For more information, see the Container Registry Java API reference documentation .

import com.google.cloud.devtools.containeranalysis.v1beta1.GrafeasV1Beta1Client;
import com.google.containeranalysis.v1beta1.ProjectName;
import io.grafeas.v1beta1.Occurrence;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public class VulnerabilityOccurrencesForImage {
  // Retrieve a list of vulnerability occurrences assoviated with a resource
  public static List<Occurrence> findVulnerabilityOccurrencesForImage(String resourceUrl,
      String projectId) throws IOException {
    // String resourceUrl = "https://gcr.io/project/image@sha256:123";
    // String projectId = "my-project-id";
    final String projectName = ProjectName.format(projectId);
    String filterStr = String.format("kind=\"VULNERABILITY\" AND resourceUrl=\"%s\"", resourceUrl);

    // Initialize client that will be used to send requests. After completing all of your requests,
    // call the "close" method on the client to safely clean up any remaining background resources.
    GrafeasV1Beta1Client client = GrafeasV1Beta1Client.create();
    LinkedList<Occurrence> vulnerabilitylist = new LinkedList<Occurrence>();
    for (Occurrence o : client.listOccurrences(projectName, filterStr).iterateAll()) {
      vulnerabilitylist.add(o);
    }
    return vulnerabilitylist;
  }
}

Go

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries . For more information, see the Container Registry Go API reference documentation .

import (
	"context"
	"fmt"

	containeranalysis "cloud.google.com/go/containeranalysis/apiv1beta1"
	"google.golang.org/api/iterator"
	grafeaspb "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/grafeas"
)

// findVulnerabilityOccurrencesForImage retrieves all vulnerability Occurrences associated with a resource.
func findVulnerabilityOccurrencesForImage(resourceURL, projectID string) ([]*grafeaspb.Occurrence, error) {
	// resourceURL := fmt.Sprintf("https://gcr.io/my-project/my-image")
	ctx := context.Background()
	client, err := containeranalysis.NewGrafeasV1Beta1Client(ctx)
	if err != nil {
		return nil, fmt.Errorf("NewGrafeasV1Beta1Client: %v", err)
	}
	defer client.Close()

	req := &grafeaspb.ListOccurrencesRequest{
		Parent: fmt.Sprintf("projects/%s", projectID),
		Filter: fmt.Sprintf("resourceUrl = %q kind = %q", resourceURL, "VULNERABILITY"),
	}

	var occurrenceList []*grafeaspb.Occurrence
	it := client.ListOccurrences(ctx, req)
	for {
		occ, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("occurrence iteration error: %v", err)
		}
		occurrenceList = append(occurrenceList, occ)
	}

	return occurrenceList, nil
}

Listing high severity vulnerabilites

A severity level is associated with each vulnerability. Container Analysis uses severity level information provided by supported Linux distributions. If distribution-specific severity levels are unavailable, assigned levels are based on CVSS scores.

To prioritize plans to address vulnerabilities, you can refine your list of vulnerabilities to high severity ones only.

The following code sample retrieves a list of vulnerabilities for an image that is assigned high or critical severity.

Java

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries . For more information, see the Container Registry Java API reference documentation .

import com.google.cloud.devtools.containeranalysis.v1beta1.GrafeasV1Beta1Client;
import com.google.containeranalysis.v1beta1.ProjectName;
import io.grafeas.v1beta1.Occurrence;
import io.grafeas.v1beta1.vulnerability.Severity;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public class HighVulnerabilitiesForImage {
  // Retrieve a list of vulnerability occurrences with a severity level of 'HIGH' or greater
  public static List<Occurrence> findHighSeverityVulnerabilitiesForImage(String resourceUrl,
      String projectId) throws IOException {
    // String resourceUrl = "https://gcr.io/project/image@sha256:123";
    // String projectId = "my-project-id";
    final String projectName = ProjectName.format(projectId);
    String filterStr = String.format("kind=\"VULNERABILITY\" AND resourceUrl=\"%s\"", resourceUrl);

    // Initialize client that will be used to send requests. After completing all of your requests,
    // call the "close" method on the client to safely clean up any remaining background resources.
    GrafeasV1Beta1Client client = GrafeasV1Beta1Client.create();
    LinkedList<Occurrence> vulnerabilitylist = new LinkedList<Occurrence>();
    for (Occurrence o : client.listOccurrences(projectName, filterStr).iterateAll()) {
      Severity severity = o.getVulnerability().getSeverity();
      if (severity == Severity.HIGH || severity == Severity.CRITICAL) {
        vulnerabilitylist.add(o);
      }
    }
    return vulnerabilitylist;
  }
}

Go

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries . For more information, see the Container Registry Go API reference documentation .

import (
	"context"
	"fmt"

	containeranalysis "cloud.google.com/go/containeranalysis/apiv1beta1"
	"google.golang.org/api/iterator"
	grafeaspb "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/grafeas"
	vulnerability "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1beta1/vulnerability"
)

// findHighSeverityVulnerabilitiesForImage retrieves a list of only high vulnerability occurrences associated with a resource.
func findHighSeverityVulnerabilitiesForImage(resourceURL, projectID string) ([]*grafeaspb.Occurrence, error) {
	// resourceURL := fmt.Sprintf("https://gcr.io/my-project/my-image")
	ctx := context.Background()
	client, err := containeranalysis.NewGrafeasV1Beta1Client(ctx)
	if err != nil {
		return nil, fmt.Errorf("NewGrafeasV1Beta1Client: %v", err)
	}
	defer client.Close()

	req := &grafeaspb.ListOccurrencesRequest{
		Parent: fmt.Sprintf("projects/%s", projectID),
		Filter: fmt.Sprintf("resourceUrl = %q kind = %q", resourceURL, "VULNERABILITY"),
	}

	var occurrenceList []*grafeaspb.Occurrence
	it := client.ListOccurrences(ctx, req)
	for {
		occ, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("occurrence iteration error: %v", err)
		}

		severityLevel := occ.GetVulnerability().GetSeverity()
		if severityLevel == vulnerability.Severity_HIGH || severityLevel == vulnerability.Severity_CRITICAL {
			occurrenceList = append(occurrenceList, occ)
		}
	}

	return occurrenceList, nil
}

Cleaning up

To avoid incurring charges to your Google Cloud Platform account for the resources used in this tutorial:

Deleting the image

If you only want to remove the Docker image you added, run the following command to delete the Docker image from Container Registry.

gcloud container images delete gcr.io/[PROJECT-ID]/vulnerabilities-tutorial-image:tag1 --force-delete-tags

where:[PROJECT-ID] is your Google Cloud Platform Console project ID. If your project ID contains a colon (:), see Domain-scoped projects.

Deleting the project

The easiest way to eliminate billing is to delete the project that you created for the tutorial.

To delete the project:

  1. In the GCP Console, go to the Projects page.

    Go to the Projects page

  2. In the project list, select the project you want to delete and click Delete .
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

What's next

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...