-
Notifications
You must be signed in to change notification settings - Fork 762
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
Key vault secret reference for secure string module param #1571
Conversation
@majastrz @anthony-c-martin @shenglol @TarunSunkaraneni This is my initial implementation of referencing secrets in module parameters. I've created this PR as a draft, it still lacks tests and probably support for decorators. However I wanted to get your opinion on the approach in introducing a type My idea for implementation was that the function Is this the good approach? |
Codecov Report
@@ Coverage Diff @@
## main #1571 +/- ##
==========================================
+ Coverage 95.07% 95.15% +0.07%
==========================================
Files 370 371 +1
Lines 21543 21733 +190
Branches 15 15
==========================================
+ Hits 20483 20681 +198
+ Misses 1060 1052 -8
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Thank you for getting this done!! Did some testing with these files:
param vaultName string = 'keyVault${uniqueString(resourceGroup().id)}' // must be globally unique
param location string = resourceGroup().location
param sku string = 'Standard'
param tenant string = '72f988bf-86f1-41af-91ab-2d7cd011db47' // replace with your tenantId
param accessPolicies array = [
{
tenantId: tenant
objectId: 'caeebed6-cfa8-45ff-9d8a-03dba4ef9a7d' // replace with your objectId
permissions: {
keys: [
'Get'
'List'
'Update'
'Create'
'Import'
'Delete'
'Recover'
'Backup'
'Restore'
]
secrets: [
'Get'
'List'
'Set'
'Delete'
'Recover'
'Backup'
'Restore'
]
certificates: [
'Get'
'List'
'Update'
'Create'
'Import'
'Delete'
'Recover'
'Backup'
'Restore'
'ManageContacts'
'ManageIssuers'
'GetIssuers'
'ListIssuers'
'SetIssuers'
'DeleteIssuers'
]
}
}
]
param enabledForDeployment bool = true
param enabledForTemplateDeployment bool = true
param enabledForDiskEncryption bool = true
param enableRbacAuthorization bool = false
param softDeleteRetentionInDays int = 90
param keyName string = 'prodKey'
param secretName string = 'bankAccountPassword'
param secretValue string = '12345'
param networkAcls object = {
ipRules: []
virtualNetworkRules: []
}
resource keyvault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: vaultName
location: location
properties: {
tenantId: tenant
sku: {
family: 'A'
name: sku
}
accessPolicies: accessPolicies
enabledForDeployment: enabledForDeployment
enabledForDiskEncryption: enabledForDiskEncryption
enabledForTemplateDeployment: enabledForTemplateDeployment
softDeleteRetentionInDays: softDeleteRetentionInDays
enableRbacAuthorization: enableRbacAuthorization
networkAcls: networkAcls
}
}
// create secret
resource secret 'Microsoft.KeyVault/vaults/secrets@2018-02-14' = {
name: '${keyvault.name}/${secretName}'
properties: {
value: secretValue
}
}
module secretDeploy 'module-with-secret.bicep' = {
name: 'foo'
params: {
secret: keyvault.getSecret(secretName) // would also like to provide the secret resource as a valid type
}
} And a dummy module // @secure() did not work
param secret string {
secure: true
}
output badPractice string = secret I hit a bug if I use a decorator One potential improvement (not sure how realistic it is). I created the secret as a top-level child resource. I'd like to be able to just pass the resource // create secret
resource secret 'Microsoft.KeyVault/vaults/secrets@2018-02-14' = { ... }
module secretDeploy 'module-with-secret.bicep' = {
name: 'foo'
params: {
secret: secret
}
} Or module secretDeploy 'module-with-secret.bicep' = {
name: 'foo'
params: {
secret: secret.getSecret()
}
} |
@alex-frankel This is still work in progress, I wanted to get some insight if my approach to implementing this is the right way to go. In general I still have to implement that parameter with |
As for the improvement, I initially though about this. First option seems to me to be very hard to implement in bicep, but second one is possible, but perhaps However this will not work in green field deployments. When I run validate (so even before deployment begins), I receive following error (key vault existed before, but secret didn't):
Or for non-existing key-vault (that is going to be deployed):
Perhaps this is something to be fixed/changed on the Deployment Manager level. |
eb5dbb6
to
a442719
Compare
Also, a question here - how to implement decompilation? Existing resource we define using 2 parameters: name and scope. In ARM we had only 1 - id. I think of 3 options:
For now I think I will go with 1. as 2 might be overcomplicated and 3. can be a future enhancement. |
Hey @miqm - @alex-frankel, @majastrz, @shenglol and I had a chat about this PR yesterday. We thought that the approach we should follow is to use the
Sorry for the delay in getting back to you - we really appreciate your contribution, and please feel free to check in with us for any pointers! |
Thanks for the hint, I'll try to rework my PR. What about the decompilation approach? Perhaps we can catch up on Teams sometime next week? |
70ccd4a
to
c7d1d59
Compare
After some time merging/rebasing with main (💘 LF) I managed to put some improvements. After talking with @anthony-c-martin I renamed and changed the introduced type - it's now Function flags are used to indicate placement inside module params, although I needed to make TypeAssignmentVisitor capable of using them when checking instance functions. In the visitor at the point of validating function placement we do not have information about the type of the parameter and therefore we're unable to detect if we're using function in param that accepts it. I didn't want to write new SyntaxVisitor and introduce a mechanism for validating function flags which might add even more complexity, have performance drawbacks and could be easily mixed up with existing one that is basically split between TypeAssignmentVisitor and NameBindingVisitor. The decompilation is not done as it's blocked by #1722 - Suggestion here is to implement it later - after having nice way of referencing external resources by id only rather than name/scope pair (which here would require some extra complexity and smart detecting "what author had id mind" style. Also, added some scenario tests to cover proper and wrong use cases, so I think we're ready for the proper review. |
…ference-in-module-parameters-1028
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just had some questions, mostly about the type system changes.
…vault-reference-1028
@anthony-c-martin I just wanted to add a bit more regarding introducing the type. My main thinking flow is that the Even in the ARM output we have a different structure passed to the parameter: instead If the method would return a With this type, we're saying - hey - this is a reference to the key vault secret - you can use it in the module params. And there's no confusion that this is a string. Developer would think more what she or he can do with this type, rather than: "ok, this is string, so I can use it in interpolation, get count of it, concat it, etc.". |
@majastrz @alex-frankel - I think this is ready now for final review, I implemented all we talked about and added tests with module loops as suggested. Only thing that is not working is support for |
/// Flags that may be placed on functions to indicate where they can be placed in semantic tree | ||
/// </summary> | ||
[Flags] | ||
public enum FunctionPlacementFlags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function flags are used in type assignment. adding a flag there and assigning it to function overload will trigger the mechanism and prevent using it - type assignment visitor will want function with default flag no with something different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@majastrz - is this explanation sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both FunctionFlags
and FunctionPlacementFlags
are declared and assigned together in every place they appear, so their lifecycles appear to be identical.
If you look at these values of FunctionFlags
, all of them control placement of the functions:
ParamDefaultsOnly
ParameterDecorator
VariableDecorator
ResourceDecorator
ModuleDecorator
OutputDecorator
ResourceOrModuleDecorator
AnyDecorator
In fact it seem like the majority of the enum values are about placement, so it's kind of weird to have a second set of flags also about placement.
You mentioned that adding the flag to FunctionFlag
would trigger the type checking mechanism? Can you point me to the code that's doing it? Maybe we can tweak that to avoid it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's here:
private static Symbol ResolveFunctionFlags(FunctionFlags allowedFlags, FunctionSymbol functionSymbol, IPositionable span) |
In fact I can see now that additional flag will not impact the binding mechanism, but we will end up in having flags checked in 2 places.
Originally I wanted to use this mechanism, but this method for InstanceFunctions is called from TypeAssignmentVisitor so I ended up with recording placement there.
Of course joining those flags will be quite easy if we're fine with having a flag that is checked elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@majastrz - as discussed, I merged it into one enum (Function Flags).
src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep
Outdated
Show resolved
Hide resolved
src/Bicep.Core.Samples/Files/InvalidOutputs_CRLF/main.diagnostics.bicep
Outdated
Show resolved
Hide resolved
…vault-reference-1028
…vault-reference-1028
…racted VisitorRecorder to a separate class.
…t in all overloads.
…vault-reference-1028
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is amazing! Thanks for getting this done!
Changes have been made since.
@miqm May I ask if there is any solution to solve the problem about using getSecret() to retrieve a secret value from a KeyVault provisioned in the same deployment? I saw you have encountered a such problem previously as below: For non-existing key-vault (that is going to be deployed): I found an other discussion on the same issue in this thread: https://githubmemory.com/repo/Azure/bicep/issues/4081. Any suggestions will be very appreciated! |
getSecret()
method on KeyVault typesecure: true
Modifier accepts output from kv.getSecret() function@secure()
Decorator accepts output from kv.getSecret() functionDecompile produces external keyVault resource and getSecret() call when key vault reference is usedFixes #1028
Contributing a feature