Skip to content

Commit 76246bd

Browse files
committed
v2.1.0 changes
1 parent 43f031a commit 76246bd

16 files changed

Lines changed: 328 additions & 375 deletions

File tree

docs/src/.vuepress/components/ImageVerification.vue

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
Verify the signature before using the image in production:
99
</p>
1010
<pre><code>cosign verify \
11-
ghcr.io/datasharingframework/{{ image }}:{{ tag }}@sha256:{{ digestDisplay }} \
11+
ghcr.io/datasharingframework/{{ image }}:{{ resolvedTag }}@sha256:{{ digestDisplay }} \
1212
--certificate-identity-regexp "https://github.com/datasharingframework/dsf/.*" \
1313
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"</code></pre>
14-
<p v-if="!digest">
14+
<p v-if="!resolvedDigest">
1515
Replace <code>&lt;digest&gt;</code> with the immutable digest of the image
1616
you intend to deploy. See
1717
<a :href="guide">How to Verify Image Signatures</a>
@@ -24,18 +24,32 @@
2424
</div>
2525
</template>
2626

27-
<script>
28-
export default {
29-
props: {
30-
image: { type: String, required: true },
31-
tag: { type: String, default: '2.1.0' },
32-
digest: { type: String, default: '' },
33-
guide: { type: String, default: '../image-verification' }
34-
},
35-
computed: {
36-
digestDisplay() {
37-
return this.digest ? this.digest.replace(/^sha256:/, '') : '<digest>';
38-
}
39-
}
40-
}
27+
<script setup lang="ts">
28+
import { computed } from 'vue';
29+
import { useRoute } from 'vue-router';
30+
import { getReleaseFromPath } from '../data/releases';
31+
32+
const props = defineProps<{
33+
image: string;
34+
tag?: string;
35+
digest?: string;
36+
guide?: string;
37+
}>();
38+
39+
const route = useRoute();
40+
41+
const release = computed(() => getReleaseFromPath(route.path));
42+
43+
const resolvedTag = computed(() => props.tag ?? release.value?.tag ?? '<tag>');
44+
45+
const resolvedDigest = computed(() => {
46+
if (props.digest) return props.digest.replace(/^sha256:/, '');
47+
const fromData = release.value?.images[props.image as keyof NonNullable<typeof release.value>['images']]?.digest;
48+
if (fromData && fromData !== 'sha256:TODO') return fromData.replace(/^sha256:/, '');
49+
return '';
50+
});
51+
52+
const digestDisplay = computed(() => resolvedDigest.value || '<digest>');
53+
54+
const guide = computed(() => props.guide ?? '../image-verification');
4155
</script>

docs/src/.vuepress/config.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { viteBundler } from '@vuepress/bundler-vite';
22
import { defineUserConfig } from "vuepress";
33
import theme from "./theme.js";
4+
import { releaseVarsPlugin, replaceReleaseTokens } from "./markdown/releaseVarsPlugin.js";
5+
import { getReleaseFromPath } from "./data/releases.js";
46

57

68
export default defineUserConfig({
@@ -28,7 +30,32 @@ export default defineUserConfig({
2830
},
2931
},*/
3032
plugins: [
31-
33+
{
34+
name: 'release-vars',
35+
extendsMarkdown: (md) => {
36+
md.use(releaseVarsPlugin);
37+
},
38+
extendsPage: (page) => {
39+
const lookup = page.filePathRelative ? '/' + page.filePathRelative : page.path;
40+
const release = getReleaseFromPath(lookup);
41+
if (!release) return;
42+
const walk = (obj: Record<string, unknown> | undefined) => {
43+
if (!obj) return;
44+
for (const key of Object.keys(obj)) {
45+
const v = obj[key];
46+
if (typeof v === 'string') {
47+
obj[key] = replaceReleaseTokens(v, release);
48+
}
49+
}
50+
};
51+
walk(page.frontmatter as Record<string, unknown>);
52+
walk(page.routeMeta as Record<string, unknown>);
53+
walk(page.data as unknown as Record<string, unknown>);
54+
if (typeof page.title === 'string') {
55+
page.title = replaceReleaseTokens(page.title, release);
56+
}
57+
},
58+
},
3259
],
3360

3461
// Enable it with pwa
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export interface ReleaseImage {
2+
digest?: string;
3+
}
4+
5+
export interface Release {
6+
tag: string;
7+
previousTag?: string;
8+
images: {
9+
fhir: ReleaseImage;
10+
fhir_proxy: ReleaseImage;
11+
bpe: ReleaseImage;
12+
bpe_proxy: ReleaseImage;
13+
};
14+
}
15+
16+
export const releases: Record<string, Release> = {
17+
'2.1.0': {
18+
tag: '2.1.0',
19+
previousTag: '2.0.2',
20+
images: {
21+
fhir: { digest: 'sha256:TODO' },
22+
fhir_proxy: { digest: 'sha256:TODO' },
23+
bpe: { digest: 'sha256:2087a12f2cc8386a019bc9706f8bcfdcd5ee9e12da07ca2bd5f09aaaba8133d7' },
24+
bpe_proxy: { digest: 'sha256:TODO' },
25+
},
26+
},
27+
};
28+
29+
export const latestVersion = '2.1.0';
30+
31+
export function getReleaseFromPath(path: string): Release | undefined {
32+
const versionMatch = path.match(/(?:^|\/)operations\/v(\d+\.\d+\.\d+)\//);
33+
if (versionMatch) return releases[versionMatch[1]];
34+
if (/(?:^|\/)operations\/latest\//.test(path)) return releases[latestVersion];
35+
return undefined;
36+
}
37+
38+
export function tagUnderscored(tag: string): string {
39+
return tag.replace(/\./g, '_');
40+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { PluginSimple } from 'markdown-it';
2+
import { getReleaseFromPath, tagUnderscored, type Release } from '../data/releases.js';
3+
4+
const TOKEN_RE = /\{\{\s*release\.([a-zA-Z0-9_.]+)\s*\}\}/g;
5+
6+
export function replaceReleaseTokens(input: string, release: Release): string {
7+
return input.replace(TOKEN_RE, (match, key) => {
8+
const value = resolve(release, key);
9+
return value !== undefined ? value : match;
10+
});
11+
}
12+
13+
function resolve(release: Release, key: string): string | undefined {
14+
if (key === 'tag') return release.tag;
15+
if (key === 'tagUnderscored') return tagUnderscored(release.tag);
16+
if (key === 'previousTag') return release.previousTag;
17+
if (key === 'previousTagUnderscored')
18+
return release.previousTag ? tagUnderscored(release.previousTag) : undefined;
19+
20+
const imageMatch = key.match(/^image\.([a-z_]+)$/);
21+
if (imageMatch) {
22+
const name = imageMatch[1] as keyof Release['images'];
23+
if (release.images[name]) return `ghcr.io/datasharingframework/${name}:${release.tag}`;
24+
}
25+
const previousImageMatch = key.match(/^previousImage\.([a-z_]+)$/);
26+
if (previousImageMatch && release.previousTag) {
27+
const name = previousImageMatch[1] as keyof Release['images'];
28+
if (release.images[name]) return `ghcr.io/datasharingframework/${name}:${release.previousTag}`;
29+
}
30+
const digestMatch = key.match(/^digest\.([a-z_]+)$/);
31+
if (digestMatch) {
32+
const name = digestMatch[1] as keyof Release['images'];
33+
const digest = release.images[name]?.digest;
34+
if (!digest || /TODO/i.test(digest)) return '<digest>';
35+
return digest.replace(/^sha256:/, '');
36+
}
37+
return undefined;
38+
}
39+
40+
export const releaseVarsPlugin: PluginSimple = (md) => {
41+
md.core.ruler.before('normalize', 'release-vars', (state) => {
42+
const env = state.env ?? {};
43+
const filePath: string = env.filePathRelative ?? env.filePath ?? '';
44+
if (!filePath) return;
45+
46+
const release = getReleaseFromPath('/' + filePath.replace(/^\/+/, ''));
47+
if (!release) return;
48+
49+
state.src = replaceReleaseTokens(state.src, release);
50+
});
51+
};

docs/src/.vuepress/sidebar/operations-v2.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export function generate_v2_latest_sidebar() {
44
icon: "tool",
55
link: "./",
66
},
7-
"release-notes", "install", "upgrade-from-2", "upgrade-from-1", "allowList-mgm", "root-certificates", "passwords-secrets", "image-verification", {
7+
"release-notes", "install", "upgrade-from-2", "upgrade-from-1", "allowList-mgm", "root-certificates", "passwords-secrets", "image-verification", "hardening-measures", {
88
text: "FHIR Reverse Proxy",
99
icon: "module",
1010
prefix: "fhir-reverse-proxy/",

docs/src/operations/v2.1.0/bpe-reverse-proxy/README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@ icon: module
55

66
## Purpose
77

8-
The **DSF BPE Reverse Proxy** is an Apache HTTP Server based front for the [BPE Server](../bpe/).
9-
It terminates TLS for the BPE's web UI and OIDC-authenticated administrative
10-
endpoints, and forwards authenticated requests to the BPE backend. Unlike the
11-
[FHIR Reverse Proxy](../fhir-reverse-proxy/), it is intended for internal
12-
operator and administrator access only, not for DSF-to-DSF traffic.
8+
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.
139

1410
## Docker Image
1511

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

19-
<ImageVerification image="bpe_proxy" tag="2.1.0" guide="../image-verification" />
15+
## Verify Image Signature
16+
17+
Verify the signed image before deploying. See [How to Verify Image Signatures](../image-verification) for prerequisites, SBOM verification, and troubleshooting.
18+
19+
```bash
20+
cosign verify \
21+
{{release.image.bpe_proxy}}@sha256:{{release.digest.bpe_proxy}} \
22+
--certificate-identity-regexp "https://github.com/datasharingframework/dsf/.*" \
23+
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
24+
```
2025

2126
## Useful Pages
2227

docs/src/operations/v2.1.0/bpe/README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@ icon: module
55

66
## Purpose
77

8-
The **DSF Business Process Engine (BPE)** executes the BPMN 2.0 workflows that
9-
drive distributed data sharing processes between DSF instances. It listens for
10-
new `Task` resources on the local FHIR Server, runs the corresponding process
11-
plugin, and creates follow-up `Task` resources on remote FHIR Servers via its
12-
configured FHIR client connections. The BPE is an internal component and is not
13-
exposed to the public network — it talks to local systeme (e.g., the local FHIR Store) and to remote
14-
DSF FHIR Servers through their reverse proxies.
8+
The **DSF Business Process Engine (BPE)** executes the BPMN 2.0 workflows that drive distributed data sharing processes between DSF instances. It listens for new `Task` resources on the local FHIR Server, runs the corresponding process plugin, and creates follow-up `Task` resources on remote FHIR Servers via its configured FHIR client connections. The BPE is an internal component and is not exposed to the public network — it talks to local systeme (e.g., the local FHIR Store) and to remote DSF FHIR Servers through their reverse proxies.
159

1610
## Docker Image
1711

1812
- Registry: [`ghcr.io/datasharingframework/bpe`](https://github.com/datasharingframework/dsf/pkgs/container/bpe)
19-
- Tag for this release: `2.1.0`
13+
- Tag for this release: `{{release.tag}}`
2014

21-
<ImageVerification image="bpe" tag="2.1.0" guide="../image-verification" />
15+
## Verify Image Signature
16+
17+
Verify the signed image before deploying. See [How to Verify Image Signatures](../image-verification) for prerequisites, SBOM verification, and troubleshooting.
18+
19+
```bash
20+
cosign verify \
21+
{{release.image.bpe}}@sha256:{{release.digest.bpe}} \
22+
--certificate-identity-regexp "https://github.com/datasharingframework/dsf/.*" \
23+
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
24+
```
2225

2326
## Useful Pages
2427

0 commit comments

Comments
 (0)