.net-5identitymodelsteeltoe

Configure ClientCredential of IdentityModel using values retrieved from Cloud Foundry CredHub


Short Problem Statement:
How to get value from Cloud Foundry CredHub to be used inside ConfigureServices(IServiceCollection services)?

Existing Condition:
Let's say that I have a class called SystemConfig for holding configuration values. I use a combination of json file and dotnet secrets in 'Development' stage for storing those values, and they can be retrieved via

services.Configure<SystemConfig>(Configuration.GetSection("GLOBAL"));

In 'SIT' stage, I use Pivotal Cloud Foundry to host the application and a combination of ConfigServer - CredHub for storing configuration values. Values from CredHub can be retrieved inside Configure(IApplicationBuilder app) via

var credhub = app.ApplicationServices.GetService<IOptions<CloudFoundryServicesOptions>>().Value.Services["credhub"].First(x => x.Name.Equals("credhub-instance"));
var sysConfig = app.ApplicationServices.GetService<IOptions<SystemConfig>>().Value;
sysConfig.SAMPLE_A = credhub.Credentials[nameof(sysConfig.SAMPLE_A)].Value;

The problem arises when I need to configure Access Token Management from IdentityModel.AspNetCore inside ConfigureServices:

services.AddAccessTokenManagement(x => 
{
     x.Client.Clients.Add("key_sample", new IdentityModel.Client.ClientCredentialsTokenRequest()
     {
         Address = "sysConfig.SAMPLE_A",
         ClientId = "taken from sysConfig as well",
         ClientSecret = "taken from sysConfig as well"
     });
});

The method AddAccessTokenManagement can only be used inside ConfigureService(IServiceCollection services) but at the same time I don't have values from CredHub yet because they are retrieved inside Configure(IApplicationBuilder app).
Usage of services.BuildServiceProvider() is not recommended as well since it will create additional copy of Singleton services that might lead to unexpected bugs.

So the question boils down to :


Solution

  • I can't speak to the intricacies of IdentityModel here, and at a glance I don't see anything that makes this easy from that side, but I have a solution that should work that uses Steeltoe Connectors by customizing the way SsoServiceInfo can be located.

    Ideally Steeltoe would bind these credentials to SsoServiceInfo automatically, but there isn't currently anything built in that makes that connection. This code will work to retrieve the service info by the time we're done here:

    services.AddAccessTokenManagement(x =>
    {
        var creds = Configuration.GetServiceInfo<SsoServiceInfo>("credhub-instance");
        x.Client.Clients.Add("key_sample", new IdentityModel.Client.ClientCredentialsTokenRequest()
        {
            Address = creds.AuthDomain,
            ClientId = creds.ClientId,
            ClientSecret = creds.ClientSecret
        });
    });
    

    In order to enable the above code, we'll need to add a custom ServiceInfoFactory that can bind your credhub service instance to SsoServiceInfo:

    using Steeltoe.Connector.Services;
    using Steeltoe.Extensions.Configuration;
    
    public class AuthServiceInfoFactory : ServiceInfoFactory
    {
        public AuthServiceInfoFactory()
            : base(new Tags("credhub"), "credhub")
             // second param here for url scheme won't actually be used but can't be empty
        {
        }
    
        public override IServiceInfo Create(Service binding)
        {
            // these methods ship with Steeltoe, but look for "client_id" and "client_secret" - customize as desired
            var clientId = GetClientIdFromCredentials(binding.Credentials);
            var clientSecret = GetClientSecretFromCredentials(binding.Credentials);
            var authDomain = GetStringFromCredentials(binding.Credentials, "auth_domain");
    
            return new SsoServiceInfo(binding.Name, clientId, clientSecret, authDomain);
        }
    }
    

    Next add the ServiceInfoFactoryAssembly attribute to AssemblyInfo.cs so Steeltoe can find your new factory:

    using Steeltoe.Connector;
    
    [assembly: ServiceInfoFactoryAssembly]
    

    Also, Connectors build on the CloudFoundry Configuration Provider, so be sure you've added it to your host builder or configuration builder with something like this:

    Host.CreateDefaultBuilder(args)
         .AddCloudFoundryConfiguration();
    

    Finally, please note that as of v3.0, Steeltoe won't recognize vcap:services unless vcap:application has been set, so you might want use this configuration format for non-CloudFoundry environments:

      "services": {
        "credhub": [{
          "name": "credhub-instance",
          "credentials": {
            "Client_Id": "myClientId",
            "Client_Secret": "myClientSecret"
          },
          "tags": ["credhub"]
        }]
      }