[FIX] hw_drivers: HTTPS certificate info on IoT homepage

HTTPS certificate IoT issues can be complicated to troubleshoot
as the information are not visible/given.
This PR aim to share this information on the IoT box homepage.
As there is a lot of possible causes for a given problem,
a code is used that will be explained/detailed in Odoo's
IoT documentation:
https://github.com/odoo/documentation/pull/3818

OPW-3227004

closes odoo/odoo#116650

X-original-commit: 8bc6b2d0033676507f95b74b3ae383ab17164203
Signed-off-by: Loan (LSE) <lse@odoo.com>
Signed-off-by: Sens Loan (lse) <lse@odoo.com>
This commit is contained in:
Loan (LSE)
2023-03-27 12:47:20 +02:00
parent 5d40181178
commit a6a7cd83f9
5 changed files with 134 additions and 47 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ class DriverController(http.Controller):
This route is called when we want to check if certificate is up-to-date
Used in cron.daily
"""
helpers.check_certificate()
helpers.get_certificate_status()
@http.route('/hw_drivers/event', type='json', auth='none', cors='*', csrf=False, save_session=False)
def event(self, listener):
+4 -1
View File
@@ -80,7 +80,10 @@ class Manager(Thread):
helpers.start_nginx_server()
if platform.system() == 'Linux':
helpers.check_git_branch()
helpers.check_certificate()
is_certificate_ok, certificate_details = helpers.get_certificate_status()
if not is_certificate_ok:
_logger.warning("An error happened when trying to get the HTTPS certificate: %s",
certificate_details)
# We first add the IoT Box to the connected DB because IoT handlers cannot be downloaded if
# the identifier of the Box is not found in the DB. So add the Box to the DB.
+102 -45
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from enum import Enum
from importlib import util
import platform
import io
@@ -29,6 +30,13 @@ _logger = logging.getLogger(__name__)
# Helper
#----------------------------------------------------------
class CertificateStatus(Enum):
OK = 1
NEED_REFRESH = 2
ERROR = 3
class IoTRestart(Thread):
"""
Thread to restart odoo server in IoT Box when we must return a answer before
@@ -73,27 +81,42 @@ def start_nginx_server():
def check_certificate():
"""
Check if the current certificate is up to date or not authenticated
:return CheckCertificateStatus
"""
server = get_odoo_server_url()
if server:
if platform.system() == 'Windows':
path = Path(get_path_nginx()).joinpath('conf/nginx-cert.crt')
elif platform.system() == 'Linux':
path = Path('/etc/ssl/certs/nginx-cert.crt')
if path.exists():
with path.open('r') as f:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
cert_end_date = datetime.datetime.strptime(cert.get_notAfter().decode('utf-8'), "%Y%m%d%H%M%SZ") - datetime.timedelta(days=10)
for key in cert.get_subject().get_components():
if key[0] == b'CN':
cn = key[1].decode('utf-8')
if cn == 'OdooTempIoTBoxCertificate' or datetime.datetime.now() > cert_end_date:
_logger.info(_('Your certificate %s must be updated') % (cn))
load_certificate()
else:
_logger.info(_('Your certificate %s is valid until %s') % (cn, cert_end_date))
else:
load_certificate()
if not server:
return {"status": CertificateStatus.ERROR,
"error_code": "ERR_IOT_HTTPS_CHECK_NO_SERVER"}
if platform.system() == 'Windows':
path = Path(get_path_nginx()).joinpath('conf/nginx-cert.crt')
elif platform.system() == 'Linux':
path = Path('/etc/ssl/certs/nginx-cert.crt')
if not path.exists():
return {"status": CertificateStatus.NEED_REFRESH}
try:
with path.open('r') as f:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
except EnvironmentError:
_logger.exception("Unable to read certificate file")
return {"status": CertificateStatus.ERROR,
"error_code": "ERR_IOT_HTTPS_CHECK_CERT_READ_EXCEPTION"}
cert_end_date = datetime.datetime.strptime(cert.get_notAfter().decode('utf-8'), "%Y%m%d%H%M%SZ") - datetime.timedelta(days=10)
for key in cert.get_subject().get_components():
if key[0] == b'CN':
cn = key[1].decode('utf-8')
if cn == 'OdooTempIoTBoxCertificate' or datetime.datetime.now() > cert_end_date:
message = _('Your certificate %s must be updated') % (cn)
_logger.info(message)
return {"status": CertificateStatus.NEED_REFRESH}
else:
message = _('Your certificate %s is valid until %s') % (cn, cert_end_date)
_logger.info(message)
return {"status": CertificateStatus.OK, "message": message}
def check_git_branch():
"""
@@ -165,6 +188,27 @@ def save_conf_server(url, token, db_uuid, enterprise_code):
write_file('odoo-db-uuid.conf', db_uuid or '')
write_file('odoo-enterprise-code.conf', enterprise_code or '')
def get_certificate_status(is_first=True):
"""
Will get the HTTPS certificate details if present. Will load the certificate if missing.
:param is_first: Use to make sure that the recursion happens only once
:return: (bool, str)
"""
check_certificate_result = check_certificate()
certificateStatus = check_certificate_result["status"]
if certificateStatus == CertificateStatus.ERROR:
return False, check_certificate_result["error_code"]
if certificateStatus == CertificateStatus.NEED_REFRESH and is_first:
certificate_process = load_certificate()
if certificate_process is not True:
return False, certificate_process
return get_certificate_status(is_first=False) # recursive call to attempt certificate read
return True, check_certificate_result.get("message",
"The HTTPS certificate was generated correctly")
def get_img_name():
major, minor = get_version().split('.')
return 'iotboxv%s_%s.zip' % (major, minor)
@@ -228,39 +272,52 @@ def load_certificate():
"""
db_uuid = read_file_first_line('odoo-db-uuid.conf')
enterprise_code = read_file_first_line('odoo-enterprise-code.conf')
if db_uuid and enterprise_code:
url = 'https://www.odoo.com/odoo-enterprise/iot/x509'
data = {
'params': {
'db_uuid': db_uuid,
'enterprise_code': enterprise_code
}
if not (db_uuid and enterprise_code):
return "ERR_IOT_HTTPS_LOAD_NO_CREDENTIAL"
url = 'https://www.odoo.com/odoo-enterprise/iot/x509'
data = {
'params': {
'db_uuid': db_uuid,
'enterprise_code': enterprise_code
}
urllib3.disable_warnings()
http = urllib3.PoolManager(cert_reqs='CERT_NONE')
}
urllib3.disable_warnings()
http = urllib3.PoolManager(cert_reqs='CERT_NONE', retries=urllib3.Retry(4))
try:
response = http.request(
'POST',
url,
body = json.dumps(data).encode('utf8'),
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
)
result = json.loads(response.data.decode('utf8'))['result']
if result:
write_file('odoo-subject.conf', result['subject_cn'])
if platform.system() == 'Linux':
with writable():
Path('/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/root_bypass_ramdisks/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
Path('/root_bypass_ramdisks/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
elif platform.system() == 'Windows':
Path(get_path_nginx()).joinpath('conf/nginx-cert.crt').write_text(result['x509_pem'])
Path(get_path_nginx()).joinpath('conf/nginx-cert.key').write_text(result['private_key_pem'])
time.sleep(3)
if platform.system() == 'Windows':
odoo_restart(0)
elif platform.system() == 'Linux':
start_nginx_server()
except Exception as e:
_logger.exception("An error occurred while trying to reach odoo.com servers.")
return "ERR_IOT_HTTPS_LOAD_REQUEST_EXCEPTION\n\n%s" % e
if response.status != 200:
return "ERR_IOT_HTTPS_LOAD_REQUEST_STATUS %s\n\n%s" % (response.status, response.reason)
result = json.loads(response.data.decode('utf8'))['result']
if not result:
return "ERR_IOT_HTTPS_LOAD_REQUEST_NO_RESULT"
write_file('odoo-subject.conf', result['subject_cn'])
if platform.system() == 'Linux':
with writable():
Path('/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/root_bypass_ramdisks/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem'])
Path('/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
Path('/root_bypass_ramdisks/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem'])
elif platform.system() == 'Windows':
Path(get_path_nginx()).joinpath('conf/nginx-cert.crt').write_text(result['x509_pem'])
Path(get_path_nginx()).joinpath('conf/nginx-cert.key').write_text(result['private_key_pem'])
time.sleep(3)
if platform.system() == 'Windows':
odoo_restart(0)
elif platform.system() == 'Linux':
start_nginx_server()
return True
def download_iot_handlers(auto=True):
"""
@@ -76,6 +76,8 @@ class IoTboxHomepage(Home):
else:
network = 'Not Connected'
is_certificate_ok, certificate_details = helpers.get_certificate_status()
iot_device = []
for device in iot_devices:
iot_device.append({
@@ -95,6 +97,8 @@ class IoTboxHomepage(Home):
'network_status': network,
'version': helpers.get_version(),
'system': platform.system(),
'is_certificate_ok': is_certificate_ok,
'certificate_details': certificate_details,
}
@http.route()
@@ -89,6 +89,11 @@
position: absolute;
right: 0;
}
.warn-tr {
color: #856404;
background-color: #fff3cd;
border: 2px solid #f3e4ce;
}
</style>
<script>
$(document).ready(function () {
@@ -189,6 +194,24 @@
<td class="heading">Server</td>
<td><a href='{{ server_status }}' target=_blank>{{ server_status }}<a class="btn btn-sm float-end" href='/server'>configure</a></td>
</tr>
<tr class="{{ 'warn-tr' if not is_certificate_ok }}">
<td class="heading">HTTPS certificate</td>
<td>
{% if is_certificate_ok %}
<details>
<summary>OK</summary>
<code>{{ certificate_details }}</code>
</details>
{% else %}
Error code:
{% set error_code = certificate_details.split(' ') | first | replace("_", "-") | lower %}
{% set doc_url = 'https://www.odoo.com/documentation/master/applications/productivity/iot/config/https_certificate_iot.html#' ~ error_code %}
<a target="_blank" class="btn btn-sm float-end" href="{{ doc_url }}">help</a>
<br/>
<code style="white-space: pre-wrap;">{{ certificate_details }}</code>
{% endif %}
</td>
</tr>
{% if server_status != "Not Configured" %}
<tr>
<td class="heading">Six payment terminal</td>