diff --git a/CHANGELOG.md b/CHANGELOG.md index 23d57c8..73312a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,13 @@ ## Unreleased +- Added new rules for API deprecation removals: + - Planned Kubernetes v1.17.0 deprecation removals. [#38](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/38) + - Planned Kubernetes v1.20.0 deprecation removals. [#39](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/39) - **Breaking change**: Use qualified target names. [#36](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/36) - If using suppression, update suppressed target name with qualified name. +- **Breaking change**: Renamed `Kubernetes.API.Removal` to handle future API deprecations. [#40](https://github.com/Microsoft/PSRule.Rules.Kubernetes/issues/40) + - The rule `Kubernetes.API.Removal` is now `Kubernetes.API.v1.16`. ## v0.1.0 diff --git a/docs/rules/en/Kubernetes.API.Removal.md b/docs/rules/en/Kubernetes.API.Removal.md deleted file mode 100644 index 8c3116c..0000000 --- a/docs/rules/en/Kubernetes.API.Removal.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -severity: Important -category: API -online version: https://github.com/Microsoft/PSRule.Rules.Kubernetes/blob/master/docs/rules/en/Kubernetes.API.Removal.md ---- - -# Use supported APIs - -## SYNOPSIS - -Avoid using legacy API endpoints. - -## DESCRIPTION - -In Kubernetes v1.16.0 a number of previously deprecated API endpoints have been removed. -These removed endpoints will no longer work for new deployments after upgrading to Kubernetes v1.16.0 or greater. - -To prevent deployment issues use the newer API endpoints for these resources. - -- NetworkPolicy should use `networking.k8s.io/v1`. -- PodSecurityPolicy should use `policy/v1beta1`. -- DaemonSet, Deployment, StatefulSet, and ReplicaSet should use `apps/v1`. - -## RECOMMENDATION - -Consider updating resource deployments to use newer API endpoints prior to upgrading to Kubernetes >= v1.16.0. - -## LINKS - -- [Kubernetes v1.15.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.15.md#deprecations-and-removals) diff --git a/docs/rules/en/Kubernetes.API.v1.16.md b/docs/rules/en/Kubernetes.API.v1.16.md new file mode 100644 index 0000000..c05dc73 --- /dev/null +++ b/docs/rules/en/Kubernetes.API.v1.16.md @@ -0,0 +1,31 @@ +--- +severity: Important +category: API +online version: https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/master/docs/rules/en/Kubernetes.API.v1.16.md +--- + +# Use APIs supported in v1.16 + +## SYNOPSIS + +Avoid using legacy API endpoints not served by Kubernetes v1.16. + +## DESCRIPTION + +In Kubernetes v1.16.0 a number of previously deprecated API endpoints are no longer served. +These endpoints will no longer work for new deployments after upgrading to Kubernetes v1.16.0 or greater. + +To prevent deployment issues use the newer API endpoints for these resources. + +- NetworkPolicy should use `networking.k8s.io/v1`. +- PodSecurityPolicy should use `policy/v1beta1`. +- DaemonSet, Deployment, StatefulSet and ReplicaSet should use `apps/v1`. + +## RECOMMENDATION + +Consider updating resource deployments to use newer API endpoints prior to upgrading to Kubernetes >= v1.16.0. + +## LINKS + +- [Kubernetes v1.15.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.15.md#deprecations-and-removals) +- [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) diff --git a/docs/rules/en/Kubernetes.API.v1.17.md b/docs/rules/en/Kubernetes.API.v1.17.md new file mode 100644 index 0000000..0f238ce --- /dev/null +++ b/docs/rules/en/Kubernetes.API.v1.17.md @@ -0,0 +1,29 @@ +--- +severity: Important +category: API +online version: https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/master/docs/rules/en/Kubernetes.API.v1.17.md +--- + +# Use APIs supported in v1.17 + +## SYNOPSIS + +Avoid using legacy API endpoints not served by Kubernetes v1.17. + +## DESCRIPTION + +In Kubernetes v1.17.0 previously deprecated API endpoints are no longer served. +These endpoints will no longer work for new deployments after upgrading to Kubernetes v1.17.0 or greater. + +To prevent deployment issues use the newer API endpoints for these resources. + +- PriorityClass should use `scheduling.k8s.io/v1`. + +## RECOMMENDATION + +Consider updating resource deployments to use newer API endpoints prior to upgrading to Kubernetes >= v1.17.0. + +## LINKS + +- [Kubernetes v1.16.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.16.md#deprecations-and-removals) +- [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) diff --git a/docs/rules/en/Kubernetes.API.v1.20.md b/docs/rules/en/Kubernetes.API.v1.20.md new file mode 100644 index 0000000..47629f4 --- /dev/null +++ b/docs/rules/en/Kubernetes.API.v1.20.md @@ -0,0 +1,31 @@ +--- +severity: Important +category: API +online version: https://github.com/microsoft/PSRule.Rules.Kubernetes/blob/master/docs/rules/en/Kubernetes.API.v1.20.md +--- + +# Use APIs supported in v1.20 + +## SYNOPSIS + +Avoid using legacy API endpoints not served by Kubernetes v1.20. + +## DESCRIPTION + +In Kubernetes v1.20.0 a number of previously deprecated API endpoints are planned to be no longer served. +These endpoints will no longer work for new deployments after upgrading to Kubernetes v1.20.0 or greater. + +To prevent deployment issues use the newer API endpoints for these resources. + +- Ingress should use `networking.k8s.io/v1beta1`. +- Role, RoleBinding, ClusterRoleBinding and ClusterRole should use `rbac.authorization.k8s.io/v1`. + +## RECOMMENDATION + +Consider updating resource deployments to use newer API endpoints prior to upgrading to Kubernetes >= v1.20.0. + +## LINKS + +- [Kubernetes v1.15.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.15.md#deprecations-and-removals) +- [Kubernetes v1.17.0 deprecations and removals](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.17.md#deprecations-and-removals) +- [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) diff --git a/src/PSRule.Rules.Kubernetes/rules/Kubernetes.API.Rule.ps1 b/src/PSRule.Rules.Kubernetes/rules/Kubernetes.API.Rule.ps1 index 174d65d..8bc21cb 100644 --- a/src/PSRule.Rules.Kubernetes/rules/Kubernetes.API.Rule.ps1 +++ b/src/PSRule.Rules.Kubernetes/rules/Kubernetes.API.Rule.ps1 @@ -5,15 +5,50 @@ # Validation rules for Kubernetes resource requirements # -# Synopsis: Avoid using legacy API endpoints -Rule 'Kubernetes.API.Removal' -Type DaemonSet, Deployment, StatefulSet, ReplicaSet, NetworkPolicy, PodSecurityPolicy -Tag @{ group = 'core' } { - if ($PSRule.TargetType -in 'DaemonSet', 'Deployment', 'StatefulSet', 'ReplicaSet') { - $TargetObject.apiVersion -eq 'apps/v1' +# Synopsis: Avoid using legacy API endpoints for v1.16.0 +Rule 'Kubernetes.API.v1.16' -Type DaemonSet, Deployment, StatefulSet, ReplicaSet, NetworkPolicy, PodSecurityPolicy -Tag @{ group = 'core' } { + if ($PSRule.TargetType -eq 'ReplicaSet') { + # Use apps/v1 + $TargetObject.apiVersion -notin 'extensions/v1beta1', 'apps/v1beta1', 'apps/v1beta2' + } + elseif ($PSRule.TargetType -eq 'StatefulSet') { + # Use apps/v1 + $TargetObject.apiVersion -notin 'apps/v1beta1', 'apps/v1beta2' + } + elseif ($PSRule.TargetType -eq 'Deployment') { + # Use apps/v1 + $TargetObject.apiVersion -notin 'extensions/v1beta1', 'apps/v1beta1', 'apps/v1beta2' + } + elseif ($PSRule.TargetType -eq 'DaemonSet') { + # Use apps/v1 + $TargetObject.apiVersion -notin 'extensions/v1beta1', 'apps/v1beta2' } elseif ($PSRule.TargetType -eq 'NetworkPolicy') { - $TargetObject.apiVersion -eq 'networking.k8s.io/v1' + # Use networking.k8s.io/v1 + $TargetObject.apiVersion -notin 'extensions/v1beta1' } elseif ($PSRule.TargetType -eq 'PodSecurityPolicy') { - $TargetObject.apiVersion -eq 'policy/v1beta1' + # Use policy/v1beta1 + $TargetObject.apiVersion -notin 'extensions/v1beta1' + } +} + +# Synopsis: Avoid using legacy API endpoints for v1.17.0 +Rule 'Kubernetes.API.v1.17' -Type PriorityClass -Tag @{ group = 'core' } { + if ($PSRule.TargetType -eq 'PriorityClass') { + # Use scheduling.k8s.io/v1 + $TargetObject.apiVersion -notin 'scheduling.k8s.io/v1beta1', 'scheduling.k8s.io/v1alpha1' + } +} + +# Synopsis: Avoid using legacy API endpoints for v1.20.0 +Rule 'Kubernetes.API.v1.20' -Type Ingress, Role, RoleBinding, ClusterRoleBinding, ClusterRole -Tag @{ group = 'core' } { + if ($PSRule.TargetType -eq 'Ingress') { + # Use networking.k8s.io/v1beta1 + $TargetObject.apiVersion -notin 'extensions/v1beta1' + } + elseif ($PSRule.TargetType -in 'Role', 'RoleBinding', 'ClusterRoleBinding', 'ClusterRole') { + # Use rbac.authorization.k8s.io/v1 + $TargetObject.apiVersion -notin 'rbac.authorization.k8s.io/v1alpha1', 'rbac.authorization.k8s.io/v1beta1' } } diff --git a/tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.API.Tests.ps1 b/tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.API.Tests.ps1 index 82133be..9665a59 100644 --- a/tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.API.Tests.ps1 +++ b/tests/PSRule.Rules.Kubernetes.Tests/Kubernetes.API.Tests.ps1 @@ -21,30 +21,62 @@ $rootPath = $PWD; Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Kubernetes) -Force; $here = (Resolve-Path $PSScriptRoot).Path; -Describe 'Kubernetes.API.Removal' { +Describe 'Kubernetes.API' { $testParams = @{ Module = 'PSRule.Rules.Kubernetes' Option = Join-Path -Path $here -ChildPath ps-rule.yaml - InputPath = Join-Path -Path $here -ChildPath Resources.Pod.yaml + InputPath = Join-Path -Path $here -ChildPath Resources.API.yaml } $result = Invoke-PSRule @testParams -WarningAction Ignore; Context 'API' { - It 'Kubernetes.API.Removal' { - $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.API.Removal' }; + It 'Kubernetes.API.v1.16' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.API.v1.16' }; # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; $ruleResult.Length | Should -Be 1; - $ruleResult.TargetName | Should -Be 'deployment/deployment-B'; + $ruleResult.TargetName | Should -Be 'Deployment/deployment-B'; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; $ruleResult.Length | Should -Be 1; - $ruleResult.TargetName | Should -Be 'deployment/deployment-A'; + $ruleResult.TargetName | Should -Be 'Deployment/deployment-A'; + } + + It 'Kubernetes.API.v1.17' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.API.v1.17' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'PriorityClass/priority-B'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'PriorityClass/priority-A'; + } + + It 'Kubernetes.API.v1.20' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Kubernetes.API.v1.20' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 5; + $ruleResult.TargetName | Should -BeIn 'Ingress/ingress-A', 'ClusterRole/clusterRole-B', 'Role/role-B', 'ClusterRoleBinding/clusterRoleBinding-B', 'RoleBinding/roleBinding-B'; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult | Should -Not -BeNullOrEmpty; + $ruleResult.Length | Should -Be 5; + $ruleResult.TargetName | Should -BeIn 'Ingress/ingress-B', 'ClusterRole/clusterRole-A', 'Role/role-A', 'ClusterRoleBinding/clusterRoleBinding-A', 'RoleBinding/roleBinding-A'; } } } diff --git a/tests/PSRule.Rules.Kubernetes.Tests/Resources.API.yaml b/tests/PSRule.Rules.Kubernetes.Tests/Resources.API.yaml new file mode 100644 index 0000000..640b675 --- /dev/null +++ b/tests/PSRule.Rules.Kubernetes.Tests/Resources.API.yaml @@ -0,0 +1,172 @@ +# +# Kubernetes Pod resources for unit tests +# + +--- +# An example deployment that should pass all rules. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-A +spec: + replicas: 2 + selector: + matchLabels: + app: app-A + template: + metadata: + labels: + app: app-A + spec: + containers: + - name: app-a + image: app-a-image:v1 + securityContext: + allowPrivilegeEscalation: false + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + livenessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 3 + periodSeconds: 3 + readinessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 3 + periodSeconds: 3 + ports: + - containerPort: 80 + +--- +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: deployment-B +spec: + replicas: 1 + selector: + matchLabels: + app: app-B + template: + metadata: + labels: + app: app-B + spec: + containers: + - name: app-b + image: app-b-image + env: + - name: insecure-password + value: Pass123 + +--- +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: priority-A +value: 1000000 +globalDefault: false +description: "This is a high priority class." + +--- +apiVersion: scheduling.k8s.io/v1beta1 +kind: PriorityClass +metadata: + name: priority-B +value: 100 +globalDefault: false +description: "This is a low priority class." + +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: ingress-A +spec: {} + +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: ingress-B +spec: {} + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: clusterRole-A +rules: [] + +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: role-A +rules: [] + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: roleBinding-A +roleRef: + kind: Role + name: role-A + apiGroup: rbac.authorization.k8s.io +subjects: [] + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: clusterRoleBinding-A +roleRef: + kind: ClusterRole + name: clusterRole-A + apiGroup: rbac.authorization.k8s.io +subjects: [] + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: clusterRole-B +rules: [] + +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1alpha1 +metadata: + name: role-B +rules: [] + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1alpha1 +metadata: + name: roleBinding-B +roleRef: + kind: Role + name: role-B + apiGroup: rbac.authorization.k8s.io +subjects: [] + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: clusterRoleBinding-B +roleRef: + kind: ClusterRole + name: clusterRole-B + apiGroup: rbac.authorization.k8s.io +subjects: []