Compare commits

...

5 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
eea59927f8 feat: add skip_cosign_check URL parameter to temporarily bypass cosign verification
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/d269a60b-15fe-4f81-a51e-c4d8212e0d5b

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-27 01:29:36 +00:00
copilot-swe-agent[bot]
c962fcc10c fix: extend signature verification and docs to cover Docker Hub nextcloud/all-in-one image
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/c9cb8e66-7b4f-4e38-ba33-b9568de6d421

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-27 01:20:12 +00:00
copilot-swe-agent[bot]
2a2e69f047 docs: document how to verify image signature in readme
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/0127a09a-2fa8-4d32-9215-751016cf7db9

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-27 01:17:50 +00:00
copilot-swe-agent[bot]
e5aaacf07e feat: verify mastercontainer image signature before starting watchtower
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/a09baa5e-3611-40ef-a9a2-d14d9db094b1

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-27 01:14:47 +00:00
copilot-swe-agent[bot]
f41fe58455 feat: verify cosign image signatures before pulling ghcr.io/nextcloud-releases images
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/fd95dc7d-c221-4c10-9e14-e6f2253a7a22

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-27 01:02:12 +00:00
6 changed files with 97 additions and 3 deletions

View File

@@ -46,7 +46,8 @@ RUN set -ex; \
sudo \
netcat-openbsd \
curl \
grep; \
grep \
cosign; \
\
apk add --no-cache --virtual .build-deps \
autoconf \

View File

@@ -128,6 +128,7 @@ $app->get('/containers', function (Request $request, Response $response, array $
$bypass_mastercontainer_update = isset($params['bypass_mastercontainer_update']);
$bypass_container_update = isset($params['bypass_container_update']);
$skip_domain_validation = isset($params['skip_domain_validation']);
$skip_cosign_check = isset($params['skip_cosign_check']);
return $view->render($response, 'containers.twig', [
'domain' => $configurationManager->domain,
@@ -180,6 +181,7 @@ $app->get('/containers', function (Request $request, Response $response, array $
'community_containers' => $configurationManager->listAvailableCommunityContainers(),
'community_containers_enabled' => $configurationManager->aioCommunityContainers,
'bypass_container_update' => $bypass_container_update,
'skip_cosign_check' => $skip_cosign_check,
]);
})->setName('profile');
$app->get('/login', function (Request $request, Response $response, array $args) use ($container) {

View File

@@ -273,14 +273,18 @@ readonly class DockerController {
$nonbufResp = $this->startStreamingResponse($response);
$addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp);
$this->startWatchtower($addToStreamingResponseBody);
// Allow temporarily skipping the cosign check via a POST body parameter
$skipCosignCheck = isset($request->getParsedBody()['skip_cosign_check']);
$this->startWatchtower($addToStreamingResponseBody, $skipCosignCheck);
// End streaming response
$this->finalizeStreamingResponse($nonbufResp);
return $nonbufResp;
}
public function startWatchtower(?\Closure $addToStreamingResponseBody = null) : void {
public function startWatchtower(?\Closure $addToStreamingResponseBody = null, bool $skipCosignCheck = false) : void {
$this->dockerActionManager->verifyMastercontainerImageSignature($skipCosignCheck);
$id = 'nextcloud-aio-watchtower';
$this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody);

View File

@@ -523,6 +523,7 @@ readonly class DockerActionManager {
}
$imageName = $this->BuildImageName($container);
$this->verifyImageSignature($imageName);
$encodedImageName = urlencode($imageName);
$url = $this->BuildApiUrl(sprintf('images/create?fromImage=%s', $encodedImageName));
$imageIsThere = true;
@@ -557,6 +558,58 @@ readonly class DockerActionManager {
}
}
private function verifyImageSignature(string $imageName): void {
// Only verify images from the nextcloud-releases ghcr.io registry or
// the nextcloud/all-in-one Docker Hub image, as those are the images
// signed via the CI workflows in PR #97.
if (!str_starts_with($imageName, 'ghcr.io/nextcloud-releases/') && !str_starts_with($imageName, 'nextcloud/all-in-one')) {
return;
}
$command = [
'cosign',
'verify',
'--certificate-identity-regexp',
'^https://github\\.com/nextcloud-releases/all-in-one/\\.github/workflows/',
'--certificate-oidc-issuer',
'https://token.actions.githubusercontent.com',
$imageName,
];
$process = proc_open(
$command,
[
0 => ['file', '/dev/null', 'r'],
1 => ['file', '/dev/null', 'w'],
2 => ['pipe', 'w'],
],
$pipes
);
if (!is_resource($process)) {
throw new \Exception('Could not execute cosign command to verify image ' . $imageName . '. Ensure cosign is installed and accessible.');
}
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$exitCode = proc_close($process);
if ($exitCode !== 0) {
$stderrOutput = $stderr !== false ? $stderr : '';
error_log('cosign verification output for ' . $imageName . ': ' . $stderrOutput);
throw new \Exception('Image signature verification failed for ' . $imageName . '. The image may not be correctly signed.');
}
}
public function verifyMastercontainerImageSignature(bool $skipCosignCheck = false): void {
if ($skipCosignCheck) {
error_log('WARNING: Skipping cosign signature verification for mastercontainer image. This should only be done temporarily.');
return;
}
$imageName = $this->GetCurrentImageName() . ':' . $this->GetCurrentChannel();
$this->verifyImageSignature($imageName);
}
private function isContainerUpdateAvailable(string $id): string {
$container = $this->containerDefinitionFetcher->GetContainerById($id);

View File

@@ -93,6 +93,9 @@
<form method="POST" action="api/docker/watchtower" target="overlay-log">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
{% if skip_cosign_check == true %}
<input type="hidden" name="skip_cosign_check" value="true">
{% endif %}
<input type="submit" value="Update mastercontainer" />
</form>
{% else %}
@@ -335,6 +338,9 @@
<form method="POST" action="api/docker/watchtower" target="overlay-log">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
{% if skip_cosign_check == true %}
<input type="hidden" name="skip_cosign_check" value="true">
{% endif %}
<input type="submit" value="Update mastercontainer" />
</form>
{% else %}

View File

@@ -231,6 +231,9 @@ sudo docker run \
> [!NOTE]
> For production usage (and ease of upgrades and changes), we suggest using the example [Compose file](https://github.com/nextcloud/all-in-one/blob/main/compose.yaml) rather than `docker run`.
> [!TIP]
> You can optionally verify the cryptographic signature of the mastercontainer image before running it. See [How to verify the image signature?](#how-to-verify-the-image-signature)
4. After the initial startup, open the Nextcloud AIO interface on port 8080 of this server **by IP address**, for example:
```txt
https://192.168.5.5:8080
@@ -357,6 +360,7 @@ https://your-domain-that-points-to-this-server.tld:8443
- [Update policy](#update-policy)
- [How often are update notifications sent?](#how-often-are-update-notifications-sent)
- [Huge docker logs](#huge-docker-logs)
- [How to verify the image signature?](#how-to-verify-the-image-signature)
### Where can I find additional documentation?
Some of the documentation is available on [GitHub Discussions](https://github.com/nextcloud/all-in-one/discussions/categories/wiki).
@@ -1285,6 +1289,30 @@ AIO ships its own update notifications implementation. It checks if container up
### Huge docker logs
If you should run into issues with huge docker logs, you can adjust the log size by following https://docs.docker.com/config/containers/logging/local/#usage. However for the included AIO containers, this should usually not be needed because almost all of them have the log level set to warn so they should not produce many logs.
### How to verify the image signature?
All AIO images published to `ghcr.io/nextcloud-releases/` and the `nextcloud/all-in-one` Docker Hub image are signed using [cosign](https://github.com/sigstore/cosign) with keyless signing via GitHub Actions (Sigstore). You can verify the signature of the mastercontainer image before running it by installing cosign and executing one of the following commands depending on which registry you use:
For `ghcr.io/nextcloud-releases/all-in-one` (GitHub Container Registry):
```sh
cosign verify \
--certificate-identity-regexp '^https://github\.com/nextcloud-releases/all-in-one/\.github/workflows/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
ghcr.io/nextcloud-releases/all-in-one:latest
```
For `nextcloud/all-in-one` (Docker Hub):
```sh
cosign verify \
--certificate-identity-regexp '^https://github\.com/nextcloud-releases/all-in-one/\.github/workflows/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
nextcloud/all-in-one:latest
```
Replace `latest` with the tag you intend to run (e.g. `beta`). A successful verification prints the signing certificate details and exits with code 0. Any non-zero exit code means the signature could not be verified and you should **not** run that image.
> [!NOTE]
> AIO itself automatically verifies the cosign signature of every image it pulls from `ghcr.io/nextcloud-releases/` or `nextcloud/all-in-one` (Docker Hub) and refuses to pull images that fail verification. The manual command above lets you perform the same check independently before the initial `docker run`.
<details>
<summary>Badges</summary>