diff --git a/.bazelrc b/.bazelrc index 5052237d4..eac94e781 100644 --- a/.bazelrc +++ b/.bazelrc @@ -111,8 +111,12 @@ build:instance_gcp --//:instance=gcp build:instance_aws --//:instance=aws +build:instance_azure --//:instance=azure + build:platform_aws --//:platform=aws +build:platform_azure --//:platform=azure + build:platform_gcp --//:platform=gcp build:local_aws --config=instance_local @@ -128,5 +132,12 @@ build:gcp_gcp --config=platform_gcp build:aws_aws --config=instance_aws build:aws_aws --config=platform_aws + +build:local_azure --config=instance_local +build:local_azure --config=platform_azure + +build:azure_azure --config=instance_azure +build:azure_azure --config=platform_azure + build:non_prod --//:build_flavor=non_prod build:prod --//:build_flavor=prod diff --git a/BUILD.bazel b/BUILD.bazel index 5e11618ee..8271e6bb6 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2023 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ string_flag( build_setting_default = "aws", values = [ "aws", + "azure", "gcp", "local", ], @@ -34,6 +36,14 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "azure_platform", + flag_values = { + ":platform": "azure", + }, + visibility = ["//visibility:public"], +) + config_setting( name = "gcp_platform", flag_values = { @@ -55,6 +65,7 @@ string_flag( build_setting_default = "aws", values = [ "aws", + "azure", "gcp", "local", ], @@ -69,6 +80,14 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "azure_instance", + flag_values = { + ":instance": "azure", + }, + visibility = ["//visibility:public"], +) + config_setting( name = "gcp_instance", flag_values = { diff --git a/scp/cc/azure/BUILD.bazel b/scp/cc/azure/BUILD.bazel new file mode 100644 index 000000000..d020523a7 --- /dev/null +++ b/scp/cc/azure/BUILD.bazel @@ -0,0 +1,23 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//scp/cc/azure:__subpackages__"]) + +# A dummy library to propagate the include directories. +cc_library( + name = "include_dir", + includes = ["."], +) diff --git a/scp/cc/azure/attestation/BUILD.bazel b/scp/cc/azure/attestation/BUILD.bazel new file mode 100644 index 000000000..38758273d --- /dev/null +++ b/scp/cc/azure/attestation/BUILD.bazel @@ -0,0 +1,55 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "aci_attestation_lib", + srcs = glob( + [ + "json_attestation_report.cc", + "json_attestation_report.h", + "security_context_fetcher.cc", + "security_context_fetcher.h", + ], + ), + deps = [ + "@boost//:system", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/log:check", + ":get-snp-report", + "@nlohmann_json//:lib", + "//scp/cc/core/utils/src:core_utils", + ], +) + +cc_library( + name="get-snp-report", + srcs=glob(["get-snp-report/*.c"]), + hdrs=glob(["get-snp-report/*.h"]), +) + +cc_binary( + name = "print_snp_json", + srcs = ["print_snp_json.cc"], + linkopts=['-static'], + linkstatic=True, + features = ["fully_static_link"], + visibility = ["//visibility:public"], + deps = [ + "aci_attestation_lib", + ], +) diff --git a/scp/cc/azure/attestation/get-snp-report/fetch5.h b/scp/cc/azure/attestation/get-snp-report/fetch5.h new file mode 100644 index 000000000..9d3783eee --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/fetch5.h @@ -0,0 +1,22 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +bool fetchAttestationReport5(const char* report_data_hexstring, void **snp_report); + +// does /dev/sev exists. This is where the PSP is exposed in 5.15.* +bool supportsDevSev(); \ No newline at end of file diff --git a/scp/cc/azure/attestation/get-snp-report/fetch6.h b/scp/cc/azure/attestation/get-snp-report/fetch6.h new file mode 100644 index 000000000..9895ba14b --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/fetch6.h @@ -0,0 +1,23 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +bool fetchAttestationReport6(const char* report_data_hexstring, void **snp_report); + +// 6.1 linux exposees the PSP via /dev/sev-guest + +bool supportsDevSevGuest(); diff --git a/scp/cc/azure/attestation/get-snp-report/get-snp-report.c b/scp/cc/azure/attestation/get-snp-report/get-snp-report.c new file mode 100644 index 000000000..301ee2992 --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/get-snp-report.c @@ -0,0 +1,37 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "get-snp-report.h" + +bool fetchSnpReport(const char* report_data_hexstring, void** snp_report) { + bool success = false; + uint8_t *snp_report_hex; + + if (supportsDevSev()) { + success = fetchAttestationReport5(report_data_hexstring, (void*) &snp_report_hex); + } else if (supportsDevSevGuest()) { + success = fetchAttestationReport6(report_data_hexstring, (void*) &snp_report_hex); + } else { + fprintf(stderr, "No supported SNP device found\n"); + } + + if (success) { + *snp_report = snp_report_hex; + return 0; + } + + return -1; +} diff --git a/scp/cc/azure/attestation/get-snp-report/get-snp-report.h b/scp/cc/azure/attestation/get-snp-report/get-snp-report.h new file mode 100644 index 000000000..68fdf2b39 --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/get-snp-report.h @@ -0,0 +1,31 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fetch5.h" +#include "fetch6.h" +#include "snp-attestation.h" + +bool fetchSnpReport(const char* report_data_hexstring, void** snp_report); \ No newline at end of file diff --git a/scp/cc/azure/attestation/get-snp-report/get-snp-report5.c b/scp/cc/azure/attestation/get-snp-report/get-snp-report5.c new file mode 100644 index 000000000..0afa72ee0 --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/get-snp-report5.c @@ -0,0 +1,101 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snp-attestation.h" +#include "snp-ioctl5.h" + +#include "helpers.h" + +bool supportsDevSev() +{ + return access("/dev/sev", W_OK) == 0; +} + +bool fetchAttestationReport5(const char* report_data_hexstring, void **snp_report) +{ + msg_report_req msg_report_in; + msg_response_resp msg_report_out; + + int fd, rc; + + struct sev_snp_guest_request payload = { + .req_msg_type = SNP_MSG_REPORT_REQ, + .rsp_msg_type = SNP_MSG_REPORT_RSP, + .msg_version = 1, + .request_len = sizeof(msg_report_in), + .request_uaddr = (uint64_t) (void*) &msg_report_in, + .response_len = sizeof(msg_report_out), + .response_uaddr = (uint64_t) (void*) &msg_report_out, + .error = 0 + }; + + memset((void*) &msg_report_in, 0, sizeof(msg_report_in)); + memset((void*) &msg_report_out, 0, sizeof(msg_report_out)); + + // the report data is passed as a hexstring which needs to be decoded into an array of + // unsigned bytes + // MAA expects a SHA-256. So we use left align the bytes in the report data + + uint8_t *reportData = decodeHexString(report_data_hexstring, sizeof(msg_report_in.report_data)); + memcpy(msg_report_in.report_data, reportData, sizeof(msg_report_in.report_data)); + + // open the file descriptor of the PSP + fd = open("/dev/sev", O_RDWR | O_CLOEXEC); + + if (fd < 0) { + fprintf(stderr, "Failed to open /dev/sev\n"); + return false; + } + + // issue the custom SEV_SNP_GUEST_MSG_REPORT sys call to the sev driver + rc = ioctl(fd, SEV_SNP_GUEST_MSG_REPORT, &payload); + + if (rc < 0) { + fprintf(stderr, "Failed to issue ioctl SEV_SNP_GUEST_MSG_REPORT\n"); + return false; + } + + #ifdef DEBUG_OUTPUT + fprintf(stderr, "Response header:\n"); + uint8_t *hdr = (uint8_t*) &msg_report_out; + + for (size_t i = 0; i < 32; i++) { + fprintf(stderr, "%02x", hdr[i]); + if (i % 16 == 15) + fprintf(stderr, "\n"); + else + fprintf(stderr, " "); + } + fprintf(stderr, "Attestation report:\n"); + printReport(&msg_report_out.report); + #endif + + *snp_report = (snp_attestation_report *) malloc (sizeof(snp_attestation_report)); + memcpy(*snp_report, &msg_report_out.report, sizeof(snp_attestation_report)); + + return true; +} diff --git a/scp/cc/azure/attestation/get-snp-report/get-snp-report6.c b/scp/cc/azure/attestation/get-snp-report/get-snp-report6.c new file mode 100644 index 000000000..2cdca1483 --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/get-snp-report6.c @@ -0,0 +1,89 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snp-attestation.h" +#include "snp-ioctl6.h" +#include "helpers.h" + +bool supportsDevSevGuest() +{ + return access("/dev/sev-guest", W_OK) == 0; +} + +bool fetchAttestationReport6(const char* report_data_hexstring, void **snp_report) +{ + int fd; + int rc; + + fd = open("/dev/sev-guest", O_RDWR | O_CLOEXEC); + + if (fd < 0) { + fprintf(stdout, "Failed to open /dev/sev-guest\n"); + return false; + } + + // this is the request, mostly the report data, vmpl + snp_report_req snp_request; + // and the result from the ioctl, in the get report case this will be the report + snp_report_resp snp_response; + + // the object we pass to the ioctl that wraps the psp request. + snp_guest_request_ioctl ioctl_request; + + memset(&snp_request, 0, sizeof(snp_request)); + + // the report data is passed as a hexstring which needs to be decoded into an array of + // unsigned bytes + // MAA expects a SHA-256. So we use left align the bytes in the report data. + + uint8_t *reportData = decodeHexString(report_data_hexstring, sizeof(snp_request.report_data)); + memcpy(snp_request.report_data, reportData, sizeof(snp_request.report_data)); + + memset(&snp_response, 0, sizeof(snp_response)); + memset(&ioctl_request, 0, sizeof(ioctl_request)); + + ioctl_request.msg_version = 1; + ioctl_request.req_data = (uint64_t)&snp_request; + ioctl_request.resp_data = (uint64_t)&snp_response; + + rc = ioctl(fd, SNP_GET_REPORT, &ioctl_request); + + if (rc < 0) { + fprintf(stderr, "Failed to issue ioctl SEV_SNP_GUEST_MSG_REPORT\n"); + return false; + } + + msg_response_resp *response = (msg_response_resp *)&snp_response.data; + snp_attestation_report *report = &response->report; + + + *snp_report = (snp_attestation_report *) malloc (sizeof(snp_attestation_report)); + memcpy(*snp_report, report, sizeof(snp_attestation_report)); + + return true; +} diff --git a/scp/cc/azure/attestation/get-snp-report/helpers.c b/scp/cc/azure/attestation/get-snp-report/helpers.c new file mode 100644 index 000000000..d7c50324e --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/helpers.c @@ -0,0 +1,106 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snp-ioctl5.h" + +#include "helpers.h" + + + +// Helper functions +uint8_t* decodeHexString(const char *hexstring, size_t padTo) // will zero pad to bufferLen +{ + size_t len = strlen(hexstring); + size_t out_len = len/2+1; + if (out_len < padTo) + out_len = padTo; + uint8_t *byte_array = (uint8_t*) malloc(out_len); + memset(byte_array, 0, out_len); + + for (size_t i = 0; i < len; i+=2) { + sscanf(hexstring, "%2hhx", &byte_array[i/2]); + hexstring += 2; + } + + return byte_array; +} + +char* encodeHexToString(uint8_t byte_array[], size_t len) +{ + char* hexstring = (char*) malloc((2*len+1)*sizeof(char)); + + for (size_t i = 0; i < len; i++) + sprintf(&hexstring[i*2], "%02x", byte_array[i]); + + hexstring[2*len] = '\0'; // string padding character + return hexstring; +} + +void printBytes(const char *desc, const uint8_t *data, size_t len, bool swap) +{ + fprintf(stderr, " %s: ", desc); + int padding = 20 - strlen(desc); + if (padding < 0) + padding = 0; + for (int count = 0; count < padding; count++) + putchar(' '); + + for (size_t pos = 0; pos < len; pos++) { + fprintf(stderr, "%02x", data[swap ? len - pos - 1 : pos]); + if (pos % 32 == 31) + printf("\n "); + else if (pos % 16 == 15) + putchar(' '); + } + fprintf(stderr, "\n"); +} + +void printReport(const snp_attestation_report *r) +{ + PRINT_VAL(r, version); + PRINT_VAL(r, guest_svn); + PRINT_VAL(r, policy); + PRINT_VAL(r, family_id); + PRINT_VAL(r, image_id); + PRINT_VAL(r, vmpl); + PRINT_VAL(r, signature_algo); + PRINT_BYTES(r, platform_version); + PRINT_BYTES(r, platform_info); + PRINT_VAL(r, author_key_en); + PRINT_VAL(r, reserved1); + PRINT_BYTES(r, report_data); + PRINT_BYTES(r, measurement); + PRINT_BYTES(r, host_data); + PRINT_BYTES(r, id_key_digest); + PRINT_BYTES(r, author_key_digest); + PRINT_BYTES(r, report_id); + PRINT_BYTES(r, report_id_ma); + PRINT_VAL(r, reported_tcb); + PRINT_BYTES(r, reserved2); + PRINT_BYTES(r, chip_id); + PRINT_BYTES(r, reserved3); + PRINT_BYTES(r, signature); +} \ No newline at end of file diff --git a/scp/cc/azure/attestation/get-snp-report/helpers.h b/scp/cc/azure/attestation/get-snp-report/helpers.h new file mode 100644 index 000000000..fd45ba1d6 --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/helpers.h @@ -0,0 +1,42 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snp-attestation.h" + + +#define PRINT_VAL(ptr, field) printBytes(#field, (const uint8_t *)&(ptr->field), sizeof(ptr->field), true) +#define PRINT_BYTES(ptr, field) printBytes(#field, (const uint8_t *)&(ptr->field), sizeof(ptr->field), false) + +// Helper functions +uint8_t* decodeHexString(const char *hexstring, size_t padTo); + +char* encodeHexToString(uint8_t byte_array[], size_t len); + +void printBytes(const char *desc, const uint8_t *data, size_t len, bool swap); + +void printReport(const snp_attestation_report *r); \ No newline at end of file diff --git a/scp/cc/azure/attestation/get-snp-report/snp-attestation.h b/scp/cc/azure/attestation/get-snp-report/snp-attestation.h new file mode 100644 index 000000000..ce8315882 --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/snp-attestation.h @@ -0,0 +1,71 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +/* structures common to both 5.15.* and 6.* kernels */ +/* essentially this is the interface to the PSP */ + +/* from SEV-SNP Firmware ABI Specification Table 20 */ +typedef struct { + uint8_t report_data[64]; + uint32_t vmpl; + uint8_t reserved[28]; // needs to be zero +} msg_report_req; + +/* from SEV-SNP Firmware ABI Specification from Table 21 */ +typedef struct { + uint32_t version; // version no. of this attestation report. Set to 1 for this specification. + uint32_t guest_svn; // The guest SVN + uint64_t policy; // see table 8 - various settings + __uint128_t family_id; // as provided at launch + __uint128_t image_id; // as provided at launch + uint32_t vmpl; // the request VMPL for the attestation report + uint32_t signature_algo; + uint64_t platform_version; // The install version of the firmware + uint64_t platform_info; // information about the platform see table 22 + // not going to try to use bit fields for this next one. Too confusing as to which bit of the byte will be used. Make a mask if you need it + uint32_t author_key_en; // 31 bits of reserved, must be zero, bottom bit indicates that the digest of the + // author key is present in AUTHOR_KEY_DIGEST. Set to the value of GCTX.AuthorKeyEn. + uint32_t reserved1; // must be zero + uint8_t report_data[64]; // Guest provided data. + uint8_t measurement[48]; // measurement calculated at launch + uint8_t host_data[32]; // data provided by the hypervisor at launch + uint8_t id_key_digest[48]; // SHA-384 digest of the ID public key that signed the ID block provided in SNP_LAUNCH_FINISH + uint8_t author_key_digest[48]; // SHA-384 digest of the Author public key that certified the ID key, if provided in SNP_LAUNCH_FINISH. Zeros if author_key_en is 1 (sounds backwards to me). + uint8_t report_id[32]; // Report ID of this guest. + uint8_t report_id_ma[32]; // Report ID of this guest's mmigration agent. + uint64_t reported_tcb; // Reported TCB version used to derive the VCEK that signed this report + uint8_t reserved2[24]; // reserved + uint8_t chip_id[64]; // Identifier unique to the chip + uint8_t committed_svn[8]; // The current commited SVN of the firware (version 2 report feature) + uint8_t committed_version[8]; // The current commited version of the firware + uint8_t launch_svn[8]; // The SVN that this guest was launched or migrated at + uint8_t reserved3[168]; // reserved + uint8_t signature[512]; // Signature of this attestation report. See table 23. +} snp_attestation_report; + +/* from SEV-SNP Firmware ABI Specification Table 22 */ +typedef struct { + uint32_t status; + uint32_t report_size; + uint8_t reserved[24]; + snp_attestation_report report; + uint8_t padding[64]; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) +} msg_response_resp; diff --git a/scp/cc/azure/attestation/get-snp-report/snp-ioctl5.h b/scp/cc/azure/attestation/get-snp-report/snp-ioctl5.h new file mode 100644 index 000000000..348b546b1 --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/snp-ioctl5.h @@ -0,0 +1,60 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +/* linux kernel 5.15.* versions of the ioctls that talk to the PSP */ + +/* From sev-snp driver include/uapi/linux/psp-sev-guest.h */ +struct sev_snp_guest_request { + uint8_t req_msg_type; + uint8_t rsp_msg_type; + uint8_t msg_version; + uint16_t request_len; + uint64_t request_uaddr; + uint16_t response_len; + uint64_t response_uaddr; + uint32_t error; /* firmware error code on failure (see psp-sev.h) */ +}; + +enum snp_msg_type { + SNP_MSG_TYPE_INVALID = 0, + SNP_MSG_CPUID_REQ, + SNP_MSG_CPUID_RSP, + SNP_MSG_KEY_REQ, + SNP_MSG_KEY_RSP, + SNP_MSG_REPORT_REQ, + SNP_MSG_REPORT_RSP, + SNP_MSG_EXPORT_REQ, + SNP_MSG_EXPORT_RSP, + SNP_MSG_IMPORT_REQ, + SNP_MSG_IMPORT_RSP, + SNP_MSG_ABSORB_REQ, + SNP_MSG_ABSORB_RSP, + SNP_MSG_VMRK_REQ, + SNP_MSG_VMRK_RSP, + SNP_MSG_TYPE_MAX +}; + + +#define SEV_GUEST_IOC_TYPE 'S' +#define SEV_SNP_GUEST_MSG_REQUEST _IOWR(SEV_GUEST_IOC_TYPE, 0x0, struct sev_snp_guest_request) +#define SEV_SNP_GUEST_MSG_REPORT _IOWR(SEV_GUEST_IOC_TYPE, 0x1, struct sev_snp_guest_request) +#define SEV_SNP_GUEST_MSG_KEY _IOWR(SEV_GUEST_IOC_TYPE, 0x2, struct sev_snp_guest_request) diff --git a/scp/cc/azure/attestation/get-snp-report/snp-ioctl6.h b/scp/cc/azure/attestation/get-snp-report/snp-ioctl6.h new file mode 100644 index 000000000..6f2e9ce9f --- /dev/null +++ b/scp/cc/azure/attestation/get-snp-report/snp-ioctl6.h @@ -0,0 +1,88 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + + +#include +#include +#include + +/* linux kernel 6.* versions of the ioctls that talk to the PSP */ + +/* From sev-snp driver include/uapi/linux/psp-sev-guest.h */ + +struct sev_snp_guest_request { + uint8_t req_msg_type; + uint8_t rsp_msg_type; + uint8_t msg_version; + uint16_t request_len; + uint64_t request_uaddr; + uint16_t response_len; + uint64_t response_uaddr; + uint32_t error; /* firmware error code on failure (see psp-sev.h) */ +}; + +// aka/replaced by this from include/uapi/linux/sev-guest.h +// +typedef struct { + /* message version number (must be non-zero) */ + uint8_t msg_version; + + /* Request and response structure address */ + uint64_t req_data; + uint64_t resp_data; + + /* firmware error code on failure (see psp-sev.h) */ + uint64_t fw_err; +} snp_guest_request_ioctl; + +enum snp_msg_type { + SNP_MSG_TYPE_INVALID = 0, + SNP_MSG_CPUID_REQ, + SNP_MSG_CPUID_RSP, + SNP_MSG_KEY_REQ, + SNP_MSG_KEY_RSP, + SNP_MSG_REPORT_REQ, + SNP_MSG_REPORT_RSP, + SNP_MSG_EXPORT_REQ, + SNP_MSG_EXPORT_RSP, + SNP_MSG_IMPORT_REQ, + SNP_MSG_IMPORT_RSP, + SNP_MSG_ABSORB_REQ, + SNP_MSG_ABSORB_RSP, + SNP_MSG_VMRK_REQ, + SNP_MSG_VMRK_RSP, + SNP_MSG_TYPE_MAX +}; + +#define SNP_GUEST_REQ_IOC_TYPE 'S' +#define SNP_GET_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x0, snp_guest_request_ioctl) +#define SNP_GET_DERIVED_KEY _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x1, snp_guest_request_ioctl) +#define SNP_GET_EXT_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x2, snp_guest_request_ioctl) + +/* from SEV-SNP Firmware ABI Specification Table 20 */ + +typedef struct { + uint8_t report_data[64]; + uint32_t vmpl; + uint8_t reserved[28]; // needs to be zero +} snp_report_req; // aka snp_report_req in (linux) include/uapi/linux/sev-guest.h + +typedef struct { +/* response data, see SEV-SNP spec for the format */ + uint8_t data[4000]; +} snp_report_resp; diff --git a/scp/cc/azure/attestation/json_attestation_report.cc b/scp/cc/azure/attestation/json_attestation_report.cc new file mode 100644 index 000000000..bd9e3939e --- /dev/null +++ b/scp/cc/azure/attestation/json_attestation_report.cc @@ -0,0 +1,152 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include "json_attestation_report.h" +#include "core/utils/src/base64.h" +#include +#include + +using google::scp::core::utils::Base64Encode; +using google::scp::core::utils::Base64Decode; + + +void Base64EncodeBytes(const uint8_t* decoded, size_t size, std::string& encoded) { + size_t required_len = 0; + EVP_EncodedLength(&required_len, size); + auto buffer = std::make_unique(required_len); + int ret = EVP_EncodeBlock(buffer.get(), decoded, size); + encoded = std::string(reinterpret_cast(buffer.get()), ret); +} + + +std::string replace(const std::string& input, const std::string toReplace, const std::string replaceWith) { + std::string output = input; + size_t pos = 0; + while ((pos = output.find(toReplace, pos)) != std::string::npos) { + output.replace(pos, toReplace.length(), replaceWith); + pos += replaceWith.length(); + } + return output; +} + + +std::string getSnpReport(const std::string report_data) { + + // Call the C code to fetch the report + uint8_t* snp_report_hex; + fetchSnpReport(report_data.c_str(), (void*)&snp_report_hex); + + // Base64 encode the report + std::string snp_report_hex_str; + Base64EncodeBytes(snp_report_hex, sizeof(snp_attestation_report), snp_report_hex_str); + + return snp_report_hex_str; +} + + +nlohmann::json loadHostAmdCerts() { + + // Read the local Base64 encoded AMD certs + const auto host_certs_b64 = fetchSecurityContextFile("/host-amd-cert-base64"); + std::string host_certs_b64_str(host_certs_b64.begin(), host_certs_b64.end()); + + // Decode the contents of the file + std::string host_certs_str; + Base64Decode(host_certs_b64_str, host_certs_str); + + // Parse the decoded string into JSON + return nlohmann::json::parse(host_certs_str); +} + + +std::string getEndorsementCerts(nlohmann::json host_certs_json) { + + // Extract the certs from the JSON + std::string vcekCert = host_certs_json["vcekCert"].dump(); + std::string certificateChain = host_certs_json["certificateChain"].dump(); + + // Combine the certs into a chain and cleanup characters that shouldn't be there + std::string endorsementCerts = vcekCert + certificateChain; + endorsementCerts.erase(std::remove(endorsementCerts.begin(), endorsementCerts.end(), '\"'), endorsementCerts.cend()); + endorsementCerts = replace(endorsementCerts, "\\n", "\n"); + + // Base64 encode the certificate chain + Base64Encode(endorsementCerts, endorsementCerts); + return endorsementCerts; +} + + +std::string getUvmEndorsements() { + + // Read the local Base64 encoded UVM endorsements + const auto uvm_endorsements = fetchSecurityContextFile("/reference-info-base64"); + std::string uvm_endorsements_str(uvm_endorsements.begin(), uvm_endorsements.end()); + + return uvm_endorsements_str; +} + + +std::string getEndorsedTcb(nlohmann::json host_certs_json) { + + // Extract the endorsed TCB from the JSON + std::string endorsed_tcb_reversed_endian = host_certs_json["tcbm"]; + + // Reverse the endianess of the endorsed TCB + std::string endorsed_tcb = ""; + for (int i = endorsed_tcb_reversed_endian.length() - 2; i >= 0; i -= 2) { + endorsed_tcb += endorsed_tcb_reversed_endian.substr(i, 2); + } + + return endorsed_tcb; +} + + +bool hasSnp() { + std::ifstream sev_file("/dev/sev"); + return sev_file.good(); +} + + +nlohmann::json fetchFakeSnpAttestation() { + // Update this value by running //scp/cc/azure/attestation:print_snp_json (The usage is in print_snp_json.cc). + // Also you need to update the fake attestation in Azure KMS. + nlohmann::json kms_request_body; + kms_request_body["evidence"] = "AgAAAAIAAAAfAAMAAAAAAAEAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAAAI0gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsOw1b8dJW+k47Xe78B7Vf8vcCkIXtNQ9glZFAoaUfExB1O6WrLAOgU2scDBk69HT0RIxn88jfyN6KXjcSXYB9rcxB8GzyP2FdvVLux3fRCBLO9VZ+eO/KBz/eiJSxgOqu87VghAkMZHgrWtU2vPED0ZSRSyKcqsXFQIr86R/IYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC76j4CdAnTfrWAxZAaBHUBSCMnekwrkur1innnjjqhHv//////////////////////////////////////////AwAAAAAACHMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcfjfMm5W00HLsTG68q+r5imZVsuaSdo+et54P4fqdbHCx0KJRdX3NSLNg1mD4m78ijvn5kAMBTG1TSmgUc+oHAwAAAAAACHMENAEABDQBAAMAAAAAAAhzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuU7THp0vh0SIcHW94ShZ8YwgXYcM7A5Oe9XXKdeEJpwQm7RNqX/1a8NOjmrzYS0yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA100HVbSEoK/tifyXvY7Br+k7UN/CIDBE/c+LYoInHSAzIphkFdIQf3H5oACyEAv+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + kms_request_body["endorsements"] = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZURENDQXZ1Z0F3SUJBZ0lCQURCR0Jna3Foa2lHOXcwQkFRb3dPYUFQTUEwR0NXQ0dTQUZsQXdRQ0FnVUEKb1J3d0dnWUpLb1pJaHZjTkFRRUlNQTBHQ1dDR1NBRmxBd1FDQWdVQW9nTUNBVENqQXdJQkFUQjdNUlF3RWdZRApWUVFMREF0RmJtZHBibVZsY21sdVp6RUxNQWtHQTFVRUJoTUNWVk14RkRBU0JnTlZCQWNNQzFOaGJuUmhJRU5zCllYSmhNUXN3Q1FZRFZRUUlEQUpEUVRFZk1CMEdBMVVFQ2d3V1FXUjJZVzVqWldRZ1RXbGpjbThnUkdWMmFXTmwKY3pFU01CQUdBMVVFQXd3SlUwVldMVTFwYkdGdU1CNFhEVEl6TURFeE5URTBNRFl6TVZvWERUTXdNREV4TlRFMApNRFl6TVZvd2VqRVVNQklHQTFVRUN3d0xSVzVuYVc1bFpYSnBibWN4Q3pBSkJnTlZCQVlUQWxWVE1SUXdFZ1lEClZRUUhEQXRUWVc1MFlTQkRiR0Z5WVRFTE1Ba0dBMVVFQ0F3Q1EwRXhIekFkQmdOVkJBb01Ga0ZrZG1GdVkyVmsKSUUxcFkzSnZJRVJsZG1salpYTXhFVEFQQmdOVkJBTU1DRk5GVmkxV1EwVkxNSFl3RUFZSEtvWkl6ajBDQVFZRgpLNEVFQUNJRFlnQUVZdElnNUQ2dlErajljQVRPYkd1dkhGa1lqU05ma3JLNXJIWHJUdEJUTnJXMjhDMkFxTFJsCkZpRDcrT0daRTJxa3UyVFVtUlM5cHhxbUNKM2pGK0NDMXZGOXg2UmF5dXVqVnA4Z0VGTWwzU0cvaFZwZjJCZzMKaWorYVFiZUhqUHgrbzRJQkZqQ0NBUkl3RUFZSkt3WUJCQUdjZUFFQkJBTUNBUUF3RndZSkt3WUJCQUdjZUFFQwpCQW9XQ0UxcGJHRnVMVUl3TUJFR0Npc0dBUVFCbkhnQkF3RUVBd0lCQXpBUkJnb3JCZ0VFQVp4NEFRTUNCQU1DCkFRQXdFUVlLS3dZQkJBR2NlQUVEQkFRREFnRUFNQkVHQ2lzR0FRUUJuSGdCQXdVRUF3SUJBREFSQmdvckJnRUUKQVp4NEFRTUdCQU1DQVFBd0VRWUtLd1lCQkFHY2VBRURCd1FEQWdFQU1CRUdDaXNHQVFRQm5IZ0JBd01FQXdJQgpDREFSQmdvckJnRUVBWng0QVFNSUJBTUNBWE13VFFZSkt3WUJCQUdjZUFFRUJFRGNmamZNbTVXMDBITHNURzY4CnErcjVpbVpWc3VhU2RvK2V0NTRQNGZxZGJIQ3gwS0pSZFgzTlNMTmcxbUQ0bTc4aWp2bjVrQU1CVEcxVFNtZ1UKYytvSE1FWUdDU3FHU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBSUNCUUNoSERBYUJna3Foa2lHOXcwQgpBUWd3RFFZSllJWklBV1VEQkFJQ0JRQ2lBd0lCTUtNREFnRUJBNElDQVFCZ0NJWlZHR2VyUFNGRExwQXBPSDBQCmVyYVRndUhOb2cwQ29qcloyMWc1QnBwcm0zaG5ZMUNYMVcwaTBKVEJ1YTRta1hsTmVGdHc5THRoOFlPNVdmUW0KQ1RnamdCcWovQXJ3U2xtbGFxcDI0QVVIVll3dlVxcXlsclp4L2pBMFo3TmdPY0FiZmFFNXhDNWFZOTFPTTFNRwpDSDkxY3A3SW9ZT2dYY0lMaVZ6dmdTR25hQXpvd2VJbVozbVRETVZTanorekpLbUZIRGFQbENZN1E5b3N4Y0syCjdrcHQ0WjEzN3RJc2VLTzM0TXo1c1NvYzk2bTExeCtZaE9LbjBseWoxdnlMdnN4OWdLZDdDM01RbnVKZ1pqd2oKQ1lkYzhzRlZFdGJ4MU45azd6c3JSUUpMQ0sxdkMrVnd5T0RsYnY1dFBuK0duV1B0ZEhVcHZuVFp1czFmKzlFWAo4aGhoY1lFeHdWcFh5SVB4b29lMVhHZ2E5NXpHYVA5dWFJM2tPcXNDeDVYVmxiMms4YytFbncxK0ZKcUlSZFk1Ci96b3VVa0pqbGYxci9Bb0FpaEhBTmM2QkpucDIzZkFuRGVTVEJWT28vaXhJeFFISjRKaXRGMFNLTzdBVFI2QmoKOGM3QjFXZnNBdHoyeWFRelNLbG5Ra2liVzJrbGQ1OHUyNmkwS29ZSmxROHhaYktnV0krZVh1NTF6cTFqejR0dQpsRHdCcUN2WEJUemZuYXlpMFM3Z3E2QUFETHo1TDRqK2xOTnJtaHpaSTZDRWxudHFYeWV5RmNVYXI5QnptQ3gyCklpeUt2bkRIWXV0MnpaRFh6enN2Z3ljN0VlSmVpcGE3VXBrU1NyZE54b3F3bEgwSlRCYUpvNlFFcG5TY2JkamwKSENuUUhKMVBPWHlMbnJoc0xqU3A5Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdpVENDQkRpZ0F3SUJBZ0lEQVFBQk1FWUdDU3FHU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBSUMKQlFDaEhEQWFCZ2txaGtpRzl3MEJBUWd3RFFZSllJWklBV1VEQkFJQ0JRQ2lBd0lCTUtNREFnRUJNSHN4RkRBUwpCZ05WQkFzTUMwVnVaMmx1WldWeWFXNW5NUXN3Q1FZRFZRUUdFd0pWVXpFVU1CSUdBMVVFQnd3TFUyRnVkR0VnClEyeGhjbUV4Q3pBSkJnTlZCQWdNQWtOQk1SOHdIUVlEVlFRS0RCWkJaSFpoYm1ObFpDQk5hV055YnlCRVpYWnAKWTJWek1SSXdFQVlEVlFRRERBbEJVa3N0VFdsc1lXNHdIaGNOTWpBeE1ESXlNVGd5TkRJd1doY05ORFV4TURJeQpNVGd5TkRJd1dqQjdNUlF3RWdZRFZRUUxEQXRGYm1kcGJtVmxjbWx1WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhGREFTCkJnTlZCQWNNQzFOaGJuUmhJRU5zWVhKaE1Rc3dDUVlEVlFRSURBSkRRVEVmTUIwR0ExVUVDZ3dXUVdSMllXNWoKWldRZ1RXbGpjbThnUkdWMmFXTmxjekVTTUJBR0ExVUVBd3dKVTBWV0xVMXBiR0Z1TUlJQ0lqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBblUyZHJyTlRmYmhOUUlsbGYrVzJ5K1JPQ2JTeklkMWFLWmZ0CjJUOXpqWlFPempHY2NsMTdpMW1JS1dsN05UY0IwVllYdDNKeFpTek9aanNqTE5WQUVOMk1HajlUaWVkTCtRZXcKS1pYMEptUUV1WWptK1dLa3NMdHhnZExwOUU3RVpOd05EcVYxcjBxUlA1dEI4T1dreVFiSWRMZXU0YUN6N2ovUwpsMUZrQnl0ZXY5c2JGR3p0N2N3bmp6aTltN25vcXNrK3VSVkJwMytJbjM1UVBkY2o4WWZsRW1uSEJOdnVVREpoCkxDSk1XOEtPalA2KytQaGJzM2lDaXRKY0FORXRXNHFUTkZvS1czQ0hsYmNTQ2pUTThLc05iVXgzQThlazVFVkwKalpXSDFwdDlFM1RmcFI2WHlmUUtuWTZrbDVhRUlQd2RXM2VGWWFxQ0ZQcklvOXBRVDZXdURTUDRKQ1lKYlpuZQpLS0liWmp6WGtKdDNOUUczMkV1a1lJbUJiOVNDa205K2ZTNUxaRmc5b2p6dWJNWDMrTmtCb1NYSTdPUHZuSE14Cmp1cDltdzVzZTZRVVY3R3FwQ0EyVE55cG9sbXVRK2NBYXhWN0pxSEU4ZGw5cFdmK1kzYXJiKzlpaUZDd0Z0NGwKQWxKdzVEMENUUlRDMVk1WVdGREJDckEvdkdubVRucUc4QytqalVBUzdjampSOHE0T1BoeURtSlJQbmFDL1pHNQp1UDBLMHo2R29PLzN1ZW45d3FzaEN1SGVnTFRwT2VIRUpSS3JRRnI0UFZJd1ZPQjArZWJPNUZnb3lPdzQzbnlGCkQ1VUtCRHhFQjRCS28vMHVBaUtITFJ2dmdMYk9SYlU4S0FSSXMxRW9xRWptRjhVdHJtUVdWMmhVand6cXd2SEYKZWk4clB4TUNBd0VBQWFPQm96Q0JvREFkQmdOVkhRNEVGZ1FVTzhadUdDckQvVDFpWkVpYjQ3ZEhMTFQ4di9ndwpId1lEVlIwakJCZ3dGb0FVaGF3YTBVUDN5S3hWMU1VZFFVaXIxWGhLMUZNd0VnWURWUjBUQVFIL0JBZ3dCZ0VCCi93SUJBREFPQmdOVkhROEJBZjhFQkFNQ0FRUXdPZ1lEVlIwZkJETXdNVEF2b0MyZ0s0WXBhSFIwY0hNNkx5OXIKWkhOcGJuUm1MbUZ0WkM1amIyMHZkbU5sYXk5Mk1TOU5hV3hoYmk5amNtd3dSZ1lKS29aSWh2Y05BUUVLTURtZwpEekFOQmdsZ2hrZ0JaUU1FQWdJRkFLRWNNQm9HQ1NxR1NJYjNEUUVCQ0RBTkJnbGdoa2dCWlFNRUFnSUZBS0lECkFnRXdvd01DQVFFRGdnSUJBSWdlVVFTY0FmM2xEWXFnV1UxVnRsRGJtSU44UzJkQzVrbVF6c1ovSHRBalFuTEUKUEkxamgzZ0piTHhMNmdmM0s4anhjdHpPV25rWWNiZGZNT09yMjhLVDM1SWFBUjIwcmVrS1JGcHRUSGhlK0RGcgozQUZ6WkxERDdjV0syOS9HcFBpdFBKREtDdkk3QTRVZzA2cms3SjB6QmUxZnovcWU0aTIvRjEycnZmd0NHWWhjClJ4UHk3UUYzcThmUjZHQ0pkQjFVUTVTbHdDakZ4RDR1ZXpVUnp0SWxJQWpNa3Q3REZ2S1JoKzJ6Sys1cGxWR0cKRnNqREp0TXoydWQ5eTBwdk9FNGozZEg1SVc5akd4YVNHU3RxTnJhYm5ucEYyMzZFVHIxL2E0M2I4RkZLTDVRTgptdDhWcjl4blhScHpucUNSdnFqcitrVnJiNmRsZnVUbGxpWGVRVE1sQm9SV0ZKT1JMOEFjQkp4R1o0SzJtWGZ0CmwxalU1VExlaDVLWEw5Tlc3YS9xQU9JVXMyRmlPaHFydHpBaEpSZzlJajhRa1E5UGsrY0tHenc2RWwzVDNrRnIKRWc2emt4bXZNdWFiWk9zZEtmUmtXZmhIMlpLY1RsRGZtSDFIMHpxMFEyYkczdXZhVmRpQ3RGWTFMbFd5QjM4SgpTMmZOc1IvUHk2dDVickVKQ0ZOdnphRGt5NktlQzRpb24vY1ZnVWFpN3p6UzNiR1FXektES1UzNVNxTlUyV2tQCkk4eENaMDBXdElpS0tGblhXVVF4dmxLbW1nWkJJWVBlMDF6RDBOOGF0RnhtV2lTbmZKbDY5MEI5ckpwTlIvZkkKYWp4Q1czU2Vpd3M2cjFabSt0Q3VWYk1pTnRwUzlUaGpOWDR1dmU1dGh5ZkUyRGdveFJGdlkxQ3NvRjVNCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdZekNDQkJLZ0F3SUJBZ0lEQVFBQU1FWUdDU3FHU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBSUMKQlFDaEhEQWFCZ2txaGtpRzl3MEJBUWd3RFFZSllJWklBV1VEQkFJQ0JRQ2lBd0lCTUtNREFnRUJNSHN4RkRBUwpCZ05WQkFzTUMwVnVaMmx1WldWeWFXNW5NUXN3Q1FZRFZRUUdFd0pWVXpFVU1CSUdBMVVFQnd3TFUyRnVkR0VnClEyeGhjbUV4Q3pBSkJnTlZCQWdNQWtOQk1SOHdIUVlEVlFRS0RCWkJaSFpoYm1ObFpDQk5hV055YnlCRVpYWnAKWTJWek1SSXdFQVlEVlFRRERBbEJVa3N0VFdsc1lXNHdIaGNOTWpBeE1ESXlNVGN5TXpBMVdoY05ORFV4TURJeQpNVGN5TXpBMVdqQjdNUlF3RWdZRFZRUUxEQXRGYm1kcGJtVmxjbWx1WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhGREFTCkJnTlZCQWNNQzFOaGJuUmhJRU5zWVhKaE1Rc3dDUVlEVlFRSURBSkRRVEVmTUIwR0ExVUVDZ3dXUVdSMllXNWoKWldRZ1RXbGpjbThnUkdWMmFXTmxjekVTTUJBR0ExVUVBd3dKUVZKTExVMXBiR0Z1TUlJQ0lqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBMExkNTJSSk9kZWlKbHFLMkpkc1ZtRDdGa3R1b3RXd1gxZk5nClc0MVhZOVh6MUhFaFNVbWhMejlDdTlESFJsdmdKU054YmVZWXNuSmZ2eWp4MU1mVTBWNXRrS2lVMUVlc05GdGEKMWtUQTBzek5pc2RZYzlpc3FrN21YVDUrS2ZHUmJmYzRWLzl6UkljRThqbEhONjFTMWp1OFg5Mys2ZHhEVXJHMgpTenhxSjRCaHF5WW1VRHJ1UFhKU1g0dlVjMDFQN2o5OE1wcU9TOTVyT1JkR0hlSTUyTmF6NW0yQitPK3Zqc0MwCjYwZDM3alk5TEZldU9QNE1lcmk4cWdmaTJTNWtLcWcvYUY2YVB0dUFaUVZSN3UzS0ZZWFA1OVhtSmd0Y29nMDUKZ21JMFQvT2l0TGh1elZ2cFpjTHBoMG9kaC8xSVBYcXgzK01uakQ5N0E3ZlhwcUdkL3k4S3hYN2prc1RFekFPZwpiS0FlYW0zbG0rM3lLSWNUWU1sc1JNWFBjak5iSXZtc0J5a0QvL3hTbml1c3VIQmtnbmxFTkVXeDFVY2JRUXJzCitnVkRrdVZQaHNueklSTmdZdk00OFkrN0xHaUpZbnJtRTh4Y3JleGVrQnhydmEyVjlUSlFxbk4zUTUza3Q1dmkKUWkzK2dDZm1rd0MwRjB0aXJJWmJMa1hQclB3elowTTllTnhoSXlTYjJucEpmZ25xejU1STB1MzN3aDRyMFpOUQplVEdmdzAzTUJVdHl1ekdlc0drY3crbG9xTWFxMXFSNHRqR2JQWXhDdnBDcTcrT2dwQ0NvTU5pdDJ1TG85TTE4CmZIejEwbE9NVDhuV0FVdlJaRnp0ZVhDbSs3UEhkWVBsbVF3VXczTHZlbkovSUxYb1FQSGZia0gwQ3lQZmhsMWoKV2hKRlphc0NBd0VBQWFOK01Id3dEZ1lEVlIwUEFRSC9CQVFEQWdFR01CMEdBMVVkRGdRV0JCU0ZyQnJSUS9mSQpyRlhVeFIxQlNLdlZlRXJVVXpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTURvR0ExVWRId1F6TURFd0w2QXRvQ3VHCktXaDBkSEJ6T2k4dmEyUnphVzUwWmk1aGJXUXVZMjl0TDNaalpXc3ZkakV2VFdsc1lXNHZZM0pzTUVZR0NTcUcKU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBSUNCUUNoSERBYUJna3Foa2lHOXcwQkFRZ3dEUVlKWUlaSQpBV1VEQkFJQ0JRQ2lBd0lCTUtNREFnRUJBNElDQVFDNm0wa0RwNnp2NE9qZmd5K3psZWVoc3g2b2wwb2NnVmVsCkVUb2JweCtFdUNzcVZGUlBLMWpaMXNwL2x5ZDkrMGZRMHI2Nm43a2FnUms0Q2EzOWc2NldHVEpNZUpkcVlyaXcKU1RqakRDS1ZQU2VzV1hZUFZBeURobVA1bjJ2K0JZaXBaV2hwdnFwYWlPK0VHSzVJQlArNTc4UWVXL3NTb2tySwpkSGFMQXhHMkxoWnhqOWFGNzNmcUM3T0FKWjVhUG9udzRSRTI5OUZWYXJoMVR4MmVUM3dTZ2tEZ3V0Q1RCMVlxCnpUNUR1d3ZBZStjbzJDSVZJek1EYW1ZdVNGalBOMEJDZ29qbDdWK2JUb3U3ZE1zcUl1L1RXL3JQQ1g5L0VVY3AKS0dLcVBRM1ArTjlyMWhqRUZZMXBsQmc5M3Q1M09PbzQ5R05JK1YxenZYUExJNnhJRlZzaCttdG8yUnRnRVgvZQpwbU1LVE5ONnBzVzg4cWc3YzFoVFd0TjZNYlJ1UTB2bStPKy8ydEtCRjJoOFRIYjk0T3Z2SEhvRkRwYkNFTGxxCkhuSVloeHkwWUtYR3lhVzFOamZVTHhycm14Vlc0d2NuNUU4R2RkbXZOYTZ5WW04c2NKYWdFaTEzbWhHdTRKcWgKM1FVM3NmOGlVU1VyMDl4UUR3SHRPUVVWSXF4NG1hQlpQQnRTTWYrcVVEdGpYU1NxOGxmV2NkOGJMcjltZHNVbgpKWkowK3R1UE1LbUJuU0g4NjBsbEtrK1ZwVlFzZ3FiekRJdk9MdkQ2VzFVbXEyNWJveENZSitUdUJvYTRzK0hICkNWaUF2Z1Q5a2YvckJxMWQraXZqNnNra0h4dXpjeGJrMXh2NlpHeHJ0ZUp4Vkg3S2xYN1lSZFo2ZUFSS3dMZTQKQUZaRUF3b0tDUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"; + kms_request_body["uvm_endorsements"] = "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"; + kms_request_body["endorsed_tcb"] = "0300000000000873"; + return kms_request_body; +} + + +nlohmann::json fetchSnpAttestation(const std::string report_data) { + + assert(hasSnp()); + + const auto host_certs_json = loadHostAmdCerts(); + + nlohmann::json kms_request_body; + kms_request_body["evidence"] = getSnpReport(report_data); + kms_request_body["endorsements"] = getEndorsementCerts(host_certs_json); + kms_request_body["uvm_endorsements"] = getUvmEndorsements(); + kms_request_body["endorsed_tcb"] = getEndorsedTcb(host_certs_json); + + return kms_request_body; +} \ No newline at end of file diff --git a/scp/cc/azure/attestation/json_attestation_report.h b/scp/cc/azure/attestation/json_attestation_report.h new file mode 100644 index 000000000..833218e93 --- /dev/null +++ b/scp/cc/azure/attestation/json_attestation_report.h @@ -0,0 +1,31 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JSON_ATTESTATION_REPORT_H +#define JSON_ATTESTATION_REPORT_H + +#include "get-snp-report/get-snp-report.h" +#include +#include "security_context_fetcher.h" + +bool hasSnp(); +nlohmann::json fetchFakeSnpAttestation(); +nlohmann::json fetchSnpAttestation(const std::string report_data = ""); + +extern "C" { + bool fetchSnpReport(const char* report_data_hexstring, void* snp_report); +} +#endif // JSON_ATTESTATION_REPORT_H diff --git a/scp/cc/azure/attestation/print_snp_json.cc b/scp/cc/azure/attestation/print_snp_json.cc new file mode 100644 index 000000000..84ab8535c --- /dev/null +++ b/scp/cc/azure/attestation/print_snp_json.cc @@ -0,0 +1,29 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "scp/cc/azure/attestation/json_attestation_report.h" + +// Usage: bazel run //scp/cc/azure/attestation:print_snp_json +// The output's path is `bazel-bin/scp/cc/azure/attestation/print_snp_json`. + +int main() { + const bool snp = hasSnp(); + const auto report = hasSnp() ? fetchSnpAttestation() : fetchFakeSnpAttestation(); + std::cout << "report (fake=" << !hasSnp() << "):\n"; + std::cout << report.dump(2) << std::endl; + return 0; +} \ No newline at end of file diff --git a/scp/cc/azure/attestation/readme.md b/scp/cc/azure/attestation/readme.md new file mode 100644 index 000000000..57dd646f0 --- /dev/null +++ b/scp/cc/azure/attestation/readme.md @@ -0,0 +1,18 @@ +# Overview + +This is a utility to fetch attestation report and its endorsements. +It works on [Confidential containers on Azure Container Instances](https://learn.microsoft.com/en-gb/azure/container-instances/container-instances-confidential-overview) and also provide an API to provide fake attestation for testing purpose. + +## print_snp_json + +`print_snp_json` is a utility to print attestation report and its endorsements that can be used for Azure KMS. + +Usage: + +```bash +# Run the utility +bazel run //scp/cc/azure/attestation:print_snp_json + +# Build statically-linked binary +bazel build //scp/cc/azure/attestation:print_snp_json +``` diff --git a/scp/cc/azure/attestation/security_context_fetcher.cc b/scp/cc/azure/attestation/security_context_fetcher.cc new file mode 100644 index 000000000..203343849 --- /dev/null +++ b/scp/cc/azure/attestation/security_context_fetcher.cc @@ -0,0 +1,35 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "security_context_fetcher.h" + +std::vector fetchSecurityContextFile(std::string file_path) { + const char* dir = std::getenv("UVM_SECURITY_CONTEXT_DIR"); + if (!dir) { + throw std::runtime_error( + "UVM_SECURITY_CONTEXT_DIR environment variable is not set"); + } + + std::string full_path = std::string(dir) + file_path; + std::ifstream file(full_path, std::ios::binary); + + if (!file) { + throw std::runtime_error("Unable to open file at full_path: " + full_path); + } + + return {std::istreambuf_iterator(file), + std::istreambuf_iterator()}; +} \ No newline at end of file diff --git a/scp/cc/azure/attestation/security_context_fetcher.h b/scp/cc/azure/attestation/security_context_fetcher.h new file mode 100644 index 000000000..c0e6351a7 --- /dev/null +++ b/scp/cc/azure/attestation/security_context_fetcher.h @@ -0,0 +1,22 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +std::vector fetchSecurityContextFile(std::string file_path); \ No newline at end of file diff --git a/scp/cc/cpio/client_providers/auth_token_provider/src/BUILD.bazel b/scp/cc/cpio/client_providers/auth_token_provider/src/BUILD.bazel index e9c984111..35ab8fe31 100644 --- a/scp/cc/cpio/client_providers/auth_token_provider/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/auth_token_provider/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//scp/cc/cpio/client_providers/auth_token_provider/src/aws:aws_auth_token_provider_lib", ], + "//:azure_platform": [ + "//scp/cc/cpio/client_providers/auth_token_provider/src/gcp:gcp_auth_token_provider_lib", + ], "//:gcp_platform": [ "//scp/cc/cpio/client_providers/auth_token_provider/src/gcp:gcp_auth_token_provider_lib", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/scp/cc/cpio/client_providers/blob_storage_client_provider/src/BUILD.bazel b/scp/cc/cpio/client_providers/blob_storage_client_provider/src/BUILD.bazel index 2c1cccc11..45fa11b89 100644 --- a/scp/cc/cpio/client_providers/blob_storage_client_provider/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/blob_storage_client_provider/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//scp/cc/cpio/client_providers/blob_storage_client_provider/src/aws:aws_blob_storage_client_provider_lib", ], + "//:azure_platform": [ + "//scp/cc/cpio/client_providers/blob_storage_client_provider/src/gcp:gcp_blob_storage_client_provider_lib", + ], "//:gcp_platform": [ "//scp/cc/cpio/client_providers/blob_storage_client_provider/src/gcp:gcp_blob_storage_client_provider_lib", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/scp/cc/cpio/client_providers/cloud_initializer/src/BUILD.bazel b/scp/cc/cpio/client_providers/cloud_initializer/src/BUILD.bazel index cb7c85c83..246841e5c 100644 --- a/scp/cc/cpio/client_providers/cloud_initializer/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/cloud_initializer/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//scp/cc/cpio/client_providers/cloud_initializer/src/aws:aws_initializer_lib", ], + "//:azure_platform": [ + "//scp/cc/cpio/client_providers/cloud_initializer/src/azure:no_op_initializer_lib", + ], "//:gcp_platform": [ "//scp/cc/cpio/client_providers/cloud_initializer/src/gcp:no_op_initializer_lib", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/scp/cc/cpio/client_providers/cloud_initializer/src/azure/BUILD.bazel b/scp/cc/cpio/client_providers/cloud_initializer/src/azure/BUILD.bazel new file mode 100644 index 000000000..201c70793 --- /dev/null +++ b/scp/cc/cpio/client_providers/cloud_initializer/src/azure/BUILD.bazel @@ -0,0 +1,42 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +cc_library( + name = "no_op_initializer_lib", + srcs = [ + ":no_op_initializer_srcs", + ], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/cpio/client_providers/interface:cpio_client_providers_interface_lib", + "//scp/cc/public/core/interface:execution_result", + ], +) + +exports_files([ + "no_op_initializer.h", + "no_op_initializer.cc", +]) + +filegroup( + name = "no_op_initializer_srcs", + srcs = [ + ":no_op_initializer.cc", + ":no_op_initializer.h", + ], +) diff --git a/scp/cc/cpio/client_providers/cloud_initializer/src/azure/no_op_initializer.cc b/scp/cc/cpio/client_providers/cloud_initializer/src/azure/no_op_initializer.cc new file mode 100644 index 000000000..269e2ff10 --- /dev/null +++ b/scp/cc/cpio/client_providers/cloud_initializer/src/azure/no_op_initializer.cc @@ -0,0 +1,46 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "no_op_initializer.h" + +#include + +#include "public/core/interface/execution_result.h" + +using google::scp::core::ExecutionResult; +using google::scp::core::SuccessExecutionResult; + +namespace google::scp::cpio::client_providers { +ExecutionResult NoOpInitializer::Init() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult NoOpInitializer::Run() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult NoOpInitializer::Stop() noexcept { + return SuccessExecutionResult(); +} + +void NoOpInitializer::InitCloud() noexcept {} + +void NoOpInitializer::ShutdownCloud() noexcept {} + +std::shared_ptr CloudInitializerFactory::Create() { + return std::make_shared(); +} +} // namespace google::scp::cpio::client_providers diff --git a/scp/cc/cpio/client_providers/cloud_initializer/src/azure/no_op_initializer.h b/scp/cc/cpio/client_providers/cloud_initializer/src/azure/no_op_initializer.h new file mode 100644 index 000000000..3eff5062c --- /dev/null +++ b/scp/cc/cpio/client_providers/cloud_initializer/src/azure/no_op_initializer.h @@ -0,0 +1,40 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_CLOUD_INITIALIZER_SRC_GCP_NO_OP_INITIALIZER_H_ +#define CPIO_CLIENT_PROVIDERS_CLOUD_INITIALIZER_SRC_GCP_NO_OP_INITIALIZER_H_ + +#include "cpio/client_providers/interface/cloud_initializer_interface.h" +#include "public/core/interface/execution_result.h" + +namespace google::scp::cpio::client_providers { +/*! @copydoc CloudInitializerInterface + */ +class NoOpInitializer : public CloudInitializerInterface { + public: + core::ExecutionResult Init() noexcept override; + + core::ExecutionResult Run() noexcept override; + + core::ExecutionResult Stop() noexcept override; + + void InitCloud() noexcept override; + + void ShutdownCloud() noexcept override; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_CLOUD_INITIALIZER_SRC_GCP_NO_OP_INITIALIZER_H_ diff --git a/scp/cc/cpio/client_providers/instance_client_provider/src/BUILD.bazel b/scp/cc/cpio/client_providers/instance_client_provider/src/BUILD.bazel index 60dccf19d..37d9782a9 100644 --- a/scp/cc/cpio/client_providers/instance_client_provider/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/instance_client_provider/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//scp/cc/cpio/client_providers/instance_client_provider/src/aws:aws_instance_client_provider_lib", ], + "//:azure_platform": [ + "//scp/cc/cpio/client_providers/instance_client_provider/src/azure:azure_instance_client_provider_lib", + ], "//:gcp_platform": [ "//scp/cc/cpio/client_providers/instance_client_provider/src/gcp:gcp_instance_client_provider_lib", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/scp/cc/cpio/client_providers/instance_client_provider/src/azure/BUILD.bazel b/scp/cc/cpio/client_providers/instance_client_provider/src/azure/BUILD.bazel new file mode 100644 index 000000000..a880b6fa4 --- /dev/null +++ b/scp/cc/cpio/client_providers/instance_client_provider/src/azure/BUILD.bazel @@ -0,0 +1,45 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +exports_files([ + "azure_instance_client_provider.h", + "azure_instance_client_provider.cc", +]) + +filegroup( + name = "azure_instance_client_provider_srcs", + srcs = [ + ":azure_instance_client_provider.cc", + ":azure_instance_client_provider.h", + ], +) + +cc_library( + name = "azure_instance_client_provider_lib", + srcs = [ + "azure_instance_client_provider.cc", + "azure_instance_client_provider.h", + ], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/interface:interface_lib", + "//scp/cc/cpio/client_providers/interface:cpio_client_providers_interface_lib", + + "@com_google_absl//absl/log:check", + ], +) diff --git a/scp/cc/cpio/client_providers/instance_client_provider/src/azure/azure_instance_client_provider.cc b/scp/cc/cpio/client_providers/instance_client_provider/src/azure/azure_instance_client_provider.cc new file mode 100644 index 000000000..b3f79d6f4 --- /dev/null +++ b/scp/cc/cpio/client_providers/instance_client_provider/src/azure/azure_instance_client_provider.cc @@ -0,0 +1,149 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_instance_client_provider.h" + +#include "absl/log/check.h" +#include "scp/cc/core/interface/errors.h" + +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameResponse; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::InstanceDetails; +using google::cmrt::sdk::instance_service::v1:: + ListInstanceDetailsByEnvironmentRequest; +using google::cmrt::sdk::instance_service::v1:: + ListInstanceDetailsByEnvironmentResponse; +using google::scp::core::AsyncContext; +using google::scp::core::AsyncExecutorInterface; +using google::scp::core::ExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::FailureExecutionResult; + +namespace google::scp::cpio::client_providers { + +inline constexpr char kOperatorTagName[] = "operator"; +inline constexpr char kEnvironmentTagName[] = "environment"; +inline constexpr char kServiceTagName[] = "service"; +// Dummy values +inline constexpr char kResourceNameValue[] = "azure_instance_resource_name"; +inline constexpr char kInstanceId[] = "azure_instance_id"; +inline constexpr char kOperatorTagValue[] = "azure_operator"; +inline constexpr char kEnvironmentTagValue[] = "azure_environment"; +inline constexpr char kServiceTagValue[] = "azure_service"; + +AzureInstanceClientProvider::AzureInstanceClientProvider() {} + +ExecutionResult AzureInstanceClientProvider::Init() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureInstanceClientProvider::Run() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureInstanceClientProvider::Stop() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureInstanceClientProvider::GetCurrentInstanceResourceNameSync( + std::string& resource_name) noexcept { + // Not implemented. + return FailureExecutionResult(SC_UNKNOWN); +} + +ExecutionResult AzureInstanceClientProvider::GetCurrentInstanceResourceName( + AsyncContext& + get_resource_name_context) noexcept { + get_resource_name_context.response = + std::make_shared(); + // We need to figure out what we should return here. + get_resource_name_context.response->set_instance_resource_name(kResourceNameValue); + get_resource_name_context.result = SuccessExecutionResult(); + get_resource_name_context.Finish(); + return SuccessExecutionResult(); +} + +ExecutionResult AzureInstanceClientProvider::GetTagsByResourceName( + AsyncContext& + get_tags_context) noexcept { + // Not implemented. + return FailureExecutionResult(SC_UNKNOWN); +} + +ExecutionResult AzureInstanceClientProvider::GetInstanceDetailsByResourceNameSync( + const std::string& resource_name, + cmrt::sdk::instance_service::v1::InstanceDetails& + instance_details) noexcept { + + // Not implemented. + return FailureExecutionResult(SC_UNKNOWN); +} + +ExecutionResult AzureInstanceClientProvider::GetInstanceDetailsByResourceName( + AsyncContext& + get_instance_details_context) noexcept { + get_instance_details_context.response = + std::make_shared(); + // We need to igure out what we should return here. + InstanceDetails instance_details; + instance_details.set_instance_id(kInstanceId); + + // We need to provide network info here. + // auto* network = instance_details.add_networks(); + // network->set_private_ipv4_address(std::move(private_ip)); + // network->set_public_ipv4_address(std::move(public_ip)); + + auto& labels_proto = *instance_details.mutable_labels(); + labels_proto[kOperatorTagName] = kOperatorTagValue; + labels_proto[kEnvironmentTagName] = kEnvironmentTagValue; + labels_proto[kServiceTagName] = kServiceTagValue; + + *(get_instance_details_context.response->mutable_instance_details()) = instance_details; + get_instance_details_context.result = SuccessExecutionResult(); + get_instance_details_context.Finish(); + return SuccessExecutionResult(); +} + +ExecutionResult AzureInstanceClientProvider::ListInstanceDetailsByEnvironment( + AsyncContext& + get_instance_details_context) noexcept { + // Not implemented. + return FailureExecutionResult(SC_UNKNOWN); +} + +std::shared_ptr +InstanceClientProviderFactory::Create( + const std::shared_ptr& auth_token_provider, + const std::shared_ptr& http1_client, + const std::shared_ptr& http2_client, + const std::shared_ptr& async_executor, + const std::shared_ptr& io_async_executor) { + return std::make_shared(); +} + +} // namespace google::scp::cpio::client_providers diff --git a/scp/cc/cpio/client_providers/instance_client_provider/src/azure/azure_instance_client_provider.h b/scp/cc/cpio/client_providers/instance_client_provider/src/azure/azure_instance_client_provider.h new file mode 100644 index 000000000..d4b8ec328 --- /dev/null +++ b/scp/cc/cpio/client_providers/instance_client_provider/src/azure/azure_instance_client_provider.h @@ -0,0 +1,77 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_INSTANCE_CLIENT_PROVIDER_SRC_AZURE_AZURE_INSTANCE_CLIENT_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_INSTANCE_CLIENT_PROVIDER_SRC_AZURE_AZURE_INSTANCE_CLIENT_PROVIDER_H_ + +#include +#include +#include +#include + +#include "core/interface/http_client_interface.h" +#include "cpio/client_providers/interface/instance_client_provider_interface.h" + +namespace google::scp::cpio::client_providers { +// Returns dummy values currently. +class AzureInstanceClientProvider : public InstanceClientProviderInterface { + public: + AzureInstanceClientProvider(); + + core::ExecutionResult Init() noexcept override; + + core::ExecutionResult Run() noexcept override; + + core::ExecutionResult Stop() noexcept override; + + core::ExecutionResult GetCurrentInstanceResourceName( + core::AsyncContext& + context) noexcept override; + + core::ExecutionResult GetTagsByResourceName( + core::AsyncContext< + cmrt::sdk::instance_service::v1::GetTagsByResourceNameRequest, + cmrt::sdk::instance_service::v1::GetTagsByResourceNameResponse>& + context) noexcept override; + + core::ExecutionResult GetInstanceDetailsByResourceName( + core::AsyncContext& + context) noexcept override; + + core::ExecutionResult ListInstanceDetailsByEnvironment( + core::AsyncContext& + context) noexcept override; + + core::ExecutionResult GetCurrentInstanceResourceNameSync( + std::string& resource_name) noexcept override; + + core::ExecutionResult GetInstanceDetailsByResourceNameSync( + const std::string& resource_name, + cmrt::sdk::instance_service::v1::InstanceDetails& + instance_details) noexcept override; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_INSTANCE_CLIENT_PROVIDER_SRC_AZURE_AZURE_INSTANCE_CLIENT_PROVIDER_H_ diff --git a/scp/cc/cpio/client_providers/instance_client_provider/test/azure/BUILD.bazel b/scp/cc/cpio/client_providers/instance_client_provider/test/azure/BUILD.bazel new file mode 100644 index 000000000..24a68cd92 --- /dev/null +++ b/scp/cc/cpio/client_providers/instance_client_provider/test/azure/BUILD.bazel @@ -0,0 +1,36 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +cc_test( + name = "azure_instance_client_provider_test", + size = "small", + srcs = + [ + "azure_instance_client_provider_test.cc", + ], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/curl_client/mock:mock_curl_client", + "//scp/cc/core/interface:interface_lib", + "//scp/cc/cpio/client_providers/auth_token_provider/mock:auth_token_provider_mock", + "//scp/cc/cpio/client_providers/instance_client_provider/src/azure:azure_instance_client_provider_lib", + "//scp/cc/public/core/test/interface:execution_result_matchers", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/scp/cc/cpio/client_providers/instance_client_provider/test/azure/azure_instance_client_provider_test.cc b/scp/cc/cpio/client_providers/instance_client_provider/test/azure/azure_instance_client_provider_test.cc new file mode 100644 index 000000000..ca1ae4fae --- /dev/null +++ b/scp/cc/cpio/client_providers/instance_client_provider/test/azure/azure_instance_client_provider_test.cc @@ -0,0 +1,150 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cpio/client_providers/instance_client_provider/src/azure/azure_instance_client_provider.h" + +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/notification.h" +#include "public/core/test/interface/execution_result_matchers.h" + +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameResponse; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::InstanceDetails; +using google::scp::core::AsyncContext; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::ResultIs; +using google::scp::cpio::client_providers::AzureInstanceClientProvider; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::Pair; +using ::testing::Pointee; +using ::testing::Return; +using ::testing::SizeIs; +using ::testing::StrEq; +using ::testing::UnorderedElementsAre; + +namespace { +inline constexpr char kOperatorTagName[] = "operator"; +inline constexpr char kEnvironmentTagName[] = "environment"; +inline constexpr char kServiceTagName[] = "service"; +constexpr char kInstanceResourceName[] = + "azure_instance_resource_name"; +constexpr char kInstanceId[] = "azure_instance_id"; +constexpr char kOperatorTagValue[] = "azure_operator"; +constexpr char kEnvironmentTagValue[] = "azure_environment"; +constexpr char kServiceTagValue[] = "azure_service"; +} // namespace + +namespace google::scp::cpio::client_providers::test { +class AzureInstanceClientProviderTest : public testing::Test { + protected: + AzureInstanceClientProviderTest() + : instance_provider_(std::make_unique()) { + EXPECT_SUCCESS(instance_provider_->Init()); + EXPECT_SUCCESS(instance_provider_->Run()); + } + + ~AzureInstanceClientProviderTest() { + EXPECT_SUCCESS(instance_provider_->Stop()); + } + + std::unique_ptr instance_provider_; +}; + +TEST_F(AzureInstanceClientProviderTest, GetCurrentInstanceResourceNameSyncNotImplemented) { + std::string resource_name; + EXPECT_THAT(instance_provider_->GetCurrentInstanceResourceNameSync(resource_name), ResultIs(FailureExecutionResult(SC_UNKNOWN))); +} + +TEST_F(AzureInstanceClientProviderTest, GetCurrentInstanceResourceName) { + // Currently it returns a hard coded value. + absl::Notification done; + AsyncContext + context( + std::make_shared(), + [&](AsyncContext& context) { + ASSERT_SUCCESS(context.result); + EXPECT_THAT( + context.response->instance_resource_name(), + StrEq(kInstanceResourceName)); + done.Notify(); + }); + + EXPECT_THAT(instance_provider_->GetCurrentInstanceResourceName(context), + IsSuccessful()); + done.WaitForNotification(); +} + +TEST_F(AzureInstanceClientProviderTest, GetInstanceDetailsSyncNotImplemented) { + InstanceDetails details; + EXPECT_THAT(instance_provider_->GetInstanceDetailsByResourceNameSync( + kInstanceResourceName, details), + ResultIs(FailureExecutionResult(SC_UNKNOWN))); +} + +TEST_F(AzureInstanceClientProviderTest, GetInstanceDetailsSuccess) { + absl::Notification done; + AsyncContext + context( + std::make_shared(), + [&](AsyncContext& context) { + ASSERT_SUCCESS(context.result); + const auto& details = context.response->instance_details(); + EXPECT_THAT(details.instance_id(),StrEq(kInstanceId)); + // We need to update implementation to return networks. + EXPECT_THAT(details.networks(), SizeIs(0)); + EXPECT_THAT(details.labels(), + UnorderedElementsAre(Pair(kOperatorTagName, kOperatorTagValue), + Pair(kEnvironmentTagName, kEnvironmentTagValue), + Pair(kServiceTagName, kServiceTagValue))); + done.Notify(); + }); + + EXPECT_THAT(instance_provider_->GetInstanceDetailsByResourceName(context), + IsSuccessful()); + done.WaitForNotification(); +} + +TEST_F(AzureInstanceClientProviderTest, GetTagsByResourceNameNotImplemented) { + AsyncContext + context(std::make_shared(), + [&](AsyncContext& context) { + }); + + EXPECT_THAT(instance_provider_->GetTagsByResourceName(context), + ResultIs(FailureExecutionResult(SC_UNKNOWN))); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/scp/cc/cpio/client_providers/kms_client_provider/src/BUILD.bazel b/scp/cc/cpio/client_providers/kms_client_provider/src/BUILD.bazel index f9a25929b..e713a5ded 100644 --- a/scp/cc/cpio/client_providers/kms_client_provider/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/kms_client_provider/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -27,6 +28,13 @@ cc_library( "//scp/cc/public/cpio/interface:aws_cpio_lib_outside_tee": [ "//scp/cc/cpio/client_providers/kms_client_provider/src/aws:nontee_aws_kms_client_provider_lib", ], + # We should split implementation here. Currently it uses fake attestation silently when it's outside TEE. + "//scp/cc/public/cpio/interface:azure_cpio_lib_inside_tee": [ + "//scp/cc/cpio/client_providers/kms_client_provider/src/azure:azure_kms_client_provider_lib", + ], + "//scp/cc/public/cpio/interface:azure_cpio_lib_outside_tee": [ + "//scp/cc/cpio/client_providers/kms_client_provider/src/azure:azure_kms_client_provider_lib", + ], "//scp/cc/public/cpio/interface:gcp_cpio_lib_inside_tee": [ "//scp/cc/cpio/client_providers/kms_client_provider/src/gcp:tee_gcp_kms_client_provider_lib", ], @@ -34,6 +42,6 @@ cc_library( "//scp/cc/cpio/client_providers/kms_client_provider/src/gcp:nontee_gcp_kms_client_provider_lib", ], }, - no_match_error = "Please build for AWS or GCP, inside TEE or outside TEE", + no_match_error = "Please build for AWS, Azure or GCP, inside TEE or outside TEE", ), ) diff --git a/scp/cc/cpio/client_providers/kms_client_provider/src/azure/BUILD.bazel b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/BUILD.bazel new file mode 100644 index 000000000..b020f3e77 --- /dev/null +++ b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/BUILD.bazel @@ -0,0 +1,59 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility=["//scp/cc:scp_internal_pkg"]) + +cc_library( + name="azure_kms_client_provider_lib", + srcs=[ + ":azure_kms_client_provider_srcs", + ], + copts=[ + "-std=c++17", + ], + deps=[ + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/common/uuid/src:uuid_lib", + "//scp/cc/core/interface:async_context_lib", + "//scp/cc/core/interface:interface_lib", + "//scp/cc/core/utils/src:core_utils", + "//scp/cc/cpio/client_providers/interface:cpio_client_providers_interface_lib", + "//scp/cc/cpio/client_providers/global_cpio/src:global_cpio_lib", + "//scp/cc/public/cpio/interface:cpio_errors", + "//scp/cc/public/cpio/interface/kms_client:type_def", + "@com_github_googleapis_google_cloud_cpp//:kms", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/log:check", + "@nlohmann_json//:lib", + "@tink_cc", + "//scp/cc/azure/attestation:aci_attestation_lib", + ], +) + +exports_files( + [ + "azure_kms_client_provider.cc", + "azure_kms_client_provider.h", + "error_codes.h", + ] +) + +filegroup( + name="azure_kms_client_provider_srcs", + srcs=[ + ":azure_kms_client_provider.cc", + ":azure_kms_client_provider.h", + ":error_codes.h", + ], +) diff --git a/scp/cc/cpio/client_providers/kms_client_provider/src/azure/azure_kms_client_provider.cc b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/azure_kms_client_provider.cc new file mode 100644 index 000000000..f15bac0fe --- /dev/null +++ b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/azure_kms_client_provider.cc @@ -0,0 +1,179 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_kms_client_provider.h" + +#include + +#include "cpio/client_providers/interface/kms_client_provider_interface.h" +#include "cpio/client_providers/global_cpio/src/global_cpio.h" +#include "public/cpio/interface/kms_client/type_def.h" +#include "absl/strings/escaping.h" +#include "absl/log/check.h" + +#include "error_codes.h" + +using google::cmrt::sdk::kms_service::v1::DecryptRequest; +using google::cmrt::sdk::kms_service::v1::DecryptResponse; +using google::scp::core::AsyncExecutorInterface; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::RetryExecutionResult; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND; +using google::scp::core::errors::SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND; +using google::scp::core::errors::SC_AZURE_KMS_CLIENT_PROVIDER_BAD_UNWRAPPED_KEY; +using google::scp::core::AsyncContext; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::Uri; +using std::make_shared; +using std::shared_ptr; +using std::all_of; +using std::bind; +using std::cbegin; +using std::cend; +using std::placeholders::_1; +using std::make_pair; +using std::pair; + +namespace google::scp::cpio::client_providers { + +static constexpr char kAzureKmsClientProvider[] = "AzureKmsClientProvider"; + +// We need to take this value from a command line option (It already exists somewhere). +constexpr char kKMSUnwrapPath[] = + "https://127.0.0.1:8000/app/unwrapKey?fmt=tink"; + +ExecutionResult AzureKmsClientProvider::Init() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureKmsClientProvider::Run() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureKmsClientProvider::Stop() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureKmsClientProvider::Decrypt( + core::AsyncContext& + decrypt_context) noexcept { + const auto& ciphertext = decrypt_context.request->ciphertext(); + if (ciphertext.empty()) { + auto execution_result = FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND); + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, + "Failed to get cipher text from decryption request."); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return decrypt_context.result; + } + + // Check that there is an ID for the key to decrypt with + const auto& key_id = decrypt_context.request->key_resource_name(); + if (key_id.empty()) { + auto execution_result = + FailureExecutionResult(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND); + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, + "Failed to get Key ID from decryption request."); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return decrypt_context.result; + } + + AsyncContext http_context; + http_context.request = std::make_shared(); + + http_context.request->path = std::make_shared(kKMSUnwrapPath); + http_context.request->method = HttpMethod::POST; + + // Get Attestation Report + const auto report = hasSnp() ? fetchSnpAttestation() : fetchFakeSnpAttestation(); + + nlohmann::json payload; + payload["wrapped"] = ciphertext; + payload["kid"] = key_id; + payload["attestation"] = report; + + http_context.request->body = core::BytesBuffer(nlohmann::to_string(payload)); + + http_context.callback = bind(&AzureKmsClientProvider::OnDecryptCallback, + this, decrypt_context, _1); + + auto execution_result = http_client_->PerformRequest(http_context); + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, + "Failed to perform http request to decrypt wrapped key."); + + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return execution_result; + } + + return SuccessExecutionResult(); +} + +void AzureKmsClientProvider::OnDecryptCallback( + AsyncContext& + decrypt_context, + AsyncContext& http_client_context) noexcept { + if (!http_client_context.result.Successful()) { + SCP_ERROR_CONTEXT( + kAzureKmsClientProvider, decrypt_context, http_client_context.result, + "Failed to decrypt wrapped key using Azure KMS"); + + decrypt_context.result = http_client_context.result; + decrypt_context.Finish(); + return; + } + + std::string resp(http_client_context.response->body.bytes->begin(), + http_client_context.response->body.bytes->end()); + + decrypt_context.response = std::make_shared(); + + decrypt_context.response->set_plaintext(resp); + + decrypt_context.result = SuccessExecutionResult(); + decrypt_context.Finish(); +} + +#ifndef TEST_CPIO +shared_ptr KmsClientProviderFactory::Create( + const shared_ptr& options, + const shared_ptr& + role_credentials_provider, + const shared_ptr& io_async_executor) noexcept { + // We uses GlobalCpio::GetGlobalCpio()->GetHttpClient() to get http_client object instead of + // adding it to KmsClientProviderFactory::Create() as a new parameter. + // This is to prevent the existing GCP and AWS implementations from being changed. + std::shared_ptr http_client; + auto execution_result = + GlobalCpio::GetGlobalCpio()->GetHttpClient(http_client); + CHECK(execution_result.Successful()) << "failed to get http client"; + return make_shared(http_client); +} +#endif +} // namespace google::scp::cpio::client_providers \ No newline at end of file diff --git a/scp/cc/cpio/client_providers/kms_client_provider/src/azure/azure_kms_client_provider.h b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/azure_kms_client_provider.h new file mode 100644 index 000000000..a4023ec0e --- /dev/null +++ b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/azure_kms_client_provider.h @@ -0,0 +1,73 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_SRC_AZURE_AZURE_KMS_CLIENT_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_SRC_AZURE_AZURE_KMS_CLIENT_PROVIDER_H_ + +#include +#include + +#include + +#include "core/interface/async_context.h" +#include "cpio/client_providers/interface/kms_client_provider_interface.h" +#include "public/core/interface/execution_result.h" +#include "azure/attestation/json_attestation_report.h" + +namespace google::scp::cpio::client_providers { + +/*! @copydoc KmsClientProviderInterface + */ +class AzureKmsClientProvider : public KmsClientProviderInterface { + public: + + explicit AzureKmsClientProvider( + const std::shared_ptr& + http_client) + : http_client_(http_client) {} + + core::ExecutionResult Init() noexcept override; + + core::ExecutionResult Run() noexcept override; + + core::ExecutionResult Stop() noexcept override; + + core::ExecutionResult Decrypt( + core::AsyncContext& + decrypt_context) noexcept override; + + private: + + /** + * @brief Is called when the decrypt operation + * is completed. + * + * @param decrypt_context The context of the decrypt operation. + * @param http_client_context http client operation context. + */ + void OnDecryptCallback( + core::AsyncContext& + decrypt_context, + core::AsyncContext& + http_client_context) noexcept; + + std::shared_ptr http_client_; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_SRC_AZURE_AZURE_KMS_CLIENT_PROVIDER_H_ diff --git a/scp/cc/cpio/client_providers/kms_client_provider/src/azure/error_codes.h b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/error_codes.h new file mode 100644 index 000000000..02c75a19c --- /dev/null +++ b/scp/cc/cpio/client_providers/kms_client_provider/src/azure/error_codes.h @@ -0,0 +1,50 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_SRC_AZURE_ERROR_CODES_H_ +#define CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_SRC_AZURE_ERROR_CODES_H_ + +#include "core/interface/errors.h" +#include "public/cpio/interface/error_codes.h" + +namespace google::scp::core::errors { +REGISTER_COMPONENT_CODE(SC_AZURE_KMS_CLIENT_PROVIDER, 0x022C) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0001, + "Cannot find cipher text", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0002, + "Cannot find decryption Key ID", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_BAD_UNWRAPPED_KEY, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0003, + "Unwrapped key is malformed.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND, + SC_CPIO_INVALID_REQUEST) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND, + SC_CPIO_INVALID_REQUEST) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_BAD_UNWRAPPED_KEY, + SC_CPIO_INVALID_RESOURCE) + +} // namespace google::scp::core::errors + +#endif // CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_SRC_AZURE_ERROR_CODES_H_ diff --git a/scp/cc/cpio/client_providers/kms_client_provider/test/azure/BUILD.bazel b/scp/cc/cpio/client_providers/kms_client_provider/test/azure/BUILD.bazel new file mode 100644 index 000000000..299c44c30 --- /dev/null +++ b/scp/cc/cpio/client_providers/kms_client_provider/test/azure/BUILD.bazel @@ -0,0 +1,34 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_test") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +cc_test( + name = "azure_kms_client_provider_test", + size = "small", + srcs = + ["azure_kms_client_provider_test.cc"], + deps = [ + "//scp/cc/core/curl_client/mock:mock_curl_client", + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/interface:interface_lib", + "//scp/cc/core/test/utils:utils_lib", + "//scp/cc/cpio/client_providers/kms_client_provider/src/azure:azure_kms_client_provider_lib", + "//scp/cc/public/core/test/interface:execution_result_matchers", + "@com_github_googleapis_google_cloud_cpp//:kms", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/scp/cc/cpio/client_providers/kms_client_provider/test/azure/azure_kms_client_provider_test.cc b/scp/cc/cpio/client_providers/kms_client_provider/test/azure/azure_kms_client_provider_test.cc new file mode 100644 index 000000000..87f33fdb9 --- /dev/null +++ b/scp/cc/cpio/client_providers/kms_client_provider/test/azure/azure_kms_client_provider_test.cc @@ -0,0 +1,195 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cpio/client_providers/kms_client_provider/src/azure/azure_kms_client_provider.h" + +#include +#include +#include + +#include +#include + +#include "core/curl_client/mock/mock_curl_client.h" +#include "core/interface/async_context.h" +#include "absl/synchronization/notification.h" +#include "core/utils/src/base64.h" +#include "public/core/test/interface/execution_result_matchers.h" +#include "cpio/client_providers/kms_client_provider/src/azure/error_codes.h" + +using google::scp::core::BytesBuffer; +using google::cmrt::sdk::kms_service::v1::DecryptRequest; +using google::cmrt::sdk::kms_service::v1::DecryptResponse; +using google::scp::core::AsyncContext; +using google::scp::core::FailureExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::errors::SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND; +using google::scp::core::errors::SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::ResultIs; +using google::scp::core::utils::Base64Encode; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using std::atomic; +using testing::Eq; +using testing::Return; +using testing::Pointee; +using google::scp::core::test::MockCurlClient; + +static constexpr char kServiceAccount[] = "account"; +static constexpr char kWipProvider[] = "wip"; +static constexpr char kKeyId[] = "keyId"; +static constexpr char kCiphertext[] = "ciphertext"; +static constexpr char kPlaintext[] = "plaintext"; +static constexpr char kKmsUnwrapPath[] = + "https://127.0.0.1:8000/app/unwrapKey?fmt=tink"; + +namespace google::scp::cpio::client_providers::test { + +class AzureKmsClientProviderTest : public ::testing::Test { + protected: + void SetUp() override { + http_client_ = std::make_shared(); + client_ = std::make_unique(http_client_); + } + + void TearDown() override { + + } + + std::shared_ptr http_client_; + std::unique_ptr client_; +}; + +TEST_F(AzureKmsClientProviderTest, NullKeyId) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_ciphertext(kCiphertext); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) {}); + + EXPECT_THAT(client_->Decrypt(context), + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND))); +} + +TEST_F(AzureKmsClientProviderTest, EmptyKeyArn) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(""); + kms_decrpyt_request->set_ciphertext(kCiphertext); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) {}); + + EXPECT_THAT(client_->Decrypt(context), + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND))); +} + +TEST_F(AzureKmsClientProviderTest, NullCiphertext) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) {}); + + EXPECT_THAT(client_->Decrypt(context), + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND))); +} + +TEST_F(AzureKmsClientProviderTest, EmptyCiphertext) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + kms_decrpyt_request->set_ciphertext(""); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) {}); + + EXPECT_THAT(client_->Decrypt(context), + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND))); +} + +TEST_F(AzureKmsClientProviderTest, SuccessToDecrypt) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + kms_decrpyt_request->set_ciphertext(kCiphertext); + kms_decrpyt_request->set_account_identity(kServiceAccount); + kms_decrpyt_request->set_gcp_wip_provider(kWipProvider); + + EXPECT_CALL(*http_client_, PerformRequest).WillOnce([](auto& http_context) { + http_context.result = SuccessExecutionResult(); + EXPECT_EQ(http_context.request->method, HttpMethod::POST); + EXPECT_THAT(http_context.request->path, Pointee(Eq(kKmsUnwrapPath))); + std::string payload(http_context.request->body.bytes->begin(), + http_context.request->body.bytes->end()); + nlohmann::json json_payload = nlohmann::json::parse(payload); + EXPECT_EQ(json_payload["wrapped"], kCiphertext); + EXPECT_EQ(json_payload["kid"], kKeyId); + EXPECT_TRUE(json_payload["attestation"].is_object()); + + http_context.response = std::make_shared(); + http_context.response->body = BytesBuffer(kPlaintext); + http_context.Finish(); + return SuccessExecutionResult(); + }); + + absl::Notification condition; + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_SUCCESS(context.result); + EXPECT_EQ(context.response->plaintext(), kPlaintext); + condition.Notify(); + }); + + EXPECT_SUCCESS(client_->Decrypt(context)); + + condition.WaitForNotification(); +} + +TEST_F(AzureKmsClientProviderTest, FailedToDecrypt) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + kms_decrpyt_request->set_ciphertext(kCiphertext); + kms_decrpyt_request->set_account_identity(kServiceAccount); + kms_decrpyt_request->set_gcp_wip_provider(kWipProvider); + + EXPECT_CALL(*http_client_, PerformRequest).WillOnce([](auto& http_context) { + http_context.result = FailureExecutionResult(SC_UNKNOWN); + http_context.Finish(); + return FailureExecutionResult(SC_UNKNOWN); + }); + + absl::Notification condition; + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, ResultIs(FailureExecutionResult(SC_UNKNOWN))); + condition.Notify(); + }); + + EXPECT_THAT(client_->Decrypt(context), ResultIs(FailureExecutionResult(SC_UNKNOWN))); + + condition.WaitForNotification(); +} +} // namespace google::scp::cpio::client_providers::test diff --git a/scp/cc/cpio/client_providers/parameter_client_provider/src/BUILD.bazel b/scp/cc/cpio/client_providers/parameter_client_provider/src/BUILD.bazel index 440051006..ce78bbc5f 100644 --- a/scp/cc/cpio/client_providers/parameter_client_provider/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/parameter_client_provider/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//scp/cc/cpio/client_providers/parameter_client_provider/src/aws:aws_parameter_client_provider_lib", ], + "//:azure_platform": [ + "//scp/cc/cpio/client_providers/parameter_client_provider/src/azure:azure_parameter_client_provider_lib", + ], "//:gcp_platform": [ "//scp/cc/cpio/client_providers/parameter_client_provider/src/gcp:gcp_parameter_client_provider_lib", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/BUILD.bazel b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/BUILD.bazel new file mode 100644 index 000000000..9eaad51af --- /dev/null +++ b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/BUILD.bazel @@ -0,0 +1,49 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +cc_library( + name = "azure_parameter_client_provider_lib", + srcs = [ + ":azure_parameter_client_provider_srcs", + ], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/cpio/client_providers/instance_client_provider/src/azure:azure_instance_client_provider_lib", + "//scp/cc/cpio/client_providers/interface:cpio_client_providers_interface_lib", + "//scp/cc/cpio/client_providers/interface:type_def", + "//scp/cc/public/cpio/interface:cpio_errors", + "//scp/cc/public/cpio/proto/parameter_service/v1:parameter_service_cc_proto", + "@com_github_googleapis_google_cloud_cpp//:secretmanager", + "@com_google_absl//absl/log:check", + ], +) + +exports_files([ + "azure_parameter_client_provider.h", + "azure_parameter_client_provider.cc", + "error_codes.h", +]) + +filegroup( + name = "azure_parameter_client_provider_srcs", + srcs = [ + ":error_codes.h", + ":azure_parameter_client_provider.cc", + ":azure_parameter_client_provider.h", + ], +) diff --git a/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/azure_parameter_client_provider.cc b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/azure_parameter_client_provider.cc new file mode 100644 index 000000000..8925969ff --- /dev/null +++ b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/azure_parameter_client_provider.cc @@ -0,0 +1,145 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_parameter_client_provider.h" + +#include +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/log/check.h" +#include "core/common/uuid/src/uuid.h" +#include "core/interface/async_context.h" +#include "google/cloud/secretmanager/secret_manager_client.h" +#include "google/cloud/secretmanager/secret_manager_connection.h" +#include "public/core/interface/execution_result.h" +#include "public/cpio/proto/parameter_service/v1/parameter_service.pb.h" + +#include "error_codes.h" + +using google::cloud::StatusCode; +using google::cloud::StatusOr; +using google::cloud::secretmanager::MakeSecretManagerServiceConnection; +using google::cloud::secretmanager::SecretManagerServiceClient; +using google::cloud::secretmanager::v1::AccessSecretVersionRequest; +using google::cloud::secretmanager::v1::AccessSecretVersionResponse; +using google::cmrt::sdk::parameter_service::v1::GetParameterRequest; +using google::cmrt::sdk::parameter_service::v1::GetParameterResponse; +using google::scp::core::AsyncContext; +using google::scp::core::AsyncPriority; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::common::kZeroUuid; +using google::scp::core::errors:: + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND; +using google::scp::core::errors:: + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME; +using std::bind; +using std::placeholders::_1; + +static constexpr char kAzureParameterClientProvider[] = + "AzureParameterClientProvider"; + +namespace google::scp::cpio::client_providers { +ExecutionResult AzureParameterClientProvider::Init() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureParameterClientProvider::Run() noexcept { + return SuccessExecutionResult(); +} + +ExecutionResult AzureParameterClientProvider::Stop() noexcept { + return SuccessExecutionResult(); +} + +std::shared_ptr +AzureParameterClientProvider::GetSecretManagerClient() noexcept { + return std::make_shared( + MakeSecretManagerServiceConnection()); +} + +ExecutionResult AzureParameterClientProvider::GetParameter( + AsyncContext& + get_parameter_context) noexcept { + get_parameter_context.response = std::make_shared(); + const auto& parameter_name = get_parameter_context.request->parameter_name(); + // The `parameter_name` follows the format of -, and the prefix + // consists of the values from `instance_client_provider`. Our instance client + // always returns the same dummy values for the current implementation. + // So we can just ignore the prefix for now. + const std::string prefix = "azure_operator-azure_environment-"; + + if (parameter_name.empty()) { + auto execution_result = FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME); + SCP_ERROR_CONTEXT(kAzureParameterClientProvider, get_parameter_context, + execution_result, "Failed due to an empty parameter."); + get_parameter_context.result = execution_result; + get_parameter_context.Finish(); + return execution_result; + } + + if (parameter_name.size() <= prefix.size() || parameter_name.substr(0, prefix.size()) != prefix) { + auto execution_result = FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME); + SCP_ERROR_CONTEXT(kAzureParameterClientProvider, get_parameter_context, + execution_result, "Request does not have expected prefix."); + get_parameter_context.result = execution_result; + get_parameter_context.Finish(); + return execution_result; + } + + // Example value: "BUYER_FRONTEND_PORT" + const auto& flag = parameter_name.substr(prefix.size(), parameter_name.size() - prefix.size()); + + // Get flag values from environment variables. + // We need to consider adding prefix for environment variables to avoid collision. + const char* value_from_env = std::getenv(flag.c_str()); + if (value_from_env) { + get_parameter_context.response->set_parameter_value(value_from_env); + get_parameter_context.result = SuccessExecutionResult(); + get_parameter_context.Finish(); + return SuccessExecutionResult(); + } else { + auto execution_result = FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND); + SCP_ERROR_CONTEXT(kAzureParameterClientProvider, get_parameter_context, + execution_result, + "Failed to get the parameter value for %s.", + get_parameter_context.request->parameter_name().c_str()); + get_parameter_context.result = execution_result; + get_parameter_context.Finish(); + return execution_result; + } +} + + +#ifndef TEST_CPIO +std::shared_ptr +ParameterClientProviderFactory::Create( + const std::shared_ptr& options, + const std::shared_ptr& + instance_client_provider, + const std::shared_ptr& cpu_async_executor, + const std::shared_ptr& io_async_executor) { + return std::make_shared(); +} +#endif +} // namespace google::scp::cpio::client_providers diff --git a/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/azure_parameter_client_provider.h b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/azure_parameter_client_provider.h new file mode 100644 index 000000000..eb758804d --- /dev/null +++ b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/azure_parameter_client_provider.h @@ -0,0 +1,64 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_SRC_AZURE_AZURE_PARAMETER_CLIENT_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_SRC_AZURE_AZURE_PARAMETER_CLIENT_PROVIDER_H_ + +#include +#include +#include + +#include "core/interface/async_context.h" +#include "core/interface/async_executor_interface.h" +#include "cpio/client_providers/interface/instance_client_provider_interface.h" +#include "cpio/client_providers/interface/parameter_client_provider_interface.h" +#include "google/cloud/secretmanager/secret_manager_client.h" +#include "public/core/interface/execution_result.h" +#include "public/cpio/proto/parameter_service/v1/parameter_service.pb.h" + +#include "error_codes.h" + +namespace google::scp::cpio::client_providers { +/*! @copydoc ParameterClientProviderInterface + */ +class AzureParameterClientProvider : public ParameterClientProviderInterface { + public: + AzureParameterClientProvider() {} + + core::ExecutionResult Init() noexcept override; + + core::ExecutionResult Run() noexcept override; + + core::ExecutionResult Stop() noexcept override; + + core::ExecutionResult GetParameter( + core::AsyncContext< + cmrt::sdk::parameter_service::v1::GetParameterRequest, + cmrt::sdk::parameter_service::v1::GetParameterResponse>& + get_parameter_context) noexcept override; + + protected: + /** + * @brief Get the default Secret Manager Service Client object. + * + * @return std::shared_ptr + */ + virtual std::shared_ptr + GetSecretManagerClient() noexcept; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_SRC_AZURE_AZURE_PARAMETER_CLIENT_PROVIDER_H_ diff --git a/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/error_codes.h b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/error_codes.h new file mode 100644 index 000000000..9d547f122 --- /dev/null +++ b/scp/cc/cpio/client_providers/parameter_client_provider/src/azure/error_codes.h @@ -0,0 +1,42 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_SRC_AZURE_ERROR_CODES_H_ +#define CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_SRC_AZURE_ERROR_CODES_H_ + +#include "core/interface/errors.h" +#include "public/core/interface/execution_result.h" +#include "public/cpio/interface/error_codes.h" + +namespace google::scp::core::errors { +/// Registers component code as 0x0208 for AZURE parameter client. +REGISTER_COMPONENT_CODE(SC_AZURE_PARAMETER_CLIENT_PROVIDER, 0x0208) + +DEFINE_ERROR_CODE(SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND, + SC_AZURE_PARAMETER_CLIENT_PROVIDER, 0x0001, + "Parameter is not found", HttpStatusCode::BAD_REQUEST) +DEFINE_ERROR_CODE(SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME, + SC_AZURE_PARAMETER_CLIENT_PROVIDER, 0x0003, + "Parameter name is invalid", HttpStatusCode::BAD_REQUEST) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND, + SC_CPIO_INTERNAL_ERROR) +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME, + SC_CPIO_INVALID_REQUEST) + +} // namespace google::scp::core::errors + +#endif // CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_SRC_AZURE_ERROR_CODES_H_ diff --git a/scp/cc/cpio/client_providers/parameter_client_provider/test/azure/BUILD.bazel b/scp/cc/cpio/client_providers/parameter_client_provider/test/azure/BUILD.bazel new file mode 100644 index 000000000..835cc0056 --- /dev/null +++ b/scp/cc/cpio/client_providers/parameter_client_provider/test/azure/BUILD.bazel @@ -0,0 +1,35 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_test") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +cc_test( + name = "azure_parameter_client_provider_test", + size = "small", + srcs = ["azure_parameter_client_provider_test.cc"], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/async_executor/mock:core_async_executor_mock", + "//scp/cc/core/interface:async_context_lib", + "//scp/cc/core/interface:interface_lib", + "//scp/cc/core/test/utils:utils_lib", + "//scp/cc/cpio/client_providers/parameter_client_provider/src/azure:azure_parameter_client_provider_lib", + "//scp/cc/public/core/test/interface:execution_result_matchers", + "@com_github_googleapis_google_cloud_cpp//:secretmanager_mocks", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/scp/cc/cpio/client_providers/parameter_client_provider/test/azure/azure_parameter_client_provider_test.cc b/scp/cc/cpio/client_providers/parameter_client_provider/test/azure/azure_parameter_client_provider_test.cc new file mode 100644 index 000000000..60b88ee13 --- /dev/null +++ b/scp/cc/cpio/client_providers/parameter_client_provider/test/azure/azure_parameter_client_provider_test.cc @@ -0,0 +1,131 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/synchronization/notification.h" +#include "core/async_executor/mock/mock_async_executor.h" +#include "core/interface/async_context.h" +#include "cpio/client_providers/parameter_client_provider/src/azure/azure_parameter_client_provider.h" +#include "cpio/client_providers/parameter_client_provider/src/azure/error_codes.h" +#include "public/core/interface/execution_result.h" +#include "public/core/test/interface/execution_result_matchers.h" +#include "public/cpio/proto/parameter_service/v1/parameter_service.pb.h" + +using google::scp::core::AsyncContext; +using google::scp::core::AsyncOperation; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::ResultIs; +using google::cmrt::sdk::parameter_service::v1::GetParameterRequest; +using google::cmrt::sdk::parameter_service::v1::GetParameterResponse; +using google::scp::cpio::client_providers::AzureParameterClientProvider; +using testing::Eq; +using testing::ExplainMatchResult; +using google::scp::core::errors::SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND; +using google::scp::core::errors:: + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME; + +namespace { +constexpr char kTestParameterName[] = "TEST_PARAM"; +constexpr char kTestValue[] = "test-param-value"; +constexpr char kParameterRequestPrefix[] = "azure_operator-azure_environment-"; +} // namespace + +namespace google::scp::cpio::test { +class AzureParameterClientProviderTest : public ::testing::Test { + protected: + void SetUp() override { + client_ = std::make_unique(); + + EXPECT_SUCCESS(client_->Init()); + EXPECT_SUCCESS(client_->Run()); + SetParameter(kTestParameterName, kTestValue); + } + + void TearDown() override { + EXPECT_SUCCESS(client_->Stop()); + } + + void SetParameter( + const std::string& parameter_name, const std::string& parameter_value) { + EXPECT_EQ(setenv(parameter_name.c_str(), parameter_value.c_str(), 1), 0); + } + + std::unique_ptr client_; +}; + +TEST_F(AzureParameterClientProviderTest, SucceedToFetchParameter) { + absl::Notification condition; + auto request = std::make_shared(); + const std::string parameter_with_prefix = std::string(kParameterRequestPrefix) + std::string(kTestParameterName); + request->set_parameter_name(parameter_with_prefix); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) { + EXPECT_SUCCESS(context.result); + EXPECT_EQ(context.response->parameter_value(), kTestValue); + condition.Notify(); + }); + + EXPECT_SUCCESS(client_->GetParameter(context)); + condition.WaitForNotification(); +} + +TEST_F(AzureParameterClientProviderTest, FailedToFetchParameterErrorNotFound) { + absl::Notification condition; + auto request = std::make_shared(); + const std::string parameter_with_prefix = std::string(kParameterRequestPrefix) + std::string("DO_NOT_EXIST"); + request->set_parameter_name(parameter_with_prefix); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) {}); + + EXPECT_THAT(client_->GetParameter(context), + ResultIs(FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND))); +} + +TEST_F(AzureParameterClientProviderTest, FailedWithInvalidParameterName) { + absl::Notification condition; + auto request = std::make_shared(); + request->set_parameter_name(kTestParameterName/*No correct prefix*/); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) {}); + + EXPECT_THAT(client_->GetParameter(context), + ResultIs(FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME))); +} + +TEST_F(AzureParameterClientProviderTest, + FailedToFetchParameterEmptyInput) { + absl::Notification condition; + auto request = std::make_shared(); + request->set_parameter_name(""); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) {}); + + EXPECT_THAT(client_->GetParameter(context), + ResultIs(FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME))); +} +} // namespace google::scp::cpio::test diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/BUILD.bazel b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/BUILD.bazel index dd768c936..1078b5fbd 100644 --- a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,11 +24,14 @@ cc_library( "//:aws_platform": [ "//scp/cc/cpio/client_providers/private_key_fetcher_provider/src/aws:aws_private_key_fetcher_provider_lib", ], + "//:azure_platform": [ + "//scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure:azure_private_key_fetcher_provider_lib", + ], "//:gcp_platform": [ "//scp/cc/cpio/client_providers/private_key_fetcher_provider/src/gcp:gcp_private_key_fetcher_provider_lib", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/BUILD.bazel b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/BUILD.bazel new file mode 100644 index 000000000..e1f9d318f --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/BUILD.bazel @@ -0,0 +1,51 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +cc_library( + name = "azure_private_key_fetcher_provider_lib", + srcs = [ + ":azure_private_key_fetcher_provider_srcs", + ], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/http2_client/src:http2_client_lib", + "//scp/cc/cpio/client_providers/auth_token_provider/src/gcp:gcp_auth_token_provider_lib", + "//scp/cc/cpio/client_providers/interface:cpio_client_providers_interface_lib", + "//scp/cc/cpio/client_providers/interface:type_def", + "//scp/cc/cpio/client_providers/private_key_fetcher_provider/src:private_key_fetcher_provider_lib", + "//scp/cc/public/cpio/interface/private_key_client:type_def", + "//scp/cc/azure/attestation:aci_attestation_lib", + ], +) + +exports_files([ + "error_codes.h", + "azure_private_key_fetcher_provider.cc", + "azure_private_key_fetcher_provider.h", +]) + +filegroup( + name = "azure_private_key_fetcher_provider_srcs", + srcs = [ + ":error_codes.h", + ":azure_private_key_fetcher_provider.cc", + ":azure_private_key_fetcher_provider.h", + ":azure_private_key_fetcher_provider_utils.cc", + ":azure_private_key_fetcher_provider_utils.h", + ], +) diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider.cc b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider.cc new file mode 100644 index 000000000..b777d6151 --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider.cc @@ -0,0 +1,198 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_private_key_fetcher_provider.h" +#include "azure_private_key_fetcher_provider_utils.h" + +#include +#include + +#include "absl/strings/str_cat.h" +#include "azure/attestation/json_attestation_report.h" +#include "core/interface/http_client_interface.h" +#include "cpio/client_providers/interface/auth_token_provider_interface.h" +#include "cpio/client_providers/interface/role_credentials_provider_interface.h" +#include "cpio/client_providers/private_key_fetcher_provider/src/private_key_fetcher_provider_utils.h" + +#include "error_codes.h" + +using google::scp::core::AsyncContext; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::common::kZeroUuid; +using google::scp::core::HttpMethod; +using google::scp::core::errors:: + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND; +using std::bind; +using std::placeholders::_1; + +namespace { +constexpr char kAzurePrivateKeyFetcherProvider[] = "AzurePrivateKeyFetcherProvider"; +constexpr char kAuthorizationHeaderKey[] = "Authorization"; +constexpr char kBearerTokenPrefix[] = "Bearer "; +} // namespace + +namespace google::scp::cpio::client_providers { + +ExecutionResult AzurePrivateKeyFetcherProvider::Init() noexcept { + RETURN_IF_FAILURE(PrivateKeyFetcherProvider::Init()); + + if (!auth_token_provider_) { + auto execution_result = FailureExecutionResult( + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND); + SCP_ERROR(kAzurePrivateKeyFetcherProvider, kZeroUuid, execution_result, + "Failed to get credentials provider."); + return execution_result; + } + + return SuccessExecutionResult(); +} + +ExecutionResult AzurePrivateKeyFetcherProvider::SignHttpRequest( + AsyncContext& + sign_request_context) noexcept { + auto http_request = std::make_shared(); + AzurePrivateKeyFetchingClientUtils::CreateHttpRequest( + *sign_request_context.request, *http_request); + + sign_request_context.response = std::move(http_request); + sign_request_context.result = SuccessExecutionResult(); + sign_request_context.Finish(); + return SuccessExecutionResult(); +} + +void AzurePrivateKeyFetcherProvider::OnGetSessionTokenCallback( + AsyncContext& + sign_request_context, + AsyncContext& get_token_context) noexcept {} + +ExecutionResult AzurePrivateKeyFetcherProvider::FetchPrivateKey( + AsyncContext& + private_key_fetching_context) noexcept { + AsyncContext + sign_http_request_context( + private_key_fetching_context.request, + bind(&AzurePrivateKeyFetcherProvider::SignHttpRequestCallback, this, + private_key_fetching_context, _1), + private_key_fetching_context); + + return SignHttpRequest(sign_http_request_context); +} + +void AzurePrivateKeyFetcherProvider::SignHttpRequestCallback( + AsyncContext& + private_key_fetching_context, + AsyncContext& + sign_http_request_context) noexcept { + auto execution_result = sign_http_request_context.result; + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + execution_result, "Failed to sign http request."); + private_key_fetching_context.result = execution_result; + private_key_fetching_context.Finish(); + return; + } + + AsyncContext http_client_context( + std::move(sign_http_request_context.response), + bind(&AzurePrivateKeyFetcherProvider::PrivateKeyFetchingCallback, this, + private_key_fetching_context, _1), + private_key_fetching_context); + execution_result = http_client_->PerformRequest(http_client_context); + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT( + kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + execution_result, + "Failed to perform sign http request to reach endpoint %s.", + private_key_fetching_context.request->key_vending_endpoint + ->private_key_vending_service_endpoint.c_str()); + private_key_fetching_context.result = execution_result; + private_key_fetching_context.Finish(); + } +} + +void AzurePrivateKeyFetcherProvider::PrivateKeyFetchingCallback( + AsyncContext& + private_key_fetching_context, + AsyncContext& http_client_context) noexcept { + private_key_fetching_context.result = http_client_context.result; + if (!http_client_context.result.Successful()) { + std::cout << "Private Key Fetching failed: " << http_client_context.response->body.ToString() << std::endl; + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + private_key_fetching_context.result, + "Failed to fetch private key."); + private_key_fetching_context.Finish(); + return; + } + + if (static_cast(http_client_context.response->code) == 202) { + // `OperationDispatcher` will limit number of retry and control the amount of wait before sending next request + // based on `http_client_context.retry_count` value. + // Incrementing it here might not be the expected usage of the field. + // In that case we can either: + // - Modify `HttpClient` implementation under `http2_client/` so that it retries for 202 like it already does for some other status codes + // (set `RetryExecutionResult()` to http_context.result). + // - Implement a retry mechanizm in this class without depending on `OperationDispatcher`. + http_client_context.retry_count++; + auto execution_result = http_client_->PerformRequest(http_client_context); + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT( + kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + execution_result, + "Failed to perform sign http request to reach endpoint %s.", + private_key_fetching_context.request->key_vending_endpoint + ->private_key_vending_service_endpoint.c_str()); + private_key_fetching_context.result = execution_result; + private_key_fetching_context.Finish(); + } + return; + } + + PrivateKeyFetchingResponse response; + auto result = PrivateKeyFetchingClientUtils::ParsePrivateKey( + http_client_context.response->body, response); + if (!result.Successful()) { + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + private_key_fetching_context.result, + "Failed to parse private key."); + private_key_fetching_context.result = result; + private_key_fetching_context.Finish(); + return; + } + + private_key_fetching_context.response = + std::make_shared(response); + private_key_fetching_context.Finish(); +} + +#ifndef TEST_CPIO +std::shared_ptr +PrivateKeyFetcherProviderFactory::Create( + const std::shared_ptr& http_client, + const std::shared_ptr& + role_credentials_provider, + const std::shared_ptr& auth_token_provider) { + return std::make_shared(http_client, + auth_token_provider); +} +#endif +} // namespace google::scp::cpio::client_providers diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider.h b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider.h new file mode 100644 index 000000000..8a8c2824e --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider.h @@ -0,0 +1,105 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_H_ + +#include +#include + +#include "core/interface/async_context.h" +#include "cpio/client_providers/interface/auth_token_provider_interface.h" +#include "cpio/client_providers/private_key_fetcher_provider/src/private_key_fetcher_provider.h" +#include "public/core/interface/execution_result.h" + +#include "error_codes.h" + +namespace google::scp::cpio::client_providers { +/*! @copydoc PrivateKeyFetcherProviderInterface + */ +class AzurePrivateKeyFetcherProvider : public PrivateKeyFetcherProvider { + public: + /** + * @brief Constructs a new GCP Private Key Fetching Client Provider object. + * + * @param http_client http client to issue http requests. + * @param auth_token_provider auth token provider. + * service. + */ + AzurePrivateKeyFetcherProvider( + const std::shared_ptr& http_client, + const std::shared_ptr& auth_token_provider) + : PrivateKeyFetcherProvider(http_client), + auth_token_provider_(auth_token_provider) {} + + core::ExecutionResult Init() noexcept override; + + core::ExecutionResult FetchPrivateKey( + core::AsyncContext& + private_key_fetching_context) noexcept override; + + core::ExecutionResult SignHttpRequest( + core::AsyncContext& + sign_http_request_context) noexcept override; + + protected: + + /** + * @brief Triggered to fetch private key when http request is signed. + * + * @param private_key_fetching_context context to fetch private key. + * @param sign_http_request_context context to sign http request. + * @return core::ExecutionResult execution result. + */ + void SignHttpRequestCallback( + core::AsyncContext& + private_key_fetching_context, + core::AsyncContext& + sign_http_request_context) noexcept; + + /** + * @brief Triggered to parse private key when private key payload is fetched. + * + * @param private_key_fetching_context context to fetch private key. + * @param http_client_context context to perform http request. + * @return core::ExecutionResult execution result. + */ + void PrivateKeyFetchingCallback( + core::AsyncContext& + private_key_fetching_context, + core::AsyncContext& + http_client_context) noexcept; + + private: + /** + * @brief Is called after auth_token_provider GetSessionToken() for session + * token is completed + * + * @param sign_http_request_context the context for sign http request. + * @param get_session_token the context of get session token. + */ + void OnGetSessionTokenCallback( + core::AsyncContext& + sign_http_request_context, + core::AsyncContext& get_session_token) noexcept; + + // Auth token provider. + std::shared_ptr auth_token_provider_; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_H_ diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider_utils.cc b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider_utils.cc new file mode 100644 index 000000000..9d925e44b --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider_utils.cc @@ -0,0 +1,39 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_private_key_fetcher_provider_utils.h" + +#include "azure/attestation/json_attestation_report.h" + + +using google::scp::core::HttpRequest; +using google::scp::core::Uri; +using google::scp::core::HttpMethod; + +namespace google::scp::cpio::client_providers { + +void AzurePrivateKeyFetchingClientUtils::CreateHttpRequest( + const PrivateKeyFetchingRequest& request, HttpRequest& http_request) { + const auto& base_uri = + request.key_vending_endpoint->private_key_vending_service_endpoint; + http_request.method = HttpMethod::POST; + + http_request.path = + std::make_shared(base_uri); + const auto report = hasSnp() ? fetchSnpAttestation() : fetchFakeSnpAttestation(); + http_request.body = core::BytesBuffer(report.dump()); +} +} // namespace google::scp::cpio::client_providers diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider_utils.h b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider_utils.h new file mode 100644 index 000000000..9faaf6700 --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider_utils.h @@ -0,0 +1,37 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_UTILS_H_ +#define CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_UTILS_H_ + +#include "cpio/client_providers/private_key_fetcher_provider/src/private_key_fetcher_provider.h" + +namespace google::scp::cpio::client_providers { +class AzurePrivateKeyFetchingClientUtils { + public: + /** + * @brief Create a Http Request object to query private key vending endpoint. + * + * @param private_key_fetching_request request to query private key. + * @param http_request returned http request. + */ + static void CreateHttpRequest( + const PrivateKeyFetchingRequest& private_key_fetching_request, + core::HttpRequest& http_request); +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_UTILS_H_ diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/error_codes.h b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/error_codes.h new file mode 100644 index 000000000..f53307a04 --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure/error_codes.h @@ -0,0 +1,36 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_ERROR_CODES_H_ +#define CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_ERROR_CODES_H_ + +#include "core/interface/errors.h" +#include "public/core/interface/execution_result.h" +#include "public/cpio/interface/error_codes.h" + +namespace google::scp::core::errors { + +REGISTER_COMPONENT_CODE(SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER, 0x022B) + +DEFINE_ERROR_CODE( + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND, + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER, 0x0001, + "No credentials provider found", HttpStatusCode::NOT_FOUND) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND, + SC_CPIO_COMPONENT_FAILED_INITIALIZED) +} // namespace google::scp::core::errors + +#endif // CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_SRC_AZURE_ERROR_CODES_H_ diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/BUILD.bazel b/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/BUILD.bazel new file mode 100644 index 000000000..76f8fcb8d --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/BUILD.bazel @@ -0,0 +1,53 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_test") + +package(default_visibility = ["//scp/cc:scp_internal_pkg"]) + +cc_test( + name = "azure_private_key_fetcher_provider_test", + size = "small", + srcs = ["azure_private_key_fetcher_provider_test.cc"], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/async_executor/mock:core_async_executor_mock", + "//scp/cc/core/http2_client/mock:http2_client_mock", + "//scp/cc/core/interface:interface_lib", + "//scp/cc/core/test/utils:utils_lib", + "//scp/cc/cpio/client_providers/auth_token_provider/mock:auth_token_provider_mock", + "//scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure:azure_private_key_fetcher_provider_lib", + "//scp/cc/public/core/test/interface:execution_result_matchers", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "azure_private_key_fetcher_provider_utils_test", + size = "small", + srcs = ["azure_private_key_fetcher_provider_utils_test.cc"], + deps = [ + "//scp/cc:cc_base_include_dir", + "//scp/cc/core/async_executor/mock:core_async_executor_mock", + "//scp/cc/core/http2_client/mock:http2_client_mock", + "//scp/cc/core/interface:interface_lib", + "//scp/cc/core/test/utils:utils_lib", + "//scp/cc/cpio/client_providers/private_key_fetcher_provider/mock:private_key_fetcher_provider_mock", + "//scp/cc/cpio/client_providers/private_key_fetcher_provider/src/azure:azure_private_key_fetcher_provider_lib", + "//scp/cc/public/core/test/interface:execution_result_matchers", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/azure_private_key_fetcher_provider_test.cc b/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/azure_private_key_fetcher_provider_test.cc new file mode 100644 index 000000000..501839181 --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/azure_private_key_fetcher_provider_test.cc @@ -0,0 +1,146 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider.h" + +#include +#include + +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "core/http2_client/mock/mock_http_client.h" +#include "core/interface/async_context.h" +#include "absl/synchronization/notification.h" +#include "cpio/client_providers/auth_token_provider/mock/mock_auth_token_provider.h" +#include "cpio/client_providers/private_key_fetcher_provider/src/error_codes.h" +#include "public/core/interface/execution_result.h" +#include "public/core/test/interface/execution_result_matchers.h" + +using google::scp::core::AsyncContext; +using google::scp::core::Byte; +using google::scp::core::BytesBuffer; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::errors:: + SC_PRIVATE_KEY_FETCHER_PROVIDER_HTTP_CLIENT_NOT_FOUND; +using google::scp::core::errors:: + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND; +using google::scp::core::http2_client::mock::MockHttpClient; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::ResultIs; +using google::scp::cpio::client_providers::AzurePrivateKeyFetcherProvider; +using google::scp::cpio::client_providers::mock::MockAuthTokenProvider; +using std::atomic; +using testing::Pair; +using testing::Pointee; +using testing::Return; +using testing::SetArgPointee; +using testing::UnorderedElementsAre; + +namespace { +constexpr char kAccountIdentity[] = "accountIdentity"; +constexpr char kRegion[] = "us-east-1"; +constexpr char kKeyId[] = "123"; +constexpr char kPrivateKeyBaseUri[] = "http://localhost.test:8000"; +constexpr char kPrivateKeyCloudfunctionUri[] = "http://cloudfunction.test:8000"; +constexpr char kSessionTokenMock[] = "session-token-test"; +constexpr char kAuthorizationHeaderKey[] = "Authorization"; +constexpr char kBearerTokenPrefix[] = "Bearer "; +} // namespace + +namespace google::scp::cpio::client_providers::test { +class AzurePrivateKeyFetcherProviderTest : public ::testing::Test { + protected: + AzurePrivateKeyFetcherProviderTest() + : http_client_(std::make_shared()), + credentials_provider_(std::make_shared()), + azure_private_key_fetcher_provider_( + std::make_unique( + http_client_, credentials_provider_)) { + EXPECT_SUCCESS(azure_private_key_fetcher_provider_->Init()); + EXPECT_SUCCESS(azure_private_key_fetcher_provider_->Run()); + + request_ = std::make_shared(); + request_->key_id = std::make_shared(kKeyId); + auto endpoint = std::make_shared(); + endpoint->gcp_private_key_vending_service_cloudfunction_url = + kPrivateKeyCloudfunctionUri; + endpoint->private_key_vending_service_endpoint = kPrivateKeyBaseUri; + endpoint->service_region = kRegion; + endpoint->account_identity = kAccountIdentity; + request_->key_vending_endpoint = std::move(endpoint); + } + + ~AzurePrivateKeyFetcherProviderTest() { + if (azure_private_key_fetcher_provider_) { + EXPECT_SUCCESS(azure_private_key_fetcher_provider_->Stop()); + } + } + + void MockResponse(const std::string& str) { + http_client_->response_mock = HttpResponse(); + http_client_->response_mock.body = BytesBuffer(str); + } + + std::shared_ptr http_client_; + std::shared_ptr credentials_provider_; + std::unique_ptr + azure_private_key_fetcher_provider_; + std::shared_ptr request_; +}; + +TEST_F(AzurePrivateKeyFetcherProviderTest, MissingHttpClient) { + azure_private_key_fetcher_provider_ = + std::make_unique(nullptr, + credentials_provider_); + + EXPECT_THAT(azure_private_key_fetcher_provider_->Init(), + ResultIs(FailureExecutionResult( + SC_PRIVATE_KEY_FETCHER_PROVIDER_HTTP_CLIENT_NOT_FOUND))); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, MissingCredentialsProvider) { + azure_private_key_fetcher_provider_ = + std::make_unique(http_client_, nullptr); + + EXPECT_THAT( + azure_private_key_fetcher_provider_->Init(), + ResultIs(FailureExecutionResult( + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND))); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, SignHttpRequest) { + absl::Notification condition; + AsyncContext context( + request_, + [&](AsyncContext& context) { + EXPECT_SUCCESS(context.result); + const auto& signed_request_ = *context.response; + condition.Notify(); + return SuccessExecutionResult(); + }); + + EXPECT_THAT(azure_private_key_fetcher_provider_->SignHttpRequest(context), + IsSuccessful()); + condition.WaitForNotification(); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/azure_private_key_fetcher_provider_utils_test.cc b/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/azure_private_key_fetcher_provider_utils_test.cc new file mode 100644 index 000000000..f341c9e35 --- /dev/null +++ b/scp/cc/cpio/client_providers/private_key_fetcher_provider/test/azure/azure_private_key_fetcher_provider_utils_test.cc @@ -0,0 +1,49 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cpio/client_providers/private_key_fetcher_provider/src/azure/azure_private_key_fetcher_provider_utils.h" + +#include + +#include "core/interface/http_types.h" +#include "public/core/interface/execution_result.h" +#include "public/core/test/interface/execution_result_matchers.h" + +using google::scp::core::ExecutionResult; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; + +namespace { +constexpr char kKeyId[] = "123"; +constexpr char kPrivateKeyBaseUri[] = "http://localhost.test:8000"; +} // namespace + +namespace google::scp::cpio::client_providers::test { + +TEST(AzurePrivateKeyFetchingClientUtilsTest, CreateHttpRequest) { + PrivateKeyFetchingRequest request; + request.key_vending_endpoint = std::make_shared(); + request.key_vending_endpoint->private_key_vending_service_endpoint = + kPrivateKeyBaseUri; + request.max_age_seconds = 1000000; + HttpRequest http_request; + AzurePrivateKeyFetchingClientUtils::CreateHttpRequest(request, http_request); + + EXPECT_EQ(http_request.method, HttpMethod::POST); + EXPECT_EQ(*http_request.path, std::string(kPrivateKeyBaseUri)); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/scp/cc/cpio/client_providers/role_credentials_provider/src/BUILD.bazel b/scp/cc/cpio/client_providers/role_credentials_provider/src/BUILD.bazel index aa26c0377..e62826701 100644 --- a/scp/cc/cpio/client_providers/role_credentials_provider/src/BUILD.bazel +++ b/scp/cc/cpio/client_providers/role_credentials_provider/src/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//scp/cc/cpio/client_providers/role_credentials_provider/src/aws:aws_role_credentials_provider_lib", ], + "//:azure_platform": [ + "//scp/cc/cpio/client_providers/role_credentials_provider/src/gcp:gcp_role_credentials_provider_lib", + ], "//:gcp_platform": [ "//scp/cc/cpio/client_providers/role_credentials_provider/src/gcp:gcp_role_credentials_provider_lib", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/scp/cc/public/cpio/interface/BUILD.bazel b/scp/cc/public/cpio/interface/BUILD.bazel index 1d0e09b1a..2edb94206 100644 --- a/scp/cc/public/cpio/interface/BUILD.bazel +++ b/scp/cc/public/cpio/interface/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -31,6 +32,14 @@ config_setting( }, ) +config_setting( + name = "azure_cpio_lib_inside_tee", + flag_values = { + "//:platform": "azure", + ":run_inside_tee": "True", + }, +) + config_setting( name = "gcp_cpio_lib_inside_tee", flag_values = { @@ -47,6 +56,14 @@ config_setting( }, ) +config_setting( + name = "azure_cpio_lib_outside_tee", + flag_values = { + "//:platform": "azure", + ":run_inside_tee": "False", + }, +) + config_setting( name = "gcp_cpio_lib_outside_tee", flag_values = { diff --git a/src/cpp/public/core/interface/cloud_platform.h b/src/cpp/public/core/interface/cloud_platform.h index 169a26650..325713880 100644 --- a/src/cpp/public/core/interface/cloud_platform.h +++ b/src/cpp/public/core/interface/cloud_platform.h @@ -1,5 +1,6 @@ /* * Copyright 2023 Google LLC + * Copyright (C) Microsoft Corporation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ enum class CloudPlatform { kLocal, kGcp, kAws, + kAzure, }; } // namespace privacy_sandbox::server_common diff --git a/src/cpp/telemetry/BUILD.bazel b/src/cpp/telemetry/BUILD.bazel index e5daead59..4d6ab8098 100644 --- a/src/cpp/telemetry/BUILD.bazel +++ b/src/cpp/telemetry/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2023 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -186,6 +187,9 @@ cc_library( "//:aws_instance": [ ":init_aws", ], + "//:azure_instance": [ + ":init_aws", + ], "//:gcp_instance": [ ":init_gcp", ],