Merge lp:~snappy-dev/click/default-apparmor into lp:~snappy-dev/click/snappy

Proposed by Barry Warsaw
Status: Needs review
Proposed branch: lp:~snappy-dev/click/default-apparmor
Merge into: lp:~snappy-dev/click/snappy
Diff against target: 345 lines (+254/-25)
2 files modified
click/build.py (+33/-5)
click/tests/test_build.py (+221/-20)
To merge this branch: bzr merge lp:~snappy-dev/click/default-apparmor
Reviewer Review Type Date Requested Status
Michael Vogt (community) Approve
Jamie Strandboge Pending
Review via email: mp+243609@code.launchpad.net

Description of the change

snappy build needs to generate default apparmor profile automatically

To post a comment you must log in.
Revision history for this message
Jamie Strandboge (jdstrand) wrote :

Without testing the code, this looks reasonable. I can say that the hardcoding of the policy_version is something we won't want long term. We'll need to have click-apparmor expose that some how so it can be consumed by click.

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

Do you have debs available for testing?

Revision history for this message
Barry Warsaw (barry) wrote :

> Without testing the code, this looks reasonable. I can say that the hardcoding
> of the policy_version is something we won't want long term. We'll need to have
> click-apparmor expose that some how so it can be consumed by click.

Will the rest of the file contents potentially change, or just the policy_version? If it's just the latter and we can determine the version at build-time, we can easily substitute that in. Actually, if the entire file contents is available somewhere we can pretty easily read that in at build time.

Revision history for this message
Barry Warsaw (barry) wrote :

> Do you have debs available for testing?

Not yet. I was hoping that once Michael reviews he can set that up.

Revision history for this message
Michael Vogt (mvo) wrote :

Looks great, thanks! And extra credits for the tests, yay! I merged it into the yaml-manifest branch and it will go to the PPA next, I did a tiny change to name APPARMOR_DEFAULT_TEMPLATE -> SNAPPY_APPARMOR_DEFAULT_TEMPLATE

review: Approve
Revision history for this message
Michael Vogt (mvo) wrote :

I just uploaded the new click to the ppa:snappy-dev/tools PPA so it should be ready for testing RSN. I did a manual test with my xkcd-webserver example and it worked there, I will do another one with the golang example next.

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

On 12/03/2014 06:41 PM, Barry Warsaw wrote:
>> Without testing the code, this looks reasonable. I can say that the hardcoding
>> of the policy_version is something we won't want long term. We'll need to have
>> click-apparmor expose that some how so it can be consumed by click.
>
> Will the rest of the file contents potentially change, or just the policy_version?
> If it's just the latter and we can determine the version at build-time, we can
> easily substitute that in. Actually, if the entire file contents is available
> somewhere we can pretty easily read that in at build time.

Well,the contents of the file are up to us-- we can define the defaults. Dealing
with the policy_version requires further thought and discussion since it has
been changing based on the Ubuntu release (eg, 1.2 is 14.10, 1.1 14.04). That is
just convention though-- there is no reason why it has to be that way, but it
will definitely change if we want to remove policy groups or templates, or if
new policy groups and templates need to use new apparmor syntax.

--
Jamie Strandboge | http://www.ubuntu.com

Unmerged revisions

574. By Barry Warsaw

Create default apparmor profiles if the yaml doesn't explicitly name
integration:<app>:apparmor{,-profile}

With tests!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'click/build.py'
--- click/build.py 2014-12-03 15:46:34 +0000
+++ click/build.py 2014-12-03 23:21:56 +0000
@@ -62,6 +62,18 @@
62)62)
6363
6464
65APPARMOR_DEFAULT_TEMPLATE = """\
66{
67 "template": "default",
68 "policy_groups": [
69 "networking"
70 ],
71 "policy_vendor": "ubuntu-snappy",
72 "policy_version": 1.3
73}
74"""
75
76
65@contextlib.contextmanager77@contextlib.contextmanager
66def make_temp_dir():78def make_temp_dir():
67 temp_dir = tempfile.mkdtemp(prefix="click")79 temp_dir = tempfile.mkdtemp(prefix="click")
@@ -89,6 +101,7 @@
89class ClickBuilderBase:101class ClickBuilderBase:
90 def __init__(self):102 def __init__(self):
91 self.file_map = {}103 self.file_map = {}
104 self.autogen_apparmor = []
92105
93 def add_file(self, source_path, dest_path):106 def add_file(self, source_path, dest_path):
94 self.file_map[source_path] = dest_path107 self.file_map[source_path] = dest_path
@@ -136,18 +149,28 @@
136 # deal with new "services" field149 # deal with new "services" field
137 hooks = self.manifest.get("hooks", {})150 hooks = self.manifest.get("hooks", {})
138 for service in self.manifest.get("services", []):151 for service in self.manifest.get("services", []):
139 current_hook = hooks.get(service["name"], {})152 name = service["name"]
140 snappy_systemd = "%s.snappy-systemd" % service["name"]153 current_hook = hooks.get(name, {})
154 snappy_systemd = "{}.snappy-systemd".format(name)
141 description = service.get(155 description = service.get(
142 "description",156 "description",
143 self.manifest.get("description", "no description"))157 self.manifest.get("description", "no description"))
144 with open(os.path.join(158 systemd_path = os.path.join(
145 os.path.dirname(manifest_path), snappy_systemd), "w") as fp:159 os.path.dirname(manifest_path), snappy_systemd)
160 with open(systemd_path, "w") as fp:
146 yaml.dump({"description": description,161 yaml.dump({"description": description,
147 "start": service["start"],162 "start": service["start"],
148 }, fp)163 }, fp)
149 current_hook["snappy-systemd"] = "meta/"+snappy_systemd164 current_hook["snappy-systemd"] = "meta/"+snappy_systemd
150 hooks[service["name"]] = current_hook165 hooks[name] = current_hook
166 # Is there an explicit apparmor hook? If so, use it otherwise
167 # generate a default one.
168 if ( 'apparmor' not in current_hook and
169 'apparmor-profile' not in current_hook):
170 template_path = os.path.join(
171 'meta', '{}.apparmor'.format(name))
172 current_hook['apparmor'] = template_path
173 self.autogen_apparmor.append(template_path)
151 self.manifest["hooks"] = hooks174 self.manifest["hooks"] = hooks
152 # cleanup175 # cleanup
153 if "services" in self.manifest:176 if "services" in self.manifest:
@@ -316,6 +339,11 @@
316 if "framework" in self.manifest:339 if "framework" in self.manifest:
317 self._validate_framework(self.manifest["framework"])340 self._validate_framework(self.manifest["framework"])
318341
342 for template_path in self.autogen_apparmor:
343 path = os.path.join(root_path, template_path)
344 with io.open(path, 'w', encoding='utf-8') as fp:
345 fp.write(APPARMOR_DEFAULT_TEMPLATE)
346
319 du_output = subprocess.check_output(347 du_output = subprocess.check_output(
320 ["du", "-k", "-s", "--apparent-size", "."],348 ["du", "-k", "-s", "--apparent-size", "."],
321 cwd=temp_dir, universal_newlines=True).rstrip("\n")349 cwd=temp_dir, universal_newlines=True).rstrip("\n")
322350
=== modified file 'click/tests/test_build.py'
--- click/tests/test_build.py 2014-12-02 15:34:43 +0000
+++ click/tests/test_build.py 2014-12-03 23:21:56 +0000
@@ -342,31 +342,82 @@
342 self.assertTrue(tar.getmember("./bin/foo").isfile())342 self.assertTrue(tar.getmember("./bin/foo").isfile())
343343
344344
345SIMPLE_TEMPLATE = """\
346name: test-yaml
347icon: data/icon.svg
348version: 1.0
349maintainer: Foo Bar <foo@example.org>
350architecture: all
351framework: ubuntu-core-15.04
352integration:
353 app1:
354 hookname: hook-file-name
355services:
356 - name: app1
357 description: some description
358 start: start-something
359 stop: stop-something
360binaries:
361 - name: path/to/cli1
362"""
363
364APPARMOR_1_TEMPLATE = """\
365name: test-yaml
366icon: data/icon.svg
367version: 1.0
368maintainer: Foo Bar <foo@example.org>
369architecture: all
370framework: ubuntu-core-15.04
371integration:
372 app1:
373 hookname: hook-file-name
374 apparmor: path/to/app1.apparmor
375services:
376 - name: app1
377 description: some description
378 start: start-something
379 stop: stop-something
380 - name: app2
381 description: some description
382 start: start-something
383 stop: stop-something
384binaries:
385 - name: path/to/cli1
386"""
387
388APPARMOR_2_TEMPLATE = """\
389name: test-yaml
390icon: data/icon.svg
391version: 1.0
392maintainer: Foo Bar <foo@example.org>
393architecture: all
394framework: ubuntu-core-15.04
395integration:
396 app1:
397 hookname: hook-file-name
398 apparmor-profile: path/to/app1.apparmor-profile
399services:
400 - name: app1
401 description: some description
402 start: start-something
403 stop: stop-something
404 - name: app2
405 description: some description
406 start: start-something
407 stop: stop-something
408binaries:
409 - name: path/to/cli1
410"""
411
412
345class TestClickBuildYaml(TestCase):413class TestClickBuildYaml(TestCase):
346 def setUp(self):414 def setUp(self):
347 super(TestClickBuildYaml, self).setUp()415 super(TestClickBuildYaml, self).setUp()
348 self.builder = ClickBuilder()416 self.builder = ClickBuilder()
349417
350 def _make_metadata(self, scratch):418 def _make_metadata(self, scratch, template=SIMPLE_TEMPLATE):
351 with mkfile(os.path.join(scratch, "meta", "package.yaml")) as f:419 with mkfile(os.path.join(scratch, "meta", "package.yaml")) as f:
352 f.write(dedent("""\420 f.write(dedent(template))
353 name: test-yaml
354 icon: data/icon.svg
355 version: 1.0
356 maintainer: Foo Bar <foo@example.org>
357 architecture: all
358 framework: ubuntu-core-15.04
359 integration:
360 app1:
361 hookname: hook-file-name
362 services:
363 - name: app1
364 description: some description
365 start: start-something
366 stop: stop-something
367 binaries:
368 - name: path/to/cli1
369 """))
370 with mkfile(os.path.join(scratch, "meta", "readme.md")) as f:421 with mkfile(os.path.join(scratch, "meta", "readme.md")) as f:
371 f.write(dedent("""test title422 f.write(dedent("""test title
372423
@@ -401,7 +452,8 @@
401 click_contents = subprocess.check_output(452 click_contents = subprocess.check_output(
402 ["dpkg-deb", "-c", actual_path], universal_newlines=True)453 ["dpkg-deb", "-c", actual_path], universal_newlines=True)
403 for file_path in ("./data/icon.svg", "./meta/package.yaml",454 for file_path in ("./data/icon.svg", "./meta/package.yaml",
404 "./meta/readme.md", "./bin/foo"):455 "./meta/readme.md", "./bin/foo",
456 "./meta/app1.apparmor"):
405 self.assertIn(file_path, click_contents)457 self.assertIn(file_path, click_contents)
406458
407 def test_read_manifest(self):459 def test_read_manifest(self):
@@ -422,3 +474,152 @@
422 self.assertEqual(474 self.assertEqual(
423 "path/to/cli1",475 "path/to/cli1",
424 self.builder.manifest["hooks"]["cli1"]["bin-path"])476 self.builder.manifest["hooks"]["cli1"]["bin-path"])
477 self.assertEqual(
478 "meta/app1.apparmor",
479 self.builder.manifest["hooks"]["app1"]["apparmor"])
480
481 def test_one_explicit_apparmor(self):
482 self.use_temp_dir()
483 self._make_metadata(self.temp_dir, APPARMOR_1_TEMPLATE)
484 self.builder.read_manifest(self.temp_dir + "/meta/package.yaml")
485 self.assertEqual(
486 "path/to/app1.apparmor",
487 self.builder.manifest["hooks"]["app1"]["apparmor"])
488 self.assertEqual(
489 "meta/app2.apparmor",
490 self.builder.manifest["hooks"]["app2"]["apparmor"])
491
492 def test_one_explicit_apparmor_profile(self):
493 self.use_temp_dir()
494 self._make_metadata(self.temp_dir, APPARMOR_2_TEMPLATE)
495 self.builder.read_manifest(self.temp_dir + "/meta/package.yaml")
496 self.assertEqual(
497 "path/to/app1.apparmor-profile",
498 self.builder.manifest["hooks"]["app1"]["apparmor-profile"])
499 self.assertIsNone(
500 self.builder.manifest["hooks"]["app1"].get('apparmor'))
501 self.assertEqual(
502 "meta/app2.apparmor",
503 self.builder.manifest["hooks"]["app2"]["apparmor"])
504
505 def test_one_explicit_apparmor_file(self):
506 self.use_temp_dir()
507 scratch = os.path.join(self.temp_dir, "scratch")
508 touch(os.path.join(scratch, "bin", "foo"))
509 touch(os.path.join(scratch, "data", "icon.svg"))
510 apparmor_path = os.path.join(scratch, 'path', 'to', 'app1.apparmor')
511 os.makedirs(os.path.dirname(apparmor_path))
512 with open(apparmor_path, 'w', encoding='utf-8') as fp:
513 fp.write(dedent("""\
514 {
515 "template": "default",
516 "policy_groups": [
517 "networking"
518 ],
519 "policy_vendor": "ubuntu-snappy",
520 "policy_version": 99.99
521 }
522 """))
523 self._make_metadata(scratch, APPARMOR_1_TEMPLATE)
524 self.builder.add_file(scratch, "./")
525 # FIXME: make it a .snap
526 expected_path = os.path.join(self.temp_dir, "test-yaml_1.0_all.click")
527 actual_path = self.builder.build(self.temp_dir, "meta/package.yaml")
528 self.assertEqual(expected_path, actual_path)
529 self.assertTrue(os.path.exists(actual_path))
530 contents = os.path.join(self.temp_dir, 'contents')
531 subprocess.check_output(
532 ["dpkg-deb", "-R", actual_path, contents], universal_newlines=True)
533 self.assertEqual(sorted(os.listdir(contents)),
534 ['DEBIAN', 'bin', 'data', 'meta', 'path'])
535 self.assertEqual(
536 sorted(os.listdir(os.path.join(contents, 'path', 'to'))),
537 ['app1.apparmor'])
538 with open(os.path.join(contents, 'path', 'to', 'app1.apparmor'),
539 encoding='utf-8') as fp:
540 data = fp.read()
541 self.assertEqual(data, dedent("""\
542 {
543 "template": "default",
544 "policy_groups": [
545 "networking"
546 ],
547 "policy_vendor": "ubuntu-snappy",
548 "policy_version": 99.99
549 }
550 """))
551 with open(os.path.join(contents, 'meta', 'app2.apparmor'),
552 encoding='utf-8') as fp:
553 data = fp.read()
554 self.assertEqual(data, dedent("""\
555 {
556 "template": "default",
557 "policy_groups": [
558 "networking"
559 ],
560 "policy_vendor": "ubuntu-snappy",
561 "policy_version": 1.3
562 }
563 """))
564
565 def test_one_explicit_apparmor_profile_file(self):
566 self.use_temp_dir()
567 scratch = os.path.join(self.temp_dir, "scratch")
568 touch(os.path.join(scratch, "bin", "foo"))
569 touch(os.path.join(scratch, "data", "icon.svg"))
570 apparmor_path = os.path.join(
571 scratch, 'path', 'to', 'app1.apparmor-profile')
572 os.makedirs(os.path.dirname(apparmor_path))
573 with open(apparmor_path, 'w', encoding='utf-8') as fp:
574 fp.write(dedent("""\
575 {
576 "template": "default",
577 "policy_groups": [
578 "networking"
579 ],
580 "policy_vendor": "ubuntu-snappy",
581 "policy_version": 88.88
582 }
583 """))
584 self._make_metadata(scratch, APPARMOR_2_TEMPLATE)
585 self.builder.add_file(scratch, "./")
586 # FIXME: make it a .snap
587 expected_path = os.path.join(self.temp_dir, "test-yaml_1.0_all.click")
588 actual_path = self.builder.build(self.temp_dir, "meta/package.yaml")
589 self.assertEqual(expected_path, actual_path)
590 self.assertTrue(os.path.exists(actual_path))
591 contents = os.path.join(self.temp_dir, 'contents')
592 subprocess.check_output(
593 ["dpkg-deb", "-R", actual_path, contents], universal_newlines=True)
594 self.assertEqual(sorted(os.listdir(contents)),
595 ['DEBIAN', 'bin', 'data', 'meta', 'path'])
596 self.assertEqual(
597 sorted(os.listdir(os.path.join(contents, 'path', 'to'))),
598 ['app1.apparmor-profile'])
599 with open(
600 os.path.join(contents, 'path', 'to', 'app1.apparmor-profile'),
601 encoding='utf-8') as fp:
602 data = fp.read()
603 self.assertEqual(data, dedent("""\
604 {
605 "template": "default",
606 "policy_groups": [
607 "networking"
608 ],
609 "policy_vendor": "ubuntu-snappy",
610 "policy_version": 88.88
611 }
612 """))
613 with open(os.path.join(contents, 'meta', 'app2.apparmor'),
614 encoding='utf-8') as fp:
615 data = fp.read()
616 self.assertEqual(data, dedent("""\
617 {
618 "template": "default",
619 "policy_groups": [
620 "networking"
621 ],
622 "policy_vendor": "ubuntu-snappy",
623 "policy_version": 1.3
624 }
625 """))

Subscribers

People subscribed via source and target branches

to all changes: