Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

WORK IN PROGRESS

Introduction: A1-PMS Compliance with O-RAN Specifications

...

All these solutions are viable, but will ad add complexity and abstraction to the code

...

Code Block
languageyml
themeMidnight
   components:
     parameters:
       PolicyId:
         name: policyId
         in: path
         required: true
         schema:
           type: string

   paths:
     /policies/{policyId}:
       get:
         parameters:
           - $ref: '#/components/parameters/PolicyId'

TODO and topic to follow

...

Schema Usage in Paths

SchemaCountPaths
ErrorInformation4/policies/{policyId}, /policytypes/{policyTypeId}/policies, /policytypes/{policyTypeId}/policies/{policyId}, /policytypes/{policyTypeId}/policies/{policyId}/status
JsonSchema2/policytypes/{policyTypeId}/policies, /policytypes/{policyTypeId}/policies/{policyId}
NearRtRicId3/policies, /policies/{policyId}, /policytypes/{policyTypeId}/policies
NotificationDestination1/policies/{policyId}
PolicyId3/policies, /policies/{policyId}, /policytypes/{policyTypeId}/policies
PolicyInformation2/policies/{policyId}, /policytypes/{policyTypeId}/policies/{policyId}
PolicyObjectInformation1/policytypes/{policyTypeId}/policies/{policyId}/status
PolicyStatusObject2/policies/{policyId}, /policytypes/{policyTypeId}/policies/{policyId}/status
PolicyTypeId3/policytypes/{policyTypeId}/policies, /policytypes/{policyTypeId}/policies/{policyId}, /policytypes/{policyTypeId}/policies/{policyId}/status
PolicyTypeInformation2/policytypes/{policyTypeId}/policies, /policytypes/{policyTypeId}/policies/{policyId}
PolicyTypeObject1

/policytypes/{policyTypeId}/policies/{policyTypeId}/status

Using allOf to Extend Objects

To avoid using the `required` flag directly and create more flexible schemas, we can use the allOf keyword to combine schemas. This allows you to create an extended schema from an original one without repeating required properties.

https://openapi-generator.tech/docs/generators/spring/#schema-support-feature 

From the documentation of the openapi generator used in A1PMS, AllOf is not supported for spring server generator. But it still generated objects without the use of extending the parent class. For example:

An extended object in the specification yaml:

Code Block
languageyml
themeMidnight
components:
  schemas:
    Policy:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
    PolicyExtended:
      allOf:
        - $ref: '#/components/schemas/Policy'
        - type: object
          properties:
            description:
              type: string



Code Block
languagejava
themeMidnight
public class Policy {

  private String id;
  private String name;
...
}

public class PolicyExtended {

  private String id;
  private String name;
  private String description;
...
}



OpenAPI Required Properties

In OpenAPI 3 by default, all object properties are optional. Required properties can be identified in the required list:  

Code Block
languageyml
themeMidnight
components: 
  schemas:
    Policy:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
      required:
        - id

Having required parameters has only one effect of genereted code, the tool generates only one construcotr with only default parameters. So the implementer can use the setters and getters.

Code Block
languagejava
themeMidnight
  /**
   * Constructor with only required parameters
   */
  public Policy(String id, String name) {
    this.id= id;
  }

Handling serviceId in Request Bodies and Bearer Tokens

Current Implementation

In the current implementation, the `serviceId` can be set in the body of a request and is defined as optional. The default value for `serviceId` in `PolicyObjectInformation` (PolicyApi) is a space, which accommodates cases where the `serviceId` might be missing. However, in the `ServiceApi`, the `serviceId` is required, for example, when creating a service.

Ideal Implementation

The use of a space as a default value for `serviceId` is a workaround to handle missing IDs. Ideally, the `serviceId` should be extracted from a bearer token. This can be done by decoding a JWT token using built-in Java functions.

Here’s a sample code snippet to decode a JWT token and extract the `serviceId`:

Code Block
languagejava
themeMidnight
String token = getAuthToken(receivedHttpHeaders);
String[] chunks = token.split("\\.");
Base64.Decoder decoder = Base64.getUrlDecoder();
String payload = new String(decoder.decode(chunks[1]));
JsonObject jsonObject = new JsonObject();
jsonObject.add("payload", JsonParser.parseString(payload));
String serviceId = jsonObject.getAsJsonObject("payload").get("client_id").getAsString(); 

Integrating with Existing Code

In the codebase, there is already a function to get the token located in the class:

org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.AuthorizationCheck

String getAuthToken(Map<String, String> httpHeaders)

Bearer Token in Headers

The bearer token would be passed during the API call via headers as follows:

"Authorization: Bearer <bearer_token>"

By extracting the serviceId from the bearer token, we can ensure that the `serviceId` is always available and accurate, enhancing the security and reliability of the API.

Istio with Keycloak service mesh general use

When integrating Istio with Keycloak for JWT-based authorization, the typical workflow involves clients obtaining a JWT (JSON Web Token) from Keycloak, which is then used to access services secured by Istio. Keycloak acts as an identity provider (IdP), issuing tokens that contain various claims about the authenticated user or client.
A JWT from Keycloak includes three parts: the header, payload, and signature. The payload carries the claims about the user or client, and it is used by Istio to make authorization decisions.

1. Header: This part of the token contains metadata about the token itself, such as the type of token and the signing algorithm used. For Keycloak, this often looks like:


   {
     "alg": "RS256", // or another algorithm
     "typ": "JWT",
     "kid": "key-id" // key identifier, optional
   }

2. Payload: The payload contains the claims, which are statements about the entity (typically, the user) and additional data. Some common claims include:

   A sample payload might look like:

  

Code Block
themeMidnight
   {
     "iss": "https://keycloak.example.com/auth/realms/myrealm", #Issuer of the token, typically the Keycloak server URL.
     "sub": "12345678-1234-1234-1234-1234567890ab", 			#Subject of the token, usually the user ID.
     "aud": "myclient", 										#Audience for whom the token is intended. This is usually the client ID.
     "clientId": "myclientID", 									#Custom Parameter
     "exp": 1627584000,
     "iat": 1627576800,
     "nbf": 1627576800,
     "jti": "abcd1234efgh5678ijkl9012", 						#JWT ID
     "preferred_username": "user",
     "email": "user@example.com",
     "roles": ["user", "admin"]
   }


3. Signature: The signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn't changed along the way. The signature is created using the algorithm specified in the header and a private key, and it is then appended to the header and payload.

Client ID
The client_id is a common parameter in the payload of a JWT, particularly in tokens issued as part of OAuth 2.0 or OpenID Connect flows.

https://www.keycloak.org/docs-api/latest/rest-api/index.html#ApplicationRepresentation
Curl Examples: 

Getting Admin Token

Code Block
languagebash
themeMidnight
curl -k -sS --request POST \
          	 --url "http://$KEYCLOAK_HOST/auth/realms/$REALM_NAME/protocol/openid-connect/token" \
            --data client_id=$CLIENT_ID \
            --data username=$USERNAME \
            --data password=$PASSWORD \
            --data grant_type=password \
        	--data scope=openid

Getting Client Secret

Code Block
languagebash
themeMidnight
curl -k -sS -X GET "http://$KEYCLOAK_HOST/auth/admin/realms/$REALM_NAME/clients/$CLIENT_ID/client-secret" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer $ACCESS_TOKEN"


Demo with tokens and clientId extraction for using it as serviceId


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
languagebash
themeMidnight
#!/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
languagebash
themeMidnight
collapsetrue
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleGFtcGxlX2lzc3VlciIsInN1YiI6IjEyMzQ1Njc4OTAiLCJhdWQiOiJteWNsaWVudCIsImV4cCI6MzAwMDAwMDAwMCwiY2xpZW50X2lkIjoibXljbGllbnQiLCJyb2xlIjoidXNlciJ9.O5QN_SWN4J1mWKyXk_-PCvOA6GF3ypv1rSdg2uTb_Ls



Note: for the expiration time

$ ([DateTime]('1970,1,1')).AddSeconds(3000000000)
24 January 2065 05:20:00

Sending the JWT in a REST Request Header

You can use curl to send the JWT in a request header:

Code Block
languagebash
themeMidnight
curl -H "Authorization: Bearer $jwt" http://A1PMS/policy..

Parsing the JWT Payload in Java

Here's an example of how to parse the `client_id` from the JWT in Java:


Code Block
languagejava
themeMidnight
import java.util.Base64;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

public class ParseJWT {
    public String parseServiceId(String token) {
        // Split token into its parts
        String[] chunks = token.split("\\.");
        Base64.Decoder decoder = Base64.getUrlDecoder();

        // Decode payload
        String payload = new String(decoder.decode(chunks[1]));

        // Parse JSON using Gson
        JsonObject jsonObject = JsonParser.parseString(payload).getAsJsonObject();

        // Extract the client_id
        String clientId = jsonObject.get("client_id").getAsString();
        return clientId;
    }
}


In the create policy code check if there is an header and if there is a clientId use it as serviceId, other cases are covered having default serviceId (If there is no header, if there is an header but not a clientId)

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:
Image Added

It would be equivalent to:

Code Block
languagebash
themeMidnight
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
languagejava
themeMidnight
    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
languagebash
themeMidnight
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



TODO and topic to follow

- Evaluate the necessity of optional fields: Determine if certain optional fields can be removed or if their use can be better documented to avoid dead data.
- Consider adopting more specific schemas for critical operations: This can improve both the documentation and the generated code quality. Leverage OpenAPI Features: Use OpenAPI's advanced features like `allOf`, `oneOf`, `anyOf`, and discriminators to create flexible and reusable components
- Prepare for code adaptations: Implement patterns like Adapter/Builder/Transformer to handle translations between similar objects, facilitating easier maintenance and adaptation to specification changes.
- Regular compliance checks.


...



...