Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/management.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,24 @@ For more information, see the [Watchtower Docs](https://containrrr.dev/watchtowe

Stop your current instance of Dashy, then navigate into the source directory. Pull down the latest code, with `git pull origin master`, then update dependencies with `yarn`, rebuild with `yarn build`, and start the server again with `yarn start`.

### Verifying a Release Download

Each [GitHub release](https://github.com/lissy93/dashy/releases) bundles a SHA256 checksum and a SLSA build-provenance attestation alongside the source tarball (`dashy-<version>.tar.gz`). You don't need either to run Dashy, but they let you confirm a download arrived intact and was genuinely built from our source, rather than tampered with in transit or on a mirror.

Check the tarball is intact using the `.sha256` file published next to it:

```bash
sha256sum -c dashy-<version>.tar.gz.sha256
```

An `OK` means the file is untampered. To go further and prove it was built by our CI from our repo, verify the attestation with the [GitHub CLI](https://cli.github.com/):

```bash
gh attestation verify dashy-<version>.tar.gz --repo lissy93/dashy
```

The release notes for each version also list the checksum and a link to view the attestation directly.

**[⬆️ Back to Top](#management)**

---
Expand Down Expand Up @@ -797,6 +815,22 @@ Only use trusted images, from verified/ official sources. If an app is open sour

Unless otherwise configured, containers can communicate among each other, so running one bad image may lead to other areas of your setup being compromised. Docker images typically contain both original code, as well as up-stream packages, and even if that image has come from a trusted source, the up-stream packages it includes may not have.

Every Dashy image published to [GHCR](https://github.com/lissy93/dashy/pkgs/container/dashy) ships with a build-provenance attestation and an SBOM (software bill of materials), both signed keylessly via [Sigstore](https://www.sigstore.dev/). Provenance cryptographically ties the image back to the exact GitHub Actions run and commit that built it, so you can confirm it really came from our pipeline and was not swapped out along the way. The SBOM lists every package baked into the image, which is handy when a new CVE lands and you want to know in seconds whether you're affected.

To verify the image you're about to run, use the [GitHub CLI](https://cli.github.com/):

```bash
gh attestation verify oci://ghcr.io/lissy93/dashy:latest --repo lissy93/dashy
```

A green check means it was genuinely built by us, from our repo. Worth doing on a fresh Proxmox or homelab box, especially before exposing Dashy beyond your LAN.

To pull the SBOM and inspect what's inside, use [cosign](https://github.com/sigstore/cosign):

```bash
cosign download sbom ghcr.io/lissy93/dashy:latest
```

### Specify the Tag

Using fixed tags (as opposed to `:latest` ) will ensure immutability, meaning the base image will not change between builds. Note that for Dashy, the app is being actively developed, new features, bug fixes and general improvements are merged each week, and if you use a fixed version you will not enjoy these benefits. So it's up to you weather you would prefer a stable and reproducible environment, or the latest features and enhancements.
Expand Down
8 changes: 4 additions & 4 deletions services/auth-oidc.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ async function getIssuerContext(issuer) {
return ctx;
}

/* Pure helper: true when the token's claims map to the configured admin group/role.
Handles standard OIDC top-level `groups`/`roles` claims plus Keycloak's nested
realm_access / resource_access role shapes (mirrors what KeycloakAuth.js does
client-side). */
/* Helper is true when the token's claims map to the configured admin group/role.
Handles standard OIDC top-level `groups`/`roles` claims, GitLab's `groups_direct`
plus Keycloak's nested realm_access / resource_access role shapes */
function deriveIsAdmin(claims, settings) {
if (!claims) return false;
const groups = Array.isArray(claims.groups) ? [...claims.groups] : [];
if (Array.isArray(claims.groups_direct)) groups.push(...claims.groups_direct);
const roles = Array.isArray(claims.roles) ? [...claims.roles] : [];

if (settings.kind === 'keycloak') {
Expand Down
6 changes: 3 additions & 3 deletions src/mixins/ConfigSaving.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ export default {
})
.catch((error) => {
this.saveSuccess = false;
this.responseText = error;
this.showToast(error, false);
ErrorHandler(`Failed to save config. ${error}`);
this.responseText = error.response?.data?.message || error.message || String(error);
this.showToast(this.$t('config-editor.error-msg-cannot-save'), false);
ErrorHandler(`Failed to save config. ${this.responseText}`);
this.progress.end();
return false;
});
Expand Down
6 changes: 5 additions & 1 deletion src/utils/auth/OidcAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,11 @@ class OidcAuth {

/* Mirror the OIDC user into the localStorage keys other parts of Dashy read */
persistUserInfo(user) {
const { roles = [], groups = [] } = user.profile;
const { roles = [] } = user.profile;
// GitLab puts groups in `groups_direct`, so merge both claim names
const groups = [...new Set(
[user.profile.groups, user.profile.groups_direct].filter(Array.isArray).flat(),
)];
const info = { groups, roles };
const isAdmin = (Array.isArray(groups) && groups.includes(this.adminGroup))
|| (Array.isArray(roles) && roles.includes(this.adminRole))
Expand Down
35 changes: 35 additions & 0 deletions tests/server/auth-oidc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @vitest-environment node
import { describe, it, expect } from 'vitest';
import { deriveIsAdmin } from '../../services/auth-oidc';

const oidc = { kind: 'oidc', clientId: 'dashy', adminGroup: 'admins', adminRole: null };
const keycloak = { kind: 'keycloak', clientId: 'dashy', adminGroup: null, adminRole: 'admin' };

describe('deriveIsAdmin', () => {
it('matches adminGroup against the standard groups claim', () => {
expect(deriveIsAdmin({ groups: ['admins', 'users'] }, oidc)).toBe(true);
expect(deriveIsAdmin({ groups: ['users'] }, oidc)).toBe(false);
});

it('matches adminGroup against GitLab groups_direct claim', () => {
expect(deriveIsAdmin({ groups_direct: ['admins'] }, oidc)).toBe(true);
expect(deriveIsAdmin({ groups_direct: ['users'] }, oidc)).toBe(false);
});

it('matches adminRole against top-level roles claim', () => {
const settings = { ...oidc, adminGroup: null, adminRole: 'admin' };
expect(deriveIsAdmin({ roles: ['admin'] }, settings)).toBe(true);
});

it('matches Keycloak realm and client roles', () => {
expect(deriveIsAdmin({ realm_access: { roles: ['admin'] } }, keycloak)).toBe(true);
expect(deriveIsAdmin({ resource_access: { dashy: { roles: ['admin'] } } }, keycloak)).toBe(true);
expect(deriveIsAdmin({ realm_access: { roles: ['viewer'] } }, keycloak)).toBe(false);
});

it('is false on missing claims or no admin config', () => {
expect(deriveIsAdmin(null, oidc)).toBe(false);
expect(deriveIsAdmin({}, oidc)).toBe(false);
expect(deriveIsAdmin({ groups: ['admins'] }, { ...oidc, adminGroup: null })).toBe(false);
});
});