Fix SSL certificate setting error

When initializing k8s client in InfraDriverV2, the SSL CA
certificate is set incorrectly.

To fix the issue, the following modifies are made in this patch:

* A temp file for ssl_ca_cert is created before initializing
  k8s client and the temp file path is set to k8s_config.ssl_ca_cert,
* The temp file is deleted until the lifetime of k8s client ends.
Note: This references the implementation in InfraDriverV1.

If set the ssl_ca_cert in instantiate request, the validation of
request is failed because of the length of ssl_ca_cert exceeds 1024.
For this issue, add a new type `keyvalue_pairs_no_length_limit`
which has no max length limitation to verify the request.
And the interfaceInfo, accessInfo, extra are all set to the new type
for unity.

In Zuul test environment, when registering default vim, ssl_ca_cert
is not set. So the case with ssl_ca_cert is not tested.
In this patch ssl_ca_cert is set into the default vim.

Closes-Bug: #1979413
Change-Id: I61dbd70690b737a72fc619e5a08b4bab51160a27
This commit is contained in:
Qibin Yao 2022-07-07 11:33:46 +09:00
parent 8a82d758f1
commit 98d3f4bf31
10 changed files with 207 additions and 77 deletions

View File

@ -522,6 +522,7 @@
controller_worker:
amp_active_retries: 9999
kuryr_k8s_api_url: "https://{{ hostvars['controller-k8s']['nodepool']['private_ipv4'] }}:6443"
k8s_ssl_verify: true
helm_version: "3.5.4"
test_matrix_configs: [neutron]
zuul_work_dir: src/opendev.org/openstack/tacker

View File

@ -97,6 +97,14 @@
become: yes
become_user: stack
- name: Fetch k8s's CA Certificate
fetch:
src: "/etc/kubernetes/pki/ca.crt"
dest: "/tmp/"
flat: true
when:
- k8s_ssl_verify
when:
- inventory_hostname == 'controller-k8s'
- kuryr_k8s_api_url is defined
@ -171,6 +179,32 @@
when:
- p.stat.exists
- name: Copy k8s's CA Certificate to tacker
copy:
src: "/tmp/ca.crt"
dest: "/tmp/"
when:
- p.stat.exists
- k8s_ssl_verify
- name: Get k8s's CA Certificate
command: cat "/tmp/ca.crt"
register: ssl_ca_cert
when:
- p.stat.exists
- k8s_ssl_verify
- name: Replace k8s CA Certificate in local-k8s-vim.yaml
replace:
path: "{{ item }}"
regexp: "ssl_ca_cert: .*$"
replace: "ssl_ca_cert: '{{ ssl_ca_cert.stdout }}'"
with_items:
- "{{ zuul_work_dir }}/tacker/tests/etc/samples/local-k8s-vim.yaml"
when:
- p.stat.exists
- k8s_ssl_verify
- name: Replace the config file path in the test-setup-k8s-vim.sh
replace:
path: "{{ zuul_work_dir }}/tools/test-setup-k8s-vim.sh"

View File

@ -131,6 +131,23 @@ keyvalue_pairs = {
'additionalProperties': False
}
keyvalue_pairs_no_length_limit = {
'type': 'object',
'patternProperties': {
'^[a-zA-Z0-9-_:. /]+$': {
'anyOf': [
{'type': 'array'},
{'type': 'string'},
{'type': 'object'},
{'type': 'null'},
{'type': 'boolean'},
{'type': 'number'}
]
}
},
'additionalProperties': False
}
description = {
'type': 'string', 'minLength': 0, 'maxLength': 1024,
'pattern': valid_description_regex,

View File

@ -61,9 +61,9 @@ VimConnectionInfo = {
'properties': {
'vimId': {'type': 'string', 'maxLength': 255},
'vimType': {'type': 'string', 'minLength': 1, 'maxLength': 255},
'interfaceInfo': parameter_types.keyvalue_pairs,
'accessInfo': parameter_types.keyvalue_pairs,
'extra': parameter_types.keyvalue_pairs,
'interfaceInfo': parameter_types.keyvalue_pairs_no_length_limit,
'accessInfo': parameter_types.keyvalue_pairs_no_length_limit,
'extra': parameter_types.keyvalue_pairs_no_length_limit,
},
'required': ['vimType'],
'additionalProperties': True,

View File

@ -63,17 +63,26 @@ class Kubernetes(object):
# deploy k8s resources with sorted resources
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
created_k8s_reses = k8s_client.create_k8s_resource(
sorted_k8s_reses, namespace)
# This is Context Manager for creation and deletion
# of CA certificate temp file
with kubernetes_utils.CaCertFileContextManager(
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm:
# wait k8s resource create complete
k8s_client.wait_k8s_res_create(created_k8s_reses)
# add an item ca_cert_file:file_path into vim_info.interfaceInfo,
# and will be deleted in KubernetesClient
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path
# make instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._make_cnf_instantiated_info(
req, inst, vnfd, namespace, created_k8s_reses, all_pods)
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
created_k8s_reses = k8s_client.create_k8s_resource(
sorted_k8s_reses, namespace)
# wait k8s resource create complete
k8s_client.wait_k8s_res_create(created_k8s_reses)
# make instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._make_cnf_instantiated_info(
req, inst, vnfd, namespace, created_k8s_reses, all_pods)
def terminate(self, req, inst, grant_req, grant, vnfd):
target_k8s_files = inst.metadata.get('lcm-kubernetes-def-files')
@ -88,11 +97,20 @@ class Kubernetes(object):
# delete k8s resources with sorted resources
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
k8s_client.delete_k8s_resource(req, sorted_k8s_reses, namespace)
# This is Context Manager for creation and deletion
# of CA certificate temp file
with kubernetes_utils.CaCertFileContextManager(
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm:
# wait k8s resource delete complete
k8s_client.wait_k8s_res_delete(sorted_k8s_reses, namespace)
# add an item ca_cert_file:file_path into vim_info.interfaceInfo,
# and will be deleted in KubernetesClient
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
k8s_client.delete_k8s_resource(req, sorted_k8s_reses, namespace)
# wait k8s resource delete complete
k8s_client.wait_k8s_res_delete(sorted_k8s_reses, namespace)
def change_vnfpkg(self, req, inst, grant_req, grant, vnfd):
if req.additionalParams.get('upgrade_type') == 'RollingUpdate':
@ -103,43 +121,56 @@ class Kubernetes(object):
# check deployment exists in kubernetes
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
k8s_client.check_deployment_exist(deployment_names, namespace)
# This is Context Manager for creation and deletion
# of CA certificate temp file
with kubernetes_utils.CaCertFileContextManager(
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm:
# get new deployment body
new_deploy_reses = kubernetes_utils.get_new_deployment_body(
req, inst, vnfd, deployment_names, operation='CHANGE_VNFPKG')
# add an item ca_cert_file:file_path
# into vim_info.interfaceInfo,
# and will be deleted in KubernetesClient
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path
# apply new deployment
k8s_client.update_k8s_resource(new_deploy_reses, namespace)
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
k8s_client.check_deployment_exist(deployment_names, namespace)
# wait k8s resource update complete
old_pods_names = [vnfc.computeResource.resourceId for vnfc in
inst.instantiatedVnfInfo.vnfcResourceInfo]
try:
k8s_client.wait_k8s_res_update(
new_deploy_reses, namespace, old_pods_names)
except sol_ex.UpdateK8SResourceFailed as ex:
# get new deployment body
new_deploy_reses = kubernetes_utils.get_new_deployment_body(
req, inst, vnfd, deployment_names,
operation='CHANGE_VNFPKG')
# apply new deployment
k8s_client.update_k8s_resource(new_deploy_reses, namespace)
# wait k8s resource update complete
old_pods_names = [vnfc.computeResource.resourceId for vnfc in
inst.instantiatedVnfInfo.vnfcResourceInfo]
try:
k8s_client.wait_k8s_res_update(
new_deploy_reses, namespace, old_pods_names)
except sol_ex.UpdateK8SResourceFailed as ex:
self._update_cnf_instantiated_info(
inst, deployment_names,
k8s_client.list_namespaced_pods(
namespace=namespace))
raise ex
# execute coordinate vnf script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd, 'CHANGE_VNFPKG',
namespace, new_deploy_reses)
except sol_ex.CoordinateVNFExecutionFailed as ex:
self._update_cnf_instantiated_info(
inst, deployment_names,
k8s_client.list_namespaced_pods(
namespace=namespace))
raise ex
# update cnf instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._update_cnf_instantiated_info(
inst, deployment_names, k8s_client.list_namespaced_pods(
namespace=namespace))
raise ex
# execute coordinate vnf script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd, 'CHANGE_VNFPKG',
namespace, new_deploy_reses)
except sol_ex.CoordinateVNFExecutionFailed as ex:
self._update_cnf_instantiated_info(
inst, deployment_names, k8s_client.list_namespaced_pods(
namespace=namespace))
raise ex
# update cnf instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._update_cnf_instantiated_info(
inst, deployment_names, all_pods)
inst, deployment_names, all_pods)
else:
# TODO(YiFeng): Blue-Green type will be supported in next version.
@ -169,31 +200,41 @@ class Kubernetes(object):
# apply old deployment
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
k8s_client.update_k8s_resource(old_deploy_reses, namespace)
# This is Context Manager for creation and deletion
# of CA certificate temp file
with kubernetes_utils.CaCertFileContextManager(
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm:
# wait k8s resource update complete
old_pods_names = [vnfc.computeResource.resourceId for vnfc in
inst.instantiatedVnfInfo.vnfcResourceInfo]
try:
k8s_client.wait_k8s_res_update(
old_deploy_reses, namespace, old_pods_names)
except sol_ex.UpdateK8SResourceFailed as ex:
raise ex
# add an item ca_cert_file:file_path
# into vim_info.interfaceInfo,
# and will be deleted in KubernetesClient
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path
# execute coordinate vnf script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd,
'CHANGE_VNFPKG_ROLLBACK',
namespace, old_deploy_reses)
except sol_ex.CoordinateVNFExecutionFailed as ex:
raise ex
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
k8s_client.update_k8s_resource(old_deploy_reses, namespace)
# update cnf instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._update_cnf_instantiated_info(
inst, deployment_names, all_pods)
# wait k8s resource update complete
old_pods_names = [vnfc.computeResource.resourceId for vnfc in
inst.instantiatedVnfInfo.vnfcResourceInfo]
try:
k8s_client.wait_k8s_res_update(
old_deploy_reses, namespace, old_pods_names)
except sol_ex.UpdateK8SResourceFailed as ex:
raise ex
# execute coordinate vnf script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd,
'CHANGE_VNFPKG_ROLLBACK',
namespace, old_deploy_reses)
except sol_ex.CoordinateVNFExecutionFailed as ex:
raise ex
# update cnf instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._update_cnf_instantiated_info(
inst, deployment_names, all_pods)
else:
# TODO(YiFeng): Blue-Green type will be supported in next version.

View File

@ -17,6 +17,7 @@ import copy
import ipaddress
import os
import re
import tempfile
import time
from urllib.parse import urlparse
import urllib.request as urllib2
@ -583,6 +584,9 @@ def init_k8s_api_client(vim_info):
k8s_config = client.Configuration()
k8s_config.host = vim_info.interfaceInfo['endpoint']
ca_cert_file = (vim_info.interfaceInfo.pop('ca_cert_file')
if 'ca_cert_file' in vim_info.interfaceInfo else None)
if ('username' in vim_info.accessInfo and 'password'
in vim_info.accessInfo and vim_info.accessInfo.get(
'password') is not None):
@ -596,8 +600,8 @@ def init_k8s_api_client(vim_info):
k8s_config.api_key['authorization'] = vim_info.accessInfo[
'bearer_token']
if 'ssl_ca_cert' in vim_info.accessInfo:
k8s_config.ssl_ca_cert = vim_info.accessInfo['ssl_ca_cert']
if 'ssl_ca_cert' in vim_info.interfaceInfo and ca_cert_file:
k8s_config.ssl_ca_cert = ca_cert_file
k8s_config.verify_ssl = True
else:
k8s_config.verify_ssl = False
@ -724,3 +728,27 @@ def get_new_deployment_body(
new_deploy_reses.append(k8s_res)
return new_deploy_reses
class CaCertFileContextManager:
def __init__(self, ca_cert_str):
self._file_descriptor = None
self.file_path = None
self.ca_cert_str = ca_cert_str
def __enter__(self):
if not self.ca_cert_str:
return self
self._file_descriptor, self.file_path = tempfile.mkstemp()
ca_cert = re.sub(r'\s', '\n', self.ca_cert_str)
ca_cert = re.sub(r'BEGIN\nCERT', r'BEGIN CERT', ca_cert)
ca_cert = re.sub(r'END\nCERT', r'END CERT', ca_cert)
# write ca cert file
os.write(self._file_descriptor, ca_cert.encode())
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
if not self.ca_cert_str:
return
os.close(self._file_descriptor)
os.remove(self.file_path)

View File

@ -49,6 +49,7 @@ class BaseVnfLcmKubernetesV2Test(base.BaseTestCase):
k8s_vim_info = cls.get_k8s_vim_info()
cls.auth_url = k8s_vim_info.interfaceInfo['endpoint']
cls.bearer_token = k8s_vim_info.accessInfo['bearer_token']
cls.ssl_ca_cert = k8s_vim_info.interfaceInfo['ssl_ca_cert']
vim_info = cls.get_vim_info()
auth = http_client.KeystonePasswordAuthHandle(
@ -85,7 +86,10 @@ class BaseVnfLcmKubernetesV2Test(base.BaseTestCase):
vim_params = yaml.safe_load(base_utils.read_file('local-k8s-vim.yaml'))
vim_info = objects.VimConnectionInfo(
interfaceInfo={'endpoint': vim_params['auth_url']},
interfaceInfo={
'endpoint': vim_params['auth_url'],
'ssl_ca_cert': vim_params.get('ssl_ca_cert')
},
accessInfo={
'region': 'RegionOne',
'bearer_token': vim_params['bearer_token']

View File

@ -47,7 +47,7 @@ def test_instantiate_cnf_resources_terminate():
}
def max_sample_instantiate(auth_url, bearer_token):
def max_sample_instantiate(auth_url, bearer_token, ssl_ca_cert=None):
# All attributes are set.
# NOTE: All of the following cardinality attributes are set.
# In addition, 0..N or 1..N attributes are set to 2 or more.
@ -78,6 +78,9 @@ def max_sample_instantiate(auth_url, bearer_token):
},
"extra": {"dummy-key": "dummy-val"}
}
if ssl_ca_cert:
vim_1["interfaceInfo"]["ssl_ca_cert"] = ssl_ca_cert
vim_2["interfaceInfo"]["ssl_ca_cert"] = ssl_ca_cert
return {
"flavourId": "simple",
"vimConnectionInfo": {

View File

@ -98,7 +98,7 @@ class VnfLcmKubernetesTest(base_v2.BaseVnfLcmKubernetesV2Test):
# 2. Instantiate a VNF instance
instantiate_req = paramgen.max_sample_instantiate(
self.auth_url, self.bearer_token)
self.auth_url, self.bearer_token, ssl_ca_cert=self.ssl_ca_cert)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)

View File

@ -146,7 +146,7 @@ class TestKubernetes(base.BaseTestCase):
_instantiate_req_example)
req.additionalParams['lcm-kubernetes-def-files'].append(
'Files/kubernetes/namespace.yaml')
req.vimConnectionInfo['vim1']['interfaceInfo']['ssl_ca_cert '] = 'test'
req.vimConnectionInfo['vim1']['interfaceInfo']['ssl_ca_cert'] = 'test'
req.additionalParams['namespace'] = 'curry'
inst = objects.VnfInstanceV2(
vimConnectionInfo=req.vimConnectionInfo
@ -1009,6 +1009,7 @@ class TestKubernetes(base.BaseTestCase):
# prepare instantiate
req = objects.InstantiateVnfRequest.from_dict(
_instantiate_req_example)
req.vimConnectionInfo['vim1']['interfaceInfo']['ssl_ca_cert'] = 'test'
inst = objects.VnfInstanceV2(
vimConnectionInfo=req.vimConnectionInfo
)
@ -1302,6 +1303,7 @@ class TestKubernetes(base.BaseTestCase):
# prepare instantiate
req = objects.InstantiateVnfRequest.from_dict(
_instantiate_req_example)
req.vimConnectionInfo['vim1']['interfaceInfo']['ssl_ca_cert'] = 'test'
inst = objects.VnfInstanceV2(
id=uuidutils.generate_uuid(),
vimConnectionInfo=req.vimConnectionInfo