Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for Feature/custom certs #136

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8bf8fd5
pass certs from Kong Admin API to kube-api via gwa-api
rustyjux Nov 13, 2024
85b9fc0
improve checking for custom domain
rustyjux Nov 13, 2024
e760fa8
only get ns certs if custom domain
rustyjux Nov 13, 2024
b7406d6
don't transform custom hosts
rustyjux Nov 13, 2024
5e58882
add tests for gwa-api passing certs
rustyjux Nov 13, 2024
2725064
use custom cert in ocp route in kube-api
rustyjux Nov 13, 2024
268c77f
split lines in PEM
rustyjux Nov 13, 2024
545a159
ensure cert provided for custom domains
rustyjux Nov 15, 2024
42fc5da
add label to k8s route w/ cert id, check with gwa scheduler
rustyjux Nov 15, 2024
7aac554
fix gwa scheduler tests
rustyjux Nov 15, 2024
cde98fd
fix kube-api tests
rustyjux Nov 15, 2024
630cfbd
fix scheduler to use snis from Kong Admin API cache
rustyjux Nov 16, 2024
1b904eb
scheduler: include certificate and sni from admin cache
rustyjux Nov 18, 2024
a67f005
add kubeapi tests
rustyjux Nov 18, 2024
853857c
Merge branch 'dev' of https://github.com/bcgov/gwa-api into feature/c…
rustyjux Nov 18, 2024
5ba7708
add unhappy path tests
rustyjux Nov 18, 2024
af61756
kube: find certs for all routes
rustyjux Nov 20, 2024
ff2344f
scheduler: isolate get certs function
rustyjux Nov 20, 2024
5b43cb8
scheduler: use cert serial number instead of id
rustyjux Nov 20, 2024
0202233
kube: use cert serial number instead of id
rustyjux Nov 20, 2024
27f6006
don't log secrets
rustyjux Nov 21, 2024
9d9a2d5
fix log entry
rustyjux Nov 21, 2024
77415db
debug logging
rustyjux Nov 21, 2024
ac3933b
kube: ssl cert serial set default to none
rustyjux Nov 21, 2024
ae0d6f1
remove debug logging
rustyjux Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions microservices/gatewayApi/clients/kong.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def get_service_routes (service_id):
def get_local_certs_by_ns (ns):
return recurse_get_records ([], "/certificates?tags=gwa.ns.%s" % ns)

def get_public_certs_by_ns (ns):
return recurse_get_records ([], "/certificates?tags=ns.%s" % ns)

def get_acls ():
return recurse_get_records ([], "/acls")

Expand Down
86 changes: 70 additions & 16 deletions microservices/gatewayApi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def get_group_by_path(path, search_in_subgroups):
return {"id": "g002"}
elif path == "/ns/mytest3":
return {"id": "g003"}
elif path == "/ns/customcert":
return {"id": "g004"}
else:
return {"id": "g001"}
def get_group(id):
Expand All @@ -91,6 +93,12 @@ def get_group(id):
"perm-domains": [ ".api.gov.bc.ca", ".cluster.local" ]
}
}
elif id == "g004":
return {
"attributes": {
"perm-domains": [ ".api.gov.bc.ca", ".custom.gov.bc.ca" ]
}
}

mocker.patch("v2.services.namespaces.admin_api", return_value=mock_kc_admin)

Expand All @@ -115,14 +123,35 @@ def json():
return Response
elif (path == 'http://kong/certificates?tags=gwa.ns.mytest' or
path == 'http://kong/certificates?tags=gwa.ns.sescookie' or
path == 'http://kong/certificates?tags=gwa.ns.dclass'):
path == 'http://kong/certificates?tags=gwa.ns.dclass' or
path == 'http://kong/certificates?tags=gwa.ns.customcert'):
class Response:
def json():
return {
"data": [],
"next": None
}
return Response
elif (path == 'http://kong/certificates?tags=ns.customcert'):
class Response:
def json():
return {
"next": None,
"data": [
{
"id": "41d14845-669f-4dcd-aff2-926fb32a4b25",
"snis": [
"test.custom.gov.bc.ca"
],
"tags": [
"ns.customcert",
],
"cert": "CERT",
"key": "KEY"
}
]
}
return Response

else:
raise Exception(path)
Expand Down Expand Up @@ -180,7 +209,8 @@ class Response:
"aps.route.dataclass.high": [],
"aps.route.dataclass.public": []
},
'select_tag': 'ns.sescookie.dev'
'select_tag': 'ns.sescookie.dev',
'certificates': []
}

assert json.dumps(kwargs['json'], sort_keys=True) == json.dumps(matched, sort_keys=True)
Expand All @@ -198,7 +228,39 @@ class Response:
"aps.route.dataclass.high": ['myapi.api.gov.bc.ca'],
"aps.route.dataclass.public": []
},
'select_tag': 'ns.dclass.dev'
'select_tag': 'ns.dclass.dev',
'certificates': []
}

assert json.dumps(kwargs['json'], sort_keys=True) == json.dumps(matched, sort_keys=True)
return Response
elif (url == 'http://kube-api/namespaces/customcert/routes'):
class Response:
status_code = 201
matched = {
'hosts': ['test.custom.gov.bc.ca'],
'ns_attributes': {'perm-domains': ['.api.gov.bc.ca', '.custom.gov.bc.ca']},
'overrides': {
'aps.route.session.cookie.enabled': [],
"aps.route.dataclass.low": [],
"aps.route.dataclass.medium": [],
"aps.route.dataclass.high": [],
"aps.route.dataclass.public": []
},
'select_tag': 'ns.customcert',
'certificates': [
{
"id": "41d14845-669f-4dcd-aff2-926fb32a4b25",
"snis": [
"test.custom.gov.bc.ca"
],
"tags": [
"ns.customcert",
],
"cert": "CERT",
"key": "KEY"
}
]
}

assert json.dumps(kwargs['json'], sort_keys=True) == json.dumps(matched, sort_keys=True)
Expand All @@ -213,24 +275,16 @@ class Response:
raise Exception(url)

def mock_requests_get(self, url, **kwards):
if (url == 'http://kube-api/namespaces/mytest/local_tls'):
class Response:
status_code = 200
def json():
return {}
return Response
elif (url == 'http://kube-api/namespaces/sescookie/local_tls'):
class Response:
status_code = 200
def json():
return {}
return Response
elif (url == 'http://kube-api/namespaces/dclass/local_tls'):
if (url == 'http://kube-api/namespaces/mytest/local_tls' or
url == 'http://kube-api/namespaces/sescookie/local_tls' or
url == 'http://kube-api/namespaces/dclass/local_tls' or
url == 'http://kube-api/namespaces/customcert/local_tls'):
class Response:
status_code = 200
def json():
return {}
return Response

else:
raise Exception(url)

Expand Down
25 changes: 25 additions & 0 deletions microservices/gatewayApi/tests/routes/v2/test_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,31 @@ def test_happy_with_data_class_gateway_call(client):
assert response.status_code == 200
assert json.dumps(response.json) == '{"message": "Sync successful.", "results": "Deck reported no changes"}'

def test_happy_with_custom_domain_gateway_call(client):
configFile = '''
services:
- name: my-service
host: myupstream.local
ca_certificates: [ "0000-0000-0000-0000" ]
certificate: "41d14845-669f-4dcd-aff2-926fb32a4b25"
tags: ["ns.customcert"]
routes:
- name: route-1
hosts: [ test.custom.gov.bc.ca ]
tags: ["ns.customcert"]
plugins:
- name: acl-auth
tags: ["ns.customcert"]
'''

data={
"configFile": configFile,
"dryRun": False
}
response = client.put('/v2/namespaces/customcert/gateway', json=data)
assert response.status_code == 200
assert json.dumps(response.json) == '{"message": "Sync successful.", "results": "Deck reported no changes"}'

def test_success_mtls_reference(client):
configFile = '''
services:
Expand Down
53 changes: 47 additions & 6 deletions microservices/gatewayApi/v2/routes/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from v2.services.namespaces import NamespaceService

from clients.portal import record_gateway_event
from clients.kong import get_routes, register_kong_certs
from clients.kong import get_routes, register_kong_certs, get_public_certs_by_ns
from clients.ocp_gateway_secret import prep_submitted_config
from utils.validators import host_valid, validate_upstream
from utils.transforms import plugins_transformations
Expand Down Expand Up @@ -330,6 +330,15 @@ def write_config(namespace: str) -> object:
try:
if update_routes_flag:
host_list = get_host_list(tempFolder)
certs = []
custom_domain_in_host_list = False
for host in host_list:
if is_host_custom_domain(host):
custom_domain_in_host_list = True
break
if custom_domain_in_host_list:
certs = get_public_certs_by_ns(namespace)
log.debug("[%s] Found %d certs in namespace" % (namespace, len(certs)))
session = requests.Session()
session.headers.update({"Content-Type": "application/json"})
route_payload = {
Expand All @@ -342,9 +351,17 @@ def write_config(namespace: str) -> object:
"aps.route.dataclass.medium": get_route_overrides(tempFolder, "aps.route.dataclass.medium"),
"aps.route.dataclass.high": get_route_overrides(tempFolder, "aps.route.dataclass.high"),
"aps.route.dataclass.public": get_route_overrides(tempFolder, "aps.route.dataclass.public"),
}
},
"certificates": certs
}
log.debug("[%s] - Initiating request to kube API %s" % (dp, route_payload))
# Create a copy without certificates for logging
route_payload_log = {
"hosts": host_list,
"select_tag": selectTag,
"ns_attributes": ns_attributes.getAttrs(),
"overrides": route_payload["overrides"]
}
log.debug("[%s] - Initiating request to kube API %s" % (dp, route_payload_log))
rqst_url = app.config['data_planes'][dp]["kube-api"]
res = session.put(rqst_url + "/namespaces/%s/routes" % namespace, json=route_payload, auth=(
app.config['kubeApiCreds']['kubeApiUser'], app.config['kubeApiCreds']['kubeApiPass']))
Expand Down Expand Up @@ -469,6 +486,8 @@ def host_transformation(namespace, data_plane, yaml):
for host in route['hosts']:
if is_host_local(host):
new_hosts.append(transform_local_host(data_plane, host))
elif is_host_custom_domain(host):
new_hosts.append(host)
elif is_host_transform_enabled():
new_hosts.append(transform_host(host))
transforms = transforms + 1
Expand All @@ -477,10 +496,32 @@ def host_transformation(namespace, data_plane, yaml):
route['hosts'] = new_hosts
log.debug("[%s] Host transformations %d" % (namespace, transforms))

def is_host_local (host):
def is_host_local(host):
return host.endswith(".cluster.local")

def has_namespace_local_host_permission (ns_attributes):
def is_host_custom_domain(host):
log = app.logger

non_custom_suffixes = [
'.cluster.local',
'.api.gov.bc.ca',
'.data.gov.bc.ca',
'.maps.gov.bc.ca',
'.openmaps.gov.bc.ca',
'.apps.gov.bc.ca',
'.apis.gov.bc.ca',
'.test'
]

# Check if the host is one of the standard cert domains or a subdomain of them
for suffix in non_custom_suffixes:
if host == suffix[1:] or host.endswith(suffix):
return False

log.debug("Host %s is custom" % (host))
return True

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):
return True
Expand All @@ -499,7 +540,7 @@ def transform_local_host(data_plane, host):
return "gw-%s.%s.svc.cluster.local" % (name_part, kube_ns)

def transform_host(host):
if is_host_local(host):
if is_host_local(host) or is_host_custom_domain(host):
return host
elif is_host_transform_enabled():
conf = app.config['hostTransformation']
Expand Down
Loading
Loading