diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c447e63dd0..2092a2cc13 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,6 +23,8 @@ updates: patterns: - System.Management.Automation - Microsoft.PowerShell.SDK + ignore: + - dependency-name: gitversion.tool # Maintain dependencies for GitHub Actions - package-ecosystem: github-actions diff --git a/.gitignore b/.gitignore index da5d32dd7f..66b18fef19 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ dist/ **/bin/ **/obj/ **/__pycache__/ +**/*.egg-info/ +**/build/ BenchmarkDotNet.Artifacts/ results/ .venv/ diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css index 669c51bff7..3c66ab76a0 100644 --- a/docs/assets/stylesheets/extra.css +++ b/docs/assets/stylesheets/extra.css @@ -31,6 +31,7 @@ --md-admonition-icon--experimental: url('data:image/svg+xml;charset=utf-8,'); --md-admonition-icon--learn: url('data:image/svg+xml;charset=utf-8,'); --md-admonition-icon--message: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--security: url('data:image/svg+xml;charset=utf-8,'); } /* experimental */ @@ -90,6 +91,25 @@ mask-image: var(--md-admonition-icon--message); } +/* security */ +.md-typeset .admonition.security, +.md-typeset details.security { + border-color: rgb(32, 148, 243) +} + +.md-typeset .security>.admonition-title, +.md-typeset .security>summary { + background-color: rgba(32, 148, 243, .1); + border-color: rgb(32, 148, 243) +} + +.md-typeset .security>.admonition-title::before, +.md-typeset .security>summary::before { + background-color: rgb(32, 148, 243); + -webkit-mask-image: var(--md-admonition-icon--security); + mask-image: var(--md-admonition-icon--security); +} + /* badges */ .badge { diff --git a/docs/en/rules/Azure.Deployment.AdminUsername.md b/docs/en/rules/Azure.Deployment.AdminUsername.md index 135765f483..1ae8fea8be 100644 --- a/docs/en/rules/Azure.Deployment.AdminUsername.md +++ b/docs/en/rules/Azure.Deployment.AdminUsername.md @@ -1,36 +1,42 @@ --- +reviewed: 2024-10-26 severity: Awareness pillar: Security -category: Infrastructure provisioning +category: SE:02 Secured development lifecycle resource: Deployment online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Deployment.AdminUsername/ --- -# Administrator Username Types +# Deployment uses deterministic credential values ## SYNOPSIS -Use secure parameters for sensitive resource properties. +A sensitive property set from deterministic or hardcoded values is not secure. ## DESCRIPTION Resource properties can be configured using a hardcoded value or Azure Bicep/ template expressions. -When specifying sensitive values use _secure_ parameters such as `secureString` or `secureObject`. +When specifying sensitive values use _secure_ parameters. +Secure parameters use the `@secure` decorator in Bicep or the `secureString` / `secureObject` type. -Sensitive values that use deterministic expressions such as hardcodes string literals or variables are not secure. +Sensitive values that use deterministic expressions such as hardcoded string literals or variables are not secure. +These values can be read by anyone with read access to deployment history or logs. +Logs are often exposed at multiple levels including CI pipeline logs, Azure Activity Logs, and SIEM systems. + + ## RECOMMENDATION Sensitive properties should be passed as parameters. -Avoid using deterministic values for sensitive properties. +Avoid using deterministic or hardcoded values for sensitive properties. ## EXAMPLES ### Configure with Azure template -To deploy resources that pass this rule: +To configure deployments that pass this rule: -- Use secure parameters to specify sensitive properties. +- Set the `type` of parameters used set sensitive resource properties to `secureString` or `secureObject`. For example: @@ -85,9 +91,9 @@ For example: ### Configure with Bicep -To deploy resources that pass this rule: +To configure deployments that pass this rule: -- Use secure parameters to specify sensitive properties. +- Add the `@secure()` decorators on parameters used to set sensitive resource properties. For example: @@ -145,6 +151,10 @@ resource vm1 'Microsoft.Compute/virtualMachines@2022-03-01' = { ## NOTES +### Rule configuration + + + Configure `AZURE_DEPLOYMENT_SENSITIVE_PROPERTY_NAMES` to specify sensitive property names. By default, the following values are used: @@ -154,6 +164,6 @@ By default, the following values are used: ## LINKS -- [Infrastructure provisioning considerations in Azure](https://learn.microsoft.com/azure/architecture/framework/security/deploy-infrastructure) +- [SE:02 Secured development lifecycle](https://learn.microsoft.com/azure/well-architected/security/secure-development-lifecycle) - [Use Azure Key Vault to pass secure parameter value during Bicep deployment](https://learn.microsoft.com/azure/azure-resource-manager/bicep/key-vault-parameter) - [Integrate Azure Key Vault in your ARM template deployment](https://learn.microsoft.com/azure/azure-resource-manager/templates/template-tutorial-use-key-vault#edit-the-parameters-file) diff --git a/docs/en/rules/Azure.Deployment.OuterSecret.md b/docs/en/rules/Azure.Deployment.OuterSecret.md index e4a421c5c3..b42951872c 100644 --- a/docs/en/rules/Azure.Deployment.OuterSecret.md +++ b/docs/en/rules/Azure.Deployment.OuterSecret.md @@ -2,16 +2,16 @@ reviewed: 2022-11-15 severity: Critical pillar: Security -category: Infrastructure provisioning +category: SE:02 Secured development lifecycle resource: Deployment online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Deployment.OuterSecret/ --- -# Secret value in deployment output +# Deployment exposes secrets with outer deployment ## SYNOPSIS -Do not use Outer deployments when references SecureString or SecureObject parameters. +Outer evaluation deployments may leak secrets exposed as secure parameters into logs and nested deployments. ## DESCRIPTION @@ -22,6 +22,8 @@ templates instead of enforcing `secureString` and `secureObject` types. When passing secure values to nested deployments always use `inner` scope deployments to ensure secure values are not logging. Bicep modules always use `inner` scope evaluated deployments. + + ## RECOMMENDATION Consider using `inner` deployments to prevent secure values from being exposed. @@ -86,9 +88,11 @@ Nested Deployments within an ARM template need the property `expressionEvaluatio ### Configure with Bicep -Bicep templates will do this by default when performing nested deployments. +This does not apply to Bicep code as under normal circumstances. +If you use the `module` keyword your deployments always use the `inner` evaluation mode. ## LINKS +- [SE:02 Secured development lifecycle](https://learn.microsoft.com/azure/well-architected/security/secure-development-lifecycle) - [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.resources/deployments?pivots=deployment-language-bicep) - [Deployment Function Scopes](https://learn.microsoft.com/azure/azure-resource-manager/templates/scope-functions?tabs=azure-powershell#function-resolution-in-scopes) diff --git a/docs/en/rules/Azure.Deployment.OutputSecretValue.md b/docs/en/rules/Azure.Deployment.OutputSecretValue.md index 000e7ed815..4d79ef8e75 100644 --- a/docs/en/rules/Azure.Deployment.OutputSecretValue.md +++ b/docs/en/rules/Azure.Deployment.OutputSecretValue.md @@ -2,16 +2,16 @@ reviewed: 2022-06-12 severity: Critical pillar: Security -category: Infrastructure provisioning +category: SE:02 Secured development lifecycle resource: Deployment online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Deployment.OutputSecretValue/ --- -# Secret value in deployment output +# Deployment exposes a secret in output ## SYNOPSIS -Avoid outputting sensitive deployment values. +Outputting a sensitive value from deployment may leak secrets into deployment history or logs. ## DESCRIPTION @@ -23,6 +23,11 @@ Examples of secrets are: - Parameters using the `secureString` or `secureObject` type. - Output from `list*` functions such as `listKeys`. +Outputs are recorded in clear texts within deployment history and logs. +Logs are often exposed at multiple levels including CI pipeline logs, Azure Activity Logs, and SIEM systems. + + + ## RECOMMENDATION Consider removing any output values that return secret values in code. @@ -82,7 +87,7 @@ The following example fails because it returns a secret: To deploy securely pass secrets within Infrastructure as Code: -- Mark secrets with the `@secure()` annotation. +- Add the `@secure()` decorators on sensitive parameters. - Avoid returning a secret in output values. Example using `@secure()` annotation: @@ -101,7 +106,7 @@ output accountPassword string = adminPassword ## LINKS -- [Pipeline secret management](https://learn.microsoft.com/azure/architecture/framework/security/deploy-infrastructure#pipeline-secret-management) +- [SE:02 Secured development lifecycle](https://learn.microsoft.com/azure/well-architected/security/secure-development-lifecycle) - [Test cases for ARM templates](https://learn.microsoft.com/azure/azure-resource-manager/templates/template-test-cases#outputs-cant-include-secrets) - [Outputs should not contain secrets](https://learn.microsoft.com/azure/azure-resource-manager/bicep/linter-rule-outputs-should-not-contain-secrets) - [List function](https://learn.microsoft.com/azure/azure-resource-manager/bicep/bicep-functions-resource#list) diff --git a/docs/en/rules/Azure.Deployment.SecureValue.md b/docs/en/rules/Azure.Deployment.SecureValue.md index d4e4383958..5b78d32ee5 100644 --- a/docs/en/rules/Azure.Deployment.SecureValue.md +++ b/docs/en/rules/Azure.Deployment.SecureValue.md @@ -1,5 +1,5 @@ --- -reviewed: 2022-10-10 +reviewed: 2024-10-26 severity: Critical pillar: Security category: SE:02 Secured development lifecycle @@ -17,14 +17,17 @@ A secret property set from a non-secure value may leak the secret into deploymen Azure Bicep and Azure Resource Manager (ARM) templates can be used to deploy resources to Azure. When deploying Azure resources, sensitive values such as passwords, certificates, and keys should be passed as secure parameters. -Secure parameters use the `secureString` or `secureObject` type. +Secure parameters use the `@secure` decorator in Bicep or the `secureString` / `secureObject` type. -Parameters that do not use secure types are recorded in logs and deployment history. -These values can be retrieved by anyone with access to the deployment history. +Parameters that do not use secure types are recorded in deployment history and logs. +These values can be retrieved by anyone with read access to the deployment history and logs. +Logs are often exposed at multiple levels including CI pipeline logs, Azure Activity Logs, and SIEM systems. + + ## RECOMMENDATION -Consider using secure parameters for sensitive resource properties. +Consider using secure parameters for setting the value of any sensitive resource properties. ## EXAMPLES @@ -32,7 +35,7 @@ Consider using secure parameters for sensitive resource properties. To configure deployments that pass this rule: -- Set the type of parameters used set sensitive resource properties to `secureString` or `secureObject`. +- Set the `type` of parameters used set sensitive resource properties to `secureString` or `secureObject`. For example: @@ -62,7 +65,7 @@ For example: To configure deployments that pass this rule: -- Add the `@secure()` attribute on parameters used to set sensitive resource properties. +- Add the `@secure()` decorators on parameters used to set sensitive resource properties. For example: @@ -80,12 +83,14 @@ resource goodSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { ## NOTES -For a list of resource types and properties that are checked by this rule see: +For a list of resource types and properties that are checked by this rule see [secret properties][1]. +If you find properties that are missing, please let us know by logging an issue on GitHub. -- https://github.com/Azure/PSRule.Rules.Azure/blob/main/data/secret-property.json + [1]: https://github.com/Azure/PSRule.Rules.Azure/blob/main/data/secret-property.json ## LINKS - [SE:02 Secured development lifecycle](https://learn.microsoft.com/azure/well-architected/security/secure-development-lifecycle) +- [Secure parameters](https://learn.microsoft.com/azure/azure-resource-manager/bicep/parameters#secure-parameters) - [Use Azure Key Vault to pass secure parameter value during Bicep deployment](https://learn.microsoft.com/azure/azure-resource-manager/bicep/key-vault-parameter) - [Integrate Azure Key Vault in your ARM template deployment](https://learn.microsoft.com/azure/azure-resource-manager/templates/template-tutorial-use-key-vault#edit-the-parameters-file) diff --git a/docs/hooks/shortcodes.py b/docs/hooks/shortcodes.py index a86675f379..b7eeebf243 100644 --- a/docs/hooks/shortcodes.py +++ b/docs/hooks/shortcodes.py @@ -8,6 +8,7 @@ import os import re import json +import io from mkdocs.config.defaults import MkDocsConfig from mkdocs.structure.files import File, Files @@ -22,7 +23,7 @@ def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files) -> str: '''Hook on_page_markdown event.''' - return external(module(markdown, page, config, files), page, config, files) + return security_note(external(module(markdown, page, config, files), page, config, files), page, config, files) # # Supporting functions @@ -68,6 +69,24 @@ def replace(match: re.Match) -> str: replace, markdown, flags = re.I | re.M ) +def security_note(markdown: str, page: Page, config: MkDocsConfig, files: Files) -> str: + '''Replace security notes shortcodes in markdown.''' + + # Callback for regular expression replacement. + def replace(match: re.Match) -> str: + type, args = match.groups() + args = args.strip() + if type == "note": + return _security_note_block(args, page, config) + + raise RuntimeError(f"Unknown shortcode security:{type}") + + # Replace security note shortcodes. + return re.sub( + r"", + replace, markdown, flags = re.I | re.M + ) + def _relative_uri(path: str, page: Page, files: Files) -> str: '''Get relative URI for a file including anchor.''' @@ -210,3 +229,46 @@ def _avm_module_latest_tag(page: Page, name: str) -> str: latest = data[name]['Latest'] return latest + +def _find_include_for_culture(config: MkDocsConfig, culture: str, path: str) -> str: + '''Find the markdown include file for a specific culture.''' + defaultCulture = config.theme.locale + culture = culture.lower() + + # Get the repo root path. + repo_root_dir = os.path.join(config.docs_dir, "..") + + # Find the include file for the culture. + includeFile = f"{repo_root_dir}/includes/{culture}/{path}" + fallbackFile = f"{repo_root_dir}/includes/{defaultCulture}/{path}" + + # If the path exists load the contents from disk. + if os.path.exists(includeFile) and os.path.isfile(includeFile): + return io.open(includeFile, 'r', encoding='utf-8').read() + + # If the path does not exist, try the default culture. + if os.path.exists(fallbackFile) and os.path.isfile(fallbackFile): + return io.open(fallbackFile, 'r', encoding='utf-8').read() + + raise RuntimeError(f"Unknown include '{path}' not found at '{includeFile}' or '{fallbackFile}'.") + +def _get_culture_from_page(page: Page, config: MkDocsConfig) -> str: + '''Get the culture from the page file path.''' + + culture = config.theme.locale + if page.file.src_path.startswith('en'): + culture = 'en' + + elif page.file.src_path.startswith('es'): + culture = 'es' + + return culture + + +def _security_note_block(text: str, page: Page, config: MkDocsConfig) -> str: + '''Create a security note block.''' + + culture = _get_culture_from_page(page, config) + name = text.split(' ')[0] + + return _find_include_for_culture(config, culture, f"security-notes/{name}.md") diff --git a/includes/en/abbreviations.md b/includes/en/abbreviations.md new file mode 100644 index 0000000000..531324b9d3 --- /dev/null +++ b/includes/en/abbreviations.md @@ -0,0 +1,2 @@ + +*[SIEM]: Security information and event management diff --git a/includes/en/security-notes/rotate-secret.md b/includes/en/security-notes/rotate-secret.md new file mode 100644 index 0000000000..39af14be7e --- /dev/null +++ b/includes/en/security-notes/rotate-secret.md @@ -0,0 +1,7 @@ + +!!! Security "Secret rotation — [SE:09 Application secrets](https://learn.microsoft.com/azure/well-architected/security/application-secrets#secret-rotation)" + If a secret has already been exposed by a previous insecure deployment, + rotate it immediately to prevent unauthorized access to your resources. + + Rotating a secret involves changing or regenerating the secret value and updating all dependent resources with the new value. + This process should be done in a secure manner to prevent the new secret from being exposed. diff --git a/mkdocs.yml b/mkdocs.yml index a549552d27..00cc1a28d5 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -112,7 +112,9 @@ markdown_extensions: - footnotes - meta - md_in_html - - pymdownx.snippets + - pymdownx.snippets: + auto_append: + - includes/en/abbreviations.md - pymdownx.highlight: auto_title: false anchor_linenums: true @@ -160,6 +162,16 @@ hooks: - docs/hooks/aliases.py - docs/hooks/old_hooks.py +watch: + - includes + +exclude_docs: | + specs/ + benchmark/ + examples/ + *.bicep + examples.json + extra: social: - icon: fontawesome/brands/github diff --git a/packages/bicep-syntax/README.md b/packages/bicep-syntax/README.md new file mode 100644 index 0000000000..c3592b4fbe --- /dev/null +++ b/packages/bicep-syntax/README.md @@ -0,0 +1,3 @@ +# Bicep syntax + +Pygments lexer for Bicep syntax. diff --git a/packages/bicep-syntax/__init__.py b/packages/bicep-syntax/__init__.py new file mode 100644 index 0000000000..59e481eb93 --- /dev/null +++ b/packages/bicep-syntax/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. diff --git a/packages/bicep-syntax/lexer.py b/packages/bicep-syntax/lexer.py new file mode 100644 index 0000000000..5f617ffa35 --- /dev/null +++ b/packages/bicep-syntax/lexer.py @@ -0,0 +1,137 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# NOTES: +# This module implements a pygments lexer for Bicep. + +# Local testing: +# To test the lexer, you can use the following code snippet: +# pygmentize -l bicep docs/examples/resources/keyvault.bicep + +import logging + +from pygments.lexer import RegexLexer, bygroups, words, include +from pygments.token import Comment, Operator, Keyword, Name, String, Number, Punctuation, Whitespace + +log = logging.getLogger(f"mkdocs") + +__all__ = ['BicepLexer',] + +BICEP_KEYWORDS = [ + 'metadata', 'targetScope', 'for', 'in', 'if', 'existing', 'as', 'with', 'extends', 'assert', 'extension', +] +BICEP_DECLARATIONS = [ + 'var', 'func', 'type' +] +BICEP_DECORATORS = [ + 'secure', 'metadata', 'description', 'export', 'maxLength', 'minLength', 'allowed' +] + +class BicepLexer(RegexLexer): + '''A lexer for Bicep.''' + name = 'Bicep' + aliases = ['bicep'] + filenames = ['*.bicep'] + + log.warning(f"Loading Bicep lexer") + + tokens = { + 'root': [ + (words(['import', 'using'], suffix=' '), Keyword.Namespace), + + (r'^(param)(\s)(\w+)(\s)(\w+)', bygroups(Keyword.Declaration, Whitespace, Name.Symbol, Whitespace, Name.Type), 'param'), + (r'^(output)(\s)(\w+)(\s)(\w+)', bygroups(Keyword.Declaration, Whitespace, Name.Symbol, Whitespace, Name.Type), 'output'), + + (r'^(resource)(\s)(\w+)', bygroups(Keyword.Declaration, Whitespace, Name.Symbol), 'resource'), + (r'^(module)(\s)(\w+)', bygroups(Keyword.Declaration, Whitespace, Name.Symbol), 'module'), + + (words(BICEP_DECLARATIONS, suffix=' ', prefix='\n'), Keyword.Declaration), + (r'^(\@)(secure|metadata|description|export|maxLength|minLength|allowed)(\()', bygroups(Keyword.Decorator, Keyword.Decorator, Punctuation), 'decorator'), + (words(BICEP_KEYWORDS, suffix=r'\b'), Keyword), + + include('core'), + ], + 'core': [ + (r'(\[)(for)', bygroups(Punctuation, Keyword), 'loop'), + include('comments'), + include('complex'), + include('literal'), + (r'\n', Whitespace), + (r'\s+', Whitespace), + (r'\=|\+|\-', Operator), + ], + 'numbers': [ + (r"\d+[.]\d*|[.]\d+", Number.Float), + (r"\d+", Number.Integer), + ], + 'literal': [ + include('numbers'), + (r"'", String, 'single_string'), + (r'(true|false|null)\b', Keyword.Constant), + ], + 'complex': [ + (r'\{', Punctuation, 'object'), + (r'\[', Punctuation, 'array'), + ], + 'comments': [ + (r'//.*?$', Comment.Single), + (r'/\*', Comment.Multiline, 'multiline-comments'), + ], + 'multiline-comments': [ + (r'.*?\*/', Comment.Multiline, '#pop'), + (r'.+', Comment.Multiline), + ], + 'decorator': [ + (r'\)', Punctuation, '#pop'), + (r'\n', Whitespace, '#pop'), + (r'\s+', Whitespace), + include('literal'), + include('complex'), + include('comments'), + ], + 'single_string': [ + (r"'", String, "#pop"), + (r"\\.", String.Escape), + (r"[^'\\]+", String), + ], + 'array': [ + (r',', Punctuation), + (r'\]', Punctuation, '#pop'), + include('core'), + ], + 'object': [ + (r'(\s+)(\w+)(\:)', bygroups(Whitespace, Name.Property, Punctuation)), + (r'\s+', Whitespace), + (r'\}', Punctuation, '#pop'), + include('core'), + ], + 'param': [ + (r'\n', Whitespace, '#pop'), + (r'\=', Operator, '#pop'), + ], + 'output': [ + (r'\n', Whitespace, '#pop'), + (r'\=', Operator, '#pop'), + ], + 'resource': [ + (r"'", String, 'single_string'), + (words(['existing']), Keyword), + (r'\n', Whitespace, '#pop'), + (r'\=', Operator, '#pop'), + ], + 'module': [ + (r"'", String, 'single_string'), + (r'\n', Whitespace, '#pop'), + (r'\=', Operator, '#pop'), + ], + 'loop': [ + (words(['in', 'range', 'items']), Keyword), + (r'\(|\)|\,', Punctuation), + (r'\:', Punctuation, '#pop'), + include('core'), + ], + 'condition': [ + (r'\)', Punctuation, '#pop'), + include('literal'), + ] + } diff --git a/packages/bicep-syntax/pyproject.toml b/packages/bicep-syntax/pyproject.toml new file mode 100644 index 0000000000..0ced050a60 --- /dev/null +++ b/packages/bicep-syntax/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "bicep-syntax" +version = "0.0.1" +dependencies = ["pygments"] +readme = "README.md" + +[project.entry-points."pygments.lexers"] +bicep = "lexer:BicepLexer" diff --git a/requirements-docs.txt b/requirements-docs.txt index 4d3c7ffad1..a1b2ad1e49 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -6,3 +6,5 @@ mkdocs-git-revision-date-localized-plugin==1.3.0 mkdocs-git-committers-plugin-2==2.4.1 mdx-truly-sane-lists==1.3 mkdocs-redirects==1.2.1 +pygments==2.18.0 +./packages/bicep-syntax