├── tests ├── k8s_cis_check_suite_test.go ├── resource.go ├── privileged_container_pod_test.go ├── impersonate_operation_test.go ├── privileged_container_deploy_test.go ├── privileged_container_caps_not_allowed_test.go └── restricted_volumes_deploy_test.go ├── Contributing.md ├── client └── connection.go ├── go.mod ├── README.md ├── util └── util.go ├── Code-of-Conduct.md ├── LICENSE └── go.sum /tests/k8s_cis_check_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | 4 | package tests 5 | 6 | import ( 7 | "log" 8 | "testing" 9 | 10 | "github.com/yahoo/k8s-sec-check/client" 11 | "github.com/yahoo/k8s-sec-check/util" 12 | . "github.com/onsi/ginkgo/v2" 13 | . "github.com/onsi/gomega" 14 | ) 15 | 16 | const ( 17 | defaultStyle = "\x1b[0m" 18 | redColor = "\x1b[91m" 19 | //greenColor = "\x1b[32m" 20 | ) 21 | 22 | var err error 23 | 24 | var _ = BeforeSuite(func() { 25 | 26 | client.KubernetesClient, client.RestConfig, err = client.GetClients() 27 | if err != nil { 28 | Fail("Failed in before setup " + err.Error()) 29 | } 30 | log.Println("Target Namespace: " + util.TargetNamespace) 31 | log.Println("Target Service Account: " + util.TargetServiceAccount) 32 | }) 33 | 34 | var _ = AfterSuite(func() { 35 | log.Println("Done running K8s Cluster Check Tests") 36 | }) 37 | 38 | func TestK8sCISCheckTests(t *testing.T) { 39 | RegisterFailHandler(Fail) 40 | RunSpecs(t, "K8s Cluster CIS Check") 41 | } 42 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | First, thanks for taking the time to contribute to our project! The following information provides a guide for making contributions. 3 | 4 | ## Code of Conduct 5 | 6 | By participating in this project, you agree to abide by the [Oath Code of Conduct](Code-of-Conduct.md). Everyone is welcome to submit a pull request or open an issue to improve the documentation, add improvements, or report bugs. 7 | 8 | ## How to Ask a Question 9 | 10 | If you simply have a question that needs an answer, [create an issue](https://help.github.com/articles/creating-an-issue/), and label it as a question. 11 | 12 | ## How To Contribute 13 | 14 | ### Report a Bug or Request a Feature 15 | 16 | If you encounter any bugs while using this software, or want to request a new feature or enhancement, feel free to [create an issue](https://help.github.com/articles/creating-an-issue/) to report it, make sure you add a label to indicate what type of issue it is. 17 | 18 | ### Contribute Code 19 | Pull requests are welcome for bug fixes. If you want to implement something new, please [request a feature first](#report-a-bug-or-request-a-feature) so we can discuss it. 20 | 21 | #### Creating a Pull Request 22 | Please follow [best practices](https://github.com/trein/dev-best-practices/wiki/Git-Commit-Best-Practices) for creating git commits. 23 | 24 | When your code is ready to be submitted, you can [submit a pull request](https://help.github.com/articles/creating-a-pull-request/) to begin the code review process. 25 | -------------------------------------------------------------------------------- /client/connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | 4 | // Package client helps to setup Kubernetes client and config 5 | // All kubernetes connection related utilities go here. 6 | package client 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "os" 12 | 13 | "k8s.io/client-go/kubernetes" 14 | "k8s.io/client-go/rest" 15 | "k8s.io/client-go/tools/clientcmd" 16 | ) 17 | 18 | var ( 19 | // KubernetesClient : Kubernetes client 20 | KubernetesClient kubernetes.Interface 21 | // RestConfig : Kubernetes rest config 22 | RestConfig *rest.Config 23 | ) 24 | 25 | // GetClients retrieve the Kubernetes cluster client and restConfig 26 | // based on KUBECONFIG file or inClusterConfig 27 | // KUBECONFIG is an absolute file path, if not set then fallback on inClusterConfig 28 | func GetClients() (kubernetes.Interface, *rest.Config, error) { 29 | 30 | var inClusterConfig bool 31 | var kubeConfig string 32 | 33 | // if KUBECONFIG is set then use it. use cluster config otherwise 34 | kubeConfig = os.Getenv("KUBECONFIG") 35 | if kubeConfig == "" { 36 | inClusterConfig = true 37 | } else { 38 | log.Println("Using KUBECONFIG: " + kubeConfig) 39 | } 40 | 41 | // if inCluserConfig is set to true 42 | if inClusterConfig { 43 | emptystr := "" 44 | kubeConfig = emptystr 45 | } 46 | 47 | // Build kubeconfig 48 | config, err := clientcmd.BuildConfigFromFlags("", kubeConfig) 49 | if err != nil { 50 | log.Println(err.Error()) 51 | return nil, nil, err 52 | } 53 | 54 | // generate the client based off of the config 55 | client, err := kubernetes.NewForConfig(config) 56 | if err != nil { 57 | return nil, nil, fmt.Errorf("failed to create k8s client from config. Error: %v", err) 58 | } 59 | 60 | log.Println("Successfully constructed k8s client") 61 | return client, config, nil 62 | } 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yahoo/k8s-sec-check 2 | 3 | go 1.25 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.27.2 7 | github.com/onsi/gomega v1.38.2 8 | k8s.io/api v0.34.3 9 | k8s.io/apimachinery v0.34.3 10 | k8s.io/client-go v0.34.3 11 | ) 12 | 13 | require ( 14 | github.com/Masterminds/semver/v3 v3.4.0 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 17 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 18 | github.com/go-logr/logr v1.4.3 // indirect 19 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 20 | github.com/go-openapi/jsonreference v0.20.2 // indirect 21 | github.com/go-openapi/swag v0.23.0 // indirect 22 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 23 | github.com/gogo/protobuf v1.3.2 // indirect 24 | github.com/google/gnostic-models v0.7.0 // indirect 25 | github.com/google/go-cmp v0.7.0 // indirect 26 | github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect 27 | github.com/google/uuid v1.6.0 // indirect 28 | github.com/josharian/intern v1.0.0 // indirect 29 | github.com/json-iterator/go v1.1.12 // indirect 30 | github.com/mailru/easyjson v0.7.7 // indirect 31 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 32 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect 33 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 34 | github.com/pkg/errors v0.9.1 // indirect 35 | github.com/spf13/pflag v1.0.6 // indirect 36 | github.com/x448/float16 v0.8.4 // indirect 37 | go.yaml.in/yaml/v2 v2.4.2 // indirect 38 | go.yaml.in/yaml/v3 v3.0.4 // indirect 39 | golang.org/x/mod v0.31.0 // indirect 40 | golang.org/x/net v0.48.0 // indirect 41 | golang.org/x/oauth2 v0.34.0 // indirect 42 | golang.org/x/sync v0.19.0 // indirect 43 | golang.org/x/sys v0.39.0 // indirect 44 | golang.org/x/term v0.38.0 // indirect 45 | golang.org/x/text v0.32.0 // indirect 46 | golang.org/x/time v0.9.0 // indirect 47 | golang.org/x/tools v0.40.0 // indirect 48 | google.golang.org/protobuf v1.36.7 // indirect 49 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 50 | gopkg.in/inf.v0 v0.9.1 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | k8s.io/klog/v2 v2.130.1 // indirect 53 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect 54 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect 55 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 56 | sigs.k8s.io/randfill v1.0.0 // indirect 57 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect 58 | sigs.k8s.io/yaml v1.6.0 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /tests/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | 4 | // Package tests contains all tests. 5 | package tests 6 | 7 | import ( 8 | appsv1 "k8s.io/api/apps/v1" 9 | v1 "k8s.io/api/core/v1" 10 | "k8s.io/apimachinery/pkg/api/resource" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/util/intstr" 13 | ) 14 | 15 | // GetNginxDeploymentSpec returns the nginx deployment spec 16 | func GetNginxDeploymentSpec(namespace string, deploymentName string, replicaCount int32, 17 | privileged bool) *appsv1.Deployment { 18 | 19 | maxSurge := intstr.FromInt(1) 20 | 21 | return &appsv1.Deployment{ 22 | ObjectMeta: metav1.ObjectMeta{ 23 | Name: deploymentName, 24 | Namespace: namespace, 25 | }, 26 | Spec: appsv1.DeploymentSpec{ 27 | Replicas: &replicaCount, 28 | Selector: &metav1.LabelSelector{ 29 | MatchLabels: map[string]string{ 30 | "k8s-app": deploymentName, 31 | }, 32 | }, 33 | Strategy: appsv1.DeploymentStrategy{ 34 | RollingUpdate: &appsv1.RollingUpdateDeployment{ 35 | MaxSurge: &maxSurge, 36 | }, 37 | Type: appsv1.RollingUpdateDeploymentStrategyType, 38 | }, 39 | Template: v1.PodTemplateSpec{ 40 | ObjectMeta: metav1.ObjectMeta{ 41 | Labels: map[string]string{ 42 | "k8s-app": deploymentName, 43 | }, 44 | }, 45 | Spec: v1.PodSpec{ 46 | Containers: []v1.Container{ 47 | { 48 | Name: "nginx", 49 | ImagePullPolicy: v1.PullIfNotPresent, 50 | Image: "nginx", 51 | Ports: []v1.ContainerPort{ 52 | { 53 | ContainerPort: 4080, 54 | Protocol: v1.ProtocolTCP, 55 | }, 56 | }, 57 | Resources: v1.ResourceRequirements{ 58 | Limits: v1.ResourceList{ 59 | v1.ResourceCPU: resource.MustParse("500m"), 60 | v1.ResourceMemory: resource.MustParse("100Mi"), 61 | }, 62 | }, 63 | SecurityContext: &v1.SecurityContext{ 64 | Privileged: &privileged, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | }, 71 | } 72 | } 73 | 74 | // GetNginxPodSpec returns the nginx deployment spec 75 | func GetNginxPodSpec(namespace string, podName string, privileged bool) *v1.Pod { 76 | 77 | return &v1.Pod{ 78 | ObjectMeta: metav1.ObjectMeta{ 79 | Name: podName, 80 | Namespace: namespace, 81 | }, 82 | Spec: v1.PodSpec{ 83 | Containers: []v1.Container{ 84 | { 85 | Name: "nginx", 86 | ImagePullPolicy: v1.PullIfNotPresent, 87 | Image: "nginx", 88 | Ports: []v1.ContainerPort{ 89 | { 90 | ContainerPort: 4080, 91 | Protocol: v1.ProtocolTCP, 92 | }, 93 | }, 94 | Resources: v1.ResourceRequirements{ 95 | Limits: v1.ResourceList{ 96 | v1.ResourceCPU: resource.MustParse("500m"), 97 | v1.ResourceMemory: resource.MustParse("100Mi"), 98 | }, 99 | }, 100 | SecurityContext: &v1.SecurityContext{ 101 | Privileged: &privileged, 102 | }, 103 | }, 104 | }, 105 | }, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/privileged_container_pod_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | package tests 4 | 5 | import ( 6 | "github.com/yahoo/k8s-sec-check/client" 7 | "github.com/yahoo/k8s-sec-check/util" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | //Test case: 13 | // Do not admit privileged containers 14 | 15 | // Create Privileged pod with host network, host PID and host IPC 16 | // and assert that it fails to create the pod and return the 17 | // appropriate error for each. 18 | 19 | //Sample error output: 20 | // Failed to create pod: pods "nginx-privileged-container-pod-test" is forbidden: 21 | // unable to validate against any pod security policy: 22 | // [spec.securityContext.hostNetwork: Invalid value: true: Host network is not allowed to be used 23 | // spec.securityContext.hostPID: Invalid value: true: Host PID is not allowed to be used 24 | // spec.securityContext.hostIPC: Invalid value: true: Host IPC is not allowed to be used 25 | // spec.containers[0].securityContext.containers[0].hostPort: Invalid value: 4080: 26 | // Host port 4080 is not allowed to be used. Allowed ports: [] 27 | // spec.securityContext.hostNetwork: Invalid value: true: Host network is not allowed to be used 28 | // spec.securityContext.hostPID: Invalid value: true: Host PID is not allowed to be used 29 | // spec.securityContext.hostIPC: Invalid value: true: Host IPC is not allowed to be used 30 | // spec.containers[0].securityContext.containers[0].hostPort: Invalid value: 4080: 31 | // Host port 4080 is not allowed to be used. Allowed ports: []] 32 | 33 | var _ = Describe("creating a pod", func() { 34 | 35 | var PodName = "nginx-privileged-container-pod-test" 36 | 37 | Context("with Privileged security context", func() { 38 | 39 | It("should return an error on creating pod", func() { 40 | // create pod with privilege true 41 | pod := GetNginxPodSpec(util.TargetNamespace, PodName, false) 42 | pod.Spec.HostNetwork = true 43 | pod.Spec.HostPID = true 44 | pod.Spec.HostIPC = true 45 | err := util.CreatePod(client.KubernetesClient, pod, util.TargetNamespace) 46 | // assert for an existence of an error 47 | Ω(err).ShouldNot(BeNil()) 48 | // assert on Host network 49 | Expect(err.Error()).To( 50 | ContainSubstring("spec.securityContext.hostNetwork: Invalid value: true: " + 51 | "Host network is not allowed to be used")) 52 | // assert on host PID 53 | Expect(err.Error()).To( 54 | ContainSubstring("spec.securityContext.hostPID: Invalid value: true: " + 55 | "Host PID is not allowed to be used")) 56 | // assert on host IPC 57 | Expect(err.Error()).To( 58 | ContainSubstring("spec.securityContext.hostIPC: Invalid value: true: " + 59 | "Host IPC is not allowed to be used")) 60 | // assert if operation is forbidden and do not admit the operation 61 | Expect(err.Error()).To(ContainSubstring("is forbidden: ")) 62 | }) 63 | }) 64 | 65 | AfterEach(func() { 66 | if CurrentSpecReport().Failed() { 67 | // delete the pod once the test is complete, if test created the pod successfully 68 | err := util.DeletePod(client.KubernetesClient, PodName, util.TargetNamespace) 69 | if err != nil { 70 | GinkgoLogr.Error(err, "Failed in teardown", 71 | "file", CurrentSpecReport().LeafNodeLocation.FileName, 72 | "line", CurrentSpecReport().LeafNodeLocation.LineNumber, 73 | ) 74 | } 75 | } 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Security Check (k8s-sec-check) 2 | 3 | > Kubernetes Security Check automates the complex security checks based on CIS guidelines. 4 | 5 | ## Table of Contents 6 | - [Background](#background) 7 | - [Architecture](#architecture) 8 | - [Install](#install) 9 | - [Usage](#usage) 10 | - [Maintainers](#maintainers) 11 | - [Contribute](#contribute) 12 | - [License](#license) 13 | 14 | ## Background 15 | 16 | There are many tools in the open-source world that provide a way to certify the security of Kubernetes security and some tools are also implemented based on the detailed CIS guidelines. 17 | However, not all tools are covering complex security checks. For example. Pod Security Policy checks. Kubernetes Security Check automates the complex security checks based on CIS guidelines. 18 | It can be further extended to add more checks as well. 19 | 20 | ## Architecture 21 | The Kube security check tool is a simple test suite based on Ginkgo. Once the binary is built, it can be run remotely by simply passing the KUBECONFIG environment variable which represents the path to a Kubernetes configuration file. 22 | Currently, it covers the following tests with respective Kubernetes fields: 23 | - [User impersonation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation) 24 | - Impersonate Kubernetes calls as a user 25 | - Do not admit container with restricted volume e.g [flexVolume](https://kubernetes.io/docs/concepts/storage/volumes/#flexVolume), [hostPath](https://kubernetes.io/docs/concepts/storage/volumes/#hostpath) 26 | - Volume - [AllowedHostPaths](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems) 27 | - [Pod Security Policy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/): 28 | - Do not admit privileged containers 29 | - Security Context: [privileged](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged) 30 | - Do not admit containers wishing to share the host process ID namespace 31 | - [hostPID](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces) 32 | - Do not admit containers wishing to share the host IPC namespace 33 | - [hostIPC](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces) 34 | - Do not admit containers wishing to share the host network namespace 35 | - [hostNetwork](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces) 36 | - Do not admit containers with dangerous [capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html) 37 | - [allowedCapabilities](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities) 38 | 39 | ## Install 40 | 41 | Make sure to set the relevant namespace, service account, and context in the kubeconfig file. 42 | 43 | ``export KUBECONFIG=~/.kube/config`` 44 | 45 | Run the binary: 46 | ``k8s-sec-check`` 47 | 48 | ## Usage 49 | 50 | If checks are being run remotely using the KUBECONFIG file, users must set the following environment variables. 51 | 52 | `KUBECONFIG`: Kubeconfig file absolute path. 53 | - Set environment variable with `KUBECONFIG` to run the tests remotely. 54 | - If the `KUBECONFIG` variable is not set, it sets to `INCLUSTERCONFIG` by default. 55 | 56 | `KUBE_NAMESPACE` : Target Kubernetes namespace to run tests (default: `k8s-sec-check`) 57 | 58 | `KUBE_SERVICEACCOUNT`: Target Kubernetes Service account to be used during tests. (default: `k8s-sec-check`) 59 | 60 | ## Maintainers 61 | Core Team : omega-core@verizonmedia.com 62 | 63 | ## Contribute 64 | Please refer to the [contributing file](Contributing.md) for information about how to get involved. We welcome issues, questions, and pull requests. 65 | 66 | ## License 67 | 68 | Copyright 2019 Oath Inc. Licensed under the Apache License, Version 2.0 (the "License") -------------------------------------------------------------------------------- /tests/impersonate_operation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | package tests 4 | 5 | import ( 6 | "log" 7 | 8 | "github.com/yahoo/k8s-sec-check/client" 9 | "github.com/yahoo/k8s-sec-check/util" 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | "k8s.io/client-go/kubernetes" 13 | "k8s.io/client-go/rest" 14 | ) 15 | 16 | //Test case: 17 | // User impersonation 18 | 19 | // Create a deployment with impersonated user and it should return an error and failed 20 | // to create the deployment. More information on impersonation on kubernetes is available 21 | // on the following page. 22 | 23 | // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation 24 | 25 | // Following is a table test where different users can be used to run the same test case. 26 | // Test runs as a cd user and a random user and checks if impersonation operation can be 27 | // successful or not. 28 | 29 | var _ = Describe("creating a deployment", func() { 30 | 31 | var deploymentName = "nginx-priv-impersonation" 32 | 33 | Context("as Impersonated user or group with Privileged container enabled", func() { 34 | 35 | DescribeTable("impersonate as user to create prvileged deployment", 36 | func(ImpersonationUser string) { 37 | client.RestConfig.Impersonate = rest.ImpersonationConfig{ 38 | // UserName is the username to impersonate on each request. 39 | UserName: ImpersonationUser, 40 | // Groups are the groups to impersonate on each request. 41 | Groups: []string{"system:masters"}, 42 | // Extra is a free-form field which can be used to link some authentication information 43 | // to authorization information. This field allows you to impersonate it. 44 | //Extra: ImpersonateUserExtra, 45 | } 46 | log.Println(CurrentSpecReport().FullText() + 47 | ": Creating deployment as user: " + 48 | client.RestConfig.Impersonate.UserName) 49 | 50 | kc, err := kubernetes.NewForConfig(client.RestConfig) 51 | if err != nil { 52 | Fail(CurrentSpecReport().FullText() + 53 | ": Failed to get kubernetes client set" + err.Error()) 54 | } 55 | 56 | // create deployment with privilege true and replicacount set to 1 57 | deployment := GetNginxDeploymentSpec(util.TargetNamespace, deploymentName, 1, true) 58 | deployment.Spec.Template.Spec.HostNetwork = true 59 | deployment.Spec.Template.Spec.HostPID = true 60 | deployment.Spec.Template.Spec.HostIPC = true 61 | 62 | // create deployment with whitelisted service account name matching with the namespace 63 | deployment.Spec.Template.Spec.ServiceAccountName = util.TargetServiceAccount 64 | 65 | err = util.CreateDeployment(kc, deployment, util.TargetNamespace) 66 | 67 | // it should return an error as operation is forbidden. 68 | // NOTE: if you're a cluster admin and running this test, if will fail 69 | // as cluster admin user as a permission to impersonate. 70 | Ω(err).ShouldNot(BeNil()) 71 | // if error happens, make sure it matches with forbidden message 72 | Expect(err.Error()).To(MatchRegexp("Failed to create deployment: users \".*\" is forbidden: " + 73 | "User \".*\" cannot impersonate resource \"users\" in API group \"\" at the cluster scope")) 74 | }, 75 | // as an random non-existent user 76 | Entry("Impersonate as wronguser", "wronguser"), 77 | ) 78 | }) 79 | 80 | AfterEach(func() { 81 | // delete the deployment once the test is complete 82 | err := util.DeleteDeployment(client.KubernetesClient, deploymentName, util.TargetNamespace) 83 | if err != nil { 84 | GinkgoLogr.Error(err, "Failed in teardown", 85 | "file", CurrentSpecReport().LeafNodeLocation.FileName, 86 | "line", CurrentSpecReport().LeafNodeLocation.LineNumber, 87 | ) 88 | } 89 | }) 90 | }) -------------------------------------------------------------------------------- /tests/privileged_container_deploy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | 4 | package tests 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/yahoo/k8s-sec-check/client" 10 | "github.com/yahoo/k8s-sec-check/util" 11 | v1 "k8s.io/api/core/v1" 12 | "k8s.io/api/extensions/v1beta1" 13 | 14 | . "github.com/onsi/ginkgo/v2" 15 | . "github.com/onsi/gomega" 16 | ) 17 | 18 | //Test case(s): 19 | // Do not admit privileged containers 20 | // Do not admit containers wishing to share the host network namespace 21 | // Do not admit containers wishing to share the host IPC namespace 22 | // Do not admit containers wishing to share the host process ID namespace 23 | 24 | // Create Privileged pod with host network, host PID and host IPC 25 | // and assert that it fails to create the pod and return the 26 | // appropriate error for each. Following document has information on privileged 27 | // container creation. 28 | 29 | // https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged 30 | // https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces 31 | 32 | //Sample error output: 33 | // status: 34 | // conditions: 35 | // - lastTransitionTime: 2019-05-15T00:21:08Z 36 | // message: 'pods "nginx-privileged-container-deployment-test-7c68b45968-" is forbidden: 37 | // unable to validate against any pod security policy: [spec.securityContext.hostNetwork: 38 | // Invalid value: true: Host network is not allowed to be used spec.securityContext.hostPID: 39 | // Invalid value: true: Host PID is not allowed to be used spec.securityContext.hostIPC: 40 | // Invalid value: true: Host IPC is not allowed to be used spec.containers[0].securityContext.privileged: 41 | // Invalid value: true: Privileged containers are not allowed spec.containers[0].securityContext.containers[0].hostPort: 42 | // Invalid value: 4080: Host port 4080 is not allowed to be used. Allowed ports: 43 | // [] spec.securityContext.hostNetwork: Invalid value: true: Host network is not 44 | // allowed to be used spec.securityContext.hostPID: Invalid value: true: Host PID 45 | // is not allowed to be used spec.securityContext.hostIPC: Invalid value: true: 46 | // Host IPC is not allowed to be used spec.containers[0].securityContext.privileged: 47 | // Invalid value: true: Privileged containers are not allowed spec.containers[0].securityContext.containers[0].hostPort: 48 | // Invalid value: 4080: Host port 4080 is not allowed to be used. Allowed ports: 49 | // []]' 50 | // reason: FailedCreate 51 | // status: "True" 52 | // type: ReplicaFailure 53 | 54 | var _ = Describe("creating a deployment", func() { 55 | 56 | var deploymentName = "nginx-privileged-container-deploy-test" 57 | 58 | Context("with Privileged container", func() { 59 | 60 | It("should return an error on creating of replicaset", func() { 61 | // set privileged container and host network, pid and ipc. 62 | deployment := GetNginxDeploymentSpec(util.TargetNamespace, deploymentName, 1, true) 63 | deployment.Spec.Template.Spec.HostNetwork = true 64 | deployment.Spec.Template.Spec.HostPID = true 65 | deployment.Spec.Template.Spec.HostIPC = true 66 | 67 | // create deployment with privilege true and replicacount set to 1 68 | err := util.CreateDeployment(client.KubernetesClient, deployment, util.TargetNamespace) 69 | Ω(err).Should(BeNil()) 70 | 71 | // find replicaSet for the deployment by label 72 | // retry 3 times, every 10 seconds 73 | rsList, err := util.GetStatusCondition(client.KubernetesClient, deploymentName) 74 | if err != nil { 75 | Fail(CurrentSpecReport().LeafNodeText + ":" + err.Error()) 76 | } 77 | 78 | // if not error, but failed to find the replicaset, fail. 79 | Expect(len(rsList.Items) == 1).Should(Equal(true)) 80 | fmt.Println(len(rsList.Items[0].Status.Conditions)) 81 | Ω(rsList.Items[0].Status).ShouldNot(BeNil()) 82 | Expect(len(rsList.Items[0].Status.Conditions) == 1).Should(Equal(true)) 83 | // check if privileged container is failed to create with 84 | // failure reason and condition 85 | Expect(rsList.Items[0].Status.Conditions[0].Reason).To(Equal("FailedCreate")) 86 | Expect(rsList.Items[0].Status.Conditions[0].Type).To(Equal(v1beta1.ReplicaSetReplicaFailure)) 87 | Expect(rsList.Items[0].Status.Conditions[0].Status).To(Equal(v1.ConditionStatus("True"))) 88 | // assert on Host network 89 | Expect(rsList.Items[0].Status.Conditions[0].Message). 90 | To(ContainSubstring( 91 | "spec.securityContext.hostNetwork: Invalid value: true: " + 92 | "Host network is not allowed to be used")) 93 | // assert on host PID 94 | Expect(rsList.Items[0].Status.Conditions[0].Message). 95 | To(ContainSubstring("spec.securityContext.hostPID: Invalid value: true: " + 96 | "Host PID is not allowed to be used")) 97 | // assert on host IPC 98 | Expect(rsList.Items[0].Status.Conditions[0].Message). 99 | To(ContainSubstring("spec.securityContext.hostIPC: Invalid value: true: " + 100 | "Host IPC is not allowed to be used")) 101 | // assert if operation is forbidden and do not admit the operation 102 | Expect(rsList.Items[0].Status.Conditions[0].Message).To(ContainSubstring("is forbidden: ")) 103 | // assert on Privileged container 104 | Expect(rsList.Items[0].Status.Conditions[0].Message). 105 | To(ContainSubstring("spec.containers[0].securityContext.privileged: Invalid value: true: " + 106 | "Privileged containers are not allowed")) 107 | }) 108 | }) 109 | 110 | AfterEach(func() { 111 | // delete the deployment once the test is complete 112 | err := util.DeleteDeployment(client.KubernetesClient, deploymentName, util.TargetNamespace) 113 | if err != nil { 114 | GinkgoLogr.Error(err, "Failed in teardown", 115 | "file", CurrentSpecReport().LeafNodeLocation.FileName, 116 | "line", CurrentSpecReport().LeafNodeLocation.LineNumber, 117 | ) 118 | } 119 | }) 120 | }) 121 | -------------------------------------------------------------------------------- /tests/privileged_container_caps_not_allowed_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | 4 | package tests 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/yahoo/k8s-sec-check/client" 10 | "github.com/yahoo/k8s-sec-check/util" 11 | . "github.com/onsi/ginkgo/v2" 12 | . "github.com/onsi/gomega" 13 | v1 "k8s.io/api/core/v1" 14 | "k8s.io/api/extensions/v1beta1" 15 | ) 16 | 17 | //Test case: 18 | // Do not admit containers with dangerous capabilities 19 | 20 | // Create Privileged pod with capabilities, 21 | // and assert that it fails to create the pod and return the 22 | // appropriate error for each capabilities. Following document has 23 | // information on capabilties on k8s container. 24 | 25 | //http://man7.org/linux/man-pages/man7/capabilities.7.html 26 | //https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities 27 | //https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities 28 | 29 | //sample output: 30 | //status: 31 | //conditions: 32 | // - lastTransitionTime: 2019-05-15T21:00:04Z 33 | //message: 'pods "nginx-privileged-container-capability-not-allowed-deploy-test-69fc5b5b7d-" 34 | // is forbidden: unable to validate against any pod security policy: [ 35 | // capabilities.add: Invalid value: "NET_ADMIN": capability may not be added 36 | // capabilities.add: Invalid value: "NET_RAW": capability may not be added 37 | // capabilities.add: Invalid value: "SYS_PTRACE": capability may not be added 38 | // capabilities.add: Invalid value: "SYS_ADMIN": capability may not be added 39 | // spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed 40 | // capabilities.add: Invalid value: "NET_ADMIN": capability may not be added 41 | // capabilities.add: Invalid value: "NET_RAW": capability may not be added 42 | // capabilities.add: Invalid value: "SYS_PTRACE": capability may not be added 43 | // capabilities.add: Invalid value: "SYS_ADMIN": capability may not be added 44 | // spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed 45 | // capabilities.add: Invalid value: "NET_ADMIN": capability may not be added 46 | // capabilities.add: Invalid value: "NET_RAW": capability may not be added 47 | // capabilities.add: Invalid value: "SYS_PTRACE": capability may not be added 48 | // capabilities.add: Invalid value: "SYS_ADMIN": capability may not be added]' 49 | //reason: FailedCreate 50 | // status: "True" 51 | // type: ReplicaFailure 52 | 53 | var _ = Describe("creating a deployment", func() { 54 | 55 | var deploymentName = "nginx-privileged-container-capability-not-allowed-deploy-test" 56 | 57 | Context("with Privileged container with capabilities", func() { 58 | 59 | It("should return an error on creating of replicaset", func() { 60 | // set the deployment with privilege true and replicacount and other linux capabilities 61 | deployment := GetNginxDeploymentSpec(util.TargetNamespace, deploymentName, 1, true) 62 | deployment.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities = &v1.Capabilities{} 63 | deployment.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities. 64 | Add = []v1.Capability{"NET_ADMIN", "NET_RAW", "SYS_PTRACE", "SYS_ADMIN", "KILL"} 65 | 66 | // create deployment with above set configs 67 | err := util.CreateDeployment(client.KubernetesClient, deployment, util.TargetNamespace) 68 | Ω(err).Should(BeNil()) 69 | 70 | // find replicaSet for the deployment by label 71 | // retry 3 times, every 10 seconds 72 | rsList, err := util.GetStatusCondition(client.KubernetesClient, deploymentName) 73 | if err != nil { 74 | Fail(CurrentSpecReport().LeafNodeText + ":" + err.Error()) 75 | } 76 | 77 | // if not error, but failed to find the replicaset, fail. 78 | Expect(len(rsList.Items) == 1).Should(Equal(true)) 79 | fmt.Println(len(rsList.Items[0].Status.Conditions)) 80 | Ω(rsList.Items[0].Status).ShouldNot(BeNil()) 81 | Expect(len(rsList.Items[0].Status.Conditions) == 1).Should(Equal(true)) 82 | // check if privileged container is failed to create with 83 | // failure reason and condition 84 | Expect(rsList.Items[0].Status.Conditions[0].Reason).To(Equal("FailedCreate")) 85 | Expect(rsList.Items[0].Status.Conditions[0].Type).To(Equal(v1beta1.ReplicaSetReplicaFailure)) 86 | Expect(rsList.Items[0].Status.Conditions[0].Status).To(Equal(v1.ConditionStatus("True"))) 87 | // assert if operation is forbidden and do not admit the operation 88 | Expect(rsList.Items[0].Status.Conditions[0].Message).To(ContainSubstring("is forbidden: ")) 89 | // assert on Privileged container 90 | 91 | Expect(rsList.Items[0].Status.Conditions[0].Message). 92 | To(ContainSubstring("Privileged containers are not allowed")) 93 | Expect(rsList.Items[0].Status.Conditions[0].Message). 94 | To(ContainSubstring("capabilities.add: Invalid value: \"NET_ADMIN\": capability may not be added")) 95 | Expect(rsList.Items[0].Status.Conditions[0].Message). 96 | To(ContainSubstring("capabilities.add: Invalid value: \"NET_RAW\": capability may not be added")) 97 | Expect(rsList.Items[0].Status.Conditions[0].Message). 98 | To(ContainSubstring("capabilities.add: Invalid value: \"SYS_PTRACE\": capability may not be added")) 99 | Expect(rsList.Items[0].Status.Conditions[0].Message). 100 | To(ContainSubstring("capabilities.add: Invalid value: \"SYS_ADMIN\": capability may not be added")) 101 | Expect(rsList.Items[0].Status.Conditions[0].Message). 102 | To(ContainSubstring("capabilities.add: Invalid value: \"KILL\": capability may not be added")) 103 | 104 | }) 105 | }) 106 | 107 | AfterEach(func() { 108 | // delete the deployment once the test is complete 109 | err := util.DeleteDeployment(client.KubernetesClient, deploymentName, util.TargetNamespace) 110 | if err != nil { 111 | GinkgoLogr.Error(err, "Failed in teardown", 112 | "file", CurrentSpecReport().LeafNodeLocation.FileName, 113 | "line", CurrentSpecReport().LeafNodeLocation.LineNumber, 114 | ) 115 | } 116 | }) 117 | }) 118 | -------------------------------------------------------------------------------- /tests/restricted_volumes_deploy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | 4 | package tests 5 | 6 | import ( 7 | "log" 8 | 9 | "github.com/yahoo/k8s-sec-check/client" 10 | "github.com/yahoo/k8s-sec-check/util" 11 | . "github.com/onsi/ginkgo/v2" 12 | . "github.com/onsi/gomega" 13 | appsv1 "k8s.io/api/apps/v1" 14 | v1 "k8s.io/api/core/v1" 15 | "k8s.io/api/extensions/v1beta1" 16 | ) 17 | 18 | //Test case: 19 | // Do not admit container with restricted volume e.g flexVolume, hostPath 20 | 21 | // Create Privileged deployment with restricted volume such as hostpath and flex volume 22 | // and assert that it fails to create the pod and return the 23 | // appropriate error for each. Following document has information on privileged 24 | // container creation. 25 | 26 | // https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged 27 | // https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes 28 | // https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems 29 | 30 | //Sample error output: 31 | // status: 32 | // conditions: 33 | // - lastTransitionTime: 2019-05-22T18:37:47Z 34 | // message: 'pods "nginx-privileged-container-deployment-test-6585dc5475-" is forbidden: 35 | // unable to validate against any pod security policy: [ 36 | // spec.volumes[0]: Invalid value: "hostPath": hostPath volumes are not allowed to be used 37 | // spec.volumes[1]: Invalid value: "flexVolume": flexVolume volumes are not allowed to be used 38 | // spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed 39 | // spec.volumes[0]: Invalid value: "hostPath": hostPath volumes are not allowed to be used 40 | // spec.securityContext.volumes[1].driver: Invalid value: "kubernetes.io/lvm": Flexvolume driver is not allowed to be used 41 | // spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed 42 | // ]' 43 | // reason: FailedCreate 44 | // status: "True" 45 | // type: ReplicaFailure 46 | 47 | var _ = Describe("creating a deployment", func() { 48 | 49 | var deploymentName = "nginx-volume-deploy-test" 50 | var deployment *appsv1.Deployment 51 | 52 | BeforeEach(func() { 53 | // create the deployment instance 54 | // set privileged container with replica count to 1 55 | deployment = GetNginxDeploymentSpec(util.TargetNamespace, deploymentName, 1, true) 56 | 57 | // host path of type directory. note: you can't get the address of a constant. 58 | t := v1.HostPathDirectory 59 | 60 | // set restricted privileged host path volume and flex volume 61 | deployment.Spec.Template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{ 62 | { 63 | Name: deploymentName + "hostpath", 64 | MountPath: "/datahostpath", 65 | }, 66 | { 67 | Name: deploymentName + "flex", 68 | MountPath: "/dataflex", 69 | }, 70 | } 71 | deployment.Spec.Template.Spec.Volumes = []v1.Volume{ 72 | { 73 | Name: deploymentName + "hostpath", 74 | VolumeSource: v1.VolumeSource{ 75 | HostPath: &v1.HostPathVolumeSource{ 76 | Path: "/datahostpath", 77 | Type: &t, 78 | }, 79 | }, 80 | }, 81 | { 82 | Name: deploymentName + "flex", 83 | VolumeSource: v1.VolumeSource{ 84 | FlexVolume: &v1.FlexVolumeSource{ 85 | Driver: "kubernetes.io/lvm", 86 | FSType: "ext4", 87 | }, 88 | }, 89 | }, 90 | } 91 | }) 92 | 93 | Context("with Privileged container, host path and flex volumes", func() { 94 | 95 | It("should return an error on creating of replicaset", func() { 96 | 97 | // create deployment with privilege true and set volumes 98 | err := util.CreateDeployment(client.KubernetesClient, deployment, util.TargetNamespace) 99 | // error should be nil 100 | Ω(err).Should(BeNil()) 101 | 102 | // once deployment is created, find replicaSet for the deployment by label 103 | rsList, err := util.GetStatusCondition(client.KubernetesClient, deploymentName) 104 | if err != nil { 105 | Fail(CurrentSpecReport().LeafNodeText + ":" + err.Error()) 106 | } 107 | 108 | // if not error, but failed to find the replica set, fail. 109 | Expect(len(rsList.Items) == 1).Should(Equal(true)) 110 | log.Println(len(rsList.Items[0].Status.Conditions)) 111 | 112 | // status should not be nil 113 | Ω(rsList.Items[0].Status).ShouldNot(BeNil()) 114 | 115 | // match conditions count 116 | Expect(len(rsList.Items[0].Status.Conditions) == 1).Should(Equal(true)) 117 | 118 | // check if privileged container is failed to create with 119 | // failure reason and condition 120 | Expect(rsList.Items[0].Status.Conditions[0].Reason).To(Equal("FailedCreate")) 121 | Expect(rsList.Items[0].Status.Conditions[0].Type).To(Equal(v1beta1.ReplicaSetReplicaFailure)) 122 | Expect(rsList.Items[0].Status.Conditions[0].Status).To(Equal(v1.ConditionStatus("True"))) 123 | 124 | // assert on host path volume 125 | Expect(rsList.Items[0].Status.Conditions[0].Message). 126 | To(ContainSubstring( 127 | "\"hostPath\": " + "hostPath volumes are not allowed to be used")) 128 | 129 | // assert on flex volume 130 | Expect(rsList.Items[0].Status.Conditions[0].Message). 131 | To(ContainSubstring( 132 | "\"flexVolume\": flexVolume volumes are not allowed to be used")) 133 | 134 | // assert of flex volume driver 135 | Expect(rsList.Items[0].Status.Conditions[0].Message). 136 | To(ContainSubstring( 137 | "\"kubernetes.io/lvm\": Flexvolume driver is not allowed to be used")) 138 | 139 | // assert if operation is forbidden and do not admit the operation 140 | Expect(rsList.Items[0].Status.Conditions[0].Message).To(ContainSubstring("is forbidden: ")) 141 | 142 | // assert on Privileged container 143 | Expect(rsList.Items[0].Status.Conditions[0].Message). 144 | To(ContainSubstring("spec.containers[0].securityContext.privileged: Invalid value: true: " + 145 | "Privileged containers are not allowed")) 146 | }) 147 | }) 148 | 149 | AfterEach(func() { 150 | // delete the deployment once the test is complete 151 | err := util.DeleteDeployment(client.KubernetesClient, deploymentName, util.TargetNamespace) 152 | if err != nil { 153 | GinkgoLogr.Error(err, "Failed in teardown", 154 | "file", CurrentSpecReport().LeafNodeLocation.FileName, 155 | "line", CurrentSpecReport().LeafNodeLocation.LineNumber, 156 | ) 157 | } 158 | }) 159 | }) 160 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Oath, Inc. 2 | // Licensed under the terms of the Apache Version 2.0 License. See LICENSE file for terms. 3 | 4 | // Package util helps to setup Kubernetes resource related operations. 5 | // All kubernetes resource creation operation goes here. 6 | // In addition, any common tests related library functions goes here. 7 | package util 8 | 9 | import ( 10 | "context" 11 | "errors" 12 | "log" 13 | "os" 14 | "strings" 15 | "time" 16 | 17 | g "github.com/onsi/ginkgo/v2" 18 | appsv1 "k8s.io/api/apps/v1" 19 | v1 "k8s.io/api/core/v1" 20 | "k8s.io/api/extensions/v1beta1" 21 | kerr "k8s.io/apimachinery/pkg/api/errors" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/client-go/kubernetes" 24 | ) 25 | 26 | const ( 27 | defaultNamespace = "k8s-sec-check" 28 | defaultServiceAccount = "k8s-sec-check" 29 | ) 30 | 31 | // TargetNamespace represents Kubernetes namespace to run tests 32 | var TargetNamespace = getTargetNamespace() 33 | 34 | // TargetServiceAccount represents Kubernetes service account 35 | var TargetServiceAccount = getTargetServiceAccount() 36 | 37 | // getTargetNamespace returns the value of KUBE_NAMESPACE, 38 | // or if that is not defined, set the default namespace 39 | func getTargetNamespace() string { 40 | targetNamespace := os.Getenv("KUBE_NAMESPACE") 41 | if targetNamespace != "" { 42 | return targetNamespace 43 | } 44 | return defaultNamespace 45 | 46 | } 47 | 48 | // getTargetServiceAccount returns the value of KUBE_SERVICEACCOUNT, 49 | // or if that is not defined, set the default service account 50 | func getTargetServiceAccount() string { 51 | targetServiceAccount := os.Getenv("KUBE_SERVICEACCOUNT") 52 | if targetServiceAccount != "" { 53 | return targetServiceAccount 54 | } 55 | return defaultServiceAccount 56 | } 57 | 58 | // CreateDeployment creates kubernetes deployment 59 | func CreateDeployment(clientset kubernetes.Interface, deployment *appsv1.Deployment, targetNamespace string) error { 60 | _, err := clientset.AppsV1().Deployments(targetNamespace).Create(context.Background(), deployment, metav1.CreateOptions{}) 61 | if err != nil { 62 | return errors.New("Failed to create deployment: " + err.Error()) 63 | } 64 | return nil 65 | } 66 | 67 | // DeleteDeployment deletes kubernetes deployment 68 | func DeleteDeployment(clientset kubernetes.Interface, deploymentName string, targetNamespace string) error { 69 | propagationPolicy := metav1.DeletePropagationForeground 70 | err := clientset.AppsV1().Deployments(targetNamespace).Delete(context.Background(), deploymentName, metav1.DeleteOptions{ 71 | PropagationPolicy: &propagationPolicy, 72 | }) 73 | if err != nil && !kerr.IsNotFound(err) { 74 | return errors.New("Failed to delete deployment: " + err.Error()) 75 | } 76 | return nil 77 | } 78 | 79 | // ReplicaSetsByLabel returns rs list by labelSelectorValue and targetNamespace 80 | func ReplicaSetsByLabel(clientset kubernetes.Interface, labelSelectorValue string, targetNamespace string) (*v1beta1.ReplicaSetList, error) { 81 | return clientset.ExtensionsV1beta1(). 82 | ReplicaSets(targetNamespace). 83 | List(context.Background(), metav1.ListOptions{ 84 | LabelSelector: labelSelectorValue, 85 | }) 86 | } 87 | 88 | // IsPrivilegedContainerCreated gets the replicaset and match status failure 89 | // condition and status failure reason 90 | func IsPrivilegedContainerCreated(rsList *v1beta1.ReplicaSetList, 91 | statusReason string, statusCondition string) bool { 92 | for _, rs := range rsList.Items { 93 | for _, cond := range rs.Status.Conditions { 94 | if cond.Reason == statusReason && 95 | cond.Type == v1beta1.ReplicaSetReplicaFailure && 96 | strings.Contains(cond.Message, statusCondition) { 97 | return false 98 | } 99 | } 100 | } 101 | log.Println("failed to find the replicaSet for the deployment") 102 | return true 103 | } 104 | 105 | // CreatePod creates kubernetes pod 106 | func CreatePod(clientset kubernetes.Interface, pod *v1.Pod, targetNamespace string) error { 107 | _, err := clientset.CoreV1().Pods(targetNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) 108 | if err != nil { 109 | return errors.New("Failed to create pod: " + err.Error()) 110 | } 111 | return nil 112 | } 113 | 114 | // DeletePod deletes kubernetes pod 115 | func DeletePod(clientset kubernetes.Interface, podName string, targetNamespace string) error { 116 | propagationPolicy := metav1.DeletePropagationForeground 117 | err := clientset.CoreV1().Pods(targetNamespace).Delete(context.Background(), podName, metav1.DeleteOptions{ 118 | PropagationPolicy: &propagationPolicy, 119 | }) 120 | if err != nil && !kerr.IsNotFound(err) { 121 | return errors.New("Failed to delete pod: " + err.Error()) 122 | } 123 | return nil 124 | } 125 | 126 | // CheckReadyReplicas will wait until the resource is fully rolled out with all replicas 127 | func CheckReadyReplicas(clientset kubernetes.Interface, deploymentName string, 128 | targetNamespace string, retryCount int) error { 129 | for i := 0; i <= retryCount; i++ { 130 | deployment, err := clientset.AppsV1().Deployments(targetNamespace). 131 | Get(context.Background(), deploymentName, metav1.GetOptions{}) 132 | if err != nil { 133 | return errors.New("Failed to get deployment: " + err.Error()) 134 | } 135 | log.Println(g.CurrentSpecReport().LeafNodeText + ": waiting for the ready replicas...") 136 | // try every 30 seconds 137 | time.Sleep(30 * time.Second) 138 | 139 | // check if number of ready replica count is matching desired replicas. 140 | if deployment.Status.ReadyReplicas == *deployment.Spec.Replicas { 141 | return nil 142 | } 143 | } 144 | 145 | // return error after multiple attempts 146 | return errors.New("CheckReadyReplicas status failed even after multiple " + 147 | "retryCount for deployment " + deploymentName) 148 | } 149 | 150 | // GetStatusCondition waits until status conditions are availabe for a 151 | // given replica set. If not found after multiple retries, return an error 152 | // otherwise return the replicasetList instance. 153 | func GetStatusCondition(clientset kubernetes.Interface, deploymentName string) (*v1beta1.ReplicaSetList, error) { 154 | retryCount := 3 155 | for i := 0; i <= retryCount; i++ { 156 | rsList, err := ReplicaSetsByLabel(clientset, 157 | "k8s-app="+deploymentName, TargetNamespace) 158 | // fail if any other error happens while fetching replicaset 159 | if err != nil { 160 | log.Println("error from ReplicaSetsByLabel") 161 | return rsList, err 162 | } 163 | log.Printf(g.CurrentSpecReport().LeafNodeText+ 164 | ": waiting for replicaset and status condition to be available "+ 165 | "for deployment: %v\n", deploymentName) 166 | // sleep 30 seconds, wait for replication controller to create replicaset 167 | time.Sleep(30 * time.Second) 168 | if len(rsList.Items) != 0 && len(rsList.Items[0].Status.Conditions) != 0 { 169 | return rsList, nil 170 | } 171 | } 172 | return nil, 173 | errors.New("failed to fetch the replicaset status condition" + 174 | " after multiple attempts") 175 | } 176 | -------------------------------------------------------------------------------- /Code-of-Conduct.md: -------------------------------------------------------------------------------- 1 | # Verizon Media Open Source Code of Conduct 2 | 3 | ## Summary 4 | This Code of Conduct is our way to encourage good behavior and discourage bad behavior in our open source community. We invite participation from many people to bring different perspectives to support this project. We pledge to do our part to foster a welcoming and professional environment free of harassment. We expect participants to communicate professionally and thoughtfully during their involvement with this project. 5 | 6 | Participants may lose their good standing by engaging in misconduct. For example: insulting, threatening, or conveying unwelcome sexual content. We ask participants who observe conduct issues to report the incident directly to the project's Response Team at opensource-conduct@verizonmedia.com. Verizon Media will assign a respondent to address the issue. We may remove harassers from this project. 7 | 8 | This code does not replace the terms of service or acceptable use policies of the websites used to support this project. We acknowledge that participants may be subject to additional conduct terms based on their employment which may govern their online expressions. 9 | 10 | ## Details 11 | This Code of Conduct makes our expectations of participants in this community explicit. 12 | * We forbid harassment and abusive speech within this community. 13 | * We request participants to report misconduct to the project’s Response Team. 14 | * We urge participants to refrain from using discussion forums to play out a fight. 15 | 16 | ### Expected Behaviors 17 | We expect participants in this community to conduct themselves professionally. Since our primary mode of communication is text on an online forum (e.g. issues, pull requests, comments, emails, or chats) devoid of vocal tone, gestures, or other context that is often vital to understanding, it is important that participants are attentive to their interaction style. 18 | 19 | * **Assume positive intent.** We ask community members to assume positive intent on the part of other people’s communications. We may disagree on details, but we expect all suggestions to be supportive of the community goals. 20 | * **Respect participants.** We expect participants will occasionally disagree. Even if we reject an idea, we welcome everyone’s participation. Open Source projects are learning experiences. Ask, explore, challenge, and then respectfully assert if you agree or disagree. If your idea is rejected, be more persuasive not bitter. 21 | * **Welcoming to new members.** New members bring new perspectives. Some may raise questions that have been addressed before. Kindly point them to existing discussions. Everyone is new to every project once. 22 | * **Be kind to beginners.** Beginners use open source projects to get experience. They might not be talented coders yet, and projects should not accept poor quality code. But we were all beginners once, and we need to engage kindly. 23 | * **Consider your impact on others.** Your work will be used by others, and you depend on the work of others. We expect community members to be considerate and establish a balance their self-interest with communal interest. 24 | * **Use words carefully.** We may not understand intent when you say something ironic. Poe’s Law suggests that without an emoticon people will misinterpret sarcasm. We ask community members to communicate plainly. 25 | * **Leave with class.** When you wish to resign from participating in this project for any reason, you are free to fork the code and create a competitive project. Open Source explicitly allows this. Your exit should not be dramatic or bitter. 26 | 27 | ### Unacceptable Behaviors 28 | Participants remain in good standing when they do not engage in misconduct or harassment. To elaborate: 29 | * **Don't be a bigot.** Calling out project members by their identity or background in a negative or insulting manner. This includes, but is not limited to, slurs or insinuations related to protected or suspect classes e.g. race, color, citizenship, national origin, political belief, religion, sexual orientation, gender identity and expression, age, size, culture, ethnicity, genetic features, language, profession, national minority status, mental or physical ability. 30 | * **Don't insult.** Insulting remarks about a person’s lifestyle practices. 31 | * **Don't dox.** Revealing private information about other participants without explicit permission. 32 | * **Don't intimidate.** Threats of violence or intimidation of any project member. 33 | * **Don't creep.** Unwanted sexual attention or content unsuited for the subject of this project. 34 | * **Don't disrupt.** Sustained disruptions in a discussion. 35 | * **Let us help.** Refusal to assist the Response Team to resolve an issue in the community. 36 | 37 | We do not list all forms of harassment, nor imply some forms of harassment are not worthy of action. Any participant who *feels* harassed or *observes* harassment, should report the incident. Victim of harassment should not address grievances in the public forum, as this often intensifies the problem. Report it, and let us address it off-line. 38 | 39 | ### Reporting Issues 40 | If you experience or witness misconduct, or have any other concerns about the conduct of members of this project, please report it by contacting our Response Team at opensource-conduct@verizonmedia.com who will handle your report with discretion. Your report should include: 41 | * Your preferred contact information. We cannot process anonymous reports. 42 | * Names (real or usernames) of those involved in the incident. 43 | * Your account of what occurred, and if the incident is ongoing. Please provide links to or transcripts of the publicly available records (e.g. a mailing list archive or a public IRC logger), so that we can review it. 44 | * Any additional information that may be helpful to achieve resolution. 45 | 46 | After filing a report, a representative will contact you directly to review the incident and ask additional questions. If a member of the Verizon Media Response Team is named in an incident report, that member will be recused from handling your incident. If the complaint originates from a member of the Response Team, it will be addressed by a different member of the Response Team. We will consider reports to be confidential for the purpose of protecting victims of abuse. 47 | 48 | ### Scope 49 | Verizon Media will assign a Response Team member with admin rights on the project and legal rights on the project copyright. The Response Team is empowered to restrict some privileges to the project as needed. Since this project is governed by an open source license, any participant may fork the code under the terms of the project license. The Response Team’s goal is to preserve the project if possible, and will restrict or remove participation from those who disrupt the project. 50 | 51 | This code does not replace the terms of service or acceptable use policies that are provided by the websites used to support this community. Nor does this code apply to communications or actions that take place outside of the context of this community. Many participants in this project are also subject to codes of conduct based on their employment. This code is a social-contract that informs participants of our social expectations. It is not a terms of service or legal contract. 52 | 53 | ## License and Acknowledgment. 54 | This text is shared under the [CC-BY-4.0 license](https://creativecommons.org/licenses/by/4.0/). This code is based on a study conducted by the [TODO Group](https://todogroup.org/) of many codes used in the open source community. If you have feedback about this code, contact our Response Team at the address listed above. 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 2 | github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= 8 | github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 9 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 10 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 11 | github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= 12 | github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= 13 | github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= 14 | github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= 15 | github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= 16 | github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= 17 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 18 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 19 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 20 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 21 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 22 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 23 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 24 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 25 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 26 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 27 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 28 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 29 | github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 30 | github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= 31 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 32 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 33 | github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= 34 | github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 35 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 36 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 37 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 38 | github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= 39 | github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= 40 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 41 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 42 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 43 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 44 | github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= 45 | github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= 46 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 47 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 48 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 49 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 50 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 51 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 52 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 53 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 54 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 55 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 56 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 57 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 58 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 59 | github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= 60 | github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= 61 | github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= 62 | github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= 63 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 64 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 65 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 66 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 67 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= 68 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 69 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 70 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 71 | github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= 72 | github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= 73 | github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= 74 | github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= 75 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 76 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 77 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 78 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 79 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 80 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 81 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 82 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 83 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 84 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 85 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 86 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 87 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 88 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 89 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 90 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 91 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 92 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 93 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 94 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= 95 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 96 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 97 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 98 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 99 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 100 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= 101 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= 102 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 103 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 104 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 105 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 106 | go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= 107 | go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 108 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 109 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 110 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 111 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 112 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 113 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 114 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 115 | golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= 116 | golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= 117 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 118 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 119 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 120 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 121 | golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= 122 | golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 123 | golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= 124 | golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 125 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 126 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 127 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 128 | golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= 129 | golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 130 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 131 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 132 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 134 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 135 | golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= 136 | golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= 137 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 138 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 139 | golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= 140 | golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 141 | golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 142 | golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 143 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 144 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 145 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 146 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 147 | golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= 148 | golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= 149 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 150 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 151 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 152 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 153 | google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= 154 | google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 155 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 156 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 157 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 158 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 159 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 160 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 161 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 162 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 163 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 164 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 165 | k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= 166 | k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= 167 | k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= 168 | k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= 169 | k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= 170 | k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= 171 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 172 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 173 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= 174 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= 175 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= 176 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 177 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 178 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 179 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 180 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 181 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= 182 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= 183 | sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= 184 | sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= 185 | --------------------------------------------------------------------------------