diff --git a/pkg/cloud/azure/assets/validating-admission-service-annotation-policy-binding.yaml b/pkg/cloud/azure/assets/validating-admission-service-annotation-policy-binding.yaml new file mode 100644 index 000000000..211d04a63 --- /dev/null +++ b/pkg/cloud/azure/assets/validating-admission-service-annotation-policy-binding.yaml @@ -0,0 +1,7 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: azure-load-balancer-tcp-idle-timeout-validation-annotation-binding +spec: + policyName: azure-load-balancer-tcp-idle-timeout-annotation-validation-policy + validationActions: ["Deny"] diff --git a/pkg/cloud/azure/assets/validating-admission-service-annotation-policy.yaml b/pkg/cloud/azure/assets/validating-admission-service-annotation-policy.yaml new file mode 100644 index 000000000..af8b9758c --- /dev/null +++ b/pkg/cloud/azure/assets/validating-admission-service-annotation-policy.yaml @@ -0,0 +1,28 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingAdmissionPolicy +metadata: + name: azure-load-balancer-tcp-idle-timeout-annotation-validation-policy +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["services"] + variables: + - name: hasIdleTimeout + expression: has(object.metadata.annotations) && 'service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout' in object.metadata.annotations + - name: idleTimeoutValue + expression: object.metadata.?annotations['service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout'].orValue("") + - name: hasOldIdleTimeout + expression: oldObject != null && oldObject.metadata.?annotations != null && 'service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout' in oldObject.metadata.annotations + - name: oldIdleTimeoutValue + expression: oldObject.metadata.?annotations['service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout'].orValue("") + - name: valueHasChanged + expression: variables.hasIdleTimeout && (!variables.hasOldIdleTimeout || variables.oldIdleTimeoutValue != variables.idleTimeoutValue) + validations: + - expression: "!variables.hasIdleTimeout || !variables.valueHasChanged || variables.idleTimeoutValue.matches('^[0-9]+$')" + message: "value for annotation 'service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout' must be an integer" + - expression: "!variables.hasIdleTimeout || !variables.valueHasChanged || (int(variables.idleTimeoutValue) >= 4 && int(variables.idleTimeoutValue) <= 100)" + message: "value for annotation 'service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout' must be no less than 4 and no more than 100" + diff --git a/pkg/cloud/azure/azure.go b/pkg/cloud/azure/azure.go index 812432ed2..560e831e4 100644 --- a/pkg/cloud/azure/azure.go +++ b/pkg/cloud/azure/azure.go @@ -36,6 +36,8 @@ var ( {ReferenceObject: &rbacv1.ClusterRoleBinding{}, EmbedFsPath: "assets/azure-cloud-controller-manager-clusterrolebinding.yaml"}, {ReferenceObject: &admissionregistrationv1.ValidatingAdmissionPolicy{}, EmbedFsPath: "assets/validating-admission-policy.yaml"}, {ReferenceObject: &admissionregistrationv1.ValidatingAdmissionPolicyBinding{}, EmbedFsPath: "assets/validating-admission-policy-binding.yaml"}, + {ReferenceObject: &admissionregistrationv1.ValidatingAdmissionPolicyBinding{}, EmbedFsPath: "assets/validating-admission-service-annotation-policy-binding.yaml"}, + {ReferenceObject: &admissionregistrationv1.ValidatingAdmissionPolicy{}, EmbedFsPath: "assets/validating-admission-service-annotation-policy.yaml"}, } ) diff --git a/pkg/cloud/azure/azure_test.go b/pkg/cloud/azure/azure_test.go index 8c1141114..578c41e8d 100644 --- a/pkg/cloud/azure/azure_test.go +++ b/pkg/cloud/azure/azure_test.go @@ -88,7 +88,7 @@ func TestResourcesRenderingSmoke(t *testing.T) { } resources := assets.GetRenderedResources() - assert.Len(t, resources, 8) + assert.Len(t, resources, 10) }) } } diff --git a/pkg/cloud/cloud_test.go b/pkg/cloud/cloud_test.go index 0a7b5b87a..954b19c07 100644 --- a/pkg/cloud/cloud_test.go +++ b/pkg/cloud/cloud_test.go @@ -129,27 +129,31 @@ func TestGetResources(t *testing.T) { }, { name: "GCP resources returned as expected", testPlatform: platformsMap[string(configv1.GCPPlatformType)], - expectedResourceCount: 4, + expectedResourceCount: 6, expectedResourcesKindName: []string{ "Deployment/gcp-cloud-controller-manager", "PodDisruptionBudget/gcp-cloud-controller-manager", "ClusterRole/gcp-cloud-controller-manager", "ClusterRoleBinding/gcp-cloud-controller-manager:cloud-provider", + "ValidatingAdmissionPolicyBinding/network-tier-annotation-binding", + "ValidatingAdmissionPolicy/network-tier-annotation-validation-policy", }, }, { name: "GCP resources returned as expected with single node cluster", testPlatform: platformsMap[string(configv1.GCPPlatformType)], - expectedResourceCount: 3, + expectedResourceCount: 5, singleReplica: true, expectedResourcesKindName: []string{ "Deployment/gcp-cloud-controller-manager", "ClusterRole/gcp-cloud-controller-manager", "ClusterRoleBinding/gcp-cloud-controller-manager:cloud-provider", + "ValidatingAdmissionPolicyBinding/network-tier-annotation-binding", + "ValidatingAdmissionPolicy/network-tier-annotation-validation-policy", }, }, { name: "Azure resources returned as expected", testPlatform: platformsMap[string(configv1.AzurePlatformType)], - expectedResourceCount: 9, + expectedResourceCount: 11, expectedResourcesKindName: []string{ "Deployment/azure-cloud-controller-manager", "DaemonSet/azure-cloud-node-manager", @@ -159,12 +163,14 @@ func TestGetResources(t *testing.T) { "ClusterRoleBinding/cloud-controller-manager:azure-cloud-controller-manager", "ValidatingAdmissionPolicy/openshift-cloud-controller-manager-cloud-provider-azure-node-admission", "ValidatingAdmissionPolicyBinding/openshift-cloud-controller-manager-cloud-provider-azure-node-admission", + "ValidatingAdmissionPolicyBinding/azure-load-balancer-tcp-idle-timeout-validation-annotation-binding", + "ValidatingAdmissionPolicy/azure-load-balancer-tcp-idle-timeout-annotation-validation-policy", "PodDisruptionBudget/azure-cloud-controller-manager", }, }, { name: "Azure resources returned as expected with single node cluster", testPlatform: platformsMap[string(configv1.AzurePlatformType)], - expectedResourceCount: 8, + expectedResourceCount: 10, singleReplica: true, expectedResourcesKindName: []string{ "Deployment/azure-cloud-controller-manager", @@ -175,6 +181,8 @@ func TestGetResources(t *testing.T) { "ClusterRoleBinding/cloud-controller-manager:azure-cloud-controller-manager", "ValidatingAdmissionPolicy/openshift-cloud-controller-manager-cloud-provider-azure-node-admission", "ValidatingAdmissionPolicyBinding/openshift-cloud-controller-manager-cloud-provider-azure-node-admission", + "ValidatingAdmissionPolicyBinding/azure-load-balancer-tcp-idle-timeout-validation-annotation-binding", + "ValidatingAdmissionPolicy/azure-load-balancer-tcp-idle-timeout-annotation-validation-policy", }, }, { name: "Azure Stack resources returned as expected", diff --git a/pkg/cloud/gcp/assets/validating-admission-policy-binding.yaml b/pkg/cloud/gcp/assets/validating-admission-policy-binding.yaml new file mode 100644 index 000000000..64f994524 --- /dev/null +++ b/pkg/cloud/gcp/assets/validating-admission-policy-binding.yaml @@ -0,0 +1,7 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: network-tier-annotation-binding +spec: + policyName: network-tier-annotation-validation-policy + validationActions: ["Deny"] diff --git a/pkg/cloud/gcp/assets/validating-admission-policy.yaml b/pkg/cloud/gcp/assets/validating-admission-policy.yaml new file mode 100644 index 000000000..c67ae32ee --- /dev/null +++ b/pkg/cloud/gcp/assets/validating-admission-policy.yaml @@ -0,0 +1,26 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: network-tier-annotation-validation-policy +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["services"] + variables: + - name: hasNetworkTier + expression: has(object.metadata.annotations) && 'cloud.google.com/network-tier' in object.metadata.annotations + - name: networkTierValue + expression: object.metadata.?annotations['cloud.google.com/network-tier'].orValue("") + - name: hasOldNetworkTier + expression: oldObject != null && oldObject.metadata.?annotations != null && 'cloud.google.com/network-tier' in oldObject.metadata.annotations + - name: oldNetworkTierValue + expression: oldObject.metadata.?annotations['cloud.google.com/network-tier'].orValue("") + - name: valueHasChanged + expression: variables.hasNetworkTier && (!variables.hasOldNetworkTier || variables.oldNetworkTierValue != variables.networkTierValue) + validations: + - expression: | + !variables.valueHasChanged || variables.networkTierValue in ['Standard', 'Premium'] + message: "The annotation 'cloud.google.com/network-tier', if specified, must be either 'Standard' or 'Premium'." diff --git a/pkg/cloud/gcp/gcp.go b/pkg/cloud/gcp/gcp.go index ed5bc0a7d..092d77b69 100644 --- a/pkg/cloud/gcp/gcp.go +++ b/pkg/cloud/gcp/gcp.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/asaskevich/govalidator" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" rbacv1 "k8s.io/api/rbac/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -22,6 +23,8 @@ var ( {ReferenceObject: &appsv1.Deployment{}, EmbedFsPath: "assets/cloud-controller-manager.yaml"}, {ReferenceObject: &rbacv1.ClusterRole{}, EmbedFsPath: "assets/gcp-cloud-controller-manager-clusterrole.yaml"}, {ReferenceObject: &rbacv1.ClusterRoleBinding{}, EmbedFsPath: "assets/gcp-cloud-controller-manager-clusterrolebinding.yaml"}, + {ReferenceObject: &admissionregistrationv1.ValidatingAdmissionPolicyBinding{}, EmbedFsPath: "assets/validating-admission-policy-binding.yaml"}, + {ReferenceObject: &admissionregistrationv1.ValidatingAdmissionPolicy{}, EmbedFsPath: "assets/validating-admission-policy.yaml"}, } ) diff --git a/pkg/cloud/gcp/gcp_test.go b/pkg/cloud/gcp/gcp_test.go index 79bd7802d..345a0a030 100644 --- a/pkg/cloud/gcp/gcp_test.go +++ b/pkg/cloud/gcp/gcp_test.go @@ -55,7 +55,7 @@ func TestResourcesRenderingSmoke(t *testing.T) { } resources := assets.GetRenderedResources() - assert.Len(t, resources, 3) + assert.Len(t, resources, 5) }) } }