From 6eb92b8753d3a7290c36096009200b493c66a03a Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 17 Mar 2023 12:19:01 -0700 Subject: [PATCH 01/10] introduce runtime groups --- microservices/gatewayApi/v2/routes/gateway.py | 33 ++++++++++++++++--- microservices/kubeApi/main.py | 3 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index 24ccd99b..3f4e015a 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -164,6 +164,7 @@ def write_config(namespace: str) -> object: ns_attributes = ns_svc.get_namespace_attributes(namespace) dp = get_data_plane(ns_attributes) + runtime_group_admin = is_allowed_to_manage_runtime_group(ns_attributes) # Build a list of existing hosts that are outside this namespace # They become reserved and any conflict will return an error @@ -235,7 +236,7 @@ def write_config(namespace: str) -> object: ####################### # Transformation route hosts if in non-prod environment (HOST_TRANSFORM_ENABLED) - host_transformation(namespace, dp, gw_config) + host_transformation(namespace, dp, runtime_group_admin, gw_config) # If there is a tag with a pipeline qualifier (i.e./ ns..dev) # then add to tags automatically the tag: ns. @@ -256,6 +257,10 @@ def write_config(namespace: str) -> object: try: validate_base_entities(gw_config, ns_attributes) validate_tags(gw_config, selectTag) + + if runtime_group_admin: + validate_runtime_group_config (yaml, 'dp.%s' % dp) + except Exception as ex: traceback.print_exc() log.error("%s - %s" % (namespace, " Tag Validation Errors: %s" % ex)) @@ -395,7 +400,7 @@ def validate_base_entities(yaml, ns_attributes): traversables = ['_format_version', '_plugin_configs', 'services', 'upstreams', 'certificates', 'caCertificates'] allow_protected_ns = ns_attributes.get('perm-protected-ns', ['deny'])[0] == 'allow' - if allow_protected_ns: + if allow_protected_ns or is_allowed_to_manage_runtime_group(ns_attributes): traversables.append('plugins') for k in yaml: @@ -418,6 +423,20 @@ def validate_tags(yaml, required_tag): if len(errors) != 0: raise Exception('\n'.join(errors)) +def validate_runtime_group_config (errors, yaml, required_tag): + errors = [] + for k in yaml: + if k == 'plugins': + for index, item in enumerate(yaml[k]): + if item['enabled'] is True: + errors.append("%s.%s global plugin must have enabled set to false" % (k, item['name'], required_tag)) + if 'tags' in item: + if required_tag not in item['tags']: + errors.append("%s.%s missing required tag %s" % (k, item['name'], required_tag)) + else: + errors.append("%s.%s no tags found" % (k, item['name'])) + if len(errors) != 0: + raise Exception('\n'.join(errors)) def traverse(source, errors, yaml, required_tag, qualifiers): traversables = ['services', 'routes', 'plugins', 'upstreams', 'consumers', 'certificates', 'caCertificates'] @@ -442,7 +461,7 @@ def traverse(source, errors, yaml, required_tag, qualifiers): traverse("%s.%s.%s" % (source, k, nm), errors, item, required_tag, qualifiers) -def host_transformation(namespace, data_plane, yaml): +def host_transformation(namespace, data_plane, runtime_group_admin, yaml): log = app.logger transforms = 0 @@ -453,7 +472,9 @@ def host_transformation(namespace, data_plane, yaml): if 'hosts' in route: new_hosts = [] for host in route['hosts']: - if is_host_local(host): + if runtime_group_admin: + new_hosts.append(host) + elif is_host_local(host): new_hosts.append(transform_local_host(data_plane, host)) elif is_host_transform_enabled(): new_hosts.append(transform_host(host)) @@ -466,6 +487,10 @@ def host_transformation(namespace, data_plane, yaml): def is_host_local (host): return host.endswith(".cluster.local") +# Is the namespace responsible for configuring the Runtime Group +def is_allowed_to_manage_runtime_group (ns_attributes): + return ns_attributes.get('perm-manage-runtime-group', [''])[0] == 'allow' + def has_namespace_local_host_permission (ns_attributes): for domain in ns_attributes.get('perm-domains', ['.api.gov.bc.ca']): if is_host_local(domain): diff --git a/microservices/kubeApi/main.py b/microservices/kubeApi/main.py index 299ce9b8..b26e3ff7 100644 --- a/microservices/kubeApi/main.py +++ b/microservices/kubeApi/main.py @@ -4,7 +4,7 @@ from fastapi.responses import JSONResponse from fastapi.exceptions import HTTPException from starlette.responses import HTMLResponse -from routers import routes +from routers import routes, noop from config import settings import logging import logging.config @@ -37,6 +37,7 @@ app = FastAPI(title="GWA Kubernetes API", description="Description: API to create resources in Openshift using Kubectl", version="1.0.0") +app.include_router(noop.router) app.include_router(routes.router) logger = logging.getLogger(__name__) From 9e999dae1a0795328db8f9e4f1c7a21ff35ebead Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 17 Mar 2023 12:21:22 -0700 Subject: [PATCH 02/10] inc noop kube --- microservices/kubeApi/routers/noop.py | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 microservices/kubeApi/routers/noop.py diff --git a/microservices/kubeApi/routers/noop.py b/microservices/kubeApi/routers/noop.py new file mode 100644 index 00000000..eb8712fc --- /dev/null +++ b/microservices/kubeApi/routers/noop.py @@ -0,0 +1,29 @@ +from fastapi import APIRouter, Depends, Request +from pydantic.main import BaseModel +from starlette.responses import Response +from auth.basic_auth import verify_credentials + +router = APIRouter( + prefix="/noop", + tags=["routes"], + responses={404: {"description": "Not found"}}, +) + + +class OCPRoute(BaseModel): + hosts: list + select_tag: str + ns_attributes: dict + + +@router.put("/namespaces/{namespace}/routes", status_code=201, dependencies=[Depends(verify_credentials)]) +def add_routes(namespace: str, route: OCPRoute): + return {"message": "created"} + +@router.delete("/namespaces/{namespace}/routes/{name}", status_code=204, dependencies=[Depends(verify_credentials)]) +def delete_route(name: str): + return Response(status_code=204) + +@router.post("/namespaces/{namespace}/routes/sync", status_code=200, dependencies=[Depends(verify_credentials)]) +async def verify_and_create_routes(namespace: str, request: Request): + return Response(status_code=200) From a6346dfab9cdfb6e07e40c3fda26e6718ad647bd Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 17 Mar 2023 16:34:31 -0700 Subject: [PATCH 03/10] fix runtime group validation --- microservices/gatewayApi/v2/routes/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index 3f4e015a..ff034894 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -423,7 +423,7 @@ def validate_tags(yaml, required_tag): if len(errors) != 0: raise Exception('\n'.join(errors)) -def validate_runtime_group_config (errors, yaml, required_tag): +def validate_runtime_group_config (yaml, required_tag): errors = [] for k in yaml: if k == 'plugins': From a6354a2d8669d89a89acd051b1aac2c75cad729f Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 17 Mar 2023 16:34:51 -0700 Subject: [PATCH 04/10] fix runtime group validation --- microservices/gatewayApi/v2/routes/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index ff034894..b5fc8130 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -489,7 +489,7 @@ def is_host_local (host): # Is the namespace responsible for configuring the Runtime Group def is_allowed_to_manage_runtime_group (ns_attributes): - return ns_attributes.get('perm-manage-runtime-group', [''])[0] == 'allow' + return ns_attributes.get('perm-admin-runtime-group', [''])[0] == 'allow' def has_namespace_local_host_permission (ns_attributes): for domain in ns_attributes.get('perm-domains', ['.api.gov.bc.ca']): From 7daca7c7b50e260f8ef94c45557024562a9d1458 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 17 Mar 2023 16:40:34 -0700 Subject: [PATCH 05/10] fix validation --- microservices/gatewayApi/v2/routes/gateway.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index b5fc8130..e34a191d 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -259,7 +259,7 @@ def write_config(namespace: str) -> object: validate_tags(gw_config, selectTag) if runtime_group_admin: - validate_runtime_group_config (yaml, 'dp.%s' % dp) + validate_runtime_group_config (gw_config, dp) except Exception as ex: traceback.print_exc() @@ -423,7 +423,8 @@ def validate_tags(yaml, required_tag): if len(errors) != 0: raise Exception('\n'.join(errors)) -def validate_runtime_group_config (yaml, required_tag): +def validate_runtime_group_config (yaml, dp): + required_tag = 'dp.%s' % dp errors = [] for k in yaml: if k == 'plugins': From 76c2c1c97e34503e2269d905bb93d3bcab3a3e00 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 17 Mar 2023 16:45:37 -0700 Subject: [PATCH 06/10] fix validation --- microservices/gatewayApi/v2/routes/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index e34a191d..c71be06a 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -430,7 +430,7 @@ def validate_runtime_group_config (yaml, dp): if k == 'plugins': for index, item in enumerate(yaml[k]): if item['enabled'] is True: - errors.append("%s.%s global plugin must have enabled set to false" % (k, item['name'], required_tag)) + errors.append("%s.%s global plugin must have enabled set to false" % (k, item['name'])) if 'tags' in item: if required_tag not in item['tags']: errors.append("%s.%s missing required tag %s" % (k, item['name'], required_tag)) From 9d3139cc65c70d0e27a34d45888ac8931796329b Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Sat, 18 Mar 2023 08:24:38 -0700 Subject: [PATCH 07/10] upd validate hosts --- microservices/gatewayApi/v2/routes/gateway.py | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index c71be06a..01a37e59 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -174,10 +174,9 @@ def write_config(namespace: str) -> object: for route in all_routes: if tag_match not in route['tags'] and 'hosts' in route: for host in route['hosts']: - reserved_hosts.append(transform_host(host)) + reserved_hosts.append(host) reserved_hosts = list(set(reserved_hosts)) - dfile = None if 'configFile' in request.files and not request.files['configFile'].filename == '': @@ -236,7 +235,7 @@ def write_config(namespace: str) -> object: ####################### # Transformation route hosts if in non-prod environment (HOST_TRANSFORM_ENABLED) - host_transformation(namespace, dp, runtime_group_admin, gw_config) + host_transformation(runtime_group_admin, gw_config) # If there is a tag with a pipeline qualifier (i.e./ ns..dev) # then add to tags automatically the tag: ns. @@ -462,10 +461,7 @@ def traverse(source, errors, yaml, required_tag, qualifiers): traverse("%s.%s.%s" % (source, k, nm), errors, item, required_tag, qualifiers) -def host_transformation(namespace, data_plane, runtime_group_admin, yaml): - log = app.logger - - transforms = 0 +def host_transformation(runtime_group_admin, yaml): if 'services' in yaml: for service in yaml['services']: if 'routes' in service: @@ -473,17 +469,8 @@ def host_transformation(namespace, data_plane, runtime_group_admin, yaml): if 'hosts' in route: new_hosts = [] for host in route['hosts']: - if runtime_group_admin: - new_hosts.append(host) - elif is_host_local(host): - new_hosts.append(transform_local_host(data_plane, host)) - elif is_host_transform_enabled(): - new_hosts.append(transform_host(host)) - transforms = transforms + 1 - else: - new_hosts.append(host) + new_hosts.append(transform_host(runtime_group_admin, host)) route['hosts'] = new_hosts - log.debug("[%s] Host transformations %d" % (namespace, transforms)) def is_host_local (host): return host.endswith(".cluster.local") @@ -511,8 +498,10 @@ def transform_local_host(data_plane, host): name_part = host[:-suffix_len] return "gw-%s.%s.svc.cluster.local" % (name_part, kube_ns) -def transform_host(host): - if is_host_local(host): +def transform_host(runtime_group_admin, host): + if runtime_group_admin: + return host + elif is_host_local(host): return host elif is_host_transform_enabled(): conf = app.config['hostTransformation'] @@ -577,6 +566,8 @@ def update_routes_check(yaml): def validate_hosts(yaml, reserved_hosts, ns_attributes): errors = [] + runtime_group_admin = is_allowed_to_manage_runtime_group(ns_attributes) + allowed_domains = [] for domain in ns_attributes.get('perm-domains', ['.api.gov.bc.ca']): allowed_domains.append("%s" % domain) @@ -595,7 +586,7 @@ def validate_hosts(yaml, reserved_hosts, ns_attributes): errors.append("Host not passing DNS-952 validation '%s'" % host) if validate_local_host(host) is False: errors.append("Host failed validation for data plane '%s'" % host) - if host_ends_with_one_of_list(host, allowed_domains) is False: + if host_ends_with_one_of_list(runtime_group_admin, host, allowed_domains) is False: errors.append("Host invalid: %s %s. Route hosts must end with one of [%s] for this namespace." % ( route['name'], host, ','.join(allowed_domains))) else: @@ -606,9 +597,9 @@ def validate_hosts(yaml, reserved_hosts, ns_attributes): raise Exception('\n'.join(errors)) -def host_ends_with_one_of_list(a_str, a_list): +def host_ends_with_one_of_list(runtime_group_admin, a_str, a_list): for item in a_list: - if a_str.endswith(transform_host(item)): + if a_str.endswith(transform_host(runtime_group_admin, item)): return True return False From 5b035966645a2cd7be6f393556d02e81ebeafd6e Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Sat, 18 Mar 2023 08:42:09 -0700 Subject: [PATCH 08/10] upd validate hosts --- microservices/gatewayApi/v2/routes/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index 01a37e59..63cf0cb7 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -579,7 +579,7 @@ def validate_hosts(yaml, reserved_hosts, ns_attributes): for route in service['routes']: if 'hosts' in route: for host in route['hosts']: - if host in reserved_hosts: + if transform_host(runtime_group_admin, host) in reserved_hosts: errors.append("service.%s.route.%s The host is already used in another namespace '%s'" % ( service['name'], route['name'], host)) if host_valid(host) is False: From 1c05b5e988d9f8ca7f1eb8c27ed91f9d02471d2e Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Sat, 18 Mar 2023 08:44:59 -0700 Subject: [PATCH 09/10] upd validate hosts --- microservices/gatewayApi/v2/routes/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index 63cf0cb7..b1bf5fb4 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -549,7 +549,7 @@ def validate_upstream_host(_host, errors, allow_protected_ns, protected_kube_nam errors.append("service upstream is invalid (e2)") elif partials[1] in protected_kube_namespaces and allow_protected_ns is False: errors.append("service upstream is invalid (e3)") - if host.endswith('svc.cluster.local'): + elif host.endswith('svc.cluster.local'): partials = host.split('.') # get the namespace, and make sure it is not in the protected_kube_namespaces list if len(partials) != 5: From 94bfd18b1028fb614b44aae2e403ca90ac77be7e Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Mon, 20 Mar 2023 15:56:12 -0700 Subject: [PATCH 10/10] allow restricted for runtime group --- microservices/gatewayApi/v2/routes/gateway.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/microservices/gatewayApi/v2/routes/gateway.py b/microservices/gatewayApi/v2/routes/gateway.py index b1bf5fb4..a9fcf0ca 100644 --- a/microservices/gatewayApi/v2/routes/gateway.py +++ b/microservices/gatewayApi/v2/routes/gateway.py @@ -513,6 +513,7 @@ def validate_upstream(yaml, ns_attributes, protected_kube_namespaces): errors = [] allow_protected_ns = ns_attributes.get('perm-protected-ns', ['deny'])[0] == 'allow' + runtime_group_admin = is_allowed_to_manage_runtime_group(ns_attributes) # A host must not contain a list of protected if 'services' in yaml: @@ -523,24 +524,24 @@ def validate_upstream(yaml, ns_attributes, protected_kube_namespaces): if u.hostname is None: errors.append("service upstream has invalid url specified (e1)") else: - validate_upstream_host(u.hostname, errors, allow_protected_ns, protected_kube_namespaces) + validate_upstream_host(u.hostname, errors, runtime_group_admin, allow_protected_ns, protected_kube_namespaces) except Exception as e: errors.append("service upstream has invalid url specified (e2)") if 'host' in service: host = service["host"] - validate_upstream_host(host, errors, allow_protected_ns, protected_kube_namespaces) + validate_upstream_host(host, errors, runtime_group_admin, allow_protected_ns, protected_kube_namespaces) if len(errors) != 0: raise Exception('\n'.join(errors)) -def validate_upstream_host(_host, errors, allow_protected_ns, protected_kube_namespaces): +def validate_upstream_host(_host, errors, runtime_group_admin, allow_protected_ns, protected_kube_namespaces): host = _host.lower() restricted = ['localhost', '127.0.0.1', '0.0.0.0'] - if host in restricted: + if host in restricted and runtime_group_admin is False: errors.append("service upstream is invalid (e1)") if host.endswith('svc'): partials = host.split('.')