Skip to content

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 roles owner and maintainer (updates only); admins can do everything. Only admins can register services with chosen identifiers (PUT of a new identifier) (#62, #106; #166, #183)
  • API: bearer tokens must be access tokens as per RFC 9068 (typ header at+jwt); other tokens, such as ID tokens, are rejected (401) (#156, #165)
  • API: POST /service-info is 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-info is 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, returns 409 Conflict naming 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 (contactUrl can also be a mailto: URL); timestamps (createdAt, updatedAt) must be RFC 3339 date-times; strings are limited in length; others return 400 (#115, #118; #114, #119; #157, #169)
  • API: service URLs (url) must be https URLs by default, as clients send access tokens to the registered services; http URLs return 400 unless allowed (custom.endpoints.services.url_schemes, see the configuration changes below). Links keep accepting HTTP(S) URLs (contactUrl also mailto:) (#147, #152)
  • API: registering a service (POST /services, PUT of a new identifier) returns 201 Created with a Location header instead of 200 (#78, #110)
  • API: the identifiers ., .. and types are 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 invalid Content-Length are 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); 400 responses say why the request was rejected. Error responses keep the headers of the HTTP errors, e.g., Allow (405) and WWW-Authenticate: Bearer (401, with error="invalid_token" for invalid tokens). Gunicorn rejects malformed requests itself (e.g., with an invalid Content-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 if security.access_control.policies isn'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 of dev images 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 from service_info.package); configs setting it in custom.endpoints.service_info are 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 setting custom.max_content_length, as for images built from dev before 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 id is 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): id is org.example.cloud-registry (was ELIXIR_CLOUD_SERVICE_REGISTRY_1), organization is Example Organization at https://example.org, there is no contactUrl, name is Cloud Registry (was ELIXIR_CLOUD), with a new description. Deployments set their own (service_info.service); the app logs a warning on startup while id or organization are the placeholders (#148, #160)
  • Configuration: custom.endpoints.service.api_path is 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_version is 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 named registry, 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_schemes lists the schemes allowed in service URLs: [https] by default; Docker Compose (config.dev.yaml) allows http too. 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.tag defaults to the chart's appVersion) (#81, #112)
  • Helm chart: values keys are camelCase, following Helm's conventions: host_name is renamed to hostName and cloud_registry to cloudRegistry; 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). appName of the legacy charts is removed (use fullnameOverride) (#82, #109)
  • Helm chart: the chart no longer creates the app-config ConfigMap, the config Job, its ServiceAccount and RoleBinding, and the apiServer value is gone: the app config is set via config, rendered into a config overlay. hostName is required (#67, #98)
  • Helm chart: MongoDB is pinned to mongo:7.0 (instead of mongo: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, default true), 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), hostName or 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" in deployment/README.md (#178, #184; #189, #193)
  • Helm chart: MongoDB's volume is set via mongodb.persistence (size, accessModes, storageClass, existingClaim) instead of mongodb.volumeSize (as in the legacy charts), which is deprecated: rendering fails while it is set, as the chart sets mongodb.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, via mongodb.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 in cloudRegistry.ingress.annotations (see "Ingress" on the Deployment page of the documentation) (#179, #210)
  • Image: latest is the highest stable release (not alpha, beta or release candidate) on main; images of commits on dev are tagged dev and sha-<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 via cloudRegistry.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.1 only (#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 owner and maintainer (/services/{serviceId}/permissions), and GET /me with 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 (403 otherwise), admins to any group. GET /services/{serviceId}/permissions lists the roles of users and groups together ({"group": ..., "role": ...} for groups), and GET /me also lists roles through groups (with group). 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 sets security.access_control.keep_group_subjects: true for 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-info with 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 Conflict for 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-common 0.2.0, which the released chart includes, so that FOCA apps share one chart implementation. Values were not renamed or removed for it, except for mongodb.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 via parentRefs), 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, with config.db.host) and the images' pull policies (cloudRegistry.imagePullPolicy, mongodb.imagePullPolicy; Always by 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, dev and sha-<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)