Skip to content

Commit 31291fc

Browse files
Allow Base64 characters in variable path regex (#180)
#179
1 parent 2dda974 commit 31291fc

3 files changed

Lines changed: 106 additions & 2 deletions

File tree

src/Confix.Tool/src/Confix.Library/Variables/VariablePath.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@ public static bool TryParse(string variable, [NotNullWhen(true)] out VariablePat
3636
private const string VariableProviderCaptureGroup = "VariableProvider";
3737
private const string VariableNameCaptureGroup = "VariableName";
3838

39+
// The variable name (path) part is allowed to contain the standard and url-safe Base64
40+
// alphabet characters (`+`, `/`, `=`, `-`) in addition to word characters and dots.
41+
// This is required by providers such as the secret provider, where the variable's path
42+
// is the Base64-encoded ciphertext (e.g. `$secret:K2b8F2zG9HpJxMI+...==`).
43+
private const string VariableNameCharacterClass = @"[\w\.+/=\-]";
44+
3945
[GeneratedRegex(
40-
$$"""^\$(?<{{VariableProviderCaptureGroup}}>[\w\.]+):(?<{{VariableNameCaptureGroup}}>[\w\.]+)$""")]
46+
$$"""^\$(?<{{VariableProviderCaptureGroup}}>[\w\.]+):(?<{{VariableNameCaptureGroup}}>{{VariableNameCharacterClass}}+)$""")]
4147
private static partial Regex VariableNameRegex();
4248
}
4349

@@ -59,6 +65,6 @@ public static string ReplaceVariables(this string value, Func<VariablePath, stri
5965

6066
private const string VariableCaptureGroup = "variable";
6167

62-
[GeneratedRegex($$"""(\{\{)?(?<{{VariableCaptureGroup}}>\$(?:[\w\.]+):(?:[\w\.]+))(\}\})?""")]
68+
[GeneratedRegex($$"""(\{\{)?(?<{{VariableCaptureGroup}}>\$(?:[\w\.]+):(?:[\w\.+/=\-]+))(\}\})?""")]
6369
private static partial Regex MultipleInterpolatedVariablesRegex();
6470
}

src/Confix.Tool/test/Confix.Tool.Tests/Variables/SecretVariableProviderTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,36 @@ public async Task ResolveAsync__WrongPassword_Should_Exit()
179179
// assert
180180
ex.Message.Should().Be("Invalid password for private key");
181181
}
182+
183+
[Theory]
184+
[InlineData(EncryptionPadding.OaepSHA256)]
185+
[InlineData(EncryptionPadding.Pkcs1)]
186+
public async Task SetAsync_Result_Is_A_Parseable_VariablePath(EncryptionPadding padding)
187+
{
188+
// arrange
189+
SecretVariableProvider provider = new(new SecretVariableProviderDefinition(
190+
SecretVariableProviderAlgorithm.RSA,
191+
padding,
192+
PUBLIC_KEY,
193+
null,
194+
PRIVATE_KEY,
195+
null,
196+
null)
197+
);
198+
199+
var initialSecret = JsonValue.Create("I'm super secret string")!;
200+
201+
// act
202+
var encryptedPath = await provider.SetAsync("not relevant here", initialSecret, default);
203+
204+
// assert
205+
// The Base64-encoded ciphertext returned by SetAsync must round-trip through the
206+
// `$secret:<ciphertext>` syntax used in configuration files (see VariablePath regex).
207+
bool parsed = VariablePath.TryParse($"$secret:{encryptedPath}", out VariablePath? variablePath);
208+
parsed.Should().BeTrue("the encrypted ciphertext must be a valid VariablePath");
209+
variablePath!.Value.Path.Should().Be(encryptedPath);
210+
211+
var decrypted = await provider.ResolveAsync(variablePath.Value.Path, default);
212+
decrypted.IsEquivalentTo(initialSecret).Should().BeTrue();
213+
}
182214
}

src/Confix.Tool/test/Confix.Tool.Tests/Variables/VariablePathTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ public class VariablePathTests
88
[InlineData("$foo:bar", "foo", "bar")]
99
[InlineData("$foo.bar:baz.x", "foo.bar", "baz.x")]
1010
[InlineData("$foo_bar:baz_x", "foo_bar", "baz_x")]
11+
[InlineData("$secret:K2b8F2zG9HpJxMI", "secret", "K2b8F2zG9HpJxMI")]
12+
[InlineData("$secret:abc+def/ghi==", "secret", "abc+def/ghi==")]
13+
[InlineData("$secret:abc/def+ghi=", "secret", "abc/def+ghi=")]
14+
[InlineData("$secret:abc-def_ghi", "secret", "abc-def_ghi")]
1115
public void Parse_ValidVariableName_CorrectResult(string variableName, string providerName, string path)
1216
{
1317
// arrange & act
@@ -31,6 +35,22 @@ public void TryParse_ValidVariableName_CorrectResult()
3135
path!.Value.Path.Should().Be("baz.x");
3236
}
3337

38+
[Fact]
39+
public void TryParse_Base64EncodedPath_CorrectResult()
40+
{
41+
// arrange
42+
const string base64 = "K2b8F2zG9HpJxMImaYwlf0ByzArc+abc/def==";
43+
44+
// act
45+
bool success = VariablePath.TryParse($"$secret:{base64}", out VariablePath? path);
46+
47+
// assert
48+
success.Should().BeTrue();
49+
path.HasValue.Should().BeTrue();
50+
path!.Value.ProviderName.Should().Be("secret");
51+
path.Value.Path.Should().Be(base64);
52+
}
53+
3454
[Theory]
3555
[InlineData("bar")]
3656
[InlineData("$foo.bar")]
@@ -44,4 +64,50 @@ public void TryParse_Invalid_SuccessFalse(string variableName)
4464
success.Should().BeFalse();
4565
path.HasValue.Should().BeFalse();
4666
}
67+
68+
[Fact]
69+
public void GetVariables_Base64EncodedPath_ReturnsFullVariable()
70+
{
71+
// arrange
72+
const string base64 = "K2b8F2zG9HpJxMImaYwlf0ByzArc+abc/def==";
73+
string value = $"$secret:{base64}";
74+
75+
// act
76+
var variables = value.GetVariables().ToArray();
77+
78+
// assert
79+
variables.Should().HaveCount(1);
80+
variables[0].ProviderName.Should().Be("secret");
81+
variables[0].Path.Should().Be(base64);
82+
}
83+
84+
[Fact]
85+
public void GetVariables_InterpolatedBase64EncodedPath_ReturnsFullVariable()
86+
{
87+
// arrange
88+
const string base64 = "K2b8F2zG9HpJxMImaYwlf0ByzArc+abc/def==";
89+
string value = $"prefix-{{{{$secret:{base64}}}}}-suffix";
90+
91+
// act
92+
var variables = value.GetVariables().ToArray();
93+
94+
// assert
95+
variables.Should().HaveCount(1);
96+
variables[0].ProviderName.Should().Be("secret");
97+
variables[0].Path.Should().Be(base64);
98+
}
99+
100+
[Fact]
101+
public void ReplaceVariables_Base64EncodedPath_ReplacesFullVariable()
102+
{
103+
// arrange
104+
const string base64 = "K2b8F2zG9HpJxMImaYwlf0ByzArc+abc/def==";
105+
string value = $"$secret:{base64}";
106+
107+
// act
108+
string result = value.ReplaceVariables(_ => "REPLACED");
109+
110+
// assert
111+
result.Should().Be("REPLACED");
112+
}
47113
}

0 commit comments

Comments
 (0)