2 Commits
Author SHA1 Message Date
zemion 517dfb720e Record governed encryption lifecycle defaults 2026-08-04 14:01:44 +02:00
zemion 99a5fa52a5 Harden module package publication 2026-08-04 14:01:36 +02:00
2 changed files with 35 additions and 21 deletions
+4 -21
View File
@@ -14,6 +14,8 @@ on:
jobs: jobs:
publish-packages: publish-packages:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
GITEA_REPOSITORY: ${{ gitea.repository }}
steps: steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with: with:
@@ -29,7 +31,6 @@ jobs:
env: env:
REQUESTED_TAG: ${{ inputs.release_tag }} REQUESTED_TAG: ${{ inputs.release_tag }}
TRIGGER_TAG: ${{ gitea.ref_name }} TRIGGER_TAG: ${{ gitea.ref_name }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: | run: |
set -euo pipefail set -euo pipefail
tag="${REQUESTED_TAG:-$TRIGGER_TAG}" tag="${REQUESTED_TAG:-$TRIGGER_TAG}"
@@ -43,24 +44,6 @@ jobs:
echo "Release tag is not contained in main" >&2 echo "Release tag is not contained in main" >&2
exit 1 exit 1
} }
python - "$tag" <<'PY'
import fnmatch
import json
import os
import sys
import urllib.request
tag = sys.argv[1]
repository = os.environ["GITEA_REPOSITORY"]
request = urllib.request.Request(
f"{os.environ['GITEA_API_URL']}/repos/{repository}/tag_protections",
headers={"Authorization": f"token {os.environ['GITEA_TOKEN']}"},
)
with urllib.request.urlopen(request, timeout=30) as response:
protections = json.load(response)
if not any(fnmatch.fnmatchcase(tag, item.get("name_pattern", "")) for item in protections):
raise SystemExit(f"Release tag {tag!r} is not covered by repository tag protection")
PY
git checkout --detach "$tag" git checkout --detach "$tag"
printf 'RELEASE_TAG=%s\n' "$tag" >> "$GITEA_ENV" printf 'RELEASE_TAG=%s\n' "$tag" >> "$GITEA_ENV"
printf 'SOURCE_DATE_EPOCH=%s\n' "$(git show -s --format=%ct HEAD)" >> "$GITEA_ENV" printf 'SOURCE_DATE_EPOCH=%s\n' "$(git show -s --format=%ct HEAD)" >> "$GITEA_ENV"
@@ -130,7 +113,7 @@ jobs:
const escapedRepository = repository.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const escapedRepository = repository.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const gitTag = specifier.match( const gitTag = specifier.match(
new RegExp( new RegExp(
`^git\\+(?:ssh://git@|https://)git\\.add-ideas\\.de/GovOPlaN/${escapedRepository}\\.git#v([0-9]+\\.[0-9]+\\.[0-9]+)$`, `^git\\+(?:ssh://git@|https://)git\\.add-ideas\\.de/(?:GovOPlaN|add-ideas)/${escapedRepository}\\.git#v([0-9]+\\.[0-9]+\\.[0-9]+)$`,
), ),
); );
if (gitTag) { if (gitTag) {
@@ -203,7 +186,7 @@ jobs:
'@govoplan:registry=https://git.add-ideas.de/api/packages/GovOPlaN/npm/' \ '@govoplan:registry=https://git.add-ideas.de/api/packages/GovOPlaN/npm/' \
"//git.add-ideas.de/api/packages/GovOPlaN/npm/:_authToken=$PACKAGE_TOKEN" \ "//git.add-ideas.de/api/packages/GovOPlaN/npm/:_authToken=$PACKAGE_TOKEN" \
> "$npmrc" > "$npmrc"
NPM_CONFIG_USERCONFIG="$npmrc" npm publish "${webui_packages[0]}" \ NPM_CONFIG_USERCONFIG="$npmrc" npm publish "./${webui_packages[0]}" \
--ignore-scripts --access public \ --ignore-scripts --access public \
--registry https://git.add-ideas.de/api/packages/GovOPlaN/npm/ --registry https://git.add-ideas.de/api/packages/GovOPlaN/npm/
fi fi
+31
View File
@@ -18,6 +18,37 @@ certification claim. Stronger claims require a named protection profile whose
provider, clients, algorithms, backup procedure, and failure tests have passed provider, clients, algorithms, backup procedure, and failure tests have passed
the profile's conformance and security review. the profile's conformance and security review.
## Approved Product Baseline
The product decision recorded on 2026-08-04 separates module activation from
content protection. Enabling Encryption makes its capabilities and
administration available but never encrypts existing or new content by itself.
- System definitions are reusable profile templates. Tenants explicitly
activate profiles for an owner-module collection or individual object.
Accounts and groups remain policy subjects rather than cryptographic scopes.
- New objects inherit protection only inside an explicitly activated scope.
Existing objects move through an explicit, checkpointed, copy-on-write
migration with verified cutover.
- The first production profile is managed server-envelope encryption backed by
a KMS/HSM provider. The bundled local provider remains a bounded reference
implementation. Tenant-held and client E2EE profiles require separate
provider approval and conformance evidence.
- Recovery defaults to a high-assurance, distinct-custodian 2-of-3 quorum with
requester separation. Ordinary platform administration does not bypass the
ceremony.
- Horizontally scaled nodes may receive only short-lived, in-memory unwrap
grants. They do not persist usable key material on node-local storage.
- Disable remains blocked until every protected object is verifiably decrypted,
migrated, exported, or destroyed. Externally opaque ciphertext must be
resolved through its provider before the owning profile can be removed.
After a migration has retired plaintext, reversal is another governed
migration rather than retention of an undisclosed plaintext rollback copy.
True E2EE is deliberately not part of the first production profile because it
changes search, server-side processing, inspection, reporting, legal-hold
export, and recovery guarantees.
## Ownership Boundary ## Ownership Boundary
Encryption owns: Encryption owns: