WORK IN PROGRESS & For Discussion
Background
Some A1-Policy operations require the creator (and owner) of an A1-Policy to be known.
In the pre-R1 A1-Policy API it was required to provide a 'service_id' to link A1-Policies to the client or service that 'owns' them.
- https://docs.onap.org/projects/onap-ccsdk-oran/en/latest/offeredapis/pms-api.html#tag/Service-Registry-and-Supervision
- https://docs.onap.org/projects/onap-ccsdk-oran/en/latest/offeredapis/pms-api.html#tag/A1-Policy-Management/operation/putPolicy
Where this mandatory 'service_id' is a registered 'Service', then the new A1-Policy instance is subject to the 'keep-alive' requirements for the service, and policies are deleted when the 'Service' is unregistered/deleted.
However, this mandatory 'service_id' did not need to correspond to an existing registered 'Service', and if the value corresponded to an unregistered 'Service' service registration is not performed for that service.
A "Service" cannot be registered with a null or empty (""), however an empty value "" can be passed for 'service_id' when creating a policy - to represent "no service".
O-RAN Alliance R1-GAP, R1-AP and Security specifications describe how R1 requests authentication headers should include a bearer-token, which includes the 'ClientID' of the R1-Service invoker. There is an assumption (TBC) that this is the 'rAppID' for R1 invocations by rApps. When creating A1-Polices this 'ClientID' corresponds to the 'owner' and 'creator' of those A1-Policies. ('owner' == 'creator').
When a 'ClientID' is present in a the Auth header of a request, as part of a cryptographically signed/verified JWT token, the 'ClientID' will be the actual service invoker. JWT tokens generally should not be shared, and it should not be possible for one client to masquerade as a different client.
However, there may be cases where non-rApp clients need to be 'creator' for an A1-Policy, but not the 'owner'. e.g. an admin client, a GUI, a batch or restore operation, distributed applications, etc.. So it should be possible to also include supplementary 'owner' information in an A1-Policy create request.
In the R1-aligned enhanced ('v3') API introduced for A1-PMS (link currently broken: https://docs.onap.org/projects/onap-ccsdk-oran/en/latest/offeredapis/v3/pms-api-v3.html) the Create-Policy operation retained the 'service_id' parameter as an optional non-spec parameter, with default value as empty string "". In this way we can use either/both 'ClientID' in the header's JWT token and/or the 'service_id' parameter in the create-policy operation.
This page discusses how to deal with combinations of 'ClientID' (as "creator" and/or "own
er") in the Header, and 'service_id' ( as "owner") in the request body.
Scenarios:
Info | ||
---|---|---|
| ||
The discussion below does not consider whether a Create-Policy request (or any other request) should be allowed or not.
Therefore, depending on how the service is deployed and configured, some of the scenarios below may be restricted, and the operation may fail with an 'Unauthorized' error. |
Original pre-spec v2 API:
- Request 'service_id' ❌ absent (null)
Token or its 'ClientID' ❌ absent (null), or
Token's 'ClientID' ✔️ present but blank/empty (""), or
Token's 'ClientID' ✅present
Panel border true Leave parameter 'service_id' as null and continue.
Operation will fail because 'service_id' is mandatory.
- Request 'service_id' ✔️ present but blank/empty ("")
Token or its 'ClientID' ❌ absent (null)
Panel border true Leave parameter 'service_id' as empty ("") and continue.
This policy has no 'owner', so cannot be retrieved using a service-id filter in Get-Instances, and cannot be later inherited by a newly-registered service ("" is not a valid service name when registering a 'Service').
Policy cannot be auto-deleted/garbage-collected, since this functionality is tried to the life-cycle of registered Services only.
Token's 'ClientID' ✔️ present but blank/empty ("")
Panel border true Leave parameter 'service_id' as empty ("") and continue.
This policy has no 'owner', so cannot be retrieved using a service-id filter in Get-Instances, and cannot be later inherited by a newly-registered service ("" is not a valid service name when registering a 'Service').
Policy cannot be auto-deleted/garbage-collected, since this functionality is tried to the life-cycle of registered Services only.
Token's 'ClientID' ✅present
Panel border true Set parameter 'service_id' = 'ClientID' and continue.
This policy's 'owner' is set be its 'creator'.
See options 3.d and 4.d below to understand behavior when 'service_id' == 'ClientID'
- Request 'service_id' ✅present but NOT a registered 'Service'
Token or its 'ClientID' ❌ absent (null)
Panel border true Leave parameter 'service_id' as is (not-registered Service) and continue.
This policy has an unregistered 'owner', but can be retrieved using that 'service-id' filter in Get-Instances
Policy cannot be auto-deleted/garbage-collected, since this functionality is tried to the life-cycle of registered Services only.
Policy can be later inherited by a newly-registered Service with same 'service_id'.Token's 'ClientID' ✔️ present but blank/empty ("")
Panel border true Leave parameter 'service_id' as is (not-registered Service) and continue.
This policy has an unregistered 'owner', but can be retrieved using that 'service-id' filter in Get-Instances
Policy cannot be auto-deleted/garbage-collected, since this functionality is tried to the life-cycle of registered Services only.
Policy can be later inherited by a newly-registered Service with same 'service_id'.Token's 'ClientID' ✅present but != 'service_id'
Panel border true Leave parameter 'service_id' as is (not-registered Service) and continue.
Policy has an different 'owner' than 'creator'This policy has an unregistered 'owner', but can be retrieved using that 'service-id' filter in Get-Instances
Policy cannot be auto-deleted/garbage-collected, since this functionality is tried to the life-cycle of registered Services only.
Policy can be later inherited by a newly-registered Service with same 'service_id' (see option 4.c below).Warning title To Do! Need to extend functionality to save the additional 'creator' information.
Token's 'ClientID' ✅present and == 'service_id'
Panel border true Leave parameter 'service_id' as is (not-registered Service) and continue.
This policy has an unregistered 'owner', but can be retrieved using that 'service-id' filter in Get-Instances
Policy cannot be auto-deleted/garbage-collected, since this functionality is tried to the life-cycle of registered Services only.
Policy can be later inherited by a newly-registered Service with same 'service_id' (see option 4.d below).
- Request 'service_id' ✅present but is a registered 'Service'
Token or its 'ClientID' ❌ absent (null)
Panel border true Leave parameter 'service_id' as is (pre-registered Service) and continue.
This policy has a registered 'owner', and can be retrieved using that 'service-id' filter in Get-Instances
Policy may be auto-deleted/garbage-collected, depending on the life-cycle and activity registered Service.
Policy will be deleted if Service is deleted or times-out according to however the Service is configured.Token's 'ClientID' ✔️ present but blank/empty ("")
Panel border true Leave parameter 'service_id' as is (pre-registered Service) and continue.
This policy has a registered 'owner', and can be retrieved using that 'service-id' filter in Get-Instances
Policy may be auto-deleted/garbage-collected, depending on the life-cycle and activity registered Service.
Policy will be deleted if Service is deleted or times-out according to however the Service is configured.Token's 'ClientID' ✅present but != 'service_id'
Panel border true Leave parameter 'service_id' as is (pre-registered Service) and continue.
Policy has an different 'owner' than 'creator'This policy has a registered 'owner', and can be retrieved using that 'service-id' filter in Get-Instances
Policy may be auto-deleted/garbage-collected, depending on the life-cycle and activity registered Service.
Policy will be deleted if Service is deleted or times-out according to however the Service is configured.Warning title To Do! Need to extend functionality to save the additional 'creator' information.
Token's 'ClientID' ✅present and == 'service_id'
Panel border true Leave parameter 'service_id' as is (pre-registered Service) and continue.
Policy has same 'owner' than 'creator'This policy has a registered 'owner' - same as 'creator', and can be retrieved using that 'service-id' filter in Get-Instances
Policy may be auto-deleted/garbage-collected, depending on the life-cycle and activity registered Service.
Policy will be deleted if Service is deleted or times-out according to however the Service is configured.
New R1-spec v3 API:
The main difference between 'v2' API and new 'v3' API is that 'service_id' is now optional instead of mandatory.
So all scenarios are the same as above EXCEPT scenario #1
Request 'service_id' ❌ absent (null)
Panel border true Set parameter 'service_id' as empty ("") and continue.
See option set #2 above
Demo with tokens and ClientID extraction for using it as service_id
To generate a JWT token and parse a value from it demonstration.
Below, I'll outline the steps to achieve this, including generating a JWT, sending it in a request header, and then parsing a value (like `client_id`) from the JWT payload.
Generating a JWT Token using Bash
Note that this example is for educational purposes and doesn't include proper security practices like using secure keys.
Code Block | ||||
---|---|---|---|---|
| ||||
#!/bin/bash
header='{"alg": "HS256", "typ": "JWT"}'
payload='{"iss": "example_issuer", "sub": "1234567890", "aud": "myclient", "exp": 3000000000, "client_id": "myclient", "role": "user"}'
# Base64 encode the header and payload without padding
header_base64=$(echo -n "$header" | openssl base64 -e | tr -d '=' | tr '/+' '_-' )
payload_base64=$(echo -n "$payload" | openssl base64 -e -A | tr -d '=' | tr '/+' '_-')
# Create a signature
secret="mysecret"
signature_base64=$(echo -n "${header_base64}.${payload_base64}" | openssl dgst -sha256 -hmac "${secret}" -binary | openssl base64 -e | tr -d '=' | tr '/+' '_-')
# Combine to form the JWT
jwt="${header_base64}.${payload_base64}.${signature_base64}"
echo "$jwt" |
This script generates a JWT and prints it out. Replace "your-256-bit-secret" with a proper secret key.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleGFtcGxlX2lzc3VlciIsInN1YiI6IjEyMzQ1Njc4OTAiLCJhdWQiOiJteWNsaWVudCIsImV4cCI6MzAwMDAwMDAwMCwiY2xpZW50X2lkIjoibXljbGllbnQiLCJyb2xlIjoidXNlciJ9.O5QN_SWN4J1mWKyXk_-PCvOA6GF3ypv1rSdg2uTb_Ls |
Note: for the expiration time
$ ([DateTime]('1970,1,1')).AddSeconds(3000000000)
24 January 2065 05:20:00
Policy Creation Scenario
In this example I want to log the Bearer Token given to the call: PUT
In Postman I use the generated token I got from the bash script as Bearer Token:
It would be equivalent to:
Code Block | ||||
---|---|---|---|---|
| ||||
curl -v -X 'PUT' 'http://localhost:8081/a1-policy/v2/policies' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleGFtcGxlX2lzc3VlciIsInN1YiI6IjEyMzQ1Njc4OTAiLCJhdWQiOiJteWNsaWVudCIsImV4cCI6MzAwMDAwMDAwMCwiY2xpZW50X2lkIjoibXljbGllbnQiLCJyb2xlIjoidXNlciJ9.O5QN_SWN4J1mWKyXk_-PCvOA6GF3ypv1rSdg2uTb_Ls' \
-d '
{
"ric_id": "ric1",
"policy_id": "aa8feaa88d944d919ef0e83f2172a51001",
"is_transient": true,
"service_id": "service-1",
"policy_data": {
"scope": {
"ueId": "ue5100",
"qosId": "qos5100"
},
"qosObjectives": {
"priorityLevel": 5100.0
}
},
"status_notification_uri": "http://callback-receiver:8090/callbacks/test",
"policytype_id": "1"
}' |
In org.onap.ccsdk.oran.a1policymanagementservice.controllers.v2.PolicyController
Code Block | ||||
---|---|---|---|---|
| ||||
private void logHeaders(ServerWebExchange exchange) {
HttpHeaders headers = exchange.getRequest().getHeaders();
String authHeader = headers.getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
logger.info("Token: " + token);
logger.info("ServiceId: " + parseServiceId(token));
} else {
logger.info("Authorization header is missing or does not contain a Bearer token");
}
}
public String parseServiceId(String token) {
String[] chunks = token.split("\\.");
Base64.Decoder decoder = Base64.getUrlDecoder();
String payload = new String(decoder.decode(chunks[1]));
JsonObject jsonObject = JsonParser.parseString(payload).getAsJsonObject();
String clientId = jsonObject.get("client_id").getAsString();
return clientId;
} |
Output of the call:
Code Block | ||||
---|---|---|---|---|
| ||||
2024-07-30 13:32:55 2024-07-30 12:32:55.413 [INFO ] [http-nio-8081-exec-3] o.o.c.o.a.c.v.PolicyController - Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleGFtcGxlX2lzc3VlciIsInN1YiI6IjEyMzQ1Njc4OTAiLCJhdWQiOiJteWNsaWVudCIsImV4cCI6MzAwMDAwMDAwMCwiY2xpZW50X2lkIjoibXljbGllbnQiLCJyb2xlIjoidXNlciJ9.O5QN_SWN4J1mWKyXk_-PCvOA6GF3ypv1rSdg2uTb_Ls
2024-07-30 13:32:55 2024-07-30 12:32:55.415 [INFO ] [http-nio-8081-exec-3] o.o.c.o.a.c.v.PolicyController - ServiceId: myclient |
...
Summary:
ClientID coming from the JWT, ServiceID coming in from the body request.
v3 | ClientID NULL | ClientID EMPTY | ClientID VALID |
---|---|---|---|
ServiceID NULL | S EMPTY | C EMPTY | C |
ServiceID EMPTY | S | C EMPTY | C |
ServiceID REGISTERED | S | S | S (*) |
ServiceID UNREGISTERED | S | S | S (*) |
(*) Owner (serviceID) != Creator (clientID)