Skip to content

6 potential issues in IaC templates: unguarded conditional resource, plain-string secrets #15282

Closed

Description

@comp-boop

Describe the bug

We are researchers studying the quality of infrastructure-as-code (IaC) templates. We ran a static analyzer on this repository and found 6 potential issues in 6 templates (one CloudFormation template and five ARM templates). We reviewed each finding by hand before filing. To avoid noise, all of them are in this one issue.

One finding can make the AWS Security Hub stack fail to deploy when CreateNewBucket is set to false, which the parameter description offers as an option. The other five are about secret parameters (a Duo secret key, a GitHub token, an Azure AD client secret and Okta API tokens) that are declared as plain string, so their values can be read in the deployment history.

If any of these are intentional, please let us know or simply close this issue. Thank you!

Findings

# File Lines Finding Description
1 SecurityHubResourcesAndConfig.json 84–86, 155–168, 208–229, 247–252 Unguarded reference to a conditional resource S3Bucket is only created when CreateNewBucket is true, but S3BucketPolicy, FirehoseDeliveryStreamRole and SecurityHubDeliveryStream use Fn::GetAtt S3Bucket.Arn without a guard.
2 azuredeploy_DuoSecurity_API_FunctionApp.json 23–26 Secret parameter not marked as sensitive; placeholder default Parameter SecretKey (Duo Admin API secret key) is a plain string with a placeholder default, which is deployed unless the user replaces it.
3 GitHub/azuredeploy.json 17–22 Secret parameter not marked as sensitive Parameter PersonalAccessToken holds a GitHub personal access token but is declared as string, not securestring.
4 O365 Data/azuredeploy.json 9–12 Secret parameter not marked as sensitive Parameter clientSecret holds the Azure AD client secret but is declared as string, not securestring.
5 azuredeploy_OktaSingleSignOn_API_FunctionApp.json 17–20 Secret parameter not marked as sensitive Parameter APIToken holds the Okta API token but is declared as string, not securestring.
6 azuredeploy_OktaSingleSignOn_API_FunctionApp_V2.json 19–22 Secret parameter not marked as sensitive Parameter APIToken (Okta API token) is declared as string; the copy of this template under Solutions/ already uses securestring.

Details

1. Unguarded reference to a conditional resource: S3Bucket in SecurityHubResourcesAndConfig.json

Where: Solutions/AWS Security Hub/CloudFormationTemplates/SecurityHubResourcesAndConfig.json, lines 84–86 (the conditional bucket), with unguarded references at lines 155–168, lines 208–229 and lines 247–252

        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "Condition": "CreateNewBucketCondition",

One of the references, in SecurityHubDeliveryStream (lines 247–252):

                    "BucketARN": {
                        "Fn::GetAtt": [
                            "S3Bucket",
                            "Arn"
                        ]
                    },

What we found: S3Bucket has "Condition": "CreateNewBucketCondition" (line 86), so it is skipped when the parameter CreateNewBucket (lines 26–34) is false. Three resources have no condition but use {"Fn::GetAtt": ["S3Bucket", "Arn"]} without an Fn::If:

  • S3BucketPolicy: the policy Resource, inside Fn::Join (lines 160–163);
  • FirehoseDeliveryStreamRole: the policy Resource list (lines 210–213 and 220–223);
  • SecurityHubDeliveryStream: ExtendedS3DestinationConfiguration.BucketARN (lines 247–252).

Why it matters: The parameter description says "Set to false to have Amazon S3 use an existing S3 Bucket", but with false the stack can fail to create, because S3Bucket is not part of it. Users who want to reuse an existing bucket cannot deploy this template, and Security Hub findings cannot be delivered to that bucket.

Suggested fix: Wrap each Fn::GetAtt on S3Bucket in Fn::If, and fall back to the ARN of the existing bucket named by BucketName:

{
  "Fn::If": [
    "CreateNewBucketCondition",
    { "Fn::GetAtt": ["S3Bucket", "Arn"] },
    { "Fn::Sub": "arn:${AWS::Partition}:s3:::${BucketName}" }
  ]
}

Use it in the four places listed above (lines 160–163, 210–213, 220–223 and 247–252). When a new bucket is created, the result is the same as today, so this is not a breaking change. Note that an existing bucket does not get the SQS NotificationConfiguration that S3Bucket sets, so that would have to be configured separately.

2. Secret parameter not marked as sensitive; placeholder default: SecretKey in azuredeploy_DuoSecurity_API_FunctionApp.json

Where: DataConnectors/Duo Security/azuredeploy_DuoSecurity_API_FunctionApp.json, lines 23–26

        "SecretKey": {
            "type": "string",
            "defaultValue": "<sk***"
        },

What we found: SecretKey is declared with "type": "string" and a placeholder defaultValue. Users paste their Duo Admin API secret key into it, and the template passes it to the Function App as the app setting sKey (line 194), which the function reads as its Duo secret key. Nothing in the template marks the value as secret, and nothing rejects the placeholder. The parameter WorkspaceKey (lines 15–18), which holds the Log Analytics workspace key, has the same shape.

Why it matters: Azure stores plain string parameter values in the deployment history of the resource group, so the Duo secret key (and the workspace key) can be read there by anyone with read access to deployments, long after the deployment. If a user keeps the default in the "Deploy to Azure" form, the connector is deployed with an invalid secret key, and log collection can fail without any deployment error.

Suggested fix: Declare both secret parameters as securestring and drop the placeholder defaults, so the values must be supplied:

"WorkspaceKey": {
    "type": "securestring"
},
"SecretKey": {
    "type": "securestring"
},

Callers that pass a value are unaffected, and sKey in the appsettings resource keeps working unchanged. Without the defaults the parameters become required, so a deployment that leaves them out stops at validation instead of deploying a connector that cannot authenticate.

3. Secret parameter not marked as sensitive: PersonalAccessToken in GitHub/azuredeploy.json

Where: DataConnectors/GitHub/azuredeploy.json, lines 17–22

    "PersonalAccessToken": {
      "type": "string",
      "metadata": {
        "description": "Enter the GitHub personal access token."
      }
    },

What we found: PersonalAccessToken has "type": "string". Its value is the GitHub PAT, which the template writes into a Key Vault secret at line 104 ("value": "[parameters('PersonalAccessToken')]"). Because the parameter is not a securestring, Azure Resource Manager does not mask it.

Why it matters: Plain string parameter values are saved in the resource group's deployment history and shown in the portal and in az deployment group show output. Anyone with read access to the deployments can recover the GitHub token, even though the template otherwise keeps it in Key Vault.

Suggested fix: Change the parameter type to securestring:

"PersonalAccessToken": {
  "type": "securestring",
  "metadata": {
    "description": "Enter the GitHub personal access token."
  }
}

This is not a breaking change: callers pass the same value, and the portal shows a masked input. The parameter workSpaceKey (the Log Analytics workspace key, lines 41–46) can be changed to securestring in the same way.

4. Secret parameter not marked as sensitive: clientSecret in O365 Data/azuredeploy.json

Where: DataConnectors/O365 Data/azuredeploy.json, lines 9–12

        "clientSecret": {
            "type": "string",
            "defaultValue": "AAD***"
        },

What we found: clientSecret is declared with "type": "string". It carries the Azure AD application's client secret: the template stores it in the Key Vault secret clientSecret (line 101), and the Function App reads it from there (line 252). The parameter itself is not marked secure, so the value entered at deployment is not hidden. The parameter workspaceKey (lines 37–40) has the same problem.

Why it matters: Azure Resource Manager keeps plain string parameter values in the deployment history, so anyone who can read the resource group's deployments can see the client secret, even though the template later moves it into Key Vault.

Suggested fix: Declare the parameter as securestring and drop the placeholder default (secure parameters should not have a default):

"clientSecret": {
    "type": "securestring"
},

Apply the same change to workspaceKey. Changing the type is not breaking: callers still pass a string, and [parameters('clientSecret')] works unchanged. Removing the default makes the parameter required.

5. Secret parameter not marked as sensitive: APIToken in azuredeploy_OktaSingleSignOn_API_FunctionApp.json

Where: DataConnectors/Okta Single Sign-On/azuredeploy_OktaSingleSignOn_API_FunctionApp.json, lines 17–20

          "APIToken": {
              "type": "string",
              "defaultValue": "<ap***"
          },

What we found: APIToken is declared with "type": "string". Its value is the Okta API token, which is copied into the Function App setting apiToken on line 182 ("apiToken": "[parameters('APIToken')]"). No part of the template marks it as secure. The parameter WorkspaceKey (lines 13–16) is also a plain string.

Why it matters: Plain string parameter values are saved in the deployment history of the resource group, so the Okta API token can be read by anyone with read access to deployments. An Okta API token carries the permissions of the admin who created it, so a leak can expose more than the System Log.

Suggested fix: Declare the parameter as securestring and drop the placeholder default, so the token is never recorded in the deployment history:

"APIToken": {
    "type": "securestring",
    "metadata": { "description": "Okta API token used to read the System Log." }
},

Apply the same change to WorkspaceKey. Callers that already pass a value are unaffected; the placeholder default never worked as a real token anyway. For stronger protection, the app setting can hold a Key Vault reference (@Microsoft.KeyVault(SecretUri=...)), as the connector instructions already suggest.

6. Secret parameter not marked as sensitive: APIToken in azuredeploy_OktaSingleSignOn_API_FunctionApp_V2.json

Where: DataConnectors/Okta Single Sign-On/azuredeploy_OktaSingleSignOn_API_FunctionApp_V2.json, lines 19–22

          "APIToken": {
              "type": "string",
              "defaultValue": "<ap***"
          },

What we found: In the V2 template, APIToken also has "type": "string". It holds the Okta API token that the Function App uses to read the Okta System Log, and it is passed into the app setting apiToken (line 198). The copy of this template under Solutions/Okta Single Sign-On/Data Connectors/OktaSingleSign-On/ already declares APIToken as securestring, but this copy in DataConnectors/ does not.

Why it matters: With type string, the Okta token is shown in plain text in the Deploy-to-Azure form and is stored in the deployment history of the resource group, where it can be read by anyone with read access to deployments.

Suggested fix: Change the parameter type to securestring, as in the Solutions/ copy of this template, and drop the placeholder default as in #5. The parameter WorkspaceKey (lines 15–18) holds the workspace key and can be changed in the same way. Callers pass the same value, so this is not a breaking change; without the default, the parameter becomes required.

To Reproduce

We found these by reading the templates at commit 02e803a; we did not deploy them. To check:

  1. In SecurityHubResourcesAndConfig.json, S3Bucket has "Condition": "CreateNewBucketCondition" (line 86). The condition (lines 37–44) is false when the parameter CreateNewBucket is false, one of its two allowed values.
  2. S3BucketPolicy, FirehoseDeliveryStreamRole and SecurityHubDeliveryStream have no Condition, but use Fn::GetAtt on S3Bucket (lines 160–163, 210–213, 220–223 and 247–252), so creating the stack with CreateNewBucket = false can fail.
  3. In the five ARM templates of rows 2–6, the listed parameters have "type": "string". After a deployment, the values entered for them can be seen in the resource group's deployment history (in the portal, or with az deployment group show).

Expected behavior

  • The Security Hub stack can be created with CreateNewBucket = false, and then uses the existing bucket named by BucketName.
  • Secret parameters (the Duo secret key, the GitHub token, the Azure AD client secret, the Okta API tokens and the workspace keys) are declared as securestring, so their values are masked in the portal and are not kept in the deployment history.

Additional context

The links point to commit 02e803a, the version we analysed. This report is part of an academic research project on IaC quality. The findings come from a static analyzer, the text was drafted with the help of an AI assistant, and we checked both by hand. In the snippets, the placeholder defaults of secret parameters are shortened to their first three characters.

If it helps, we are happy to open a pull request with the suggested fixes. Feedback on the format is welcome too. Thank you for maintaining this project!

Activity

  1. v-kasghosh commented on Oct 12, 2026

    @v-kasghosh
    Contributor

    Hey comp-boop ,

    Thank you for sharing this detailed analysis.

    We reviewed the referenced files against the current solution manifests. The ARM templates under the root DataConnectors/ folder are legacy artifacts and are not included in the current Duo, GitHub, Okta, or Office 365 solution packages. Their supported implementations use replacement CCF or native-poller connectors, or templates that declare sensitive parameters as securestring.

    The AWS Security Hub CloudFormation template is likewise not included in the generated solution package, although it remains available in the repository as a manually downloadable template.

    Based on the currently shipped solution content, these findings do not affect active solution packages. Thank you again for raising them. May we proceed with closing this issue?

  2. v-kasghosh commented on Oct 14, 2026

    @v-kasghosh
    Contributor

    Hey comp-boop ,
    Gentle Reminder: We are waiting for your response on this issue. If you still need to keep this issue active, please respond to it in the next 2 days. If we don't receive response by the given date, we will close this issue.

  3. comp-boop commented on Oct 15, 2026

    @comp-boop
    Author

    Hi v-kasghosh, thank you for checking these against the solution manifests.

    That makes sense for findings 2–6: they are the root DataConnectors/ templates are legacy and not part of the shipped Duo, GitHub, Okta and Office 365 packages.

    Finding 1 may still be worth a small fix. The readme.md next to the template asks users to download SecurityHubResourcesAndConfig.json and upload it in CloudFormation to get the Role ARN and SQS Queue URL for the AWS Security Hub Findings connector. Anyone who follows it and sets CreateNewBucket to false, as the parameter description suggests for an existing bucket, gets a stack that fails to create, because S3BucketPolicy, FirehoseDeliveryStreamRole and SecurityHubDeliveryStream still reference S3Bucket.

    The fix is the Fn::If change described in the issue, in four places, and it does not change the default behaviour. If it helps, we can open a small PR for it. If you would rather treat the repository copy as unsupported, please feel free to close the issue. Thanks again for the review!

  4. v-kasghosh commented on Oct 26, 2026

    @v-kasghosh
    Contributor

    Hey comp-boop ,

    Thank you for the clarification. Although the CloudFormation template is not included in the packaged solution, the README still directs users to deploy it manually. We agree that the existing-bucket path is worth addressing.

    We’d welcome a focused PR. Please validate both new- and existing-bucket deployments, including the bucket references and any S3-to-SQS notification setup needed for an existing bucket. The PR team will review the PR accordingly.

    We’ll proceed with closing this issue, but you’re welcome to reference it in the PR so the context remains available.

    Thanks Again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Problem SolutionThis provides a potential solution to questions others may ask

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions