【问题标题】:Scaling an IdentityServer4 service扩展 IdentityServer4 服务
【发布时间】:2017-12-27 06:07:04
【问题描述】:

我已遵循IdentityServer4 quickstarts 并能够使用隐式授权使用本地化的 IdentityServer 实例验证我的 javascript 网页(与快速入门中提供的几乎相同)。同样,我的 IdentityServer 与上面提到的快速入门中提供的几乎完全相同 - 它只是有一些自定义用户详细信息。

然后,我将我的应用程序(C# .NET Core)移动到一个 docker 容器中,并在 Kubernetes 集群(单个实例)中托管了这个实例,并创建了一个 Kubernetes 服务(一个或多个“真实”服务的外观)让我可以从集群外部访问身份服务器。我可以修改我的 JavaScript 网页并将其指向我的 Kubernetes 服务,它仍然会非常高兴地显示登录页面,并且它似乎可以按预期工作。

当我将 IdentityServer 扩展到三个实例(均在单个 Kubernetes 服务之后提供)时,我开始遇到问题。 Kubernetes 服务轮询对每个身份服务器的请求,所以第一个会显示登录页面,但第二个会在我按下登录按钮后尝试处理身份验证。这会导致以下错误:

System.InvalidOperationException: 无法使用防伪令牌 解密。 ---> System.Security.Cryptography.CryptographicException: 密钥 {19742e88-9dc6-44a0-9e89-e7b09db83329} 钥匙圈。在 Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(字节[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& 状态)在 Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(字节[] protectedData,布尔型 ignoreRevocationErrors,布尔型& requiresMigration, Boolean& wasRevoked) 在 Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(字节[] 受保护数据)在 Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(字符串 serializedToken) --- 内部异常堆栈跟踪结束 --- ......还有更多......

所以 - 我知道我收到了这个错误,因为期望同一个 IdentityServer 应该为它显示的页面的请求提供服务(否则防伪令牌将如何工作,对吗?),但我我想了解的是如何在复制环境中进行这项工作。

我不想在不同的 IP/端口上托管多个身份服务器;我正在尝试构建一个 HA 配置,如果一个 IdentityServer 死了,调用端点的任何事情都不应该关心(因为请求应该由其他工作实例提供服务)。

我说我使用的是快速启动代码——这意味着在 IdentityServer 的启动中,有这样的代码......

    public void ConfigureServices(IServiceCollection services)  
    {
        services.AddMvc();

        services.AddIdentityServer(options =>
            {
                options.Events.RaiseSuccessEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseErrorEvents = true;
            })
            .AddTemporarySigningCredential()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())

我假设我需要将 .AddTemporarySigningCredential() 逻辑替换为一个证书,该证书可由在我的 Kubernetes 集群中运行的 IdentityServer 的所有实例使用。不知道 MVC 是如何工作的(MVC6 用于在 IdentityServer 服务中生成登录页面,我从上面的示例代码中获得 - 我想知道是否只是更改代码以使用在之间共享的正确证书所有服务都足以让原型 HA IdentityServer 集群正常工作吗?

通过工作,我的意思是我的期望是我可以在 Kubernetes 集群中运行 n 个 IdentityServer 实例,让 Kubernetes 服务充当我运行的许多 IdentityServer 的外观,并且能够使用多个 IdentityServer 实例可以共享数据,只要它们都为我的调用 Web 应用程序提供完全相同的权限,并且可以在一个或多个实例死亡的情况下处理彼此的请求。

任何帮助或见解将不胜感激。

【问题讨论】:

  • 添加 TemporarySingingCrendentials 将罐。为每个 pod 生成不同的值。你肯定需要一个静态证书。见.AddSigningCredential 但我不确定这是否是唯一的问题。因为我打算做一些非常相似的事情,这很有趣
  • 我们在负载平衡环境中使用 Identity Server 没有问题,但我们没有使用涉及由 IdSvr 生成的登录页面的流程。我们使用单个证书进行签名(不是临时证书),而且设置/测试很简单,因此您应该能够很快确定您的问题是临时证书还是其他问题。
  • @Mashton 很高兴知道;我之前遇到了一些其他问题,所以还没有机会尝试 Boas 建议的 .AddSigningCredential() 。当我更新证书时我会更新 - 可能在周末之后。
  • 为什么这篇文章被否决了?如果这样做的人可以留下便条说明他们认为错误的地方,那就太好了。

标签: c# asp.net-mvc kubernetes scaling identityserver4


【解决方案1】:

我想我已经解决了这个问题。为了解决我的问题,我做了两件事:

  1. 创建我自己的 X509 证书并在我的每个 IdentityServer 之间共享此证书。网上有很多关于如何创建有效证书的例子;我刚用过

    services.AddIdentityServer(...).AddSigningCredential(new X509Certificate2(bytes, "password")
    

    在我的创业课上。

  2. 深入研究 MVC 框架代码并发现我需要实现 Key storage provider 以便在提供登录页面的 Identity Server 的 MVC 部分的不同实例之间共享状态。

原来有一个Redis backed KSP available from NuGet,这意味着我只需要在我的 Kube 集群中启动一个私有 redis 实例(在我的集群之外无法访问它)来共享解密秘密。

/* Note: Use an IP, or resolve from DNS prior to adding redis based key store as direct DNS resolution doesn't work for this inside a K8s cluster, though it works quite happily in a Windows environment. */  
var redis = ConnectionMultiplexer.Connect("1.2.3.4:6379");
services.AddDataProtection()
        .PersistKeysToRedis(redis, "DataProtection-Keys");

我现在可以将身份服务扩展到 3 个实例,并让 Kube 服务充当所有可用实例的外观。我可以在身份服务之间查看 Kubernetes 循环请求的日志,并且我的身份验证按预期进行。

感谢那些在这篇文章之前对这个问题发表评论的人。

【讨论】:

  • 这两件事情我也做了,但还是有这个问题。
  • 嘿@jay 快速提问。如果存储密钥的 Redis 服务器被擦除会怎样?您是否必须重新登录所有 Identity Server 实例,或者应用程序是否会自动生成可与旧 cookie 一起使用的新密钥?谢谢!
  • @HubertJarema - 我说我曾经注意到除了再次登录之外必须做任何事情。自从这篇文章以来,我已经擦除了我的 Redis 数据库数百次,但我从来没有得到任何理由去考虑它:)
【解决方案2】:

对于使用 Kubernetes 的用户,可以使用文件系统密钥存储提供程序

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"/app/key-storage"));
}

目录“/app/key-storage”映射到支持 nfs 的持久卷。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-key-storage
spec:
  selector:
    matchLabels:
      type: nfs-pv
  storageClassName: manual
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Mi
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
  labels:
    type: nfs-pv
spec:
  storageClassName: manual
  capacity:
    storage: 10Mi
  accessModes:
    - ReadWriteMany
  nfs:
    server: <server>
    path: /<path>
  persistentVolumeReclaimPolicy: Delete

在 IDP 部署中

template:
  spec:
    containers:
      - name: <name>
        volumeMounts:
          - name: key-storage
            mountPath: /app/key-storage
            readOnly: false
    volumes:
      - name: key-storage
        persistentVolumeClaim:
        claimName: pvc-key-storage

而且您需要签名证书。这可以作为机密添加,然后 IDP 部署可以使用另一个卷来装载机密(未显示)。

apiVersion: v1
kind: Secret
metadata:
  name: cert-secret
  labels:
    app: <app-label>
type: Opaque
data:
  signingcert.pfx: <base64 cert value>

【讨论】:

  • 嘿,我正在尝试实现这一点(使用跨 kubernetes pod 的共享文件夹)并且仍然遇到数据保护问题。是否涉及任何其他配置?谢谢
猜你喜欢
  • 2016-10-24
  • 2019-04-21
  • 2011-10-19
  • 1970-01-01
  • 1970-01-01
  • 2013-10-17
  • 2013-01-19
  • 2017-09-16
  • 1970-01-01
相关资源
最近更新 更多