Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Config/ObjectModel/RuntimeConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,12 @@ public RuntimeConfig(
// This loader is not used as a part of hot reload and therefore does not need a handler.
FileSystemRuntimeConfigLoader loader = new(fileSystem, handler: null);

// Pass the parent's AKV options so @akv() references in child configs can
// be resolved using the parent's Key Vault configuration.
DeserializationVariableReplacementSettings replacementSettings = new(azureKeyVaultOptions: this.AzureKeyVault, doReplaceEnvVar: true, doReplaceAkvVar: true);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure if the child config has its own AKV options, those will override the ones passed onto by the parent?


foreach (string dataSourceFile in DataSourceFiles.SourceFiles)
{
// Use default replacement settings for environment variable replacement
DeserializationVariableReplacementSettings replacementSettings = new(azureKeyVaultOptions: null, doReplaceEnvVar: true, doReplaceAkvVar: true);

if (loader.TryLoadConfig(dataSourceFile, out RuntimeConfig? config, replacementSettings: replacementSettings))
{
Expand Down
105 changes: 105 additions & 0 deletions src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Linq;
using System.Threading.Tasks;
using Azure.DataApiBuilder.Config;
using Azure.DataApiBuilder.Config.Converters;
using Azure.DataApiBuilder.Config.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -131,4 +132,108 @@ public async Task CanLoadValidMultiSourceConfigWithAutoentities(string configPat
Assert.IsTrue(runtimeConfig.SqlDataSourceUsed, "Should have Sql data source");
Assert.AreEqual(expectedEntities, runtimeConfig.Entities.Entities.Count, "Number of entities is not what is expected.");
}

/// <summary>
/// Validates that when a parent config has azure-key-vault options configured,
/// child configs can resolve @akv('...') references using the parent's AKV configuration.
/// Uses a local .akv file to simulate Azure Key Vault without requiring a real vault.
/// Regression test for https://github.com/Azure/data-api-builder/issues/3322
/// </summary>
[TestMethod]
public async Task ChildConfigResolvesAkvReferencesFromParentAkvOptions()
{
string akvFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".akv");
string childFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".json");

try
{
// Create a local .akv secrets file with test secrets.
await File.WriteAllTextAsync(akvFilePath, "my-connection-secret=Server=tcp:127.0.0.1,1433;Trusted_Connection=True;\n");

// Parent config with azure-key-vault pointing to the local .akv file.
string parentConfig = $@"{{
""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"",
""data-source"": {{
""database-type"": ""mssql"",
""connection-string"": ""Server=tcp:127.0.0.1,1433;Persist Security Info=False;Trusted_Connection=True;TrustServerCertificate=True;MultipleActiveResultSets=False;Connection Timeout=5;""
}},
""azure-key-vault"": {{
""endpoint"": ""{akvFilePath.Replace("\\", "\\\\")}""
}},
""data-source-files"": [""{childFilePath.Replace("\\", "\\\\")}""],
""runtime"": {{
""rest"": {{ ""enabled"": true }},
""graphql"": {{ ""enabled"": true }},
""host"": {{
""cors"": {{ ""origins"": [] }},
""authentication"": {{ ""provider"": ""StaticWebApps"" }}
}}
}},
""entities"": {{}}
}}";

// Child config with @akv('...') reference in its connection string.
string childConfig = @"{
""$schema"": ""https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json"",
""data-source"": {
""database-type"": ""mssql"",
""connection-string"": ""@akv('my-connection-secret')""
},
""runtime"": {
""rest"": { ""enabled"": true },
""graphql"": { ""enabled"": true },
""host"": {
""cors"": { ""origins"": [] },
""authentication"": { ""provider"": ""StaticWebApps"" }
}
},
""entities"": {
""AkvChildEntity"": {
""source"": ""dbo.AkvTable"",
""permissions"": [{ ""role"": ""anonymous"", ""actions"": [""read""] }]
}
}
}";

await File.WriteAllTextAsync(childFilePath, childConfig);

MockFileSystem fs = new(new Dictionary<string, MockFileData>()
{
{ "dab-config.json", new MockFileData(parentConfig) }
});

FileSystemRuntimeConfigLoader loader = new(fs);

DeserializationVariableReplacementSettings replacementSettings = new(
azureKeyVaultOptions: new AzureKeyVaultOptions() { Endpoint = akvFilePath, UserProvidedEndpoint = true },
doReplaceEnvVar: true,
doReplaceAkvVar: true,
envFailureMode: EnvironmentVariableReplacementFailureMode.Ignore);

Assert.IsTrue(
loader.TryLoadConfig("dab-config.json", out RuntimeConfig runtimeConfig, replacementSettings: replacementSettings),
"Config should load successfully when child config has @akv() references resolvable via parent AKV options.");

Assert.IsTrue(runtimeConfig.Entities.ContainsKey("AkvChildEntity"), "Child config entity should be merged into the parent config.");

// Verify the child's connection string was resolved from the .akv file.
string childDataSourceName = runtimeConfig.GetDataSourceNameFromEntityName("AkvChildEntity");
DataSource childDataSource = runtimeConfig.GetDataSourceFromDataSourceName(childDataSourceName);
Assert.IsTrue(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also verify in a different case that the child AKV settings override the ones passed onto by the parent?

childDataSource.ConnectionString.Contains("127.0.0.1"),
"Child config connection string should have the AKV secret resolved.");
}
finally
{
if (File.Exists(akvFilePath))
{
File.Delete(akvFilePath);
}

if (File.Exists(childFilePath))
{
File.Delete(childFilePath);
}
}
}
}