Skip to content

Commit 9c6f0c3

Browse files
authored
Merge pull request #160 from datasharingframework/v2.1.0
V2.1.0 docs
2 parents a36bce2 + 650c287 commit 9c6f0c3

37 files changed

Lines changed: 5193 additions & 9 deletions
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<template>
2+
<div class="image-verification">
3+
<h2>Verify Image Signature</h2>
4+
<p>
5+
The <code>{{ image }}</code> image is signed using
6+
<a href="https://docs.sigstore.dev/cosign/overview/" target="_blank" rel="noopener">Cosign</a>
7+
keyless signing via the official DSF GitHub Actions release workflow.
8+
Verify the signature before using the image in production:
9+
</p>
10+
<pre><code>cosign verify \
11+
ghcr.io/datasharingframework/{{ image }}:{{ resolvedTag }}@sha256:{{ digestDisplay }} \
12+
--certificate-identity-regexp "https://github.com/datasharingframework/dsf/.*" \
13+
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"</code></pre>
14+
<p v-if="!resolvedDigest">
15+
Replace <code>&lt;digest&gt;</code> with the immutable digest of the image
16+
you intend to deploy. See
17+
<a :href="guide">How to Verify Image Signatures</a>
18+
for the complete guide, SBOM verification, and troubleshooting.
19+
</p>
20+
<p v-else>
21+
See <a :href="guide">How to Verify Image Signatures</a> for the complete
22+
guide, SBOM verification, and troubleshooting.
23+
</p>
24+
</div>
25+
</template>
26+
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');
55+
</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:71599af143f0262a7265aa2bc4ea5a9660f11de6248a053e060b5667070203fd' },
22+
fhir_proxy: { digest: 'sha256:9f11a3580c970314532f5951808be6fe72f1de7d53348e625d2dd0c95bcf1d96' },
23+
bpe: { digest: 'sha256:3ee7ef0ac201fc3776273fbfc2569bdc4edf724a2bb9f1b4a889eb7e13ff4049' },
24+
bpe_proxy: { digest: 'sha256:c67da4a1720ea75a383764db2bf25619fe70f57773b1069029f5b49588eb1ecc' },
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+
}

docs/src/.vuepress/layouts/PageLayout.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useRoute, useRouter } from "vue-router";
44
import { ref, onMounted } from 'vue'
55
66
const version = ref("");
7-
const latestVersion = "v2.0.2";
7+
const latestVersion = "v2.1.0";
88
99
1010
function setVersionBasedOnCurrentPath() : void {
@@ -55,7 +55,8 @@ function navigateToNewVersion() {
5555
<div class="version-selector" v-if="route.path.startsWith('/operations/')">
5656
<label class="vp-sidebar-header" for="version-select"><strong>Version:</strong> </label>
5757
<select id="version-select" class="vp-sidebar-header" v-model="version" @change="navigateToNewVersion">
58-
<option value="v2.0.2">latest (2.0.2)</option>
58+
<option value="v2.1.0">latest (2.1.0)</option>
59+
<option value="v2.0.2">2.0.2</option>
5960
<option value="v2.0.1">2.0.1</option>
6061
<option value="v2.0.0">2.0.0</option>
6162
<option value="v1.9.0">1.9.0</option>
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+
};
1.59 KB
Binary file not shown.
1.81 KB
Binary file not shown.

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ 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", {
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",
10+
prefix: "fhir-reverse-proxy/",
11+
link: "fhir-reverse-proxy/",
1012
children: [
1113
{
1214
icon: "config",
1315
text: "Configuration",
14-
link: "fhir-reverse-proxy/configuration",
16+
link: "configuration",
1517
}
1618
]
1719
},
@@ -40,11 +42,13 @@ export function generate_v2_latest_sidebar() {
4042
}, {
4143
text: "BPE Reverse Proxy",
4244
icon: "module",
45+
prefix: "bpe-reverse-proxy/",
46+
link: "bpe-reverse-proxy/",
4347
children: [
4448
{
4549
icon: "config",
4650
text: "Configuration",
47-
link: "bpe-reverse-proxy/configuration",
51+
link: "configuration",
4852
}
4953
]
5054
}, {

docs/src/.vuepress/theme.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default hopeTheme({
3939
icon: "launch",
4040
prefix: "/operations/",
4141
children: [ {
42-
text: "Current Version - 2.0.2",
42+
text: "Current Version - 2.1.0",
4343
link: "get-started.md",
4444
icon: "launch"
4545
}, "old-versions.md"],
@@ -127,7 +127,8 @@ export default hopeTheme({
127127
"/operations/old-versions": [],
128128
"/operations/latest/": generate_v2_latest_sidebar(),
129129
"/operations/next/": [],
130-
"/operations/v2.0.2/": generate_v2_latest_sidebar(),
130+
"/operations/v2.1.0/": generate_v2_latest_sidebar(),
131+
"/operations/v2.0.2/": generate_v2_0_0_sidebar(),
131132
"/operations/v2.0.1/": generate_v2_0_0_sidebar(),
132133
"/operations/v2.0.0/": generate_v2_0_0_sidebar(),
133134
"/operations/v1.9.0/": generate_v1_latest_sidebar(),

docs/src/operations/latest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v2.0.2
1+
v2.1.0

0 commit comments

Comments
 (0)