Поделиться через


Клиентская библиотека Конфиденциального реестра Azure для Python версии 1.1.1

Конфиденциальный реестр Azure предоставляет службу для ведения журнала в неизменяемом реестре, защищенном от незаконного изменения. Как часть портфеля конфиденциальных вычислений Azure Конфиденциальный реестр Azure выполняется в безопасных аппаратных доверенных средах выполнения, также известных как анклавы. Он основан на платформе конфиденциального консорциума Microsoft Research.

Исходный код | Пакет (PyPI) | Пакет (Conda) | Справочная документация по | APIДокументация по продукту

Начало работы

Установка пакетов

Установите azure-confidentialledger и azure-identity с помощью pip:

pip install azure-identity azure-confidentialledger

Azure-identity используется для проверки подлинности Azure Active Directory, как показано ниже.

Предварительные требования

  • Подписка Azure
  • Python 3.6 или более поздней версии
  • Выполняющийся экземпляр конфиденциального реестра Azure.
  • Зарегистрированный пользователь в Конфиденциальном реестре, обычно назначенный во время создания ресурса ARM , с Administrator привилегиями.

Аутентификация клиента

Использование Azure Active Directory

В этом документе показано использование DefaultAzureCredential для проверки подлинности в Конфиденциальном реестре через Azure Active Directory. ConfidentialLedgerClient Тем не менее принимает любые учетные данные azure-identity. Дополнительные сведения о других учетных данных см. в документации по azure-identity .

Использование сертификата клиента

В качестве альтернативы Azure Active Directory клиенты могут использовать сертификат клиента для проверки подлинности по взаимному протоколу TLS. azure.confidentialledger.ConfidentialLedgerCertificateCredential для этой цели можно использовать.

Создание клиента

DefaultAzureCredential автоматически обрабатывает большинство клиентских сценариев пакета AZURE SDK. Чтобы приступить к работе, задайте переменные среды для удостоверения AAD, зарегистрированного в конфиденциальном реестре.

export AZURE_CLIENT_ID="generated app id"
export AZURE_CLIENT_SECRET="random password"
export AZURE_TENANT_ID="tenant id"

DefaultAzureCredential Затем сможет пройти проверку подлинности ConfidentialLedgerClient.

Для создания клиента также требуется URL-адрес и идентификатор конфиденциального реестра, которые можно получить с помощью Azure CLI или портала Azure. Получив эти значения, замените экземпляры "my-ledger-id" и "https://my-ledger-id.confidential-ledger.azure.com" в приведенных ниже примерах. Также может потребоваться заменить "https://identity.confidential-ledger.core.azure.com" именем узла из identityServiceUri в описании ARM реестра.

Так как конфиденциальные реестры используют самозаверяемые сертификаты, безопасно созданные и хранящиеся в анклаве, сертификат подписи для каждого конфиденциального реестра необходимо сначала получить из службы удостоверений конфиденциального реестра.

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

Удобно, ConfidentialLedgerClient что конструктор получит TLS-сертификат реестра (и запишет его в указанный файл), если он предоставляется с несуществующим файлом. Пользователь несет ответственность за удаление созданного файла при необходимости.

from azure.confidentialledger import ConfidentialLedgerClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path="ledger_certificate.pem"
)

# The ledger TLS certificate is written to `ledger_certificate.pem`.

Чтобы понять, что файл используется для tls-сертификата реестра, в последующих примерах сертификат TLS реестра будет явно записываться в файл.

Основные понятия

Записи и транзакции реестра

При каждой записи в Конфиденциальный реестр Azure создается неизменяемая запись реестра в службе. Операции записи, также называемые транзакциями, однозначно идентифицируются идентификаторами транзакций, которые увеличиваются с каждой записью. После записи записи реестра можно получить в любое время.

Коллекции

Хотя большинство вариантов использования включает только одну коллекцию для каждого конфиденциального реестра, мы предоставляем функцию идентификатора коллекции в случае, если семантически или логически разные группы данных должны храниться в одном конфиденциальном реестре.

Записи реестра извлекаются их collectionId. Конфиденциальный реестр всегда принимает константу, определяемую collectionId службой для записей, написанных без указанного collectionId .

Пользователи

Управление пользователями осуществляется непосредственно с помощью Конфиденциального реестра, а не через Azure. Пользователи могут быть основаны на AAD, идентифицируются по идентификатору объекта AAD или на основе сертификата, идентифицируются по отпечатку сертификата PEM.

Receipts

Для обеспечения целостности транзакций Конфиденциальный реестр Azure использует структуру данных [дерево Меркла][merkle_tree_wiki] для записи хэша всех блоков транзакций, добавляемых в неизменяемый реестр. После фиксации транзакции записи пользователи Конфиденциального реестра Azure могут получить криптографическое подтверждение Merkle или квитанцию по записи, созданной в Конфиденциальном реестре, чтобы убедиться, что операция записи была сохранена правильно. Квитанция о транзакции записи является подтверждением того, что система зафиксировала соответствующую транзакцию, и может использоваться для проверки того, что запись была эффективно добавлена в реестр.

Дополнительные сведения о квитанциях о записи транзакций конфиденциального реестра Azure см. в следующей статье .

Проверка квитанции

После получения квитанции о транзакции записи пользователи Конфиденциального реестра Azure могут проверить содержимое полученного квитанции с помощью алгоритма проверки. Успешность проверки является подтверждением того, что операция записи, связанная с квитанцией, была правильно добавлена в неизменяемый реестр.

Дополнительные сведения о процессе проверки для квитанций о записи транзакций в Конфиденциальном реестре Azure см. в следующей статье .

Утверждения приложения

Приложения Конфиденциального реестра Azure могут присоединять произвольные данные, называемые утверждениями приложений, для записи транзакций. Эти утверждения представляют действия, выполняемые во время операции записи. При присоединении к транзакции хэш-код SHA-256 объекта утверждений включается в реестр и фиксируется как часть транзакции записи. Это гарантирует, что дайджест будет выполнен и не может быть изменен.

Позже утверждения приложения могут быть выявлены в их неперевариваемой форме в полезных данных квитанции, соответствующих той же транзакции, в которой они были добавлены. Это позволяет пользователям использовать сведения в квитанции для повторного вычисления того же дайджеста утверждений, который был присоединен и подписан экземпляром Конфиденциального реестра Azure во время транзакции. Дайджест утверждений можно использовать в процессе проверки получения транзакции записи, предоставляя пользователям автономный способ полной проверки подлинности записанных утверждений.

Дополнительные сведения о формате утверждений приложения и алгоритме вычислений дайджеста можно найти по следующим ссылкам:

Дополнительные сведения о утверждениях приложения CCF см. на следующих страницах документации по CCF:

Конфиденциальные вычисления

Конфиденциальные вычисления Azure позволяют изолировать и защищать данные во время их обработки в облаке. Конфиденциальный реестр Azure работает на виртуальных машинах Конфиденциальных вычислений Azure, обеспечивая более надежную защиту данных с шифрованием используемых данных.

Платформа конфиденциального консорциума

Конфиденциальный реестр Azure основан на платформе Консорциума конфиденциального консорциума (CCF) Microsoft Research с открытым кодом. В рамках CCF приложения управляются консорциумом членов с возможностью отправлять предложения по изменению и управлению работой приложения. В Конфиденциальном реестре Azure Microsoft Azure владеет удостоверением участника оператора, которое позволяет выполнять действия по управлению и обслуживанию, такие как замена неработоспособных узлов в Конфиденциальном реестре и обновление кода анклава.

Примеры

В этом разделе содержатся фрагменты кода, охватывающие распространенные задачи, в том числе:

Добавление записи

Данные, которые должны храниться неизменяемо в защищенном от незаконного изменения способе, можно сохранить в Конфиденциальном реестре Azure, добавив запись в реестр.

Так как Конфиденциальный реестр является распределенной системой, редкие временные сбои могут привести к потере операций записи. Для записей, которые необходимо сохранить, рекомендуется убедиться, что запись стала устойчивой. Для менее важных операций записи, где предпочтительнее использовать более высокую пропускную способность клиента, можно пропустить этап ожидания.

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

post_entry_result = ledger_client.create_ledger_entry(
        {"contents": "Hello world!"}
    )
transaction_id = post_entry_result["transactionId"]

wait_poller = ledger_client.begin_wait_for_commit(transaction_id)
wait_poller.wait()
print(f'Ledger entry at transaction id {transaction_id} has been committed successfully')

Кроме того, клиент может ждать фиксации при записи реестра.

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

post_poller = ledger_client.begin_create_ledger_entry(
    {"contents": "Hello world again!"}
)
new_post_result = post_poller.result()
print(
    'The new ledger entry has been committed successfully at transaction id '
    f'{new_post_result["transactionId"]}'
)

Получение записей реестра

Получение записей реестра старше последней может занять некоторое время, так как служба загружает исторические записи, поэтому предоставляется средство опроса.

Записи реестра извлекаются коллекцией. Возвращаемое значение — это значение, содержащееся в указанной коллекции на момент времени, определенный идентификатором транзакции.

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

post_poller = ledger_client.begin_create_ledger_entry(
    {"contents": "Original hello"}
)
post_result = post_poller.result()

post_transaction_id = post_result["transactionId"]

latest_entry = ledger_client.get_current_ledger_entry()
print(
    f'Current entry (transaction id = {latest_entry["transactionId"]}) '
    f'in collection {latest_entry["collectionId"]}: {latest_entry["contents"]}'
)

post_poller = ledger_client.begin_create_ledger_entry(
    {"contents": "Hello!"}
)
post_result = post_poller.result()

get_entry_poller = ledger_client.begin_get_ledger_entry(post_transaction_id)
older_entry = get_entry_poller.result()
print(
    f'Contents of {older_entry["entry"]["collectionId"]} at {post_transaction_id}: {older_entry["entry"]["contents"]}'
)

Создание запроса с диапазоном

Записи реестра могут извлекаться по диапазону идентификаторов транзакций. Записи будут возвращены только из коллекции по умолчанию или из указанной коллекции.

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

post_poller = ledger_client.begin_create_ledger_entry(
    {"contents": "First message"}
)
first_transaction_id = post_poller.result()["transactionId"]

for i in range(10):
    ledger_client.create_ledger_entry(
        {"contents": f"Message {i}"}
    )

post_poller = ledger_client.begin_create_ledger_entry(
    {"contents": "Last message"}
)
last_transaction_id = post_poller.result()["transactionId"]

ranged_result = ledger_client.list_ledger_entries(
    from_transaction_id=first_transaction_id,
    to_transaction_id=last_transaction_id,
)
for entry in ranged_result:
    print(f'Contents at {entry["transactionId"]}: {entry["contents"]}')

Управление пользователями

Пользователи с Administrator привилегиями могут управлять пользователями конфиденциального реестра непосредственно с помощью самого конфиденциального реестра. Доступны роли Reader (только для чтения), Contributor (чтение и запись) и Administrator (чтение, запись, а также добавление или удаление пользователей).

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

user_id = "some AAD object id"
user = ledger_client.create_or_update_user(
    user_id, {"assignedRole": "Contributor"}
)
# A client may now be created and used with AAD credentials (i.e. AAD-issued JWT tokens) for the user identified by `user_id`.

user = ledger_client.get_user(user_id)
assert user["userId"] == user_id
assert user["assignedRole"] == "Contributor"

ledger_client.delete_user(user_id)

# For a certificate-based user, their user ID is the fingerprint for their PEM certificate.
user_id = "PEM certificate fingerprint"
user = ledger_client.create_or_update_user(
    user_id, {"assignedRole": "Reader"}
)

user = ledger_client.get_user(user_id)
assert user["userId"] == user_id
assert user["assignedRole"] == "Reader"

ledger_client.delete_user(user_id)

Использование проверки подлинности на основе сертификата

Клиенты могут проходить проверку подлинности с помощью сертификата клиента по взаимному протоколу TLS, а не с помощью маркера Azure Active Directory. ConfidentialLedgerCertificateCredential предоставляется для таких клиентов.

from azure.confidentialledger import (
    ConfidentialLedgerCertificateCredential,
    ConfidentialLedgerClient,
)
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = ConfidentialLedgerCertificateCredential(
    certificate_path="Path to user certificate PEM file"
)
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

Проверка квитанций о записях транзакций

Клиенты могут использовать библиотеку проверки квитанций в пакете SDK для проверки квитанций о записях транзакций, выданных экземплярами Azure Confidential Legder. Служебную программу можно использовать для полной проверки квитанций в автономном режиме, так как алгоритм проверки не требует подключения к конфиденциальному реестру или любой другой службе Azure.

После добавления новой записи в реестр (см. этот пример), можно получить квитанцию о зафиксированной транзакции записи.

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

# Replace this with the Confidential Ledger ID 
ledger_id = "my-ledger-id"

# Setup authentication
credential = DefaultAzureCredential()

# Create a Ledger Certificate client and use it to
# retrieve the service identity for our ledger
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id=ledger_id
)

# Save ledger service certificate into a file for later use
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

# Create Confidential Ledger client
ledger_client = ConfidentialLedgerClient(
    endpoint=f"https://{ledger_id}.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

# The method begin_get_receipt returns a poller that
# we can use to wait for the receipt to be available for retrieval 
get_receipt_poller = ledger_client.begin_get_receipt(transaction_id)
get_receipt_result = get_receipt_poller.result()

print(f"Write receipt for transaction id {transaction_id} was successfully retrieved: {get_receipt_result}")

После получения квитанции для транзакции записи можно вызвать функцию verify_receipt , чтобы убедиться, что квитанция действительна. Функция может принимать необязательный список утверждений приложения для проверки на соответствие дайджесту утверждений о получении.

from azure.confidentialledger.receipt import (
    verify_receipt,
)

# Read contents of service certificate file saved in previous step.
with open(ledger_tls_cert_file_name, "r") as service_cert_file:
    service_cert_content = service_cert_file.read()

# Optionally read application claims, if any
application_claims = get_receipt_result.get("applicationClaims", None) 

try:
    # Verify the contents of the receipt.
    verify_receipt(get_receipt_result["receipt"], service_cert_content, application_claims=application_claims)
    print(f"Receipt for transaction id {transaction_id} successfully verified")
except ValueError:
    print(f"Receipt verification for transaction id {transaction_id} failed")

Полный пример программы Python, в котором показано, как добавить новую запись в работающий экземпляр Конфиденциального реестра, получить квитанцию о зафиксированной транзакции и проверить, что содержимое квитанции можно найти в папке примеров: get_and_verify_receipt.py.

Асинхронный API

Эта библиотека включает полный асинхронный API, поддерживаемый в Python 3.5 и более поздних версий. Чтобы использовать его, необходимо сначала установить асинхронный транспорт, например aiohttp. Дополнительные сведения см. в документации по azure-core .

Асинхронный клиент получается из azure.confidentialledger.aio. Методы имеют те же имена и сигнатуры, что и синхронный клиент. Примеры можно найти здесь.

Устранение неполадок

Общие сведения

Клиенты конфиденциального реестра вызывают исключения, определенные в azure-core. Например, при попытке получить транзакцию, которая не существует, ConfidentialLedgerClient вызывает resourceNotFoundError:

from azure.core.exceptions import ResourceNotFoundError
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name
)

try:
    ledger_client.begin_get_ledger_entry(
        transaction_id="10000.100000"  # Using a very high id that probably doesn't exist in the ledger if it's relatively new.
    )
except ResourceNotFoundError as e:
    print(e.message)

Ведение журнала

Эта библиотека использует стандартную библиотеку ведения журнала для ведения журнала. Основные сведения о сеансах HTTP (URL-адреса, заголовки и т. д.) регистрируются на уровне INFO.

Подробное ведение журнала на уровне DEBUG, включая тексты запросов и ответов и неотредактированные заголовки, можно включить на клиенте с помощью аргумента logging_enable :

import logging
import sys

from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential

# Create a logger for the 'azure' SDK
logger = logging.getLogger('azure')
logger.setLevel(logging.DEBUG)

# Configure a console output
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
    ledger_id="my-ledger-id"
)

ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
    cert_file.write(network_identity["ledgerTlsCertificate"])

credential = DefaultAzureCredential()

# This client will log detailed information about its HTTP sessions, at DEBUG level.
ledger_client = ConfidentialLedgerClient(
    endpoint="https://my-ledger-id.confidential-ledger.azure.com",
    credential=credential,
    ledger_certificate_path=ledger_tls_cert_file_name,
    logging_enable=True,
)

Аналогичным образом с помощью параметра logging_enable можно включить подробное журналирование для отдельной операции (даже если этот режим не включен в клиенте):

ledger_client.get_current_ledger_entry(logging_enable=True)

Дальнейшие действия

Больше примеров кода

В этих примерах кода показаны распространенные операции сценария с клиентской библиотекой Конфиденциального реестра Azure.

Распространенные сценарии

Расширенные сценарии

Дополнительная документация

Более подробную документацию по Конфиденциальному реестру Azure см. в справочной документации по API. Вы также можете узнать больше о платформе Консорциума конфиденциальных данных Microsoft Research с открытым кодом.

Участие

На этом проекте приветствуются публикации и предложения. Для участия в большинстве процессов по разработке документации необходимо принять лицензионное соглашение участника (CLA), в котором указывается, что вы предоставляете нам права на использование ваших публикаций. Для получения подробных сведений посетите веб-страницу https://cla.microsoft.com.

При отправке запроса на включение внесенных изменений CLA-бот автоматически определит необходимость предоставления соглашения CLA и соответствующего оформления запроса на включение внесенных изменений (например, добавление метки, комментария). Просто следуйте инструкциям бота. Будет достаточно выполнить их один раз для всех репозиториев, поддерживающих соглашение CLA.

В рамках этого проекта действуют правила поведения в отношении продуктов с открытым исходным кодом Майкрософт. Дополнительные сведения: Вопросы и ответы по правилам поведения. С любыми другими вопросами или комментариями обращайтесь по адресу opencode@microsoft.com.