Skip to content

Commit

Permalink
Merge pull request #94 from axel7083/feature/keys-support
Browse files Browse the repository at this point in the history
feat: support keys in valueFrom to filter secrets and global improvements
  • Loading branch information
axel7083 authored Sep 18, 2023
2 parents fcb98b9 + 3fe7da5 commit 77602f4
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 112 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/e2e-testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ on: [push, pull_request]
jobs:
lint-test:
runs-on: ubuntu-latest
strategy:
matrix:
kind-node-images:
- kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
- kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb
- kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8
- kindest/node:v1.24.15@sha256:7db4f8bea3e14b82d12e044e25e34bd53754b7f2b0e9d56df21774e6f66a70ab

steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down Expand Up @@ -42,6 +50,8 @@ jobs:

- name: Create kind cluster
uses: helm/[email protected]
with:
node_image: ${{ matrix.kind-node-images }}

- name: Loading locally build image to kind cluster
run: kind load docker-image cluster-secret:${{ github.sha }} --name=chart-testing
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# ClusterSecret
![CI](https://github.com/zakkg3/ClusterSecret/workflows/CI/badge.svg) [![Docker Repository on Quay](https://quay.io/repository/clustersecret/clustersecret/status "Docker Repository on Quay")](https://quay.io/repository/clustersecret/clustersecret) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/clutersecret)](https://artifacthub.io/packages/search?repo=clutersecret) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4283/badge)](https://bestpractices.coreinfrastructure.org/projects/4283) [![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)

![CI](https://github.com/zakkg3/ClusterSecret/workflows/CI/badge.svg) [![Docker Repository on Quay](https://quay.io/repository/clustersecret/clustersecret/status "Docker Repository on Quay")](https://quay.io/repository/clustersecret/clustersecret) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/clutersecret)](https://artifacthub.io/packages/search?repo=clutersecret) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4283/badge)](https://bestpractices.coreinfrastructure.org/projects/4283) [![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) [![Kubernetes - v1.24.15 | v1.25.11 | v1.26.6 | v1.27.3](https://img.shields.io/static/v1?label=Kubernetes&message=v1.24.15+|+v1.25.11+|+v1.26.6+|+v1.27.3&color=2ea44f)](https://)
---

## Kubernetes ClusterSecret
Expand Down
51 changes: 48 additions & 3 deletions conformance/k8s_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import time
from typing import Dict, Optional, List, Callable
from typing import Dict, Optional, List, Callable, Any
from kubernetes import client, config
from kubernetes.client import V1Secret, CoreV1Api, CustomObjectsApi
from kubernetes.client.rest import ApiException
Expand Down Expand Up @@ -55,16 +55,57 @@ def __init__(self, custom_objects_api: CustomObjectsApi, api_instance: CoreV1Api
self.retry_attempts = 3
self.retry_delay = 5

def create_secret(
self,
name: str,
namespace: str,
data: Dict[str, Any],
labels: Optional[Dict[str, str]] = None,
annotations: Optional[Dict[str, str]] = None,
):
self.api_instance.create_namespaced_secret(
namespace=namespace,
body=client.V1Secret(
metadata=client.V1ObjectMeta(
name=name,
labels=labels,
annotations=annotations,
),
data=data,
),
)

@staticmethod
def _generate_secret_key_ref_dict(secret_key_ref: Dict[str, str]) -> Dict[str, Any]:
if secret_key_ref.get('name', None) is None or secret_key_ref.get('namespace', None) is None:
raise Exception(f'secretKeyRef dict should have a name and a namespace property defined.')

return (
{
"valueFrom": {
"secretKeyRef": {
"name": secret_key_ref.get('name'),
"namespace": secret_key_ref.get('namespace'),
"keys": secret_key_ref.get('keys'),
},
},
}
)

def create_cluster_secret(
self,
name: str,
namespace: str,
data: Dict[str, str],
data: Optional[Dict[str, Any]] = None,
secret_key_ref: Optional[Dict[str, str]] = None,
labels: Optional[Dict[str, str]] = None,
annotations: Optional[Dict[str, str]] = None,
match_namespace: Optional[List[str]] = None,
avoid_namespaces: Optional[List[str]] = None,
):
if data is None and secret_key_ref is None:
raise Exception('You need to either define data or secret_key_ref.')

return self.custom_objects_api.create_namespaced_custom_object(
group="clustersecret.io",
version="v1",
Expand All @@ -73,7 +114,7 @@ def create_cluster_secret(
"apiVersion": "clustersecret.io/v1",
"kind": "ClusterSecret",
"metadata": {"name": name, "labels": labels, "annotations": annotations},
"data": data,
"data": data if data is not None else self._generate_secret_key_ref_dict(secret_key_ref),
"matchNamespace": match_namespace,
"avoidNamespaces": avoid_namespaces,
},
Expand Down Expand Up @@ -169,3 +210,7 @@ def retry(self, f: Callable[[], bool]) -> bool:
sleep(self.retry_delay)
self.retry_attempts -= 1
return False

def cleanup(self):
# TODO: cleanup all secrets and cluster secrets created.
pass
133 changes: 96 additions & 37 deletions conformance/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import unittest

from kubernetes import client, config
from kubernetes.client.rest import ApiException

Expand All @@ -17,6 +16,18 @@


class ClusterSecretCases(unittest.TestCase):

def setUp(self) -> None:
self.cluster_secret_manager = ClusterSecretManager(
custom_objects_api=custom_objects_api,
api_instance=api_instance
)
super().setUp()

def tearDown(self) -> None:
self.cluster_secret_manager.cleanup()
super().tearDown()

@classmethod
def setUpClass(cls) -> None:
# Wait for the cluster secret pod to be ready before running tests
Expand All @@ -33,6 +44,7 @@ def setUpClass(cls) -> None:
print(f"Namespace '{namespace_name}' already exists.")
else:
print(f"Error creating namespace '{namespace_name}': {e}")

super().setUpClass()

def test_running(self):
Expand All @@ -42,20 +54,16 @@ def test_running(self):
def test_simple_cluster_secret(self):
name = "simple-cluster-secret"
username_data = "MTIzNDU2Cg=="
cluster_secret_manager = ClusterSecretManager(
custom_objects_api=custom_objects_api,
api_instance=api_instance
)

cluster_secret_manager.create_cluster_secret(
self.cluster_secret_manager.create_cluster_secret(
name=name,
namespace=USER_NAMESPACES[0],
data={"username": username_data}
)

# We expect the secret to be in ALL namespaces
self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": username_data},
)
Expand All @@ -64,13 +72,9 @@ def test_simple_cluster_secret(self):
def test_complex_cluster_secret(self):
name = "complex-cluster-secret"
username_data = "MTIzNDU2Cg=="
cluster_secret_manager = ClusterSecretManager(
custom_objects_api=custom_objects_api,
api_instance=api_instance
)

# Create a secret in all user namespace expect the first one
cluster_secret_manager.create_cluster_secret(
self.cluster_secret_manager.create_cluster_secret(
name=name,
namespace=USER_NAMESPACES[0],
data={"username": username_data},
Expand All @@ -80,7 +84,7 @@ def test_complex_cluster_secret(self):

# Ensure the secrets is only present where is to suppose to be
self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": username_data},
namespaces=USER_NAMESPACES[1:],
Expand All @@ -91,36 +95,32 @@ def test_patch_cluster_secret_data(self):
name = "dynamic-cluster-secret"
username_data = "MTIzNDU2Cg=="
updated_data = "Nzg5MTAxMTIxMgo="
cluster_secret_manager = ClusterSecretManager(
custom_objects_api=custom_objects_api,
api_instance=api_instance
)

# Create a secret with username_data
cluster_secret_manager.create_cluster_secret(
self.cluster_secret_manager.create_cluster_secret(
name=name,
namespace=USER_NAMESPACES[0],
data={"username": username_data},
)

# Ensure the secret is created with the right data
self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": username_data},
)
)

# Update the cluster secret's data
cluster_secret_manager.update_data_cluster_secret(
self.cluster_secret_manager.update_data_cluster_secret(
name=name,
data={"username": updated_data},
namespace=USER_NAMESPACES[0],
)

# Ensure the secrets are updated with the right data (at some point)
self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": updated_data},
),
Expand All @@ -130,12 +130,8 @@ def test_patch_cluster_secret_data(self):
def test_patch_cluster_secret_match_namespaces(self):
name = "dynamic-cluster-secret-match-namespaces"
username_data = "MTIzNDU2Cg=="
cluster_secret_manager = ClusterSecretManager(
custom_objects_api=custom_objects_api,
api_instance=api_instance
)

cluster_secret_manager.create_cluster_secret(
self.cluster_secret_manager.create_cluster_secret(
name=name,
namespace=USER_NAMESPACES[0],
data={"username": username_data},
Expand All @@ -145,7 +141,7 @@ def test_patch_cluster_secret_match_namespaces(self):
)

self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": username_data},
namespaces=[
Expand All @@ -156,15 +152,15 @@ def test_patch_cluster_secret_match_namespaces(self):
)

# Update the cluster match_namespace to ALL user namespace
cluster_secret_manager.update_data_cluster_secret(
self.cluster_secret_manager.update_data_cluster_secret(
name=name,
namespace=USER_NAMESPACES[0],
match_namespace=USER_NAMESPACES,
data={"username": username_data},
)

self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": username_data},
namespaces=USER_NAMESPACES,
Expand All @@ -175,40 +171,103 @@ def test_patch_cluster_secret_match_namespaces(self):
def test_simple_cluster_secret_deleted(self):
name = "simple-cluster-secret-deleted"
username_data = "MTIzNDU2Cg=="
cluster_secret_manager = ClusterSecretManager(
custom_objects_api=custom_objects_api,
api_instance=api_instance
)

cluster_secret_manager.create_cluster_secret(
self.cluster_secret_manager.create_cluster_secret(
name=name,
namespace=USER_NAMESPACES[0],
data={"username": username_data}
)

# We expect the secret to be in ALL namespaces
self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": username_data}
)
)

cluster_secret_manager.delete_cluster_secret(
self.cluster_secret_manager.delete_cluster_secret(
name=name,
namespace=USER_NAMESPACES[0],
)

# We expect the secret to be in NO namespaces
self.assertTrue(
cluster_secret_manager.validate_namespace_secrets(
self.cluster_secret_manager.validate_namespace_secrets(
name=name,
data={"username": username_data},
namespaces=[],
),
f'secret {name} should be deleted from all namespaces.'
)

def test_value_from_cluster_secret(self):
cluster_secret_name = "value-from-cluster-secret"
secret_name = "basic-secret-example"

username_data = "MTIzNDU2Cg=="

# Create a kubernetes secrets
self.cluster_secret_manager.create_secret(
name=secret_name,
namespace=USER_NAMESPACES[0],
data={'username': username_data}
)

# Create the cluster secret
self.cluster_secret_manager.create_cluster_secret(
name=cluster_secret_name,
namespace=USER_NAMESPACES[0],
secret_key_ref={
'name': secret_name,
'namespace': USER_NAMESPACES[0],
},
)

# We expect the secret to be in ALL namespaces
self.assertTrue(
self.cluster_secret_manager.validate_namespace_secrets(
name=cluster_secret_name,
data={"username": username_data},
),
msg=f'Cluster secret should take the data from the {secret_name} secret.'
)

def test_value_from_with_keys_cluster_secret(self):
cluster_secret_name = "value-from-with-keys-cluster-secret"
secret_name = "k8s-basic-secret-example"

username_data = "MTIzNDU2Cg=="
password_data = "aGloaXBhc3M="
more_data = "aWlpaWlhYWE="

# Create a kubernetes secrets
self.cluster_secret_manager.create_secret(
name=secret_name,
namespace=USER_NAMESPACES[0],
data={'username': username_data, 'password': password_data, 'more-data': more_data}
)

# Create the cluster secret
self.cluster_secret_manager.create_cluster_secret(
name=cluster_secret_name,
namespace=USER_NAMESPACES[0],
secret_key_ref={
'name': secret_name,
'namespace': USER_NAMESPACES[0],
'keys': ['username', 'password']
},
)

# We expect the secret to be in ALL namespaces
self.assertTrue(
self.cluster_secret_manager.validate_namespace_secrets(
name=cluster_secret_name,
data={'username': username_data, 'password': password_data},
),
msg=f'Cluster secret should take the data from the {secret_name} secret but only the keys specified.'
)


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 77602f4

Please sign in to comment.