Skip to content

Commit 52863ec

Browse files
spbsolubleLee Fineleefine02Keyfactor
authored
Ab#84816 (#8)
* ab#84816 * Update generated docs * ab#84816 * ab#84816 * ab#84816 * ab#84816 * ab#84816 * ab#84816 * Update generated docs * ab#84816 * ab#84816 * ab#84816 * Update generated docs * chore(docs): Generate screenshots * ab#84816 * chore(docs): Generate screenshots * ab#84816 --------- Co-authored-by: Lee Fine <lfine@keyfactor.com> Co-authored-by: Lee Fine <50836957+leefine02@users.noreply.github.com> Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
1 parent 7d809b0 commit 52863ec

25 files changed

Lines changed: 171 additions & 87 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
v1.2.0
2+
- Added support for labels
3+
- Added support for setting ttl duration
4+
- Added support for setting version destroy ttl duration
5+
- Added support for setting replication regions
6+
- Bug Fix: Modified logic to obtain organization from project to allow for recursive folder ownership between these layers
7+
18
v1.1.0
29
- Added new Entry Parameter of Tags to support the assignment of one-to-many organization tags to a certificate/secret being added as a new certificate/secret
310

GCPSecretManager/GCPClient.cs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal class GCPClient
2323
TagBindingsClient TagBindingsClient { get; set; }
2424
TagValuesClient TagValuesClient { get; set; }
2525
ProjectsClient ProjectsClient { get; set; }
26+
FoldersClient FoldersClient { get; set; }
2627

2728
private const string ResourcePrefix = "//secretmanager.googleapis.com/";
2829

@@ -35,6 +36,7 @@ public GCPClient(string projectId)
3536
TagBindingsClient = TagBindingsClient.Create();
3637
TagValuesClient = TagValuesClient.Create();
3738
ProjectsClient = ProjectsClient.Create();
39+
FoldersClient = FoldersClient.Create();
3840
}
3941

4042
public List<string> GetSecretNames()
@@ -92,7 +94,25 @@ public SecretWithLabels GetCertificateEntry(string name)
9294
rtnValue.Secret = version.Payload.Data.ToStringUtf8();
9395
rtnValue.Labels = string.Empty;
9496

95-
Secret secret = GetSecret(name);
97+
Secret secret = GetSecret(name.Substring(name.LastIndexOf("/")+1));
98+
rtnValue.TTLDuration = secret.Ttl;
99+
rtnValue.VersionDestroyTTLDuration = secret.VersionDestroyTtl;
100+
101+
102+
if (secret.Replication != null && secret.Replication.UserManaged != null && secret.Replication.UserManaged.Replicas != null && secret.Replication.UserManaged.Replicas.Count > 0)
103+
{
104+
foreach (Replication.Types.UserManaged.Types.Replica replica in secret.Replication.UserManaged.Replicas)
105+
{
106+
rtnValue.ReplicationRegions += $",{replica.Location}";
107+
if (replica.CustomerManagedEncryption != null && !string.IsNullOrEmpty(replica.CustomerManagedEncryption.KmsKeyName))
108+
{
109+
rtnValue.ReplicationRegions += $":{replica.CustomerManagedEncryption.KmsKeyName}";
110+
}
111+
}
112+
113+
rtnValue.ReplicationRegions = rtnValue.ReplicationRegions.Substring(1);
114+
}
115+
96116
List<string> labelsString = new List<string>();
97117
foreach(var label in secret.Labels)
98118
{
@@ -500,12 +520,23 @@ private string GetOrganizationFromProject()
500520
{
501521
_logger.MethodEntry(LogLevel.Debug);
502522

503-
string organization = string.Empty;
523+
string parent = string.Empty;
504524

505525
try
506526
{
507527
Project project = ProjectsClient.GetProject(new GetProjectRequest() { ProjectName = ProjectName.FromProject(ProjectId) });
508-
organization = project.Parent;
528+
parent = project.Parent;
529+
530+
while(string.IsNullOrEmpty(null))
531+
{
532+
if (parent.StartsWith("organizations/"))
533+
break;
534+
535+
if (!parent.StartsWith("folders/"))
536+
throw new Exception($"Invalid or unknown project parent - {parent}");
537+
538+
parent = FoldersClient.GetFolder(parent).Parent;
539+
}
509540
}
510541
catch (Exception ex)
511542
{
@@ -517,20 +548,23 @@ private string GetOrganizationFromProject()
517548
_logger.MethodExit(LogLevel.Debug);
518549
}
519550

520-
return organization.Substring(organization.IndexOf("/") + 1);
551+
return parent.Substring(parent.IndexOf("/") + 1);
521552
}
522553

523554
private void AssignLabels(string labels, MapField<string, string> labelMap)
524555
{
525-
List<(string, string)> labelsList = labels != null ? null :
556+
List<(string, string)> labelsList = labels == null ? null :
526557
labels.Split(',', StringSplitOptions.RemoveEmptyEntries)
527558
.Select(pair => pair.Split(':', 2))
528559
.Where(parts => parts.Length == 2)
529560
.Select(parts => (Key: parts[0].Trim(), Value: parts[1].Trim()))
530561
.ToList();
531562

532-
foreach (var label in labelsList)
533-
labelMap[label.Item1] = label.Item2;
563+
if (labelsList != null)
564+
{
565+
foreach (var label in labelsList)
566+
labelMap[label.Item1] = label.Item2;
567+
}
534568
}
535569
}
536570
}

GCPSecretManager/Inventory.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,12 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
7272
Dictionary<string, object> entryParameters = new()
7373
{
7474
{ "tags", secretTags },
75-
{ "labels", certificateEntry.Labels }
75+
{ "labels", certificateEntry.Labels },
76+
{ "replicationRegions", certificateEntry.ReplicationRegions },
77+
{ "ttlDuration", certificateEntry.TTLDuration?.ToTimeSpan().Days.ToString() },
78+
{ "versionDestroyTtlDuration", certificateEntry.VersionDestroyTTLDuration?.ToTimeSpan().Days.ToString() }
7679
};
7780

78-
Dictionary<string, object> secretTags = new Dictionary<string, object>();
79-
try
80-
{
81-
secretTags = client.GetSecretTags(secretName);
82-
}
83-
catch (Exception)
84-
{
85-
hasWarnings = true;
86-
continue;
87-
}
88-
8981
inventoryItems.Add(new CurrentInventoryItem()
9082
{
9183
ItemStatus = OrchestratorInventoryItemStatus.Unknown,

GCPSecretManager/Management.cs

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private bool PerformAdd(ManagementJobConfiguration config, GCPClient client, boo
109109
try
110110
{
111111
string secret = CertificateFormatter.ConvertCertificateEntryToSecret(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword);
112-
string labels = (config.JobProperties.ContainsKey("labels") && config.JobProperties["labels"] != null && !entryExists) ? config.JobProperties["labels"].ToString() : null;
112+
string labels = (config.JobProperties.ContainsKey("labels") && config.JobProperties["labels"] != null) ? config.JobProperties["labels"].ToString() : null;
113113

114114
int ttlDuration = 0;
115115
TimeSpan? ttlDurationTS = null;
@@ -174,50 +174,6 @@ private bool SetTags(ManagementJobConfiguration config, GCPClient client, out st
174174
.ToList();
175175

176176

177-
foreach ((string,string) tagValue in newTagKeyValues)
178-
{
179-
if (availableTagKeyValues.Exists(t => t.TagKey.ShortName == tagValue.Item1 && t.TagValues.Exists(t2 => t2.ShortName == tagValue.Item2)))
180-
{
181-
TagKeyValue keyValue = availableTagKeyValues.First(t => t.TagKey.ShortName == tagValue.Item1 && t.TagValues.Exists(t2 => t2.ShortName == tagValue.Item2));
182-
183-
try
184-
{
185-
client.SetSecretTag(config.JobCertificate.Alias, keyValue.TagValues.Find(t => t.ShortName == tagValue.Item2).Name);
186-
}
187-
catch (Exception ex)
188-
{
189-
hasWarnings = true;
190-
message += $"Error attempting to add tag key/value pair {tagValue.Item1}/{tagValue.Item2}: {ex.Message}";
191-
}
192-
}
193-
else
194-
{
195-
hasWarnings = true;
196-
message += $"Tag key/value pair {tagValue.Item1}/{tagValue.Item2} not set up as a valid organization level tag in GCP. Tag will not be assigned. ";
197-
}
198-
}
199-
200-
Logger.MethodExit(LogLevel.Debug);
201-
202-
return hasWarnings;
203-
}
204-
private bool SetTags(ManagementJobConfiguration config, GCPClient client, out string message)
205-
{
206-
Logger.MethodEntry(LogLevel.Debug);
207-
208-
bool hasWarnings = false;
209-
message = string.Empty;
210-
211-
List<TagKeyValue> availableTagKeyValues = client.GetTagKeysValues();
212-
213-
List<(string,string)> newTagKeyValues = config.JobProperties["tags"].ToString()
214-
.Split(',', StringSplitOptions.RemoveEmptyEntries)
215-
.Select(pair => pair.Split(':', 2))
216-
.Where(parts => parts.Length == 2)
217-
.Select(parts => (Key: parts[0].Trim(), Value: parts[1].Trim()))
218-
.ToList();
219-
220-
221177
foreach ((string,string) tagValue in newTagKeyValues)
222178
{
223179
if (availableTagKeyValues.Exists(t => t.TagKey.ShortName == tagValue.Item1 && t.TagValues.Exists(t2 => t2.ShortName == tagValue.Item2)))

GCPSecretManager/Models.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Google.Cloud.ResourceManager.V3;
2+
using Google.Protobuf.WellKnownTypes;
23
using System.Collections.Generic;
34

45
namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager
@@ -19,5 +20,8 @@ internal class SecretWithLabels
1920
{
2021
internal string Secret { get; set; }
2122
internal string Labels { get; set; }
23+
internal Duration TTLDuration { get; set; }
24+
internal Duration VersionDestroyTTLDuration { get; set; }
25+
internal string ReplicationRegions { get; set; }
2226
}
2327
}

README.md

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,12 @@ The Google Cloud Platform (GCP) Secret Manager Orchestrator Extension remotely m
3737
* PEM encoded certificate and unencrypted or encrypted private key with full certificate chain
3838
* PEM encoded certificate only
3939

40-
For use cases including an encrypted private key, please refer to [Certificate Encryption Details](#certificate-encryption-details) for more information on handling/storing the encryption password for the private key.
41-
42-
This extension also optionally supports the management of secret tags. **If** the optional Entry Parameter of "Tags" exists in the store type definition:
43-
* Inventory will return all tags assigned to a secert in the comma delimited format of "TagKey1:TagValue1,TagKey2:TagValue2,...,TagKeyN:TagValueN".
44-
* The same format of one-to-many tag key/value pairs ("TagKey1:TagValue1,TagKey2:TagValue2,...,TagKeyN:TagValueN") can be added to the "Tags" field during the setup of Management-Add jobs to assign tags to the secret **as long as each tag key/value pair is already set up as a valid Organization level tag key/value combination in GCP**.
45-
46-
Additional notes regarding tags:
47-
* This integration does **not** support Project level tags when adding new certificates (secrets). Only Organization level tags will be recognized.
48-
* The Tags field will be ignored when renewing/replacing a certificate since in this scenario the extension is only adding a new secret version and not replacing the entire secret. Assigning tags is only attempted when adding a completely new certificate (secret).
49-
* When adding a new secret, any errors attempting to add tags **will not** impact the adding of the secret. If a Management-Add job successfully adds a new certificate (secret) but fails to assign the tag, the job will be reported back with a status of Warning along with detailed messages why each tag could not be assigned. The certificate (secret) itself, however, **will** be added.
50-
* If multiple tags are provided, and errors occur on some but not others, the successful ones will be assigned to the certificate (secret) and warning messages will be written to the log and job status for the others.
40+
Additional features:
41+
* For use cases including an encrypted private key, please refer to [Certificate Encryption Details](#certificate-encryption-details) for more information on handling/storing the encryption password for the private key.
42+
* For information on Tag Support, please refer to [Tag Support](#tag-support)
43+
* For information on Label Support, please refer to [Label Support](#label-support)
44+
* For information on Automatic vs User Managed Replication, please refer to [Region Replication](#region-replication)
45+
* For information on Secret and Secret Version Retention, please refer to [TTL and TTL Version Retention](#ttl-and-ttl-version-retention)
5146

5247

5348

@@ -67,7 +62,11 @@ Before installing the GCP Secret Manager Universal Orchestrator extension, we re
6762

6863
The GCP Secret Manager Orchestrator Extension uses Google Application Default Credentials (ADC) for authentication. Testing of this orchestrator extension was performed using a service account, but please review [Google Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) for more information on the various ways authentication can be set up.
6964

70-
The GCP project and account being used to access Secret Manager must have access to and enabled the Secret Manger API and also must have assigned to it the Secret Manager Admin and Tag Administrator roles.
65+
The GCP project and account being used to access Secret Manager must have access to and enabled the Secret Manger API and also must have assigned to it the following roles:
66+
* Secret Manager Admin
67+
* Tag User (if assigning tags to secrets)
68+
* Folder Viewer (if assigning tags to secrets AND the project assigned for this certificate store has a folder as a direct parent)
69+
* Cloud KMS CryptoKey Encrypter/Decrypter (If assigning KMS Paths to regions when adding secrets using user managed replication)
7170

7271

7372
## GCPScrtMgr Certificate Store Type
@@ -379,6 +378,50 @@ For GCP Secret Manager secrets containing private keys, the GCP Secret Manager O
379378

380379
If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". Please note that if using the generated password Keyfactor Command provides and storing the password in Secret Manager, each renewal/replacement of a certificate will encrypt the private key with a new generated password, which will then be stored as a new version of the password secret.
381380

381+
## Tag Support
382+
383+
This extension supports the management of secret tags. **If** the optional Entry Parameter "Tags" exists in the store type definition:
384+
* Inventory will return all tags assigned to a secret in the comma delimited format of "TagKey1:TagValue1,TagKey2:TagValue2,...,TagKeyN:TagValueN".
385+
* The same format of one-to-many tag key/value pairs ("TagKey1:TagValue1,TagKey2:TagValue2,...,TagKeyN:TagValueN") can be added to the "Tags" field during the setup of Management-Add jobs to assign tags to the secret **as long as each tag key/value pair is already set up as a valid Organization level tag key/value combination in GCP**.
386+
387+
Additional notes regarding tags:
388+
* This integration does **not** support Project level tags when adding new certificates (secrets). Only Organization level tags will be recognized.
389+
* The Tags field will be ignored when renewing/replacing a certificate since in this scenario the extension is only adding a new secret version and not replacing the entire secret. Assigning tags is only attempted when adding a completely new certificate (secret).
390+
* When adding a new secret, any errors attempting to add tags **will not** impact the adding of the secret. If a Management-Add job successfully adds a new certificate (secret) but fails to assign the tag, the job will be reported back with a status of Warning along with detailed messages why each tag could not be assigned. The certificate (secret) itself, however, **will** be added.
391+
* If multiple tags are provided, and errors occur on some but not others, the successful ones will be assigned to the certificate (secret) and warning messages will be written to the log and job status for the others.
392+
393+
## Label Support
394+
395+
This extension supports the management of secret labels. **If** the optional Entry Parameter "Labels" exists in the store type definition:
396+
* Inventory will return all labels attached to each secret in the comma delimited format of "LabelName1:LabelValue1,LabelName2:LabelValue2,...,LabelNameN:LabelValueN"
397+
* The same format of one-to-many label name/value pairs can be added to new secrets during Management-Add jobs in the Labels Entry Parameter. These values can also be modified when renewing/replacing an existing secret.
398+
399+
Additional notes regarding labels:
400+
* A blank Labels Entry Parameter will not remove any existing labels from an existing secret being replaced (certificate renewal use case).
401+
* A non blank Labels Entry Parameter will cause all pre-existing labels to be removed and replaced for the secret being replaced.
402+
* Improperly formatted Labels may cause pre-existing labels to be removed but none or only valid ones added, but this will not prevent the secret from being added/replaced.
403+
404+
## Region Replication
405+
406+
This extension supports replicating secrets to one-to-many valid GCP regions, along with optionally specifying a valid GCP Key Management Service (KMS) patha for each region. **If** the optional Entry Parameter "Replication Regions" exists in the store type definition:
407+
* Inventory will return all replication regions and KMS paths attached to each secret in the comma delimited format of "Region1:KMSPath1,Region2:KMSPath2,...,RegionN:KMSPathN"
408+
* The same format of one-to-many region/KMS path pairs can be added to new secrets during Managment-Add jobs in the Replication Regions Entry Parameter. Modification of these values is **NOT** supported when renewing/replacing an existing secret. Region/KMS values entered when renewing/replacing a certificate in Management-Add job will be ignored.
409+
* Replication regions without KMS paths can also be provided - i.e. "Region1,Region2,...,RegionN", but GCP enforces the convention that **all** supplied regions must have an associated valid KMS path or all of them must **not** have a KMS path. Cannot mix some with and some without.
410+
411+
Additionsl notes regarding replication regions:
412+
* Each region must be a [GCP allowed region for secrets](https://docs.cloud.google.com/secret-manager/docs/locations).
413+
* In order to apply KMS paths to replication regions:
414+
- A valid KMS Key Ring and Crypto Key must be created for the applicable region.
415+
- The Cloud KMS CryptoKey Encrypter/Decrypter role must be applied to the service-{PROJECT ID}@gcp-sa-secretmanager.iam.gserviceaccount.com service principle where {PROJECT ID} is the numeric project id for the project the secret is being added to.
416+
- The Cloud Key Management Service API must be enabled.
417+
418+
## TTL and TTL Version Retention
419+
420+
This extension supports supplying TTL (Time To Live) and Destroy Version TTL values. **If** the optional Entry Parameters of "TTL Duration" and "Version Destroy TTL Duration" exist in the store type definition:
421+
* A numeric value (in days) can be entered for either or both values specifying when a secret will be deleted (TTL Duration) and how many days after secret deletion each version will be destroyed (Version Destroy TTL Duration).
422+
* These values will be returned in Inventory jobs as well as modified when renewing/replacing a secret.
423+
* Blank values supplied during Management job when replacing a secret will not affect current values for the secret.
424+
382425

383426
## License
384427

0 commit comments

Comments
 (0)