Skip to content

Commit 1448a33

Browse files
authored
Remove Codenotary integrity check (#6236)
* Formally deprecate CodeNotary build config * Remove CodeNotary specific integrity checking The current code is specific to how CodeNotary was doing integrity checking. A future integrity checking mechanism likely will work differently (e.g. through EROFS based containers). Remove the current code to make way for a future implementation. * Drop CodeNotary integrity fixups * Drop unused tests * Fix pytest * Fix pytest * Remove CodeNotary related exceptions and handling Remove CodeNotary related exceptions and handling from the Docker interface. * Drop unnecessary comment * Remove Codenotary specific IssueType/SuggestionType * Drop Codenotary specific environment and secret reference * Remove unused constants * Introduce APIGone exception for removed APIs Introduce a new exception class APIGone to indicate that certain API features have been removed and are no longer available. Update the security integrity check endpoint to raise this new exception instead of a generic APIError, providing clearer communication to clients that the feature has been intentionally removed. * Drop content trust A cosign based signature verification will likely be named differently to avoid confusion with existing implementations. For now, remove the content trust option entirely. * Drop code sign test * Remove source_mods/content_trust evaluations * Remove content_trust reference in bootstrap.py * Fix security tests * Drop unused tests * Drop codenotary from schema Since we have "remove extra" in voluptuous, we can remove the codenotary field from the addon schema. * Remove content_trust from tests * Remove content_trust unsupported reason * Remove unnecessary comment * Remove unrelated pytest * Remove unrelated fixtures
1 parent 1657769 commit 1448a33

37 files changed

+37
-1296
lines changed

.github/workflows/builder.yml

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,6 @@ jobs:
170170
--target /data \
171171
--cosign \
172172
--generic ${{ needs.init.outputs.version }}
173-
env:
174-
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
175173
176174
version:
177175
name: Update version
@@ -293,33 +291,6 @@ jobs:
293291
exit 1
294292
fi
295293
296-
- name: Check the Supervisor code sign
297-
if: needs.init.outputs.publish == 'true'
298-
run: |
299-
echo "Enable Content-Trust"
300-
test=$(docker exec hassio_cli ha security options --content-trust=true --no-progress --raw-json | jq -r '.result')
301-
if [ "$test" != "ok" ]; then
302-
exit 1
303-
fi
304-
305-
echo "Run supervisor health check"
306-
test=$(docker exec hassio_cli ha resolution healthcheck --no-progress --raw-json | jq -r '.result')
307-
if [ "$test" != "ok" ]; then
308-
exit 1
309-
fi
310-
311-
echo "Check supervisor unhealthy"
312-
test=$(docker exec hassio_cli ha resolution info --no-progress --raw-json | jq -r '.data.unhealthy[]')
313-
if [ "$test" != "" ]; then
314-
exit 1
315-
fi
316-
317-
echo "Check supervisor supported"
318-
test=$(docker exec hassio_cli ha resolution info --no-progress --raw-json | jq -r '.data.unsupported[]')
319-
if [[ "$test" =~ source_mods ]]; then
320-
exit 1
321-
fi
322-
323294
- name: Create full backup
324295
id: backup
325296
run: |

supervisor/addons/addon.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,13 +1513,6 @@ def _restore_data():
15131513
_LOGGER.info("Finished restore for add-on %s", self.slug)
15141514
return wait_for_start
15151515

1516-
def check_trust(self) -> Awaitable[None]:
1517-
"""Calculate Addon docker content trust.
1518-
1519-
Return Coroutine.
1520-
"""
1521-
return self.instance.check_trust()
1522-
15231516
@Job(
15241517
name="addon_restart_after_problem",
15251518
throttle_period=WATCHDOG_THROTTLE_PERIOD,

supervisor/addons/model.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@
103103
from .const import (
104104
ATTR_BACKUP,
105105
ATTR_BREAKING_VERSIONS,
106-
ATTR_CODENOTARY,
107106
ATTR_PATH,
108107
ATTR_READ_ONLY,
109108
AddonBackupMode,
@@ -632,13 +631,8 @@ def with_journald(self) -> bool:
632631

633632
@property
634633
def signed(self) -> bool:
635-
"""Return True if the image is signed."""
636-
return ATTR_CODENOTARY in self.data
637-
638-
@property
639-
def codenotary(self) -> str | None:
640-
"""Return Signer email address for CAS."""
641-
return self.data.get(ATTR_CODENOTARY)
634+
"""Currently no signing support."""
635+
return False
642636

643637
@property
644638
def breaking_versions(self) -> list[AwesomeVersion]:

supervisor/addons/validate.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ def _warn_addon_config(config: dict[str, Any]):
207207
name,
208208
)
209209

210+
if ATTR_CODENOTARY in config:
211+
_LOGGER.warning(
212+
"Add-on '%s' uses deprecated 'codenotary' field in config. This field is no longer used and will be ignored. Please report this to the maintainer.",
213+
name,
214+
)
215+
210216
return config
211217

212218

@@ -417,7 +423,6 @@ def _migrate(config: dict[str, Any]):
417423
vol.Optional(ATTR_BACKUP, default=AddonBackupMode.HOT): vol.Coerce(
418424
AddonBackupMode
419425
),
420-
vol.Optional(ATTR_CODENOTARY): vol.Email(),
421426
vol.Optional(ATTR_OPTIONS, default={}): dict,
422427
vol.Optional(ATTR_SCHEMA, default={}): vol.Any(
423428
vol.Schema({str: SCHEMA_ELEMENT}),

supervisor/api/security.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
"""Init file for Supervisor Security RESTful API."""
22

3-
import asyncio
4-
import logging
53
from typing import Any
64

75
from aiohttp import web
8-
import attr
96
import voluptuous as vol
107

11-
from ..const import ATTR_CONTENT_TRUST, ATTR_FORCE_SECURITY, ATTR_PWNED
8+
from supervisor.exceptions import APIGone
9+
10+
from ..const import ATTR_FORCE_SECURITY, ATTR_PWNED
1211
from ..coresys import CoreSysAttributes
1312
from .utils import api_process, api_validate
1413

15-
_LOGGER: logging.Logger = logging.getLogger(__name__)
16-
1714
# pylint: disable=no-value-for-parameter
1815
SCHEMA_OPTIONS = vol.Schema(
1916
{
2017
vol.Optional(ATTR_PWNED): vol.Boolean(),
21-
vol.Optional(ATTR_CONTENT_TRUST): vol.Boolean(),
2218
vol.Optional(ATTR_FORCE_SECURITY): vol.Boolean(),
2319
}
2420
)
@@ -31,7 +27,6 @@ class APISecurity(CoreSysAttributes):
3127
async def info(self, request: web.Request) -> dict[str, Any]:
3228
"""Return Security information."""
3329
return {
34-
ATTR_CONTENT_TRUST: self.sys_security.content_trust,
3530
ATTR_PWNED: self.sys_security.pwned,
3631
ATTR_FORCE_SECURITY: self.sys_security.force,
3732
}
@@ -43,8 +38,6 @@ async def options(self, request: web.Request) -> None:
4338

4439
if ATTR_PWNED in body:
4540
self.sys_security.pwned = body[ATTR_PWNED]
46-
if ATTR_CONTENT_TRUST in body:
47-
self.sys_security.content_trust = body[ATTR_CONTENT_TRUST]
4841
if ATTR_FORCE_SECURITY in body:
4942
self.sys_security.force = body[ATTR_FORCE_SECURITY]
5043

@@ -54,6 +47,9 @@ async def options(self, request: web.Request) -> None:
5447

5548
@api_process
5649
async def integrity_check(self, request: web.Request) -> dict[str, Any]:
57-
"""Run backend integrity check."""
58-
result = await asyncio.shield(self.sys_security.integrity_check())
59-
return attr.asdict(result)
50+
"""Run backend integrity check.
51+
52+
CodeNotary integrity checking has been removed. This endpoint now returns
53+
an error indicating the feature is gone.
54+
"""
55+
raise APIGone("Integrity check feature has been removed.")

supervisor/api/supervisor.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616
ATTR_BLK_READ,
1717
ATTR_BLK_WRITE,
1818
ATTR_CHANNEL,
19-
ATTR_CONTENT_TRUST,
2019
ATTR_COUNTRY,
2120
ATTR_CPU_PERCENT,
2221
ATTR_DEBUG,
2322
ATTR_DEBUG_BLOCK,
2423
ATTR_DETECT_BLOCKING_IO,
2524
ATTR_DIAGNOSTICS,
26-
ATTR_FORCE_SECURITY,
2725
ATTR_HEALTHY,
2826
ATTR_ICON,
2927
ATTR_IP_ADDRESS,
@@ -69,8 +67,6 @@
6967
vol.Optional(ATTR_DEBUG): vol.Boolean(),
7068
vol.Optional(ATTR_DEBUG_BLOCK): vol.Boolean(),
7169
vol.Optional(ATTR_DIAGNOSTICS): vol.Boolean(),
72-
vol.Optional(ATTR_CONTENT_TRUST): vol.Boolean(),
73-
vol.Optional(ATTR_FORCE_SECURITY): vol.Boolean(),
7470
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
7571
vol.Optional(ATTR_DETECT_BLOCKING_IO): vol.Coerce(DetectBlockingIO),
7672
vol.Optional(ATTR_COUNTRY): str,

supervisor/bootstrap.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ async def initialize_coresys() -> CoreSys:
105105

106106
if coresys.dev:
107107
coresys.updater.channel = UpdateChannel.DEV
108-
coresys.security.content_trust = False
109108

110109
# Convert datetime
111110
logging.Formatter.converter = lambda *args: coresys.now().timetuple()

supervisor/docker/addon.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -846,16 +846,6 @@ async def stop(self, remove_container: bool = True) -> None:
846846
):
847847
self.sys_resolution.dismiss_issue(self.addon.device_access_missing_issue)
848848

849-
async def _validate_trust(self, image_id: str) -> None:
850-
"""Validate trust of content."""
851-
if not self.addon.signed:
852-
return
853-
854-
checksum = image_id.partition(":")[2]
855-
return await self.sys_security.verify_content(
856-
cast(str, self.addon.codenotary), checksum
857-
)
858-
859849
@Job(
860850
name="docker_addon_hardware_events",
861851
conditions=[JobCondition.OS_AGENT],

supervisor/docker/homeassistant.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import re
77

8-
from awesomeversion import AwesomeVersion, AwesomeVersionCompareException
8+
from awesomeversion import AwesomeVersion
99
from docker.types import Mount
1010

1111
from ..const import LABEL_MACHINE
@@ -244,13 +244,3 @@ def is_initialize(self) -> Awaitable[bool]:
244244
self.image,
245245
self.sys_homeassistant.version,
246246
)
247-
248-
async def _validate_trust(self, image_id: str) -> None:
249-
"""Validate trust of content."""
250-
try:
251-
if self.version in {None, LANDINGPAGE} or self.version < _VERIFY_TRUST:
252-
return
253-
except AwesomeVersionCompareException:
254-
return
255-
256-
await super()._validate_trust(image_id)

supervisor/docker/interface.py

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,12 @@
3131
)
3232
from ..coresys import CoreSys
3333
from ..exceptions import (
34-
CodeNotaryError,
35-
CodeNotaryUntrusted,
3634
DockerAPIError,
3735
DockerError,
3836
DockerJobError,
3937
DockerLogOutOfOrder,
4038
DockerNotFound,
4139
DockerRequestError,
42-
DockerTrustError,
4340
)
4441
from ..jobs import SupervisorJob
4542
from ..jobs.const import JOB_GROUP_DOCKER_INTERFACE, JobConcurrency
@@ -425,18 +422,6 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
425422
platform=MAP_ARCH[image_arch],
426423
)
427424

428-
# Validate content
429-
try:
430-
await self._validate_trust(cast(str, docker_image.id))
431-
except CodeNotaryError:
432-
with suppress(docker.errors.DockerException):
433-
await self.sys_run_in_executor(
434-
self.sys_docker.images.remove,
435-
image=f"{image}:{version!s}",
436-
force=True,
437-
)
438-
raise
439-
440425
# Tag latest
441426
if latest:
442427
_LOGGER.info(
@@ -462,16 +447,6 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
462447
raise DockerError(
463448
f"Unknown error with {image}:{version!s} -> {err!s}", _LOGGER.error
464449
) from err
465-
except CodeNotaryUntrusted as err:
466-
raise DockerTrustError(
467-
f"Pulled image {image}:{version!s} failed on content-trust verification!",
468-
_LOGGER.critical,
469-
) from err
470-
except CodeNotaryError as err:
471-
raise DockerTrustError(
472-
f"Error happened on Content-Trust check for {image}:{version!s}: {err!s}",
473-
_LOGGER.error,
474-
) from err
475450
finally:
476451
if listener:
477452
self.sys_bus.remove_listener(listener)
@@ -809,24 +784,3 @@ def run_inside(self, command: str) -> Awaitable[CommandReturn]:
809784
return self.sys_run_in_executor(
810785
self.sys_docker.container_run_inside, self.name, command
811786
)
812-
813-
async def _validate_trust(self, image_id: str) -> None:
814-
"""Validate trust of content."""
815-
checksum = image_id.partition(":")[2]
816-
return await self.sys_security.verify_own_content(checksum)
817-
818-
@Job(
819-
name="docker_interface_check_trust",
820-
on_condition=DockerJobError,
821-
concurrency=JobConcurrency.GROUP_REJECT,
822-
)
823-
async def check_trust(self) -> None:
824-
"""Check trust of exists Docker image."""
825-
try:
826-
image = await self.sys_run_in_executor(
827-
self.sys_docker.images.get, f"{self.image}:{self.version!s}"
828-
)
829-
except (docker.errors.DockerException, requests.RequestException):
830-
return
831-
832-
await self._validate_trust(cast(str, image.id))

0 commit comments

Comments
 (0)