diff --git a/GCPAnyAgent/GCPAnyAgent.csproj b/GCPAnyAgent/GCPAnyAgent.csproj
index 7b67f1c..fee842d 100644
--- a/GCPAnyAgent/GCPAnyAgent.csproj
+++ b/GCPAnyAgent/GCPAnyAgent.csproj
@@ -53,23 +53,23 @@
..\packages\CSS.Common.1.5.0\lib\net462\CSS.Common.dll
-
- ..\packages\Google.Apis.1.51.0\lib\net45\Google.Apis.dll
+
+ ..\packages\Google.Apis.1.54.0\lib\net45\Google.Apis.dll
-
- ..\packages\Google.Apis.Auth.1.51.0\lib\net45\Google.Apis.Auth.dll
+
+ ..\packages\Google.Apis.Auth.1.54.0\lib\net461\Google.Apis.Auth.dll
-
- ..\packages\Google.Apis.Auth.1.51.0\lib\net45\Google.Apis.Auth.PlatformServices.dll
+
+ ..\packages\Google.Apis.Auth.1.54.0\lib\net461\Google.Apis.Auth.PlatformServices.dll
-
- ..\packages\Google.Apis.Compute.v1.1.51.0.2260\lib\net45\Google.Apis.Compute.v1.dll
+
+ ..\packages\Google.Apis.Compute.v1.1.54.0\lib\net45\Google.Apis.Compute.v1.dll
-
- ..\packages\Google.Apis.Core.1.51.0\lib\net45\Google.Apis.Core.dll
+
+ ..\packages\Google.Apis.Core.1.54.0\lib\net45\Google.Apis.Core.dll
-
- ..\packages\Google.Apis.1.51.0\lib\net45\Google.Apis.PlatformServices.dll
+
+ ..\packages\Google.Apis.1.54.0\lib\net45\Google.Apis.PlatformServices.dll
..\packages\Keyfactor.Platform.Extensions.Agents.1.3.1\lib\net462\Keyfactor.Platform.Extensions.Agents.dll
@@ -236,6 +236,7 @@
+
diff --git a/GCPAnyAgent/GCPStore.cs b/GCPAnyAgent/GCPStore.cs
index da353dc..36a55e0 100644
--- a/GCPAnyAgent/GCPStore.cs
+++ b/GCPAnyAgent/GCPStore.cs
@@ -14,6 +14,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
@@ -22,6 +23,7 @@
using Keyfactor.Platform.Extensions.Agents;
+using Google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Compute.v1;
using Google.Apis.Compute.v1.Data;
@@ -37,11 +39,12 @@ public class GCPStore : LoggingClientBase
{
private string jsonKey;
private string project;
+ private string region = string.Empty;
private ComputeService service;
public GCPStore(AnyJobConfigInfo config)
{
- this.project = config.Store.StorePath;
+ SetProjectAndRegion(config.Store.StorePath);
Dictionary storeProperties = JsonConvert.DeserializeObject>((string)config.Store.Properties);
this.jsonKey = storeProperties["jsonKey"];
@@ -51,46 +54,121 @@ public GCPStore(AnyJobConfigInfo config)
public void insert(SslCertificate sslCertificate, bool overwrite)
{
+ string alias = sslCertificate.Name;
+ string tempAlias = alias + "-temp";
+ string targetCertificateSelfLink = string.Empty;
+ string tempCertificateSelfLink = string.Empty;
+
try
{
- insert(sslCertificate);
- }
- catch (Exception ex)
- {
- if (overwrite)
+ try
{
- try
- {
- delete(sslCertificate.Name);
- insert(sslCertificate);
- }
- catch (Exception ex2)
+ targetCertificateSelfLink = GetCeritificateSelfLink(alias);
+ }
+ catch (Google.GoogleApiException ex)
+ {
+ if (ex.HttpStatusCode != System.Net.HttpStatusCode.NotFound)
+ throw ex;
+ }
+
+ //SCENARIO => certificate alias exists, but overwrite flag not set. ERROR
+ if (!string.IsNullOrEmpty(targetCertificateSelfLink) && !overwrite)
+ {
+ string message = "Overwrite flag not set but certificate exists. If attempting to renew, please check overwrite when scheduling this job.";
+ Logger.Error(message);
+ throw new Exception(message);
+ }
+
+ //SCENARIO => certificate alias does not exist and overwrite not set. Because overwrite was not set we do not need to check for temporary alias that may have been created in an earlier
+ // job but not removed due to error. Since overwrite is not set, the renewal workflow that could have generated a temporary alias would not have been run. INSERT NEW CERTIFICATE, NO BINDINGS
+ if (string.IsNullOrEmpty(targetCertificateSelfLink) && !overwrite)
+ {
+ Logger.Debug("Certificate is not in GCP. Insert new certificate.");
+ insert(sslCertificate);
+ return;
+ }
+
+ // check for existence of cert with this temporary alias in GCP
+ Logger.Debug($"Get cert for temp alias - {tempAlias}");
+ try
+ {
+ tempCertificateSelfLink = GetCeritificateSelfLink(tempAlias);
+ }
+ catch (Google.GoogleApiException ex)
+ {
+ if (ex.HttpStatusCode != System.Net.HttpStatusCode.NotFound)
+ throw ex;
+ }
+
+ //SCENARIO => Overwrite flag set. Neither the passed in alias nor the temporary alias exists, so no clean up from a previous job is necessary. No
+ // certificate exists. INSERT NEW CERTIFICATE, NO BINDINGS
+ if (string.IsNullOrEmpty(targetCertificateSelfLink) && string.IsNullOrEmpty(tempCertificateSelfLink))
+ {
+ Logger.Debug("Certificate is not in GCP. Insert new certificate.");
+ insert(sslCertificate);
+ return;
+ }
+
+ //SCENARIO => certificate exists for passed in alias
+ if (!string.IsNullOrEmpty(targetCertificateSelfLink))
+ {
+ //SCENARIO => if temporary certificate does not already exist, it must be added so it can be bound next as a temporary pre-cursor to removing desired alias, adding it and binding with it
+ if (string.IsNullOrEmpty(tempCertificateSelfLink))
{
- Logger.Error("Error performing certificate add with overwrite: " + ex2.Message);
- Logger.Debug(ex2.StackTrace);
- throw ex2;
+ Logger.Debug("Certificate exists in GCP. Begin renewal by adding certificate with temporary alias.");
+ SslCertificate tempSSLCertificate = new SslCertificate() { Certificate = sslCertificate.Certificate, PrivateKey = sslCertificate.PrivateKey, Name = tempAlias };
+ insert(tempSSLCertificate);
+ try
+ {
+ tempCertificateSelfLink = GetCeritificateSelfLink(tempAlias);
+ }
+ catch (Google.GoogleApiException ex)
+ {
+ if (ex.HttpStatusCode != System.Net.HttpStatusCode.NotFound)
+ throw ex;
+ }
}
+
+ //SCENARIO => renew certificate process - bind to temporary alias, delete previous version of cert with desired alias, add renewed certificate, update bindings to renewed cert and remove temp bindings,
+ // delete cert with temp alias
+ Logger.Debug("Bind cert with temp alias, delete cert to be renewed, add renewed cert, update bindings to new renewed cert, delete temp cert");
+ processBindings(targetCertificateSelfLink, tempCertificateSelfLink);
+ delete(alias);
+ insert(sslCertificate);
+ processBindings(tempCertificateSelfLink, targetCertificateSelfLink);
+ delete(tempAlias);
}
+ //SCENARIO => certificate does NOT exist for passed in alias. certificate MUST exist for temporary alias since we already know one or both MUST exist from previous check.
+ // Add renewed certificate with passed in alias, bind it while removing temporary alias from binding (if exists), delete temporary alias cert
else
{
- Logger.Error("Error performing certificate add: " + ex.Message);
- Logger.Debug(ex.StackTrace);
- throw ex;
+ Logger.Debug("Certificate is not in GCP, but temporary one is - Cleanup of prior error state. insert renewed certificate, bind renewed certificate and remove temp binding, delete temporary certificate.");
+ insert(sslCertificate);
+ processBindings(tempCertificateSelfLink, targetCertificateSelfLink);
+ delete(tempAlias);
+ return;
}
}
+ catch (Exception ex)
+ {
+ Logger.Error("Error adding or binding certificate: " + ex.Message);
+ Logger.Debug(ex.StackTrace);
+ throw ex;
+ }
}
public List list()
{
List inventoryItems = new List();
- SslCertificatesResource.ListRequest request = getComputeService().SslCertificates.List(this.project);
-
- Data.SslCertificateList response;
+ SslCertificatesResource.ListRequest globalRequest = getComputeService().SslCertificates.List(this.project);
+ RegionSslCertificatesResource.ListRequest regionRequest = getComputeService().RegionSslCertificates.List(this.project, this.region);
+
+ SslCertificateList response;
do
{
// To execute asynchronously in an async method, replace `request.Execute()` as shown:
- response = request.Execute();
- // response = await request.ExecuteAsync();
+ response = string.IsNullOrEmpty(region) ? globalRequest.Execute() : regionRequest.Execute();
+ // response = string.IsNullOrEmpty(region) ? await globalRequest.ExecuteAsync() : await regionRequest.ExecuteAsync();
if (response.Items == null)
{
@@ -135,7 +213,14 @@ public List list()
});
}
}
- request.PageToken = response.NextPageToken;
+ if (string.IsNullOrEmpty(region))
+ {
+ globalRequest.PageToken = response.NextPageToken;
+ }
+ else
+ {
+ regionRequest.PageToken = response.NextPageToken;
+ }
} while (response.NextPageToken != null);
return inventoryItems;
@@ -143,8 +228,19 @@ public List list()
public void insert(SslCertificate sslCertificate)
{
- SslCertificatesResource.InsertRequest request = getComputeService().SslCertificates.Insert(sslCertificate, this.project);
- Operation response = request.Execute();
+ Operation response = new Operation();
+ if (string.IsNullOrEmpty(region))
+ {
+ SslCertificatesResource.InsertRequest request = getComputeService().SslCertificates.Insert(sslCertificate, this.project);
+ response = request.Execute();
+ System.Threading.Thread.Sleep(10000);
+ }
+ else
+ {
+ RegionSslCertificatesResource.InsertRequest request = getComputeService().RegionSslCertificates.Insert(sslCertificate, this.project, region);
+ response = request.Execute();
+ System.Threading.Thread.Sleep(10000);
+ }
if (response.HttpErrorStatusCode != null)
{
@@ -162,8 +258,17 @@ public void insert(SslCertificate sslCertificate)
public void delete(string alias)
{
- SslCertificatesResource.DeleteRequest request = this.getComputeService().SslCertificates.Delete(this.project, alias);
- Operation response = request.Execute();
+ Operation response = new Operation();
+ if (string.IsNullOrEmpty(region))
+ {
+ SslCertificatesResource.DeleteRequest request = getComputeService().SslCertificates.Delete(this.project, alias);
+ response = request.Execute();
+ }
+ else
+ {
+ RegionSslCertificatesResource.DeleteRequest request = getComputeService().RegionSslCertificates.Delete(this.project, region, alias);
+ response = request.Execute();
+ }
if (response.HttpErrorStatusCode != null)
{
@@ -179,6 +284,138 @@ public void delete(string alias)
}
}
+ private void processBindings(string prevCertificateSelfLink, string newCertificateSelfLink)
+ {
+ try
+ {
+ // For HTTPS proxy resources
+ TargetHttpsProxyList httpsProxyList = new TargetHttpsProxyList();
+ if (string.IsNullOrEmpty(region))
+ {
+ TargetHttpsProxiesResource.ListRequest request = new TargetHttpsProxiesResource(getComputeService()).List(project);
+ httpsProxyList = request.Execute();
+ }
+ else
+ {
+ RegionTargetHttpsProxiesResource.ListRequest request = new RegionTargetHttpsProxiesResource(getComputeService()).List(project, region);
+ httpsProxyList = request.Execute();
+ }
+
+ if (httpsProxyList.Items != null)
+ {
+ foreach (TargetHttpsProxy proxy in httpsProxyList.Items)
+ {
+ if (proxy.SslCertificates.Contains(prevCertificateSelfLink) || proxy.SslCertificates.Contains(newCertificateSelfLink))
+ {
+ List sslCertificates = (List)proxy.SslCertificates;
+
+ if (proxy.SslCertificates.Contains(prevCertificateSelfLink))
+ sslCertificates.Remove(prevCertificateSelfLink);
+ if (proxy.SslCertificates.Contains(newCertificateSelfLink))
+ sslCertificates.Remove(newCertificateSelfLink);
+
+ sslCertificates.Add(newCertificateSelfLink);
+
+ Operation response = new Operation();
+
+ if (string.IsNullOrEmpty(region))
+ {
+ TargetHttpsProxiesSetSslCertificatesRequest httpsCertRequest = new TargetHttpsProxiesSetSslCertificatesRequest();
+ httpsCertRequest.SslCertificates = sslCertificates;
+ TargetHttpsProxiesResource.SetSslCertificatesRequest setSSLRequest = new TargetHttpsProxiesResource(getComputeService()).SetSslCertificates(httpsCertRequest, project, proxy.Name);
+ response = setSSLRequest.Execute();
+ }
+ else
+ {
+ RegionTargetHttpsProxiesSetSslCertificatesRequest httpsCertRequest = new RegionTargetHttpsProxiesSetSslCertificatesRequest();
+ httpsCertRequest.SslCertificates = sslCertificates;
+ RegionTargetHttpsProxiesResource.SetSslCertificatesRequest setSSLRequest = new RegionTargetHttpsProxiesResource(getComputeService()).SetSslCertificates(httpsCertRequest, project, region, proxy.Name);
+ response = setSSLRequest.Execute();
+ }
+
+ if (response.HttpErrorStatusCode != null)
+ {
+ Logger.Error($"Error setting SSL Certificates for resource: {proxy.Name} " + response.HttpErrorMessage);
+ throw new Exception(response.HttpErrorMessage);
+ }
+ if (response.Error != null)
+ {
+ Logger.Error($"Error setting SSL Certificates for resource: {proxy.Name} " + response.Error.ToString());
+ throw new Exception(response.Error.ToString());
+ }
+ }
+ }
+ }
+
+
+ // For SSL proxy resources
+ //TargetSslProxiesResource.ListRequest sslRequest = new TargetSslProxiesResource(getComputeService()).List(project);
+ //TargetSslProxyList proxyList = sslRequest.Execute();
+
+ //if (proxyList.Items != null)
+ //{
+ // foreach (TargetSslProxy proxy in proxyList.Items)
+ // {
+ // if (proxy.SslCertificates.Contains(prevCertificateSelfLink) || proxy.SslCertificates.Contains(newCertificateSelfLink))
+ // {
+ // List sslCertificates = (List)proxy.SslCertificates;
+
+ // if (proxy.SslCertificates.Contains(prevCertificateSelfLink))
+ // sslCertificates.Remove(prevCertificateSelfLink);
+ // if (proxy.SslCertificates.Contains(newCertificateSelfLink))
+ // sslCertificates.Remove(newCertificateSelfLink);
+
+ // sslCertificates.Add(newCertificateSelfLink);
+
+ // TargetSslProxiesSetSslCertificatesRequest sslCertRequest = new TargetSslProxiesSetSslCertificatesRequest();
+ // sslCertRequest.SslCertificates = sslCertificates;
+ // TargetSslProxiesResource.SetSslCertificatesRequest setSSLRequest = new TargetSslProxiesResource(getComputeService()).SetSslCertificates(sslCertRequest, project, proxy.Name);
+ // Operation response = setSSLRequest.Execute();
+
+ // if (response.HttpErrorStatusCode != null)
+ // {
+ // Logger.Error($"Error setting SSL Certificates for resource: {proxy.Name} " + response.HttpErrorMessage);
+ // throw new Exception(response.HttpErrorMessage);
+ // }
+ // if (response.Error != null)
+ // {
+ // Logger.Error($"Error setting SSL Certificates for resource: {proxy.Name} " + response.Error.ToString());
+ // throw new Exception(response.Error.ToString());
+ // }
+
+ // }
+ // }
+ //}
+ }
+ catch (Exception ex)
+ {
+ string message = "Error attempting to bind added certificate to resource " + ex.Message;
+ Logger.Error(message);
+ throw new Exception(message);
+ }
+ }
+
+ private string GetCeritificateSelfLink(string prevAlias)
+ {
+ SslCertificate certificate = new SslCertificate();
+
+ if (string.IsNullOrEmpty(region))
+ {
+ SslCertificatesResource.GetRequest request = this.getComputeService().SslCertificates.Get(project, prevAlias);
+ certificate = request.Execute();
+ }
+ else
+ {
+ RegionSslCertificatesResource.GetRequest request = this.getComputeService().RegionSslCertificates.Get(project, region, prevAlias);
+ certificate = request.Execute();
+ }
+
+ if (certificate == null || string.IsNullOrEmpty(certificate.Certificate))
+ return null;
+
+ return certificate.SelfLink;
+ }
+
private ComputeService getComputeService()
{
if (this.service == null) {
@@ -221,5 +458,16 @@ private GoogleCredential GetCredential()
}
return credential;
}
+
+ private void SetProjectAndRegion(string storePath)
+ {
+ project = storePath;
+ if (storePath.Contains("/"))
+ {
+ string[] projectRegion = storePath.Split('/');
+ project = projectRegion[0];
+ region = projectRegion[1];
+ }
+ }
}
}
diff --git a/GCPAnyAgent/Management.cs b/GCPAnyAgent/Management.cs
index 8a35a0a..1af90c5 100644
--- a/GCPAnyAgent/Management.cs
+++ b/GCPAnyAgent/Management.cs
@@ -81,9 +81,18 @@ public string GetStoreType()
// Extract server certificate
String certStart = "-----BEGIN CERTIFICATE-----\n";
String certEnd = "\n-----END CERTIFICATE-----";
+
Func pemify = null;
pemify = (ss => ss.Length <= 64 ? ss : ss.Substring(0, 64) + "\n" + pemify(ss.Substring(64)));
- String certPem = certStart + pemify(Convert.ToBase64String(p.GetCertificate(alias).Certificate.GetEncoded())) + certEnd;
+
+ string certPem = string.Empty;
+ foreach (X509CertificateEntry certEntry in p.GetCertificateChain(alias))
+ {
+ if (certEntry.Certificate.IssuerDN.ToString() == certEntry.Certificate.SubjectDN.ToString())
+ continue;
+ certPem += (certStart + pemify(Convert.ToBase64String(certEntry.Certificate.GetEncoded())) + certEnd + "\n");
+ }
+
return (Encoding.ASCII.GetBytes(certPem), Encoding.ASCII.GetBytes(privateKeyString));
}
@@ -119,17 +128,11 @@ private SslCertificate GetSslCertificate(AnyJobConfigInfo config)
byte[] pfxBytes = Convert.FromBase64String(config.Job.EntryContents);
(byte[] certPem, byte[] privateKey) = GetPemFromPFX(pfxBytes, config.Job.PfxPassword.ToCharArray());
- string alias = config.Job.Alias;
- if (String.IsNullOrWhiteSpace(alias))
- {
- X509Certificate2 cert = new X509Certificate2(certPem);
- alias = generateAlias(cert);
- Logger.Debug($"Using generated alias: " + alias);
- }
- else
- {
- Logger.Debug($"Using alias from Job: " + alias);
- }
+ X509Certificate2 cert = new X509Certificate2(certPem);
+ string alias = string.IsNullOrWhiteSpace(config.Job.Alias) ? generateAlias(cert) : config.Job.Alias;
+
+ string jobOrGenerated = string.IsNullOrWhiteSpace(config.Job.Alias) ? "generated" : "job";
+ Logger.Debug($"Using {jobOrGenerated} alias {alias}");
return new SslCertificate
{
@@ -151,6 +154,8 @@ public AnyJobCompleteInfo processJob(AnyJobConfigInfo config, SubmitInventoryUpd
switch (config.Job.OperationType)
{
case AnyJobOperationType.Add:
+ if (string.IsNullOrEmpty(config.Job.PfxPassword))
+ throw new Exception("Error attempting to add or renew a certificate. No private key is present.");
store.insert(GetSslCertificate(config), config.Job.Overwrite);
break;
case AnyJobOperationType.Remove:
diff --git a/GCPAnyAgent/packages.config b/GCPAnyAgent/packages.config
index e8238a5..0ba7819 100644
--- a/GCPAnyAgent/packages.config
+++ b/GCPAnyAgent/packages.config
@@ -19,10 +19,10 @@ limitations under the License.
-
-
-
-
+
+
+
+