次の方法で共有


チュートリアル: Kestrel を使って Service Fabric アプリケーションで HTTPS エンドポイントを追加する

このチュートリアルは、シリーズの第 "3 部" です。 Azure Service Fabric で実行されている ASP.NET Core サービスに HTTPS エンドポイントを追加する方法について説明します。 完了すると、HTTPS が有効な、ポート 443 でリッスンする ASP.NET Core Web フロントエンドを備えた投票アプリケーションができあがります。 チュートリアル シリーズの第 1 部で投票アプリケーションを手動で作成しない場合は、ソース コードをダウンロードして、完成したアプリケーションを取得できます。

このチュートリアルでは、次の作業を行う方法について説明します。

  • サービスに HTTPS エンドポイントを定義する
  • HTTPS を使うように Kestrel を設定する
  • TLS/SSL 証明書をリモート クラスター ノードにインストールする
  • 証明書の秘密キーへのアクセス権を NetworkService に付与する
  • Azure Load Balancer のポート 443 を開く
  • アプリケーションをリモート クラスターにデプロイする

このチュートリアル シリーズでは、次の操作方法について説明します。

Note

Azure を操作するには、Azure Az PowerShell モジュールを使用することをお勧めします。 作業を始めるには、「Azure PowerShell をインストールする」を参照してください。 Az PowerShell モジュールに移行する方法については、「AzureRM から Az への Azure PowerShell の移行」を参照してください。

前提条件

このチュートリアルを開始する前に

証明書を取得する、または開発用の自己署名証明書を作成する

運用アプリケーションの場合は、証明機関 (CA) から取得した証明書を使用してください。 開発およびテスト用には、自己署名証明書を作成して使用することができます。 Service Fabric SDK には、CertSetup.ps1 スクリプトが含まれています。 このスクリプトを使うと、自己署名証明書が作成されて Cert:\LocalMachine\My 証明書ストアにインポートされます。 管理者としてコマンド プロンプト ウィンドウを開き、次のコマンドを実行して、サブジェクト "CN=mytestcert" を含む証明書を作成します。

PS C:\program files\microsoft sdks\service fabric\clustersetup\secure> .\CertSetup.ps1 -Install -CertSubjectName CN=mytestcert

既に証明書個人情報交換 (PFX) ファイルがある場合は、次のコマンドを実行して、証明書を Cert:\LocalMachine\My 証明書ストアにインポートします。


PS C:\mycertificates> Import-PfxCertificate -FilePath .\mysslcertificate.pfx -CertStoreLocation Cert:\LocalMachine\My -Password (ConvertTo-SecureString "!Passw0rd321" -AsPlainText -Force)


   PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My

Thumbprint                                Subject
----------                                -------
3B138D84C077C292579BA35E4410634E164075CD  CN=zwin7fh14scd.westus.cloudapp.azure.com

サービス マニフェストに HTTPS エンドポイントを定義する

[管理者として実行] オプションを使って Visual Studio を開き、投票ソリューションを開きます。 ソリューション エクスプローラーで、VotingWeb/PackageRoot/ServiceManifest.xml を開きます。 サービス マニフェストは、サービス エンドポイントを定義します。 Endpoints セクションを見つけて、ServiceEndpoint エンドポイントの値を編集します。 名前を EndpointHttps に変更し、プロトコルを https に、型を Input に、ポートを 443 に設定します。 変更を保存。

<?xml version="1.0" encoding="utf-8"?>
<ServiceManifest Name="VotingWebPkg"
                 Version="1.0.0"
                 xmlns="http://schemas.microsoft.com/2011/01/fabric"
                 xmlns:xsd="https://www.w3.org/2001/XMLSchema"
                 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
  <ServiceTypes>
    <StatelessServiceType ServiceTypeName="VotingWebType" />
  </ServiceTypes>

  <CodePackage Name="Code" Version="1.0.0">
    <EntryPoint>
      <ExeHost>
        <Program>VotingWeb.exe</Program>
        <WorkingFolder>CodePackage</WorkingFolder>
      </ExeHost>
    </EntryPoint>
  </CodePackage>

  <ConfigPackage Name="Config" Version="1.0.0" />

  <Resources>
    <Endpoints>
      <Endpoint Protocol="https" Name="EndpointHttps" Type="Input" Port="443" />
    </Endpoints>
  </Resources>
</ServiceManifest>

HTTPS を使用するように Kestrel を構成する

ソリューション エクスプローラーで、VotingWeb/VotingWeb.cs ファイルを開きます。 HTTPS を使うとともに Cert:\LocalMachine\My ストア内の証明書を参照するように Kestrel を構成します。 次の using ステートメントを追加します。

using System.Net;
using Microsoft.Extensions.Configuration;
using System.Security.Cryptography.X509Certificates;

新しい EndpointHttps エンドポイントを使うとともにポート 443 をリッスンするように、ServiceInstanceListener の値を更新します。 Kestrel サーバーを使うように Web ホストを設定する場合は、すべてのネットワーク インターフェイスで IPv6 アドレスをリッスンするように、Kestrel を構成する必要があります (opt.Listen(IPAddress.IPv6Any, port, listenOptions => {...})。

new ServiceInstanceListener(
serviceContext =>
    new KestrelCommunicationListener(
        serviceContext,
        "EndpointHttps",
        (url, listener) =>
        {
            ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

            return new WebHostBuilder()
                .UseKestrel(opt =>
                {
                    int port = serviceContext.CodePackageActivationContext.GetEndpoint("EndpointHttps").Port;
                    opt.Listen(IPAddress.IPv6Any, port, listenOptions =>
                    {
                        listenOptions.UseHttps(FindMatchingCertificateBySubject());
                        listenOptions.NoDelay = true;
                    });
                })
                .ConfigureAppConfiguration((builderContext, config) =>
                {
                    config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
                })

                .ConfigureServices(
                    services => services
                        .AddSingleton<HttpClient>(new HttpClient())
                        .AddSingleton<FabricClient>(new FabricClient())
                        .AddSingleton<StatelessServiceContext>(serviceContext))
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseStartup<Startup>()
                .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                .UseUrls(url)
                .Build();
        }))

次に、Kestrel でサブジェクトを使って Cert:\LocalMachine\My ストア内で証明書を見つけることができるように、次のメソッドを追加します。

先ほどの PowerShell コマンドを使って自己署名証明書を作成した場合は、<your_CN_value>mytestcert に置き換えるか、証明書の CN を使います。

localhost へのローカル デプロイを利用する場合は、認証の例外を回避するために CN=localhost を使うことをお勧めします。

private X509Certificate2 FindMatchingCertificateBySubject(string subjectCommonName)
{
    using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
    {
        store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
        var certCollection = store.Certificates;
        var matchingCerts = new X509Certificate2Collection();
    
    foreach (var enumeratedCert in certCollection)
    {
      if (StringComparer.OrdinalIgnoreCase.Equals(subjectCommonName, enumeratedCert.GetNameInfo(X509NameType.SimpleName, forIssuer: false))
        && DateTime.Now < enumeratedCert.NotAfter
        && DateTime.Now >= enumeratedCert.NotBefore)
        {
          matchingCerts.Add(enumeratedCert);
        }
    }

        if (matchingCerts.Count == 0)
    {
        throw new Exception($"Could not find a match for a certificate with subject 'CN={subjectCommonName}'.");
    }
        
        return matchingCerts[0];
    }
}


証明書の秘密キーへのアクセス権を Network Service に付与する

先ほどの手順では、開発用コンピューターの Cert:\LocalMachine\My ストアに証明書をインポートしました。

ここで、サービスを実行しているアカウント (既定では Network Service) に証明書の秘密キーへのアクセス権を明示的に付与する必要があります。 この手順は手動で実行できますが (certlm.msc ツールを使用)、サービス マニフェストの SetupEntryPointスタートアップ スクリプトを構成して PowerShell スクリプトを実行することをお勧めします。

Note

Service Fabric は、サムプリントまたはサブジェクトの共通名によるエンドポイント証明書の宣言をサポートしています。 その場合、ランタイムによって、サービスの実行時の ID に対する証明書の秘密キーのバインドと割り当てが設定されます。 また、ランタイムによって、証明書の変更や更新に加え、対応する秘密キーの割り当ての更新の監視が行われます。

サービス セットアップ エントリ ポイントを構成する

ソリューション エクスプローラーで、VotingWeb/PackageRoot/ServiceManifest.xml を開きます。 CodePackage セクションでは、SetupEntryPoint ノードを追加して、ExeHost ノードを追加します。 ExeHost では、ProgramSetup.bat に設定し、WorkingFolderCodePackage に設定します。 VotingWeb サービスが開始すると、VotingWeb.exe が開始する前に Setup.bat スクリプトが CodePackage フォルダー内で実行されます。

<?xml version="1.0" encoding="utf-8"?>
<ServiceManifest Name="VotingWebPkg"
                 Version="1.0.0"
                 xmlns="http://schemas.microsoft.com/2011/01/fabric"
                 xmlns:xsd="https://www.w3.org/2001/XMLSchema"
                 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
  <ServiceTypes>
    <StatelessServiceType ServiceTypeName="VotingWebType" />
  </ServiceTypes>

  <CodePackage Name="Code" Version="1.0.0">
    <SetupEntryPoint>
      <ExeHost>
        <Program>Setup.bat</Program>
        <WorkingFolder>CodePackage</WorkingFolder>
      </ExeHost>
    </SetupEntryPoint>

    <EntryPoint>
      <ExeHost>
        <Program>VotingWeb.exe</Program>
        <WorkingFolder>CodePackage</WorkingFolder>
      </ExeHost>
    </EntryPoint>
  </CodePackage>

  <ConfigPackage Name="Config" Version="1.0.0" />

  <Resources>
    <Endpoints>
      <Endpoint Protocol="https" Name="EndpointHttps" Type="Input" Port="443" />
    </Endpoints>
  </Resources>
</ServiceManifest>

バッチ スクリプトと PowerShell セットアップ スクリプトを追加する

SetupEntryPoint の値から PowerShell を実行するには、PowerShell ファイルをポイントするバッチ ファイル内で PowerShell.exe を実行します。

最初に、バッチ ファイルをサービス プロジェクトに追加します。 ソリューション エクスプローラーで、VotingWeb を右クリックして、[追加]>[新しい項目] を選択します。 Setup.bat という名前の新しいファイルを追加します。 Setup.bat ファイルを編集して、次のコマンドを追加します。

powershell.exe -ExecutionPolicy Bypass -Command ".\SetCertAccess.ps1"

Setup.bat ファイルのプロパティを変更して、[出力ディレクトリにコピー][新しい場合はコピーする] に設定します。

ファイルのプロパティの設定を示すスクリーンショット。

ソリューション エクスプローラーで、VotingWeb を右クリックします。 次に、[追加]>[新しい項目] を選択し、SetCertAccess.ps1 という名前の新しいファイルを追加します。 SetCertAccess.ps1 ファイルを編集して、次のスクリプトを追加します。

$subject="mytestcert"
$userGroup="Network Service"

Write-Host "Checking permissions to certificate $subject.." -ForegroundColor DarkCyan

$cert = (gci Cert:\LocalMachine\My\ | where { $_.Subject.Contains($subject) })[-1]

if ($cert -eq $null)
{
    $message="Certificate with subject:"+$subject+" does not exist at Cert:\LocalMachine\My\"
    Write-Host $message -ForegroundColor Red
    exit 1;
}elseif($cert.HasPrivateKey -eq $false){
    $message="Certificate with subject:"+$subject+" does not have a private key"
    Write-Host $message -ForegroundColor Red
    exit 1;
}else
{
    $keyName=$cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName

    $keyPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\"

    if ($keyName -eq $null){
      $privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)      
      $keyName = $privateKey.Key.UniqueName
      $keyPath = "C:\ProgramData\Microsoft\Crypto\Keys"
    }

    $fullPath=$keyPath+$keyName
    $acl=(Get-Item $fullPath).GetAccessControl('Access')


    $hasPermissionsAlready = ($acl.Access | where {$_.IdentityReference.Value.Contains($userGroup.ToUpperInvariant()) -and $_.FileSystemRights -eq [System.Security.AccessControl.FileSystemRights]::FullControl}).Count -eq 1

    if ($hasPermissionsAlready){
        Write-Host "Account $userGroup already has permissions to certificate '$subject'." -ForegroundColor Green
        return $false;
    } else {
        Write-Host "Need add permissions to '$subject' certificate..." -ForegroundColor DarkYellow

        $permission=$userGroup,"Full","Allow"
        $accessRule=new-object System.Security.AccessControl.FileSystemAccessRule $permission
        $acl.AddAccessRule($accessRule)
        Set-Acl $fullPath $acl

        Write-Output "Permissions were added"

        return $true;
    }
}

SetCertAccess.ps1 ファイルのプロパティを変更して、[出力ディレクトリにコピー][新しい場合はコピーする] に設定します。

セットアップ スクリプトを管理者として実行する

既定では、サービス セットアップ エントリ ポイント実行可能ファイルが、Service Fabric と同じ資格情報を使って実行されます (通常は、Network Service アカウント)。 SetCertAccess.ps1 には、管理者権限が必要です。 アプリケーション マニフェストでは、ローカル管理者アカウントで、スタートアップ スクリプトを実行するセキュリティのアクセス許可を変更できます。

ソリューション エクスプローラーで Voting/ApplicationPackageRoot/ApplicationManifest.xml を開きます。 まず、Principals セクションを作成して、新しいユーザー (SetupAdminUser など) を追加します。 SetupAdminUser ユーザー アカウントを Administrators システム グループに追加します。

次に、VotingWebPkg の ServiceManifestImport セクションで、SetupAdminUser プリンシパルをセットアップ エントリ ポイントに適用するように RunAsPolicy を構成します。 このポリシーでは、Service Fabric に対し、Setup.bat ファイルで SetupAdminUser (管理者権限あり) が実行されることを通知します。

<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" ApplicationTypeName="VotingType" ApplicationTypeVersion="1.0.0" xmlns="http://schemas.microsoft.com/2011/01/fabric">
  <Parameters>
    <Parameter Name="VotingData_MinReplicaSetSize" DefaultValue="3" />
    <Parameter Name="VotingData_PartitionCount" DefaultValue="1" />
    <Parameter Name="VotingData_TargetReplicaSetSize" DefaultValue="3" />
    <Parameter Name="VotingWeb_InstanceCount" DefaultValue="-1" />
  </Parameters>
  <ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="VotingDataPkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
  </ServiceManifestImport>
  <ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="VotingWebPkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
    <Policies>
      <RunAsPolicy CodePackageRef="Code" UserRef="SetupAdminUser" EntryPointType="Setup" />
    </Policies>
  </ServiceManifestImport>
  <DefaultServices>
    <Service Name="VotingData">
      <StatefulService ServiceTypeName="VotingDataType" TargetReplicaSetSize="[VotingData_TargetReplicaSetSize]" MinReplicaSetSize="[VotingData_MinReplicaSetSize]">
        <UniformInt64Partition PartitionCount="[VotingData_PartitionCount]" LowKey="0" HighKey="25" />
      </StatefulService>
    </Service>
    <Service Name="VotingWeb" ServicePackageActivationMode="ExclusiveProcess">
      <StatelessService ServiceTypeName="VotingWebType" InstanceCount="[VotingWeb_InstanceCount]">
        <SingletonPartition />
      </StatelessService>
    </Service>
  </DefaultServices>
  <Principals>
    <Users>
      <User Name="SetupAdminUser">
        <MemberOf>
          <SystemGroup Name="Administrators" />
        </MemberOf>
      </User>
    </Users>
  </Principals>
</ApplicationManifest>

ローカルでアプリケーションを実行する

ソリューション エクスプローラーで、投票アプリケーションを選択し、Application URL プロパティを https://localhost:443 に設定します。

すべてのファイルを保存したら、F5 キーを押してアプリケーションをローカルで実行します。 アプリケーションのデプロイ後、ブラウザーで https://localhost:443 が開きます。 自己署名証明書を使用している場合は、PC がこの Web サイトのセキュリティを信頼していないことを示す警告が表示されます。 Web ページに進みます。

ブラウザーで実行されている Service Fabric の投票サンプル アプリと localhost URL を示すスクリーンショット。

クラスター ノードに証明書をインストールする

アプリケーションを Azure にデプロイするには、すべてのリモート クラスター ノードの Cert:\LocalMachine\My ストアに証明書をインストールします。 サービスは、クラスターの別のノードに移動することができます。 フロントエンド Web サービスがクラスター ノードで開始すると、スタートアップ スクリプトによって、証明書の参照とアクセス許可の構成が行われます。

クラスター ノードに証明書をインストールするには、まず証明書を PFX ファイルとしてエクスポートします。 certlm.msc アプリケーション ファイルを開き、[個人]>[証明書] に移動します。 mytestcert 証明書を右クリックして、[すべてのタスク]>[エクスポート] を選択します。

証明書のエクスポートを示すスクリーンショット。

エクスポート ウィザードで、[はい、秘密キーをエクスポートします] を選択して、PFX 形式を選択します。 ファイルを C:\Users\sfuser\votingappcert.pfx にエクスポートします。

次に、PowerShell スクリプトを使って、リモート クラスターに証明書をインストールします。

警告

アプリケーションの開発およびテスト用には自己署名証明書で十分です。 運用アプリケーションの場合は、自己署名証明書を使うのではなく、証明機関 (CA) から取得した証明書を使います。

Azure ロード バランサーと仮想ネットワークでポート 443 を開く

ロード バランサーのポート 443 をまだ開いていなければ、開きます。

$probename = "AppPortProbe6"
$rulename="AppPortLBRule6"
$RGname="voting_RG"
$port=443

# Get the load balancer resource
$resource = Get-AzResource | Where {$_.ResourceGroupName –eq $RGname -and $_.ResourceType -eq "Microsoft.Network/loadBalancers"}
$slb = Get-AzLoadBalancer -Name $resource.Name -ResourceGroupName $RGname

# Add a new probe configuration to the load balancer
$slb | Add-AzLoadBalancerProbeConfig -Name $probename -Protocol Tcp -Port $port -IntervalInSeconds 15 -ProbeCount 2

# Add rule configuration to the load balancer
$probe = Get-AzLoadBalancerProbeConfig -Name $probename -LoadBalancer $slb
$slb | Add-AzLoadBalancerRuleConfig -Name $rulename -BackendAddressPool $slb.BackendAddressPools[0] -FrontendIpConfiguration $slb.FrontendIpConfigurations[0] -Probe $probe -Protocol Tcp -FrontendPort $port -BackendPort $port

# Set the goal state for the load balancer
$slb | Set-AzLoadBalancer

関連付けられている仮想ネットワークに対して同じ操作を行います。

$rulename="allowAppPort$port"
$nsgname="voting-vnet-security"
$RGname="voting_RG"
$port=443

# Get the network security group resource
$nsg = Get-AzNetworkSecurityGroup -Name $nsgname -ResourceGroupName $RGname

# Add the inbound security rule.
$nsg | Add-AzNetworkSecurityRuleConfig -Name $rulename -Description "Allow app port" -Access Allow `
    -Protocol * -Direction Inbound -Priority 3891 -SourceAddressPrefix "*" -SourcePortRange * `
    -DestinationAddressPrefix * -DestinationPortRange $port

# Update the network security group
$nsg | Set-AzNetworkSecurityGroup

アプリケーションを Azure にデプロイする

すべてのファイルを保存します。[デバッグ] から [リリース] に切り替えた後、F6 キーを押してリビルドします。 ソリューション エクスプローラーで、[投票] を右クリックし、[発行] を選択します。 クラスターへのアプリケーションのデプロイに関するページで作成したクラスターの接続エンドポイントを選択するか、別のクラスターを選択します。 [発行] を選択して、リモート クラスターにアプリケーションを発行します。

アプリケーションがデプロイされたら、Web ブラウザーを開き、https://mycluster.region.cloudapp.azure.com:443 に移動します (クラスターの接続エンドポイントで URL を更新します)。 自己署名証明書を使用している場合は、PC がこの Web サイトのセキュリティを信頼していないことを示す警告が表示されます。 Web ページに進みます。

ブラウザー ウィンドウで実行されている Service Fabric の投票サンプル アプリを示すスクリーンショット。

次のステップ

次のチュートリアルに進みます。