Skip to content

Commit

Permalink
Include pem chain (#17)
Browse files Browse the repository at this point in the history
* updated to support 2 store types. HCVKV and HCV.

* Updated to distinguish between multiple supported store types.

* Fixed store path and mount point mapping

* updated doc to reflect multiple store type configs.

* removed sensitive info from tracelog.

* Fixed issue with path not being resolved before attempting to write cert.

* Removed KEY_SECRET from PutCertificate Task

* Modified PutCertificate to include ---BEGIN ----END banners (for key+cert)

* Allows for Recursive subfolder inventory

* Update readme to call out cert store limits

* Adding InputValidation for KeyValue secrets

* including certificate chain when enrolling via platform.

* added flag on store type to indicate whether to include cert chain

* fixed issue when checking for revocation time for inventory.
  • Loading branch information
joevanwanzeeleKF committed Jun 21, 2023
1 parent a679306 commit 7f9eecb
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 31 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ This integration was built on the .NET Core 3.1 target framework and are compati

1. It is not necessary to use the Vault root token when creating a Certificate Store for HashicorpVault. We recommend creating a token with policies that reflect the minimum permissions necessary to perform the intended operations.

1. For the Key-Value secrets engine, the certificates are stored as an entry with 2 fields.
1. For the Key-Value secrets engine, the certificates are stored as an entry with these fields.

- `PUBLIC_KEY` - The certificate public key
- `PUBLIC_KEY_<n>` - The nth certificate in the chain
- `PRIVATE_KEY` - The certificate private key

**Note**: Key/Value secrets that do not include these keys (PUBLIC_KEY, and PRIVATE_KEY), will be ignored during inventory scans.
Expand Down Expand Up @@ -126,6 +127,7 @@ This integration was built on the .NET Core 3.1 target framework and are compati
- **VaultServerUrl** - type: *string*, *required*
- **VaultToken** - type: *secret*, *required*
- **SubfolderInventory** - type: *bool* (By default, this is set to false. Not a required field)
- **IncludeCertChain** - type: *bool* (If true, the available intermediate certificates will also be written to Vault during enrollment)

![](images/store_type_fields.png)

Expand Down
59 changes: 42 additions & 17 deletions hashicorp-vault-orchestrator/HcvKeyValueClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class HcvKeyValueClient : IHashiClient

//private VaultClientSettings clientSettings { get; set; }

public HcvKeyValueClient(string vaultToken, string serverUrl, string mountPoint, string storePath,bool SubfolderInventory = false)
public HcvKeyValueClient(string vaultToken, string serverUrl, string mountPoint, string storePath, bool SubfolderInventory = false)
{
// Initialize one of the several auth methods.
IAuthMethodInfo authMethod = new TokenAuthMethodInfo(vaultToken);
Expand All @@ -54,7 +54,7 @@ public HcvKeyValueClient(string vaultToken, string serverUrl, string mountPoint,
public async Task<List<string>> ListComponentPathsAsync(string storagePath)
{
VaultClient.V1.Auth.ResetVaultToken();
List<string> componentPaths = new List<string> {};
List<string> componentPaths = new List<string> { };
try
{
Secret<ListInfo> listInfo = (await VaultClient.V1.Secrets.KeyValue.V2.ReadSecretPathsAsync(storagePath, _mountPoint));
Expand Down Expand Up @@ -89,8 +89,6 @@ public async Task<CurrentInventoryItem> GetCertificate(string key)
var relativePath = fullPath.Substring(_storePath.Length);
try
{


try
{
if (_mountPoint == null)
Expand Down Expand Up @@ -188,7 +186,7 @@ public async Task<IEnumerable<string>> GetVaults()
return vaults;
}

public async Task PutCertificate(string certName, string contents, string pfxPassword)
public async Task PutCertificate(string certName, string contents, string pfxPassword, bool includeChain)
{
VaultClient.V1.Auth.ResetVaultToken();

Expand All @@ -215,13 +213,12 @@ public async Task PutCertificate(string certName, string contents, string pfxPas
alias = p.Aliases.Cast<string>().SingleOrDefault(a => p.IsKeyEntry(a));
logger.LogTrace($"Alias = {alias}");
var publicKey = p.GetCertificate(alias).Certificate.GetPublicKey();

logger.LogTrace($"publicKey = {publicKey}");
var KeyEntry = p.GetKey(alias);
// logger.LogTrace($"KeyEntry = {KeyEntry}");
if (KeyEntry == null) throw new Exception("Unable to retrieve private key");

var privateKey = KeyEntry.Key;
// logger.LogTrace($"privateKey = {privateKey}");
var keyPair = new AsymmetricCipherKeyPair(publicKey, privateKey);

pemWriter.WriteObject(keyPair.Private);
Expand All @@ -235,20 +232,48 @@ public async Task PutCertificate(string certName, string contents, string pfxPas
logger.LogTrace("Finished Extracting Private Key...");
}
}
var pubCertPem = Pemify(Convert.ToBase64String(p.GetCertificate(alias).Certificate.GetEncoded()));

var pubCert = p.GetCertificate(alias).Certificate.GetEncoded();
var pubCertPem = Pemify(Convert.ToBase64String(pubCert));

// add the certs in the chain

var pemChain = new List<string>();
var chain = p.GetCertificateChain(alias).ToList();

chain.ForEach(c =>
{
var cert = c.Certificate.GetEncoded();
var encoded = Pemify(Convert.ToBase64String(cert));
pemChain.Add(encoded);
});

try
{
certDict.Add("PRIVATE_KEY", privateKeyString);
certDict.Add("PUBLIC_KEY", pubCertPem);

if (includeChain)
{
var i = 1;
pemChain.ForEach(pc =>
{
if (pc != pubCertPem)
{
certDict.Add($"PUBLIC_KEY_{i}", pc);
i++;
}
});

}
}
catch (Exception ex)
{
logger.LogError("Error parsing certificate content", ex);
throw;
}
try
{
{
var fullPath = _storePath + certName;

if (_mountPoint == null)
Expand Down Expand Up @@ -297,8 +322,8 @@ public async Task<IEnumerable<CurrentInventoryItem>> GetCertificates()
VaultClient.V1.Auth.ResetVaultToken();
_storePath = _storePath.TrimStart('/');
List<string> subPaths = new List<string>();
//Grabs the list of subpaths to get certificates from, if SubFolder Inventory is turned on.
//Otherwise just define the single path _storePath
//Grabs the list of subpaths to get certificates from, if SubFolder Inventory is turned on.
//Otherwise just define the single path _storePath
if (_subfolderInventory == true)
{
subPaths = (await ListComponentPathsAsync(_storePath));
Expand All @@ -325,12 +350,12 @@ public async Task<IEnumerable<CurrentInventoryItem>> GetCertificates()
{
certNames = (await VaultClient.V1.Secrets.KeyValue.V2.ReadSecretPathsAsync(path, mountPoint: _mountPoint)).Data.Keys.ToList();
}
certNames.ForEach(k =>
{
var cert = GetCertificate($"{relative_path}{k}").Result;
if (cert != null) certs.Add(cert);
});

certNames.ForEach(k =>
{
var cert = GetCertificate($"{relative_path}{k}").Result;
if (cert != null) certs.Add(cert);
});
}
catch (Exception ex)
{
Expand Down
5 changes: 3 additions & 2 deletions hashicorp-vault-orchestrator/HcvKeyfactorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ public async Task<CurrentInventoryItem> GetCertificate(string key)

string revokeTime;
content.data.TryGetValue("revocation_time", out revokeTime);


if (revokeTime.Equals(0))
if (revokeTime.Equals("0"))
{
var inventoryItem = new CurrentInventoryItem()
{
Expand Down Expand Up @@ -130,7 +131,7 @@ public Task<IEnumerable<string>> GetVaults()
throw new NotSupportedException();
}

public Task PutCertificate(string certName, string contents, string pfxPassword)
public Task PutCertificate(string certName, string contents, string pfxPassword, bool includeChain)
{
throw new NotSupportedException();
}
Expand Down
2 changes: 1 addition & 1 deletion hashicorp-vault-orchestrator/IHashiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface IHashiClient
Task<IEnumerable<CurrentInventoryItem>> GetCertificates();
Task<CurrentInventoryItem> GetCertificate(string key);
Task<IEnumerable<string>> GetVaults();
Task PutCertificate(string certName, string contents, string pfxPassword);
Task PutCertificate(string certName, string contents, string pfxPassword, bool includeChain);
Task<bool> DeleteCertificate(string certName);
}
}
13 changes: 5 additions & 8 deletions hashicorp-vault-orchestrator/Jobs/JobBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public abstract class JobBase

public bool SubfolderInventory { get; set; }

public bool IncludeCertChain { get; set; }

public string MountPoint { get; set; } // the mount point of the KV secrets engine. defaults to KV

public string RoleName { get; set; }
Expand Down Expand Up @@ -71,14 +73,9 @@ private void InitProps(dynamic props, string capability)
VaultServerUrl = props["VaultServerUrl"];
SecretsEngine = props["SecretsEngine"];
MountPoint = props["MountPoint"] ?? null;
if (props["SubfolderInventory"] == null)
{
SubfolderInventory = false;
}
else
{
SubfolderInventory = props["SubfolderInventory"];
}

SubfolderInventory = props["SubfolderInventory"] ?? false;
IncludeCertChain = props["IncludeCertChain"] ?? false;

var isPki = capability.Contains("HCVPKI");

Expand Down
2 changes: 1 addition & 1 deletion hashicorp-vault-orchestrator/Jobs/Management.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st
try
{
// uploadCollection is either not null or an exception was thrown.
var cert = VaultClient.PutCertificate(alias, entryContents, pfxPassword);
var cert = VaultClient.PutCertificate(alias, entryContents, pfxPassword, IncludeCertChain);
complete.Result = OrchestratorJobStatusJobResult.Success;
}
catch (Exception ex)
Expand Down
Binary file modified images/store_type_fields.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion readme_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ This integration was built on the .NET Core 3.1 target framework and are compati

1. It is not necessary to use the Vault root token when creating a Certificate Store for HashicorpVault. We recommend creating a token with policies that reflect the minimum permissions necessary to perform the intended operations.

1. For the Key-Value secrets engine, the certificates are stored as an entry with 2 fields.
1. For the Key-Value secrets engine, the certificates are stored as an entry with these fields.

- `PUBLIC_KEY` - The certificate public key
- `PUBLIC_KEY_<n>` - The nth certificate in the chain
- `PRIVATE_KEY` - The certificate private key

**Note**: Key/Value secrets that do not include these keys (PUBLIC_KEY, and PRIVATE_KEY), will be ignored during inventory scans.
Expand Down Expand Up @@ -83,6 +84,7 @@ This integration was built on the .NET Core 3.1 target framework and are compati
- **VaultServerUrl** - type: *string*, *required*
- **VaultToken** - type: *secret*, *required*
- **SubfolderInventory** - type: *bool* (By default, this is set to false. Not a required field)
- **IncludeCertChain** - type: *bool* (If true, the available intermediate certificates will also be written to Vault during enrollment)

![](images/store_type_fields.png)

Expand Down

0 comments on commit 7f9eecb

Please sign in to comment.