Changes for README.md: 1 added line, 1 removed line.
Original line number
Diff line number
Diff line
@@ -372,7 +372,7 @@ Values are case-insensitive. If it is missing, empty or contains any other value
### Invoking the discovered APIs
Once the APIs are discovered, `invoke_api()` calls them with the security method that CAPIF selected for each one: for OAUTH it obtains, caches and renews the access token of that API and sends it in the `Authorization` header, and for PKI it uses the certificate obtained when onboarding the invoker (PSK is not supported yet). The address of the AEF is taken from the discovered API, and requests always use HTTPS.
Once the APIs are discovered, `invoke_api()` calls them with the security method that CAPIF selected for each one: for OAUTH it obtains, caches and renews the access token of that API and sends it in the `Authorization` header, and for PKI it uses the certificate obtained when onboarding the invoker. For PSK, it connects with TLS-PSK using a pre-shared key that it derives when it creates the security context, which requires Python 3.13 or later and OpenCAPIF 5.1.0 or later (see [PSK security method](./doc/sdk_full_documentation.md#psk-security-method)). The address of the AEF is taken from the discovered API, and requests always use HTTPS.
Changes for README_pipy.md: 3 added lines, 1 removed line.
Original line number
Diff line number
Diff line
@@ -69,13 +69,15 @@ Version 1.0.0 includes changes that are not backward compatible with the 0.1.x v
- New `enable_default_logging()` function, explained in the Logging section below.
-`create_subscription()` returns the CAPIF ID of the new subscription. `EventSubscriptionError` can be imported from `opencapif_sdk`.
-`service_discoverer` is now a subclass of `capif_invoker_connector`, so it reads the invoker configuration in the same way and also offers its methods and attributes (e.g. `capif_username`). `capif_invoker_username` and `ca_root_path` are kept as aliases of `capif_username` and `pathca`.
-**Invoke the discovered APIs with `invoke_api()`** (or `get_api_session()` for a configured `requests` session). The SDK uses the security method selected by CAPIF: for OAUTH it obtains, caches and renews an access token for that API (`get_access_token_by_api_name()`), and for PKI it uses the invoker certificate; PSK is not supported yet. Requests always use HTTPS, verifying the AEF with the CAPIF CA root by default (`api_invocation.ca_cert`). Errors raise `ApiInvocationError`, which can be imported from `opencapif_sdk`.
-**Invoke the discovered APIs with `invoke_api()`** (or `get_api_session()` for a configured `requests` session). The SDK uses the security method selected by CAPIF: for OAUTH it obtains, caches and renews an access token for that API (`get_access_token_by_api_name()`), for PKI it uses the invoker certificate, and for PSK it connects with TLS-PSK (see below). Requests always use HTTPS, verifying the AEF with the CAPIF CA root by default (`api_invocation.ca_cert`). Errors raise `ApiInvocationError`, which can be imported from `opencapif_sdk`.
-**PSK security method** (3GPP TS 33.122). If `PSK` is in `pref_security_context`, the security context is created over TLS 1.2, and the SDK derives the pre-shared key (AEF_PSK) of each API at each AEF for which CAPIF selects PSK, in the same way as CAPIF. `invoke_api()` and `get_api_session()` then connect to the AEF with TLS-PSK, sending the `apiInvokerId` as PSK identity (configurable with `api_invocation.psk_identity` or the `INVOKER_API_INVOCATION_PSK_IDENTITY` environment variable). It requires Python 3.13 or later (the rest of the SDK keeps working with Python 3.9 or later) and OpenCAPIF 5.1.0 or later.
### Improvements and fixes
-`get_service()` and `get_all_services()` now return the services retrieved from CAPIF (they are still saved to `service_received.json`).
- When a request fails, the original `requests` exception (e.g. `Timeout`, `ConnectionError`, `HTTPError`) is raised instead of `UnboundLocalError`, and the logs include the HTTP status and body returned by CAPIF.
- An empty `capif_https_port` now means port 443 in every operation.
- The invoker details file (`capif_api_security_context_details-<capif_username>.json`) is only readable by the user (permissions `0600`), as it contains the access tokens and, with PSK, the pre-shared keys.
- The `PUBLISH_REQ_PUBLISHER_AEFS_IDS` environment variable accepts a comma-separated list (`aef1,aef2`) or a JSON list (`["aef1", "aef2"]`).
- Environment variables for configuration fields that hold lists or objects are now parsed instead of being used as plain text: `PROVIDER_EVENTS_DESCRIPTION` and `INVOKER_EVENTS_DESCRIPTION` (comma-separated or JSON list), `PROVIDER_EVENTS_FILTERS` and `INVOKER_EVENTS_FILTERS` (JSON list), `WEB_SOCKET_NOTIF_CONFIG` and `PROVIDER_LOG` (JSON object). An invalid value raises a `ValueError` that names the variable.
-`api_schema_translator.build()` uses port 443 for `https://` URLs without a port (it used 80).
Changes for doc/sdk_configuration.md: 3 added lines, 2 removed lines.
Original line number
Diff line number
Diff line
@@ -38,7 +38,7 @@ When configuring the SDK as a **Network App Invoker**, the following fields must
**Optional:**
-`discover_filter`: useful to enable the discovery of specific APIs. Some fields under [`discover_filter`](#configuration-of-discover_filter) structure required to be configured when using discovery filters. Check devoted section below,
-`check_authentication_data`: the `ip` and `port` of an AEF, used by the `check_authentication()` function when it is called without `api_name`.
-`api_invocation`: its `ca_cert` field sets the CA certificate used to verify the AEFs when invoking their APIs. See its description below.
-`api_invocation`: its `ca_cert` field sets the CA certificate used to verify the AEFs when invoking their APIs, and its `psk_identity` field the PSK identity sent to the AEFs with the PSK security method. See their descriptions below.
### Network App Provider
@@ -107,7 +107,8 @@ If one of these variables has an invalid value, the SDK raises a `ValueError` th
-[`publish_req`](#configuration-of-publish_req): Fields required for API publishing.
-`api_description_path`: The path to the [ServiceAPIDescription](https://github.com/jdegre/5GC_APIs/blob/Rel-18/TS29222_CAPIF_Publish_Service_API.yaml) JSON file.
-`check_authentication_data`: The `ip` and `port` of the target Provider's AEF to get their supported features from, used by `check_authentication()` when it is called without `api_name` (the AEF is called over HTTPS).
-`api_invocation.ca_cert` (invoker, optional): The CA certificate used to verify the AEFs when invoking their APIs with `invoke_api()` or `get_api_session()`, and when calling `check_authentication()`. By default, the CAPIF CA root certificate is used. It can also be set with the `INVOKER_API_INVOCATION_CA_CERT` environment variable.
-`api_invocation.ca_cert` (invoker, optional): The CA certificate used to verify the AEFs when invoking their APIs with `invoke_api()` or `get_api_session()`, and when calling `check_authentication()`. By default, the CAPIF CA root certificate is used. It can also be set with the `INVOKER_API_INVOCATION_CA_CERT` environment variable. It is not used with the PSK security method, as the AEF is authenticated by the pre-shared key.
-`api_invocation.psk_identity` (invoker, optional): The PSK identity sent to the AEFs in the TLS handshake when invoking APIs with the PSK security method. By default, the `apiInvokerId`. It can also be set with the `INVOKER_API_INVOCATION_PSK_IDENTITY` environment variable.
-`security_context.pref_security_context` (invoker, mandatory): The security methods to request to CAPIF for every discovered API, in order of preference: any of `OAUTH`, `PKI` and `PSK` (e.g., `["OAUTH", "PKI", "PSK"]`). It must contain at least one method, and only these values are accepted (case-insensitive); otherwise, creating a `service_discoverer` raises a `ValueError`. It can also be set with the `INVOKER_SECURITY_CONTEXT_PREF_SECURITY_CONTEXT` environment variable, as a comma-separated list (`OAUTH,PKI,PSK`) or a JSON list (`["OAUTH","PKI","PSK"]`); the environment variable takes precedence over the configuration file.
-`log`: The structure defined in the [Log schema](https://github.com/jdegre/5GC_APIs/blob/Rel-18/TS29222_CAPIF_Logging_API_Invocation_API.yaml), it is not needed to fulfill the `apiId` field.
-`events`: The structure defined in the [EventSubscription schema](https://github.com/jdegre/5GC_APIs/blob/Rel-18/TS29222_CAPIF_Events_API.yaml), It is only necessary the `description` and `eventFilters` fields, in case of provider is also mandatory to fulfill `notificationDestination` and `websockNotifConfig`
Registers or updates the invoker's security context in CAPIF for all the [discovered APIs](#service-discovery), requesting for each of them the security methods set in the mandatory `security_context.pref_security_context` setting of the [invoker configuration](./sdk_configuration.md) (at least one of `OAUTH`, `PKI` and `PSK`, in order of preference). It is called automatically by [get_tokens()](#obtain-jwt-tokens), and it can also be called on its own to check the security context before requesting tokens.
Registers or updates the invoker's security context in CAPIF for all the [discovered APIs](#service-discovery), requesting for each of them the security methods set in the mandatory `security_context.pref_security_context` setting of the [invoker configuration](./sdk_configuration.md) (at least one of `OAUTH`, `PKI` and `PSK`, in order of preference). It is called automatically by [get_tokens()](#obtain-jwt-tokens), and it can also be called on its own to check the security context before requesting tokens. If `PSK` is one of the preferred methods, the request is sent over TLS 1.2, and the SDK derives the keys of the APIs for which CAPIF selects PSK (see [PSK security method](#psk-security-method)).
The function returns the security context returned by CAPIF. Its `securityInfo` field shows, for each API, the security method selected by CAPIF (`selSecurityMethod`):
@@ -366,7 +366,7 @@ Invokes a [discovered API](#service-discovery) with the security method selected
-**OAUTH**: the request includes `Authorization: Bearer <token>`, with a token valid only for that API. The token is renewed when it expires, and also once if the AEF answers `401`.
-**PKI**: mutual TLS with the certificate obtained when onboarding the invoker.
-**PSK**: not supported yet; an `ApiInvocationError` is raised.
-**PSK**: TLS-PSK with a key derived from the security context, described in [PSK security method](#psk-security-method) below. It requires Python 3.13 or later and OpenCAPIF 5.1.0 or later.
The address of the AEF is taken from the discovered API: its `interfaceDescriptions` (`fqdn`, `ipv4Addr` or `ipv6Addr`, `port` and `apiPrefix`; port `443` if it is not set) or its `domainName`. Requests always use HTTPS, and the AEF certificate is verified with the CAPIF CA root by default (see `api_invocation.ca_cert` in the [configuration](./sdk_configuration.md), or the `verify` parameter).
@@ -388,12 +388,30 @@ with service_discoverer.get_api_session("6G-resilience", aef_id="AEF...") as ses
session.post("/resilience",json={"profileId":3})
```
**Errors**: `ApiInvocationError` if the API, AEF, version or resource are not found, the resource does not allow the method, a `{placeholder}` of the URI has no value in `path_params`, or `aef_id` (or `api_version`) is missing when several AEFs (or versions) are published; the `requests` exception (e.g. `HTTPError`) if the AEF returns an error status.
#### PSK security method
With PSK (3GPP TS 33.122, clause 6.5.2.1), the invoker and the AEF authenticate each other with a pre-shared key, the AEF_PSK, that the invoker never receives from CAPIF: the SDK derives it.
1. If `PSK` is in `security_context.pref_security_context`, the SDK creates or updates the [security context](#security-context) over TLS 1.2 (OpenCAPIF does not select PSK over TLS 1.3, which has no master secret nor Session ID).
2. For each API at each AEF for which CAPIF selects PSK, the SDK and CAPIF derive the same AEF_PSK from the master secret and Session ID of that TLS connection and from the interface of the AEF, `https://{host}[:{port}]{apiPrefix}` (3GPP TS 33.122 Annex A, with the key derivation function of 3GPP TS 33.220 Annex B). Each AEF gets a different key.
3. CAPIF gives each AEF_PSK only to its AEF (in the `authenticationInfo` of the security context, requested with the AEF certificate).
4.`invoke_api()` and `get_api_session()` connect to the AEF with TLS 1.2 and PSK cipher suites, sending the PSK identity: the `apiInvokerId`, or `api_invocation.psk_identity` if it is set in the [configuration](./sdk_configuration.md). The AEF has no certificate to verify (the `verify` parameter is not used): it proves that it knows the AEF_PSK. A connection without a PSK cipher suite is rejected.
The AEF_PSKs are stored in the invoker details file, `capif_api_security_context_details-<capif_username>.json` (`api_psks`), which is only readable by the user. Every new security context, e.g. `get_security_context()`, renews them, so the AEFs have to get the new keys from CAPIF. If the AEF_PSK of an API is missing, `invoke_api()` updates the security context first.
Requirements:
-**Python 3.13 or later**, with OpenSSL PSK support (`ssl.HAS_PSK`). With earlier versions, invoking an API with PSK raises `ApiInvocationError`; the rest of the SDK works with Python 3.9 or later.
-**OpenCAPIF 5.1.0 or later.** Earlier versions derive a different key, so the TLS handshake with the AEF fails.
-**An AEF that supports TLS 1.2 with PSK cipher suites**, and gets the AEF_PSK of each invoker from CAPIF. See [PSK Security Method](https://ocf.etsi.org/documentation/latest/features/psk/psk/) in the OpenCAPIF documentation.
**Errors**: `ApiInvocationError` if the API, AEF, version or resource are not found, the resource does not allow the method, a `{placeholder}` of the URI has no value in `path_params`, or `aef_id` (or `api_version`) is missing when several AEFs (or versions) are published, and, with PSK, if Python does not support TLS-PSK or the AEF_PSK could not be derived (e.g. `PSK` is not in `pref_security_context`); the `requests` exception (e.g. `HTTPError`) if the AEF returns an error status, or `SSLError` if the TLS-PSK handshake fails (e.g. the AEF has a different AEF_PSK).