diff --git a/.gitignore b/.gitignore index b4ffd13..916757a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. + # User-specific files +creds.json **/temp/** artifacts/** *.suo diff --git a/README.MD b/README.MD index 9b63c65..7d14dee 100644 --- a/README.MD +++ b/README.MD @@ -30,7 +30,6 @@ applications: ``` - ** Adjust URL of the Kerberos buildpack to latest version. You can get the full zip URL from [Releases](https://github.com/macsux/kerberos-buildpack/releases) page. ## How it works @@ -43,6 +42,65 @@ Core libraries used by .NET and Java apps use MIT Kerberos to do Kerberos (aka I A sidecar runs in background that will obtain tickets Kerberos .NET +## Secure credentials with CredHub integration + +Instead of of injecting service credentials as environmental variables, this buildpack supports injecting it securely via CredHub integration. This will safely store creds associated with the app inside CredHub, and they will be injected as environmental variables when container starts. This will be only visible from the app - they will not be visible if the user tries to query environmental variables associated with the app. + +#### Prerequisite: [CredHub service broker](https://network.tanzu.vmware.com/products/credhub-service-broker) installed on the platform + +1. Omit KRB_SERVICE_ACCOUNT / KRB_PASSWORD from the manifest + +2. Create a file creds.json that looks similar to this: + + ```json + { + "ServiceAccount": "iwaclient@macsux.com", + "Password": "P@ssw0rd" + } + ``` + +3. Create a CredHub service instance that carries the above credentials as following: + + ``` + cf create-service credhub default CREDS_SERVICE_INSTANCE_NAME -c .\creds.json -t kerberos-service-principal + ``` + +4. Bind the credentials to the app + + ``` + cf bind-service APP_NAME CREDS_SERVICE_INSTANCE_NAME + ``` + +5. Push the app + +## Embedding Kerberos configuration into buildpack + +By default the buildpack will attempt to generate an MIT Kerberos configuration file (krb5.conf) out of combination of KRB5_KDC and the realm portion of the service account (everything after `@`). This may not be sufficient in more complex environments and require full control of the `krb5.conf` to properly work. It may also be desirable to not have to include location of the KDC in the push manifest. In order to support these scenarios, the buildpack allows embedding `krb5.conf` before being deployed on the platform. This has the advantage of being uniformly applied to all apps and move control over this file in the hands of central platform operator. In order to include environment specific `krb5.conf` in the buildpack, it must be placed into the buildpack zip file under `/deps/.krb5/krb5.conf`. This can be accomplished by creating a local directory structure that looks like this: + +``` +. +├── KerberosBuildpack-linux-x64-v1.0.0.zip +├── deps +│ └── .krb5 +│ └── krb5.conf +``` + +After run the following command to patch the zip file with config file: + +Powershell + +``` +Compress-Archive -Update -Path deps -DestinationPath KerberosBuildpack-linux-x64-v1.0.0.zip +``` + +Linux shell + +``` +zip -ur KerberosBuildpack-linux-x64-v1.0.0.zip deps +``` + + + ## Troubleshooting Recommendation is to start with sample app included, which exposes the folowing endpoints: diff --git a/sample/KerberosDemo/manifest-WIP.yml b/sample/KerberosDemo/manifest-WIP.yml new file mode 100644 index 0000000..4e10040 --- /dev/null +++ b/sample/KerberosDemo/manifest-WIP.yml @@ -0,0 +1,14 @@ +--- +applications: +- name: KerberosDemo + path: bin/Debug/net5.0/publish + memory: 512M + health-check-type: none + buildpacks: + - https://github.com/macsux/kerberos-buildpack/releases/download/WIP/KerberosBuildpack-linux-x64-WIP.zip + - dotnet_core_buildpack + env: + KRB5_KDC: dc1.macsux.com + KRB_SERVICE_ACCOUNT: "" + KRB_PASSWORD: "" + ConnectionStrings__SqlServer: Server=dc1.macsux.com;Database=master;Trusted_Connection=True;TrustServerCertificate=True diff --git a/src/KerberosSidecar/CloudFoundry/ConfigurationExtensions.cs b/src/KerberosSidecar/CloudFoundry/ConfigurationExtensions.cs new file mode 100644 index 0000000..7d14cb9 --- /dev/null +++ b/src/KerberosSidecar/CloudFoundry/ConfigurationExtensions.cs @@ -0,0 +1,22 @@ +namespace KerberosSidecar.CloudFoundry; + +public static class ConfigurationExtensions +{ + public static IEnumerable GetServiceBindings(this IConfiguration config) + { + return config.GetSection("vcap:services").GetChildren().SelectMany(serviceTypeSection => + { + var serviceType = serviceTypeSection.Key; + + return serviceTypeSection.Get>(c => + { + c.BindNonPublicProperties = true; + }) + .Select(x => + { + x.Type = serviceType; + return x; + }); + }); + } +} \ No newline at end of file diff --git a/src/KerberosSidecar/CloudFoundry/ServiceBinding.cs b/src/KerberosSidecar/CloudFoundry/ServiceBinding.cs new file mode 100644 index 0000000..e26bc01 --- /dev/null +++ b/src/KerberosSidecar/CloudFoundry/ServiceBinding.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; + +#nullable disable +namespace KerberosSidecar.CloudFoundry; + +[PublicAPI] +[SuppressMessage("ReSharper", "InconsistentNaming")] +public class ServiceBinding +{ + public string Name { get; set; } + public string Type {get;set;} + public string Plan { get; set; } + public IConfigurationSection Credentials { get; set; } + public T GetCredentials() => Credentials.Get(); + public List Tags {get;set;} + public string SyslogDrainUrl {get;set;} + private string Syslog_Drain_Url { set => SyslogDrainUrl = value; } + public string Provider {get;set;} + public string Label {get;set;} + public string InstanceName { get; set; } + private string Instance_Name { set => InstanceName = value; } + public string BindingName {get;set;} + private string Binding_Name { set => BindingName = value; } +} \ No newline at end of file diff --git a/src/KerberosSidecar/CloudFoundry/ServiceCredentials.cs b/src/KerberosSidecar/CloudFoundry/ServiceCredentials.cs new file mode 100644 index 0000000..5d3a769 --- /dev/null +++ b/src/KerberosSidecar/CloudFoundry/ServiceCredentials.cs @@ -0,0 +1,11 @@ +#nullable disable +using JetBrains.Annotations; + +namespace KerberosSidecar.CloudFoundry; + +[PublicAPI] +public class ServiceCredentials +{ + public string ServiceAccount { get; set; } + public string Password { get; set; } +} \ No newline at end of file diff --git a/src/KerberosSidecar/KerberosSidecar.csproj b/src/KerberosSidecar/KerberosSidecar.csproj index 973ab3e..f8a8711 100644 --- a/src/KerberosSidecar/KerberosSidecar.csproj +++ b/src/KerberosSidecar/KerberosSidecar.csproj @@ -12,8 +12,8 @@ - + diff --git a/src/KerberosSidecar/Program.cs b/src/KerberosSidecar/Program.cs index 11fa1e4..5039b1d 100644 --- a/src/KerberosSidecar/Program.cs +++ b/src/KerberosSidecar/Program.cs @@ -1,8 +1,10 @@ #pragma warning disable IL2026 +using System.Net; using Kerberos.NET.Client; using Kerberos.NET.Configuration; using Kerberos.NET.Credentials; using KerberosSidecar; +using KerberosSidecar.CloudFoundry; using KerberosSidecar.HealthChecks; using KerberosSidecar.Spn; using Microsoft.AspNetCore.Builder; @@ -13,13 +15,15 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Steeltoe.Extensions.Configuration.CloudFoundry; var webHostBuilder = WebApplication.CreateBuilder(args); -webHostBuilder.Configuration.AddYamlFile("appsettings.yaml", optional: true, reloadOnChange: true); -webHostBuilder.Configuration.AddYamlFile($"appsettings.{webHostBuilder.Environment.EnvironmentName}.yaml", optional: true, reloadOnChange: true); - +webHostBuilder.Configuration + .AddYamlFile("appsettings.yaml", optional: true, reloadOnChange: true) + .AddYamlFile($"appsettings.{webHostBuilder.Environment.EnvironmentName}.yaml", optional: true, reloadOnChange: true) + .AddCloudFoundry(); var services = webHostBuilder.Services; services.AddOptions() .Configure(c => @@ -33,6 +37,18 @@ c.Kdc = config.GetValue("KRB_KDC"); c.RunOnce = config.GetValue("KRB_RunOnce"); }) + .Configure((options, config) => + { + var serviceBindingCredentials = config.GetServiceBindings() + .Where(x => x.Tags.Contains("kerberos-service-principal")) + .Select(x => x.GetCredentials()) + .FirstOrDefault(); + if (serviceBindingCredentials != null) + { + options.ServiceAccount = serviceBindingCredentials.ServiceAccount; + options.Password = serviceBindingCredentials.Password; + } + }) .PostConfigure((options, loggerFactory) => { var log = loggerFactory.CreateLogger(); @@ -40,10 +56,12 @@ var userKerbDir = Path.Combine(homeDir, ".krb5"); // default files to user's ~/.krb/ folder if not set + // ReSharper disable ConstantNullCoalescingCondition options.Kerb5ConfigFile ??= Path.Combine(userKerbDir, "krb5.conf"); options.KeytabFile ??= Path.Combine(userKerbDir, "krb5.keytab"); options.CacheFile ??= Path.Combine(userKerbDir, "krb5cc"); - options.GenerateKrb5 = options.Kerb5ConfigFile != null! ? !File.Exists(options.Kerb5ConfigFile) : true; + // ReSharper restore ConstantNullCoalescingCondition + options.GenerateKrb5 = options.Kerb5ConfigFile == null! || !File.Exists(options.Kerb5ConfigFile); Directory.CreateDirectory(Path.GetDirectoryName(options.Kerb5ConfigFile)!); Directory.CreateDirectory(Path.GetDirectoryName(options.KeytabFile)!);