mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-05-21 02:40:09 +00:00
Compare commits
5 Commits
6288665170
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eea59927f8 | ||
|
|
c962fcc10c | ||
|
|
2a2e69f047 | ||
|
|
e5aaacf07e | ||
|
|
f41fe58455 |
@@ -46,7 +46,8 @@ RUN set -ex; \
|
||||
sudo \
|
||||
netcat-openbsd \
|
||||
curl \
|
||||
grep; \
|
||||
grep \
|
||||
cosign; \
|
||||
\
|
||||
apk add --no-cache --virtual .build-deps \
|
||||
autoconf \
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
28
readme.md
28
readme.md
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user