├── .gitignore ├── .github ├── CODEOWNERS ├── dependabot.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── rule-request.md │ └── bug_report.md └── workflows │ ├── stale.yaml │ ├── dependencies.yaml │ └── analyze.yaml ├── tests └── PSRule.Rules.Kubernetes.Tests │ ├── ps-rule.yaml │ ├── Resources.AKS.yaml │ ├── Rule.Common.Tests.ps1 │ ├── Resources.Pod.yaml │ ├── Kubernetes.AKS.Tests.ps1 │ ├── Kubernetes.Metadata.Tests.ps1 │ ├── Module.PSGallery.Tests.ps1 │ ├── Resources.Metadata.yaml │ ├── Kubernetes.API.Tests.ps1 │ ├── Resources.API.yaml │ └── Kubernetes.Pod.Tests.ps1 ├── .vscode ├── extensions.json ├── settings.json └── tasks.json ├── modules.json ├── CODE_OF_CONDUCT.md ├── src └── PSRule.Rules.Kubernetes │ ├── en │ └── PSRule-rules.psd1 │ ├── rules │ ├── Kubernetes.AKS.Rule.ps1 │ ├── Kubernetes.Metadata.Rule.ps1 │ ├── Kubernetes.Common.Rule.ps1 │ ├── Baseline.Rule.yaml │ ├── Kubernetes.API.Rule.ps1 │ └── Kubernetes.Pod.Rule.ps1 │ └── PSRule.Rules.Kubernetes.psd1 ├── ps-project.yaml ├── SUPPORT.md ├── ps-rule.yaml ├── docs ├── rules │ └── en │ │ ├── Kubernetes.Metadata.md │ │ ├── Kubernetes.Pod.Health.md │ │ ├── Kubernetes.Pod.Latest.md │ │ ├── Kubernetes.Pod.Replicas.md │ │ ├── Kubernetes.Pod.Secrets.md │ │ ├── Kubernetes.API.v1.17.md │ │ ├── Kubernetes.AKS.PublicLB.md │ │ ├── Kubernetes.API.v1.16.md │ │ ├── Kubernetes.Pod.PrivilegeEscalation.md │ │ ├── Kubernetes.API.v1.20.md │ │ ├── Kubernetes.Pod.Resources.md │ │ └── module.md └── scenarios │ └── install-instructions.md ├── .azure-pipelines ├── pipeline-deps.ps1 ├── jobs │ ├── test.yaml │ └── testContainer.yaml └── azure-pipelines.yaml ├── LICENSE ├── RuleToc.Doc.ps1 ├── .ps-rule └── Rule.Rule.ps1 ├── RuleHelp.Doc.ps1 ├── .markdownlint.json ├── CONTRIBUTING.md ├── SECURITY.md ├── CHANGELOG.md ├── scripts └── dependencies.psm1 ├── README.md └── pipeline.build.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | reports/ 3 | out/ 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/articles/about-codeowners/ 2 | * @microsoft/psrule-rules-kubernetes 3 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/ps-rule.yaml: -------------------------------------------------------------------------------- 1 | 2 | binding: 3 | targetName: 4 | - metadata.name 5 | targetType: 6 | - kind 7 | useQualifiedName: true 8 | 9 | execution: 10 | notProcessedWarning: true 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.powershell", 4 | "ms-azure-devops.azure-pipelines", 5 | "redhat.vscode-yaml", 6 | "bewhite.psrule-vscode-preview", 7 | "streetsidesoftware.code-spell-checker" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /modules.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "PSRule": { 4 | "version": "2.7.0" 5 | } 6 | }, 7 | "devDependencies": { 8 | "Pester": { 9 | "version": "5.4.0" 10 | }, 11 | "platyPS": { 12 | "version": "0.14.2" 13 | }, 14 | "PSDocs": { 15 | "version": "0.9.0" 16 | }, 17 | "PSScriptAnalyzer": { 18 | "version": "1.21.0" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Dependabot configuration 3 | # 4 | 5 | # Please see the documentation for all configuration options: 6 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 7 | 8 | version: 2 9 | updates: 10 | 11 | # Maintain dependencies for GitHub Actions 12 | - package-ecosystem: 'github-actions' 13 | directory: '/' 14 | schedule: 15 | interval: 'daily' 16 | labels: 17 | - 'ci-quality' 18 | reviewers: 19 | - 'microsoft/psrule-rules-kubernetes' 20 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/en/PSRule-rules.psd1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | @{ 5 | PodCPURequest = 'Set CPU resource reservation.' 6 | PodCPULimit = 'Set CPU resource limit.' 7 | PodMemRequest = 'Set memory resource reservation.' 8 | PodMemLimit = 'Set memory resource limit.' 9 | RecommendLabel = 'Recommend label ''{0}'' is not set.' 10 | LivenessProbe = 'Liveness probe not configured for ''{0}''' 11 | ReadinessProbe = 'Readiness probe not configured for ''{0}''' 12 | } 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## PR Summary 2 | 3 | 4 | 5 | ## PR Checklist 6 | 7 | - [ ] PR has a meaningful title 8 | - [ ] Summarized changes 9 | - [ ] Change is not breaking 10 | - [ ] This PR is ready to merge and is not **Work in Progress** 11 | - **Code changes** 12 | - [ ] Have unit tests created/ updated 13 | - [ ] Link to a filed issue 14 | - [ ] [Change log](https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/main/CHANGELOG.md) has been updated with change under unreleased section 15 | -------------------------------------------------------------------------------- /ps-project.yaml: -------------------------------------------------------------------------------- 1 | 2 | info: 3 | name: PSRule.Rules.Kubernetes 4 | description: A suite of rules to validate Kubernetes resources using PSRule. 5 | url: https://github.com/Microsoft/PSRule.Rules.Kubernetes 6 | 7 | repository: 8 | type: git 9 | url: https://github.com/Microsoft/PSRule.Rules.Kubernetes.git 10 | 11 | modules: 12 | PSRule: 1.11.0 13 | 14 | tasks: 15 | build: 16 | steps: 17 | - powershell: Invoke-Build Build 18 | 19 | clear: 20 | steps: 21 | - gitPrune: 22 | name: origin 23 | removeGone: true 24 | 25 | restore: 26 | steps: 27 | - module: '*' 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | 12 | A clear and concise description of what you want to happen. 13 | 14 | **Describe alternatives you've considered** 15 | 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. 6 | 7 | Please search the existing issues before filing new issues to avoid duplicates. 8 | 9 | - For new issues, file your bug or feature request as a new [issue]. 10 | - For help, discussion, and support questions about using this project, join or start a [discussion]. 11 | 12 | ## Microsoft Support Policy 13 | 14 | Support for this project/ product is limited to the resources listed above. 15 | 16 | [issue]: https://github.com/microsoft/PSRule.Rules.Kubernetes/issues 17 | [discussion]: https://github.com/microsoft/PSRule.Rules.Kubernetes/discussions 18 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/rules/Kubernetes.AKS.Rule.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Validation rules for Azure Kubernetes Service (AKS) 6 | # 7 | 8 | # Synopsis: Services should not include a public load balancer 9 | Rule 'Kubernetes.AKS.PublicLB' -Type Service -If { $PSRule.TargetName -ne 'addon-http-application-routing-nginx-ingress' } -Tag @{ group = 'AKS' } { 10 | if ($Assert.HasFieldValue($TargetObject, 'spec.type', 'LoadBalancer').Result) { 11 | Within 'metadata.annotations.''service.beta.kubernetes.io/azure-load-balancer-internal''' 'true' 12 | } 13 | else { 14 | return $True; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ps-rule.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # PSRule configuration 3 | # 4 | 5 | # Please see the documentation for all configuration options: 6 | # https://microsoft.github.io/PSRule/ 7 | 8 | input: 9 | pathIgnore: 10 | - '.vscode/' 11 | - '*.md' 12 | - '*.Designer.cs' 13 | - '*.resx' 14 | - '*.sln' 15 | - '*.txt' 16 | - '*.html' 17 | - '*.ico' 18 | 19 | include: 20 | path: [] 21 | 22 | binding: 23 | targetName: 24 | - RuleName 25 | - FullName 26 | 27 | output: 28 | culture: 29 | - en-US 30 | 31 | configuration: 32 | # Authoring rules 33 | RULE_AUTHORING_ONLINE_HELP: 'https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/' 34 | RULE_AUTHORING_PREFIX: 'Kubernetes' 35 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.Metadata.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Awareness 3 | category: Management 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.Metadata.md 5 | --- 6 | 7 | # Use recommended labels 8 | 9 | ## SYNOPSIS 10 | 11 | Use Kubernetes common labels. 12 | 13 | ## DESCRIPTION 14 | 15 | Kubernetes defines a common set of labels that are recommended for tool interoperability. 16 | These labels should be used to consistently apply standard metadata. 17 | 18 | ## RECOMMENDATION 19 | 20 | Consider applying recommended labels defined by Kubernetes. 21 | 22 | ## LINKS 23 | 24 | - [Recommended Labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/rule-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Rule request 3 | about: Suggest the creation of a new or to update an existing rule 4 | --- 5 | 6 | # Rule request 7 | 8 | ## Suggested rule change 9 | 10 | A clear and concise description of the what the rule should check and why. 11 | 12 | ## Applies to the following 13 | 14 | The rule applies to the following: 15 | 16 | - Resource kind: **[i.e. Deployment]** 17 | 18 | ## Sample data 19 | 20 | Include a sample of passing/ failing resource YAML data. 21 | 22 | > Only provide sanitized samples, replacing information such as metadata and names with examples (i.e. `name: deployment-A`). 23 | 24 | A passing sample: 25 | 26 | ```yaml 27 | 28 | ``` 29 | 30 | A failing sample: 31 | 32 | ```yaml 33 | 34 | ``` 35 | 36 | ## Additional context 37 | 38 | Add any other context or references. 39 | -------------------------------------------------------------------------------- /.azure-pipelines/pipeline-deps.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Install dependencies for integration with Azure DevOps 6 | # 7 | 8 | if ($Env:SYSTEM_DEBUG -eq 'true') { 9 | $VerbosePreference = 'Continue'; 10 | } 11 | 12 | if ($Null -eq (Get-PackageProvider -Name NuGet -ErrorAction Ignore)) { 13 | Install-PackageProvider -Name NuGet -Force -Scope CurrentUser; 14 | } 15 | 16 | if ($Null -eq (Get-InstalledModule -Name PowerShellGet -MinimumVersion 2.1.4 -ErrorAction Ignore)) { 17 | Install-Module PowerShellGet -MinimumVersion 2.1.4 -Scope CurrentUser -Force -AllowClobber; 18 | } 19 | 20 | if ($Null -eq (Get-InstalledModule -Name InvokeBuild -MinimumVersion 5.4.0 -ErrorAction Ignore)) { 21 | Install-Module InvokeBuild -MinimumVersion 5.4.0 -Scope CurrentUser -Force; 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report errors or unexpected behaviour 4 | --- 5 | 6 | **Description of the issue** 7 | 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | 12 | Steps to reproduce the issue: 13 | 14 | ```powershell 15 | 16 | ``` 17 | 18 | **Expected behaviour** 19 | 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Error output** 23 | 24 | Capture any error messages and or verbose messages with `-Verbose`. 25 | 26 | ```text 27 | 28 | ``` 29 | 30 | **Module in use and version:** 31 | 32 | - Module: PSRule.Rules.Kubernetes 33 | - Version: **[e.g. 0.1.0]** 34 | 35 | Captured output from `$PSVersionTable`: 36 | 37 | ```text 38 | 39 | ``` 40 | 41 | **Additional context** 42 | 43 | Add any other context about the problem here. 44 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.Pod.Health.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Important 3 | category: Reliability 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.Pod.Health.md 5 | --- 6 | 7 | # Use probes 8 | 9 | ## SYNOPSIS 10 | 11 | Containers should use liveness and readiness probes. 12 | 13 | ## DESCRIPTION 14 | 15 | Just like any other application, container applications may take time to start, fail during startup or operation. 16 | Kubernetes provides a way for the cluster to determine if each container is ready to respond to requests. 17 | This is accomplished through liveness and readiness probes. 18 | 19 | ## RECOMMENDATION 20 | 21 | Consider configuring liveness and readiness probes for pod containers. 22 | 23 | ## LINKS 24 | 25 | - [Configure Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) 26 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Resources.AKS.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Kubernetes Service resources for unit tests 3 | # 4 | 5 | --- 6 | # An example service that should pass all rules. 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: service-A 11 | spec: 12 | ports: 13 | - port: 80 14 | name: http 15 | selector: 16 | app: app-A 17 | 18 | --- 19 | # This service should fail Kubernetes.AKS.PublicLB 20 | apiVersion: v1 21 | kind: Service 22 | metadata: 23 | name: service-B 24 | spec: 25 | type: LoadBalancer 26 | ports: 27 | - port: 6379 28 | selector: 29 | app: app-B 30 | 31 | --- 32 | # This service should pass Kubernetes.AKS.PublicLB 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | name: service-C 37 | annotations: 38 | service.beta.kubernetes.io/azure-load-balancer-internal: "true" 39 | spec: 40 | type: LoadBalancer 41 | ports: 42 | - port: 6379 43 | selector: 44 | app: app-C 45 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.Pod.Latest.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Important 3 | category: Security 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.Pod.Latest.md 5 | --- 6 | 7 | # Use specific tags 8 | 9 | ## SYNOPSIS 10 | 11 | Containers should use specific tags instead of latest. 12 | 13 | ## DESCRIPTION 14 | 15 | Containers should use specific tags instead of latest. 16 | 17 | ## RECOMMENDATION 18 | 19 | Deployments or pods should identify a specific tag to use for container images instead of latest. 20 | When latest is used it may be hard to determine which version of the image is running. 21 | 22 | When using variable tags such as v1.0 (which may refer to v1.0.0 or v1.0.1) consider using `imagePullPolicy: Always` to ensure that the an out-of-date cached image is not used. 23 | 24 | The latest tag automatically uses `imagePullPolicy: Always` instead of the default `imagePullPolicy: IfNotPresent`. 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "reports/": true, 4 | "out/": true 5 | }, 6 | "search.exclude": { 7 | "out/": true 8 | }, 9 | "editor.insertSpaces": true, 10 | "editor.tabSize": 4, 11 | "[yaml]": { 12 | "editor.tabSize": 2 13 | }, 14 | "[markdown]": { 15 | "editor.tabSize": 2 16 | }, 17 | "files.associations": { 18 | "**/.azure-pipelines/*.yaml": "azure-pipelines", 19 | "**/.azure-pipelines/jobs/*.yaml": "azure-pipelines" 20 | }, 21 | "cSpell.words": [ 22 | "Kubernetes", 23 | "setuid" 24 | ], 25 | "cSpell.enabledLanguageIds": [ 26 | "csharp", 27 | "git-commit", 28 | "markdown", 29 | "plaintext", 30 | "powershell", 31 | "text", 32 | "yaml", 33 | "yml" 34 | ], 35 | "yaml.schemas": { 36 | "kubernetes": "/tests/PSRule.Rules.Kubernetes.Tests/Resources.*.yaml" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.Pod.Replicas.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Important 3 | category: Reliability 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.Pod.Replicas.md 5 | --- 6 | 7 | # Use two or more replicas 8 | 9 | ## SYNOPSIS 10 | 11 | Use two or more replicas. 12 | 13 | ## DESCRIPTION 14 | 15 | Deployments, ReplicaSets and StatefulSets are Kubernetes resources that orchestrate the life-cycle management of pods. 16 | These resources can specify a number of replicas pods to create. 17 | 18 | By specifying more than one replica, Kubernetes will maintain additional copies of a pods, removing a pod as a single point of failure. 19 | 20 | ## RECOMMENDATION 21 | 22 | Consider increasing replicas to two or more to provide high availability of pod. 23 | 24 | ## LINKS 25 | 26 | - [Replication controller](https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/) 27 | - [Creating a deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#creating-a-deployment) 28 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.Pod.Secrets.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Critical 3 | category: Security 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.Pod.Secrets.md 5 | --- 6 | 7 | # Use secret references 8 | 9 | ## SYNOPSIS 10 | 11 | Sensitive environment variables should be referenced as a secret. 12 | 13 | ## DESCRIPTION 14 | 15 | When defining pods, environment variables may be used to configure containers. 16 | Environment variables can be included as plain text value or reference a Kubernetes secret. 17 | 18 | Environment variables that are specified as plain text can be read by anyone with permissions read the resource. 19 | Additionally Kubernetes resource manifests are commonly stored in source control. 20 | 21 | ## RECOMMENDATION 22 | 23 | Use Kubernetes secrets to store information such as passwords or connection strings that contain sensitive data instead of plain text. 24 | Access to read secrets can be provided using role-based access control on an as needed basis separate from reading other resources. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.API.v1.17.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Important 3 | category: API 4 | online version: https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.API.v1.17.md 5 | --- 6 | 7 | # Use APIs supported in v1.17 8 | 9 | ## SYNOPSIS 10 | 11 | Avoid using legacy API endpoints not served by Kubernetes v1.17. 12 | 13 | ## DESCRIPTION 14 | 15 | In Kubernetes v1.17.0 previously deprecated API endpoints are no longer served. 16 | These endpoints will no longer work for new deployments after upgrading to Kubernetes v1.17.0 or greater. 17 | 18 | To prevent deployment issues use the newer API endpoints for these resources. 19 | 20 | - PriorityClass should use `scheduling.k8s.io/v1`. 21 | 22 | ## RECOMMENDATION 23 | 24 | Consider updating resource deployments to use newer API endpoints prior to upgrading to Kubernetes >= v1.17.0. 25 | 26 | ## LINKS 27 | 28 | - [Kubernetes v1.16.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/main/CHANGELOG/CHANGELOG-1.16.md#deprecations-and-removals) 29 | - [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) 30 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/rules/Kubernetes.Metadata.Rule.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Validation rules for Kubernetes metadata requirements 6 | # 7 | 8 | # Synopsis: Use recommended labels 9 | Rule 'Kubernetes.Metadata' -Type 'Deployment', 'Service', 'ReplicaSet', 'Pod' -Tag @{ group = 'core' } { 10 | Exists 'metadata.labels.''app.kubernetes.io/name''' -Reason ($LocalizedData.RecommendLabel -f 'app.kubernetes.io/name') 11 | Exists 'metadata.labels.''app.kubernetes.io/instance''' -Reason ($LocalizedData.RecommendLabel -f 'app.kubernetes.io/instance') 12 | Exists 'metadata.labels.''app.kubernetes.io/version''' -Reason ($LocalizedData.RecommendLabel -f 'app.kubernetes.io/version') 13 | Exists 'metadata.labels.''app.kubernetes.io/component''' -Reason ($LocalizedData.RecommendLabel -f 'app.kubernetes.io/component') 14 | Exists 'metadata.labels.''app.kubernetes.io/part-of''' -Reason ($LocalizedData.RecommendLabel -f 'app.kubernetes.io/part-of') 15 | Exists 'metadata.labels.''app.kubernetes.io/managed-by''' -Reason ($LocalizedData.RecommendLabel -f 'app.kubernetes.io/managed-by') 16 | } 17 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.AKS.PublicLB.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Critical 3 | category: Security 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.AKS.PublicLB.md 5 | --- 6 | 7 | # Use internal load balancer 8 | 9 | ## SYNOPSIS 10 | 11 | Use internal Azure load balancers. 12 | 13 | ## DESCRIPTION 14 | 15 | When creating a load balanced service, by default AKS will create and attach an Azure load balancer with a public IP address. 16 | Creating a load balancer with a public IP address may allow Internet clients to connect to applications running on AKS. 17 | 18 | To create a load balanced service with an internal load balancer use the annotation `service.beta.kubernetes.io/azure-load-balancer-internal: "true"`. 19 | When this annotation is used on a load balanced service, the Azure load balancer will only be assigned a private IP address. 20 | 21 | ## RECOMMENDATION 22 | 23 | Consider creating services with an internal load balancer instead of a public load balancer. 24 | 25 | ## LINKS 26 | 27 | - [Use an internal load balancer with Azure Kubernetes Service (AKS)](https://docs.microsoft.com/en-us/azure/aks/internal-lb#create-an-internal-load-balancer) 28 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.API.v1.16.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Important 3 | category: API 4 | online version: https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.API.v1.16.md 5 | --- 6 | 7 | # Use APIs supported in v1.16 8 | 9 | ## SYNOPSIS 10 | 11 | Avoid using legacy API endpoints not served by Kubernetes v1.16. 12 | 13 | ## DESCRIPTION 14 | 15 | In Kubernetes v1.16.0 a number of previously deprecated API endpoints are no longer served. 16 | These endpoints will no longer work for new deployments after upgrading to Kubernetes v1.16.0 or greater. 17 | 18 | To prevent deployment issues use the newer API endpoints for these resources. 19 | 20 | - NetworkPolicy should use `networking.k8s.io/v1`. 21 | - PodSecurityPolicy should use `policy/v1beta1`. 22 | - DaemonSet, Deployment, StatefulSet and ReplicaSet should use `apps/v1`. 23 | 24 | ## RECOMMENDATION 25 | 26 | Consider updating resource deployments to use newer API endpoints prior to upgrading to Kubernetes >= v1.16.0. 27 | 28 | ## LINKS 29 | 30 | - [Kubernetes v1.15.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/main/CHANGELOG/CHANGELOG-1.15.md#deprecations-and-removals) 31 | - [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) 32 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Stale issues 3 | # 4 | 5 | # NOTES: 6 | # Repository stale issue management. 7 | # Issues with open ended labels are automatically closed if no activity occurs. 8 | # Issues are marked stale after 14 days, then closed after a further 7 days. 9 | 10 | name: 'Close stale issues' 11 | on: 12 | schedule: 13 | - cron: '50 2 * * *' # At 2:50 AM, daily 14 | 15 | jobs: 16 | stale: 17 | runs-on: ubuntu-latest 18 | if: github.repository == 'microsoft/PSRule.Rules.Kubernetes' 19 | permissions: 20 | issues: write 21 | pull-requests: write 22 | steps: 23 | 24 | - uses: actions/stale@v8 25 | with: 26 | stale-issue-message: > 27 | This issue has been automatically marked as stale because it has not had 28 | recent activity. It will be closed if no further activity occurs within 7 days. 29 | Thank you for your contributions. 30 | 31 | close-issue-message: 'This issue was closed because it has not had any recent activity.' 32 | 33 | days-before-stale: 14 34 | days-before-pr-stale: -1 35 | 36 | days-before-close: 7 37 | days-before-pr-close: -1 38 | 39 | any-of-labels: 'question,duplicate,incomplete,waiting-feedback' 40 | stale-issue-label: stale 41 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/rules/Kubernetes.Common.Rule.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | function global:GetPodSpec { 5 | [CmdletBinding()] 6 | param () 7 | process { 8 | if ($PSRule.TargetType -eq 'Deployment' -or $PSRule.TargetType -eq 'ReplicaSet') { 9 | return $TargetObject.spec.template.spec; 10 | } 11 | elseif ($PSRule.TargetType -eq 'Pod') { 12 | return $TargetObject.spec; 13 | } 14 | } 15 | } 16 | 17 | function global:GetContainerSpec { 18 | [CmdletBinding()] 19 | param () 20 | process { 21 | (GetPodSpec).containers | Where-Object -FilterScript { 22 | $_.name -notin @('istio-proxy') # Exclude sidecar container 23 | } 24 | } 25 | } 26 | 27 | function global:HasContainerSpec { 28 | [CmdletBinding()] 29 | param () 30 | process { 31 | # Only include pod specs that are standalone. i.e. not already included in a deployment or replicaset 32 | if ($PSRule.TargetType -eq 'Pod' -or $PSRule.TargetType -eq 'ReplicaSet') { 33 | if ($Null -ne $TargetObject.metadata.ownerReferences) { 34 | return $False; 35 | } 36 | } 37 | return @(GetContainerSpec).Length -ge 1; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/dependencies.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Automated dependency updates 3 | # 4 | 5 | # NOTES: 6 | # This automatically bumps PowerShell dependency versions. 7 | 8 | name: Dependencies 9 | on: 10 | schedule: 11 | - cron: '0 1 * * 1' # At 01:00 AM, on Monday each week 12 | workflow_dispatch: 13 | 14 | env: 15 | WORKING_BRANCH: dependencies/powershell-bump 16 | 17 | jobs: 18 | dependencies: 19 | name: Bump dependencies 20 | runs-on: ubuntu-latest 21 | if: github.repository == 'microsoft/PSRule.Rules.Kubernetes' 22 | permissions: 23 | contents: write 24 | pull-requests: write 25 | steps: 26 | 27 | - name: Checkout 28 | uses: actions/checkout@v3 29 | with: 30 | fetch-depth: 0 31 | 32 | - name: Configure 33 | run: | 34 | git config user.name github-actions 35 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com' 36 | 37 | - name: Get working branch 38 | run: | 39 | git checkout -B ${{ env.WORKING_BRANCH }} --force 40 | 41 | - name: Check dependencies 42 | run: | 43 | Import-Module ./scripts/dependencies.psm1; 44 | Update-Dependencies -Path ./modules.json; 45 | shell: pwsh 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | -------------------------------------------------------------------------------- /RuleToc.Doc.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | Document 'module' { 5 | Title 'Module rule reference' 6 | 7 | Import-Module .\out\modules\PSRule.Rules.Kubernetes 8 | $rules = Get-PSRule -Module PSRule.Rules.Kubernetes -WarningAction SilentlyContinue -Baseline AKS | 9 | Add-Member -MemberType ScriptProperty -Name Category -Value { $this.Info.Annotations.category } -PassThru | 10 | Sort-Object -Property Category; 11 | 12 | Section 'Baselines' { 13 | # 'The following baselines are included within `PSRule.Rules.Kubernetes`.' 14 | } 15 | 16 | Section 'Rules' { 17 | 'The following rules are included within `PSRule.Rules.Kubernetes`.' 18 | 19 | $categories = $rules | Group-Object -Property Category; 20 | 21 | foreach ($category in $categories) { 22 | Section "$($category.Name)" { 23 | $category.Group | 24 | Sort-Object -Property RuleName | 25 | Table -Property @{ Name = 'Name'; Expression = { 26 | "[$($_.RuleName)]($($_.RuleName).md)" 27 | }}, Synopsis, @{ Name = 'Severity'; Expression = { 28 | $_.Info.Annotations.severity 29 | }} 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.Pod.PrivilegeEscalation.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Critical 3 | category: Security 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.Pod.PriviledgeEscalation.md 5 | --- 6 | 7 | # Deny privilege escalation 8 | 9 | ## SYNOPSIS 10 | 11 | Containers should deny privilege escalation. 12 | 13 | ## DESCRIPTION 14 | 15 | In the default configuration, container processes are permitted to change the effective user ID through the _setuid_ binary. 16 | Changing the effective user ID could allow a malicious or vulnerable process to gain a higher level of permission then intended. 17 | To prevent this, explicitly set the `securityContext.allowPrivilegeEscalation` option to `false` on pod containers. 18 | 19 | ## RECOMMENDATION 20 | 21 | Consider explicitly setting the `securityContext.allowPrivilegeEscalation` option to `false` on pod containers. 22 | 23 | ## LINKS 24 | 25 | - [Secure container access to resources](https://docs.microsoft.com/en-us/azure/aks/operator-best-practices-cluster-security#secure-container-access-to-resources) 26 | - [Set the security context for a Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) 27 | - [Privilege Escalation](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation) 28 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/rules/Baseline.Rule.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | --- 5 | # Synopsis: A baseline for Kubernetes. 6 | apiVersion: github.com/microsoft/PSRule/v1 7 | kind: ModuleConfig 8 | metadata: 9 | name: PSRule.Rules.Kubernetes 10 | spec: 11 | binding: 12 | targetName: 13 | - metadata.name 14 | targetType: 15 | - kind 16 | field: 17 | namespace: [ 'metadata.namespace' ] 18 | useQualifiedName: true 19 | rule: 20 | baseline: Kubernetes 21 | 22 | --- 23 | # Synopsis: A baseline for Kubernetes. 24 | apiVersion: github.com/microsoft/PSRule/v1 25 | kind: Baseline 26 | metadata: 27 | name: Kubernetes 28 | spec: 29 | binding: 30 | targetName: 31 | - metadata.name 32 | targetType: 33 | - kind 34 | field: 35 | namespace: [ 'metadata.namespace' ] 36 | useQualifiedName: true 37 | rule: 38 | tag: 39 | group: core 40 | 41 | --- 42 | # Synopsis: A baseline for Azure Kubernetes Service (AKS). 43 | apiVersion: github.com/microsoft/PSRule/v1 44 | kind: Baseline 45 | metadata: 46 | name: AKS 47 | spec: 48 | binding: 49 | targetName: 50 | - metadata.name 51 | targetType: 52 | - kind 53 | field: 54 | namespace: [ 'metadata.namespace' ] 55 | useQualifiedName: true 56 | rule: 57 | tag: 58 | group: [ 'core', 'AKS' ] 59 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.API.v1.20.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Important 3 | category: API 4 | online version: https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.API.v1.20.md 5 | --- 6 | 7 | # Use APIs supported in v1.20 8 | 9 | ## SYNOPSIS 10 | 11 | Avoid using legacy API endpoints not served by Kubernetes v1.20. 12 | 13 | ## DESCRIPTION 14 | 15 | In Kubernetes v1.20.0 a number of previously deprecated API endpoints are planned to be no longer served. 16 | These endpoints will no longer work for new deployments after upgrading to Kubernetes v1.20.0 or greater. 17 | 18 | To prevent deployment issues use the newer API endpoints for these resources. 19 | 20 | - Ingress should use `networking.k8s.io/v1beta1`. 21 | - Role, RoleBinding, ClusterRoleBinding and ClusterRole should use `rbac.authorization.k8s.io/v1`. 22 | 23 | ## RECOMMENDATION 24 | 25 | Consider updating resource deployments to use newer API endpoints prior to upgrading to Kubernetes >= v1.20.0. 26 | 27 | ## LINKS 28 | 29 | - [Kubernetes v1.15.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/main/CHANGELOG/CHANGELOG-1.15.md#deprecations-and-removals) 30 | - [Kubernetes v1.17.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/main/CHANGELOG/CHANGELOG-1.17.md#deprecations-and-removals) 31 | - [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) 32 | -------------------------------------------------------------------------------- /.ps-rule/Rule.Rule.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # Synopsis: Use short rule names 5 | Rule 'Rule.Name' -Type 'PSRule.Rules.Rule' { 6 | Recommend 'Rule name should be less than 35 characters to prevent being truncated.' 7 | Reason "The rule name is too long." 8 | $TargetObject.RuleName.Length -le 35 9 | $TargetObject.RuleName.StartsWith('Kubernetes.') 10 | } 11 | 12 | # Synopsis: Complete help documentation 13 | Rule 'Rule.Help' -Type 'PSRule.Rules.Rule' { 14 | $Assert.HasFieldValue($TargetObject, 'Info.Synopsis') 15 | $Assert.HasFieldValue($TargetObject, 'Info.Description') 16 | $Assert.HasFieldValue($TargetObject, 'Info.Recommendation') 17 | } 18 | 19 | # Synopsis: Rules must flag if the Kubernetes feature is core or AKS 20 | Rule 'Rule.Tags' -Type 'PSRule.Rules.Rule' { 21 | Recommend 'Add a group tag to the rule.' 22 | $TargetObject.Tag.ToHashtable() | Within 'group' 'core', 'AKS' -CaseSensitive 23 | } 24 | 25 | # Synopsis: Use severity and category annotations 26 | Rule 'Rule.Annotations' -Type 'PSRule.Rules.Rule' { 27 | $Assert.HasFieldValue($TargetObject, 'Info.Annotations.severity') 28 | $Assert.HasFieldValue($TargetObject, 'Info.Annotations.category') 29 | } 30 | 31 | # Synopsis: Use online help 32 | Rule 'Rule.OnlineHelp' -Type 'PSRule.Rules.Rule' { 33 | $Assert.HasFieldValue($TargetObject, 'Info.Annotations.''online version''') 34 | } 35 | -------------------------------------------------------------------------------- /RuleHelp.Doc.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Generate rule help 6 | # 7 | 8 | Document 'RuleHelp' { 9 | $tags = $InputObject.Tag; 10 | $rule = $InputObject.Info; 11 | Title $rule.Name 12 | 13 | $annotations = [ordered]@{} 14 | if ($Null -ne $rule.Annotations) { 15 | $annotations += $rule.Annotations; 16 | } 17 | elseif ($Null -ne $tags) { 18 | $annotations += $tags.ToHashTable(); 19 | } 20 | 21 | if (!$annotations.Contains('online version')) { 22 | $annotations['online version'] = "https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/$($rule.Name).md"; 23 | } 24 | 25 | Metadata $annotations; 26 | 27 | Section 'SYNOPSIS' -Force { 28 | if ($Null -ne $rule.Synopsis) { 29 | $rule.Synopsis; 30 | } 31 | } 32 | 33 | Section 'DESCRIPTION' -Force { 34 | if ($Null -ne $rule.Description) { 35 | $rule.Description; 36 | } 37 | elseif ($Null -ne $rule.Synopsis) { 38 | $rule.Synopsis; 39 | } 40 | } 41 | 42 | Section 'RECOMMENDATION' -Force { 43 | if ($Null -ne $rule.Recommendation) { 44 | $rule.Recommendation; 45 | } 46 | elseif ($Null -ne $rule.Synopsis) { 47 | $rule.Synopsis; 48 | } 49 | } 50 | 51 | Section 'NOTES' { 52 | $rule.Notes; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Rule.Common.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Unit tests for PSRule rule quality 6 | # 7 | 8 | [CmdletBinding()] 9 | param () 10 | 11 | BeforeAll { 12 | # Setup error handling 13 | $ErrorActionPreference = 'Stop'; 14 | Set-StrictMode -Version latest; 15 | 16 | if ($Env:SYSTEM_DEBUG -eq 'true') { 17 | $VerbosePreference = 'Continue'; 18 | } 19 | 20 | # Setup tests paths 21 | $rootPath = $PWD; 22 | Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 23 | $here = (Resolve-Path $PSScriptRoot).Path; 24 | } 25 | 26 | Describe 'Rule quality' { 27 | BeforeDiscovery { 28 | $rules = Get-PSRule -Module PSRule.Rules.Kubernetes -WarningAction Ignore; 29 | } 30 | 31 | Context 'Naming' { 32 | It '<_.RuleName>' -ForEach $rules { 33 | $_.RuleName.Length -le 35 | Should -Be $True; 34 | } 35 | } 36 | 37 | Context 'Metadata' { 38 | It '<_.RuleName>' -ForEach $rules { 39 | $_.Synopsis | Should -Not -BeNullOrEmpty; 40 | $_.Description | Should -Not -BeNullOrEmpty; 41 | $_.Info.Annotations.category | Should -Not -BeNullOrEmpty; 42 | $_.Info.Annotations.severity | Should -Not -BeNullOrEmpty; 43 | $_.Info.Annotations.'online version' | Should -Not -BeNullOrEmpty; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/rules/en/Kubernetes.Pod.Resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | severity: Important 3 | category: Performance 4 | online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/docs/rules/en/Kubernetes.Pod.Resources.md 5 | --- 6 | 7 | # Set compute resource requirements 8 | 9 | ## SYNOPSIS 10 | 11 | Set CPU and memory requirements for each container. 12 | 13 | ## DESCRIPTION 14 | 15 | The default scheduler uses container compute resource configuration to select a node for scheduling the pod. 16 | If compute resources values are not provided, Kubernetes can't take these into account when making scheduling decisions. 17 | 18 | Compute resources is not the only factor to determine pod placement. 19 | However, if the scheduler places a pod on a host with insufficient resources, pod performance may be impacted. 20 | 21 | If the Kubernetes cluster uses resource quotas, pods that don't specify compute resources may be rejected. 22 | 23 | Compute resources for a container are set within the pod specification by defining `requests` and `limits`. 24 | 25 | ## RECOMMENDATION 26 | 27 | Consider configuring CPU and memory resource requirements for each container. 28 | 29 | ## LINKS 30 | 31 | - [Define pod resource requests and limits](https://docs.microsoft.com/en-us/azure/aks/developer-best-practices-resource-management#define-pod-resource-requests-and-limits) 32 | - [Managing Compute Resources for Containers](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-types) 33 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Resources.Pod.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Kubernetes Pod resources for unit tests 3 | # 4 | 5 | --- 6 | # An example deployment that should pass all rules. 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: deployment-A 11 | spec: 12 | replicas: 2 13 | selector: 14 | matchLabels: 15 | app: app-A 16 | template: 17 | metadata: 18 | labels: 19 | app: app-A 20 | spec: 21 | containers: 22 | - name: app-a 23 | image: app-a-image:v1 24 | securityContext: 25 | allowPrivilegeEscalation: false 26 | resources: 27 | requests: 28 | cpu: 100m 29 | memory: 128Mi 30 | limits: 31 | cpu: 250m 32 | memory: 256Mi 33 | livenessProbe: 34 | httpGet: 35 | path: /healthz 36 | port: 80 37 | initialDelaySeconds: 3 38 | periodSeconds: 3 39 | readinessProbe: 40 | httpGet: 41 | path: /healthz 42 | port: 80 43 | initialDelaySeconds: 3 44 | periodSeconds: 3 45 | ports: 46 | - containerPort: 80 47 | 48 | --- 49 | apiVersion: apps/v1beta1 50 | kind: Deployment 51 | metadata: 52 | name: deployment-B 53 | spec: 54 | replicas: 1 55 | selector: 56 | matchLabels: 57 | app: app-B 58 | template: 59 | metadata: 60 | labels: 61 | app: app-B 62 | spec: 63 | containers: 64 | - name: app-b 65 | image: app-b-image 66 | env: 67 | - name: insecure-password 68 | value: Pass123 69 | -------------------------------------------------------------------------------- /.github/workflows/analyze.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Repository analysis 3 | # 4 | 5 | # NOTES: 6 | # This workflow uses PSRule, CodeQL, and DevSkim. 7 | # You can read more about these linting tools and configuration options here: 8 | # PSRule - https://aka.ms/ps-rule and https://github.com/Microsoft/PSRule.Rules.MSFT.OSS 9 | # DevSkim - https://github.com/microsoft/DevSkim-Action and https://github.com/Microsoft/DevSkim 10 | 11 | name: Analyze 12 | on: 13 | push: 14 | branches: [ main, 'release/*' ] 15 | pull_request: 16 | branches: [ main, 'release/*' ] 17 | schedule: 18 | - cron: '50 20 * * 0' # At 08:50 PM, on Sunday each week 19 | workflow_dispatch: 20 | 21 | jobs: 22 | oss: 23 | name: Analyze with PSRule 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | steps: 28 | 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | 32 | - name: Run PSRule analysis 33 | uses: microsoft/ps-rule@v2.9.0 34 | with: 35 | modules: PSRule.Rules.MSFT.OSS 36 | prerelease: true 37 | 38 | devskim: 39 | name: Analyze with DevSkim 40 | runs-on: ubuntu-latest 41 | permissions: 42 | actions: read 43 | contents: read 44 | security-events: write 45 | steps: 46 | 47 | - name: Checkout 48 | uses: actions/checkout@v3 49 | 50 | - name: Run DevSkim scanner 51 | uses: microsoft/DevSkim-Action@v1 52 | with: 53 | directory-to-scan: src/ 54 | 55 | - name: Upload results to security tab 56 | uses: github/codeql-action/upload-sarif@v2 57 | with: 58 | sarif_file: devskim-results.sarif 59 | -------------------------------------------------------------------------------- /docs/scenarios/install-instructions.md: -------------------------------------------------------------------------------- 1 | # Install instructions 2 | 3 | ## Prerequisites 4 | 5 | - Windows PowerShell 5.1 with .NET Framework 4.7.2+ or 6 | - PowerShell Core 6.2 or greater on Windows, MacOS and Linux 7 | 8 | For a list of platforms that PowerShell Core is supported on [see](https://github.com/PowerShell/PowerShell#get-powershell). 9 | 10 | The following modules are required for `PSRule.Rules.Kubernetes` to work: 11 | 12 | - PSRule 13 | 14 | The required version of each module will automatically be installed along-side `PSRule.Rules.Kubernetes` when using `Install-Module` or `Update-Module` cmdlets. 15 | 16 | ## Getting the modules 17 | 18 | Install from [PowerShell Gallery][module] for all users (requires permissions): 19 | 20 | ```powershell 21 | # Install module 22 | Install-Module -Name 'PSRule.Rules.Kubernetes' -Repository PSGallery; 23 | ``` 24 | 25 | Install from [PowerShell Gallery][module] for current user only: 26 | 27 | ```powershell 28 | # Install module 29 | Install-Module -Name 'PSRule.Rules.Kubernetes' -Repository PSGallery -Scope CurrentUser; 30 | ``` 31 | 32 | Save for offline use from PowerShell Gallery: 33 | 34 | ```powershell 35 | # Save module, in the .\modules directory 36 | Save-Module -Name 'PSRule', 'PSRule.Rules.Kubernetes' -Path '.\modules'; 37 | ``` 38 | 39 | > For pre-release versions the `-AllowPrerelease` switch must be added when calling `Install-Module` or `Save-Module`. 40 | > 41 | > To install pre-release module versions, upgrading to the latest version of _PowerShellGet_ may be required. 42 | To do this use: 43 | > 44 | > `Install-Module -Name PowerShellGet -Repository PSGallery -Scope CurrentUser -Force` 45 | 46 | [module]: https://www.powershellgallery.com/packages/PSRule.Rules.Kubernetes 47 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.AKS.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Unit tests for Kubernetes AKS rules 6 | # 7 | 8 | [CmdletBinding()] 9 | param () 10 | 11 | BeforeAll { 12 | # Setup error handling 13 | $ErrorActionPreference = 'Stop'; 14 | Set-StrictMode -Version latest; 15 | 16 | if ($Env:SYSTEM_DEBUG -eq 'true') { 17 | $VerbosePreference = 'Continue'; 18 | } 19 | 20 | # Setup tests paths 21 | $rootPath = $PWD; 22 | Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 23 | $here = (Resolve-Path $PSScriptRoot).Path; 24 | } 25 | 26 | Describe 'Kubernetes.AKS' { 27 | BeforeAll { 28 | $testParams = @{ 29 | Module = 'PSRule.Rules.Kubernetes' 30 | InputPath = Join-Path -Path $here -ChildPath Resources.AKS.yaml 31 | Baseline = 'AKS' 32 | } 33 | 34 | $result = Invoke-PSRule @testParams -WarningAction Ignore; 35 | } 36 | 37 | Context 'Security' { 38 | It 'Kubernetes.AKS.PublicLB' { 39 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.AKS.PublicLB' }; 40 | 41 | # Fail 42 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 43 | $ruleResult | Should -Not -BeNullOrEmpty; 44 | $ruleResult.Length | Should -Be 1; 45 | $ruleResult.TargetName | Should -Be 'service/service-B'; 46 | 47 | # Pass 48 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 49 | $ruleResult | Should -Not -BeNullOrEmpty; 50 | $ruleResult.Length | Should -Be 2; 51 | $ruleResult.TargetName | Should -BeIn 'service/service-A', 'service/service-C'; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/rules/en/module.md: -------------------------------------------------------------------------------- 1 | # Module rule reference 2 | 3 | ## Rules 4 | 5 | The following rules are included within `PSRule.Rules.Kubernetes`. 6 | 7 | ### API 8 | 9 | Name | Synopsis | Severity 10 | ---- | -------- | -------- 11 | [Kubernetes.API.v1.16](Kubernetes.API.v1.16.md) | Avoid using legacy API endpoints not served by Kubernetes v1.16. | Important 12 | [Kubernetes.API.v1.17](Kubernetes.API.v1.17.md) | Avoid using legacy API endpoints not served by Kubernetes v1.17. | Important 13 | [Kubernetes.API.v1.20](Kubernetes.API.v1.20.md) | Avoid using legacy API endpoints not served by Kubernetes v1.20. | Important 14 | 15 | ### Management 16 | 17 | Name | Synopsis | Severity 18 | ---- | -------- | -------- 19 | [Kubernetes.Metadata](Kubernetes.Metadata.md) | Use Kubernetes common labels. | Awareness 20 | 21 | ### Performance 22 | 23 | Name | Synopsis | Severity 24 | ---- | -------- | -------- 25 | [Kubernetes.Pod.Resources](Kubernetes.Pod.Resources.md) | Set CPU and memory requirements for each container. | Important 26 | 27 | ### Reliability 28 | 29 | Name | Synopsis | Severity 30 | ---- | -------- | -------- 31 | [Kubernetes.Pod.Health](Kubernetes.Pod.Health.md) | Containers should use liveness and readiness probes. | Important 32 | [Kubernetes.Pod.Replicas](Kubernetes.Pod.Replicas.md) | Use two or more replicas. | Important 33 | 34 | ### Security 35 | 36 | Name | Synopsis | Severity 37 | ---- | -------- | -------- 38 | [Kubernetes.AKS.PublicLB](Kubernetes.AKS.PublicLB.md) | Use internal Azure load balancers. | Critical 39 | [Kubernetes.Pod.Latest](Kubernetes.Pod.Latest.md) | Containers should use specific tags instead of latest. | Important 40 | [Kubernetes.Pod.PrivilegeEscalation](Kubernetes.Pod.PrivilegeEscalation.md) | Containers should deny privilege escalation. | Critical 41 | [Kubernetes.Pod.Secrets](Kubernetes.Pod.Secrets.md) | Sensitive environment variables should be referenced as a secret. | Critical 42 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "header-increment": true, 4 | "first-header-h1": { 5 | "level": 1 6 | }, 7 | "header-style": { 8 | "style": "atx" 9 | }, 10 | "ul-style": { 11 | "style": "dash" 12 | }, 13 | "list-indent": true, 14 | "ul-start-left": true, 15 | "ul-indent": { 16 | "indent": 2 17 | }, 18 | "no-trailing-spaces": true, 19 | "no-hard-tabs": true, 20 | "no-reversed-links": true, 21 | "no-multiple-blanks": true, 22 | "line-length": { 23 | "line_length": 100, 24 | "code_blocks": false, 25 | "tables": false, 26 | "headers": true 27 | }, 28 | "commands-show-output": true, 29 | "no-missing-space-atx": true, 30 | "no-multiple-space-atx": true, 31 | "no-missing-space-closed-atx": true, 32 | "no-multiple-space-closed-atx": true, 33 | "blanks-around-headers": true, 34 | "header-start-left": true, 35 | "no-duplicate-header": true, 36 | "single-h1": true, 37 | "no-trailing-punctuation": { 38 | "punctuation": ".,;:!" 39 | }, 40 | "no-multiple-space-blockquote": true, 41 | "no-blanks-blockquote": true, 42 | "ol-prefix": { 43 | "style": "one_or_ordered" 44 | }, 45 | "list-marker-space": true, 46 | "blanks-around-fences": true, 47 | "blanks-around-lists": true, 48 | "no-bare-urls": true, 49 | "hr-style": { 50 | "style": "---" 51 | }, 52 | "no-emphasis-as-header": true, 53 | "no-space-in-emphasis": true, 54 | "no-space-in-code": true, 55 | "no-space-in-links": true, 56 | "fenced-code-language": false, 57 | "first-line-h1": false, 58 | "no-empty-links": true, 59 | "proper-names": { 60 | "names": [ 61 | "PowerShell", 62 | "JavaScript" 63 | ], 64 | "code_blocks": false 65 | }, 66 | "no-alt-text": true 67 | } 68 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.Metadata.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Unit tests for Kubernetes metadata rules 6 | # 7 | 8 | [CmdletBinding()] 9 | param () 10 | 11 | BeforeAll { 12 | # Setup error handling 13 | $ErrorActionPreference = 'Stop'; 14 | Set-StrictMode -Version latest; 15 | 16 | if ($Env:SYSTEM_DEBUG -eq 'true') { 17 | $VerbosePreference = 'Continue'; 18 | } 19 | 20 | # Setup tests paths 21 | $rootPath = $PWD; 22 | Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 23 | $here = (Resolve-Path $PSScriptRoot).Path; 24 | } 25 | 26 | Describe 'Kubernetes.Metadata' { 27 | BeforeAll { 28 | $testParams = @{ 29 | Module = 'PSRule.Rules.Kubernetes' 30 | Option = Join-Path -Path $here -ChildPath ps-rule.yaml 31 | InputPath = Join-Path -Path $here -ChildPath Resources.Metadata.yaml 32 | } 33 | 34 | $result = Invoke-PSRule @testParams -WarningAction Ignore; 35 | } 36 | 37 | Context 'Resource metadata' { 38 | It 'Kubernetes.Metadata' { 39 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.Metadata' }; 40 | 41 | # Fail 42 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 43 | $ruleResult | Should -Not -BeNullOrEmpty; 44 | $ruleResult.Length | Should -Be 2; 45 | $ruleResult.TargetName | Should -BeIn 'deployment/deployment-B', 'service/service-B'; 46 | 47 | # Pass 48 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 49 | $ruleResult | Should -Not -BeNullOrEmpty; 50 | $ruleResult.Length | Should -Be 2; 51 | $ruleResult.TargetName | Should -BeIn 'deployment/deployment-A', 'service/service-A'; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Module.PSGallery.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Unit tests for validating module for publishing 6 | # 7 | 8 | [CmdletBinding()] 9 | param () 10 | 11 | BeforeAll { 12 | # Setup error handling 13 | $ErrorActionPreference = 'Stop'; 14 | Set-StrictMode -Version latest; 15 | 16 | if ($Env:SYSTEM_DEBUG -eq 'true') { 17 | $VerbosePreference = 'Continue'; 18 | } 19 | 20 | # Setup tests paths 21 | $rootPath = $PWD; 22 | $modulePath = Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Kubernetes; 23 | } 24 | 25 | Describe 'PSRule.Rules.Kubernetes' -Tag 'PowerShellGallery' { 26 | Context 'Module' { 27 | It 'Can be imported' { 28 | Import-Module $modulePath -Force; 29 | } 30 | } 31 | 32 | Context 'Manifest' { 33 | 34 | It 'Has required fields' { 35 | $manifestPath = (Join-Path -Path $modulePath -ChildPath PSRule.Rules.Kubernetes.psd1); 36 | $result = Test-ModuleManifest -Path $manifestPath; 37 | $result.Name | Should -Be 'PSRule.Rules.Kubernetes'; 38 | $result.Description | Should -Not -BeNullOrEmpty; 39 | $result.LicenseUri | Should -Not -BeNullOrEmpty; 40 | $result.ReleaseNotes | Should -Not -BeNullOrEmpty; 41 | } 42 | } 43 | 44 | # Context 'Static analysis' { 45 | # $result = Invoke-ScriptAnalyzer -Path $modulePath; 46 | 47 | # $warningCount = ($result | Where-Object { $_.Severity -eq 'Warning' } | Measure-Object).Count; 48 | # $errorCount = ($result | Where-Object { $_.Severity -eq 'Error' } | Measure-Object).Count; 49 | 50 | # if ($warningCount -gt 0) { 51 | # Write-Warning -Message "PSScriptAnalyzer reports $warningCount warnings."; 52 | # } 53 | 54 | # It 'Has no quality errors' { 55 | # $errorCount | Should -BeLessOrEqual 0; 56 | # } 57 | # } 58 | } 59 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Resources.Metadata.yaml: -------------------------------------------------------------------------------- 1 | 2 | # An example deployment that should pass all rules. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: deployment-A 7 | labels: 8 | app.kubernetes.io/name: solution-A 9 | app.kubernetes.io/instance: solution-A-unit-test 10 | app.kubernetes.io/version: 0.0.1 11 | app.kubernetes.io/component: unit-test 12 | app.kubernetes.io/part-of: blueprint-A 13 | app.kubernetes.io/managed-by: helm 14 | spec: 15 | replicas: 1 16 | selector: 17 | matchLabels: 18 | app: app-A 19 | template: 20 | metadata: 21 | labels: 22 | app: app-A 23 | spec: 24 | containers: 25 | - name: green-app 26 | image: green-image:v1 27 | securityContext: 28 | allowPrivilegeEscalation: false 29 | resources: 30 | requests: 31 | cpu: 100m 32 | memory: 128Mi 33 | limits: 34 | cpu: 250m 35 | memory: 256Mi 36 | ports: 37 | - containerPort: 80 38 | 39 | --- 40 | apiVersion: apps/v1 41 | kind: Deployment 42 | metadata: 43 | name: deployment-B 44 | spec: 45 | replicas: 1 46 | selector: 47 | matchLabels: 48 | app: unconstrained-app 49 | template: 50 | metadata: 51 | labels: 52 | app: unconstrained-app 53 | spec: 54 | containers: 55 | - name: unconstrained-app 56 | image: unconstrained-image 57 | 58 | 59 | --- 60 | # An example service that should pass all rules. 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: service-A 65 | labels: 66 | app.kubernetes.io/name: solution-A 67 | app.kubernetes.io/instance: solution-A-unit-test 68 | app.kubernetes.io/version: 0.0.1 69 | app.kubernetes.io/component: unit-test 70 | app.kubernetes.io/part-of: blueprint-A 71 | app.kubernetes.io/managed-by: helm 72 | spec: 73 | ports: 74 | - port: 80 75 | name: http 76 | selector: 77 | app: app-A 78 | 79 | --- 80 | # This service should fail kubernetes.AKS.PublicLoadBalancer 81 | apiVersion: v1 82 | kind: Service 83 | metadata: 84 | name: service-B 85 | spec: 86 | type: LoadBalancer 87 | ports: 88 | - port: 6379 89 | selector: 90 | app: red-app 91 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "test", 8 | "type": "shell", 9 | "command": "Invoke-Build Test -AssertStyle Detect", 10 | "group": { 11 | "kind": "test", 12 | "isDefault": true 13 | }, 14 | "problemMatcher": [ 15 | "$pester" 16 | ], 17 | "presentation": { 18 | "clear": true, 19 | "panel": "dedicated" 20 | } 21 | }, 22 | { 23 | "label": "coverage", 24 | "type": "shell", 25 | "command": "Invoke-Build Test -CodeCoverage", 26 | "problemMatcher": [ 27 | "$pester" 28 | ], 29 | "presentation": { 30 | "clear": true, 31 | "panel": "dedicated" 32 | } 33 | }, 34 | { 35 | "label": "build", 36 | "type": "shell", 37 | "command": "Invoke-Build Build", 38 | "group": { 39 | "kind": "build", 40 | "isDefault": true 41 | }, 42 | "problemMatcher": [], 43 | "presentation": { 44 | "clear": true, 45 | "panel": "dedicated" 46 | } 47 | }, 48 | { 49 | "label": "clean", 50 | "type": "shell", 51 | "command": "Invoke-Build Clean", 52 | "problemMatcher": [] 53 | }, 54 | { 55 | "label": "script-analyzer", 56 | "type": "shell", 57 | "command": "Invoke-Build Analyze", 58 | "problemMatcher": [], 59 | "presentation": { 60 | "clear": true, 61 | "panel": "dedicated" 62 | } 63 | }, 64 | { 65 | "label": "build-docs", 66 | "type": "shell", 67 | "command": "Invoke-Build BuildHelp", 68 | "problemMatcher": [] 69 | }, 70 | { 71 | "label": "scaffold-docs", 72 | "type": "shell", 73 | "command": "Invoke-Build ScaffoldHelp", 74 | "problemMatcher": [] 75 | } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/rules/Kubernetes.API.Rule.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Validation rules for Kubernetes resource requirements 6 | # 7 | 8 | # Synopsis: Avoid using legacy API endpoints for v1.16.0 9 | Rule 'Kubernetes.API.v1.16' -Type DaemonSet, Deployment, StatefulSet, ReplicaSet, NetworkPolicy, PodSecurityPolicy -Tag @{ group = 'core' } { 10 | if ($PSRule.TargetType -eq 'ReplicaSet') { 11 | # Use apps/v1 12 | $TargetObject.apiVersion -notin 'extensions/v1beta1', 'apps/v1beta1', 'apps/v1beta2' 13 | } 14 | elseif ($PSRule.TargetType -eq 'StatefulSet') { 15 | # Use apps/v1 16 | $TargetObject.apiVersion -notin 'apps/v1beta1', 'apps/v1beta2' 17 | } 18 | elseif ($PSRule.TargetType -eq 'Deployment') { 19 | # Use apps/v1 20 | $TargetObject.apiVersion -notin 'extensions/v1beta1', 'apps/v1beta1', 'apps/v1beta2' 21 | } 22 | elseif ($PSRule.TargetType -eq 'DaemonSet') { 23 | # Use apps/v1 24 | $TargetObject.apiVersion -notin 'extensions/v1beta1', 'apps/v1beta2' 25 | } 26 | elseif ($PSRule.TargetType -eq 'NetworkPolicy') { 27 | # Use networking.k8s.io/v1 28 | $TargetObject.apiVersion -notin 'extensions/v1beta1' 29 | } 30 | elseif ($PSRule.TargetType -eq 'PodSecurityPolicy') { 31 | # Use policy/v1beta1 32 | $TargetObject.apiVersion -notin 'extensions/v1beta1' 33 | } 34 | } 35 | 36 | # Synopsis: Avoid using legacy API endpoints for v1.17.0 37 | Rule 'Kubernetes.API.v1.17' -Type PriorityClass -Tag @{ group = 'core' } { 38 | if ($PSRule.TargetType -eq 'PriorityClass') { 39 | # Use scheduling.k8s.io/v1 40 | $TargetObject.apiVersion -notin 'scheduling.k8s.io/v1beta1', 'scheduling.k8s.io/v1alpha1' 41 | } 42 | } 43 | 44 | # Synopsis: Avoid using legacy API endpoints for v1.20.0 45 | Rule 'Kubernetes.API.v1.20' -Type Ingress, Role, RoleBinding, ClusterRoleBinding, ClusterRole -Tag @{ group = 'core' } { 46 | if ($PSRule.TargetType -eq 'Ingress') { 47 | # Use networking.k8s.io/v1beta1 48 | $TargetObject.apiVersion -notin 'extensions/v1beta1' 49 | } 50 | elseif ($PSRule.TargetType -in 'Role', 'RoleBinding', 'ClusterRoleBinding', 'ClusterRole') { 51 | # Use rbac.authorization.k8s.io/v1 52 | $TargetObject.apiVersion -notin 'rbac.authorization.k8s.io/v1alpha1', 'rbac.authorization.k8s.io/v1beta1' 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.azure-pipelines/jobs/test.yaml: -------------------------------------------------------------------------------- 1 | # Azure DevOps 2 | # CI job for running VM pipelines 3 | 4 | parameters: 5 | name: '' 6 | displayName: '' 7 | buildConfiguration: 'Release' 8 | imageName: '' 9 | coverage: 'false' 10 | publishResults: 'true' 11 | platform: 'linux' 12 | 13 | jobs: 14 | - job: ${{ parameters.name }} 15 | displayName: ${{ parameters.displayName }} 16 | pool: 17 | vmImage: ${{ parameters.imageName }} 18 | variables: 19 | COVERAGE: ${{ parameters.coverage }} 20 | PUBLISHRESULTS: ${{ parameters.publishResults }} 21 | skipComponentGovernanceDetection: true 22 | steps: 23 | 24 | # Install pipeline dependencies 25 | - powershell: ./.azure-pipelines/pipeline-deps.ps1 26 | displayName: 'Install dependencies' 27 | 28 | # Download module 29 | - task: DownloadPipelineArtifact@2 30 | displayName: 'Download module' 31 | inputs: 32 | artifact: PSRule.Rules.Kubernetes 33 | path: $(Build.SourcesDirectory)/out/modules/PSRule.Rules.Kubernetes 34 | 35 | # Build module 36 | - powershell: Invoke-Build TestModule -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber) 37 | env: 38 | COVERAGE: ${{ parameters.coverage }} 39 | RUN_BICEP_INTEGRATION: ${{ parameters.bicepIntegration }} 40 | displayName: 'Test module' 41 | 42 | # Pester test results 43 | - task: PublishTestResults@2 44 | displayName: 'Publish Pester results' 45 | inputs: 46 | testRunTitle: 'Pester on ${{ parameters.imageName }}' 47 | testRunner: NUnit 48 | testResultsFiles: 'reports/pester-unit.xml' 49 | mergeTestResults: true 50 | platform: ${{ parameters.name }} 51 | configuration: ${{ parameters.buildConfiguration }} 52 | publishRunAttachments: true 53 | condition: and(succeededOrFailed(), eq(variables['PUBLISHRESULTS'], 'true')) 54 | 55 | # Generate Code Coverage report 56 | - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 57 | displayName: 'Code coverage report generator' 58 | inputs: 59 | reports: 'reports/pester-coverage.xml' 60 | targetdir: 'reports/coverage' 61 | sourcedirs: 'src/PSRule.Rules.Kubernetes' 62 | reporttypes: 'HtmlInline_AzurePipelines;Cobertura;SonarQube;Badges' 63 | tag: $(Build.BuildNumber) 64 | condition: eq(variables['COVERAGE'], 'true') 65 | 66 | # Publish Code Coverage report 67 | - task: PublishCodeCoverageResults@1 68 | displayName: 'Publish Pester code coverage' 69 | inputs: 70 | codeCoverageTool: 'Cobertura' 71 | summaryFileLocation: 'reports/coverage/Cobertura.xml' 72 | reportDirectory: 'reports/coverage' 73 | condition: eq(variables['COVERAGE'], 'true') 74 | -------------------------------------------------------------------------------- /.azure-pipelines/jobs/testContainer.yaml: -------------------------------------------------------------------------------- 1 | # Azure DevOps 2 | # CI job for running container pipelines 3 | 4 | parameters: 5 | name: '' 6 | displayName: '' 7 | buildConfiguration: 'Release' 8 | vmImage: 'ubuntu-22.04' 9 | imageName: '' 10 | imageTag: '' 11 | coverage: 'false' 12 | publishResults: 'true' 13 | 14 | jobs: 15 | - job: ${{ parameters.name }} 16 | displayName: ${{ parameters.displayName }} 17 | pool: 18 | vmImage: ${{ parameters.vmImage }} 19 | container: 20 | image: '${{ parameters.imageName }}:${{ parameters.imageTag }}' 21 | env: 22 | COVERAGE: ${{ parameters.coverage }} 23 | PUBLISHRESULTS: ${{ parameters.publishResults }} 24 | variables: 25 | COVERAGE: ${{ parameters.coverage }} 26 | PUBLISHRESULTS: ${{ parameters.publishResults }} 27 | skipComponentGovernanceDetection: true 28 | steps: 29 | 30 | # Install pipeline dependencies 31 | - powershell: ./.azure-pipelines/pipeline-deps.ps1 32 | displayName: 'Install dependencies' 33 | 34 | # Download module 35 | - task: DownloadPipelineArtifact@2 36 | displayName: 'Download module' 37 | inputs: 38 | artifact: PSRule.Rules.Kubernetes 39 | path: $(Build.SourcesDirectory)/out/modules/PSRule.Rules.Kubernetes 40 | 41 | # Build module 42 | - powershell: Invoke-Build TestModule -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber) 43 | displayName: 'Test module' 44 | 45 | # Pester test results 46 | - task: PublishTestResults@2 47 | displayName: 'Publish Pester results' 48 | inputs: 49 | testRunTitle: 'Pester on ${{ parameters.imageTag }}' 50 | testRunner: NUnit 51 | testResultsFiles: 'reports/pester-unit.xml' 52 | mergeTestResults: true 53 | platform: ${{ parameters.imageTag }} 54 | configuration: ${{ parameters.buildConfiguration }} 55 | publishRunAttachments: true 56 | condition: and(succeededOrFailed(), eq(variables['PUBLISHRESULTS'], 'true')) 57 | 58 | # Generate Code Coverage report 59 | - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 60 | displayName: 'Code coverage report generator' 61 | inputs: 62 | reports: 'reports\pester-coverage.xml' 63 | targetdir: 'reports\coverage' 64 | sourcedirs: 'src\PSRule.Rules.Kubernetes' 65 | reporttypes: 'HtmlInline_AzurePipelines;Cobertura;SonarQube;Badges' 66 | tag: $(Build.BuildNumber) 67 | condition: eq(variables['COVERAGE'], 'true') 68 | 69 | # Publish Code Coverage report 70 | - task: PublishCodeCoverageResults@1 71 | displayName: 'Publish Pester code coverage' 72 | inputs: 73 | codeCoverageTool: 'Cobertura' 74 | summaryFileLocation: 'reports/coverage/Cobertura.xml' 75 | reportDirectory: 'reports/coverage' 76 | condition: eq(variables['COVERAGE'], 'true') 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | ## Code of Conduct 13 | 14 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 15 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 16 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 17 | 18 | ## How to contribute 19 | 20 | - File or vote up issues 21 | - Improve documentation 22 | - Fix bugs or add features 23 | 24 | ### Intro to Git and GitHub 25 | 26 | When contributing to documentation or code changes, you'll need to have a GitHub account and a basic understanding of Git. 27 | Check out the links below to get started. 28 | 29 | - Make sure you have a [GitHub account][github-signup]. 30 | - GitHub Help: 31 | - [Git and GitHub learning resources][learn-git]. 32 | - [GitHub Flow Guide][github-flow]. 33 | - [Fork a repo][github-fork]. 34 | - [About Pull Requests][github-pr]. 35 | 36 | ## Contributing to issues 37 | 38 | - Check if the issue you are going to file already exists in our GitHub [issues](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues). 39 | - If you do not see your problem captured, please file a new issue and follow the provided template. 40 | - If the an open issue exists for the problem you are experiencing, vote up the issue or add a comment. 41 | 42 | ## Contributing to code 43 | 44 | - Before writing a fix or feature enhancement, ensure that an issue is logged. 45 | - Be prepared to discuss a feature and take feedback. 46 | - Include unit tests and updates documentation to complement the change. 47 | 48 | [learn-git]: https://help.github.com/en/articles/git-and-github-learning-resources 49 | [github-flow]: https://guides.github.com/introduction/flow/ 50 | [github-signup]: https://github.com/signup/free 51 | [github-fork]: https://help.github.com/en/github/getting-started-with-github/fork-a-repo 52 | [github-pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests 53 | [github-pr-create]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork 54 | [build]: docs/scenarios/install-instructions.md#building-from-source 55 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | 4 | 5 | ## Security 6 | 7 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 8 | 9 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 10 | 11 | ## Reporting Security Issues 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues.** 14 | 15 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 16 | 17 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 18 | 19 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 20 | 21 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 22 | 23 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 24 | * Full paths of source file(s) related to the manifestation of the issue 25 | * The location of the affected source code (tag/branch/commit or direct URL) 26 | * Any special configuration required to reproduce the issue 27 | * Step-by-step instructions to reproduce the issue 28 | * Proof-of-concept or exploit code (if possible) 29 | * Impact of the issue, including how an attacker might exploit the issue 30 | 31 | This information will help us triage your report more quickly. 32 | 33 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 34 | 35 | ## Preferred Languages 36 | 37 | We prefer all communications to be in English. 38 | 39 | ## Policy 40 | 41 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/rules/Kubernetes.Pod.Rule.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Validation rules for Kubernetes deployments 6 | # 7 | 8 | # Synopsis: Containers should deny privilege escalation 9 | Rule 'Kubernetes.Pod.PrivilegeEscalation' -Type Deployment, Pod, ReplicaSet -If { (HasContainerSpec) } -Tag @{ group = 'core' } { 10 | foreach ($container in (GetContainerSpec)) { 11 | $container | Exists 'securityContext.allowPrivilegeEscalation' 12 | $container.securityContext.allowPrivilegeEscalation -eq $False 13 | } 14 | } 15 | 16 | # Synopsis: Containers should use specific tags instead of latest 17 | Rule 'Kubernetes.Pod.Latest' -Type Deployment, Pod, ReplicaSet -If { (HasContainerSpec) } -Tag @{ group = 'core' } { 18 | foreach ($container in (GetContainerSpec)) { 19 | $container.image -like '*:*' -and 20 | $container.image -notlike '*:latest' 21 | } 22 | } 23 | 24 | # Synopsis: Resource requirements are set for each container 25 | Rule 'Kubernetes.Pod.Resources' -Type Deployment, Pod, ReplicaSet -If { (HasContainerSpec) } -Tag @{ group = 'core' } { 26 | foreach ($container in (GetContainerSpec)) { 27 | $container | Exists 'resources.requests.cpu' -Reason $LocalizedData.PodCPURequest 28 | $container | Exists 'resources.requests.memory' -Reason $LocalizedData.PodMemRequest 29 | $container | Exists 'resources.limits.cpu' -Reason $LocalizedData.PodCPULimit 30 | $container | Exists 'resources.limits.memory' -Reason $LocalizedData.PodMemLimit 31 | } 32 | } 33 | 34 | # Synopsis: Sensitive environment variables should be secured 35 | Rule 'Kubernetes.Pod.Secrets' -Type Deployment, Pod, ReplicaSet -If { (HasContainerSpec) } -Tag @{ group = 'core' } { 36 | foreach ($container in (GetContainerSpec)) { 37 | if ($Assert.HasField($container, 'env').Result) { 38 | foreach ($variable in $container.env) { 39 | if ($variable.name -like "*secret*" -or $variable.name -like "*password*" -or $variable.name -like "*pwd*" -or $variable.name -like "*key" -or $variable.name -like "*connectionstring") { 40 | $variable | Exists -Not -Field 'value' 41 | } 42 | else { 43 | $True 44 | } 45 | } 46 | } 47 | else { 48 | $True 49 | } 50 | } 51 | } 52 | 53 | # Synopsis: Containers should use liveness and readiness probes 54 | Rule 'Kubernetes.Pod.Health' -Type Deployment, Pod, ReplicaSet -If { (HasContainerSpec) } -Tag @{ group = 'core' } { 55 | foreach ($container in (GetContainerSpec)) { 56 | $container | Exists 'livenessProbe' -Reason ($LocalizedData.LivenessProbe -f $container.name) 57 | } 58 | foreach ($container in (GetContainerSpec)) { 59 | $container | Exists 'readinessProbe' -Reason ($LocalizedData.ReadinessProbe -f $container.name) 60 | } 61 | } 62 | 63 | # Synopsis: Use two or more replicas 64 | Rule 'Kubernetes.Pod.Replicas' -Type Deployment, ReplicaSet, StatefulSet -Tag @{ group = 'core' } { 65 | Exists 'spec.replicas' 66 | $TargetObject.spec.replicas -ge 2 67 | } 68 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.API.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Unit tests for Kubernetes resource rules 6 | # 7 | 8 | [CmdletBinding()] 9 | param () 10 | 11 | BeforeAll { 12 | # Setup error handling 13 | $ErrorActionPreference = 'Stop'; 14 | Set-StrictMode -Version latest; 15 | 16 | if ($Env:SYSTEM_DEBUG -eq 'true') { 17 | $VerbosePreference = 'Continue'; 18 | } 19 | 20 | # Setup tests paths 21 | $rootPath = $PWD; 22 | Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 23 | $here = (Resolve-Path $PSScriptRoot).Path; 24 | } 25 | 26 | Describe 'Kubernetes.API' { 27 | BeforeAll { 28 | $testParams = @{ 29 | Module = 'PSRule.Rules.Kubernetes' 30 | Option = Join-Path -Path $here -ChildPath ps-rule.yaml 31 | InputPath = Join-Path -Path $here -ChildPath Resources.API.yaml 32 | } 33 | 34 | $result = Invoke-PSRule @testParams -WarningAction Ignore; 35 | } 36 | 37 | Context 'API' { 38 | It 'Kubernetes.API.v1.16' { 39 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.API.v1.16' }; 40 | 41 | # Fail 42 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 43 | $ruleResult | Should -Not -BeNullOrEmpty; 44 | $ruleResult.Length | Should -Be 1; 45 | $ruleResult.TargetName | Should -Be 'Deployment/deployment-B'; 46 | 47 | # Pass 48 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 49 | $ruleResult | Should -Not -BeNullOrEmpty; 50 | $ruleResult.Length | Should -Be 1; 51 | $ruleResult.TargetName | Should -Be 'Deployment/deployment-A'; 52 | } 53 | 54 | It 'Kubernetes.API.v1.17' { 55 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.API.v1.17' }; 56 | 57 | # Fail 58 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 59 | $ruleResult | Should -Not -BeNullOrEmpty; 60 | $ruleResult.Length | Should -Be 1; 61 | $ruleResult.TargetName | Should -Be 'PriorityClass/priority-B'; 62 | 63 | # Pass 64 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 65 | $ruleResult | Should -Not -BeNullOrEmpty; 66 | $ruleResult.Length | Should -Be 1; 67 | $ruleResult.TargetName | Should -Be 'PriorityClass/priority-A'; 68 | } 69 | 70 | It 'Kubernetes.API.v1.20' { 71 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.API.v1.20' }; 72 | 73 | # Fail 74 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 75 | $ruleResult | Should -Not -BeNullOrEmpty; 76 | $ruleResult.Length | Should -Be 5; 77 | $ruleResult.TargetName | Should -BeIn 'Ingress/ingress-A', 'ClusterRole/clusterRole-B', 'Role/role-B', 'ClusterRoleBinding/clusterRoleBinding-B', 'RoleBinding/roleBinding-B'; 78 | 79 | # Pass 80 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 81 | $ruleResult | Should -Not -BeNullOrEmpty; 82 | $ruleResult.Length | Should -Be 5; 83 | $ruleResult.TargetName | Should -BeIn 'Ingress/ingress-B', 'ClusterRole/clusterRole-A', 'Role/role-A', 'ClusterRoleBinding/clusterRoleBinding-A', 'RoleBinding/roleBinding-A'; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Resources.API.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Kubernetes Pod resources for unit tests 3 | # 4 | 5 | --- 6 | # An example deployment that should pass all rules. 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: deployment-A 11 | spec: 12 | replicas: 2 13 | selector: 14 | matchLabels: 15 | app: app-A 16 | template: 17 | metadata: 18 | labels: 19 | app: app-A 20 | spec: 21 | containers: 22 | - name: app-a 23 | image: app-a-image:v1 24 | securityContext: 25 | allowPrivilegeEscalation: false 26 | resources: 27 | requests: 28 | cpu: 100m 29 | memory: 128Mi 30 | limits: 31 | cpu: 250m 32 | memory: 256Mi 33 | livenessProbe: 34 | httpGet: 35 | path: /healthz 36 | port: 80 37 | initialDelaySeconds: 3 38 | periodSeconds: 3 39 | readinessProbe: 40 | httpGet: 41 | path: /healthz 42 | port: 80 43 | initialDelaySeconds: 3 44 | periodSeconds: 3 45 | ports: 46 | - containerPort: 80 47 | 48 | --- 49 | apiVersion: apps/v1beta1 50 | kind: Deployment 51 | metadata: 52 | name: deployment-B 53 | spec: 54 | replicas: 1 55 | selector: 56 | matchLabels: 57 | app: app-B 58 | template: 59 | metadata: 60 | labels: 61 | app: app-B 62 | spec: 63 | containers: 64 | - name: app-b 65 | image: app-b-image 66 | env: 67 | - name: insecure-password 68 | value: Pass123 69 | 70 | --- 71 | apiVersion: scheduling.k8s.io/v1 72 | kind: PriorityClass 73 | metadata: 74 | name: priority-A 75 | value: 1000000 76 | globalDefault: false 77 | description: "This is a high priority class." 78 | 79 | --- 80 | apiVersion: scheduling.k8s.io/v1beta1 81 | kind: PriorityClass 82 | metadata: 83 | name: priority-B 84 | value: 100 85 | globalDefault: false 86 | description: "This is a low priority class." 87 | 88 | --- 89 | apiVersion: extensions/v1beta1 90 | kind: Ingress 91 | metadata: 92 | name: ingress-A 93 | spec: {} 94 | 95 | --- 96 | apiVersion: networking.k8s.io/v1beta1 97 | kind: Ingress 98 | metadata: 99 | name: ingress-B 100 | spec: {} 101 | 102 | --- 103 | kind: ClusterRole 104 | apiVersion: rbac.authorization.k8s.io/v1 105 | metadata: 106 | name: clusterRole-A 107 | rules: [] 108 | 109 | --- 110 | kind: Role 111 | apiVersion: rbac.authorization.k8s.io/v1 112 | metadata: 113 | name: role-A 114 | rules: [] 115 | 116 | --- 117 | kind: RoleBinding 118 | apiVersion: rbac.authorization.k8s.io/v1 119 | metadata: 120 | name: roleBinding-A 121 | roleRef: 122 | kind: Role 123 | name: role-A 124 | apiGroup: rbac.authorization.k8s.io 125 | subjects: [] 126 | 127 | --- 128 | kind: ClusterRoleBinding 129 | apiVersion: rbac.authorization.k8s.io/v1 130 | metadata: 131 | name: clusterRoleBinding-A 132 | roleRef: 133 | kind: ClusterRole 134 | name: clusterRole-A 135 | apiGroup: rbac.authorization.k8s.io 136 | subjects: [] 137 | 138 | --- 139 | kind: ClusterRole 140 | apiVersion: rbac.authorization.k8s.io/v1beta1 141 | metadata: 142 | name: clusterRole-B 143 | rules: [] 144 | 145 | --- 146 | kind: Role 147 | apiVersion: rbac.authorization.k8s.io/v1alpha1 148 | metadata: 149 | name: role-B 150 | rules: [] 151 | 152 | --- 153 | kind: RoleBinding 154 | apiVersion: rbac.authorization.k8s.io/v1alpha1 155 | metadata: 156 | name: roleBinding-B 157 | roleRef: 158 | kind: Role 159 | name: role-B 160 | apiGroup: rbac.authorization.k8s.io 161 | subjects: [] 162 | 163 | --- 164 | kind: ClusterRoleBinding 165 | apiVersion: rbac.authorization.k8s.io/v1beta1 166 | metadata: 167 | name: clusterRoleBinding-B 168 | roleRef: 169 | kind: ClusterRole 170 | name: clusterRole-B 171 | apiGroup: rbac.authorization.k8s.io 172 | subjects: [] 173 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## Unreleased 4 | 5 | What's changed since v0.2.0: 6 | 7 | - Engineering: 8 | - Bump PSRule dependency to v2.7.0. 9 | [#78](https://github.com/microsoft/PSRule.Rules.Kubernetes/issues/78) 10 | - Bump Pester to v5.4.0. 11 | [#78](https://github.com/microsoft/PSRule.Rules.Kubernetes/issues/78) 12 | 13 | ## v0.2.0 14 | 15 | What's changed since v0.1.0: 16 | 17 | - New rules: 18 | - API deprecation removals: 19 | - Check for planned Kubernetes v1.17.0 API deprecation removals. 20 | [#38](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/38) 21 | - Check for planned Kubernetes v1.20.0 API deprecation removals. 22 | [#39](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/39) 23 | - General improvements: 24 | - **Breaking change**: Use qualified target names. 25 | [#36](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/36) 26 | - If using suppression, update suppressed target name with qualified name. 27 | - **Breaking change**: Renamed `Kubernetes.API.Removal` to handle future API deprecations. 28 | [#40](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/40) 29 | - The rule `Kubernetes.API.Removal` is now `Kubernetes.API.v1.16`. 30 | 31 | What's changed since pre-release v0.2.0-B2002005: 32 | 33 | - No additional changes. 34 | 35 | ## v0.2.0-B2002005 (pre-release) 36 | 37 | - Added new rules for API deprecation removals: 38 | - Planned Kubernetes v1.17.0 deprecation removals. [#38](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/38) 39 | - Planned Kubernetes v1.20.0 deprecation removals. [#39](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/39) 40 | - **Breaking change**: Use qualified target names. [#36](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/36) 41 | - If using suppression, update suppressed target name with qualified name. 42 | - **Breaking change**: Renamed `Kubernetes.API.Removal` to handle future API deprecations. 43 | [#40](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/40) 44 | - The rule `Kubernetes.API.Removal` is now `Kubernetes.API.v1.16`. 45 | 46 | ## v0.1.0 47 | 48 | - Initial release. 49 | 50 | What's changed since pre-release v0.1.0-B2001007: 51 | 52 | - Updated documentation to use parent culture `en`. 53 | [#30](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/30) 54 | 55 | ## v0.1.0-B2001007 (pre-release) 56 | 57 | - **Breaking change**: Updated and renamed baselines make them easier to use. 58 | [#27](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/27) 59 | - `KubeBaseline` is now `Kubernetes`, the default baseline. 60 | - `AKSBaseline` is now `AKS`. 61 | - The `Kubernetes` baseline include common Kubernetes rules. 62 | - The `AKS` baseline include all of `Kubernetes` plus additional AKS specific rules. 63 | 64 | ## v0.1.0-B1912003 (pre-release) 65 | 66 | - Fixed `Kubernetes.AKS.PublicLB` handling of internal LB annotation. 67 | [#17](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/17) 68 | - Updated metadata rule to align to recommended labels. 69 | [#14](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/14) 70 | - Expanded deployment rules to include pods and replica sets. 71 | [#13](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/13) 72 | - Added rule documentation. 73 | [#5](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/5) 74 | - Added new rule `Kubernetes.API.Removal` to check for use of removed APIs. 75 | [#18](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/18) 76 | - Added new rule `Kubernetes.Pod.Secrets` to check if sensitive environment variables are used. 77 | [#19](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/19) 78 | - Added new rule `Kubernetes.Pod.Health` to check health probes are used. 79 | [#20](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/20) 80 | - Added new rule `Kubernetes.Pod.Replicas` to check if more then one replica is used. 81 | [#21](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/21) 82 | - **Breaking change**: Renamed deployment rules to relate to pods. 83 | [#12](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/12) 84 | 85 | ## v0.1.0-B190521 (pre-release) 86 | 87 | - Initial pre-release. 88 | -------------------------------------------------------------------------------- /src/PSRule.Rules.Kubernetes/PSRule.Rules.Kubernetes.psd1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # PSRule.Rules.Kubernetes 6 | # 7 | 8 | @{ 9 | 10 | # Script module or binary module file associated with this manifest. 11 | # RootModule = '' 12 | 13 | # Version number of this module. 14 | ModuleVersion = '0.0.1' 15 | 16 | # Supported PSEditions 17 | CompatiblePSEditions = 'Core', 'Desktop' 18 | 19 | # ID used to uniquely identify this module 20 | GUID = 'efaacb4d-b447-4de3-96b9-93860fd87a8c' 21 | 22 | # Author of this module 23 | Author = 'Microsoft Corporation' 24 | 25 | # Company or vendor of this module 26 | CompanyName = 'Microsoft Corporation' 27 | 28 | # Copyright statement for this module 29 | Copyright = '(c) Microsoft Corporation. All rights reserved.' 30 | 31 | # Description of the functionality provided by this module 32 | Description = 'Validate Kubernetes resources using PSRule. 33 | 34 | This project is to be considered a proof-of-concept and not a supported product.' 35 | 36 | # Minimum version of the Windows PowerShell engine required by this module 37 | PowerShellVersion = '5.1' 38 | 39 | # Name of the Windows PowerShell host required by this module 40 | # PowerShellHostName = '' 41 | 42 | # Minimum version of the Windows PowerShell host required by this module 43 | # PowerShellHostVersion = '' 44 | 45 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 46 | DotNetFrameworkVersion = '4.7.2' 47 | 48 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 49 | # CLRVersion = '' 50 | 51 | # Processor architecture (None, X86, Amd64) required by this module 52 | # ProcessorArchitecture = '' 53 | 54 | # Modules that must be imported into the global environment prior to importing this module 55 | RequiredModules = @( 56 | @{ ModuleName = 'PSRule'; ModuleVersion = '0.0.1' } 57 | ) 58 | 59 | # Assemblies that must be loaded prior to importing this module 60 | # RequiredAssemblies = @() 61 | 62 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 63 | # ScriptsToProcess = @() 64 | 65 | # Type files (.ps1xml) to be loaded when importing this module 66 | # TypesToProcess = @() 67 | 68 | # Format files (.ps1xml) to be loaded when importing this module 69 | # FormatsToProcess = @() 70 | 71 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 72 | # NestedModules = @() 73 | 74 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 75 | FunctionsToExport = @() 76 | 77 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 78 | CmdletsToExport = @() 79 | 80 | # Variables to export from this module 81 | VariablesToExport = @() 82 | 83 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 84 | AliasesToExport = @() 85 | 86 | # DSC resources to export from this module 87 | # DscResourcesToExport = @() 88 | 89 | # List of all modules packaged with this module 90 | # ModuleList = @() 91 | 92 | # List of all files packaged with this module 93 | # FileList = @() 94 | 95 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 96 | PrivateData = @{ 97 | PSData = @{ 98 | # Tags applied to this module. These help with module discovery in online galleries. 99 | Tags = @('PSRule', 'PSRule-rules', 'Rule', 'Kubernetes') 100 | 101 | # A URL to the license for this module. 102 | LicenseUri = 'https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/LICENSE' 103 | 104 | # A URL to the main website for this project. 105 | ProjectUri = 'https://github.com/Microsoft/PSRule.Rules.Kubernetes' 106 | 107 | # A URL to an icon representing this module. 108 | # IconUri = '' 109 | 110 | # ReleaseNotes of this module 111 | ReleaseNotes = 'https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/CHANGELOG.md' 112 | } # End of PSData hashtable 113 | } # End of PrivateData hashtable 114 | 115 | # HelpInfo URI of this module 116 | # HelpInfoURI = '' 117 | 118 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 119 | # DefaultCommandPrefix = '' 120 | 121 | } 122 | -------------------------------------------------------------------------------- /.azure-pipelines/azure-pipelines.yaml: -------------------------------------------------------------------------------- 1 | # Azure DevOps 2 | # CI pipeline for PSRule.Rules.Kubernetes 3 | 4 | variables: 5 | version: '0.3.0' 6 | buildConfiguration: 'Release' 7 | disable.coverage.autogenerate: 'true' 8 | imageName: 'ubuntu-22.04' 9 | 10 | # Use build number format, i.e. 0.3.0-B2205001 11 | name: $(version)-B$(date:yyMM)$(rev:rrr) 12 | 13 | trigger: 14 | branches: 15 | include: 16 | - 'main' 17 | tags: 18 | include: 19 | - 'v0.*' 20 | 21 | pr: 22 | branches: 23 | include: 24 | - 'main' 25 | 26 | stages: 27 | 28 | # Build pipeline 29 | - stage: Build 30 | displayName: Build 31 | dependsOn: [] 32 | jobs: 33 | - job: 34 | pool: 35 | vmImage: $(imageName) 36 | displayName: 'Module' 37 | steps: 38 | 39 | # Install pipeline dependencies 40 | - powershell: ./.azure-pipelines/pipeline-deps.ps1 41 | displayName: 'Install dependencies' 42 | 43 | # Build module 44 | - powershell: Invoke-Build -Configuration $(buildConfiguration) -Build $(Build.BuildNumber) 45 | displayName: 'Build module' 46 | 47 | # PSRule results 48 | - task: PublishTestResults@2 49 | displayName: 'Publish PSRule results' 50 | inputs: 51 | testRunTitle: 'PSRule on $(imageName)' 52 | testRunner: NUnit 53 | testResultsFiles: 'reports/ps-rule*.xml' 54 | mergeTestResults: true 55 | platform: $(imageName) 56 | configuration: $(buildConfiguration) 57 | publishRunAttachments: true 58 | condition: succeededOrFailed() 59 | 60 | # Generate artifacts 61 | - publish: out/modules/PSRule.Rules.Kubernetes 62 | displayName: 'Publish module' 63 | artifact: PSRule.Rules.Kubernetes 64 | 65 | # Analysis pipeline 66 | - stage: Analysis 67 | displayName: Analysis 68 | dependsOn: [] 69 | jobs: 70 | 71 | - job: Secret_Scan 72 | pool: 73 | vmImage: 'windows-2022' 74 | displayName: Secret scan 75 | steps: 76 | 77 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2 78 | displayName: 'Scan for secrets' 79 | inputs: 80 | debugMode: false 81 | toolMajorVersion: V2 82 | 83 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@2 84 | displayName: 'Publish scan logs' 85 | continueOnError: true 86 | 87 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1 88 | displayName: 'Check for failures' 89 | inputs: 90 | CredScan: true 91 | ToolLogsNotFoundAction: Error 92 | 93 | # Test pipeline 94 | - stage: Test 95 | dependsOn: Build 96 | jobs: 97 | 98 | - template: jobs/test.yaml 99 | parameters: 100 | name: ubuntu_22_04_coverage 101 | imageName: 'ubuntu-22.04' 102 | displayName: 'PowerShell coverage' 103 | coverage: 'true' 104 | platform: linux 105 | publishResults: 'false' 106 | 107 | - template: jobs/test.yaml 108 | parameters: 109 | name: macOS_12 110 | displayName: 'PowerShell 7.2 - macOS-12' 111 | imageName: 'macos-12' 112 | platform: macos 113 | 114 | - template: jobs/test.yaml 115 | parameters: 116 | name: ps_5_1_windows_2022 117 | displayName: 'PowerShell 5.1 - Windows 2022' 118 | imageName: 'windows-2022' 119 | platform: windows 120 | pwsh: 'false' 121 | 122 | - template: jobs/test.yaml 123 | parameters: 124 | name: ps_7_2_windows_2022 125 | displayName: 'PowerShell 7.2 - Windows 2022' 126 | imageName: 'windows-2022' 127 | platform: windows 128 | 129 | - template: jobs/testContainer.yaml 130 | parameters: 131 | name: ps_7_2_ubuntu_22_04 132 | displayName: 'PowerShell 7.2 - ubuntu-22.04' 133 | imageName: mcr.microsoft.com/powershell 134 | imageTag: 7.2-ubuntu-22.04 135 | 136 | # Release pipeline 137 | - stage: Release 138 | displayName: Release 139 | dependsOn: [ 'Test', 'Analysis' ] 140 | condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v0.')) 141 | jobs: 142 | - job: 143 | displayName: Live 144 | pool: 145 | vmImage: $(imageName) 146 | variables: 147 | isPreRelease: $[contains(variables['Build.SourceBranchName'], '-B')] 148 | steps: 149 | 150 | # Download module from build 151 | - task: DownloadPipelineArtifact@2 152 | displayName: 'Download module' 153 | inputs: 154 | artifact: PSRule.Rules.Kubernetes 155 | path: $(Build.SourcesDirectory)/out/modules/PSRule.Rules.Kubernetes 156 | 157 | # Install pipeline dependencies 158 | - powershell: ./.azure-pipelines/pipeline-deps.ps1 159 | displayName: 'Install dependencies' 160 | 161 | # Install pipeline dependencies and build module 162 | - powershell: Invoke-Build Release -ApiKey $(apiKey) 163 | displayName: 'Publish module' 164 | 165 | # Update GitHub release 166 | - task: GitHubRelease@1 167 | displayName: 'GitHub release' 168 | inputs: 169 | gitHubConnection: 'AzureDevOps-PSRule.Rules.Kubernetes' 170 | repositoryName: '$(Build.Repository.Name)' 171 | action: edit 172 | tag: '$(Build.SourceBranchName)' 173 | releaseNotesSource: inline 174 | releaseNotesInline: 'See [change log](https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/main/CHANGELOG.md)' 175 | assetUploadMode: replace 176 | addChangeLog: false 177 | isPreRelease: $(isPreRelease) 178 | -------------------------------------------------------------------------------- /scripts/dependencies.psm1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # Note: 5 | # Handles dependencies updates. 6 | 7 | function Update-Dependencies { 8 | [CmdletBinding()] 9 | param ( 10 | [Parameter(Mandatory = $False)] 11 | [String]$Path = (Join-Path -Path $PWD -ChildPath 'modules.json'), 12 | 13 | [Parameter(Mandatory = $False)] 14 | [String]$Repository = 'PSGallery' 15 | ) 16 | process { 17 | $modules = Get-Content -Path $Path -Raw | ConvertFrom-Json -AsHashtable; 18 | $dependencies = CheckVersion $modules.dependencies -Repository $Repository; 19 | $devDependencies = CheckVersion $modules.devDependencies -Repository $Repository -Dev; 20 | 21 | $modules = [Ordered]@{ 22 | dependencies = $dependencies 23 | devDependencies = $devDependencies 24 | } 25 | $modules | ConvertTo-Json -Depth 10 | Set-Content -Path $Path; 26 | 27 | $updates = @(git status --porcelain); 28 | if ($Null -ne $Env:WORKING_BRANCH -and $Null -ne $updates -and $updates.Length -gt 0) { 29 | git add modules.json; 30 | git commit -m "Update $path"; 31 | git push --force -u origin $Env:WORKING_BRANCH; 32 | 33 | $existingBranch = @(gh pr list --head $Env:WORKING_BRANCH --state open --json number | ConvertFrom-Json); 34 | if ($Null -eq $existingBranch -or $existingBranch.Length -eq 0) { 35 | gh pr create -B 'main' -H $Env:WORKING_BRANCH -l 'dependencies' -t 'Bump PowerShell dependencies' -F 'out/updates.txt'; 36 | } 37 | else { 38 | $pr = $existingBranch[0].number 39 | gh pr edit $pr -F 'out/updates.txt'; 40 | } 41 | } 42 | } 43 | } 44 | 45 | function Install-Dependencies { 46 | [CmdletBinding()] 47 | param ( 48 | [Parameter(Mandatory = $False)] 49 | [String]$Path = (Join-Path -Path $PWD -ChildPath 'modules.json'), 50 | 51 | [Parameter(Mandatory = $False)] 52 | [String]$Repository = 'PSGallery', 53 | 54 | [Parameter(Mandatory = $False)] 55 | [Switch]$Dev 56 | ) 57 | process { 58 | $modules = Get-Content -Path $Path -Raw | ConvertFrom-Json; 59 | InstallVersion $modules.dependencies -Repository $Repository; 60 | if ($Dev) { 61 | InstallVersion $modules.devDependencies -Repository $Repository -Dev; 62 | } 63 | } 64 | } 65 | 66 | function CheckVersion { 67 | [CmdletBinding()] 68 | [OutputType([System.Collections.Specialized.OrderedDictionary])] 69 | param ( 70 | [Parameter(Mandatory = $True)] 71 | [Hashtable]$InputObject, 72 | 73 | [Parameter(Mandatory = $True)] 74 | [String]$Repository, 75 | 76 | [Parameter(Mandatory = $False)] 77 | [Switch]$Dev, 78 | 79 | [Parameter(Mandatory = $False)] 80 | [String]$OutputPath = 'out/' 81 | ) 82 | begin { 83 | $group = 'Dependencies'; 84 | if ($Dev) { 85 | $group = 'DevDependencies'; 86 | } 87 | if (!(Test-Path -Path $OutputPath)) { 88 | $Null = New-Item -Path $OutputPath -ItemType Directory -Force; 89 | } 90 | $changeNotes = Join-Path -Path $OutputPath -ChildPath 'updates.txt'; 91 | } 92 | process { 93 | $dependencies = [Ordered]@{ }; 94 | $InputObject.GetEnumerator() | Sort-Object -Property Name | ForEach-Object { 95 | $dependencies[$_.Name] = $_.Value 96 | } 97 | foreach ($module in $dependencies.GetEnumerator()) { 98 | Write-Host -Object "[$group] -- Checking $($module.Name)"; 99 | $installParams = @{} 100 | $installParams += $module.Value; 101 | $installParams.MinimumVersion = $installParams.version; 102 | $installParams.Remove('version'); 103 | $available = @(Find-Module -Repository $Repository -Name $module.Name @installParams -ErrorAction Ignore); 104 | foreach ($found in $available) { 105 | if (([Version]$found.Version) -gt ([Version]$module.Value.version)) { 106 | Write-Host -Object "[$group] -- Newer version found $($found.Version)"; 107 | $dependencies[$module.Name].version = $found.Version; 108 | $Null = Add-Content -Path $changeNotes -Value "Bump $($module.Name) to v$($found.Version)."; 109 | } 110 | else { 111 | Write-Host -Object "[$group] -- Already up to date."; 112 | } 113 | } 114 | } 115 | return $dependencies; 116 | } 117 | } 118 | 119 | function InstallVersion { 120 | [CmdletBinding()] 121 | [OutputType([void])] 122 | param ( 123 | [Parameter(Mandatory = $True)] 124 | [PSObject]$InputObject, 125 | 126 | [Parameter(Mandatory = $True)] 127 | [String]$Repository, 128 | 129 | [Parameter(Mandatory = $False)] 130 | [Switch]$Dev 131 | ) 132 | begin { 133 | $group = 'Dependencies'; 134 | if ($Dev) { 135 | $group = 'DevDependencies'; 136 | } 137 | } 138 | process { 139 | foreach ($module in $InputObject.PSObject.Properties.GetEnumerator()) { 140 | Write-Host -Object "[$group] -- Installing $($module.Name) v$($module.Value.version)"; 141 | $installParams = @{ RequiredVersion = $module.Value.version }; 142 | if ($Null -eq (Get-InstalledModule -Name $module.Name @installParams -ErrorAction Ignore)) { 143 | Install-Module -Name $module.Name @installParams -Force -Repository $Repository; 144 | } 145 | } 146 | } 147 | } 148 | 149 | Export-ModuleMember -Function @( 150 | 'Update-Dependencies' 151 | 'Install-Dependencies' 152 | ) 153 | -------------------------------------------------------------------------------- /tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.Pod.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # 5 | # Unit tests for Kubernetes pod rules 6 | # 7 | 8 | [CmdletBinding()] 9 | param () 10 | 11 | BeforeAll { 12 | # Setup error handling 13 | $ErrorActionPreference = 'Stop'; 14 | Set-StrictMode -Version latest; 15 | 16 | if ($Env:SYSTEM_DEBUG -eq 'true') { 17 | $VerbosePreference = 'Continue'; 18 | } 19 | 20 | # Setup tests paths 21 | $rootPath = $PWD; 22 | Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 23 | $here = (Resolve-Path $PSScriptRoot).Path; 24 | } 25 | 26 | Describe 'Kubernetes.Pod' { 27 | BeforeAll { 28 | $testParams = @{ 29 | Module = 'PSRule.Rules.Kubernetes' 30 | Option = Join-Path -Path $here -ChildPath ps-rule.yaml 31 | InputPath = Join-Path -Path $here -ChildPath Resources.Pod.yaml 32 | } 33 | 34 | $result = Invoke-PSRule @testParams -WarningAction Ignore; 35 | } 36 | 37 | Context 'Security' { 38 | It 'Kubernetes.Pod.PrivilegeEscalation' { 39 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.Pod.PrivilegeEscalation' }; 40 | 41 | # Fail 42 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 43 | $ruleResult | Should -Not -BeNullOrEmpty; 44 | $ruleResult.Length | Should -Be 1; 45 | $ruleResult.TargetName | Should -Be 'deployment/deployment-B'; 46 | 47 | # Pass 48 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 49 | $ruleResult | Should -Not -BeNullOrEmpty; 50 | $ruleResult.Length | Should -Be 1; 51 | $ruleResult.TargetName | Should -Be 'deployment/deployment-A'; 52 | } 53 | 54 | It 'Kubernetes.Pod.Latest' { 55 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.Pod.Latest' }; 56 | 57 | # Fail 58 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 59 | $ruleResult | Should -Not -BeNullOrEmpty; 60 | $ruleResult.Length | Should -Be 1; 61 | $ruleResult.TargetName | Should -Be 'deployment/deployment-B'; 62 | 63 | # Pass 64 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 65 | $ruleResult | Should -Not -BeNullOrEmpty; 66 | $ruleResult.Length | Should -Be 1; 67 | $ruleResult.TargetName | Should -Be 'deployment/deployment-A'; 68 | } 69 | 70 | It 'Kubernetes.Pod.Secrets' { 71 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.Pod.Secrets' }; 72 | 73 | # Fail 74 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 75 | $ruleResult | Should -Not -BeNullOrEmpty; 76 | $ruleResult.Length | Should -Be 1; 77 | $ruleResult.TargetName | Should -Be 'deployment/deployment-B'; 78 | 79 | # Pass 80 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 81 | $ruleResult | Should -Not -BeNullOrEmpty; 82 | $ruleResult.Length | Should -Be 1; 83 | $ruleResult.TargetName | Should -Be 'deployment/deployment-A'; 84 | } 85 | } 86 | 87 | Context 'Resource management' { 88 | It 'Kubernetes.Pod.Resources' { 89 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.Pod.Resources' }; 90 | 91 | # Fail 92 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 93 | $ruleResult | Should -Not -BeNullOrEmpty; 94 | $ruleResult.Length | Should -Be 1; 95 | $ruleResult.TargetName | Should -Be 'deployment/deployment-B'; 96 | 97 | # Pass 98 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 99 | $ruleResult | Should -Not -BeNullOrEmpty; 100 | $ruleResult.Length | Should -Be 1; 101 | $ruleResult.TargetName | Should -Be 'deployment/deployment-A'; 102 | } 103 | } 104 | 105 | Context 'Reliability' { 106 | It 'Kubernetes.Pod.Health' { 107 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.Pod.Health' }; 108 | 109 | # Fail 110 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 111 | $ruleResult | Should -Not -BeNullOrEmpty; 112 | $ruleResult.Length | Should -Be 1; 113 | $ruleResult.TargetName | Should -Be 'deployment/deployment-B'; 114 | 115 | # Pass 116 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 117 | $ruleResult | Should -Not -BeNullOrEmpty; 118 | $ruleResult.Length | Should -Be 1; 119 | $ruleResult.TargetName | Should -Be 'deployment/deployment-A'; 120 | } 121 | 122 | It 'Kubernetes.Pod.Replicas' { 123 | $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.Pod.Replicas' }; 124 | 125 | # Fail 126 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); 127 | $ruleResult | Should -Not -BeNullOrEmpty; 128 | $ruleResult.Length | Should -Be 1; 129 | $ruleResult.TargetName | Should -Be 'deployment/deployment-B'; 130 | 131 | # Pass 132 | $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); 133 | $ruleResult | Should -Not -BeNullOrEmpty; 134 | $ruleResult.Length | Should -Be 1; 135 | $ruleResult.TargetName | Should -Be 'deployment/deployment-A'; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSRule for Kubernetes 2 | 3 | A suite of rules to validate Kubernetes resources using PSRule. 4 | 5 | ![ci-badge] 6 | 7 | ## Disclaimer 8 | 9 | This project is to be considered a **proof-of-concept** and **not a supported product**. 10 | 11 | For issues with rules and documentation please check our GitHub [issues](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues) page. 12 | If you do not see your problem captured, please file a new issue and follow the provided template. 13 | 14 | If you have any problems with the [PSRule][project] engine, please check the project GitHub [issues](https://github.com/Microsoft/PSRule/issues) page instead. 15 | 16 | ## Getting the modules 17 | 18 | This project requires the `PSRule` PowerShell module. 19 | You can download and install these modules from the PowerShell Gallery. 20 | 21 | Module | Description | Downloads / instructions 22 | ------ | ----------- | ------------------------ 23 | PSRule.Rules.Kubernetes | Validate Kubernetes resources | [latest][module] / [instructions][install] 24 | 25 | ## Getting started 26 | 27 | PSRule for Kubernetes provides two methods for analyzing Kubernetes resources: 28 | 29 | - _Pre-flight_ - Before resources are deployed from a YAML manifest file. 30 | - _In-flight_ - After resources are deployed to a Kubernetes cluster. 31 | 32 | ### Offline with a manifest 33 | 34 | Kubernetes resources can be validated within a YAML manifest file. 35 | To validate Kubernetes resources use the `Invoke-PSRule` cmdlet. 36 | PSRule natively supports reading objects from YAML files using the `-InputPath` parameter. 37 | The `-InputPath` parameter can be abbreviated to `-f`. 38 | 39 | For example: 40 | 41 | ```powershell 42 | Invoke-PSRule -f service.yaml -Module PSRule.Rules.Kubernetes; 43 | ``` 44 | 45 | The input path can be also be a URL to a YAML file. For example: 46 | 47 | ```powershell 48 | $sourceUrl = 'https://raw.githubusercontent.com/Azure-Samples/azure-voting-app-redis/master/azure-vote-all-in-one-redis.yaml'; 49 | Invoke-PSRule -f $sourceUrl -Module PSRule.Rules.Kubernetes; 50 | ``` 51 | 52 | The output of this example is: 53 | 54 | ```text 55 | TargetName: azure-vote-back 56 | 57 | RuleName Outcome Recommendation 58 | -------- ------- -------------- 59 | Kubernetes.API.Removal Fail Consider updating resource deployments to use newer API endpoints prior… 60 | Kubernetes.Metadata Fail Consider applying recommended labels defined by Kubernetes.… 61 | Kubernetes.Pod.PrivilegeEscalation Fail Containers should deny privilege escalation. 62 | Kubernetes.Pod.Latest Fail Deployments or pods should identify a specific tag to use for container… 63 | Kubernetes.Pod.Resources Fail Resource requirements are set for each container. 64 | Kubernetes.Pod.Secrets Pass Use Kubernetes secrets to store information such as passwords or connec… 65 | Kubernetes.Pod.Health Fail Containers should use liveness and readiness probes. 66 | Kubernetes.Pod.Replicas Fail Consider increasing replicas to two or more to provide high availabilit… 67 | Kubernetes.Metadata Fail Consider applying recommended labels defined by Kubernetes.… 68 | 69 | TargetName: azure-vote-front 70 | 71 | RuleName Outcome Recommendation 72 | -------- ------- -------------- 73 | Kubernetes.API.Removal Fail Consider updating resource deployments to use newer API endpoints prior… 74 | Kubernetes.Metadata Fail Consider applying recommended labels defined by Kubernetes.… 75 | Kubernetes.Pod.PrivilegeEscalation Fail Containers should deny privilege escalation. 76 | Kubernetes.Pod.Latest Pass Deployments or pods should identify a specific tag to use for container… 77 | Kubernetes.Pod.Resources Fail Resource requirements are set for each container. 78 | Kubernetes.Pod.Secrets Pass Use Kubernetes secrets to store information such as passwords or connec… 79 | Kubernetes.Pod.Health Fail Containers should use liveness and readiness probes. 80 | Kubernetes.Pod.Replicas Fail Consider increasing replicas to two or more to provide high availabilit… 81 | Kubernetes.Metadata Fail Consider applying recommended labels defined by Kubernetes.… 82 | ``` 83 | 84 | ### Online with kubectl 85 | 86 | Kubernetes resources can be validated directly from a cluster using the output from `kubectl`. 87 | To validate resources using `kubectl`, return the output as YAML with the `-o yaml` parameter. 88 | 89 | For example: 90 | 91 | ```powershell 92 | kubectl get services -o yaml | Out-String | Invoke-PSRule -Format Yaml -ObjectPath items -Module PSRule.Rules.Kubernetes; 93 | ``` 94 | 95 | In the example above: 96 | 97 | - `Out-String` - is used to concatenate the output into a single string object. 98 | - `-Format Yaml` - indicates that the input is YAML. 99 | - `-ObjectPath items` - indicates that the input nests objects to evaluate under the `items` property. 100 | 101 | ### Using baselines 102 | 103 | PSRule for Kubernetes comes with the following baselines: 104 | 105 | - `Kubernetes` - Includes common Kubernetes rules. This is the default. 106 | - `AKS` - Includes all the rules from `Kubernetes` plus additional Azure Kubernetes Service (AKS) specific rules. 107 | 108 | To use the `AKS` baseline instead of the default use `Invoke-PSRule -Baseline AKS`. 109 | 110 | For example: 111 | 112 | ```powershell 113 | Invoke-PSRule -f $sourceUrl -Module 'PSRule.Rules.Kubernetes' -Baseline AKS; 114 | ``` 115 | 116 | If `-Baseline AKS` is not specified, the default baseline `Kubernetes` will be used. 117 | 118 | ### Additional options 119 | 120 | To filter results to only failed rules, use `Invoke-PSRule -Outcome Fail`. 121 | Passed, failed and error results are shown by default. 122 | 123 | For example: 124 | 125 | ```powershell 126 | # Only show failed results 127 | Invoke-PSRule -f $sourceUrl -Module 'PSRule.Rules.Kubernetes' -Outcome Fail; 128 | ``` 129 | 130 | A summary of results can be displayed by using `Invoke-PSRule -As Summary`. 131 | 132 | For example: 133 | 134 | ```powershell 135 | # Display as summary results 136 | Invoke-PSRule -f $sourceUrl -Module 'PSRule.Rules.Kubernetes' -As Summary; 137 | ``` 138 | 139 | The output of this example is: 140 | 141 | ```text 142 | RuleName Pass Fail Outcome 143 | -------- ---- ---- ------- 144 | Kubernetes.API.Removal 0 2 Fail 145 | Kubernetes.Metadata 0 4 Fail 146 | Kubernetes.Pod.PrivilegeEscalation 0 2 Fail 147 | Kubernetes.Pod.Latest 1 1 Fail 148 | Kubernetes.Pod.Resources 0 2 Fail 149 | Kubernetes.Pod.Secrets 2 0 Pass 150 | Kubernetes.Pod.Health 0 2 Fail 151 | Kubernetes.Pod.Replicas 0 2 Fail 152 | ``` 153 | 154 | ## Rule reference 155 | 156 | For a list of rules included in the `PSRule.Rules.Kubernetes` module see: 157 | 158 | - [Module rule reference](docs/rules/en/module.md) 159 | 160 | ## Changes and versioning 161 | 162 | Modules in this repository will use the [semantic versioning](http://semver.org/) model to declare breaking changes from v1.0.0. 163 | Prior to v1.0.0, breaking changes may be introduced in minor (0.x.0) version increments. 164 | For a list of module changes please see the [change log](CHANGELOG.md). 165 | 166 | > Pre-release module versions are created on major commits and can be installed from the PowerShell Gallery. 167 | > Pre-release versions should be considered experimental. 168 | > Modules and change log details for pre-releases will be removed as standard releases are made available. 169 | 170 | ## Contributing 171 | 172 | This project welcomes contributions and suggestions. 173 | If you are ready to contribute, please visit the [contribution guide](CONTRIBUTING.md). 174 | 175 | ## Code of Conduct 176 | 177 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 178 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 179 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 180 | 181 | ## Maintainers 182 | 183 | - [Bernie White](https://github.com/BernieWhite) 184 | - [Sam Bell](https://github.com/ms-sambell) 185 | 186 | ## License 187 | 188 | This project is [licensed under the MIT License](LICENSE). 189 | 190 | [install]: docs/scenarios/install-instructions.md 191 | [ci-badge]: https://dev.azure.com/bewhite/PSRule.Rules.Kubernetes/_apis/build/status/PSRule.Rules.Kubernetes-CI?branchName=main 192 | [module]: https://www.powershellgallery.com/packages/PSRule.Rules.Kubernetes 193 | [project]: https://github.com/Microsoft/PSRule 194 | -------------------------------------------------------------------------------- /pipeline.build.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | [CmdletBinding()] 5 | param ( 6 | [Parameter(Mandatory = $False)] 7 | [String]$Build = '0.0.1', 8 | 9 | [Parameter(Mandatory = $False)] 10 | [String]$Configuration = 'Debug', 11 | 12 | [Parameter(Mandatory = $False)] 13 | [String]$ApiKey, 14 | 15 | [Parameter(Mandatory = $False)] 16 | [Switch]$CodeCoverage = $False, 17 | 18 | [Parameter(Mandatory = $False)] 19 | [String]$ArtifactPath = (Join-Path -Path $PWD -ChildPath out/modules), 20 | 21 | [Parameter(Mandatory = $False)] 22 | [String]$AssertStyle = 'AzurePipelines' 23 | ) 24 | 25 | Write-Host -Object "[Pipeline] -- PowerShell: v$($PSVersionTable.PSVersion.ToString())" -ForegroundColor Green; 26 | Write-Host -Object "[Pipeline] -- PWD: $PWD" -ForegroundColor Green; 27 | Write-Host -Object "[Pipeline] -- ArtifactPath: $ArtifactPath" -ForegroundColor Green; 28 | Write-Host -Object "[Pipeline] -- BuildNumber: $($Env:BUILD_BUILDNUMBER)" -ForegroundColor Green; 29 | Write-Host -Object "[Pipeline] -- SourceBranch: $($Env:BUILD_SOURCEBRANCH)" -ForegroundColor Green; 30 | Write-Host -Object "[Pipeline] -- SourceBranchName: $($Env:BUILD_SOURCEBRANCHNAME)" -ForegroundColor Green; 31 | Write-Host -Object "[Pipeline] -- Culture: $((Get-Culture).Name), $((Get-Culture).Parent)" -ForegroundColor Green; 32 | 33 | if ($Env:SYSTEM_DEBUG -eq 'true') { 34 | $VerbosePreference = 'Continue'; 35 | } 36 | 37 | if ($Env:BUILD_SOURCEBRANCH -like '*/tags/*' -and $Env:BUILD_SOURCEBRANCHNAME -like 'v0.*') { 38 | $Build = $Env:BUILD_SOURCEBRANCHNAME.Substring(1); 39 | } 40 | 41 | $version = $Build; 42 | $versionSuffix = [String]::Empty; 43 | 44 | if ($version -like '*-*') { 45 | [String[]]$versionParts = $version.Split('-', [System.StringSplitOptions]::RemoveEmptyEntries); 46 | $version = $versionParts[0]; 47 | 48 | if ($versionParts.Length -eq 2) { 49 | $versionSuffix = $versionParts[1]; 50 | } 51 | } 52 | 53 | Write-Host -Object "[Pipeline] -- Using version: $version" -ForegroundColor Green; 54 | Write-Host -Object "[Pipeline] -- Using versionSuffix: $versionSuffix" -ForegroundColor Green; 55 | 56 | if ($Env:COVERAGE -eq 'true') { 57 | $CodeCoverage = $True; 58 | } 59 | 60 | function CopyModuleFiles { 61 | param ( 62 | [Parameter(Mandatory = $True)] 63 | [String]$Path, 64 | 65 | [Parameter(Mandatory = $True)] 66 | [String]$DestinationPath 67 | ) 68 | 69 | process { 70 | $sourcePath = Resolve-Path -Path $Path; 71 | 72 | Get-ChildItem -Path $sourcePath -Recurse -File -Include *.ps1,*.yaml,*.psm1,*.psd1,*.ps1xml | Where-Object -FilterScript { 73 | ($_.FullName -notmatch '(\.(cs|csproj)|(\\|\/)(obj|bin))') 74 | } | ForEach-Object -Process { 75 | $filePath = $_.FullName.Replace($sourcePath, $destinationPath); 76 | 77 | $parentPath = Split-Path -Path $filePath -Parent; 78 | 79 | if (!(Test-Path -Path $parentPath)) { 80 | $Null = New-Item -Path $parentPath -ItemType Directory -Force; 81 | } 82 | 83 | Copy-Item -Path $_.FullName -Destination $filePath -Force; 84 | }; 85 | } 86 | } 87 | 88 | task VersionModule ModuleDependencies, { 89 | $modulePath = Join-Path -Path $ArtifactPath -ChildPath PSRule.Rules.Kubernetes; 90 | $manifestPath = Join-Path -Path $modulePath -ChildPath PSRule.Rules.Kubernetes.psd1; 91 | Write-Verbose -Message "[VersionModule] -- Checking module path: $modulePath"; 92 | 93 | if (![String]::IsNullOrEmpty($Build)) { 94 | # Update module version 95 | if (![String]::IsNullOrEmpty($version)) { 96 | Write-Verbose -Message "[VersionModule] -- Updating module manifest ModuleVersion"; 97 | Update-ModuleManifest -Path $manifestPath -ModuleVersion $version; 98 | } 99 | 100 | # Update pre-release version 101 | if (![String]::IsNullOrEmpty($versionSuffix)) { 102 | Write-Verbose -Message "[VersionModule] -- Updating module manifest Prerelease"; 103 | Update-ModuleManifest -Path $manifestPath -Prerelease $versionSuffix; 104 | } 105 | } 106 | 107 | $dependencies = Get-Content -Path $PWD/modules.json -Raw | ConvertFrom-Json; 108 | $manifest = Test-ModuleManifest -Path $manifestPath; 109 | $requiredModules = $manifest.RequiredModules | ForEach-Object -Process { 110 | if ($_.Name -eq 'PSRule' -and $Configuration -eq 'Release') { 111 | @{ ModuleName = 'PSRule'; ModuleVersion = $dependencies.dependencies.PSRule.version } 112 | } 113 | else { 114 | @{ ModuleName = $_.Name; ModuleVersion = $_.Version } 115 | } 116 | }; 117 | Update-ModuleManifest -Path $manifestPath -RequiredModules $requiredModules; 118 | } 119 | 120 | # Synopsis: Publish to PowerShell Gallery 121 | task ReleaseModule VersionModule, { 122 | $modulePath = (Join-Path -Path $ArtifactPath -ChildPath PSRule.Rules.Kubernetes); 123 | Write-Verbose -Message "[ReleaseModule] -- Checking module path: $modulePath"; 124 | 125 | if (!(Test-Path -Path $modulePath)) { 126 | Write-Error -Message "[ReleaseModule] -- Module path does not exist"; 127 | } 128 | elseif (![String]::IsNullOrEmpty($ApiKey)) { 129 | Publish-Module -Path $modulePath -NuGetApiKey $ApiKey; 130 | } 131 | } 132 | 133 | # Synopsis: Install NuGet provider 134 | task NuGet { 135 | if ($Null -eq (Get-PackageProvider -Name NuGet -ErrorAction Ignore)) { 136 | Install-PackageProvider -Name NuGet -Force -Scope CurrentUser; 137 | } 138 | } 139 | 140 | # Synopsis: Install module dependencies 141 | task ModuleDependencies Dependencies, { 142 | } 143 | 144 | task Dependencies NuGet, { 145 | Import-Module $PWD/scripts/dependencies.psm1; 146 | Install-Dependencies -Path $PWD/modules.json -Dev; 147 | } 148 | 149 | task CopyModule { 150 | CopyModuleFiles -Path src/PSRule.Rules.Kubernetes -DestinationPath out/modules/PSRule.Rules.Kubernetes; 151 | } 152 | 153 | # Synopsis: Build modules only 154 | task BuildModule CopyModule 155 | 156 | task TestModule ModuleDependencies, { 157 | # Run Pester tests 158 | $pesterOptions = @{ 159 | Run = @{ 160 | Path = (Join-Path -Path $PWD -ChildPath tests/PSRule.Rules.Kubernetes.Tests); 161 | PassThru = $True; 162 | }; 163 | TestResult = @{ 164 | Enabled = $True; 165 | OutputFormat = 'NUnitXml'; 166 | OutputPath = 'reports/pester-unit.xml'; 167 | }; 168 | }; 169 | 170 | if ($CodeCoverage) { 171 | $codeCoverageOptions = @{ 172 | Enabled = $True; 173 | OutputPath = (Join-Path -Path $PWD -ChildPath 'reports/pester-coverage.xml'); 174 | Path = (Join-Path -Path $PWD -ChildPath 'out/modules/**/*.psm1'); 175 | }; 176 | 177 | $pesterOptions.Add('CodeCoverage', $codeCoverageOptions); 178 | } 179 | 180 | if (!(Test-Path -Path reports)) { 181 | $Null = New-Item -Path reports -ItemType Directory -Force; 182 | } 183 | 184 | if ($Null -ne $TestGroup) { 185 | $pesterOptions.Add('Filter', @{ Tag = $TestGroup }); 186 | } 187 | 188 | # https://pester.dev/docs/commands/New-PesterConfiguration 189 | $pesterConfiguration = New-PesterConfiguration -Hashtable $pesterOptions; 190 | 191 | $results = Invoke-Pester -Configuration $pesterConfiguration; 192 | 193 | # Throw an error if pester tests failed 194 | if ($Null -eq $results) { 195 | throw 'Failed to get Pester test results.'; 196 | } 197 | elseif ($results.FailedCount -gt 0) { 198 | throw "$($results.FailedCount) tests failed."; 199 | } 200 | } 201 | 202 | # Synopsis: Run validation 203 | task Rules Dependencies, { 204 | $assertParams = @{ 205 | Path = './.ps-rule/' 206 | Style = $AssertStyle 207 | OutputFormat = 'NUnit3' 208 | ErrorAction = 'Stop' 209 | As = 'Summary' 210 | } 211 | Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 212 | Assert-PSRule @assertParams -InputPath $PWD -Module PSRule.Rules.MSFT.OSS -Format File -OutputPath reports/ps-rule-file.xml; 213 | 214 | $rules = Get-PSRule -Module PSRule.Rules.Kubernetes; 215 | $rules | Assert-PSRule @assertParams -OutputPath reports/ps-rule-file2.xml; 216 | } 217 | 218 | # Synopsis: Run script analyzer 219 | task Analyze Build, ModuleDependencies, { 220 | Invoke-ScriptAnalyzer -Path out/modules/PSRule.Rules.Kubernetes; 221 | } 222 | 223 | # Synopsis: Build table of content for rules 224 | task BuildRuleDocs Build, ModuleDependencies, { 225 | Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 226 | $Null = Invoke-PSDocument -Name module -OutputPath .\docs\rules\en\ -Path .\RuleToc.Doc.ps1; 227 | } 228 | 229 | # Synopsis: Build help 230 | task BuildHelp BuildModule, ModuleDependencies, { 231 | if (!(Test-Path out/modules/PSRule.Rules.Kubernetes/en/)) { 232 | $Null = New-Item -Path out/modules/PSRule.Rules.Kubernetes/en/ -ItemType Directory -Force; 233 | } 234 | 235 | # Copy generated help into module out path 236 | $Null = Copy-Item -Path docs/rules/en/*.md -Destination out/modules/PSRule.Rules.Kubernetes/en/; 237 | } 238 | 239 | task ScaffoldHelp Build, BuildRuleDocs, { 240 | # Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; 241 | # Update-MarkdownHelp -Path '.\docs\commands\PSRule.Rules.Kubernetes\en'; 242 | } 243 | 244 | # Synopsis: Remove temp files. 245 | task Clean { 246 | Remove-Item -Path out,reports -Recurse -Force -ErrorAction SilentlyContinue; 247 | } 248 | 249 | task Build Clean, BuildModule, VersionModule, BuildHelp 250 | 251 | task Test Build, Rules, TestModule 252 | 253 | task Release ReleaseModule 254 | 255 | task . Build, Rules 256 | --------------------------------------------------------------------------------