Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
029406e9ee | ||
|
|
e1d0742c46 | ||
|
|
869f7bbdc4 | ||
|
|
517dfb720e | ||
|
|
99a5fa52a5 |
@@ -14,6 +14,8 @@ on:
|
||||
jobs:
|
||||
publish-packages:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
@@ -29,7 +31,6 @@ jobs:
|
||||
env:
|
||||
REQUESTED_TAG: ${{ inputs.release_tag }}
|
||||
TRIGGER_TAG: ${{ gitea.ref_name }}
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="${REQUESTED_TAG:-$TRIGGER_TAG}"
|
||||
@@ -43,24 +44,6 @@ jobs:
|
||||
echo "Release tag is not contained in main" >&2
|
||||
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"
|
||||
printf 'RELEASE_TAG=%s\n' "$tag" >> "$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 gitTag = specifier.match(
|
||||
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) {
|
||||
@@ -180,6 +163,78 @@ jobs:
|
||||
with:
|
||||
name: module-packages-${{ gitea.ref_name }}
|
||||
path: dist/package-artifacts.json
|
||||
- name: Check immutable registry state
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_TOKEN: ${{ secrets.GOVOPLAN_PACKAGE_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "$PACKAGE_TOKEN"
|
||||
python - <<'PY'
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import quote
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
api_root = "https://git.add-ideas.de/api/v1/packages/GovOPlaN"
|
||||
token = os.environ["PACKAGE_TOKEN"]
|
||||
|
||||
def should_publish(kind, name, version, path):
|
||||
package_url = "/".join(
|
||||
(api_root, kind, quote(name, safe=""), quote(version, safe=""), "files")
|
||||
)
|
||||
request = Request(
|
||||
package_url,
|
||||
headers={"Accept": "application/json", "Authorization": f"token {token}"},
|
||||
)
|
||||
try:
|
||||
with urlopen(request, timeout=30) as response:
|
||||
files = json.load(response)
|
||||
except HTTPError as exc:
|
||||
if exc.code == 404:
|
||||
print(f"{kind} package {name}=={version} is not published yet")
|
||||
return True
|
||||
raise
|
||||
if not isinstance(files, list) or len(files) != 1:
|
||||
raise SystemExit(
|
||||
f"immutable {kind} package {name}=={version} has an unexpected file set"
|
||||
)
|
||||
expected_sha256 = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
if files[0].get("sha256") != expected_sha256:
|
||||
raise SystemExit(
|
||||
f"immutable {kind} package {name}=={version} already exists with a different SHA-256"
|
||||
)
|
||||
print(f"verified existing {kind} package {name}=={version} ({expected_sha256})")
|
||||
return False
|
||||
|
||||
project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]
|
||||
wheels = tuple(Path("dist").glob("*.whl"))
|
||||
if len(wheels) != 1:
|
||||
raise SystemExit("release build must contain exactly one wheel")
|
||||
publish_pypi = should_publish(
|
||||
"pypi", str(project["name"]), str(project["version"]), wheels[0]
|
||||
)
|
||||
|
||||
tarballs = tuple(Path("dist").glob("*.tgz"))
|
||||
if len(tarballs) > 1:
|
||||
raise SystemExit("release build must contain at most one npm package")
|
||||
publish_npm = False
|
||||
if tarballs:
|
||||
webui = json.loads(
|
||||
Path(".package-webui/package.json").read_text(encoding="utf-8")
|
||||
)
|
||||
publish_npm = should_publish(
|
||||
"npm", str(webui["name"]), str(webui["version"]), tarballs[0]
|
||||
)
|
||||
|
||||
with Path(os.environ["GITEA_ENV"]).open("a", encoding="utf-8") as env_file:
|
||||
env_file.write(f"PUBLISH_PYPI={int(publish_pypi)}\n")
|
||||
env_file.write(f"PUBLISH_NPM={int(publish_npm)}\n")
|
||||
PY
|
||||
- name: Publish wheel and WebUI package
|
||||
shell: bash
|
||||
env:
|
||||
@@ -189,13 +244,17 @@ jobs:
|
||||
set -euo pipefail
|
||||
test -n "$PACKAGE_USERNAME"
|
||||
test -n "$PACKAGE_TOKEN"
|
||||
if [[ "$PUBLISH_PYPI" == 1 ]]; then
|
||||
TWINE_USERNAME="$PACKAGE_USERNAME" TWINE_PASSWORD="$PACKAGE_TOKEN" \
|
||||
python -m twine upload --non-interactive \
|
||||
--repository-url https://git.add-ideas.de/api/packages/GovOPlaN/pypi \
|
||||
dist/*.whl
|
||||
else
|
||||
echo "Exact wheel is already present; skipping immutable retry."
|
||||
fi
|
||||
shopt -s nullglob
|
||||
webui_packages=(dist/*.tgz)
|
||||
if (( ${#webui_packages[@]} )); then
|
||||
if (( ${#webui_packages[@]} )) && [[ "$PUBLISH_NPM" == 1 ]]; then
|
||||
npmrc="$(mktemp)"
|
||||
trap 'rm -f "$npmrc"' EXIT
|
||||
chmod 600 "$npmrc"
|
||||
@@ -203,7 +262,9 @@ jobs:
|
||||
'@govoplan:registry=https://git.add-ideas.de/api/packages/GovOPlaN/npm/' \
|
||||
"//git.add-ideas.de/api/packages/GovOPlaN/npm/:_authToken=$PACKAGE_TOKEN" \
|
||||
> "$npmrc"
|
||||
NPM_CONFIG_USERCONFIG="$npmrc" npm publish "${webui_packages[0]}" \
|
||||
NPM_CONFIG_USERCONFIG="$npmrc" npm publish "./${webui_packages[0]}" \
|
||||
--ignore-scripts --access public \
|
||||
--registry https://git.add-ideas.de/api/packages/GovOPlaN/npm/
|
||||
elif (( ${#webui_packages[@]} )); then
|
||||
echo "Exact WebUI package is already present; skipping immutable retry."
|
||||
fi
|
||||
|
||||
@@ -18,6 +18,37 @@ certification claim. Stronger claims require a named protection profile whose
|
||||
provider, clients, algorithms, backup procedure, and failure tests have passed
|
||||
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
|
||||
|
||||
Encryption owns:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/encryption",
|
||||
"version": "0.1.14",
|
||||
"version": "0.1.16",
|
||||
"private": true,
|
||||
"description": "GovOPlaN encryption platform module scaffold.",
|
||||
"type": "module",
|
||||
|
||||
+2
-2
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-encryption"
|
||||
version = "0.1.14"
|
||||
version = "0.1.16"
|
||||
description = "Optional key-vault and content-protection capabilities for GovOPlaN."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = [{ name = "GovOPlaN" }]
|
||||
dependencies = ["cryptography>=44", "govoplan-core>=0.1.14"]
|
||||
dependencies = ["cryptography>=44", "govoplan-core>=0.1.16"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
@@ -41,7 +41,7 @@ from govoplan_encryption.backend.service import SqlEncryptionService
|
||||
|
||||
MODULE_ID = "encryption"
|
||||
MODULE_NAME = "Encryption"
|
||||
MODULE_VERSION = "0.1.14"
|
||||
MODULE_VERSION = "0.1.16"
|
||||
|
||||
USE_SCOPE = "encryption:vault:use"
|
||||
ADMIN_SCOPE = "encryption:vault:admin"
|
||||
|
||||
+9
-4
@@ -1,23 +1,28 @@
|
||||
{
|
||||
"name": "@govoplan/encryption-webui",
|
||||
"version": "0.1.14",
|
||||
"version": "0.1.16",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"module": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"exports": {
|
||||
".": { "types": "./src/index.ts", "import": "./src/index.ts" },
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./src/index.ts"
|
||||
},
|
||||
"./styles/encryption.css": "./src/styles/encryption.css"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.14",
|
||||
"@govoplan/core-webui": "^0.1.16",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
"react-dom": ">=19.2.7 <20"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@govoplan/core-webui": { "optional": true }
|
||||
"@govoplan/core-webui": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test:encryption-ui": "node tests/encryption-ui-structure.test.mjs"
|
||||
|
||||
Reference in New Issue
Block a user