Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key vault secret reference for secure string module param #1571

Merged
merged 31 commits into from
May 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c7d1d59
Key Vault Secret reference for secure string module parameter
miqm Mar 7, 2021
5069644
Merge remote-tracking branch 'upstream/main' into feature/keyvault-re…
miqm Mar 8, 2021
c3ad045
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Mar 11, 2021
e807dd1
Tests updated
miqm Mar 11, 2021
9529eab
Avoiding conflicts
miqm Mar 11, 2021
f442ede
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Mar 11, 2021
a44a051
Type checking improved
miqm Mar 12, 2021
42612a4
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Mar 15, 2021
ab421f4
Merge remote-tracking branch 'origin/main' into feature/params-key-va…
anthony-c-martin Apr 10, 2021
7d720e7
Fix up tests
anthony-c-martin Apr 10, 2021
c456a4e
Fix decorator completion diagnostics
miqm Apr 11, 2021
d25d836
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Apr 13, 2021
5bf851d
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Apr 16, 2021
cbbfd6a
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Apr 20, 2021
5af3791
Minor fixes
miqm Apr 20, 2021
e8ffe5f
Update test baselines
Apr 20, 2021
9557bf9
Checking diagnostic messages in scenario tests.
miqm Apr 21, 2021
4b4742e
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Apr 29, 2021
79b5858
Changed logic from Type assignment checking to a function placement v…
miqm Apr 30, 2021
791158b
Module Params resource type checking in Emitter
miqm Apr 30, 2021
53152db
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm Apr 30, 2021
85bee27
Testing key vault reference usage in a module loop
miqm Apr 30, 2021
6196b67
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm May 1, 2021
7c01e01
Tests fix
miqm May 1, 2021
300d20b
Removed leftover from old way
miqm May 1, 2021
34fbd08
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm May 5, 2021
2d334d4
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm May 6, 2021
0d93980
Post-review fixes. Added SecureObject to confusing error message. Ext…
miqm May 6, 2021
d91dfbe
Removed saving matched overload. Placement Flags need to be consisten…
miqm May 6, 2021
29dd3fd
Merge remote-tracking branch 'upstream/main' into feature/params-key-…
miqm May 11, 2021
36b09af
Removed FunctionPlacementFlags
miqm May 11, 2021
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
45 changes: 44 additions & 1 deletion docs/spec/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,47 @@ module publicIp './publicIpAddress.bicep' = {
}
```

Please see [Resource Scopes](./resource-scopes.md) for more information and advanced usage.
Please see [Resource Scopes](./resource-scopes.md) for more information and advanced usage.

## Using existing Key Vault's secret as input for secure string module parameter

When a module expects a `string` parameter with `secure: true` modifier, you can use existing secret from a Key Vault. To obtain the secret you need to use special method `getSecret` that can be called on a Microsoft.KeyVault/vaults resource only and can be used only with parameter with `@secure()` decorator. For example:

```bicep
// Module accepting secure string
@secure()
param myPassword string
@secure()
param mySecondPassword string
```

```bicep
param keyVaultName string
param keyVaultSubscription string
param keyVaultResourceGroup string
param secret1Name string
param secret1Version string
param secret2Name string

resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultName
scope: resourceGroup(keyVaultSubscription, keyVaultResourceGroup)
}

module secretModule './secretModule.bicep' = {
name: 'secretModule'
params: {
myPassword: kv.getSecret(secret1Name, secret1Version)
mySecondPassword: kv.getSecret(secret2Name)
}
}
```

### Notes
* Key Vault must have `enabledForDeployment` property set to `true`
miqm marked this conversation as resolved.
Show resolved Hide resolved
* Key Vault and secret must exist before entire deployment starts.
* Secret version is optional. It defaults to latest version if omitted.

### Additional links
* https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/key-vault-parameter
* https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-use-key-vault
8 changes: 4 additions & 4 deletions src/Bicep.Core.IntegrationTests/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ param groupReaderId string
("BCP139", DiagnosticLevel.Error, "The root resource scope must match that of the Bicep file. To deploy a resource to a different root scope, use a module."),
});
}

[TestMethod]
// https://github.com/azure/bicep/issues/1364
public void Test_Issue1364()
Expand All @@ -685,7 +685,7 @@ public void Test_Issue569_success()
var result = CompilationHelper.Compile(@"
param myparam string
var myvar = 'hello'

output myparam string = myparam
output myvar string = myvar
");
Expand Down Expand Up @@ -791,8 +791,8 @@ param tags object
name: 'CosmosDb-PrimaryKey'
value: global_resources.outputs.cosmosDbKey
}
]
]

module stamp_0_secrets './kevault-secrets.bicep' = [for secret in secrets: {
name: 'stamp_0_secrets-${deploymentId}'
scope: resourceGroup(rg_stamps[0].name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Bicep.Core.UnitTests.Assertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FluentAssertions;
using Bicep.Core.UnitTests.Utils;
using FluentAssertions.Execution;
using Bicep.Core.Diagnostics;
using System.Linq;

namespace Bicep.Core.IntegrationTests.Scenarios
{
[TestClass]
public class ParamKeyVaultSecretReferenceTests
{

[TestMethod]
public void ValidKeyVaultSecretReference()
{
var (template, diags, _) = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}


module secret 'secret.bicep' = {
name: 'secret'
params: {
mySecret: kv.getSecret('mySecret')
}
}
"),
("secret.bicep", @"
@secure()
param mySecret string

output exposed string = mySecret
"));

diags.Should().BeEmpty();
template!.Should().NotBeNull();
var parameterToken = template!.SelectToken("$.resources[?(@.name == 'secret')].properties.parameters.mySecret")!;
using (new AssertionScope())
{
parameterToken.SelectToken("$.value")!.Should().BeNull();
parameterToken.SelectToken("$.reference.keyVault.id")!.Should().DeepEqual("[resourceId('Microsoft.KeyVault/vaults', 'testkeyvault')]");
parameterToken.SelectToken("$.reference.secretName")!.Should().DeepEqual("mySecret");
parameterToken.SelectToken("$.reference.secretVersion")!.Should().BeNull();
}
}

[TestMethod]
public void ValidKeyVaultSecretReferenceWithSecretVersion()
{
var (template, diags, _) = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}

module secret 'secret.bicep' = {
name: 'secret'
params: {
mySecret: kv.getSecret('mySecret','secretversionguid')
}
}
"),
("secret.bicep", @"
@secure()
param mySecret string = 'defaultSecret'

output exposed string = mySecret
"));

diags.Should().BeEmpty();
template!.Should().NotBeNull();
var parameterToken = template!.SelectToken("$.resources[?(@.name == 'secret')].properties.parameters.mySecret")!;
using (new AssertionScope())
{
parameterToken.SelectToken("$.value")!.Should().BeNull();
parameterToken.SelectToken("$.reference.keyVault.id")!.Should().DeepEqual("[resourceId('Microsoft.KeyVault/vaults', 'testkeyvault')]");
parameterToken.SelectToken("$.reference.secretName")!.Should().DeepEqual("mySecret");
parameterToken.SelectToken("$.reference.secretVersion")!.Should().DeepEqual("secretversionguid");
}
}

[TestMethod]
public void InvalidKeyVaultSecretReferenceUsageInOutput()
{
var result = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}

output exposed string = kv.getSecret('mySecret','secretversionguid')
"));

result.Should().NotGenerateATemplate();
result.Should().OnlyContainDiagnostic("BCP180", DiagnosticLevel.Error, "Function \"getSecret\" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.");
}

[TestMethod]
public void InvalidKeyVaultSecretReferenceUsageInVariable()
{
var result = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}

var secret = kv.getSecret('mySecret','secretversionguid')
"));

result.Should().NotGenerateATemplate();
result.Should().OnlyContainDiagnostic("BCP180", DiagnosticLevel.Error, "Function \"getSecret\" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.");
}


[TestMethod]
public void InvalidKeyVaultSecretReferenceUsageInNonSecretParam()
{
var result = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}

module secret 'secret.bicep' = {
name: 'secret'
params: {
notSecret: kv.getSecret('mySecret','secretversionguid')
}
}
"),
("secret.bicep", @"
param notSecret string
"));

result.Should().NotGenerateATemplate();
result.Should().OnlyContainDiagnostic("BCP180", DiagnosticLevel.Error, "Function \"getSecret\" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.");
}

[TestMethod]
public void InvalidKeyVaultSecretReferenceUsageInSecureParamInterpolation()
{
var result = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}

module secret 'secret.bicep' = {
name: 'secret'
params: {
testParam: '${kv.getSecret('mySecret','secretversionguid')}'
}
}
"),
("secret.bicep", @"
@secure()
param testParam string
"));

result.Should().NotGenerateATemplate();
result.Should().OnlyContainDiagnostic("BCP180", DiagnosticLevel.Error, "Function \"getSecret\" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.");
}

[TestMethod]
public void InvalidKeyVaultSecretReferenceUsageInObjectParam()
{
var result = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}

module secret 'secret.bicep' = {
name: 'secret'
params: {
testParam: kv.getSecret('mySecret','secretversionguid')
}
}
"),
("secret.bicep", @"
param testParam object
"));


result.Should().NotGenerateATemplate();
result.Should().OnlyContainDiagnostic("BCP180", DiagnosticLevel.Error, "Function \"getSecret\" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.");
}

[TestMethod]
public void InvalidKeyVaultSecretReferenceUsageInArrayParam()
{
var result = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}

module secret 'secret.bicep' = {
name: 'secret'
params: {
testParam: [
kv.getSecret('mySecret','secretversionguid')
]
}
}
"),
("secret.bicep", @"
param testParam array
"));

result.Should().NotGenerateATemplate();
result.Should().OnlyContainDiagnostic("BCP180", DiagnosticLevel.Error, "Function \"getSecret\" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.");
}


[TestMethod]
public void ValidKeyVaultSecretReferenceInLoopedModule()
{
var (template, diags, _) = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}
var secrets = [
{
name: 'secret01'
version: 'versionA'
}
{
name: 'secret02'
version: 'versionB'
}
]
module secret 'secret.bicep' = [for (secret, i) in secrets : {
name: 'secret'
scope: resourceGroup('secret-${i}-rg')
params: {
mySecret: kv.getSecret('super-${secret.name}', secret.version)
}
}]
"),
("secret.bicep", @"
@secure()
param mySecret string = 'defaultSecret'

output exposed string = mySecret
"));

diags.Should().BeEmpty();
template!.Should().NotBeNull();
var parameterToken = template!.SelectToken("$.resources[?(@.name == 'secret')].properties.parameters.mySecret")!;
using (new AssertionScope())
{
parameterToken.SelectToken("$.value")!.Should().BeNull();
parameterToken.SelectToken("$.reference.keyVault.id")!.Should().DeepEqual("[resourceId('Microsoft.KeyVault/vaults', 'testkeyvault')]");
parameterToken.SelectToken("$.reference.secretName")!.Should().DeepEqual("[format('super-{0}', variables('secrets')[copyIndex()].name)]");
parameterToken.SelectToken("$.reference.secretVersion")!.Should().DeepEqual("[variables('secrets')[copyIndex()].version]");
}
}

[TestMethod]
public void InvalidKeyVaultSecretReferenceInLoopedModule()
{
var result = CompilationHelper.Compile(
("main.bicep", @"
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'testkeyvault'
}
var secrets = [
{
name: 'secret01'
version: 'versionA'
}
{
name: 'secret02'
version: 'versionB'
}
]
module secret 'secret.bicep' = [for (secret, i) in secrets : {
name: 'secret-{i}'
params: {
mySecret: '${kv.getSecret(secret.name, secret.version)}'
}
}]
"),
("secret.bicep", @"
@secure()
param mySecret string = 'defaultSecret'

output exposed string = mySecret
"));

result.Should().NotGenerateATemplate();
result.Should().OnlyContainDiagnostic("BCP180", DiagnosticLevel.Error, "Function \"getSecret\" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.");
}
}
}

Loading