0.4.0
The first release, with the app image, the Helm chart and these notes published together. It changes almost everything since the last deployed version (Helm chart 2, FOCA 0.12, images tagged by date) and the legacy charts (2.x and 3.0.0): the registry now requires FOCA 0.17.0 (or a later 0.17 release) and Python 3.13 or later, and has owner-based access control, unique services, verifications by admins and a hardened chart and image. Many of the changes are breaking: redeploy with a fresh install and an empty database, and read the upgrade steps first. See the documentation for details.
Breaking changes¶
- API: writes need an access token of a trusted issuer
(
security.auth.issuers) for one of the configured audiences, and a role: any authenticated user can register services (POST /services) and becomes their owner; owners can update and delete their services and grant the rolesownerandmaintainer(updates only); admins can do everything. Only admins can register services with chosen identifiers (PUTof a new identifier) (#62, #106; #166, #183) - API: bearer tokens must be access tokens as per RFC 9068 (
typheaderat+jwt); other tokens, such as ID tokens, are rejected (401) (#156, #165) - API:
POST /service-infois removed (405); the service info comes from the config only (service_info.service, served by FOCA), and service info stored in the database is no longer served.GET /service-infois unchanged, and its requests are logged as before (#100, #107) - API: services are unique by URL (normalized) and API type (
type.group,type.artifact): registering one again, or changing a service to the URL and API type of another one, returns409 Conflictnaming the registered service (serviceId), to users who may read it (#102, #108; #203, #208) - API: URLs must be HTTP(S) URLs with a valid host, without user info,
whitespace, control or format characters, characters that URLs only allow
percent-encoded (e.g.,
<,|) or malformed percent-encodings (contactUrlcan also be amailto:URL); timestamps (createdAt,updatedAt) must be RFC 3339 date-times; strings are limited in length; others return400(#115, #118; #114, #119; #157, #169) - API: service URLs (
url) must behttpsURLs by default, as clients send access tokens to the registered services;httpURLs return400unless allowed (custom.endpoints.services.url_schemes, see the configuration changes below). Links keep accepting HTTP(S) URLs (contactUrlalsomailto:) (#147, #152) - API: registering a service (
POST /services,PUTof a new identifier) returns201 Createdwith aLocationheader instead of200(#78, #110) - API: the identifiers
.,..andtypesare rejected (400) (#117, #121) - API: request bodies are limited to 1 MiB by default (
413 Content Too Large), also chunked ones, before the access token is checked; requests with an invalidContent-Lengthare rejected (400) (#114, #119) - API: errors handled by the app are problem details, sent as
application/json, as the GA4GH specification declares (including those of authentication, access control and unknown paths);400responses say why the request was rejected. Error responses keep the headers of the HTTP errors, e.g.,Allow(405) andWWW-Authenticate: Bearer(401, witherror="invalid_token"for invalid tokens). Gunicorn rejects malformed requests itself (e.g., with an invalidContent-Length, or a request line or headers over its limits), with HTML error pages (#75, #96; #116, #120; #127) - Configuration: custom configs must apply the config changes of FOCA 0.14,
0.15, 0.16 and 0.17 (e.g.,
exceptions.mapping; see the FOCA release notes and migration guides). Those of FOCA 0.17: the API specifications (api.specs) and the policies (security.access_control.policies) can be named entries, which overlays add, change or remove (null) by name; FOCA refuses to start if an overlay gives named entries for a section that earlier files set to a non-empty list (see below). Specifications can be patched (api.specs.<name>.patches, e.g., to require login for reads, see "Features"). Configured policies are replaced atomically on startup, and configured rules stored before are removed ifsecurity.access_control.policiesisn't set; deployments that share a policy collection must all configure the same rules. Rules added via the permissions API with the values of a configured rule stay deletable via the API, and stay stored when the rule is removed from the config. Deployments ofdevimages built with FOCA 0.16: upgrade all instances before changing configured rules, and don't start the older images again afterwards (#92; #127; #156, #165; #178, #184) - Configuration: the service info is configured in FOCA's section
service_info.service(its version comes fromservice_info.package); configs setting it incustom.endpoints.service_infoare rejected (#127) - Configuration: the size limit of request bodies is FOCA's
server.max_content_length(Helm chart:config.server.max_content_length; the chart sets no limit for ingress controllers, see "Helm chart" below); configs settingcustom.max_content_length, as for images built fromdevbefore the FOCA 0.15 upgrade, are rejected (#114, #119; #127) - Configuration: the identity provider is deployment configuration, without defaults: the base config trusts no token issuers, reads no claims from the user info and sets no group claim. Deployments must set the issuers, the group claim, the audience and the admin group mapping (see step 4 of the upgrade steps on the Upgrading page, and "Deployment configuration" on the Configuration page of the documentation); otherwise, the app refuses to start, and the chart fails to render (#166, #183)
- Configuration: the app refuses to start if the service info is invalid;
its
idis required, and the default config no longer sets a version (the app's version is served) or timestamps (#30, #91; #75, #96) - Configuration: the default service info describes the app, not a
deployment, with placeholders using reserved example domains (RFC 2606):
idisorg.example.cloud-registry(wasELIXIR_CLOUD_SERVICE_REGISTRY_1),organizationisExample Organizationathttps://example.org, there is nocontactUrl,nameisCloud Registry(wasELIXIR_CLOUD), with a newdescription. Deployments set their own (service_info.service); the app logs a warning on startup whileidororganizationare the placeholders (#148, #160) - Configuration:
custom.endpoints.service.api_pathis now a path prefix in front of the API base path (e.g., of a reverse proxy), not the API base path itself (#75, #96) - Configuration:
custom.endpoints.services.meta_versionis removed; configs that set it are rejected (#74, #95) - Configuration: the API specification (
api.specs) and the policies (security.access_control.policies) are named entries (FOCA 0.17), so that overlays change single entries by name: the specification is namedregistry, the rules are named as in the base config. Overlays that give them as lists replace them completely, as before; FOCA refuses to start if a later overlay gives named entries (#178, #184) - Configuration:
custom.endpoints.services.url_schemeslists the schemes allowed in service URLs:[https]by default; Docker Compose (config.dev.yaml) allowshttptoo. On startup, the app warns of stored services with other schemes; they are still served, but can only be updated with an allowed scheme (see "Stored services" on the Upgrading page of the documentation) (#147, #152) - Helm chart: the chart version follows the app version (0.4.0, after 4.0.0
in the repository); the chart attached to a release deploys the image of
that release (
cloudRegistry.tagdefaults to the chart'sappVersion) (#81, #112) - Helm chart: values keys are camelCase, following Helm's conventions:
host_nameis renamed tohostNameandcloud_registrytocloudRegistry; rendering fails while the old keys are set (#128) - Helm chart: resource names are derived from the release name, and the pod
selectors changed, so releases of the legacy charts must be uninstalled
(see the upgrade steps).
appNameof the legacy charts is removed (usefullnameOverride) (#82, #109) - Helm chart: the chart no longer creates the
app-configConfigMap, the config Job, its ServiceAccount and RoleBinding, and theapiServervalue is gone: the app config is set viaconfig, rendered into a config overlay.hostNameis required (#67, #98) - Helm chart: MongoDB is pinned to
mongo:7.0(instead ofmongo:noble) and requires authentication (mongodb.auth.existingSecret), with probes (#68, #99) - Helm chart: both pods run with restrictive security contexts (non-root, read-only root file system, no capabilities) and without service account tokens (#82, #109; #117, #121)
- Helm chart: a NetworkPolicy lets only the app's pods of the release
connect to MongoDB (
mongodb.networkPolicy.enabled, defaulttrue), if the cluster enforces NetworkPolicies; other pods that connected to MongoDB directly (e.g., backup jobs) need a NetworkPolicy of their own, or the policy disabled (#128) - Helm chart: rendering fails without an audience, an admin group mapping
(unless admins are optional),
hostNameor the MongoDB Secret (unless authentication is disabled) (#68, #99; #166, #183) - Helm chart: the chart needs images built with FOCA 0.17 (0.4.0 or later)
for named entries and spec patches in
config(e.g., the login-for-reads patch,config.api.specs.registry.patches); see "Configuration" indeployment/README.md(#178, #184; #189, #193) - Helm chart: MongoDB's volume is set via
mongodb.persistence(size,accessModes,storageClass,existingClaim) instead ofmongodb.volumeSize(as in the legacy charts), which is deprecated: rendering fails while it is set, as the chart setsmongodb.persistence.size, so rename it. MongoDB's claim is kept when the release is uninstalled or the claim is removed from it (helm.sh/resource-policy: keep, viamongodb.persistence.resourcePolicy): delete it by hand to remove the data (#158, #176; #179, #210) - Helm chart: the Ingress sets no size limit of request bodies for
ingress-nginx (
nginx.ingress.kubernetes.io/proxy-body-size), as the chart has no defaults for particular ingress controllers; the limit of ingress-nginx, 1 MiB by default, is the app's default. With a higher limit (config.server.max_content_length), set the annotation incloudRegistry.ingress.annotations(see "Ingress" on the Deployment page of the documentation) (#179, #210) - Image:
latestis the highest stable release (not alpha, beta or release candidate) onmain; images of commits ondevare taggeddevandsha-<commit>. Date-tagged images are no longer pushed (#81, #112) - Image: the image serves the app with Gunicorn (settings via
GUNICORN_CMD_ARGS, in the chart viacloudRegistry.gunicorn) instead of Flask's development server, and no longer contains the app's source in/app(#116, #120; #77, #97) - Image: Docker Compose publishes the app on
127.0.0.1only (#77, #97) - Python: the app requires Python 3.13 or later, the Python version of the
image (based on
elixircloud/foca:0.17.0-py3.13), the only one it is tested with (#156, #165; #178, #184)
Features¶
- Owner-based access control with the roles
ownerandmaintainer(/services/{serviceId}/permissions), andGET /mewith the current user's identifier, admin status and roles (#62, #106) - Roles on services for groups
(
/services/{serviceId}/permissions/groups/{group}, with the group, a value of the group claim, URL-encoded): all members of the group have the role, e.g., a group of the team that runs the service. Owners can grant roles only to groups they are members of (403otherwise), admins to any group.GET /services/{serviceId}/permissionslists the roles of users and groups together ({"group": ..., "role": ...}for groups), andGET /mealso lists roles through groups (withgroup). Groups count as owners: a service may be left with a group as its only owner, which then depends on the group's membership in the identity provider. The base config setssecurity.access_control.keep_group_subjects: truefor this, so all group claim values are used as subjects, besides the admin group mapping. All members of an owner group can list the identifiers and names of all users with roles on the service, and remove the other owners, so don't grant roles to broad groups (e.g., all members of a virtual organization). Users removed from a group keep its roles until their token expires, or for up to 5 minutes if the group claim is read from the user info, as FOCA caches it per token (see "Group membership changes" on the Access control page of the documentation) (#105, #129) - Verifications of services by admins
(
/services/{serviceId}/verification,GET /verifications), without changing the GA4GH endpoints (#64, #111) - Login for reads: deployments can require login for all reads except
GET /service-infowith a config overlay that applies the patch shipped with the app (api/patches/login-for-reads.yaml); any authenticated user may then read, by default policies that are only evaluated for reads that require login (see "Requiring login for reads" on the Access control page of the documentation).409 Conflictfor duplicate services names the registered service only to users who may read it; others still learn that a service with the URL and API type is registered. So deployments that allow reads for some users only should restrict registration (register-services), too, and review the roles on services, as owners and maintainers can change a service to the URL and API type of another one (#168, #187; #189, #193; #203, #208) - Audit logs of role changes, deletions and verifications (#117, #121)
- Helm chart: a thin chart over FOCA's library chart
foca-common0.2.0, which the released chart includes, so that FOCA apps share one chart implementation. Values were not renamed or removed for it, except formongodb.volumeSize, which fails to render now (see above) (#158, #176; #179, #210) - Helm chart: the Gateway API: a Gateway API HTTPRoute
(
cloudRegistry.httpRoute, attached to an existing Gateway viaparentRefs), with an optional redirect from HTTP to HTTPS, instead of the Ingress; annotations of the OpenShift Route (cloudRegistry.route), which can be turned off (cloudRegistry.route.enabled: false), e.g., for a Route of one's own (see "Ingress" on the Deployment page of the documentation) (#179, #210) - Helm chart: an external MongoDB (
mongodb.enabled: false, withconfig.db.host) and the images' pull policies (cloudRegistry.imagePullPolicy,mongodb.imagePullPolicy;Alwaysby default, as before) can be set (#158, #176) - Helm chart: an Ingress with an optional cert-manager certificate, an
OpenShift Route otherwise, Gunicorn settings, probes, the standard labels
and NetworkPolicies (for MongoDB by default, optionally for the app:
cloudRegistry.networkPolicy, whose peers, e.g., of the ingress controller, are set by the deployment, as the chart has no controller-specific defaults). The Ingress (or HTTPRoute, or Route) routes all paths, including FOCA's permissions API for admins (/admin/access-control/); see "Ingress" on the Deployment page of the documentation to keep it off the internet (#82, #109; #114, #119; #116, #120; #128; #196, #199)
Security¶
- Signed images and chart: images (releases,
devandsha-<commit>) come with SBOM and provenance attestations, a GitHub artifact attestation and a keyless Sigstore signature; the chart attached to the release has a GitHub artifact attestation and a Sigstore bundle (cloud-registry-0.4.0.tgz.sigstore.json). See "Verifying images and charts" on the Operations page of the documentation for how to verify them. Images are scanned for known vulnerabilities with Trivy in CI, before a release is published, and weekly (latest) (#172, #181; #188, #192)
Documentation¶
- A documentation site on Read the Docs (MkDocs Material), with pages on configuration, the API, access control, deployment, operations and upgrading (#80, #113; #132, #139; #137, #144)
- A code of conduct, a contributing guide, a security policy and citation metadata (#138, #146; #170, #175; #174, #180)
Maintenance¶
- Releases are published from signed version tags by a release workflow, with checks of the tag, the citation metadata, the release notes and the pinned dependencies (#81, #112; #153, #159; #155, #161; #190, #191)
- Checks with pre-commit (ruff, mypy, semgrep, ast-grep, gitleaks, actionlint), Google-style docstrings and complete type hints, and tests against a real MongoDB at 100% coverage (#31, #73, #94; #136, #145; #149, #162; #150, #151, #163; #154, #164)