From d90443c262e947894f1e1185b00327d4a1bc7198 Mon Sep 17 00:00:00 2001 From: Xiaocheng Dong Date: Thu, 22 Feb 2024 01:40:08 -0500 Subject: [PATCH] server: add the initial version of the container measurement - update the `ccnp-server.proto`, add container_id for containers and optional parameters. - add container measurement in the CCNP service. - update the SDK of CCNP Signed-off-by: Xiaocheng Dong --- sdk/python3/README.md | 158 ++--- sdk/python3/ccnp/__init__.py | 10 +- sdk/python3/ccnp/ccnp_server_pb2.py | 60 ++ sdk/python3/ccnp/ccnp_server_pb2.pyi | 132 ++++ sdk/python3/ccnp/ccnp_server_pb2_grpc.py | 198 ++++++ sdk/python3/ccnp/eventlog/__init__.py | 0 sdk/python3/ccnp/eventlog/eventlog_sdk.py | 565 ------------------ .../ccnp/eventlog/eventlog_server_pb2.py | 35 -- .../ccnp/eventlog/eventlog_server_pb2.pyi | 38 -- .../ccnp/eventlog/eventlog_server_pb2_grpc.py | 66 -- sdk/python3/ccnp/measurement/__init__.py | 0 .../ccnp/measurement/measurement_sdk.py | 156 ----- .../measurement/measurement_server_pb2.py | 35 -- .../measurement/measurement_server_pb2.pyi | 40 -- .../measurement_server_pb2_grpc.py | 66 -- sdk/python3/ccnp/quote/__init__.py | 0 sdk/python3/ccnp/quote/quote_sdk.py | 338 ----------- sdk/python3/ccnp/quote/quote_server_pb2.py | 36 -- sdk/python3/ccnp/quote/quote_server_pb2.pyi | 44 -- .../ccnp/quote/quote_server_pb2_grpc.py | 66 -- sdk/python3/ccnp/sdk.py | 242 ++++++++ sdk/python3/pyproject.toml | 2 +- sdk/python3/requirements.txt | 1 + service/ccnp-server/README.md | 12 +- service/ccnp-server/configs/policy.yaml | 19 + service/ccnp-server/proto/ccnp-server.proto | 15 +- service/ccnp-server/src/agent.rs | 290 +++++++-- service/ccnp-server/src/ccnp_server_pb.rs | 24 +- service/ccnp-server/src/container.rs | 95 +++ service/ccnp-server/src/main.rs | 10 +- service/ccnp-server/src/measurement.rs | 149 +++++ service/ccnp-server/src/policy.rs | 79 +++ service/ccnp-server/src/service.rs | 35 +- 33 files changed, 1306 insertions(+), 1710 deletions(-) create mode 100644 sdk/python3/ccnp/ccnp_server_pb2.py create mode 100644 sdk/python3/ccnp/ccnp_server_pb2.pyi create mode 100644 sdk/python3/ccnp/ccnp_server_pb2_grpc.py delete mode 100644 sdk/python3/ccnp/eventlog/__init__.py delete mode 100644 sdk/python3/ccnp/eventlog/eventlog_sdk.py delete mode 100644 sdk/python3/ccnp/eventlog/eventlog_server_pb2.py delete mode 100644 sdk/python3/ccnp/eventlog/eventlog_server_pb2.pyi delete mode 100644 sdk/python3/ccnp/eventlog/eventlog_server_pb2_grpc.py delete mode 100644 sdk/python3/ccnp/measurement/__init__.py delete mode 100644 sdk/python3/ccnp/measurement/measurement_sdk.py delete mode 100644 sdk/python3/ccnp/measurement/measurement_server_pb2.py delete mode 100644 sdk/python3/ccnp/measurement/measurement_server_pb2.pyi delete mode 100644 sdk/python3/ccnp/measurement/measurement_server_pb2_grpc.py delete mode 100644 sdk/python3/ccnp/quote/__init__.py delete mode 100644 sdk/python3/ccnp/quote/quote_sdk.py delete mode 100644 sdk/python3/ccnp/quote/quote_server_pb2.py delete mode 100644 sdk/python3/ccnp/quote/quote_server_pb2.pyi delete mode 100644 sdk/python3/ccnp/quote/quote_server_pb2_grpc.py create mode 100644 sdk/python3/ccnp/sdk.py create mode 100644 service/ccnp-server/configs/policy.yaml create mode 100644 service/ccnp-server/src/container.rs create mode 100644 service/ccnp-server/src/measurement.rs create mode 100644 service/ccnp-server/src/policy.rs diff --git a/sdk/python3/README.md b/sdk/python3/README.md index 8792ab4..66fe846 100644 --- a/sdk/python3/README.md +++ b/sdk/python3/README.md @@ -36,66 +36,48 @@ pip install -e . ## Key concepts and usage There are three major functionalities provided in this SDK: -* [Quote fetching](#quote) +* [CC report fetching](#cc-report) * [Measurement fetching](#measurement) * [Event log fetching](#event-log) -### Quote +### CC Report -Using this SDK, user could fetch the quote from different platforms, the service detect the platform automatically and return the type and the quote. +Using this SDK, user could fetch the report from different platforms, the service detect the platform automatically and return the report. -#### Quote type for platform +#### Example usage of the SDK -* TYPE_TDX - This provides the quote fetching based on Intel TDX. -* TYPE_TPM - This provides the quote fetching based on TPM. +The interface input of CC report is `nonce` and `user_data`, both of them are optional and will be measured in the report. +Here are the example usages of the SDK: -#### Example usage of quote SDK - -The interface input of quote is `nonce` and `user_data`, both of them are optional and will be measured in quote. -Here are the example usages of quote SDK: - -* Fetch quote without any inputs +* Fetch report without any inputs ```python -from ccnp import Quote - -quote = Quote.get_quote() +from ccnp import CcnpSdk -print(quote.quote_type) -print(quote.quote) +CcnpSdk.inst().get_cc_report().dump() ``` -* Fetch quote with a `nonce` +* Fetch report with a `nonce` ```python import base64 import secrets -from ccnp import Quote +from ccnp import CcnpSdk nonce = base64.b64encode(secrets.token_urlsafe().encode()) -quote = Quote.get_quote(nonce=nonce) - -print(quote.quote_type) -print(quote.quote) +CcnpSdk.inst().get_cc_report(nonce=nonce).dump() ``` -* Fetch quote with a `nonce` and `user_data` +* Fetch report with a `nonce` and `user_data` ```python import base64 import secrets -from ccnp import Quote +from ccnp import CcnpSdk nonce = base64.b64encode(secrets.token_urlsafe().encode()) user_data = base64.b64encode(b'This data should be measured.') -quote = Quote.get_quote(nonce=nonce, user_data=user_data) - -print(quote.quote_type) -print(quote.quote) +CcnpSdk.inst().get_cc_report(nonce=nonce, data=user_data).dump() -# For TD quote, it includes RTMRs, TD report, etc. -if quote.quote_type == Quote.TYPE_TDX: - print(quote.rtmrs) - print(quote.tdreport) ``` ### Measurement @@ -104,49 +86,18 @@ Using this SDK, user could fetch various measurements from different perspective Basic support on measurement focus on the platform measurements, including TEE report, values within TDX RTMR registers or values reside in TPM PCR registers. There's also advanced support to provide measurement for a certain workload or container. The feature is still developing in progress. -#### MeasurementType for platform - -The measurement SDK supports fetching different types of evidence depending on the environment. -Currently, CCNP supports the following categories of measurements: - -* TYPE_TEE_REPORT - This provides the report fetching on various Trusted Execution Environment from all kinds of vendors, including Intel TDX, AMD SEV (Working in Progress), etc. -* TYPE_TDX_RTMR - This provides the measurement fetching on TDX RTMR. Users could fetch the measurement from one single RTMR register with its index. -* TYPE_TPM_PCR - This provides th measurement fetching on TPM PCR. Users could fetch measurement from one single PCR register with its index. - -#### Example usage of measurement SDK +#### Example usage of the SDK Here are the example usages for measurement SDK: -* Fetch TEE report base on platform -```python -from ccnp import Measurement -from ccnp import MeasurementType - -# Fetch TEE report without user data -report = Measurement.get_platform_measurement() - -# Fetch TEE report with user data -data = "testing" -report = Measurement.get_platform_measurement(MeasurementType.TYPE_TEE_REPORT, data) - -``` - -* Fetch single RTMR measurement for platform +* Fetch TEE measurement base on platform ```python -from ccnp import Eventlog -from ccnp import Measurement -from ccnp import MeasurementType +from ccnp import CcnpSdk -# Fetch the value reside in register 1 of RTMR -rtmr_measurement = Measurement.get_platform_measurement(MeasurementType.TYPE_TDX_RTMR, None, 1) -``` - -* Fetch container measurement (Working in Progress) -```python -from ccnp import Measurement -from ccnp import MeasurementType +for i in [0, 1, 3]: + m = CcnpSdk.inst().get_cc_measurement([i, 12]) + print("IMR index: %d, hash: %s"%(i, m.hash.hex())) -container_measurement = Measurement.get_container_measurement() ``` ### Event log @@ -154,59 +105,34 @@ container_measurement = Measurement.get_container_measurement() Using this SDK, user can fetch the event logs to assist the attestation/verification process. It also enables two different categories of event logs - for the platform or for a single workload/container. From platform perspective, it can support different Trusted Execution Environment and TPM. This sdk can also do fetching on certain number of event logs. -#### EventlogType for platform +#### Example usage of the SDK -* TYPE_TDX - This provides the event log fetching based on Intel TDX. -* TYPE_TPM - This provides the event log fetching based on TPM. +Here are the example usages of the SDK: -#### Example usage of Eventlog SDK - -Here are the example usages of eventlog SDK: - -* Fetch event log of Intel TDX platform for platform and check the information inside +* Fetch event log of platform and check the information inside ```python -from ccnp import Eventlog -from ccnp import EventlogType - -# default type for get_platform_eventlog() is 'TYPE_TDX' -logs = Eventlog.get_platform_eventlog() -# same as setting type as 'TYPE_TDX' -logs = Eventlog.get_platform_eventlog(EventlogType.TYPE_TDX) - -# show total length -print(len(logs)) - -# fetch event log attributes -print(logs[2].evt_type) -print(logs[2].evt_type_str) -print(logs[2].evt_size) -print(logs[2].reg_idx) -print(logs[2].alg_id) -print(logs[2].event) -print(logs[2].digest) - -# fetch 5 event logs from the second one -logs = Eventlog.get_platform_eventlog(EventlogType.TYPE_TDX, 2, 5) - -# show log length, which shall equal to 5 -print(len(logs)) -``` +from ccnp import CcnpSdk -* Fetch event log of TPM platform (Working in Progress) -```python -from ccnp import Eventlog -from ccnp import EventlogType +evt = CcnpSdk.inst().get_cc_eventlog() +for e in evt: + e.dump() -# set type for get_platform_eventlog() as 'TYPE_TPM' -logs = Eventlog.get_platform_eventlog(EventlogType.TYPE_TPM) ``` -* Fetch event log for certain container (Working in Progress) +* Replay the event logs ```python -from ccnp import Eventlog -from ccnp import EventlogType - -logs = Eventlog.get_container_eventlog() +from ccnp import CcnpSdk + +evt = CcnpSdk.inst().get_cc_eventlog() +replay = CcnpSdk.inst().replay_cc_eventlog(evt) +for r in replay: + print("Replay IMR[%d]: %s"%(r, replay[r][12].hex())) + m = CcnpSdk.inst().get_cc_measurement([r, 12]) + print("Read IMR[%d]: %s"%(r, m.hash.hex())) + if m.hash != replay[r][12]: + print("Replay IMR value does not match real IMR.") + else: + print("Verify event log replay value successfully.") ``` ## End-to-end examples @@ -231,6 +157,6 @@ See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on building, testing, a If you encounter any bugs or have suggestions, please file an issue in the Issues section of the project. -[source_code]: https://github.com/intel/confidential-cloud-native-primitives/tree/main/sdk/python3 +[source_code]: https://github.com/cc-api/confidential-cloud-native-primitives/tree/main/sdk/python3 [ccnp_pypi]: https://pypi.org/project/ccnp/ [api_doc]: https://intel.github.io/confidential-cloud-native-primitives/_rst/sdk.readme.html diff --git a/sdk/python3/ccnp/__init__.py b/sdk/python3/ccnp/__init__.py index f6b80c3..543378d 100644 --- a/sdk/python3/ccnp/__init__.py +++ b/sdk/python3/ccnp/__init__.py @@ -1,11 +1,5 @@ """CCNP framework to enable TEE related operations in cloud native environments""" -__version__ = "0.0.1" +__version__ = "0.3.1" -from .eventlog.eventlog_sdk import EventlogUtility as Eventlog -from .eventlog.eventlog_sdk import EventlogType - -from .measurement.measurement_sdk import MeasurementUtility as Measurement -from .measurement.measurement_sdk import MeasurementType - -from .quote.quote_sdk import Quote +from .sdk import CcnpSdk diff --git a/sdk/python3/ccnp/ccnp_server_pb2.py b/sdk/python3/ccnp/ccnp_server_pb2.py new file mode 100644 index 0000000..77c8fd9 --- /dev/null +++ b/sdk/python3/ccnp/ccnp_server_pb2.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: ccnp/ccnp-server.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x63\x63np/ccnp-server.proto\x12\x0e\x63\x63np_server_pb\"%\n\x12HealthCheckRequest\x12\x0f\n\x07service\x18\x01 \x01(\t\"\xa9\x01\n\x13HealthCheckResponse\x12\x41\n\x06status\x18\x01 \x01(\x0e\x32\x31.ccnp_server_pb.HealthCheckResponse.ServingStatus\"O\n\rServingStatus\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07SERVING\x10\x01\x12\x0f\n\x0bNOT_SERVING\x10\x02\x12\x13\n\x0fSERVICE_UNKNOWN\x10\x03\"\x1c\n\x1aGetDefaultAlgorithmRequest\".\n\x1bGetDefaultAlgorithmResponse\x12\x0f\n\x07\x61lgo_id\x18\x01 \x01(\r\"\x1c\n\x1aGetMeasurementCountRequest\",\n\x1bGetMeasurementCountResponse\x12\r\n\x05\x63ount\x18\x01 \x01(\r\"n\n\x12GetCcReportRequest\x12\x14\n\x0c\x63ontainer_id\x18\x01 \x01(\t\x12\x16\n\tuser_data\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05nonce\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x0c\n\n_user_dataB\x08\n\x06_nonce\"9\n\x13GetCcReportResponse\x12\x0f\n\x07\x63\x63_type\x18\x01 \x01(\x05\x12\x11\n\tcc_report\x18\x02 \x01(\x0c\"O\n\x17GetCcMeasurementRequest\x12\x14\n\x0c\x63ontainer_id\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12\x0f\n\x07\x61lgo_id\x18\x03 \x01(\r\"J\n\x18GetCcMeasurementResponse\x12.\n\x0bmeasurement\x18\x01 \x01(\x0b\x32\x19.ccnp_server_pb.TcgDigest\"h\n\x14GetCcEventlogRequest\x12\x14\n\x0c\x63ontainer_id\x18\x01 \x01(\t\x12\x12\n\x05start\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x03 \x01(\rH\x01\x88\x01\x01\x42\x08\n\x06_startB\x08\n\x06_count\"*\n\tTcgDigest\x12\x0f\n\x07\x61lgo_id\x18\x01 \x01(\r\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"\x86\x02\n\x0bTcgEventlog\x12\x0f\n\x07rec_num\x18\x01 \x01(\r\x12\x11\n\timr_index\x18\x02 \x01(\r\x12\x12\n\nevent_type\x18\x03 \x01(\r\x12*\n\x07\x64igests\x18\x04 \x03(\x0b\x32\x19.ccnp_server_pb.TcgDigest\x12\x12\n\nevent_size\x18\x05 \x01(\r\x12\r\n\x05\x65vent\x18\x06 \x01(\x0c\x12>\n\nextra_info\x18\x07 \x03(\x0b\x32*.ccnp_server_pb.TcgEventlog.ExtraInfoEntry\x1a\x30\n\x0e\x45xtraInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"H\n\x15GetCcEventlogResponse\x12/\n\nevent_logs\x18\x01 \x03(\x0b\x32\x1b.ccnp_server_pb.TcgEventlog2\x87\x04\n\x04\x63\x63np\x12n\n\x13GetDefaultAlgorithm\x12*.ccnp_server_pb.GetDefaultAlgorithmRequest\x1a+.ccnp_server_pb.GetDefaultAlgorithmResponse\x12n\n\x13GetMeasurementCount\x12*.ccnp_server_pb.GetMeasurementCountRequest\x1a+.ccnp_server_pb.GetMeasurementCountResponse\x12V\n\x0bGetCcReport\x12\".ccnp_server_pb.GetCcReportRequest\x1a#.ccnp_server_pb.GetCcReportResponse\x12g\n\x10GetCcMeasurement\x12\'.ccnp_server_pb.GetCcMeasurementRequest\x1a(.ccnp_server_pb.GetCcMeasurementResponse\"\x00\x12^\n\rGetCcEventlog\x12$.ccnp_server_pb.GetCcEventlogRequest\x1a%.ccnp_server_pb.GetCcEventlogResponse\"\x00\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ccnp.ccnp_server_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _TCGEVENTLOG_EXTRAINFOENTRY._options = None + _TCGEVENTLOG_EXTRAINFOENTRY._serialized_options = b'8\001' + _globals['_HEALTHCHECKREQUEST']._serialized_start=42 + _globals['_HEALTHCHECKREQUEST']._serialized_end=79 + _globals['_HEALTHCHECKRESPONSE']._serialized_start=82 + _globals['_HEALTHCHECKRESPONSE']._serialized_end=251 + _globals['_HEALTHCHECKRESPONSE_SERVINGSTATUS']._serialized_start=172 + _globals['_HEALTHCHECKRESPONSE_SERVINGSTATUS']._serialized_end=251 + _globals['_GETDEFAULTALGORITHMREQUEST']._serialized_start=253 + _globals['_GETDEFAULTALGORITHMREQUEST']._serialized_end=281 + _globals['_GETDEFAULTALGORITHMRESPONSE']._serialized_start=283 + _globals['_GETDEFAULTALGORITHMRESPONSE']._serialized_end=329 + _globals['_GETMEASUREMENTCOUNTREQUEST']._serialized_start=331 + _globals['_GETMEASUREMENTCOUNTREQUEST']._serialized_end=359 + _globals['_GETMEASUREMENTCOUNTRESPONSE']._serialized_start=361 + _globals['_GETMEASUREMENTCOUNTRESPONSE']._serialized_end=405 + _globals['_GETCCREPORTREQUEST']._serialized_start=407 + _globals['_GETCCREPORTREQUEST']._serialized_end=517 + _globals['_GETCCREPORTRESPONSE']._serialized_start=519 + _globals['_GETCCREPORTRESPONSE']._serialized_end=576 + _globals['_GETCCMEASUREMENTREQUEST']._serialized_start=578 + _globals['_GETCCMEASUREMENTREQUEST']._serialized_end=657 + _globals['_GETCCMEASUREMENTRESPONSE']._serialized_start=659 + _globals['_GETCCMEASUREMENTRESPONSE']._serialized_end=733 + _globals['_GETCCEVENTLOGREQUEST']._serialized_start=735 + _globals['_GETCCEVENTLOGREQUEST']._serialized_end=839 + _globals['_TCGDIGEST']._serialized_start=841 + _globals['_TCGDIGEST']._serialized_end=883 + _globals['_TCGEVENTLOG']._serialized_start=886 + _globals['_TCGEVENTLOG']._serialized_end=1148 + _globals['_TCGEVENTLOG_EXTRAINFOENTRY']._serialized_start=1100 + _globals['_TCGEVENTLOG_EXTRAINFOENTRY']._serialized_end=1148 + _globals['_GETCCEVENTLOGRESPONSE']._serialized_start=1150 + _globals['_GETCCEVENTLOGRESPONSE']._serialized_end=1222 + _globals['_CCNP']._serialized_start=1225 + _globals['_CCNP']._serialized_end=1744 +# @@protoc_insertion_point(module_scope) diff --git a/sdk/python3/ccnp/ccnp_server_pb2.pyi b/sdk/python3/ccnp/ccnp_server_pb2.pyi new file mode 100644 index 0000000..a6bc095 --- /dev/null +++ b/sdk/python3/ccnp/ccnp_server_pb2.pyi @@ -0,0 +1,132 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class HealthCheckRequest(_message.Message): + __slots__ = ["service"] + SERVICE_FIELD_NUMBER: _ClassVar[int] + service: str + def __init__(self, service: _Optional[str] = ...) -> None: ... + +class HealthCheckResponse(_message.Message): + __slots__ = ["status"] + class ServingStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = [] + UNKNOWN: _ClassVar[HealthCheckResponse.ServingStatus] + SERVING: _ClassVar[HealthCheckResponse.ServingStatus] + NOT_SERVING: _ClassVar[HealthCheckResponse.ServingStatus] + SERVICE_UNKNOWN: _ClassVar[HealthCheckResponse.ServingStatus] + UNKNOWN: HealthCheckResponse.ServingStatus + SERVING: HealthCheckResponse.ServingStatus + NOT_SERVING: HealthCheckResponse.ServingStatus + SERVICE_UNKNOWN: HealthCheckResponse.ServingStatus + STATUS_FIELD_NUMBER: _ClassVar[int] + status: HealthCheckResponse.ServingStatus + def __init__(self, status: _Optional[_Union[HealthCheckResponse.ServingStatus, str]] = ...) -> None: ... + +class GetDefaultAlgorithmRequest(_message.Message): + __slots__ = [] + def __init__(self) -> None: ... + +class GetDefaultAlgorithmResponse(_message.Message): + __slots__ = ["algo_id"] + ALGO_ID_FIELD_NUMBER: _ClassVar[int] + algo_id: int + def __init__(self, algo_id: _Optional[int] = ...) -> None: ... + +class GetMeasurementCountRequest(_message.Message): + __slots__ = [] + def __init__(self) -> None: ... + +class GetMeasurementCountResponse(_message.Message): + __slots__ = ["count"] + COUNT_FIELD_NUMBER: _ClassVar[int] + count: int + def __init__(self, count: _Optional[int] = ...) -> None: ... + +class GetCcReportRequest(_message.Message): + __slots__ = ["container_id", "user_data", "nonce"] + CONTAINER_ID_FIELD_NUMBER: _ClassVar[int] + USER_DATA_FIELD_NUMBER: _ClassVar[int] + NONCE_FIELD_NUMBER: _ClassVar[int] + container_id: str + user_data: str + nonce: str + def __init__(self, container_id: _Optional[str] = ..., user_data: _Optional[str] = ..., nonce: _Optional[str] = ...) -> None: ... + +class GetCcReportResponse(_message.Message): + __slots__ = ["cc_type", "cc_report"] + CC_TYPE_FIELD_NUMBER: _ClassVar[int] + CC_REPORT_FIELD_NUMBER: _ClassVar[int] + cc_type: int + cc_report: bytes + def __init__(self, cc_type: _Optional[int] = ..., cc_report: _Optional[bytes] = ...) -> None: ... + +class GetCcMeasurementRequest(_message.Message): + __slots__ = ["container_id", "index", "algo_id"] + CONTAINER_ID_FIELD_NUMBER: _ClassVar[int] + INDEX_FIELD_NUMBER: _ClassVar[int] + ALGO_ID_FIELD_NUMBER: _ClassVar[int] + container_id: str + index: int + algo_id: int + def __init__(self, container_id: _Optional[str] = ..., index: _Optional[int] = ..., algo_id: _Optional[int] = ...) -> None: ... + +class GetCcMeasurementResponse(_message.Message): + __slots__ = ["measurement"] + MEASUREMENT_FIELD_NUMBER: _ClassVar[int] + measurement: TcgDigest + def __init__(self, measurement: _Optional[_Union[TcgDigest, _Mapping]] = ...) -> None: ... + +class GetCcEventlogRequest(_message.Message): + __slots__ = ["container_id", "start", "count"] + CONTAINER_ID_FIELD_NUMBER: _ClassVar[int] + START_FIELD_NUMBER: _ClassVar[int] + COUNT_FIELD_NUMBER: _ClassVar[int] + container_id: str + start: int + count: int + def __init__(self, container_id: _Optional[str] = ..., start: _Optional[int] = ..., count: _Optional[int] = ...) -> None: ... + +class TcgDigest(_message.Message): + __slots__ = ["algo_id", "hash"] + ALGO_ID_FIELD_NUMBER: _ClassVar[int] + HASH_FIELD_NUMBER: _ClassVar[int] + algo_id: int + hash: bytes + def __init__(self, algo_id: _Optional[int] = ..., hash: _Optional[bytes] = ...) -> None: ... + +class TcgEventlog(_message.Message): + __slots__ = ["rec_num", "imr_index", "event_type", "digests", "event_size", "event", "extra_info"] + class ExtraInfoEntry(_message.Message): + __slots__ = ["key", "value"] + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + REC_NUM_FIELD_NUMBER: _ClassVar[int] + IMR_INDEX_FIELD_NUMBER: _ClassVar[int] + EVENT_TYPE_FIELD_NUMBER: _ClassVar[int] + DIGESTS_FIELD_NUMBER: _ClassVar[int] + EVENT_SIZE_FIELD_NUMBER: _ClassVar[int] + EVENT_FIELD_NUMBER: _ClassVar[int] + EXTRA_INFO_FIELD_NUMBER: _ClassVar[int] + rec_num: int + imr_index: int + event_type: int + digests: _containers.RepeatedCompositeFieldContainer[TcgDigest] + event_size: int + event: bytes + extra_info: _containers.ScalarMap[str, str] + def __init__(self, rec_num: _Optional[int] = ..., imr_index: _Optional[int] = ..., event_type: _Optional[int] = ..., digests: _Optional[_Iterable[_Union[TcgDigest, _Mapping]]] = ..., event_size: _Optional[int] = ..., event: _Optional[bytes] = ..., extra_info: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class GetCcEventlogResponse(_message.Message): + __slots__ = ["event_logs"] + EVENT_LOGS_FIELD_NUMBER: _ClassVar[int] + event_logs: _containers.RepeatedCompositeFieldContainer[TcgEventlog] + def __init__(self, event_logs: _Optional[_Iterable[_Union[TcgEventlog, _Mapping]]] = ...) -> None: ... diff --git a/sdk/python3/ccnp/ccnp_server_pb2_grpc.py b/sdk/python3/ccnp/ccnp_server_pb2_grpc.py new file mode 100644 index 0000000..86b18ae --- /dev/null +++ b/sdk/python3/ccnp/ccnp_server_pb2_grpc.py @@ -0,0 +1,198 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from ccnp import ccnp_server_pb2 as ccnp_dot_ccnp__server__pb2 + + +class ccnpStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetDefaultAlgorithm = channel.unary_unary( + '/ccnp_server_pb.ccnp/GetDefaultAlgorithm', + request_serializer=ccnp_dot_ccnp__server__pb2.GetDefaultAlgorithmRequest.SerializeToString, + response_deserializer=ccnp_dot_ccnp__server__pb2.GetDefaultAlgorithmResponse.FromString, + ) + self.GetMeasurementCount = channel.unary_unary( + '/ccnp_server_pb.ccnp/GetMeasurementCount', + request_serializer=ccnp_dot_ccnp__server__pb2.GetMeasurementCountRequest.SerializeToString, + response_deserializer=ccnp_dot_ccnp__server__pb2.GetMeasurementCountResponse.FromString, + ) + self.GetCcReport = channel.unary_unary( + '/ccnp_server_pb.ccnp/GetCcReport', + request_serializer=ccnp_dot_ccnp__server__pb2.GetCcReportRequest.SerializeToString, + response_deserializer=ccnp_dot_ccnp__server__pb2.GetCcReportResponse.FromString, + ) + self.GetCcMeasurement = channel.unary_unary( + '/ccnp_server_pb.ccnp/GetCcMeasurement', + request_serializer=ccnp_dot_ccnp__server__pb2.GetCcMeasurementRequest.SerializeToString, + response_deserializer=ccnp_dot_ccnp__server__pb2.GetCcMeasurementResponse.FromString, + ) + self.GetCcEventlog = channel.unary_unary( + '/ccnp_server_pb.ccnp/GetCcEventlog', + request_serializer=ccnp_dot_ccnp__server__pb2.GetCcEventlogRequest.SerializeToString, + response_deserializer=ccnp_dot_ccnp__server__pb2.GetCcEventlogResponse.FromString, + ) + + +class ccnpServicer(object): + """Missing associated documentation comment in .proto file.""" + + def GetDefaultAlgorithm(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetMeasurementCount(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetCcReport(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetCcMeasurement(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetCcEventlog(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ccnpServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetDefaultAlgorithm': grpc.unary_unary_rpc_method_handler( + servicer.GetDefaultAlgorithm, + request_deserializer=ccnp_dot_ccnp__server__pb2.GetDefaultAlgorithmRequest.FromString, + response_serializer=ccnp_dot_ccnp__server__pb2.GetDefaultAlgorithmResponse.SerializeToString, + ), + 'GetMeasurementCount': grpc.unary_unary_rpc_method_handler( + servicer.GetMeasurementCount, + request_deserializer=ccnp_dot_ccnp__server__pb2.GetMeasurementCountRequest.FromString, + response_serializer=ccnp_dot_ccnp__server__pb2.GetMeasurementCountResponse.SerializeToString, + ), + 'GetCcReport': grpc.unary_unary_rpc_method_handler( + servicer.GetCcReport, + request_deserializer=ccnp_dot_ccnp__server__pb2.GetCcReportRequest.FromString, + response_serializer=ccnp_dot_ccnp__server__pb2.GetCcReportResponse.SerializeToString, + ), + 'GetCcMeasurement': grpc.unary_unary_rpc_method_handler( + servicer.GetCcMeasurement, + request_deserializer=ccnp_dot_ccnp__server__pb2.GetCcMeasurementRequest.FromString, + response_serializer=ccnp_dot_ccnp__server__pb2.GetCcMeasurementResponse.SerializeToString, + ), + 'GetCcEventlog': grpc.unary_unary_rpc_method_handler( + servicer.GetCcEventlog, + request_deserializer=ccnp_dot_ccnp__server__pb2.GetCcEventlogRequest.FromString, + response_serializer=ccnp_dot_ccnp__server__pb2.GetCcEventlogResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'ccnp_server_pb.ccnp', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ccnp(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetDefaultAlgorithm(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/ccnp_server_pb.ccnp/GetDefaultAlgorithm', + ccnp_dot_ccnp__server__pb2.GetDefaultAlgorithmRequest.SerializeToString, + ccnp_dot_ccnp__server__pb2.GetDefaultAlgorithmResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetMeasurementCount(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/ccnp_server_pb.ccnp/GetMeasurementCount', + ccnp_dot_ccnp__server__pb2.GetMeasurementCountRequest.SerializeToString, + ccnp_dot_ccnp__server__pb2.GetMeasurementCountResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetCcReport(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/ccnp_server_pb.ccnp/GetCcReport', + ccnp_dot_ccnp__server__pb2.GetCcReportRequest.SerializeToString, + ccnp_dot_ccnp__server__pb2.GetCcReportResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetCcMeasurement(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/ccnp_server_pb.ccnp/GetCcMeasurement', + ccnp_dot_ccnp__server__pb2.GetCcMeasurementRequest.SerializeToString, + ccnp_dot_ccnp__server__pb2.GetCcMeasurementResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetCcEventlog(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/ccnp_server_pb.ccnp/GetCcEventlog', + ccnp_dot_ccnp__server__pb2.GetCcEventlogRequest.SerializeToString, + ccnp_dot_ccnp__server__pb2.GetCcEventlogResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/sdk/python3/ccnp/eventlog/__init__.py b/sdk/python3/ccnp/eventlog/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sdk/python3/ccnp/eventlog/eventlog_sdk.py b/sdk/python3/ccnp/eventlog/eventlog_sdk.py deleted file mode 100644 index 2f80cde..0000000 --- a/sdk/python3/ccnp/eventlog/eventlog_sdk.py +++ /dev/null @@ -1,565 +0,0 @@ -# pylint: disable=duplicate-code -# Copyright (c) 2023, Intel Corporation. All rights reserved.
-# SPDX-License-Identifier: Apache-2.0 -""" -This package provides the definitions and helper class for the event log of TD -or TPM. - -Reference: -1. https://github.com/tpm2-software/tpm2-tcti-uefi/blob/master/src/tcg2-protocol.h -""" - -import logging -import os -import json -from typing import List -import grpc - -# pylint: disable=E1101 -from ccnp.eventlog import eventlog_server_pb2 -from ccnp.eventlog import eventlog_server_pb2_grpc - -LOG = logging.getLogger(__name__) -TIMEOUT = 5 - -class CCAlgorithms: - """ - Algorithms class for confidential computing. - - The definitions are aligning with TCG specification - "TCG Algorithm Registry" - at https://trustedcomputinggroup.org/wp-content/uploads/TCGAlgorithmRegistry_Rev01.15.pdf - """ - - ALG_SHA1 = 0xA - ALG_SHA256 = 0xB - ALG_SHA384 = 0xC - ALG_SHA512 = 0xD - ALG_SM3_256 = 0xE - ALG_INVALID = 0xffffffff - - _algo_dict = None - - _digest_size = { - ALG_SHA1: 20, - ALG_SHA256: 32, - ALG_SHA384: 48, - ALG_SHA512: 64, - ALG_SM3_256: 32 - } - - _block_size = { - ALG_SHA1: 64, - ALG_SHA256: 64, - ALG_SHA384: 128, - ALG_SHA512: 128, - ALG_SM3_256: 64 - } - - def __init__(self): - self._algo_id = CCAlgorithms.ALG_INVALID - - @property - def algo_id(self): - """ - Property of algorithms ID - """ - return self._algo_id - - @property - def digest_size(self): - """ - Property of digest size - """ - return self._digest_size[self.algo_id] - - @property - def block_size(self): - """ - Property of block size - """ - return self._block_size[self.algo_id] - - @algo_id.setter - def algo_id(self, value): - """ - Setter for the property algorithms ID - """ - assert value != CCAlgorithms.ALG_INVALID - self._algo_id = value - - @property - def is_valid(self): - """ - Property of algorithm id valid check - """ - return self._algo_id != CCAlgorithms.ALG_INVALID - - @classmethod - def algo_dict(cls): - """ - Class method to construct the algo dict - """ - if cls._algo_dict is not None: - return cls._algo_dict - - # first time initialization - cls._algo_dict = {} - for key, value in cls.__dict__.items(): - if key.startswith('ALG'): - # pylint: disable=E1137 - cls._algo_dict[value] = key - return cls._algo_dict - - def __str__(self): - """ - Get string of algorithms name - """ - CCAlgorithms.algo_dict() - assert CCAlgorithms._algo_dict is not None - # pylint: disable=E1135 - assert self.algo_id in CCAlgorithms._algo_dict - # pylint: disable=E1136 - return CCAlgorithms._algo_dict[self.algo_id] - - -class CCEventLogType: - """ - Event log type for Confidential Computing - """ - - # TCG PC Client Specific Implementation Specification for Conventional BIOS - EV_PREBOOT_CERT = 0x0 - EV_POST_CODE = 0x1 - EV_UNUSED = 0x2 - EV_NO_ACTION = 0x3 - EV_SEPARATOR = 0x4 - EV_ACTION = 0x5 - EV_EVENT_TAG = 0x6 - EV_S_CRTM_CONTENTS = 0x7 - EV_S_CRTM_VERSION = 0x8 - EV_CPU_MICROCODE = 0x9 - EV_PLATFORM_CONFIG_FLAGS = 0xa - EV_TABLE_OF_DEVICES = 0xb - EV_COMPACT_HASH = 0xc - EV_IPL = 0xd - EV_IPL_PARTITION_DATA = 0xe - EV_NONHOST_CODE = 0xf - EV_NONHOST_CONFIG = 0x10 - EV_NONHOST_INFO = 0x11 - EV_OMIT_BOOT_DEVICE_EVENTS = 0x12 - - # TCG EFI Platform Specification For TPM Family 1.1 or 1.2 - EV_EFI_EVENT_BASE = 0x80000000 - EV_EFI_VARIABLE_DRIVER_CONFIG = EV_EFI_EVENT_BASE + 0x1 - EV_EFI_VARIABLE_BOOT = EV_EFI_EVENT_BASE + 0x2 - EV_EFI_BOOT_SERVICES_APPLICATION = EV_EFI_EVENT_BASE + 0x3 - EV_EFI_BOOT_SERVICES_DRIVER = EV_EFI_EVENT_BASE + 0x4 - EV_EFI_RUNTIME_SERVICES_DRIVER = EV_EFI_EVENT_BASE + 0x5 - EV_EFI_GPT_EVENT = EV_EFI_EVENT_BASE + 0x6 - EV_EFI_ACTION = EV_EFI_EVENT_BASE + 0x7 - EV_EFI_PLATFORM_FIRMWARE_BLOB = EV_EFI_EVENT_BASE + 0x8 - EV_EFI_HANDOFF_TABLES = EV_EFI_EVENT_BASE + 0x9 - EV_EFI_VARIABLE_AUTHORITY = EV_EFI_EVENT_BASE + 0xe0 - EV_UNKNOWN_A = EV_EFI_EVENT_BASE + 0xa - EV_UNKNOWN_B = EV_EFI_EVENT_BASE + 0xb - EV_UNKNOWN_C = EV_EFI_EVENT_BASE + 0xc - - - EV_INVALID = 0xffffffff - - _type_dict = None - - @classmethod - def event_log_dict(cls): - """ - Class method to construct the event log dict - """ - if cls._type_dict is not None: - return cls._type_dict - - # first time initialization - cls._type_dict = {} - for key, value in cls.__dict__.items(): - if key.startswith('EV_'): - # pylint: disable=E1137 - cls._type_dict[value] = key - return cls._type_dict - - def __init__(self): - """ - Constructor - """ - self._log_type = CCEventLogType.EV_INVALID - - @property - def log_type(self): - """ - Property for type of event log - """ - return self._log_type - - @log_type.setter - def log_type(self, value): - """ - Set the event log type - """ - CCEventLogType.event_log_dict() - assert CCEventLogType._type_dict is not None - # pylint: disable=E1135 - assert value in CCEventLogType._type_dict - self._log_type = value - - @classmethod - def log_type_string(cls, value): - """ - Get string of eventlog type - """ - assert CCEventLogType._type_dict is not None - # pylint: disable=E1135 - assert value in CCEventLogType._type_dict - # pylint: disable=E1136 - return CCEventLogType._type_dict[value] - -class CCEventLogEntry: - - INVALID_MEASURE_REGISTER_INDEX = -1 - INVALID_EVENT_TYPE = -1 - INVALID_ALGORITHMS_ID = -1 - UNKNOWN_EVENT_TYPE_NAME = "UNKNOWN" - - def __init__(self) -> None: - self._reg_idx: int = CCEventLogEntry.INVALID_MEASURE_REGISTER_INDEX - self._evt_type: int = CCEventLogType() - self._evt_type_str: str = CCEventLogEntry.UNKNOWN_EVENT_TYPE_NAME - self._evt_size: int = -1 - self._alg_id: CCAlgorithms = CCAlgorithms() - self._event: bytearray = None - self._digest: bytearray = None - - @property - def reg_idx(self): - """ - Property for type register index - """ - return self._reg_idx - - @reg_idx.setter - def reg_idx(self, value): - """ - Setter for the property register index - """ - assert value != CCEventLogEntry.INVALID_MEASURE_REGISTER_INDEX - self._reg_idx = value - - @property - def evt_type(self): - """ - Property for type event type - """ - return self._evt_type - - @evt_type.setter - def evt_type(self, value): - """ - Setter for the property event type - """ - assert value != CCEventLogEntry.INVALID_EVENT_TYPE - self._evt_type = value - - @property - def evt_type_str(self): - """ - Property for type event type string - """ - return self._evt_type_str - - @evt_type_str.setter - def evt_type_str(self, value): - """ - Setter for the property event type string - """ - assert value != CCEventLogEntry.UNKNOWN_EVENT_TYPE_NAME - self._evt_type_str = value - - @property - def evt_size(self): - """ - Property for type event size - """ - return self._evt_size - - @evt_size.setter - def evt_size(self, value): - """ - Setter for the property event size - """ - assert value > 0 - self._evt_size = value - - @property - def alg_id(self): - """ - Property for type algorithm id - """ - return self._alg_id - - @alg_id.setter - def alg_id(self, value): - """ - Setter for the property algorithms ID - """ - assert value != CCEventLogEntry.INVALID_ALGORITHMS_ID - self._alg_id = value - - @property - def event(self): - """ - Property for type event - """ - return self._event - - @event.setter - def event(self, value): - """ - Setter for the property event - """ - assert value is not None - self._event = value - - @property - def digest(self): - """ - Property for type digest - """ - return self._digest - - @digest.setter - def digest(self, value): - """ - Setter for the property digest - """ - assert value is not None - self._digest = value - -class EventlogUtility: - """ - Common utility for eventlog related actions - """ - - def __init__(self, target="unix:/run/ccnp/uds/eventlog.sock"): - if target[:5] != "unix:": - raise ValueError("Invalid server path, only unix domain socket supported.") - - if not os.path.exists(target.replace('unix:', '')): - raise FileNotFoundError('eventlog socket does not exist.') - self._channel = grpc.insecure_channel(target) - try: - grpc.channel_ready_future(self._channel).result(timeout=TIMEOUT) - except grpc.FutureTimeoutError as err: - raise ConnectionRefusedError('Connection to eventlog server failed') from err - self._stub = eventlog_server_pb2_grpc.EventlogStub(self._channel) - self._request = eventlog_server_pb2.GetEventlogRequest() - self._raw_eventlogs = "" - - def setup_eventlog_request(self, eventlog_level=0, eventlog_category=0, - start_position=None, count=None): - """ - Generate grpc request to get eventlog - - Args: - eventlog_level(LEVEL): level of event logs to fetch(platform level - or container level) - eventlog_category(CATEGORY): different category of event logs to fetch - start_position(int): start position of event log to fetch - count(int): number of event log to fetch - """ - self._request = eventlog_server_pb2.GetEventlogRequest( - eventlog_level=eventlog_level, - eventlog_category=eventlog_category, - start_position=start_position, - count=count) - - def cleanup_channel(self): - """ Close the channel used for grpc """ - self._channel.close() - - def get_raw_eventlogs(self): - """ - Get raw eventlogs - - Args: - request (GetEventlogRequest): request data - stub (EventlogStub): the stub to call server - - Returns: - string: json string of eventlogs - """ - - e = self._stub.GetEventlog(self._request) - if e.eventlog_data_loc == "": - LOG.info("Failed to get eventlog from server.") - return "" - - LOG.info("Fetch eventlog successfully.") - with open(e.eventlog_data_loc, 'r', encoding='utf-8') as f: - self._raw_eventlogs = f.read() - return "" - - def parse_saas_eventlogs(self, eventlogs) -> List[CCEventLogEntry]: - """ - Parse SaaS level eventlog into CCEventLogEntry - - Args: - eventlogs (dict): raw eventlog data - - Returns: - array: list of CCEventLogEntry - """ - LOG.info("Not implemented") - return [] - - def parse_eventlogs(self, eventlogs) -> List[CCEventLogEntry]: - """ - Parse eventlog into CCEventLogEntry - - Args: - eventlogs (dict): raw eventlog data - - Returns: - array: list of CCEventLogEntry - """ - if self._request.eventlog_level == eventlog_server_pb2.LEVEL.SAAS: - return self.parse_saas_eventlogs(eventlogs) - - event_log_array = [] - eventlog_list = eventlogs['EventLogs'] - - etypes = CCEventLogType() - etypes.event_log_dict() - - CCAlgorithms.algo_dict() - algs = CCAlgorithms() - - for item in eventlog_list: - etypes.log_type = item['Etype'] - algs.algo_id = item['AlgorithmId'] - digest_num = item['DigestCount'] - - if digest_num < 1: - LOG.info("No digest available") - continue - digests = item['Digests'] - - event_log = CCEventLogEntry() - if self._request.eventlog_category == eventlog_server_pb2.CATEGORY.TDX_EVENTLOG: - event_log.reg_idx = item['Rtmr'] - else: - event_log.reg_idx = item['Pcr'] - event_log.evt_type = etypes.log_type - event_log.evt_type_str = etypes.log_type_string(item['Etype']) - event_log.evt_size = item['EventSize'] - event_log.alg_id = algs - event_log.event = item['Event'] - event_log.digest = digests[digest_num-1] - event_log_array.append(event_log) - - return event_log_array - - def get_eventlog(self)-> List[CCEventLogEntry]: - """ - Get eventlog function to fetch event logs - - Returns: - array: list of CCEventLogEntry - """ - self.get_raw_eventlogs() - self.cleanup_channel() - - if self._raw_eventlogs == "": - LOG.info("No eventlog found.") - return None - - eventlog_dict = json.loads(self._raw_eventlogs) - cc_event_logs = self.parse_eventlogs(eventlog_dict) - - return cc_event_logs - - @classmethod - def get_platform_eventlog(cls, eventlog_type=eventlog_server_pb2.CATEGORY.TDX_EVENTLOG, - start_position=None, count=None) -> List[CCEventLogEntry]: - """ - Get eventlogs from platform perspective. - Currently, support event log fetching on Intel TDX and TPM. - - Args: - eventlog_type(EventlogType): type of event log to fetch - start_position(int): start position of event log to fetch - count(int): number of event logs to fetch - - Returns: - array: list of CCEventlogEntry - """ - if not EventlogType.is_valid_type(eventlog_type): - raise ValueError("Invalid eventlog type specified") - - if start_position is not None: - if not isinstance(start_position, int) or start_position < 0: - raise ValueError("Invalid value specified for start_position") - - if count is not None: - if not isinstance(count, int) or count <= 0: - raise ValueError("Invalid value specified for count") - - eventlog_class = cls() - eventlog_class.setup_eventlog_request(eventlog_server_pb2.LEVEL.PAAS, eventlog_type, - start_position, count) - cc_event_logs = eventlog_class.get_eventlog() - - return cc_event_logs - - @classmethod - def get_container_eventlog(cls): - """ - Get eventlogs from container perspective - - """ - raise NotImplementedError("Not implemented") - - -class EventlogType: - - # Get TDX event logs - TYPE_TDX = eventlog_server_pb2.CATEGORY.TDX_EVENTLOG - # Get TPM event logs - TYPE_TPM = eventlog_server_pb2.CATEGORY.TPM_EVENTLOG - - _type_dict = None - - @classmethod - def event_log_type_dict(cls): - """ - Class method to construct the event log typedict - """ - if cls._type_dict is not None: - return cls._type_dict - - # first time initialization - cls._type_dict = {} - for key, value in cls.__dict__.items(): - if key.startswith('TYPE_'): - # pylint: disable=E1137 - cls._type_dict[value] = key - return cls._type_dict - - @classmethod - def is_valid_type(cls, value): - """ - Class method to check if value is a valid eventlog type - """ - cls.event_log_type_dict() - if cls._type_dict is None: - return False - for key, _ in cls._type_dict.items(): - if key == value: - return True - return False diff --git a/sdk/python3/ccnp/eventlog/eventlog_server_pb2.py b/sdk/python3/ccnp/eventlog/eventlog_server_pb2.py deleted file mode 100644 index bb8462e..0000000 --- a/sdk/python3/ccnp/eventlog/eventlog_server_pb2.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: eventlog-server.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x65ventlog-server.proto\"\x81\x01\n\x12GetEventlogRequest\x12\x1e\n\x0e\x65ventlog_level\x18\x01 \x01(\x0e\x32\x06.LEVEL\x12$\n\x11\x65ventlog_category\x18\x02 \x01(\x0e\x32\t.CATEGORY\x12\x16\n\x0estart_position\x18\x03 \x01(\x05\x12\r\n\x05\x63ount\x18\x04 \x01(\x05\"-\n\x10GetEventlogReply\x12\x19\n\x11\x65ventlog_data_loc\x18\x01 \x01(\t*.\n\x08\x43\x41TEGORY\x12\x10\n\x0cTDX_EVENTLOG\x10\x00\x12\x10\n\x0cTPM_EVENTLOG\x10\x01*\x1b\n\x05LEVEL\x12\x08\n\x04PAAS\x10\x00\x12\x08\n\x04SAAS\x10\x01\x32\x43\n\x08\x45ventlog\x12\x37\n\x0bGetEventlog\x12\x13.GetEventlogRequest\x1a\x11.GetEventlogReply\"\x00\x42\x61Z_github.com/intel/confidential-cloud-native-primitives/service/eventlog-server/proto/getEventlogb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'eventlog_server_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Z_github.com/intel/confidential-cloud-native-primitives/service/eventlog-server/proto/getEventlog' - _globals['_CATEGORY']._serialized_start=204 - _globals['_CATEGORY']._serialized_end=250 - _globals['_LEVEL']._serialized_start=252 - _globals['_LEVEL']._serialized_end=279 - _globals['_GETEVENTLOGREQUEST']._serialized_start=26 - _globals['_GETEVENTLOGREQUEST']._serialized_end=155 - _globals['_GETEVENTLOGREPLY']._serialized_start=157 - _globals['_GETEVENTLOGREPLY']._serialized_end=202 - _globals['_EVENTLOG']._serialized_start=281 - _globals['_EVENTLOG']._serialized_end=348 -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python3/ccnp/eventlog/eventlog_server_pb2.pyi b/sdk/python3/ccnp/eventlog/eventlog_server_pb2.pyi deleted file mode 100644 index 6cf7bbc..0000000 --- a/sdk/python3/ccnp/eventlog/eventlog_server_pb2.pyi +++ /dev/null @@ -1,38 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class CATEGORY(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = [] - TDX_EVENTLOG: _ClassVar[CATEGORY] - TPM_EVENTLOG: _ClassVar[CATEGORY] - -class LEVEL(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = [] - PAAS: _ClassVar[LEVEL] - SAAS: _ClassVar[LEVEL] -TDX_EVENTLOG: CATEGORY -TPM_EVENTLOG: CATEGORY -PAAS: LEVEL -SAAS: LEVEL - -class GetEventlogRequest(_message.Message): - __slots__ = ["eventlog_level", "eventlog_category", "start_position", "count"] - EVENTLOG_LEVEL_FIELD_NUMBER: _ClassVar[int] - EVENTLOG_CATEGORY_FIELD_NUMBER: _ClassVar[int] - START_POSITION_FIELD_NUMBER: _ClassVar[int] - COUNT_FIELD_NUMBER: _ClassVar[int] - eventlog_level: LEVEL - eventlog_category: CATEGORY - start_position: int - count: int - def __init__(self, eventlog_level: _Optional[_Union[LEVEL, str]] = ..., eventlog_category: _Optional[_Union[CATEGORY, str]] = ..., start_position: _Optional[int] = ..., count: _Optional[int] = ...) -> None: ... - -class GetEventlogReply(_message.Message): - __slots__ = ["eventlog_data_loc"] - EVENTLOG_DATA_LOC_FIELD_NUMBER: _ClassVar[int] - eventlog_data_loc: str - def __init__(self, eventlog_data_loc: _Optional[str] = ...) -> None: ... diff --git a/sdk/python3/ccnp/eventlog/eventlog_server_pb2_grpc.py b/sdk/python3/ccnp/eventlog/eventlog_server_pb2_grpc.py deleted file mode 100644 index e726f21..0000000 --- a/sdk/python3/ccnp/eventlog/eventlog_server_pb2_grpc.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from ccnp.eventlog import eventlog_server_pb2 as eventlog__server__pb2 - - -class EventlogStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetEventlog = channel.unary_unary( - '/Eventlog/GetEventlog', - request_serializer=eventlog__server__pb2.GetEventlogRequest.SerializeToString, - response_deserializer=eventlog__server__pb2.GetEventlogReply.FromString, - ) - - -class EventlogServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetEventlog(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_EventlogServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetEventlog': grpc.unary_unary_rpc_method_handler( - servicer.GetEventlog, - request_deserializer=eventlog__server__pb2.GetEventlogRequest.FromString, - response_serializer=eventlog__server__pb2.GetEventlogReply.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'Eventlog', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class Eventlog(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetEventlog(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/Eventlog/GetEventlog', - eventlog__server__pb2.GetEventlogRequest.SerializeToString, - eventlog__server__pb2.GetEventlogReply.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/sdk/python3/ccnp/measurement/__init__.py b/sdk/python3/ccnp/measurement/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sdk/python3/ccnp/measurement/measurement_sdk.py b/sdk/python3/ccnp/measurement/measurement_sdk.py deleted file mode 100644 index d41a73c..0000000 --- a/sdk/python3/ccnp/measurement/measurement_sdk.py +++ /dev/null @@ -1,156 +0,0 @@ -# pylint: disable=duplicate-code -# Copyright (c) 2023, Intel Corporation. All rights reserved.
-# SPDX-license-identifier: Apache-2.0 -"""This module provides the functions to talk to measurement-server and fetch measurements""" - -import logging -import os - -import grpc -# pylint: disable=E1101 -from ccnp.measurement import measurement_server_pb2 -from ccnp.measurement import measurement_server_pb2_grpc - -LOG = logging.getLogger(__name__) -TIMEOUT = 5 - - -class MeasurementUtility: - """ - Common utility for measurement related actions - """ - - def __init__(self, target="unix:/run/ccnp/uds/measurement.sock"): - if target[:5] != "unix:": - raise ValueError("Invalid server path, only unix domain socket supported.") - - if not os.path.exists(target.replace('unix:', '')): - raise FileNotFoundError('Measurement socket does not exist.') - self._channel = grpc.insecure_channel(target) - try: - grpc.channel_ready_future(self._channel).result(timeout=TIMEOUT) - except grpc.FutureTimeoutError as err: - raise ConnectionRefusedError('Connection to measurement server failed') from err - self._stub = measurement_server_pb2_grpc.MeasurementStub(self._channel) - self._request = measurement_server_pb2.GetMeasurementRequest() - - def setup_measurement_request(self, measurement_type=0, measurement_category=0, - report_data=None, register_index=0): - """ Function to generate a get_measurement request - Setup a measurement request for get_measurement API - - Args: - measurement_type(TYPE): type of measurement to fetch - PaaS or SaaS - measurement_category(CATEGORY): category of measurement to fetch - report_data(str): user data to get wrapped as part of tee report - register_index(int): register index to fetch measurements - """ - self._request = measurement_server_pb2.GetMeasurementRequest( - measurement_type=measurement_type, - measurement_category=measurement_category, - report_data=report_data, - register_index=register_index - ) - - def cleanup_channel(self): - """ Clean up channel used for grpc """ - self._channel.close() - - def get_measurement(self): - """ - Get measurement - - Args: - request (GetMeasurementRequest): request data - stub (MeasurementStub): the stub to call server - - Returns: - string: base64 encoded string of measurement - """ - - reply_data = self._stub.GetMeasurement(self._request) - if reply_data.measurement == "": - LOG.info("Failed to get measurement from server.") - return "" - - LOG.info("Fetch measurement successfully.") - return reply_data.measurement - - @classmethod - def get_platform_measurement(cls, measurement_type=measurement_server_pb2.CATEGORY.TEE_REPORT, - report_data=None, register_index=None) -> str: - """ - Get measurements from platform perspective. - Currently, support measurement fetching on TEE reports, Intel TDX RTMR and TPM. - - Args: - measurement_type(MeasurementType): type of measurement to fetch - report_data(str): user data to be wrapped as part of TEE report - register_index(int): register index used to fetch TDX RTMR or TPM PCR measurement - - Returns: - string: base64 encoded measurement string - """ - if not MeasurementType.is_valid_type(measurement_type): - raise ValueError("Invalid measurement type specified") - - if report_data is not None: - if not isinstance(report_data, str) or len(report_data) > 64: - raise ValueError("Invalid report data specified") - - if register_index is not None: - if not isinstance(register_index, int) or register_index < 0 or register_index > 16: - raise ValueError("Invalid value specified for register index") - - measurement_class = cls() - measurement_class.setup_measurement_request(measurement_server_pb2.TYPE.PAAS, - measurement_type, report_data, register_index) - return measurement_class.get_measurement() - - @classmethod - def get_container_measurement(cls) -> str: - """ - Get measurements from container perspective - - """ - raise NotImplementedError("Not implemented") - -class MeasurementType: - - # Get TEE report - TYPE_TEE_REPORT = measurement_server_pb2.CATEGORY.TEE_REPORT - # Get TDX RTMR measurement (of a specific register) - TYPE_TDX_RTMR = measurement_server_pb2.CATEGORY.TDX_RTMR - # Get TPM PCR measurement (of a specific register) - TYPE_TPM_PCR =measurement_server_pb2.CATEGORY.TPM - - _type_dict = None - - @classmethod - def measurement_type_dict(cls): - """ - Class method to construct the event log typedict - """ - if cls._type_dict is not None: - return cls._type_dict - - # first time initialization - cls._type_dict = {} - for key, value in cls.__dict__.items(): - if key.startswith('TYPE_'): - # pylint: disable=E1137 - cls._type_dict[value] = key - return cls._type_dict - - @classmethod - def is_valid_type(cls, value): - """ - Class method to check if value is a valid eventlog type - """ - cls.measurement_type_dict() - if cls._type_dict is None: - return False - for key, _ in cls._type_dict.items(): - if key == value: - return True - return False diff --git a/sdk/python3/ccnp/measurement/measurement_server_pb2.py b/sdk/python3/ccnp/measurement/measurement_server_pb2.py deleted file mode 100644 index c11728c..0000000 --- a/sdk/python3/ccnp/measurement/measurement_server_pb2.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: measurement-server.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18measurement-server.proto\x12\x0bmeasurement\"\xa6\x01\n\x15GetMeasurementRequest\x12+\n\x10measurement_type\x18\x01 \x01(\x0e\x32\x11.measurement.TYPE\x12\x33\n\x14measurement_category\x18\x02 \x01(\x0e\x32\x15.measurement.CATEGORY\x12\x13\n\x0breport_data\x18\x03 \x01(\t\x12\x16\n\x0eregister_index\x18\x04 \x01(\x05\"*\n\x13GetMeasurementReply\x12\x13\n\x0bmeasurement\x18\x01 \x01(\t*\x1a\n\x04TYPE\x12\x08\n\x04PAAS\x10\x00\x12\x08\n\x04SAAS\x10\x01*1\n\x08\x43\x41TEGORY\x12\x0e\n\nTEE_REPORT\x10\x00\x12\x07\n\x03TPM\x10\x01\x12\x0c\n\x08TDX_RTMR\x10\x02\x32g\n\x0bMeasurement\x12X\n\x0eGetMeasurement\x12\".measurement.GetMeasurementRequest\x1a .measurement.GetMeasurementReply\"\x00\x42gZegithub.com/intel/confidential-cloud-native-primitives/service/measurement-server/proto/getMeasurementb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'measurement_server_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'Zegithub.com/intel/confidential-cloud-native-primitives/service/measurement-server/proto/getMeasurement' - _globals['_TYPE']._serialized_start=254 - _globals['_TYPE']._serialized_end=280 - _globals['_CATEGORY']._serialized_start=282 - _globals['_CATEGORY']._serialized_end=331 - _globals['_GETMEASUREMENTREQUEST']._serialized_start=42 - _globals['_GETMEASUREMENTREQUEST']._serialized_end=208 - _globals['_GETMEASUREMENTREPLY']._serialized_start=210 - _globals['_GETMEASUREMENTREPLY']._serialized_end=252 - _globals['_MEASUREMENT']._serialized_start=333 - _globals['_MEASUREMENT']._serialized_end=436 -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python3/ccnp/measurement/measurement_server_pb2.pyi b/sdk/python3/ccnp/measurement/measurement_server_pb2.pyi deleted file mode 100644 index daf7f4e..0000000 --- a/sdk/python3/ccnp/measurement/measurement_server_pb2.pyi +++ /dev/null @@ -1,40 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class TYPE(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = [] - PAAS: _ClassVar[TYPE] - SAAS: _ClassVar[TYPE] - -class CATEGORY(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = [] - TEE_REPORT: _ClassVar[CATEGORY] - TPM: _ClassVar[CATEGORY] - TDX_RTMR: _ClassVar[CATEGORY] -PAAS: TYPE -SAAS: TYPE -TEE_REPORT: CATEGORY -TPM: CATEGORY -TDX_RTMR: CATEGORY - -class GetMeasurementRequest(_message.Message): - __slots__ = ["measurement_type", "measurement_category", "report_data", "register_index"] - MEASUREMENT_TYPE_FIELD_NUMBER: _ClassVar[int] - MEASUREMENT_CATEGORY_FIELD_NUMBER: _ClassVar[int] - REPORT_DATA_FIELD_NUMBER: _ClassVar[int] - REGISTER_INDEX_FIELD_NUMBER: _ClassVar[int] - measurement_type: TYPE - measurement_category: CATEGORY - report_data: str - register_index: int - def __init__(self, measurement_type: _Optional[_Union[TYPE, str]] = ..., measurement_category: _Optional[_Union[CATEGORY, str]] = ..., report_data: _Optional[str] = ..., register_index: _Optional[int] = ...) -> None: ... - -class GetMeasurementReply(_message.Message): - __slots__ = ["measurement"] - MEASUREMENT_FIELD_NUMBER: _ClassVar[int] - measurement: str - def __init__(self, measurement: _Optional[str] = ...) -> None: ... diff --git a/sdk/python3/ccnp/measurement/measurement_server_pb2_grpc.py b/sdk/python3/ccnp/measurement/measurement_server_pb2_grpc.py deleted file mode 100644 index c46c999..0000000 --- a/sdk/python3/ccnp/measurement/measurement_server_pb2_grpc.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from ccnp.measurement import measurement_server_pb2 as measurement__server__pb2 - - -class MeasurementStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetMeasurement = channel.unary_unary( - '/measurement.Measurement/GetMeasurement', - request_serializer=measurement__server__pb2.GetMeasurementRequest.SerializeToString, - response_deserializer=measurement__server__pb2.GetMeasurementReply.FromString, - ) - - -class MeasurementServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetMeasurement(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_MeasurementServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetMeasurement': grpc.unary_unary_rpc_method_handler( - servicer.GetMeasurement, - request_deserializer=measurement__server__pb2.GetMeasurementRequest.FromString, - response_serializer=measurement__server__pb2.GetMeasurementReply.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'measurement.Measurement', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class Measurement(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetMeasurement(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/measurement.Measurement/GetMeasurement', - measurement__server__pb2.GetMeasurementRequest.SerializeToString, - measurement__server__pb2.GetMeasurementReply.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/sdk/python3/ccnp/quote/__init__.py b/sdk/python3/ccnp/quote/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sdk/python3/ccnp/quote/quote_sdk.py b/sdk/python3/ccnp/quote/quote_sdk.py deleted file mode 100644 index a720b4f..0000000 --- a/sdk/python3/ccnp/quote/quote_sdk.py +++ /dev/null @@ -1,338 +0,0 @@ -# Copyright (c) 2023, Intel Corporation. All rights reserved.
-# SPDX-License-Identifier: Apache-2.0 - -""" -This package provides the definitions and helper class for Quote of confidetial computing, -which will be used for remote attestation. - -Reference: -1. Part 2: Structures, Trusted Platform Module Library -https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part2_Structures_pub.pdf -2. Architecture Specification: Intel® Trust Domain Extensions (Intel® TDX) Module -https://cdrdv2.intel.com/v1/dl/getContent/733568 -""" - -import base64 -import logging -import os -import struct -from typing import Optional -import grpc -# pylint: disable=E1101 -from ccnp.quote import quote_server_pb2 -from ccnp.quote import quote_server_pb2_grpc - -LOG = logging.getLogger(__name__) - -# Default gRPC timeout -TIMEOUT = 5 - -class QuoteClient: - """Quote client class - - This class is a client to connect to Quote Server and do gRPC call getting the - server. - - Attributes: - _server (str): The gRPC server to connect. - _channel (Channel): The gRPC channel, thread-safe. - _nonce (str): The nonce parameter to get quote. - _user_data (str): The user data parameter to get quote. - _stub (GetQuoteStub): The get quote stub for gRPC. - _request (GetQuoteRequest): The get quote request for gRPC. - """ - def __init__(self, server: str="unix:/run/ccnp/uds/quote-server.sock"): - """Initialize a quote client object - - This constructor initializes quote client object with Unix Domain Socket (UDS) - path. And prepare default atrributes. - - Args: - server (str): gRPC server UDS path, default is /run/ccnp/uds/quote-server.sock - - Raises: - ValueError: If server UDS path is not valid. - """ - if len(server) == 0 or server[:5] != "unix:": - raise ValueError("Invalid server path, only unix domain socket supported.") - self._server = server - self._channel = None - self._stub = None - self._request = None - - def request(self, nonce: str, user_data: str) -> Optional[quote_server_pb2.GetQuoteResponse]: - """Do reuqest to Quote Server - Detect the Quote Server and gRPC connect to the server. Make the getting quote stub - and request for communication. - - Args: - nonce (str): The nonce parameters for getting quote. - user_data (str): The user data parameters for getting quote. - - Raises: - RuntimeError: If Quote Server does not start. - ConnectionRefusedError: If connect to Quote Server failed. - """ - if self._channel is None: - if not os.path.exists(self._server.replace('unix:', '')): - raise RuntimeError("Quote server does not start.") - self._channel = grpc.insecure_channel(self._server, - options=[('grpc.default_authority', 'localhost')]) - try: - grpc.channel_ready_future(self._channel).result(timeout=TIMEOUT) - except grpc.FutureTimeoutError as err: - raise ConnectionRefusedError('Connection to quote server failed') from err - self._stub = quote_server_pb2_grpc.GetQuoteStub(self._channel) - - self._request = quote_server_pb2.GetQuoteRequest(nonce=nonce, user_data=user_data) - resp = self._stub.GetQuote(self._request) - return resp - -class Quote(): - """An abstract base class for Quote - - This class a abstract class with a common static method `get_quote` for external - SDK interface, the subclasses need to implement `parse` method to parse Quote - information. - - Attributes: - _quote (bytes): The bytes of a quote. - _type (str): The type of a quote. - """ - - TYPE_TDX = 'TDX' - TYPE_TPM = 'TPM' - - def __init__(self, quote: str = None, quote_type: str = None): - """Initialize Quote object. - - The constructor to initialize quote object with quote bytes and quote type. - - Args: - quote (bytes): The bytes of a quote. - quote_type (str): The type of a quote. - """ - self._quote = quote - self._type = quote_type - - @property - def quote_type(self) -> int: - """str: The type of the quote.""" - return self._type - - @property - def quote(self) -> bytes: - """bytes: the bytes of the quote""" - return self._quote - - @staticmethod - def get_quote(nonce: str = None, user_data: str = None): - """Get quote interface - - The get quote interface to expose to SDK. - - Args: - nonce (str): Base64 encoded nonce to prevent replay attack. - user_data (str): Base64 encoded user data to be measured in a quote. - - Returns: - Quote: The quote object for specific quote type. - None: Filed to get a quote. - """ - quote = QuoteClient() - resp = quote.request(nonce, user_data) - if resp is not None and resp.quote_type is not None \ - and resp.quote is not None and len(resp.quote) > 0: - LOG.info("Get quote successfully.") - quote_data = base64.b64decode(resp.quote) - quote_type = resp.quote_type - if quote_type == "TDX": - td_quote = QuoteTDX(quote_data, quote_type) - td_quote.parse() - return td_quote - LOG.error("Failed to get quote.") - return None - -class QuoteTDX(Quote): - """TDX quote class - - This class is a subclass of Quote to parse TDX sepecific quote. - Refer: https://cdrdv2.intel.com/v1/dl/getContent/733568 - - Attributes: - _version (int): TD quote version - _tdreport (bytes): The bytes of TD report. - _tee_type (int): Type of TEE for which the Quote has been generated. - _tee_tcb_svn (bytes): Array of TEE TCB SVNs. - _mrseam (bytes): Measurement of the SEAM module (SHA384 hash). - _mrsignerseam (bytes): Measurement of a 3rd party SEAM module’s signer (SHA384 hash). - _seamattributes (bytes): SEAM’s ATTRIBUTES. - _tdattributes (bytes): TD’s ATTRIBUTES. - _xfam (bytes): TD’s XFAM. - _mrtd (bytes): Measurement of the initial contents of the TD (SHA384 hash). - _mrconfigid (bytes): Software defined ID for non-owner-defined configuration of the TD - _mrowner (bytes): Software defined ID for the guest TD’s owner. - _mrownerconfig (bytes): Software defined ID for owner-defined configuration of the TD - _rtmr (bytes): Array of 4 runtime extendable measurement registers (SHA384 hash). - _reportdata (bytes): Additional Report Data. - _signature (bytes): ECDSA signature, r component followed by s component, 2 x 32 bytes. - _attestation_key (bytes): Public part of ECDSA Attestation Key generated by Quoting Enclave. - _cert_data (bytes): Data required to certify Attestation Key used to sign the Quote. - """ - def __init__(self, quote: bytes, quote_type: str): - """Initialize TD quote object - - The constructor of TD quote object, initialize attributes. - - Args: - quote (bytes): The bytes of a quote. - quote_type (str): The type of a quote. - """ - super().__init__(quote, quote_type) - self._version = 0 - self._tdreport = None - self._tee_type = 0 - self._tee_tcb_svn = None - self._mrseam = None - self._mrsignerseam = None - self._seamattributes = None - self._tdattributes = None - self._xfam = None - self._mrtd = None - self._mrconfigid = None - self._mrowner = None - self._mrownerconfig = None - self._rtmrs = [] - self._reportdata = None - self._signature = None - self._attestation_key = None - self._cert_data = None - - @property - def version(self) -> int: - """int: the version of the quote""" - return self._version - - @property - def tdreport(self) -> bytes: - """bytes: the bytes of the TD report""" - return self._tdreport - - @property - def tee_type(self) -> int: - """int: the TEE type of the quote""" - return self._tee_type - - @property - def mrseam(self) -> bytes: - """bytes: the MRSEAM in the quote""" - return self._mrseam - - @property - def mrsignerseam(self) -> bytes: - """bytes: the bytes of MRSIGNERSEAM in the quote""" - return self._mrsignerseam - - @property - def seam_attributes(self) -> bytes: - """bytes: the bytes of SEAM ATTRIBUTES in the quote""" - return self._seamattributes - - @property - def td_attributes(self) -> bytes: - """bytes: the bytes of TD ATTRIBUTES in the quote""" - return self._tdattributes - - @property - def xfam(self) -> bytes: - """bytes: the bytes of XFAM in the quote""" - return self._xfam - - @property - def mrtd(self) -> bytes: - """bytes: the bytes of MRTD in the quote""" - return self._mrtd - - @property - def mrconfigid(self) -> bytes: - """bytes: the bytes of MRCONFIGID in the quote""" - return self._mrconfigid - - @property - def mrowner(self) -> bytes: - """bytes: the bytes of MROWNER in the quote""" - return self._mrowner - - @property - def mrownerconfig(self) -> bytes: - """bytes: the bytes of MROWNERCONFIG in the quote""" - return self._mrownerconfig - - @property - def rtmrs(self) -> bytes: - """bytes: the bytes of RTMRs in the quote""" - rtmrs=[] - for i in range(4): - rtmrs.append(self._rtmrs[i*48:(i+1)*48]) - return rtmrs - - @property - def report_data(self) -> bytes: - """bytes: the bytes of REPORTDATA in the quote""" - return self._reportdata - - @property - def signature(self) -> bytes: - """bytes: the bytes of signature of the quote""" - return self._signature - - @property - def attestation_key(self) -> bytes: - """bytes: the bytes of attestation key in the quote""" - return self._attestation_key - - @property - def cert_data(self) -> bytes: - """bytes: the bytes of certification data in the quote""" - return self._cert_data - - def parse(self): - """Parse TD quote - - This method is to parse the TD quote and TD report data. - Refer: https://cdrdv2.intel.com/v1/dl/getContent/733568 - - Raises: - struct.error: Unpack quote data failed. - """ - quote_len = len(self._quote) - # Header, Body, Auth Data - header, self._tdreport, auth_size, auth_data = \ - struct.unpack(f"<48s584sI{quote_len-48-584-4}s", self._quote) - auth_data = auth_data[:auth_size] - # Header - self._version, _, self._tee_type, _ = struct.unpack(f"<2HI{len(header)-8}s", header) - # Body - self._tee_tcb_svn, self._mrseam, self._mrsignerseam, self._seamattributes, \ - self._tdattributes, self._xfam, self._mrtd, self._mrconfigid, self._mrowner, \ - self._mrownerconfig, self._rtmrs, self._reportdata = \ - struct.unpack("16s48s48s8s8s8s48s48s48s48s192s64s", self._tdreport) - # Auth Data - self._signature, self._attestation_key, cert_data = \ - struct.unpack(f"64s64s{auth_size-128}s", auth_data) - # Certification Data - _, _, self._cert_data = struct.unpack(f"\n\x06status\x18\x01 \x01(\x0e\x32..quoteserver.HealthCheckResponse.ServingStatus\"O\n\rServingStatus\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07SERVING\x10\x01\x12\x0f\n\x0bNOT_SERVING\x10\x02\x12\x13\n\x0fSERVICE_UNKNOWN\x10\x03\"3\n\x0fGetQuoteRequest\x12\x11\n\tuser_data\x18\x01 \x01(\t\x12\r\n\x05nonce\x18\x02 \x01(\t\"5\n\x10GetQuoteResponse\x12\r\n\x05quote\x18\x01 \x01(\t\x12\x12\n\nquote_type\x18\x02 \x01(\t2S\n\x08GetQuote\x12G\n\x08GetQuote\x12\x1c.quoteserver.GetQuoteRequest\x1a\x1d.quoteserver.GetQuoteResponseb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ccnp.quote.quote_server_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - _globals['_HEALTHCHECKREQUEST']._serialized_start=46 - _globals['_HEALTHCHECKREQUEST']._serialized_end=83 - _globals['_HEALTHCHECKRESPONSE']._serialized_start=86 - _globals['_HEALTHCHECKRESPONSE']._serialized_end=252 - _globals['_HEALTHCHECKRESPONSE_SERVINGSTATUS']._serialized_start=173 - _globals['_HEALTHCHECKRESPONSE_SERVINGSTATUS']._serialized_end=252 - _globals['_GETQUOTEREQUEST']._serialized_start=254 - _globals['_GETQUOTEREQUEST']._serialized_end=305 - _globals['_GETQUOTERESPONSE']._serialized_start=307 - _globals['_GETQUOTERESPONSE']._serialized_end=360 - _globals['_GETQUOTE']._serialized_start=362 - _globals['_GETQUOTE']._serialized_end=445 -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python3/ccnp/quote/quote_server_pb2.pyi b/sdk/python3/ccnp/quote/quote_server_pb2.pyi deleted file mode 100644 index f892ae6..0000000 --- a/sdk/python3/ccnp/quote/quote_server_pb2.pyi +++ /dev/null @@ -1,44 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class HealthCheckRequest(_message.Message): - __slots__ = ["service"] - SERVICE_FIELD_NUMBER: _ClassVar[int] - service: str - def __init__(self, service: _Optional[str] = ...) -> None: ... - -class HealthCheckResponse(_message.Message): - __slots__ = ["status"] - class ServingStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = [] - UNKNOWN: _ClassVar[HealthCheckResponse.ServingStatus] - SERVING: _ClassVar[HealthCheckResponse.ServingStatus] - NOT_SERVING: _ClassVar[HealthCheckResponse.ServingStatus] - SERVICE_UNKNOWN: _ClassVar[HealthCheckResponse.ServingStatus] - UNKNOWN: HealthCheckResponse.ServingStatus - SERVING: HealthCheckResponse.ServingStatus - NOT_SERVING: HealthCheckResponse.ServingStatus - SERVICE_UNKNOWN: HealthCheckResponse.ServingStatus - STATUS_FIELD_NUMBER: _ClassVar[int] - status: HealthCheckResponse.ServingStatus - def __init__(self, status: _Optional[_Union[HealthCheckResponse.ServingStatus, str]] = ...) -> None: ... - -class GetQuoteRequest(_message.Message): - __slots__ = ["user_data", "nonce"] - USER_DATA_FIELD_NUMBER: _ClassVar[int] - NONCE_FIELD_NUMBER: _ClassVar[int] - user_data: str - nonce: str - def __init__(self, user_data: _Optional[str] = ..., nonce: _Optional[str] = ...) -> None: ... - -class GetQuoteResponse(_message.Message): - __slots__ = ["quote", "quote_type"] - QUOTE_FIELD_NUMBER: _ClassVar[int] - QUOTE_TYPE_FIELD_NUMBER: _ClassVar[int] - quote: str - quote_type: str - def __init__(self, quote: _Optional[str] = ..., quote_type: _Optional[str] = ...) -> None: ... diff --git a/sdk/python3/ccnp/quote/quote_server_pb2_grpc.py b/sdk/python3/ccnp/quote/quote_server_pb2_grpc.py deleted file mode 100644 index e2ea74e..0000000 --- a/sdk/python3/ccnp/quote/quote_server_pb2_grpc.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from ccnp.quote import quote_server_pb2 as ccnp_dot_quote_dot_quote__server__pb2 - - -class GetQuoteStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetQuote = channel.unary_unary( - '/quoteserver.GetQuote/GetQuote', - request_serializer=ccnp_dot_quote_dot_quote__server__pb2.GetQuoteRequest.SerializeToString, - response_deserializer=ccnp_dot_quote_dot_quote__server__pb2.GetQuoteResponse.FromString, - ) - - -class GetQuoteServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetQuote(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_GetQuoteServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetQuote': grpc.unary_unary_rpc_method_handler( - servicer.GetQuote, - request_deserializer=ccnp_dot_quote_dot_quote__server__pb2.GetQuoteRequest.FromString, - response_serializer=ccnp_dot_quote_dot_quote__server__pb2.GetQuoteResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'quoteserver.GetQuote', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class GetQuote(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetQuote(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/quoteserver.GetQuote/GetQuote', - ccnp_dot_quote_dot_quote__server__pb2.GetQuoteRequest.SerializeToString, - ccnp_dot_quote_dot_quote__server__pb2.GetQuoteResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/sdk/python3/ccnp/sdk.py b/sdk/python3/ccnp/sdk.py new file mode 100644 index 0000000..ccdffa9 --- /dev/null +++ b/sdk/python3/ccnp/sdk.py @@ -0,0 +1,242 @@ +# Copyright (c) 2023, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0 + +""" +This package provides the definitions and helper class for CCNP SDK. +""" + +import logging +import os +from typing import Optional +import grpc + +from cctrusted_base.api import CCTrustedApi +from cctrusted_base.ccreport import CcReport +from cctrusted_base.tcg import TcgAlgorithmRegistry +from cctrusted_base.tcg import TcgDigest +from cctrusted_base.tcg import TcgImrEvent +from cctrusted_base.tcg import TcgPcClientImrEvent +from cctrusted_base.tdx.quote import TdxQuote +# pylint: disable=E1101 +from ccnp import ccnp_server_pb2 +from ccnp import ccnp_server_pb2_grpc + +LOG = logging.getLogger(__name__) + +# Default gRPC timeout +TIMEOUT = 5 + +class CcnpSdk(CCTrustedApi): + """CCNP SDK class + + This class is a client to connect to CCNP Server and do gRPC call getting the + server. + + Attributes: + _server (str): The gRPC server to connect. + _channel (Channel): The gRPC channel, thread-safe. + _stub (ccnpStub): The get CCNP stub for gRPC. + """ + _inst = None + + @classmethod + def inst(cls): + """Singleton instance function.""" + if cls._inst is None: + cls._inst = cls() + return cls._inst + + def __init__(self, server: str="unix:/run/ccnp/uds/ccnp-server.sock"): + """Initialize a gRPC client object + + This constructor initializes gRPC client object with Unix Domain Socket (UDS) + path. And prepare default atrributes. + + Args: + server (str): gRPC server UDS path, default is /run/ccnp/uds/ccnp-server.sock + + Raises: + ValueError: If server UDS path is not valid. + """ + if len(server) == 0 or server[:5] != "unix:": + raise ValueError("Invalid server path, only unix domain socket supported.") + self._server = server + + if not os.path.exists(self._server.replace('unix:', '')): + raise RuntimeError("CCNP server does not start.") + self._channel = grpc.insecure_channel(self._server, + options=[('grpc.default_authority', 'localhost')]) + try: + grpc.channel_ready_future(self._channel).result(timeout=TIMEOUT) + except grpc.FutureTimeoutError as err: + raise ConnectionRefusedError('Connection to CCNP server failed') from err + self._stub = ccnp_server_pb2_grpc.ccnpStub(self._channel) + + def _get_container_id(self) -> Optional[str]: + mountinfo = "/proc/self/mountinfo" + docker_pattern = "/docker/containers/" + k8s_pattern = "/kubelet/pods/" + with open(mountinfo, "r", encoding="utf-8") as f: + line = f.readline().strip() + while line: + if docker_pattern in line: + # /var/lib/docker/containers/{container-id}/{file} + container_id = line.split(docker_pattern)[-1] + container_id = container_id.split('/') [0] + return container_id + if k8s_pattern in line: + # /var/lib/kubelet/pods/{container-id}/{file} + container_id = line.split(k8s_pattern)[-1] + container_id = container_id.split('/') [0].replace('-', '_') + return container_id + line = f.readline().strip() + return None + + def get_default_algorithms(self) -> TcgAlgorithmRegistry: + """Get the default Digest algorithms supported by trusted foundation. + + Different trusted foundation may support different algorithms, for example + the Intel TDX use SHA384, TPM uses SHA256. + + Beyond the default digest algorithm, some trusted foundation like TPM + may support multiple algorithms. + + Returns: + The default algorithms. + """ + req = ccnp_server_pb2.GetDefaultAlgorithmRequest() + resp = self._stub.GetDefaultAlgorithm(req) + return TcgAlgorithmRegistry(resp.algo_id) + + def get_measurement_count(self) -> int: + """Get the count of measurement register. + + Different trusted foundation may provide different count of measurement + register. For example, Intel TDX TDREPORT provides the 4 measurement + register by default. TPM provides 24 measurement (0~16 for SRTM and 17~24 + for DRTM). + + Beyond the real mesurement register, some SDK may extend virtual measurement + reigster for additional trust chain like container, namespace, cluster in + cloud native paradiagm. + + Returns: + The count of measurement registers + """ + req = ccnp_server_pb2.GetMeasurementCountRequest() + resp = self._stub.GetMeasurementCount(req) + return resp.count + + def get_cc_measurement(self, imr_select:[int, int]) -> TcgDigest: + """Get measurement register according to given selected index and algorithms + + Each trusted foundation in CC environment provides the multiple measurement + registers, the count is update to ``get_measurement_count()``. And for each + measurement register, it may provides multiple digest for different algorithms. + + Args: + imr_select ([int, int]): The first is index of measurement register, + the second is the alrogithms ID + + Returns: + The object of TcgIMR + """ + container_id = self._get_container_id() + if container_id is None: + LOG.error("Cannot get the container ID, please check the runing environment.") + return None + + req = ccnp_server_pb2.GetCcMeasurementRequest( + container_id=container_id, + index=imr_select[0], algo_id=imr_select[1] + ) + resp = self._stub.GetCcMeasurement(req) + if resp is None or resp.measurement is None: + LOG.error("CCNP service response is not correct.") + return None + + return TcgDigest(resp.measurement.algo_id, resp.measurement.hash) + + def get_cc_report( + self, + nonce: bytearray = None, + data: bytearray = None, + extraArgs = None + ) -> CcReport: + """Get the CcReport (i.e. quote) for given nonce and data. + + The CcReport is signing of attestation data (IMR values or hashes of IMR + values), made by a trusted foundation (TPM) using a key trusted by the + verifier. + + Different trusted foundation may use different quote format. + + Args: + nonce (bytearray): against replay attacks. + data (bytearray): user data + extraArgs: for TPM, it will be given list of IMR/PCRs + + Returns: + The ``CcReport`` object. Return None if it fails. + """ + container_id = self._get_container_id() + if container_id is None: + LOG.error("Cannot get the container ID, please check the runing environment.") + return None + + req = ccnp_server_pb2.GetCcReportRequest( + container_id=container_id, + nonce=nonce, user_data=data + ) + resp = self._stub.GetCcReport(req) + if resp is None or resp.cc_type is None or resp.cc_report is None: + LOG.error("CCNP service response is not correct.") + return None + + if resp.cc_type == CCTrustedApi.TYPE_CC_TDX: + return TdxQuote(resp.cc_report) + + LOG.error("The SDK does not support %s yet", resp.cc_type) + return None + + def get_cc_eventlog(self, start:int = None, count:int = None) -> list: + """Get eventlog for given index and count. + + TCG log in Eventlog. Verify to spoof events in the TCG log, hence defeating + remotely-attested measured-boot. + To measure the full CC runtime environment, the eventlog may include addtional + OS type and cloud native type event beyond the measured-boot. + + Args: + start(int): the first index of event log to fetch + count(int): the number of event logs to fetch + + Returns: + Parsed event logs following TCG Spec. + """ + container_id = self._get_container_id() + if container_id is None: + LOG.error("Cannot get the container ID, please check the runing environment.") + return None + + req = ccnp_server_pb2.GetCcEventlogRequest( + container_id=container_id, + start=start, count=count + ) + resp = self._stub.GetCcEventlog(req) + if resp is None or resp.event_logs is None: + LOG.error("CCNP service response is not correct.") + return None + + event_logs = [] + for evt in resp.event_logs: + if len(evt.digests) > 0: + digests = [] + for d in evt.digests: + digests.append(TcgDigest(d.algo_id, d.hash)) + event_logs.append(TcgImrEvent(evt.imr_index, evt.event_type, digests, + evt.event_size, evt.event)) + else: + event_logs.append(TcgPcClientImrEvent(evt.imr_index, evt.event_type, evt.digest, + evt.event_size, evt.event)) + return event_logs diff --git a/sdk/python3/pyproject.toml b/sdk/python3/pyproject.toml index 37df495..de64a34 100644 --- a/sdk/python3/pyproject.toml +++ b/sdk/python3/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ccnp" -version = "0.0.2" +version = "0.3.1" authors = [ { name="Lu, Ken", email="ken.lu@intel.com" }, { name="Ying, Ruoyu", email="ruoyu.ying@intel.com" }, diff --git a/sdk/python3/requirements.txt b/sdk/python3/requirements.txt index 3235b92..aa2d607 100644 --- a/sdk/python3/requirements.txt +++ b/sdk/python3/requirements.txt @@ -1,3 +1,4 @@ +cctrusted_base grpcio-tools grpcio protobuf diff --git a/service/ccnp-server/README.md b/service/ccnp-server/README.md index 8bf03d8..206c57f 100644 --- a/service/ccnp-server/README.md +++ b/service/ccnp-server/README.md @@ -8,8 +8,10 @@ Run the command: ``` sudo ./ccnp_server -[2024-02-06T02:06:18Z INFO ccnp_server] [ccnp-server]: set sock file permissions: /run/ccnp/uds/ccnp-server.sock -[2024-02-06T02:06:18Z INFO ccnp_server] [ccnp-server]: staring the service... +[2024-02-22T07:18:29Z INFO ccnp_server] [ccnp-server]: set sock file permissions: /run/ccnp/uds/ccnp-server.sock +[2024-02-22T07:18:29Z INFO ccnp_server] [ccnp-server]: staring the service... +[2024-02-22T07:18:29Z INFO ccnp_server::agent] The system has been measured as the policy defined. +[2024-02-22T07:19:03Z INFO ccnp_server::agent] Loaded ... event logs. ``` ## Query Information @@ -19,7 +21,7 @@ sudo ./ccnp_server Run the command: ``` -grpcurl -authority "dummy" -plaintext -d '{ "user_data": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4", "nonce":"IXUKoBO1UM3c1wopN4sY" }' -unix /run/ccnp/uds/ccnp-server.sock ccnp_server_pb.ccnp.GetCcReport +grpcurl -authority "dummy" -plaintext -d '{ "container_id": "29134314a2...", "user_data": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4", "nonce":"IXUKoBO1UM3c1wopN4sY" }' -unix /run/ccnp/uds/ccnp-server.sock ccnp_server_pb.ccnp.GetCcReport ``` The output looks like this: @@ -36,7 +38,7 @@ The output looks like this: Run the command: ``` -grpcurl -authority "dummy" -plaintext -d '{ "index": 0, "algo_id": 12}' -unix /run/ccnp/uds/ccnp-server.sock ccnp_server_pb.ccnp.GetCcMeasurement +grpcurl -authority "dummy" -plaintext -d '{ "container_id": "29134314a2...", "index": 0, "algo_id": 12}' -unix /run/ccnp/uds/ccnp-server.sock ccnp_server_pb.ccnp.GetCcMeasurement ``` The output looks like: @@ -55,7 +57,7 @@ The output looks like: Run the command: ``` -grpcurl -authority "dummy" -plaintext -d '{"start": 0, "count": 3}' -unix /run/ccnp/uds/ccnp-server.sock ccnp_server_pb.ccnp.GetCcEventlog +grpcurl -authority "dummy" -plaintext -d '{"container_id": "29134314a2...", "start": 0, "count": 3}' -unix /run/ccnp/uds/ccnp-server.sock ccnp_server_pb.ccnp.GetCcEventlog ``` The output looks like: diff --git a/service/ccnp-server/configs/policy.yaml b/service/ccnp-server/configs/policy.yaml new file mode 100644 index 0000000..6779442 --- /dev/null +++ b/service/ccnp-server/configs/policy.yaml @@ -0,0 +1,19 @@ +backend: ima +hashAlgorithm: sha384 +measure: + system: + withParameter: true + processes: + - /usr/bin/containerd + - /usr/bin/kubelet + - /usr/bin/containerd-shim-runc-v2 + container: + isolated: true + kubernetes: + withParameter: true + pods: + - kube-apiserver + - kube-scheduler + - kube-proxy + - kube-scheduler + - kube-controller-manager diff --git a/service/ccnp-server/proto/ccnp-server.proto b/service/ccnp-server/proto/ccnp-server.proto index a444f60..aa6fba6 100644 --- a/service/ccnp-server/proto/ccnp-server.proto +++ b/service/ccnp-server/proto/ccnp-server.proto @@ -38,8 +38,9 @@ message GetMeasurementCountResponse { } message GetCcReportRequest { - string user_data = 1; - string nonce = 2; + string container_id = 1; + optional string user_data = 2; + optional string nonce = 3; } message GetCcReportResponse { @@ -48,8 +49,9 @@ message GetCcReportResponse { } message GetCcMeasurementRequest { - uint32 index = 1; - uint32 algo_id = 2; + string container_id = 1; + uint32 index = 2; + uint32 algo_id = 3; } message GetCcMeasurementResponse { @@ -57,8 +59,9 @@ message GetCcMeasurementResponse { } message GetCcEventlogRequest { - uint32 start = 1; - uint32 count = 2; + string container_id = 1; + optional uint32 start = 2; + optional uint32 count = 3; } message TcgDigest { diff --git a/service/ccnp-server/src/agent.rs b/service/ccnp-server/src/agent.rs index 1f293cd..3fc86a8 100644 --- a/service/ccnp-server/src/agent.rs +++ b/service/ccnp-server/src/agent.rs @@ -2,17 +2,57 @@ use anyhow::{anyhow, Error}; use cctrusted_base::{api::CCTrustedApi, api_data::ExtraArgs, tcg}; use cctrusted_vm::sdk::API; use log::info; +use std::cmp::Ordering; use std::collections::HashMap; -use crate::ccnp_pb::{TcgDigest, TcgEventlog}; +use crate::{ + ccnp_pb::{TcgDigest, TcgEventlog}, + container::Container, + measurement::Measurement, + policy::PolicyConfig, +}; + +pub enum IMR { + FIRMWARE = 0, + KERNEL = 1, + SYSTEM = 2, + CONTAINER = 3, +} pub struct Agent { - pub event_logs: Option>, + measurement: Option, + containers: HashMap, + event_logs: Vec, +} + +impl Default for Agent { + fn default() -> Self { + Self::new() + } } impl Agent { - pub fn init(&mut self) -> Result<(), Error> { - self.event_logs = Some(vec![]); + pub fn new() -> Agent { + Agent { + measurement: None, + containers: HashMap::new(), + event_logs: vec![], + } + } + + pub fn init(&mut self, policy: PolicyConfig) -> Result<(), Error> { + // Measure the system when Agent initialization + self.measurement = Some(Measurement::new(policy)); + match self + .measurement + .as_mut() + .expect("The measurement was not initialized.") + .measure() + { + Ok(_) => info!("The system has been measured as the policy defined."), + Err(e) => return Err(e), + } + self.fetch_all_event_logs() } @@ -21,6 +61,7 @@ impl Agent { Ok(v) => v, Err(e) => return Err(e), }; + Ok(algo.algo_id.into()) } @@ -33,12 +74,8 @@ impl Agent { Ok(count.into()) } - pub fn fetch_all_event_logs(&mut self) -> Result<(), Error> { - let start: u32 = self - .event_logs - .as_ref() - .expect("The event_logs is None.") - .len() as u32; + fn fetch_all_event_logs(&mut self) -> Result<(), Error> { + let start: u32 = self.event_logs.len() as u32; let entries = match API::get_cc_eventlog(Some(start), None) { Ok(v) => v, @@ -69,10 +106,14 @@ impl Agent { extra_info: HashMap::new(), }; - self.event_logs - .as_mut() - .expect("Change eventlog to mut failed.") - .push(tcg_event) + if tcg_event.event_type == tcg::IMA_MEASUREMENT_EVENT { + match self.filter_container(tcg_event.clone()) { + Ok(_v) => _v, + Err(e) => return Err(e), + } + } + + self.event_logs.push(tcg_event) } tcg::EventLogEntry::TcgPcClientImrEvent(event) => { let mut digests: Vec = vec![]; @@ -84,69 +125,168 @@ impl Agent { algo_id: algo_id.into(), hash: event.digest.to_vec(), }); - self.event_logs - .as_mut() - .expect("Change eventlog to mut failed.") - .push(TcgEventlog { - rec_num: 0, - imr_index: event.imr_index, - event_type: event.event_type, - event_size: event.event_size, - event: event.event, - digests, - extra_info: HashMap::new(), - }) + self.event_logs.push(TcgEventlog { + rec_num: 0, + imr_index: event.imr_index, + event_type: event.event_type, + event_size: event.event_size, + event: event.event, + digests, + extra_info: HashMap::new(), + }) } tcg::EventLogEntry::TcgCanonicalEvent(_event) => { todo!(); } } } - info!( - "Loaded {} event logs.", - self.event_logs - .as_ref() - .expect("Change eventlog to ref failed.") - .len() - ); + info!("Loaded {} event logs.", self.event_logs.len()); Ok(()) } - pub fn get_cc_eventlog(&mut self, start: u32, count: u32) -> Result, Error> { - let _ = self.fetch_all_event_logs(); - let event_logs = self - .event_logs - .as_ref() - .expect("The eventlog is None.") - .to_vec(); - let s: usize = start.try_into().unwrap(); - let mut e: usize = (start + count).try_into().unwrap(); - - if s >= event_logs.len() { - return Err(anyhow!( - "Invalid input start. Start must be smaller than total event log count." - )); + fn filter_container(&mut self, event: TcgEventlog) -> Result<(), Error> { + let data = match String::from_utf8(event.event.to_vec()) { + Ok(v) => v, + Err(e) => return Err(anyhow!("Convert IMA event data to string failed: {:?}", e)), + }; + + let cgpath: Vec<&str> = data.split(' ').collect(); + if cgpath.len() != 4 { + return Ok(()); } - if e >= event_logs.len() { - return Err(anyhow!( - "Invalid input count. count must be smaller than total event log count." - )); + + if cgpath[1].starts_with("/kubepods.slice/kubepods") + || cgpath[1].starts_with("/system.slice/docker-") + { + let container_id = match Container::parse_id(cgpath) { + Ok(v) => v, + Err(e) => return Err(e), + }; + + if !self.containers.contains_key(&container_id) { + let measurement = match self.measurement.as_mut() { + Some(v) => v, + None => return Err(anyhow!("The measurement was not initialized.")), + }; + + let mut container = + Container::new(measurement.imr().clone(), measurement.event_logs().to_vec()); + match container.extend_imr(IMR::CONTAINER as u32, event.clone()) { + Ok(_v) => { + self.containers.insert(container_id.clone(), container); + return Ok(()); + } + Err(e) => return Err(e), + } + } else { + let container = match self.containers.get_mut(&container_id) { + Some(v) => v, + None => return Err(anyhow!("Cannot get container as mutable.")), + }; + return container.extend_imr(IMR::CONTAINER as u32, event.clone()); + } } - if e == 0 { - e = event_logs.len(); + + Ok(()) + } + + pub fn get_cc_eventlog( + &mut self, + container_id: String, + start: Option, + count: Option, + ) -> Result, Error> { + let _ = self.fetch_all_event_logs(); + let mut event_logs = vec![]; + + let measurement = match self.measurement.as_mut() { + Some(v) => v, + None => return Err(anyhow!("The measurement was not initialized.")), + }; + + if measurement.container_isolated() { + if !self.containers.contains_key(&container_id) { + return Err(anyhow!("Container cannot be found.")); + } + + for event_log in &self.event_logs { + if event_log.imr_index == IMR::FIRMWARE as u32 + || event_log.imr_index == IMR::KERNEL as u32 + { + event_logs.push(event_log.clone()); + } + } + + let container = &self.containers[&container_id]; + event_logs.extend(container.event_logs().clone()); + } else { + event_logs.extend(self.event_logs.to_vec()); } - Ok(event_logs[s..e].to_vec().clone()) + let begin = match start { + Some(s) => match s.cmp(&(event_logs.len() as u32)) { + Ordering::Greater => { + return Err(anyhow!( + "Invalid input start. Current number of eventlog is {}", + event_logs.len() + )); + } + Ordering::Equal => return Ok(Vec::new()), + Ordering::Less => s, + }, + None => 0, + }; + + let end = match count { + Some(c) => { + if c == 0 { + return Err(anyhow!( + "Invalid input count. count must be number larger than 0!" + )); + } else if c + begin > event_logs.len() as u32 { + event_logs.len() + } else { + (c + begin).try_into().unwrap() + } + } + None => event_logs.len(), + }; + + Ok(event_logs[begin as usize..end as usize].to_vec()) } pub fn get_cc_report( &mut self, - nonce: String, - user_data: String, + container_id: String, + nonce: Option, + user_data: Option, ) -> Result<(Vec, i32), Error> { - let (report, cc_type) = match API::get_cc_report(Some(nonce), Some(user_data), ExtraArgs {}) - { + let _ = self.fetch_all_event_logs(); + + let measurement = match self.measurement.as_mut() { + Some(v) => v, + None => return Err(anyhow!("The measurement was not initialized.")), + }; + + let new_nonce = if measurement.container_isolated() { + if !self.containers.contains_key(&container_id) { + return Err(anyhow!("Container cannot be found.")); + } + + let container = &self.containers[&container_id]; + match nonce { + Some(v) => match base64::decode(v) { + Ok(v) => Some(base64::encode([container.imr().hash.to_vec(), v].concat())), + Err(e) => return Err(anyhow!("nonce is not base64 encoded: {:?}", e)), + }, + None => None, + } + } else { + nonce.clone() + }; + + let (report, cc_type) = match API::get_cc_report(new_nonce, user_data, ExtraArgs {}) { Ok(v) => (v.cc_report, v.cc_type as i32), Err(e) => return Err(e), }; @@ -154,7 +294,39 @@ impl Agent { Ok((report, cc_type)) } - pub fn get_cc_measurement(&mut self, index: u32, algo_id: u32) -> Result { + pub fn get_cc_measurement( + &mut self, + container_id: String, + index: u32, + algo_id: u32, + ) -> Result { + let _ = self.fetch_all_event_logs(); + + let measurement = match self.measurement.as_mut() { + Some(v) => v, + None => return Err(anyhow!("The measurement was not initialized.")), + }; + + if measurement.container_isolated() { + if !self.containers.contains_key(&container_id) { + return Err(anyhow!("Container cannot be found.")); + } + + if index == IMR::SYSTEM as u32 { + return Err(anyhow!("Cannot access IMR according to the policy.")); + } + + if index == IMR::CONTAINER as u32 { + let container = match self.containers.get_mut(&container_id) { + Some(v) => v, + None => { + return Err(anyhow!("The container is on the list but fails to get it.")) + } + }; + return Ok(container.imr().clone()); + } + } + let measurement = match API::get_cc_measurement(index.try_into().unwrap(), algo_id.try_into().unwrap()) { Ok(v) => TcgDigest { diff --git a/service/ccnp-server/src/ccnp_server_pb.rs b/service/ccnp-server/src/ccnp_server_pb.rs index 9f0f6df..4ed1f25 100644 --- a/service/ccnp-server/src/ccnp_server_pb.rs +++ b/service/ccnp-server/src/ccnp_server_pb.rs @@ -77,9 +77,11 @@ pub struct GetMeasurementCountResponse { #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetCcReportRequest { #[prost(string, tag = "1")] - pub user_data: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub nonce: ::prost::alloc::string::String, + pub container_id: ::prost::alloc::string::String, + #[prost(string, optional, tag = "2")] + pub user_data: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag = "3")] + pub nonce: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -92,9 +94,11 @@ pub struct GetCcReportResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetCcMeasurementRequest { - #[prost(uint32, tag = "1")] - pub index: u32, + #[prost(string, tag = "1")] + pub container_id: ::prost::alloc::string::String, #[prost(uint32, tag = "2")] + pub index: u32, + #[prost(uint32, tag = "3")] pub algo_id: u32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -106,10 +110,12 @@ pub struct GetCcMeasurementResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetCcEventlogRequest { - #[prost(uint32, tag = "1")] - pub start: u32, - #[prost(uint32, tag = "2")] - pub count: u32, + #[prost(string, tag = "1")] + pub container_id: ::prost::alloc::string::String, + #[prost(uint32, optional, tag = "2")] + pub start: ::core::option::Option, + #[prost(uint32, optional, tag = "3")] + pub count: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/service/ccnp-server/src/container.rs b/service/ccnp-server/src/container.rs new file mode 100644 index 0000000..b6deb54 --- /dev/null +++ b/service/ccnp-server/src/container.rs @@ -0,0 +1,95 @@ +use crate::ccnp_pb::{TcgDigest, TcgEventlog}; +use anyhow::{anyhow, Error}; +use cctrusted_base::tcg; +use openssl::hash::{Hasher, MessageDigest}; +use regex::Regex; + +impl From for MessageDigest { + fn from(digest: TcgDigest) -> Self { + let algo_id: u16 = digest.algo_id.try_into().unwrap(); + match algo_id { + tcg::TPM_ALG_SHA1 => MessageDigest::sha1(), + tcg::TPM_ALG_SHA256 => MessageDigest::sha256(), + tcg::TPM_ALG_SHA384 => MessageDigest::sha384(), + _ => MessageDigest::sha256(), + } + } +} + +pub struct Container { + imr: TcgDigest, + event_logs: Vec, +} + +impl Container { + pub fn new(imr: TcgDigest, event_logs: Vec) -> Container { + Container { imr, event_logs } + } + + pub fn parse_id(cgpath: Vec<&str>) -> Result { + if cgpath[1].starts_with("/kubepods") { + let id_regex1 = Regex::new(r"kubepods-pod|.slice").unwrap(); + let id_regex2 = Regex::new(r"kubepods-besteffort-pod|.slice").unwrap(); + + let cgroup: Vec<&str> = cgpath[1].split('/').collect(); + let id = match cgroup.len() { + 4 => id_regex1.replace_all(cgroup[2], "").to_string(), + 5 => id_regex2.replace_all(cgroup[3], "").to_string(), + _ => { + return Err(anyhow!( + "The container id parse failed, unknown cgpath format." + )) + } + }; + + Ok(id) + } else { + let id_regex = Regex::new(r"/system.slice/docker-|.scope").unwrap(); + let id = id_regex.replace_all(cgpath[1], "").to_string(); + if id.is_empty() { + return Err(anyhow!("The container id parse failed, id is empty.")); + } + + Ok(id) + } + } + + pub fn imr(&self) -> &TcgDigest { + &self.imr + } + + pub fn event_logs(&self) -> &Vec { + self.event_logs.as_ref() + } + + pub fn extend_imr(&mut self, imr_index: u32, mut event: TcgEventlog) -> Result<(), Error> { + let digests = event.clone().digests; + + for digest in digests { + if self.imr.hash.len() != digest.hash.len() { + return Err(anyhow!("The hash algorithm does not match.")); + } + + let new_val = [self.imr.hash.clone(), digest.hash.clone()].concat(); + let mut hasher = match Hasher::new(digest.clone().into()) { + Ok(v) => v, + Err(e) => return Err(e.into()), + }; + + match hasher.update(&new_val) { + Ok(v) => v, + Err(e) => return Err(e.into()), + }; + + self.imr.hash = match hasher.finish() { + Ok(v) => v.to_vec(), + Err(e) => return Err(e.into()), + }; + } + + event.imr_index = imr_index; + self.event_logs.push(event); + + Ok(()) + } +} diff --git a/service/ccnp-server/src/main.rs b/service/ccnp-server/src/main.rs index 8f33eef..de15558 100644 --- a/service/ccnp-server/src/main.rs +++ b/service/ccnp-server/src/main.rs @@ -1,4 +1,7 @@ pub mod agent; +pub mod container; +pub mod measurement; +pub mod policy; pub mod service; pub mod ccnp_pb { tonic::include_proto!("ccnp_server_pb"); @@ -16,6 +19,7 @@ use tokio_stream::wrappers::UnixListenerStream; use tonic::transport::Server; use ccnp_pb::{ccnp_server::CcnpServer, FILE_DESCRIPTOR_SET}; +use policy::PolicyConfig; use service::Service; #[derive(Parser)] @@ -24,6 +28,9 @@ struct Cli { #[arg(short, long)] #[clap(default_value = "/run/ccnp/uds/ccnp-server.sock")] sock: String, + /// Input policy file + #[arg(short, long)] + policy: String, } fn set_sock_perm(sock: &str) -> Result<()> { @@ -39,6 +46,7 @@ async fn main() -> Result<(), Box> { let cli = Cli::parse(); let sock = cli.sock; + let policy = PolicyConfig::new(cli.policy); let _ = std::fs::remove_file(sock.clone()); let uds = match UnixListener::bind(sock.clone()) { @@ -58,7 +66,7 @@ async fn main() -> Result<(), Box> { .unwrap(); info!("[ccnp-server]: staring the service..."); - let service = Service::new(); + let service = Service::new(policy); Server::builder() .add_service(reflection_service) .add_service(health_service) diff --git a/service/ccnp-server/src/measurement.rs b/service/ccnp-server/src/measurement.rs new file mode 100644 index 0000000..fd3b875 --- /dev/null +++ b/service/ccnp-server/src/measurement.rs @@ -0,0 +1,149 @@ +use anyhow::Error; +use cctrusted_base::tcg; +use openssl::hash::Hasher; +use regex::Regex; +use std::collections::HashMap; +use std::fs; + +use crate::{ + agent::IMR, + ccnp_pb::{TcgDigest, TcgEventlog}, + policy::PolicyConfig, +}; + +#[derive(Clone)] +pub struct Measurement { + policy: PolicyConfig, + imr: TcgDigest, + event_logs: Vec, +} + +impl Measurement { + pub fn new(policy: PolicyConfig) -> Measurement { + let algo_id: u32 = match policy.hash_alogrithm() { + Some(v) => match v.as_str() { + "sha1" => tcg::TPM_ALG_SHA1.into(), + "sha256" => tcg::TPM_ALG_SHA256.into(), + "sha384" => tcg::TPM_ALG_SHA384.into(), + "sha512" => tcg::TPM_ALG_SHA512.into(), + _ => panic!("Unknown hashing algorithm."), + }, + None => tcg::TPM_ALG_SHA384.into(), + }; + + let algo_len = tcg::TcgDigest::get_digest_size_from_algorithm_id(algo_id as u16); + let hash = vec![0; algo_len.into()]; + + Measurement { + policy, + imr: TcgDigest { algo_id, hash }, + event_logs: vec![], + } + } + + pub fn imr(&self) -> &TcgDigest { + &self.imr + } + + pub fn event_logs(&self) -> &Vec { + self.event_logs.as_ref() + } + + fn extend_imr(&mut self, val: &[u8]) -> Result<(), Error> { + let mut hasher = match Hasher::new(self.imr.clone().into()) { + Ok(v) => v, + Err(e) => return Err(e.into()), + }; + match hasher.update(val) { + Ok(_v) => _v, + Err(e) => return Err(e.into()), + }; + let hash_val = match hasher.finish() { + Ok(v) => v.to_vec(), + Err(e) => return Err(e.into()), + }; + + let new_val = [self.imr.hash.clone(), hash_val.to_vec()].concat(); + match hasher.update(&new_val) { + Ok(_v) => _v, + Err(e) => return Err(e.into()), + }; + self.imr.hash = match hasher.finish() { + Ok(v) => v.to_vec(), + Err(e) => return Err(e.into()), + }; + + let digests: Vec = vec![TcgDigest { + algo_id: self.imr.algo_id, + hash: hash_val.to_vec(), + }]; + let eventlog = TcgEventlog { + rec_num: 0, + imr_index: IMR::CONTAINER as u32, + event_type: tcg::IMA_MEASUREMENT_EVENT, + event_size: val.len().try_into().unwrap(), + event: val.to_vec(), + digests, + extra_info: HashMap::new(), + }; + + self.event_logs.push(eventlog); + + Ok(()) + } + + fn get_processes(&mut self, procfs: String) -> Result, Error> { + let mut processes = HashMap::new(); + let pattern = Regex::new(r".*/[0-9]").unwrap(); + let paths = fs::read_dir(procfs) + .unwrap() + .map(|r| r.unwrap().path()) + .filter(|r| pattern.is_match(r.to_str().unwrap())); + + for path in paths { + let cmdline_path = path.to_str().unwrap().to_owned() + "/cmdline"; + let cmdline = + fs::read_to_string(cmdline_path).expect("Failed to read process cmdline."); + + if cmdline.is_empty() { + continue; + } + + let (name, parameter) = cmdline.split_once('\0').unwrap(); + processes.insert(name.to_string(), parameter.to_string()); + } + + Ok(processes) + } + + fn measure_system(&mut self) -> Result<(), Error> { + let processes = self.get_processes("/proc".to_string()).unwrap(); + let process_policy = match self.policy.system_processes() { + Some(v) => v.clone(), + None => return Ok(()), + }; + + for p in &process_policy { + if processes.contains_key(p) { + let proc = match self.policy.system_with_parameter() { + Some(v) => match v { + true => format!("{}\0{}", p, processes[p]), + false => p.clone(), + }, + None => p.clone(), + }; + let _ = self.extend_imr(proc.as_bytes()); + } + } + + Ok(()) + } + + pub fn measure(&mut self) -> Result<(), Error> { + self.measure_system() + } + + pub fn container_isolated(&mut self) -> bool { + self.policy.container_isolated().unwrap_or(false) + } +} diff --git a/service/ccnp-server/src/policy.rs b/service/ccnp-server/src/policy.rs new file mode 100644 index 0000000..54b98c4 --- /dev/null +++ b/service/ccnp-server/src/policy.rs @@ -0,0 +1,79 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +struct SystemPolicy { + with_parameter: Option, + processes: Option>, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +struct KubernetesPolicy { + with_parameter: Option, + pods: Option>, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +struct ContainerPolicy { + with_parameter: Option, + isolated: Option, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +struct MeasurePolicy { + system: Option, + kubernetes: Option, + container: Option, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PolicyConfig { + backend: Option, + hash_algorithm: Option, + measure: Option, +} + +impl PolicyConfig { + pub fn new(path: String) -> PolicyConfig { + let file = std::fs::File::open(path).expect("Failed to open policy file."); + serde_yaml::from_reader(file).expect("Failed to serialize policy file.") + } + + pub fn hash_alogrithm(&self) -> Option<&String> { + self.hash_algorithm.as_ref() + } + + pub fn system_processes(&self) -> Option<&Vec> { + match &self.measure { + Some(v) => match &v.system { + Some(v) => v.processes.as_ref(), + None => None, + }, + None => None, + } + } + + pub fn system_with_parameter(&self) -> Option { + match &self.measure { + Some(v) => match &v.system { + Some(v) => v.with_parameter, + None => None, + }, + None => None, + } + } + + pub fn container_isolated(&self) -> Option { + match &self.measure { + Some(v) => match &v.container { + Some(v) => v.isolated, + None => None, + }, + None => None, + } + } +} diff --git a/service/ccnp-server/src/service.rs b/service/ccnp-server/src/service.rs index 32fd033..bb65c29 100644 --- a/service/ccnp-server/src/service.rs +++ b/service/ccnp-server/src/service.rs @@ -11,16 +11,17 @@ use crate::{ GetDefaultAlgorithmRequest, GetDefaultAlgorithmResponse, GetMeasurementCountRequest, GetMeasurementCountResponse, }, + policy::PolicyConfig, }; lazy_static! { - static ref AGENT: Mutex = Mutex::new(Agent { event_logs: None }); + static ref AGENT: Mutex = Mutex::new(Agent::new()); } pub struct Service; impl Service { - pub fn new() -> Service { - match AGENT.lock().expect("Agent lock() failed.").init() { + pub fn new(policy: PolicyConfig) -> Service { + match AGENT.lock().expect("Agent lock() failed.").init(policy) { Err(e) => panic!("Server panic {:?}", e), Ok(_v) => _v, } @@ -28,12 +29,6 @@ impl Service { } } -impl Default for Service { - fn default() -> Self { - Self::new() - } -} - #[tonic::async_trait] impl Ccnp for Service { async fn get_default_algorithm( @@ -76,7 +71,7 @@ impl Ccnp for Service { let measurement = match AGENT .lock() .expect("Agent lock() failed.") - .get_cc_measurement(req.index, req.algo_id) + .get_cc_measurement(req.container_id, req.index, req.algo_id) { Ok(v) => v, Err(e) => return Err(Status::internal(e.to_string())), @@ -92,11 +87,11 @@ impl Ccnp for Service { request: Request, ) -> Result, Status> { let req = request.into_inner(); - let event_logs = match AGENT - .lock() - .expect("Agent lock() failed.") - .get_cc_eventlog(req.start, req.count) - { + let event_logs = match AGENT.lock().expect("Agent lock() failed.").get_cc_eventlog( + req.container_id, + req.start, + req.count, + ) { Ok(v) => v, Err(e) => return Err(Status::internal(e.to_string())), }; @@ -109,11 +104,11 @@ impl Ccnp for Service { request: Request, ) -> Result, Status> { let req = request.into_inner(); - let (cc_report, cc_type) = match AGENT - .lock() - .expect("Agent lock() failed.") - .get_cc_report(req.nonce, req.user_data) - { + let (cc_report, cc_type) = match AGENT.lock().expect("Agent lock() failed.").get_cc_report( + req.container_id, + req.nonce, + req.user_data, + ) { Ok(v) => v, Err(e) => return Err(Status::internal(e.to_string())), };