← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pappacena/launchpad-buildd:security-manifest into launchpad-buildd:master

 

Thiago F. Pappacena has proposed merging ~pappacena/launchpad-buildd:security-manifest into launchpad-buildd:master.

Commit message:
Including manifest file in resulting OCI image.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~pappacena/launchpad-buildd/+git/launchpad-buildd/+merge/392400
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad-buildd:security-manifest into launchpad-buildd:master.
diff --git a/lpbuildd/target/build_oci.py b/lpbuildd/target/build_oci.py
index af56671..448bbdb 100644
--- a/lpbuildd/target/build_oci.py
+++ b/lpbuildd/target/build_oci.py
@@ -6,6 +6,7 @@ from __future__ import print_function
 __metaclass__ = type
 
 from collections import OrderedDict
+import json
 import logging
 import os.path
 import sys
@@ -53,6 +54,7 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
         super(BuildOCI, self).__init__(args, parser)
         self.bin = os.path.dirname(sys.argv[0])
         self.buildd_path = os.path.join("/home/buildd", self.args.name)
+        self.security_manifest_target_path = "/usr/share/rocks/manifest.json"
 
     def _add_docker_engine_proxy_settings(self):
         """Add systemd file for docker proxy settings."""
@@ -119,6 +121,56 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
         env = self.build_proxy_environment(proxy_url=self.args.proxy_url)
         self.vcs_fetch(self.args.name, cwd="/home/buildd", env=env)
 
+    def createSecurityManifest(self):
+        """Generates the security manifest file, returning the tmp file name
+        where it is stored in the backend.
+        """
+        content = {
+            "manifest-version": "1"
+        }
+        local_filename = tempfile.mktemp()
+        destination = "/tmp/security-manifest.json"
+        with open(local_filename, 'w') as fd:
+            json.dump(content, fd, indent=2)
+        self.backend.copy_in(local_filename, destination)
+        return destination
+
+    def startImageContainer(self):
+        """Starts a container from the built image without actually running
+        the image's command."""
+        self.run_build_command([
+            "docker", "run", "-d", "--name", self.args.name, self.args.name,
+            "sleep", "infinity"])
+
+    def stopImageContainer(self):
+        self.run_build_command(["docker", "stop", self.args.name])
+
+    def removeImageContainer(self):
+        self.run_build_command(["docker", "rm", self.args.name])
+
+    def commitImage(self):
+        """Commits the tmp container, overriding the originally built image."""
+        self.run_build_command([
+            "docker", "commit", self.args.name, self.args.name])
+
+    def addFileToImageContainer(self, src, dst):
+        """Copy a file in the local filesystem into the image container."""
+        dir_path = os.path.dirname(dst)
+        # Ensure the destination directory exists.
+        self.run_build_command(
+            ["docker", "exec", self.args.name, "mkdir", "-p", dir_path])
+        self.run_build_command(
+            ["docker", "cp", src, "%s:%s" % (self.args.name, dst)])
+
+    def addSecurityManifest(self):
+        self.startImageContainer()
+        manifest_filename = self.createSecurityManifest()
+        self.addFileToImageContainer(
+            manifest_filename, self.security_manifest_target_path)
+        self.stopImageContainer()
+        self.commitImage()
+        self.removeImageContainer()
+
     def build(self):
         logger.info("Running build phase...")
         args = ["docker", "build", "--no-cache"]
@@ -143,6 +195,7 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
         self._check_path_escape(build_context_path)
         args.append(build_context_path)
         self.run_build_command(args)
+        self.addSecurityManifest()
 
     def run(self):
         try:
diff --git a/lpbuildd/target/tests/test_build_oci.py b/lpbuildd/target/tests/test_build_oci.py
index e58344f..106bdf2 100644
--- a/lpbuildd/target/tests/test_build_oci.py
+++ b/lpbuildd/target/tests/test_build_oci.py
@@ -290,6 +290,31 @@ class TestBuildOCI(TestCase):
                 cwd="/home/buildd/test-image", **env),
             ]))
 
+    def assertRanPostBuildCommands(self, build_oci):
+        self.assertThat(build_oci.backend.run.calls[1:], MatchesListwise([
+            RanBuildCommand(
+                ['docker', 'run', '-d', '--name', 'test-image', 'test-image',
+                 'sleep', 'infinity'],
+                cwd="/home/buildd/test-image"),
+            RanBuildCommand(
+                ['docker', 'exec', 'test-image', 'mkdir', '-p',
+                 '/usr/share/rocks'],
+                cwd="/home/buildd/test-image"),
+            RanBuildCommand(
+                ['docker', 'cp', '/tmp/security-manifest.json',
+                 'test-image:/usr/share/rocks/manifest.json'],
+                cwd="/home/buildd/test-image"),
+            RanBuildCommand(
+                ['docker', 'stop', 'test-image'],
+                cwd="/home/buildd/test-image"),
+            RanBuildCommand(
+                ['docker', 'commit', 'test-image', 'test-image'],
+                cwd="/home/buildd/test-image"),
+            RanBuildCommand(
+                ['docker', 'rm', 'test-image'],
+                cwd="/home/buildd/test-image"),
+        ]))
+
     def test_build(self):
         args = [
             "build-oci",
@@ -299,12 +324,11 @@ class TestBuildOCI(TestCase):
         build_oci = parse_args(args=args).operation
         build_oci.backend.add_dir('/build/test-directory')
         build_oci.build()
-        self.assertThat(build_oci.backend.run.calls, MatchesListwise([
-            RanBuildCommand(
-                ["docker", "build", "--no-cache", "--tag", "test-image",
-                 "/home/buildd/test-image/."],
-                cwd="/home/buildd/test-image"),
-            ]))
+        self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
+            ["docker", "build", "--no-cache", "--tag", "test-image",
+             "/home/buildd/test-image/."],
+            cwd="/home/buildd/test-image"))
+        self.assertRanPostBuildCommands(build_oci)
 
     def test_build_with_file(self):
         args = [
@@ -316,13 +340,12 @@ class TestBuildOCI(TestCase):
         build_oci = parse_args(args=args).operation
         build_oci.backend.add_dir('/build/test-directory')
         build_oci.build()
-        self.assertThat(build_oci.backend.run.calls, MatchesListwise([
-            RanBuildCommand(
-                ["docker", "build", "--no-cache", "--tag", "test-image",
-                 "--file", "./build-aux/Dockerfile",
-                 "/home/buildd/test-image/."],
-                cwd="/home/buildd/test-image"),
-            ]))
+        self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
+            ["docker", "build", "--no-cache", "--tag", "test-image",
+             "--file", "./build-aux/Dockerfile",
+             "/home/buildd/test-image/."],
+            cwd="/home/buildd/test-image"))
+        self.assertRanPostBuildCommands(build_oci)
 
     def test_build_with_path(self):
         args = [
@@ -334,12 +357,11 @@ class TestBuildOCI(TestCase):
         build_oci = parse_args(args=args).operation
         build_oci.backend.add_dir('/build/test-directory')
         build_oci.build()
-        self.assertThat(build_oci.backend.run.calls, MatchesListwise([
-            RanBuildCommand(
-                ["docker", "build", "--no-cache", "--tag", "test-image",
-                 "/home/buildd/test-image/a-sub-directory/"],
-                cwd="/home/buildd/test-image"),
-            ]))
+        self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
+            ["docker", "build", "--no-cache", "--tag", "test-image",
+             "/home/buildd/test-image/a-sub-directory/"],
+            cwd="/home/buildd/test-image"))
+        self.assertRanPostBuildCommands(build_oci)
 
     def test_build_with_file_and_path(self):
         args = [
@@ -352,13 +374,12 @@ class TestBuildOCI(TestCase):
         build_oci = parse_args(args=args).operation
         build_oci.backend.add_dir('/build/test-directory')
         build_oci.build()
-        self.assertThat(build_oci.backend.run.calls, MatchesListwise([
-            RanBuildCommand(
-                ["docker", "build", "--no-cache", "--tag", "test-image",
-                 "--file", "test-build-path/build-aux/Dockerfile",
-                 "/home/buildd/test-image/test-build-path"],
-                cwd="/home/buildd/test-image"),
-            ]))
+        self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
+            ["docker", "build", "--no-cache", "--tag", "test-image",
+             "--file", "test-build-path/build-aux/Dockerfile",
+             "/home/buildd/test-image/test-build-path"],
+            cwd="/home/buildd/test-image"))
+        self.assertRanPostBuildCommands(build_oci)
 
     def test_build_with_args(self):
         args = [
@@ -372,14 +393,13 @@ class TestBuildOCI(TestCase):
         build_oci = parse_args(args=args).operation
         build_oci.backend.add_dir('/build/test-directory')
         build_oci.build()
-        self.assertThat(build_oci.backend.run.calls, MatchesListwise([
-            RanBuildCommand(
-                ["docker", "build", "--no-cache", "--tag", "test-image",
-                 "--file", "test-build-path/build-aux/Dockerfile",
-                 "--build-arg=VAR1=xxx", "--build-arg=VAR2=yyy",
-                 "/home/buildd/test-image/test-build-path"],
-                cwd="/home/buildd/test-image"),
-            ]))
+        self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
+            ["docker", "build", "--no-cache", "--tag", "test-image",
+             "--file", "test-build-path/build-aux/Dockerfile",
+             "--build-arg=VAR1=xxx", "--build-arg=VAR2=yyy",
+             "/home/buildd/test-image/test-build-path"],
+            cwd="/home/buildd/test-image"))
+        self.assertRanPostBuildCommands(build_oci)
 
     def test_build_proxy(self):
         args = [
@@ -391,14 +411,13 @@ class TestBuildOCI(TestCase):
         build_oci = parse_args(args=args).operation
         build_oci.backend.add_dir('/build/test-directory')
         build_oci.build()
-        self.assertThat(build_oci.backend.run.calls, MatchesListwise([
-            RanBuildCommand(
-                ["docker", "build", "--no-cache",
-                 "--build-arg", "http_proxy=http://proxy.example:3128/";,
-                 "--build-arg", "https_proxy=http://proxy.example:3128/";,
-                 "--tag", "test-image", "/home/buildd/test-image/."],
-                cwd="/home/buildd/test-image"),
-            ]))
+        self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
+            ["docker", "build", "--no-cache",
+             "--build-arg", "http_proxy=http://proxy.example:3128/";,
+             "--build-arg", "https_proxy=http://proxy.example:3128/";,
+             "--tag", "test-image", "/home/buildd/test-image/."],
+            cwd="/home/buildd/test-image"))
+        self.assertRanPostBuildCommands(build_oci)
 
     def test_run_succeeds(self):
         args = [