Skip to content

Commit

Permalink
chore: added k8s validation on user inputs (#536)
Browse files Browse the repository at this point in the history
* chore: added k8s validation on user inputs

* fix: linter complain on imports order

* chore: unit tests added for validation funcs
  • Loading branch information
mojtaba-esk authored Aug 8, 2024
1 parent c0f1c64 commit edf5f29
Show file tree
Hide file tree
Showing 18 changed files with 758 additions and 31 deletions.
10 changes: 10 additions & 0 deletions pkg/k8s/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ func (c *Client) CreateConfigMap(
ctx context.Context, name string,
labels, data map[string]string,
) (*v1.ConfigMap, error) {
if err := validateConfigMapName(name); err != nil {
return nil, err
}
if err := validateLabels(labels); err != nil {
return nil, err
}
if err := validateConfigMapKeys(data); err != nil {
return nil, err
}

exists, err := c.ConfigMapExists(ctx, name)
if err != nil {
return nil, err
Expand Down
9 changes: 9 additions & 0 deletions pkg/k8s/custom_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ func (c *Client) CreateCustomResource(
gvr *schema.GroupVersionResource,
obj *map[string]interface{},
) error {
if err := validateCustomResourceName(name); err != nil {
return err
}
if err := validateGroupVersionResource(gvr); err != nil {
return err
}
if err := validateCustomResourceObject(obj); err != nil {
return err
}
res := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": gvr.GroupVersion().String(),
Expand Down
18 changes: 18 additions & 0 deletions pkg/k8s/daemonset.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ func (c *Client) CreateDaemonSet(
initContainers []v1.Container,
containers []v1.Container,
) (*appv1.DaemonSet, error) {
if err := validateDaemonSetName(name); err != nil {
return nil, err
}
if err := validateLabels(labels); err != nil {
return nil, err
}
if err := validateContainers(containers); err != nil {
return nil, err
}
ds := prepareDaemonSet(c.namespace, name, labels, initContainers, containers)
created, err := c.clientset.AppsV1().DaemonSets(c.namespace).Create(ctx, ds, metav1.CreateOptions{})
if err != nil {
Expand All @@ -50,6 +59,15 @@ func (c *Client) UpdateDaemonSet(ctx context.Context,
initContainers []v1.Container,
containers []v1.Container,
) (*appv1.DaemonSet, error) {
if err := validateDaemonSetName(name); err != nil {
return nil, err
}
if err := validateLabels(labels); err != nil {
return nil, err
}
if err := validateContainers(containers); err != nil {
return nil, err
}
ds := prepareDaemonSet(c.namespace, name, labels, initContainers, containers)
updated, err := c.clientset.AppsV1().DaemonSets(c.namespace).Update(ctx, ds, metav1.UpdateOptions{})
if err != nil {
Expand Down
34 changes: 34 additions & 0 deletions pkg/k8s/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,38 @@ var (
ErrCheckingServiceReady = errors.New("CheckingServiceReady", "failed to check if service %s is ready")
ErrWaitingForPodDeletion = errors.New("WaitingForPodDeletion", "waiting for pod %s to be deleted")
ErrWaitingForReplicaSetDeletion = errors.New("WaitingForReplicaSetDeletion", "waiting for ReplicaSet %s to be deleted")
ErrInvalidNamespaceName = errors.New("InvalidNamespaceName", "invalid namespace name %s: %v")
ErrInvalidConfigMapName = errors.New("InvalidConfigMapName", "invalid config map name %s: %v")
ErrInvalidLabelKey = errors.New("InvalidLabelKey", "invalid label key %s: %v")
ErrInvalidLabelValue = errors.New("InvalidLabelValue", "invalid label value for key %s: %v")
ErrInvalidConfigMapKey = errors.New("InvalidConfigMapKey", "invalid config map key %s: %v")
ErrInvalidCustomResourceName = errors.New("InvalidCustomResourceName", "invalid custom resource name %s: %v")
ErrInvalidGroupVersionResource = errors.New("InvalidGroupVersionResource", "invalid group version resource, group: %s, version: %s, resource: %s")
ErrCustomResourceObjectNil = errors.New("CustomResourceObjectNil", "custom resource object cannot be nil")
ErrCustomResourceObjectNoSpec = errors.New("CustomResourceObjectNoSpec", "custom resource object must have a 'spec' field")
ErrInvalidDaemonSetName = errors.New("InvalidDaemonSetName", "invalid DaemonSet name %s: %v")
ErrInvalidContainerName = errors.New("InvalidContainerName", "invalid container name %s: %v")
ErrInvalidNetworkPolicyName = errors.New("InvalidNetworkPolicyName", "invalid network policy name %s: %v")
ErrInvalidPodName = errors.New("InvalidPodName", "invalid pod name %s: %v")
ErrEmptyCommand = errors.New("EmptyCommand", "command cannot be empty")
ErrInvalidPort = errors.New("InvalidPort", "port number %d is out of valid range (1-65535)")
ErrInvalidPodAnnotationKey = errors.New("InvalidPodAnnotationKey", "invalid annotation key %s: %v")
ErrAnnotationValueTooLarge = errors.New("AnnotationValueTooLarge", "annotation value for key %s exceeds maximum size. %d must be less than 253 characters")
ErrContainerImageEmpty = errors.New("ContainerImageEmpty", "container image cannot be empty for container %s")
ErrVolumePathEmpty = errors.New("VolumePathEmpty", "volume path cannot be empty")
ErrVolumeSizeZero = errors.New("VolumeSizeZero", "volume size must be greater than zero")
ErrFileSourceDestEmpty = errors.New("FileSourceDestEmpty", "file source and destination cannot be empty")
ErrInvalidPVCName = errors.New("InvalidPVCName", "invalid PVC name %s: %v")
ErrPVCSizeZero = errors.New("PVCSizeZero", "PVC size must be greater than zero")
ErrInvalidReplicaSetName = errors.New("InvalidReplicaSetName", "invalid ReplicaSet name %s: %v")
ErrReplicaSetReplicasNegative = errors.New("ReplicaSetReplicasNegative", "number of replicas cannot be negative: %d")
ErrInvalidRoleName = errors.New("InvalidRoleName", "invalid role name %s: %v")
ErrPolicyRuleNoVerbs = errors.New("PolicyRuleNoVerbs", "policy rule must have at least one verb")
ErrPolicyRuleVerbEmpty = errors.New("PolicyRuleVerbEmpty", "verb cannot be empty")
ErrPolicyRuleNoResources = errors.New("PolicyRuleNoResources", "policy rule must specify at least one resource or non-resource URL")
ErrInvalidClusterRoleName = errors.New("InvalidClusterRoleName", "invalid cluster role name %s: %v")
ErrInvalidRoleBindingName = errors.New("InvalidRoleBindingName", "invalid role binding name %s: %v")
ErrInvalidServiceAccountName = errors.New("InvalidServiceAccountName", "invalid service account name %s: %v")
ErrInvalidClusterRoleBindingName = errors.New("InvalidClusterRoleBindingName", "invalid cluster role binding name %s: %v")
ErrInvalidServiceName = errors.New("InvalidServiceName", "invalid service name %s: %v")
)
4 changes: 4 additions & 0 deletions pkg/k8s/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
)

func (c *Client) CreateNamespace(ctx context.Context, name string) error {
if err := validateNamespace(name); err != nil {
return err
}

namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Expand Down
6 changes: 6 additions & 0 deletions pkg/k8s/networkpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ func (c *Client) CreateNetworkPolicy(
ingressSelectorMap,
egressSelectorMap map[string]string,
) error {
if err := validateNetworkPolicyName(name); err != nil {
return err
}
if err := validateSelectorMap(selectorMap); err != nil {
return err
}
var ingress []v1.NetworkPolicyIngressRule
if ingressSelectorMap != nil {
ingress = []v1.NetworkPolicyIngressRule{
Expand Down
24 changes: 24 additions & 0 deletions pkg/k8s/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ type File struct {

// DeployPod creates a new pod in the namespace that k8s client is initiate with if it doesn't already exist.
func (c *Client) DeployPod(ctx context.Context, podConfig PodConfig, init bool) (*v1.Pod, error) {
if err := validatePodConfig(podConfig); err != nil {
return nil, err
}

pod := c.preparePod(podConfig, init)
createdPod, err := c.clientset.CoreV1().Pods(c.namespace).Create(ctx, pod, metav1.CreateOptions{})
if err != nil {
Expand Down Expand Up @@ -166,6 +170,16 @@ func (c *Client) RunCommandInPod(
containerName string,
cmd []string,
) (string, error) {
if err := validatePodName(podName); err != nil {
return "", err
}
if err := validateContainerName(containerName); err != nil {
return "", err
}
if err := validateCommand(cmd); err != nil {
return "", err
}

_, err := c.getPod(ctx, podName)
if err != nil {
return "", ErrGettingPod.WithParams(podName).Wrap(err)
Expand Down Expand Up @@ -245,6 +259,16 @@ func (c *Client) PortForwardPod(
localPort,
remotePort int,
) error {
if err := validatePodName(podName); err != nil {
return err
}
if err := validatePort(localPort); err != nil {
return err
}
if err := validatePort(remotePort); err != nil {
return err
}

if _, err := c.getPod(ctx, podName); err != nil {
return ErrGettingPod.WithParams(podName).Wrap(err)
}
Expand Down
30 changes: 17 additions & 13 deletions pkg/k8s/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,22 @@ func (s *TestSuite) TestDeployPod() {
{
name: "successful creation",
podConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
ContainerConfig: testContainerConfig,
},
init: false,
setupMock: func() {},
init: false,
expectedErr: nil,
},
{
name: "client error",
podConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
ContainerConfig: testContainerConfig,
},
init: false,
setupMock: func() {
Expand Down Expand Up @@ -77,9 +79,10 @@ func (s *TestSuite) TestReplacePod() {
{
name: "successful replacement",
podConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
ContainerConfig: testContainerConfig,
},
setupMock: func() {
s.client.Clientset().(*fake.Clientset).
Expand All @@ -93,9 +96,10 @@ func (s *TestSuite) TestReplacePod() {
{
name: "client error on deletion",
podConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
ContainerConfig: testContainerConfig,
},
setupMock: func() {
err := s.createPod("error-pod")
Expand Down
10 changes: 10 additions & 0 deletions pkg/k8s/pvc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ func (c *Client) CreatePersistentVolumeClaim(
labels map[string]string,
size resource.Quantity,
) error {
if err := validatePVCName(name); err != nil {
return err
}
if err := validatePVCSize(size); err != nil {
return err
}
if err := validateLabels(labels); err != nil {
return err
}

pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: c.namespace,
Expand Down
3 changes: 3 additions & 0 deletions pkg/k8s/replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type ReplicaSetConfig struct {

// CreateReplicaSet creates a new replicaSet in namespace that k8s is initialized with if it doesn't already exist.
func (c *Client) CreateReplicaSet(ctx context.Context, rsConfig ReplicaSetConfig, init bool) (*appv1.ReplicaSet, error) {
if err := validateReplicaSetConfig(rsConfig); err != nil {
return nil, err
}
rsConfig.Namespace = c.namespace
rs := c.prepareReplicaSet(rsConfig, init)

Expand Down
42 changes: 24 additions & 18 deletions pkg/k8s/replicaset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ func (s *TestSuite) TestCreateReplicaSet() {
Labels: map[string]string{"app": "test"},
Replicas: 1,
PodConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
ContainerConfig: testContainerConfig,
},
},
init: false,
Expand All @@ -46,9 +47,10 @@ func (s *TestSuite) TestCreateReplicaSet() {
Labels: map[string]string{"app": "error"},
Replicas: 1,
PodConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
ContainerConfig: testContainerConfig,
},
},
init: false,
Expand Down Expand Up @@ -97,9 +99,10 @@ func (s *TestSuite) TestReplaceReplicaSetWithGracePeriod() {
Labels: map[string]string{"app": "test"},
Replicas: 1,
PodConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
ContainerConfig: testContainerConfig,
},
},
gracePeriod: &gracePeriod,
Expand All @@ -117,9 +120,10 @@ func (s *TestSuite) TestReplaceReplicaSetWithGracePeriod() {
Labels: map[string]string{"app": "error"},
Replicas: 1,
PodConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
ContainerConfig: testContainerConfig,
},
},
gracePeriod: &gracePeriod,
Expand Down Expand Up @@ -171,9 +175,10 @@ func (s *TestSuite) TestReplaceReplicaSet() {
Labels: map[string]string{"app": "test"},
Replicas: 1,
PodConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
Namespace: s.namespace,
Name: "test-pod",
Labels: map[string]string{"app": "test"},
ContainerConfig: testContainerConfig,
},
},
setupMock: func() {
Expand All @@ -190,9 +195,10 @@ func (s *TestSuite) TestReplaceReplicaSet() {
Labels: map[string]string{"app": "error"},
Replicas: 1,
PodConfig: k8s.PodConfig{
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
Namespace: s.namespace,
Name: "error-pod",
Labels: map[string]string{"app": "error"},
ContainerConfig: testContainerConfig,
},
},
setupMock: func() {
Expand Down
20 changes: 20 additions & 0 deletions pkg/k8s/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ func (c *Client) CreateRole(
labels map[string]string,
policyRules []rbacv1.PolicyRule,
) error {
if err := validateRoleName(name); err != nil {
return err
}
if err := validateLabels(labels); err != nil {
return err
}
if err := validatePolicyRules(policyRules); err != nil {
return err
}

role := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Expand All @@ -37,6 +47,16 @@ func (c *Client) CreateClusterRole(
labels map[string]string,
policyRules []rbacv1.PolicyRule,
) error {
if err := validateClusterRoleName(name); err != nil {
return err
}
if err := validateLabels(labels); err != nil {
return err
}
if err := validatePolicyRules(policyRules); err != nil {
return err
}

_, err := c.clientset.RbacV1().ClusterRoles().Get(ctx, name, metav1.GetOptions{})
if err == nil || !errors.IsNotFound(err) {
return ErrClusterRoleAlreadyExists.WithParams(name).Wrap(err)
Expand Down
Loading

0 comments on commit edf5f29

Please sign in to comment.