Skip to content

Commit 8935196

Browse files
authored
{GoogleSecret:SecretName:SecretVersion} Syntax (#12)
1 parent b4a63f7 commit 8935196

5 files changed

Lines changed: 101 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and adheres to a project-specific [Versioning](/README.md).
77

88
## [Unreleased]
99

10+
### Added
11+
12+
Added support for the `{GoogleSecret:SecretName:SecretVersion}` syntax to override the default values in the `appsettings.json` or `appsettings.{Environment}.json` files.
13+
1014
## [1.2.0] - 2023-06-29
1115

1216
### Changed

GoogleSecrets/GoogleSecretsExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static IConfigurationBuilder AddGoogleSecrets(this IConfigurationBuilder
2828
throw new ArgumentNullException(nameof(googleSecretsOptions.ProjectName));
2929
}
3030

31-
configuration.Add(new GoogleSecretsSource(googleSecretsOptions));
31+
configuration.Add(new GoogleSecretsSource(googleSecretsOptions, configuration.Build()));
3232

3333
return configuration;
3434
}

GoogleSecrets/GoogleSecretsProvider.cs

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Collections.Generic;
55
using System.Linq;
6+
using System.Net.WebSockets;
67
using System.Threading.Tasks;
78
using Google.Api.Gax;
89
using Google.Api.Gax.ResourceNames;
@@ -17,15 +18,20 @@
1718
public class GoogleSecretsProvider : ConfigurationProvider
1819
{
1920
private readonly ILogger<GoogleSecretsProvider> logger;
21+
private readonly IConfigurationRoot existingConfiguration;
22+
private readonly Dictionary<string, string> secretCache;
2023

2124
/// <summary>
2225
/// Initializes a new instance of the <see cref="GoogleSecretsProvider"/> class.
2326
/// </summary>
2427
/// <param name="source">The source.</param>
25-
public GoogleSecretsProvider(GoogleSecretsSource source)
28+
/// <param name="existingConfiguration">The configuration builder.</param>
29+
public GoogleSecretsProvider(GoogleSecretsSource source, IConfigurationRoot existingConfiguration)
2630
{
2731
this.Source = source;
2832
this.logger = LoggerFactory.Create(b => b.AddConsole()).CreateLogger<GoogleSecretsProvider>();
33+
this.existingConfiguration = existingConfiguration;
34+
this.secretCache = new Dictionary<string, string>();
2935
}
3036

3137
/// <summary>
@@ -56,41 +62,84 @@ public override void Load()
5662
{
5763
foreach (Secret secret in response)
5864
{
59-
if (!this.Source.FilterFn(secret))
60-
{
61-
continue;
62-
}
63-
64-
string version = "latest";
65-
var secretId = secret.SecretName.SecretId;
66-
67-
if (this.Source.VersionDictionary?.ContainsKey(secretId) == true)
68-
{
69-
version = this.Source.VersionDictionary[secretId];
70-
}
71-
7265
try
7366
{
74-
string versionName = $"{secret.Name}/versions/{version}";
75-
var value = secretManagerServiceClient.AccessSecretVersion(versionName);
76-
var secretValue = value.Payload.Data.ToStringUtf8();
67+
if (!this.Source.FilterFn(secret))
68+
{
69+
continue;
70+
}
7771

78-
this.Set(this.Source.MapFn(secret), secretValue);
79-
this.logger.LogInformation($"Successfully loaded secret {secret.SecretName.SecretId}");
72+
ScanExistingConfiguration(secretManagerServiceClient, secret);
73+
ApplyMapFn(secretManagerServiceClient, secret);
8074
}
8175
catch (Exception e)
8276
{
8377
this.logger.LogWarning(e, $"Skipping secret {secret.SecretName.SecretId}");
8478
}
85-
86-
8779
}
8880
}
8981
}
9082
catch (Exception e)
9183
{
9284
this.logger.LogError(e, "Unhandeled Exception");
9385
}
86+
87+
void SetSecretValue(SecretManagerServiceClient secretManagerServiceClient, Secret secret, string key, string version)
88+
{
89+
string versionName = $"{secret.Name}/versions/{version}";
90+
91+
if (secretCache.TryGetValue(versionName, out var cachedValue))
92+
{
93+
this.Set(key, cachedValue);
94+
this.logger.LogDebug($"Using cached value for secret {secret.SecretName.SecretId} Key: {key} Version: {version}");
95+
}
96+
else
97+
{
98+
var value = secretManagerServiceClient.AccessSecretVersion(versionName);
99+
var secretValue = value.Payload.Data.ToStringUtf8();
100+
secretCache[versionName] = secretValue;
101+
this.Set(key, secretValue);
102+
}
103+
104+
this.logger.LogInformation($"Successfully loaded secret {secret.SecretName.SecretId} into configuration. Key: {key} Version: {version}");
105+
}
106+
107+
void ScanExistingConfiguration(SecretManagerServiceClient secretManagerServiceClient, Secret secret)
108+
{
109+
var existingKeyValues = this.existingConfiguration.AsEnumerable();
110+
// value will be in the format of {GoogleSecret:SecretName} or {GoogleSecret:SecretName:Version}
111+
var keyValuesToReplace = existingKeyValues.Where(x => x.Value?.StartsWith($"{{GoogleSecret:{secret.SecretName.SecretId}") == true);
112+
113+
foreach (var keyValue in keyValuesToReplace)
114+
{
115+
var replaceParams = keyValue.Value.Split(':');
116+
string version = "latest";
117+
if (replaceParams.Length >= 3)
118+
{
119+
version = replaceParams[2];
120+
}
121+
122+
SetSecretValue(secretManagerServiceClient, secret, keyValue.Key, version);
123+
}
124+
}
125+
126+
void ApplyMapFn(SecretManagerServiceClient secretManagerServiceClient, Secret secret)
127+
{
128+
if (this.Source.MapFn == null)
129+
{
130+
return;
131+
}
132+
133+
string version = "latest";
134+
var secretId = secret.SecretName.SecretId;
135+
136+
if (this.Source.VersionDictionary?.ContainsKey(secretId) == true)
137+
{
138+
version = this.Source.VersionDictionary[secretId];
139+
}
140+
141+
SetSecretValue(secretManagerServiceClient, secret, this.Source.MapFn(secret), version);
142+
}
94143
}
95144
}
96145
}

GoogleSecrets/GoogleSecretsSource.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
/// <seealso cref="Microsoft.Extensions.Configuration.IConfigurationSource" />
1414
public class GoogleSecretsSource : IConfigurationSource
1515
{
16+
private readonly IConfigurationRoot existingConfig;
17+
1618
/// <summary>
1719
/// Initializes a new instance of the <see cref="GoogleSecretsSource"/> class.
1820
/// </summary>
1921
/// <param name="options">The options.</param>
20-
public GoogleSecretsSource(GoogleSecretsOptions options)
22+
/// <param name="existingConfig">The existing configuration.</param>
23+
public GoogleSecretsSource(GoogleSecretsOptions options, IConfigurationRoot existingConfig)
2124
{
2225
_ = options ?? throw new ArgumentNullException(nameof(options));
2326

@@ -26,6 +29,7 @@ public GoogleSecretsSource(GoogleSecretsOptions options)
2629
this.ProjectName = options.ProjectName;
2730
this.Filter = options.Filter;
2831
this.VersionDictionary = options.VersionDictionary;
32+
this.existingConfig = existingConfig;
2933
}
3034

3135
/// <summary>
@@ -62,7 +66,7 @@ public GoogleSecretsSource(GoogleSecretsOptions options)
6266
/// </returns>
6367
public IConfigurationProvider Build(IConfigurationBuilder builder)
6468
{
65-
return new GoogleSecretsProvider(this);
69+
return new GoogleSecretsProvider(this, existingConfig);
6670
}
6771
}
6872
}

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public static IHostBuilder CreateHostBuilder(string[] args)
3838

3939
## Overriding default appsettings.json values
4040

41+
To override the default `appsettings.json` values you have to options which you can use together or separately:
42+
43+
### Use the `MapFn` to map the secret to the correct path
44+
4145
You can override existing `appsettings.json` values by making sure the `MapFn` maps it to the correct path.
4246
For example if we have the following `appsettings.json`:
4347

@@ -51,6 +55,22 @@ For example if we have the following `appsettings.json`:
5155

5256
We can override it by making sure that the `MapFn` maps it to `Secrets:MyGoogleSecret`. With the default `MapFn` the google secret would have to be called `Secrets__MyGoogleSecret`.
5357

58+
### Use the {GoogleSecret:SecretName:SecretVersion} syntax
59+
60+
In the `appsettings.json` you can use the `{GoogleSecret:SecretName:SecretVersion}` syntax to override the default values. For example:
61+
62+
```json
63+
{
64+
"Secrets": {
65+
"MyGoogleSecret": "{GoogleSecret:MyGoogleSecret:latest}"
66+
}
67+
}
68+
```
69+
70+
will override the `MyGoogleSecret` with the latest version of the secret `MyGoogleSecret`.
71+
72+
**Note: The Version is optional and can be omitted. If omitted the latest version will be taken.**
73+
5474
## Authentication
5575

5676
### Inside GCP

0 commit comments

Comments
 (0)