什麼是Secrets

應用程式通常會通過使用專用的儲存來儲存敏感資訊,如連線字串、金鑰等。

通常這需要建立一個金鑰儲存,如Azure Key Vault、Hashicorp等,並在那裡儲存應用程式級別的金鑰。 要訪問這些金鑰儲存,應用程式需要匯入金鑰儲存SDK,並使用它訪問這些金鑰。 這可能需要相當數量的模板程式碼,這些程式碼與應用的實際業務領域無關,因此在多雲場景中,可能會使用不同廠商特定的金鑰儲存,這就成為一個更大的挑戰。

讓開發人員在任何地方更容易訪問應用程式金鑰, Dapr 提供一個專用的金鑰構建塊 ,允許開發人員從一個儲存獲得金鑰。

使用 Dapr 的金鑰儲存構建塊通常涉及以下內容:

  1. 設定一個特定的金鑰儲存解決方案的元件。
  2. 在應用程式程式碼中使用 Dapr Secrets API 獲取金鑰。
  3. 在Dapr的Component檔案中引用金鑰

工作原理

  1. 服務A呼叫 Dapr Secrets API,提供要檢索的Serects的名稱和要查詢的項名字。
  2. Dapr sidecar 從Secrets儲存中檢索指定的機密。
  3. Dapr sidecar 將Secrets資訊返回給服務。

Dapr目前支援的Secrets儲存請見儲存

使用Secrets時,應用程式與 Dapr sidecar 互動。 sidecar 公開Secrets API。 可以使用 HTTP 或 gRPC 呼叫 API。 使用以下 URL 呼叫 HTTP API:

http://localhost:<dapr-port>/v1.0/secrets/<store-name>/<name>?<metadata>

URL 包含以下欄位:

  • <dapr-port> 指定 Dapr sidecar 偵聽的埠號。
  • <store-name> 指定 Dapr Secrets儲存的名稱。
  • <name> 指定要檢索的金鑰的名稱。
  • <metadata> 提供Secrets的其他資訊。 此段是可選的,每個Secrets儲存的元資料屬性不同。 有關元資料屬性詳細資訊

專案實戰

通過Dapr SDK獲取secrets

仍然使用FrontEnd專案,並使用本地檔案儲存Secrets,首先在預設component目錄C:\Users\<username>\.dapr\components中新建檔案secrets01.json,宣告金鑰內容

{
"RabbitMQConnectStr": "amqp://admin:[email protected]:5672"
}

在此目錄新建secrets01.yaml定義store

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: secrets01
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: C:\Users\username\.dapr\components\secrets01.json
- name: nestedSeparator
value: ":"

定義介面獲取Secrets01的內容,新建SecretsController

using Dapr.Client;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using System.Collections.Generic;
using System.Threading.Tasks; namespace FrontEnd.Controllers
{
[Route("[controller]")]
[ApiController]
public class SecretsController : ControllerBase
{
private readonly ILogger<SecretsController> _logger;
private readonly DaprClient _daprClient;
public SecretsController(ILogger<SecretsController> logger, DaprClient daprClient)
{
_logger = logger;
_daprClient = daprClient;
} [HttpGet]
public async Task<ActionResult> GetAsync()
{
Dictionary<string, string> secrets = await _daprClient.GetSecretAsync("secrets01", "RabbitMQConnectStr");
return Ok(secrets);
}
}
}

執行Frontend

dapr run --dapr-http-port 3501 --app-port 5001  --app-id frontend dotnet  .\FrontEnd\bin\Debug\net5.0\FrontEnd.dll

驗證此api,獲取成功

通過IConfiguration訪問Secrets

Dapr還提供了從IConfiguration中訪問Secrets的方法,首先引入nuget包Dapr.Extensions.Config

在Program.cs中修改註冊

        public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config =>
{
var daprClient = new DaprClientBuilder().Build();
var secretDescriptors = new List<DaprSecretDescriptor> { new DaprSecretDescriptor("RabbitMQConnectStr") };
config.AddDaprSecretStore("secrets01", secretDescriptors, daprClient);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>().UseUrls("http://*:5001");
});

在SecretsController注入IConfiguration

        private readonly ILogger<SecretsController> _logger;
private readonly DaprClient _daprClient;
private readonly IConfiguration _configuration;
public SecretsController(ILogger<SecretsController> logger, DaprClient daprClient, IConfiguration configuration)
{
_logger = logger;
_daprClient = daprClient;
_configuration = configuration;
}

在SecretsController中新增介面

        [HttpGet("get01")]
public async Task<ActionResult> Get01Async()
{
return Ok(_configuration["RabbitMQConnectStr"]);
}

呼叫介面,獲取資料成功

其他元件引用Secrets

Dapr的其他元件,同樣可以引用Secrets,我們以上節RabbitMQBinding為例,修改rabbitbinding.yaml

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: RabbitBinding
spec:
type: bindings.rabbitmq
version: v1
metadata:
- name: queueName
value: queue1
- name: host
secretKeyRef:
name: RabbitMQConnectStr
key: RabbitMQConnectStr
- name: durable
value: true
- name: deleteWhenUnused
value: false
- name: ttlInSeconds
value: 60
- name: prefetchCount
value: 0
- name: exclusive
value: false
- name: maxPriority
value: 5
auth:
secretStore: secrets01

secretKeyRef元素引用指定的金鑰。 它將替換以前的 明文 值。  在 auth 中找到對應的secretStore。

現在執行Frontend

dapr run --dapr-http-port 3501 --app-port 5001  --app-id frontend dotnet  .\FrontEnd\bin\Debug\net5.0\FrontEnd.dll

在RabbitMQ Management中傳送訊息,消費成功

== APP == info: FrontEnd.Controllers.RabbitBindingController[0]
== APP == .............binding.............11122444

限制Secrets訪問許可權

我們可以在Dapr的預設配置檔案C:\Users\username\.dapr\config.yaml中設定Secrets的訪問許可權,現在我們嘗試禁止secrets01的許可權

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprConfig
spec:
tracing:
samplingRate: "1"
zipkin:
endpointAddress: http://localhost:9411/api/v2/spans
secrets:
scopes:
- storeName: secrets01
defaultAccess: deny

設定之後,Frontend會啟動失敗,因為我們在Program.cs中設定了讀取secrets01。

== APP == Unhandled exception. Dapr.DaprException: Secret operation failed: the Dapr endpoint indicated a failure. See InnerException for details.
== APP == ---> Grpc.Core.RpcException: Status(StatusCode="PermissionDenied", Detail="access denied by policy to get "RabbitMQConnectStr" from "secrets01"")
== APP == at Dapr.Client.DaprClientGrpc.GetSecretAsync(String storeName, String key, IReadOnlyDictionary`2 metadata, CancellationToken cancellationToken)
== APP == --- End of inner exception stack trace ---
== APP == at Dapr.Client.DaprClientGrpc.GetSecretAsync(String storeName, String key, IReadOnlyDictionary`2 metadata, CancellationToken cancellationToken)
== APP == at Dapr.Extensions.Configuration.DaprSecretStore.DaprSecretStoreConfigurationProvider.LoadAsync()
== APP == at Dapr.Extensions.Configuration.DaprSecretStore.DaprSecretStoreConfigurationProvider.Load()
== APP == at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
== APP == at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
== APP == at Microsoft.Extensions.Hosting.HostBuilder.BuildAppConfiguration()
== APP == at Microsoft.Extensions.Hosting.HostBuilder.Build()
== APP == at FrontEnd.Program.Main(String[] args) in C:\demo\test\DaprBackEnd\FrontEnd\Program.cs:line 20

我們可以修改配置讓其允許

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprConfig
spec:
tracing:
samplingRate: "1"
zipkin:
endpointAddress: http://localhost:9411/api/v2/spans
secrets:
scopes:
- storeName: secrets01
defaultAccess: deny
allowedSecrets: ["RabbitMQConnectStr"]

重啟Frontend成功

以下表格列出了所有可能的訪問許可權配置

Scenarios defaultAccess allowedSecrets deniedSecrets permission
1 - Only default access deny/allow empty empty deny/allow
2 - Default deny with allowed list deny [“s1”] empty only “s1” can be accessed
3 - Default allow with deneied list allow empty [“s1”] only “s1” cannot be accessed
4 - Default allow with allowed list allow [“s1”] empty only “s1” can be accessed
5 - Default deny with denied list deny empty [“s1”] deny
6 - Default deny/allow with both lists deny/allow [“s1”] [“s2”] only “s1” can be accessed