Прочитать на английском

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


Программирование настраиваемых поставщиков учетных данных

В этом разделе описывается, как программировать настраиваемый поставщик учетных данных для отладчика Windows. Это позволяет использовать дополнительные символы и исходные серверы, для которых требуются уникальные типы проверки подлинности. Настройка позволяет использовать любой тип проверки подлинности, которую может потребовать сервер.

Существует два варианта:

  • Пользовательский поставщик, реализованный через исполняемый файл (или запущенный с помощью скрипта CMD/BAT).
  • Настраиваемый поставщик, реализованный в виде библиотеки DLL с помощью интерфейса API, описанного в этом документе.

Запросы проверки подлинности HTTPS для отладчика Windows

Отладчики Windows запрашивают символы с сервера символов и если сервер символов не требует проверки подлинности, эти символы возвращаются без использования каких-либо поставщиков учетных данных. Если сервер символов возвращает код состояния HTTP_STATUS_DENIED 401 (несанкционированный доступ или отказ в доступе), это указывает отладчику, что требуется проверка подлинности. Код 401 «Unauthorized» означает, что в запросе отсутствуют допустимые учетные данные для аутентификации к целевому ресурсу. Это означает, что сервер отказывается выполнить запрос, так как клиент не предоставил необходимые сведения о проверке подлинности.

Если настроен пользовательский поставщик учетных данных, он будет использоваться, и возвращаемые учетные данные будут применены для повторной отправки запроса, завершившегося ошибкой 401. Настройка пользовательского поставщика учетных данных рассматривается в следующем разделе этого раздела.

XML-конфигурация пользовательского поставщика учетных данных

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

Когда возвращается ошибка 401 (Несанкционированный доступ), отладчик вызывает DbgCredentialProvider.dll. Эта библиотека DLL будет искать поставщиков учетных данных с помощью следующего процесса. Он открывает файл DbgCredentialProvider.config.xml, который должен находиться в том же каталоге, что и DbgCredentialProvider.dll, который предоставляет расположение папки XML-файлов конфигурации.

Обратите внимание, что поведение поиска, используемое для поиска DbgCredentialProvider.config.xml файла, может измениться в будущем.

Расположение файла конфигурации XML — DbgCredentialProvider.config.xml

XML-файлы конфигурации устанавливаются в расположениях папок, как указано в DbgCredentialProvider.config.xml.

<?xml version="1.0" encoding="utf-8"?>
<!--
The config file is located next to the DbgCredentialProvider.dll.
-->
<Settings>
    <Folders>
        <!--
        This is a list of the folders which should be provided as an absolute file path or
        relative to the location of this config file.
        -->
        <Folder>CredentialProviders</Folder>
    </Folders>
</Settings>

В элементах <Folders></Folders> вы можете перечислить несколько папок. Папки могут иметь относительные или абсолютные пути. Если он относительный путь, он соответствует расположению файла DbgCredentialProvider.config.xml. Папки будут просматриваться для поиска поставщиков в указанном порядке.

В текущем примере выше коллекция папок содержит только одну папку CredentialProviders, и это относительный путь.

Сведения о конфигурации XML для пользовательского поставщика учетных данных

После расположения указанной папки будут перечисляться все файлы с расширением "*.xml" в папке CredentialProviders. XML-файлы описывают, какие поставщики учетных данных отладчика доступны для отладчика. Местоположение поставщиков учетных данных (реализованных в файлах DLL, EXE или сценариях CMD/BAT) описано в XML-файле CredentialProviders. Пути могут быть относительными или абсолютными.

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

Порядок перечисления XML-файлов не определен.

В примере DbgCredentialProvider_gcmw.xml файла показано, как можно вызвать пакетный файл.

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
    <!--
          This is a list of the provider modules which should be provided as an absolute file path or
          relative to the location of this config file.
          The provider is a DLL, EXE or CMD file.
    -->
    <CredentialProvider>PATCredentialProvider\PATCredentialProvider.bat</CredentialProvider>

</CredentialProviders>

В этом примере XML показано, как можно вызвать библиотеку DLL.

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
    <!--
      This is a list of the provider modules which should be provided as an absolute file path or
      relative to the location of this config file.
      The provider is a DLL, EXE or CMD file.
    -->
   <CredentialProvider>GCMW\DbgCredentialProvider_gcmw.dll</CredentialProvider>

</CredentialProviders>

В этом примере у нас есть только один поставщик DbgCredentialProvider_gcmw.dll, и он расположен в папке GCMW относительно расположения файла DbgCredentialProvider_gcmw.xml.

Вызов пользовательского поставщика учетных данных с помощью командной строки

В этом разделе описывается, как настраиваемый поставщик учетных данных может быть реализован как EXE (или запущен с помощью скрипта команды CMD/BAT).

Если поставщик реализован в EXE или скрипте CMD, он должен иметь возможность обрабатывать следующие параметры командной строки (без учета регистра), которые нельзя комбинировать друг с другом.

  • Получить
  • Стереть
  • Магазин

Команда Get

Команда Get используется для получения учетных данных. Остальные данные передаются поставщику через стандартный входной поток.

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

Параметры не зависят от регистра, и можно использовать любое сочетание верхнего и нижнего регистра.

Ошибка может быть возвращена через error=zzzzz

Protocol=http or https
Host=xxx 
Path=yyy
ResourceKind=symbols or sources
Interactive=0 or 1
IsRetry=0 or 1
ParentHwnd=HWND
<empty line to mark the end of the input parameters>

Получение параметров

Поле Тип Описание
Протокол LPCWSTR HTTP или HTTPS. Для повышения безопасности настоятельно рекомендуется использовать ПРОТОКОЛ HTTPS.
Хозяин LPCWSTR Имя сервера хоста, например contoso.symbols.com
Путь LPCWSTR Путь к каталогу символов, например apis/symbol/symsrv. Вызывающий или отладчик гарантирует, что Путь никогда не начинается с символа /.
ResourceKind LPCWSTR Это могут быть "символы" или "источники". Дополнительные типы ресурсов могут быть добавлены в будущем. Реализация поставщика учетных данных может использовать это для настройки необходимых разрешений при получении учетных данных. Он также может использоваться для кэширования учетных данных для дальнейшего использования.
Интерактивный bool True — для отображения пользовательского интерфейса, false — нет пользовательского интерфейса.
ЭтоПовторнаяПопытка bool Если это условие верно, то поставщик должен пропустить чтение кэшей и получить новые учетные данные.
ParentHwnd HWND Родительский HWND, если отображается пользовательский интерфейс проверки подлинности, например, 0x%Iили 64x. Приложения могут использовать переменную среды DBG_CREDENTIAL_PROVIDER_PARENT_HWND или метод imagehlp/dbghelp SymSetParentWindow для настройки родительского HWND.

Полный URI/URL-адрес создается путем объединения <protocol>://<host>/<path> с помощью перечисленных параметров. Например, https://contoso.symbols.com/apis/symbol/symsrv. Запрос будет выглядеть следующим образом:

protocol=https
host=contoso.symbols.com
path=apis/symbol/symsrv
resourceKind=symbols
isretry=false 
issilent=false
parenthwnd=593598
<Followed by an empty line to indicate the end of the input data.>

Стереть

Поставщик учетных данных может использовать эту команду для удаления учетных данных из кэша. Входные параметры совпадают с командой Get. Не требуется возвращаемое значение выходных данных. Может быть возвращена ошибка.

Магазин

Поставщик учетных данных может использовать эту команду для хранения учетных данных в кэше. Входные параметры совпадают с командой Get. Не требуется возвращаемое значение выходных данных. Ошибка может быть возвращена.

Локальный кэш токенов и isRetry

Для самого первого запроса поставщику отладчик отправляет параметр isRetry=false. Некоторые поставщики могут получать маркер из локального кэша. Когда отладчик повторно отправляет HTTP-запрос с этим маркером, сервер может снова вернуть ответ 401. Это может быть связано с истечением срока действия маркера. Затем отладчик запросит у поставщика учетных данных новый токен, и на этот раз флаг isRetry=true. В таком случае поставщик не должен использовать свой кэш, а должен получить новый токен.

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

В некоторых неинтерактивных средах, таких как лаборатории тестирования, не может быть пользователя для взаимодействия с пользовательским интерфейсом. В таком случае параметр issilent будет равен true. Поставщик не должен отображать проверку подлинности или другой пользовательский интерфейс, если этот параметр имеет значение true.

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

Настройте тихий (неинтерактивный) сервер символов.

Используйте функцию SymbolServerSetOptions для настройки беззвучного (неинтерактивного) сервера символов. Если для SSRVOPT_UNATTENDED задано значение TRUE, SymSrv не будет отображать диалоговые окна или всплывающие окна. Если значение FALSE, SymSrv будет отображать эти графические элементы при подключении.

Управление окнами интерфейса пользователя

Некоторые поставщики учетных данных могут отображать пользовательский интерфейс проверки подлинности. Если это так, он должен использовать параметр ParentHwnd, чтобы этот пользовательский интерфейс отображался как модальное диалоговое окно в главном окне отладчика. В противном случае пользовательский интерфейс проверки подлинности может быть скрыт за основным окном отладчика, и пользователю может быть предоставлено впечатление, что отладчик "заморожен".

Клиентское приложение отладчика, аналогичное WinDbg, может использовать переменную среды DBG_CREDENTIAL_PROVIDER_PARENT_HWND или imagehlp/dbghelp метод SymSetParentWindow для настройки родительского HWND. Вы также можете использовать сообщение IDebugAdvanced2::RequestDEBUG_REQUEST_SET_PARENT_HWND со значением приведения HWND к UINT32.

Требования к возвращаемым значениям

Скрипт EXE или CMD/BAT должен возвращать имя пользователя и пароль через выходной поток следующим образом:

username=aaa
password=bbb - where the password can be a password or PAT

CredentialKind

Запрос проверки подлинности возвращает CredentialKind. Существует два варианта для CredentialKind.

  • Базовая проверка подлинности: определена в RFC 7617. Учетные данные передаются в виде пар идентификатора пользователя и пароля, закодированных с помощью Base64.
username=xxx
credentialkind=Basic
password=yyy --> This can be a password or a PAT token
  • Проверка подлинности носителя: определена в RFC 6750. Маркеры носителя используются в HTTP-запросах для доступа к защищенным ресурсам OAuth 2.0.
username=xxx
credentialkind=Bearer
header=Bearer <TOKEN_GOES_HERE> ---> Usually OAuth2 tokens begin with "ey" and it is a very long string

Пример CMD-файла

Ниже приведен пример CMD-файла, который возвращает заголовок проверки подлинности HTTP:

OAuth2CredentialProvider.xml файл, расположенный в папке CredentialProviders:

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
    <CredentialProvider ApiVersion="2.0.0" >OAuth2CredentialProvider\OAuth2CredentialProvider.cmd</CredentialProvider>
</CredentialProviders>	

OAuth2CredentialProvider.cmd файл, расположенный в папке OAuth2CredentialProvider:

@echo off
echo username=UserName@domain.com
echo header=Bearer <TOKEN_GOES_HERE>

Тестирование пользовательского поставщика

Если вы пишете настраиваемого поставщика, расположенного в файле CMD или EXE, вы можете протестировать его, просто запустив его из окна консоли, используя соответствующие команды. Например:

DebuggerCredentialManager.exe Get

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

[Information] [DebuggerCredentialProvider.102949]Microsoft Debugger Credential Manager version 2024.0409.02656.285 (Windows, .NET 6.0.29) 'get'

Ниже приведен пример сведений, которые вы вводите в поток входных данных окна консоли. Его можно ввести в любом сочетании верхнего и нижнего регистра.

protocol=https
host=contoso.symbols.com
path=apis/symbol/symsrv
resourceKind=symbols
isretry=false 
issilent=false
parenthwnd=593598

Затем дважды нажмите клавишу ВВОД, чтобы отправить пустую строку и указать на завершение ввода пользователя.

Поставщик отвечает через стандартный выходной поток.

[Verbose] [DebuggerCredentialProvider.103258]AzureCredentialProvider - Attempting to acquire bearer token using provider 'Msal Cache'
[Verbose] [DebuggerCredentialProvider.103300]Token expiration data - current UTC time:9/18/2024 5:33:00 PM, ExpiresOn: 9/18/2024 6:43:25 PM
[Information] [DebuggerCredentialProvider.103300]AzureCredentialProvider - Acquired bearer token using 'Msal Cache'
protocol=https
host=contoso.symbols.com
path=apis/symbol/symsrv
username=UserName@domain.com
credentialkind=Bearer
header=Bearer eyJ0eXAi....
<empty line>

Отладчик будет игнорировать строки, не соответствующие шаблону key=value, где ключ — это один из следующих: протокол, узел, путь, имя пользователя, тип учетных данных или заголовок.

Регистр игнорируется в парах значений ключа. Отладчик обрабатывает пустую строку как конец входных данных.

Диагностические сведения поставщика

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

Пример скрипта PowerShell, возвращающего маркер PAT

Ниже приведен пример скрипта PS для возврата маркера PAT.

Файл PatCredentialProvider.xml настраивает PATCredentialProvider.bat в качестве CredentialProvider.

<?xml version="1.0" encoding="utf-8"?>
<CredentialProviders>
     <CredentialProvider ApiVersion="2.0.0">PATCredentialProvider\PATCredentialProvider.bat</CredentialProvider>
</CredentialProviders>

Файл PATCredentialProvider.bat расположен в папке PATCredentialProvider и вызывает PATCredentialProvider.ps1.

@echo off
<PATH_TO_POWERSHELL>\PowerShell.exe -NoProfile -executionpolicy Unrestricted -WindowStyle Hidden -File "%~dp0\PATCredentialProvider.ps1"

PATCredentialProvider.ps1 также находится в папке PATCredentialProvider.

<#
 .SYNOPSIS
    Given input, parses to find out which symbol server we want credentials for, and searches the Microsoft Credential Manager for those credentials.
    If found, prints the credentials to standard output. If not, prints error.

 .INPUT
    Delivered through standard input:
    protocol=http or https
    host=xxx ex. host=contoso.symbols.com
    path=yyy ex. path=apis/symbol/symsrv
    resourceKind=symbols
    isretry=false 
    issilent=false
    parenthwnd=593598
    <empty line to mark the end of the input parameters>

 .OUTPUT

    Delivered through standard output:

    username=aaa
    password=bbb - where the password can be a password or PAT. When PAT is returned the username will be any name (not necessarily the name of the currently logged in user)<!--[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="It's an example")]-->
    <empty line to mark the end of the output parameters>

 #>

 $logDirectory = (Get-Item Env:LoggingDirectory).Value
 $logFile = Join-Path $logDirectory "credProviderLog.txt"

 try
 {
    "Entering Credential Provider" | Out-File $logFile -Append

    $lines = While($line=Read-Host) {$line}
    $lines | Out-File $logFile -Append
    if (!(Get-Module "CredentialManager"))
    {
        "Installing module" | Out-File $logFile -Append

        Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force

        Install-Module CredentialManager -force -Scope CurrentUser
    }

    $pathLine = $lines | Where-Object {$_.StartsWith("path=")} | Select-Object -First 1
    "Found path line: $pathLine" | Out-File $logFile -Append

    [regex]$regex="path=(?<ServerName>.*)"
    $pathLine -Match $regex

    $symbolPath = "symbol:$($Matches.ServerName)"

    "Found symbol path: $symbolPath" | Out-File $logFile -Append

    $PAT = Get-StoredCredential -Target $symbolPath -AsCredentialObject

    if ($PAT)
    {
        "Found PAT!" | Out-File $logFile -Append
        Write-Host "username=placeholder"
        Write-Host "password=$($PAT.Password)"; # For OAuth 2 tokens You can change to output header=Bearer TOKEN
        Write-Host
    }
    else
    {
        "Could not locate PAT for Symbol Server: $symbolPath" | Out-File $logFile -Append
        Write-Host "error=Could not locate PAT for Symbol Server: $symbolPath"
    }
 }
 catch [System.SystemException]
 {
    "ERROR" | Out-File $logFile -Append
 
    $_ | Out-File $logFile -Append
 }

API C++ для поставщиков учетных данных, реализованных в виде библиотеки DLL

Ниже описаны поставщики учетных данных общедоступного интерфейса, которые должны соответствовать, если настраиваемый поставщик учетных данных реализуется в библиотеке DLL. Если поставщик реализован в библиотеке DLL, он должен экспортировать метод GetUserCredentials. Они расположены в namespace Debugger::CredentialProvider::Provider.

Требуемый файл заголовка DbgCredentialProviderImpl.h публикуется с помощью пакета SDK для Windows. Сведения о скачивании пакета SDK см. в SDK для Windows и предварительной версии SDK Insider Preview.

Перечисление MessageErrorLevelKind

// The caller of this method should not terminate the message with '\r' or '\n' characters
// (which makes this method similar to PrintLine)
typedef void (*DbgPrintMessageFn)(_In_ MessageErrorLevelKind errorLevel, _In_ PCWSTR message);

enum class MessageErrorLevelKind : uint8_t
{
    Info = 0,
    Warning,
    Error
};

CredentialResponseResultKind

enum CredentialResponseResultKind
{
    Success = 0,
    NoProviders, // There are no installed providers or can't launch 3rd party provider
    ProviderNotApplicable, // the provider can't handle requests to the provided URL
    Error
};

структура GetUserCredentialsRequest

Структура используется для хранения GetUserCredentialsRequest. Он использует те же параметры, что и описано выше в таблице параметров GetUserCredentialsRequest.

struct GetUserCredentialsRequest
{
    LPCWSTR Protocol;      // The full request URL can be built from Protocol, Host and Path as follows:
    LPCWSTR Host;          // Protocol://Host/Path
    LPCWSTR Path;          // The caller/debugger will make sure Path never starts with '/' character
    LPCWSTR ResourceKind;  // It can be "symbols", "sources", etc.
                           // The credential provider implementation may use this to adjust
                           // the required permissions when acquiring credentials. It also may be used
                           // to cache credentials for future use.
    bool Interactive;      // true - display UI is ok, false - no UI.
                           // Refer to the explanations above on how to setup a non interactive environment
    bool IsRetry;          // When true the provider may skip reading the caches and get new credentials
    HWND ParentHwnd;       // The parent HWND if an authentication UI is displayed.
                           // The applications can use DBG_CREDENTIAL_PROVIDER_PARENT_HWND environment variable
                           // or imagehlp/dbghelp SymSetParentWindow method to setup the parent HWND.

    DbgPrintMessageFn PrintMessageFn;
}

Функция GetUserCredentials

Функция GetUserCredentials используется для запроса учетных данных, которые будут отправлены на сервер символов в HTTP-запросе на символы.

HRESULT WINAPI GetUserCredentials(
  _In_ GetUserCredentialsRequest const & request,
  _Inout_ GetUserCredentialsResponse * pResponse);

Вызывающая сторона (отладчик) этого метода предоставит параметры запроса и ответа. Вызывающий объект гарантирует, что имя пользователя, пароль и сообщение об ошибке равны nullptr при входе в метод.

Реализация обязана заполнить поля: Имя пользователя, Пароль, Сообщение об ошибке (необязательно) и Результат.

Класс GetUserCredentialsResponse

Здесь показан класс GetUserCredentialsResponse.


class GetUserCredentialsResponse final
{
public:

    CredentialResponseResultKind Result = CredentialResponseResultKind::Error;
    BSTR UserName = nullptr;
    BSTR Password = nullptr;
    BSTR HttpAuthenticationHeader = nullptr;
    BSTR ErrorMessage = nullptr;

    GetUserCredentialsResponse() = default;

    GetUserCredentialsResponse(GetUserCredentialsResponse const&) = delete;
    GetUserCredentialsResponse& operator=(GetUserCredentialsResponse const&) = delete;

    GetUserCredentialsResponse(GetUserCredentialsResponse&& other) noexcept
    {
        Result = other.Result;
        UserName = other.UserName;
        Password = other.Password;
        HttpAuthenticationHeader = other.HttpAuthenticationHeader;
        ErrorMessage = other.ErrorMessage;

        other.Release();
    }

    GetUserCredentialsResponse& operator=(GetUserCredentialsResponse&& other) noexcept
    {
        if (this != addressof(other))
        {
            Clear();

            Result = other.Result;
            UserName = other.UserName;
            Password = other.Password;
            HttpAuthenticationHeader = other.HttpAuthenticationHeader;
            ErrorMessage = other.ErrorMessage;

            other.Release();
        }
        return (*this);
    }

    ~GetUserCredentialsResponse()
    {
        Clear();
    }

    void Clear()
    {
        SecureSysFreeString(UserName);
        SecureSysFreeString(Password);
        SecureSysFreeString(HttpAuthenticationHeader);
        SecureSysFreeString(ErrorMessage);
    }

private:
    template <class _Tp>
    _Tp* addressof(_Tp& __x) noexcept
    {
        return reinterpret_cast<_Tp*>(
            const_cast<char*>(&reinterpret_cast<const volatile char&>(__x)));
    }

    void Release() noexcept
    {
        Result = CredentialResponseResultKind::Error;
        UserName = nullptr;
        Password = nullptr;
        ErrorMessage = nullptr;
        HttpAuthenticationHeader = nullptr;
    }

    void SecureSysFreeString(_Inout_ BSTR& bstrString)
    {
        if (bstrString != nullptr)
        {
            size_t const length = wcslen(bstrString);
            SecureZeroMemory(bstrString, length * sizeof(wchar_t));
            SysFreeString(bstrString);
            bstrString = nullptr;
        }
    }
};

См. также

Символы и символьные файлы

открытые и частные символы