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
55 changes: 55 additions & 0 deletions docs/src/.vuepress/components/ImageVerification.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<template>
<div class="image-verification">
<h2>Verify Image Signature</h2>
<p>
The <code>{{ image }}</code> image is signed using
<a href="https://docs.sigstore.dev/cosign/overview/" target="_blank" rel="noopener">Cosign</a>
keyless signing via the official DSF GitHub Actions release workflow.
Verify the signature before using the image in production:
</p>
<pre><code>cosign verify \
ghcr.io/datasharingframework/{{ image }}:{{ resolvedTag }}@sha256:{{ digestDisplay }} \
--certificate-identity-regexp "https://github.com/datasharingframework/dsf/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"</code></pre>
<p v-if="!resolvedDigest">
Replace <code>&lt;digest&gt;</code> with the immutable digest of the image
you intend to deploy. See
<a :href="guide">How to Verify Image Signatures</a>
for the complete guide, SBOM verification, and troubleshooting.
</p>
<p v-else>
See <a :href="guide">How to Verify Image Signatures</a> for the complete
guide, SBOM verification, and troubleshooting.
</p>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { getReleaseFromPath } from '../data/releases';

const props = defineProps<{
image: string;
tag?: string;
digest?: string;
guide?: string;
}>();

const route = useRoute();

const release = computed(() => getReleaseFromPath(route.path));

const resolvedTag = computed(() => props.tag ?? release.value?.tag ?? '<tag>');

const resolvedDigest = computed(() => {
if (props.digest) return props.digest.replace(/^sha256:/, '');
const fromData = release.value?.images[props.image as keyof NonNullable<typeof release.value>['images']]?.digest;
if (fromData && fromData !== 'sha256:TODO') return fromData.replace(/^sha256:/, '');
return '';
});

const digestDisplay = computed(() => resolvedDigest.value || '<digest>');

const guide = computed(() => props.guide ?? '../image-verification');
</script>
29 changes: 28 additions & 1 deletion docs/src/.vuepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { viteBundler } from '@vuepress/bundler-vite';
import { defineUserConfig } from "vuepress";
import theme from "./theme.js";
import { releaseVarsPlugin, replaceReleaseTokens } from "./markdown/releaseVarsPlugin.js";
import { getReleaseFromPath } from "./data/releases.js";


export default defineUserConfig({
Expand Down Expand Up @@ -28,7 +30,32 @@ export default defineUserConfig({
},
},*/
plugins: [

{
name: 'release-vars',
extendsMarkdown: (md) => {
md.use(releaseVarsPlugin);
},
extendsPage: (page) => {
const lookup = page.filePathRelative ? '/' + page.filePathRelative : page.path;
const release = getReleaseFromPath(lookup);
if (!release) return;
const walk = (obj: Record<string, unknown> | undefined) => {
if (!obj) return;
for (const key of Object.keys(obj)) {
const v = obj[key];
if (typeof v === 'string') {
obj[key] = replaceReleaseTokens(v, release);
}
}
};
walk(page.frontmatter as Record<string, unknown>);
walk(page.routeMeta as Record<string, unknown>);
walk(page.data as unknown as Record<string, unknown>);
if (typeof page.title === 'string') {
page.title = replaceReleaseTokens(page.title, release);
}
},
},
],

// Enable it with pwa
Expand Down
40 changes: 40 additions & 0 deletions docs/src/.vuepress/data/releases.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export interface ReleaseImage {
digest?: string;
}

export interface Release {
tag: string;
previousTag?: string;
images: {
fhir: ReleaseImage;
fhir_proxy: ReleaseImage;
bpe: ReleaseImage;
bpe_proxy: ReleaseImage;
};
}

export const releases: Record<string, Release> = {
'2.1.0': {
tag: '2.1.0',
previousTag: '2.0.2',
images: {
fhir: { digest: 'sha256:71599af143f0262a7265aa2bc4ea5a9660f11de6248a053e060b5667070203fd' },
fhir_proxy: { digest: 'sha256:9f11a3580c970314532f5951808be6fe72f1de7d53348e625d2dd0c95bcf1d96' },
bpe: { digest: 'sha256:3ee7ef0ac201fc3776273fbfc2569bdc4edf724a2bb9f1b4a889eb7e13ff4049' },
bpe_proxy: { digest: 'sha256:c67da4a1720ea75a383764db2bf25619fe70f57773b1069029f5b49588eb1ecc' },
},
},
};

export const latestVersion = '2.1.0';

export function getReleaseFromPath(path: string): Release | undefined {
const versionMatch = path.match(/(?:^|\/)operations\/v(\d+\.\d+\.\d+)\//);
if (versionMatch) return releases[versionMatch[1]];
if (/(?:^|\/)operations\/latest\//.test(path)) return releases[latestVersion];
return undefined;
}

export function tagUnderscored(tag: string): string {
return tag.replace(/\./g, '_');
}
5 changes: 3 additions & 2 deletions docs/src/.vuepress/layouts/PageLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useRoute, useRouter } from "vue-router";
import { ref, onMounted } from 'vue'

const version = ref("");
const latestVersion = "v2.0.2";
const latestVersion = "v2.1.0";


function setVersionBasedOnCurrentPath() : void {
Expand Down Expand Up @@ -55,7 +55,8 @@ function navigateToNewVersion() {
<div class="version-selector" v-if="route.path.startsWith('/operations/')">
<label class="vp-sidebar-header" for="version-select"><strong>Version:</strong> </label>
<select id="version-select" class="vp-sidebar-header" v-model="version" @change="navigateToNewVersion">
<option value="v2.0.2">latest (2.0.2)</option>
<option value="v2.1.0">latest (2.1.0)</option>
<option value="v2.0.2">2.0.2</option>
<option value="v2.0.1">2.0.1</option>
<option value="v2.0.0">2.0.0</option>
<option value="v1.9.0">1.9.0</option>
Expand Down
51 changes: 51 additions & 0 deletions docs/src/.vuepress/markdown/releaseVarsPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { PluginSimple } from 'markdown-it';
import { getReleaseFromPath, tagUnderscored, type Release } from '../data/releases.js';

const TOKEN_RE = /\{\{\s*release\.([a-zA-Z0-9_.]+)\s*\}\}/g;

export function replaceReleaseTokens(input: string, release: Release): string {
return input.replace(TOKEN_RE, (match, key) => {
const value = resolve(release, key);
return value !== undefined ? value : match;
});
}

function resolve(release: Release, key: string): string | undefined {
if (key === 'tag') return release.tag;
if (key === 'tagUnderscored') return tagUnderscored(release.tag);
if (key === 'previousTag') return release.previousTag;
if (key === 'previousTagUnderscored')
return release.previousTag ? tagUnderscored(release.previousTag) : undefined;

const imageMatch = key.match(/^image\.([a-z_]+)$/);
if (imageMatch) {
const name = imageMatch[1] as keyof Release['images'];
if (release.images[name]) return `ghcr.io/datasharingframework/${name}:${release.tag}`;
}
const previousImageMatch = key.match(/^previousImage\.([a-z_]+)$/);
if (previousImageMatch && release.previousTag) {
const name = previousImageMatch[1] as keyof Release['images'];
if (release.images[name]) return `ghcr.io/datasharingframework/${name}:${release.previousTag}`;
}
const digestMatch = key.match(/^digest\.([a-z_]+)$/);
if (digestMatch) {
const name = digestMatch[1] as keyof Release['images'];
const digest = release.images[name]?.digest;
if (!digest || /TODO/i.test(digest)) return '<digest>';
return digest.replace(/^sha256:/, '');
}
return undefined;
}

export const releaseVarsPlugin: PluginSimple = (md) => {
md.core.ruler.before('normalize', 'release-vars', (state) => {
const env = state.env ?? {};
const filePath: string = env.filePathRelative ?? env.filePath ?? '';
if (!filePath) return;

const release = getReleaseFromPath('/' + filePath.replace(/^\/+/, ''));
if (!release) return;

state.src = replaceReleaseTokens(state.src, release);
});
};
Binary file not shown.
Binary file not shown.
10 changes: 7 additions & 3 deletions docs/src/.vuepress/sidebar/operations-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ export function generate_v2_latest_sidebar() {
icon: "tool",
link: "./",
},
"release-notes", "install", "upgrade-from-2", "upgrade-from-1", "allowList-mgm", "root-certificates", "passwords-secrets", {
"release-notes", "install", "upgrade-from-2", "upgrade-from-1", "allowList-mgm", "root-certificates", "passwords-secrets", "image-verification", "hardening-measures", {
text: "FHIR Reverse Proxy",
icon: "module",
prefix: "fhir-reverse-proxy/",
link: "fhir-reverse-proxy/",
children: [
{
icon: "config",
text: "Configuration",
link: "fhir-reverse-proxy/configuration",
link: "configuration",
}
]
},
Expand Down Expand Up @@ -40,11 +42,13 @@ export function generate_v2_latest_sidebar() {
}, {
text: "BPE Reverse Proxy",
icon: "module",
prefix: "bpe-reverse-proxy/",
link: "bpe-reverse-proxy/",
children: [
{
icon: "config",
text: "Configuration",
link: "bpe-reverse-proxy/configuration",
link: "configuration",
}
]
}, {
Expand Down
5 changes: 3 additions & 2 deletions docs/src/.vuepress/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default hopeTheme({
icon: "launch",
prefix: "/operations/",
children: [ {
text: "Current Version - 2.0.2",
text: "Current Version - 2.1.0",
link: "get-started.md",
icon: "launch"
}, "old-versions.md"],
Expand Down Expand Up @@ -127,7 +127,8 @@ export default hopeTheme({
"/operations/old-versions": [],
"/operations/latest/": generate_v2_latest_sidebar(),
"/operations/next/": [],
"/operations/v2.0.2/": generate_v2_latest_sidebar(),
"/operations/v2.1.0/": generate_v2_latest_sidebar(),
"/operations/v2.0.2/": generate_v2_0_0_sidebar(),
"/operations/v2.0.1/": generate_v2_0_0_sidebar(),
"/operations/v2.0.0/": generate_v2_0_0_sidebar(),
"/operations/v1.9.0/": generate_v1_latest_sidebar(),
Expand Down
2 changes: 1 addition & 1 deletion docs/src/operations/latest
1 change: 1 addition & 0 deletions docs/src/operations/old-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ icon: launch

## DSF v2

- [2.1.0](./v2.1.0/)
- [2.0.1](./v2.0.1/)
- [2.0.0](./v2.0.0/)

Expand Down
34 changes: 34 additions & 0 deletions docs/src/operations/v2.1.0/allowList-mgm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: Allow List Management
icon: share
---
You can read all about the concept of Allow Lists [in our introduction](/explore/concepts/allow-list.md).

## Overview
To simplify the DSF Allow List Management we have built a portal for administration. The portal is managed by the GECKO Institute at Heilbronn University. You as an DSF administrator can create or update your Allow List information. The information you provide on this portal will be transferred to us and will be used to built Allow List bundles that get distributed to the communication partners of the distributed processes.

The DSF Allow List management tool uses client certificates for authentication. You can either use a personal client certificate or the client certificate from your DSF BPE, which needs to be added to your web-browsers certificate store.


## Prerequisites
1. Deployed DSF instance (test or production infrastructure)
1.1 If none exists yet, read [the installation guide](install)
2. Certificate
2.1 If none exists yet, read [the certificate requirements](install#client-server-certificates)
3. Organization identifier, shortest FQDN of your organizations website, e.g. `my-hospital.de`
4. FHIR endpoint URL, e.g. `https://dsf.my-hospital.de/fhir`
5. Contact details from a responsible person of your organization
6. Access to the E-Mail address from your organization for verification


## Start here
When you have fulfilled all the prerequisites, you can start managing your Allow Lists via the environment specific Allow List Management Tool:

- [**Test** infrastructure](https://allowlist-test.gecko.hs-heilbronn.de)
- [**Production** infrastructure](https://allowlist.gecko.hs-heilbronn.de)

We use different highlight colors for the DSF Allow List Management Tool: Green for the **Test** environment and blue for the **Production** infrastructure. To access the site, you have to authenticate yourself with a client certificate. Your web-browser will show a dialog to choose a valid certificate.

::: tip Ideas for improvement?
Have you found an error or is something unclear to you? Then please feel free to contact us on the <a href="https://mii.zulipchat.com/#narrow/stream/392426-Data-Sharing-Framework-.28DSF.29">MII-Zulip Channel</a> or write us at <a href="mailto:dsf-gecko@hs-heilbronn.de">dsf-gecko@hs-heilbronn.de</a>. Thank you very much!
:::
29 changes: 29 additions & 0 deletions docs/src/operations/v2.1.0/bpe-reverse-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: BPE Reverse Proxy
icon: module
---

## Purpose

The **DSF BPE Reverse Proxy** is an Apache HTTP Server based front for the [BPE Server](../bpe/). It terminates TLS for the BPE's web UI and OIDC-authenticated administrative endpoints, and forwards authenticated requests to the BPE backend. Unlike the [FHIR Reverse Proxy](../fhir-reverse-proxy/), it is intended for internal operator and administrator access only, not for DSF-to-DSF traffic.

## Docker Image

- Registry: [`ghcr.io/datasharingframework/bpe_proxy`](https://github.com/datasharingframework/dsf/pkgs/container/bpe_proxy)
- Tag for this release: `{{release.tag}}`

## Verify Image Signature

Verify the signed image before deploying. See [How to Verify Image Signatures](../image-verification) for prerequisites, SBOM verification, and troubleshooting.

```bash
cosign verify \
{{release.image.bpe_proxy}}@sha256:{{release.digest.bpe_proxy}} \
--certificate-identity-regexp "https://github.com/datasharingframework/dsf/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
```

## Useful Pages

- [Configuration Parameters](configuration)
- [How to Verify Image Signatures](../image-verification)
Loading