diff --git a/CHANGELOG.md b/CHANGELOG.md index 060ae7c7..f09bde5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ By default, the ```Authorization: Bearer ``` scheme is used. You can override it using the ```VaultClientSettings.UseVaultTokenHeaderInsteadOfAuthorizationHeader``` flag. * [GH-71] SSK Key Signing + * [GH-122] CloudFoundry Auth Method: Support for CloudFoundry login tokens including ability to create signatures. **BREAKING CHANGES:** diff --git a/README.md b/README.md index 7c743418..2d2ba01c 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,30 @@ IVaultClient vaultClient = new VaultClient(vaultClientSettings); // vault token/policies mapped to the azure jwt ``` +#### CloudFoundry Auth Method + +```cs +// setup the CloudFoundry based auth to get the right token. + +IAuthMethodInfo authMethod = new CloudFoundryAuthMethodInfo(roleName, instanceCertContent, instanceKeyContent); +var vaultClientSettings = new VaultClientSettings("https://MY_VAULT_SERVER:8200", authMethod); + +IVaultClient vaultClient = new VaultClient(vaultClientSettings); + +// any operations done using the vaultClient will use the +// vault token/policies mapped to the CloudFoundry jwt +``` + +##### CloudFoundry Signature Creation + + - VaultSharp also provides a helper class to generate on-demand CloudFoundry signature. + - Use the ```CloudFoundrySignatureProvider``` class as follows + +```cs +var signing_time = CloudFoundrySignatureProvider.GetFormattedSigningTime(DateTime.UtcNow); +var signature = CloudFoundrySignatureProvider.GetSignature(signingTime, cfInstanceCertContent, roleName, cfInstanceKeyContent); +``` + #### GitHub Auth Method ```cs diff --git a/src/VaultSharp/V1/AuthMethods/AuthMethodProvider.cs b/src/VaultSharp/V1/AuthMethods/AuthMethodProvider.cs index 1379ee83..7fb302d6 100644 --- a/src/VaultSharp/V1/AuthMethods/AuthMethodProvider.cs +++ b/src/VaultSharp/V1/AuthMethods/AuthMethodProvider.cs @@ -5,6 +5,7 @@ using VaultSharp.V1.AuthMethods.AWS; using VaultSharp.V1.AuthMethods.Azure; using VaultSharp.V1.AuthMethods.Cert; +using VaultSharp.V1.AuthMethods.CloudFoundry; using VaultSharp.V1.AuthMethods.GitHub; using VaultSharp.V1.AuthMethods.Kerberos; using VaultSharp.V1.AuthMethods.Kubernetes; @@ -35,6 +36,8 @@ public AuthMethodProvider(Polymath polymath) public IAzureAuthMethod Azure => throw new NotImplementedException(); + public ICloudFoundryAuthMethod CloudFoundry => throw new NotImplementedException(); + public IGitHubAuthMethod GitHub => throw new NotImplementedException(); public IGitHubAuthMethod GoogleCloud => throw new NotImplementedException(); diff --git a/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodInfo.cs b/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodInfo.cs index 920ccd4c..30776b12 100644 --- a/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodInfo.cs +++ b/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodInfo.cs @@ -1,12 +1,9 @@ -using Newtonsoft.Json; -using VaultSharp.Core; -using VaultSharp.V1.AuthMethods.CloudFoundry.Signature; +using VaultSharp.Core; namespace VaultSharp.V1.AuthMethods.CloudFoundry { public class CloudFoundryAuthMethodInfo : AbstractAuthMethodInfo { - [JsonIgnore] public override AuthMethodType AuthMethodType => AuthMethodType.CloudFoundry; /// @@ -16,7 +13,6 @@ public class CloudFoundryAuthMethodInfo : AbstractAuthMethodInfo /// /// The mount point. /// - [JsonIgnore] public string MountPoint { get; } /// @@ -26,19 +22,19 @@ public class CloudFoundryAuthMethodInfo : AbstractAuthMethodInfo /// /// The role name. /// - [JsonProperty("role")] public string RoleName { get; } /// /// [required] - /// Gets the Signature for getting a token for a service account. + /// The full body of the file available at the path denoted by CF_INSTANCE_CERT. /// - /// - /// The Signature. - /// - [JsonProperty("signature")] - public CloudFoundrySignature Signature { get; } + public string CFInstanceCertContent { get; } + /// + /// [required] + /// The full body of the file available at the path denoted by CF_INSTANCE_KEY. + /// + public string CFInstanceKeyContent { get; } /// /// Initializes a new instance of the class. @@ -46,12 +42,14 @@ public class CloudFoundryAuthMethodInfo : AbstractAuthMethodInfo /// [required] /// The name of the role against which the login is being attempted. /// - /// - /// [required] - /// Gets the Signature for getting a token for a service account. + /// [required] + /// The full body of the file available at the path denoted by CF_INSTANCE_CERT. + /// + /// [required] + /// The full body of the file available at the path denoted by CF_INSTANCE_KEY. /// - public CloudFoundryAuthMethodInfo(string roleName, CloudFoundrySignature signature) - : this(AuthMethodType.CloudFoundry.Type, roleName, signature) + public CloudFoundryAuthMethodInfo(string roleName, string instanceCertContent, string instanceKeyContent) + : this(AuthMethodType.CloudFoundry.Type, roleName, instanceCertContent, instanceKeyContent) { } @@ -62,20 +60,23 @@ public CloudFoundryAuthMethodInfo(string roleName, CloudFoundrySignature signatu /// [required] /// The name of the role against which the login is being attempted. /// - /// - /// [required] - /// Gets the Signature for getting a token for a service account. + /// [required] + /// The full body of the file available at the path denoted by CF_INSTANCE_CERT. /// - - public CloudFoundryAuthMethodInfo(string mountPoint, string roleName, CloudFoundrySignature signature) + /// [required] + /// The full body of the file available at the path denoted by CF_INSTANCE_KEY. + /// + public CloudFoundryAuthMethodInfo(string mountPoint, string roleName, string instanceCertContent, string instanceKeyContent) { Checker.NotNull(mountPoint, "mountPoint"); Checker.NotNull(roleName, "roleName"); - Checker.NotNull(signature, "signature"); + Checker.NotNull(instanceCertContent, "instanceCertContent"); + Checker.NotNull(instanceKeyContent, "instanceKeyContent"); MountPoint = mountPoint; RoleName = roleName; - Signature = signature; + CFInstanceCertContent = instanceCertContent; + CFInstanceKeyContent = instanceKeyContent; } } } diff --git a/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodLoginProvider.cs b/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodLoginProvider.cs index 07033931..af97ff4c 100644 --- a/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodLoginProvider.cs +++ b/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundryAuthMethodLoginProvider.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Threading.Tasks; using VaultSharp.Core; +using VaultSharp.V1.AuthMethods.CloudFoundry.Signature; using VaultSharp.V1.Commons; namespace VaultSharp.V1.AuthMethods.CloudFoundry @@ -11,8 +12,7 @@ namespace VaultSharp.V1.AuthMethods.CloudFoundry internal class CloudFoundryAuthMethodLoginProvider : IAuthMethodLoginProvider { private readonly CloudFoundryAuthMethodInfo _cloudFoundryAuthMethodInfo; - private readonly Polymath _polymath; - + private readonly Polymath _polymath; public CloudFoundryAuthMethodLoginProvider(CloudFoundryAuthMethodInfo cloudFoundryAuthMethodInfo, Polymath polymath) { @@ -23,10 +23,21 @@ public CloudFoundryAuthMethodLoginProvider(CloudFoundryAuthMethodInfo cloudFound public async Task GetVaultTokenAsync() { - var requestData = _cloudFoundryAuthMethodInfo.Signature; + var signingTime = DateTime.UtcNow; + var signature = CloudFoundrySignatureProvider.GetSignature(signingTime, _cloudFoundryAuthMethodInfo.CFInstanceCertContent, _cloudFoundryAuthMethodInfo.RoleName, _cloudFoundryAuthMethodInfo.CFInstanceKeyContent); + + var requestData = new + { + role = _cloudFoundryAuthMethodInfo.RoleName, + cf_instance_cert = _cloudFoundryAuthMethodInfo.CFInstanceCertContent, + signing_time = CloudFoundrySignatureProvider.GetFormattedSigningTime(signingTime), + signature + }; - // make an unauthenticated call to Vault, since this is the call to get the token. It shouldn't need a token. + // make an unauthenticated call to Vault, since this is the call to get the token. + // It shouldn't need a token. var response = await _polymath.MakeVaultApiRequest>>(LoginResourcePath, HttpMethod.Post, requestData, unauthenticated: true).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext); + _cloudFoundryAuthMethodInfo.ReturnedLoginAuthInfo = response?.AuthInfo; if (response?.AuthInfo != null && !string.IsNullOrWhiteSpace(response.AuthInfo.ClientToken)) @@ -35,7 +46,6 @@ public async Task GetVaultTokenAsync() } throw new Exception("The call to the Vault authentication method backend did not yield a client token. Please verify your credentials."); - } private string LoginResourcePath @@ -46,8 +56,5 @@ private string LoginResourcePath return endpoint; } } - - - } } diff --git a/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundrySignatureProvider.cs b/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundrySignatureProvider.cs new file mode 100644 index 00000000..026dccab --- /dev/null +++ b/src/VaultSharp/V1/AuthMethods/CloudFoundry/CloudFoundrySignatureProvider.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Text; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Utilities.IO.Pem; + +namespace VaultSharp.V1.AuthMethods.CloudFoundry.Signature +{ + public class CloudFoundrySignatureProvider + { + public static string GetSignature(DateTime dateTime, string cfInstanceCertContent, string roleName, string cfInstanceKeyContent) + { + var formattedSigningTime = GetFormattedSigningTime(dateTime); + var stringToSign = $"{formattedSigningTime}{cfInstanceCertContent}{roleName}"; + + var data = Encoding.UTF8.GetBytes(stringToSign); + + byte[] keyBytes; + + using (var reader = new StringReader(cfInstanceKeyContent)) + { + var pemReader = new PemReader(reader); + var pemObject = pemReader.ReadPemObject(); + keyBytes = pemObject.Content; + } + + var seq = (Asn1Sequence)Asn1Object.FromByteArray(keyBytes); + var rsa = RsaPrivateKeyStructure.GetInstance(seq); + + var signer = new PssSigner(new RsaEngine(), new Sha256Digest(), 222); + signer.Init(true, new RsaKeyParameters(true, rsa.Modulus, rsa.PrivateExponent)); + signer.BlockUpdate(data, 0, data.Length); + var signature = signer.GenerateSignature(); + + return $"v1:{Convert.ToBase64String(signature)}"; + } + + public static string GetFormattedSigningTime(DateTime signingTime) + { + return signingTime.ToString("yyyy-MM-ddTHH:mm:ssZ"); + } + } +} diff --git a/src/VaultSharp/V1/AuthMethods/CloudFoundry/Signature/CloudFoundrySignature.cs b/src/VaultSharp/V1/AuthMethods/CloudFoundry/Signature/CloudFoundrySignature.cs deleted file mode 100644 index 3a05f6fd..00000000 --- a/src/VaultSharp/V1/AuthMethods/CloudFoundry/Signature/CloudFoundrySignature.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Newtonsoft.Json; - -namespace VaultSharp.V1.AuthMethods.CloudFoundry.Signature -{ - public class CloudFoundrySignature - { - [JsonProperty("role")] - public string RoleName { get; set; } - - [JsonProperty("signing_time")] - public string SigningTime { get; set; } - - [JsonProperty("cf_instance_cert")] - public string InstanceCert { get; set; } - - [JsonProperty("signature")] - public string SignatureKey { get; set; } - - } -} \ No newline at end of file diff --git a/src/VaultSharp/V1/AuthMethods/CloudFoundry/Signature/CloudFoundrySignatureProvider.cs b/src/VaultSharp/V1/AuthMethods/CloudFoundry/Signature/CloudFoundrySignatureProvider.cs deleted file mode 100644 index f9b68a43..00000000 --- a/src/VaultSharp/V1/AuthMethods/CloudFoundry/Signature/CloudFoundrySignatureProvider.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Threading; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Signers; -using Org.BouncyCastle.Utilities.IO.Pem; - -namespace VaultSharp.V1.AuthMethods.CloudFoundry.Signature -{ - public class CloudFoundrySignatureProvider - { - private static readonly ReaderWriterLockSlim FileLock = new ReaderWriterLockSlim(); - - public CloudFoundrySignature GetSignatureToken(string roleName) - { - var instanceCert = GetBodyFromFile("CF_INSTANCE_CERT"); - - var signingTime = Iso816UtcNow(); - var stringToSign = $"{signingTime}{instanceCert}{roleName}"; - - var instanceKey = GetBodyFromFile("CF_INSTANCE_KEY"); - - var data = Encoding.UTF8.GetBytes(stringToSign); - var signatureKey = GenerateSignature(instanceKey, data); - - var token = new CloudFoundrySignature - { - InstanceCert = instanceCert, - SigningTime = signingTime, - RoleName = roleName, - SignatureKey = signatureKey - }; - - return token; - } - - private string GenerateSignature(string privateKeyPem, byte[] data) - { - byte[] keyBytes; - using (var reader = new StringReader(privateKeyPem)) - { - var pemReader = new PemReader(reader); - var pemObject = pemReader.ReadPemObject(); - keyBytes = pemObject.Content; - } - - var seq = (Asn1Sequence)Asn1Object.FromByteArray(keyBytes); - var rsa = RsaPrivateKeyStructure.GetInstance(seq); - - var signer = new PssSigner(new RsaEngine(), new Sha256Digest(), 222); - signer.Init(true, new RsaKeyParameters(true, rsa.Modulus, rsa.PrivateExponent)); - signer.BlockUpdate(data, 0, data.Length); - var signature = signer.GenerateSignature(); - - return $"v1:{Convert.ToBase64String(signature)}"; - } - - private string GetBodyFromFile(string filePath) - { - var path = Environment.GetEnvironmentVariable(filePath); - if (path == null) { return null; } - - try - { - FileLock.EnterReadLock(); - return File.ReadAllText(path); - } - finally - { - FileLock.ExitReadLock(); - } - - } - - private string Iso816UtcNow() - { - return DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); - } - } -} diff --git a/src/VaultSharp/V1/AuthMethods/IAuthMethod.cs b/src/VaultSharp/V1/AuthMethods/IAuthMethod.cs index f06d8f66..81ff48d2 100644 --- a/src/VaultSharp/V1/AuthMethods/IAuthMethod.cs +++ b/src/VaultSharp/V1/AuthMethods/IAuthMethod.cs @@ -3,6 +3,7 @@ using VaultSharp.V1.AuthMethods.AWS; using VaultSharp.V1.AuthMethods.Azure; using VaultSharp.V1.AuthMethods.Cert; +using VaultSharp.V1.AuthMethods.CloudFoundry; using VaultSharp.V1.AuthMethods.GitHub; using VaultSharp.V1.AuthMethods.Kerberos; using VaultSharp.V1.AuthMethods.Kubernetes; @@ -39,6 +40,11 @@ public interface IAuthMethod /// IAzureAuthMethod Azure { get; } + /// + /// + /// + ICloudFoundryAuthMethod CloudFoundry { get; } + /// /// Hmm. ///