def create_secret(self, secret: AWSSecret): ''' Process the creation of an awssecrets resource ''' try: self.__client.create_secret( Name=secret.get_path(), SecretBinary=json.dumps( secret.get_creation_values()).encode('utf-8')) except self.__client.exceptions.ResourceExistsException: raise ESKException(409, "Path already exists") except Exception as e: raise ESKException(500, e.message)
def update_secret(self, old_secret: ExternalSecret, new_secret: ExternalSecret): ''' Process the update of an externalsecrets resource ''' backend_client = self.__controller.get_backend_client(old_secret.get_backend()) # when creation happens, this handler will skip updating. if old_secret.get_path() is None: return True self.__controller.can_access_secret(old_secret) self.__controller.can_access_secret(new_secret) __trigger_value_change = False old_values = old_secret.get_raw_values() new_values = new_secret.get_raw_values() real_values = None for k, v in new_values.items(): if old_values.get(k) == v: if real_values is None: real_values = backend_client.get_secret(old_secret) if real_values is None: raise ESKException(500, f"Values retrieved for path { old_secret.get_path() } are None") new_values[k] = real_values[k] new_secret.set_real_values(new_values) backend_client.update_secret(__trigger_value_change, old_secret, new_secret) return new_secret
def get_vault_client() -> hvac.Client: addr = os.environ['VAULT_ADDR'] logger.debug(f"Setting up vault connection to { addr }") token = os.environ.get('VAULT_TOKEN') or "/vault/secrets/token" if token is None: raise Exception( "Please specify a path to the vault token either via --token or $VAULT_TOKEN" ) with open(token, "r") as f: token = f.read().rstrip() client = hvac.Client(url=addr, token=token) if client.is_authenticated(): return client else: raise ESKException(500, "Authentication to vault unsuccessful") # def __login_approle(self): # pass # def __login_kubernetes(self): # pass # def __login_token(self): # pass
def grant_access(self, bind: SecretBinding): role, role_binding = bind.to_k8s_resources() try: self.__rbac_api.create_namespaced_role(bind.get_namespace(), role) self.__rbac_api.create_namespaced_role_binding( bind.get_namespace(), role_binding) except kubernetes.client.exceptions.ApiException as e: raise ESKException(e.status, e.reason)
def revoke_access(self, bind: SecretBinding): try: self.__rbac_api.delete_namespaced_role(bind.get_name(), bind.get_namespace()) except kubernetes.client.ApiException as e: if e.status != 404: raise ESKException(e.status, e.reason) else: logger.debug(f"Role { bind.get_name() } did not exist, skip.") try: self.__rbac_api.delete_namespaced_role_binding( bind.get_name(), bind.get_namespace()) except kubernetes.client.ApiException as e: if e.status != 404: raise ESKException(e.status, e.reason) else: logger.debug( f"Role binding { bind.get_name() } did not exist, skip.")
def list_crd(self, crd, namespace): try: return self.__crd_api.list_namespaced_custom_object( 'esk.io', 'v1alpha1', namespace, crd, ).get('items') except kubernetes.client.exceptions.ApiException as e: raise ESKException(e.status, e.reason)
def get_secret(self, secret: VaultSecret): mount_point, path = secret.get_mount_point_and_path() try: values = self.__client.secrets.kv.v2.read_secret_version( path, mount_point=mount_point)["data"]["data"] except hvac.exceptions.InvalidPath: raise ESKException(404, "Secret not found in backend") logger.debug(f"Found secret { path } in vault.") return values
def __init__(self, name, namespace, backend, placeholder, path, values): self.__name = name self.__namespace = namespace self.__backend = backend if not self.__name: raise ESKException(400, "Name cannot be empty") if not self.__namespace: raise ESKException(400, "Namespace cannot be empty") self.__placeholder = placeholder self.__values = values if values is not None else {} self.__real_values = None self.__annotations = None self.__masked_values = {} self.__set_masked_values() if path is None: self.__path = f"{ self.__namespace }-{ self.__name }" else: self.__path = path
def can_access_secret(self, secret: ExternalSecret): allowed = False for spec in self.__clients.get('k8s').list_crd('secretpolicies', secret.get_namespace()): allowed = SecretPolicy( spec.get('name'), spec.get('namespace'), spec.get('allow'), spec.get('reject')).check_path_allowed(secret) if allowed: break if not self.__allow_by_default and not allowed: raise ESKException(403, "Path not allowed.")
def delete_secret(self, secret: AWSSecret): ''' Process the deletion of an awssecrets resource ''' try: if secret.should_delete_instantly(): self.__client.delete_secret(ForceDeleteWithoutRecovery=True, SecretId=secret.get_path()) else: self.__client.delete_secret( RecoveryWindowInDays=secret.get_recovery_days(), SecretId=secret.get_path()) except self.__client.exceptions.InvalidRequestException as e: raise ESKException(500, e.message)
def get_secret_spec(self, name: str, namespace: str): ''' Get the CRD resource from kubernetes ''' api_instance = kubernetes.client.CustomObjectsApi(self.__controller.get_backend_client('k8s')) try: return api_instance.api_instance.get_namespaced_custom_object( 'esk.io', 'v1alpha1', namespace, f"externalsecrets", name ) except kubernetes.client.exceptions.ApiException: raise ESKException(404, f"Secret { namespace }/{ name } could not be found")
def create_secret(self, secret: GCPSecret): ''' Process the creation of an gcpsecrets resource ''' # Build a dict of settings for the secret secret_metadata = {'replication': secret.get_replication()} # Create the secret try: self.__client.create_secret( secret_id=secret.get_path(), parent=f"projects/{ self.__project_id }", secret=secret_metadata) except api_core.exceptions.AlreadyExists as e: raise ESKException(409, "Path already exists") self.__add_secret_version(secret)
def create_secret(self, secret: VaultSecret): ''' Process the creation of a vaultsecrets resource ''' mount_point, path = secret.get_mount_point_and_path() try: self.__client.secrets.kv.v2.create_or_update_secret( path, secret=secret.get_creation_values(), mount_point=mount_point, cas=0) except hvac.exceptions.InvalidRequest as e: raise ESKException(409, "Path already exists") logger.debug(f"Created secret { mount_point }/{ path } in vault.") # policy_name = f"{ namespace }-{ name }" # self.__client.sys.create_or_update_policy(policy_name, f"path \"{ mount_point }/{ path }\" {{\n capabilities = [\"read\"]\n}}\n") # logger.debug(f"Created policy {policy_name}") return f"{ mount_point }/{ path }"
from controller.main import get_controllers from controller.exceptions import ESKException from injector.webhook import mutate from injector.processor import ESKProcessor app = Flask(__name__) app.logger.setLevel(logging.DEBUG) s_controller, sb_controller = get_controllers() processor = ESKProcessor(s_controller) processor_addr = environ.get('PROCESSOR_ADDR') init_image = environ.get('INIT_IMAGE') if processor_addr is None: raise ESKException(500, "Processor addr is invalid.") if init_image is None: raise ESKException(500, "Init image is invalid.") @app.route('/mutate', methods=['POST']) def mutate_webhook(): allowed, patch = mutate(sb_controller, init_image, processor_addr, request.json["request"]) admission_response = { "allowed": allowed, "uid": request.json["request"]["uid"], "patch": patch, "patchtype": "JSONPatch"
def check_can_create(self): if not self.__path: raise ESKException(500, "Secrets require a valid path") if not isinstance(self.__values, dict) or len(self.__values.keys()) == 0: raise ESKException(500, f"Failed to create: values not a valid dict: { self.__values}")