2020-02-11 06:47:55 +01:00
|
|
|
import os
|
|
|
|
import logging
|
|
|
|
from pathlib import Path
|
|
|
|
from shutil import copytree, rmtree
|
|
|
|
|
|
|
|
from g import internal_tls_dir, DEFAULT_GID, DEFAULT_UID, PG_GID, PG_UID
|
2020-11-17 13:14:11 +01:00
|
|
|
from utils.misc import check_permission, owner_can_read, get_realpath, port_number_valid
|
2021-01-27 10:16:31 +01:00
|
|
|
from utils.cert import san_existed
|
2020-02-11 06:47:55 +01:00
|
|
|
|
|
|
|
class InternalTLS:
|
|
|
|
|
|
|
|
harbor_certs_filename = {
|
|
|
|
'harbor_internal_ca.crt',
|
|
|
|
'proxy.crt', 'proxy.key',
|
|
|
|
'core.crt', 'core.key',
|
|
|
|
'job_service.crt', 'job_service.key',
|
|
|
|
'registryctl.crt', 'registryctl.key',
|
2020-04-28 07:17:24 +02:00
|
|
|
'registry.crt', 'registry.key',
|
|
|
|
'portal.crt', 'portal.key'
|
2020-02-11 06:47:55 +01:00
|
|
|
}
|
|
|
|
|
2020-03-16 16:20:05 +01:00
|
|
|
trivy_certs_filename = {
|
|
|
|
'trivy_adapter.crt', 'trivy_adapter.key',
|
|
|
|
}
|
|
|
|
|
2020-02-11 06:47:55 +01:00
|
|
|
notary_certs_filename = {
|
|
|
|
'notary_signer.crt', 'notary_signer.key',
|
|
|
|
'notary_server.crt', 'notary_server.key'
|
|
|
|
}
|
|
|
|
|
|
|
|
chart_museum_filename = {
|
|
|
|
'chartmuseum.crt',
|
|
|
|
'chartmuseum.key'
|
|
|
|
}
|
|
|
|
|
|
|
|
db_certs_filename = {
|
|
|
|
'harbor_db.crt', 'harbor_db.key'
|
|
|
|
}
|
|
|
|
|
2020-03-16 16:20:05 +01:00
|
|
|
def __init__(self, tls_enabled=False, verify_client_cert=False, tls_dir='', data_volume='', **kwargs):
|
2020-02-11 06:47:55 +01:00
|
|
|
self.data_volume = data_volume
|
2020-03-16 16:20:05 +01:00
|
|
|
self.verify_client_cert = verify_client_cert
|
|
|
|
self.enabled = tls_enabled
|
|
|
|
self.tls_dir = tls_dir
|
|
|
|
if self.enabled:
|
2020-02-11 06:47:55 +01:00
|
|
|
self.required_filenames = self.harbor_certs_filename
|
|
|
|
if kwargs.get('with_notary'):
|
|
|
|
self.required_filenames.update(self.notary_certs_filename)
|
|
|
|
if kwargs.get('with_chartmuseum'):
|
|
|
|
self.required_filenames.update(self.chart_museum_filename)
|
2020-03-17 10:30:25 +01:00
|
|
|
if kwargs.get('with_trivy'):
|
|
|
|
self.required_filenames.update(self.trivy_certs_filename)
|
2020-02-11 06:47:55 +01:00
|
|
|
if not kwargs.get('external_database'):
|
|
|
|
self.required_filenames.update(self.db_certs_filename)
|
|
|
|
|
|
|
|
def __getattribute__(self, name: str):
|
|
|
|
"""
|
|
|
|
Make the call like 'internal_tls.core_crt_path' possible
|
|
|
|
"""
|
|
|
|
# only handle when enabled tls and name ends with 'path'
|
|
|
|
if name.endswith('_path'):
|
|
|
|
if not (self.enabled):
|
|
|
|
return object.__getattribute__(self, name)
|
|
|
|
|
|
|
|
name_parts = name.split('_')
|
|
|
|
if len(name_parts) < 3:
|
|
|
|
return object.__getattribute__(self, name)
|
|
|
|
|
|
|
|
filename = '{}.{}'.format('_'.join(name_parts[:-2]), name_parts[-2])
|
|
|
|
|
|
|
|
if filename in self.required_filenames:
|
|
|
|
return os.path.join(self.data_volume, 'secret', 'tls', filename)
|
|
|
|
|
|
|
|
return object.__getattribute__(self, name)
|
|
|
|
|
|
|
|
def _check(self, filename: str):
|
|
|
|
"""
|
2021-01-27 10:16:31 +01:00
|
|
|
Check cert and key files are correct
|
2020-02-11 06:47:55 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
path = Path(os.path.join(internal_tls_dir, filename))
|
|
|
|
|
|
|
|
if not path.exists:
|
|
|
|
if filename == 'harbor_internal_ca.crt':
|
|
|
|
return
|
|
|
|
raise Exception('File {} not exist'.format(filename))
|
|
|
|
|
|
|
|
if not path.is_file:
|
|
|
|
raise Exception('invalid {}'.format(filename))
|
|
|
|
|
|
|
|
# check key file permission
|
|
|
|
if filename.endswith('.key') and not check_permission(path, mode=0o600):
|
|
|
|
raise Exception('key file {} permission is not 600'.format(filename))
|
|
|
|
|
2021-01-27 10:16:31 +01:00
|
|
|
# check certificate file
|
|
|
|
if filename.endswith('.crt'):
|
|
|
|
if not owner_can_read(path.stat().st_mode):
|
|
|
|
# check owner can read cert file
|
2020-02-11 06:47:55 +01:00
|
|
|
raise Exception('File {} should readable by owner'.format(filename))
|
2021-01-27 10:16:31 +01:00
|
|
|
if not san_existed(path):
|
|
|
|
# check SAN included
|
|
|
|
if filename == 'harbor_internal_ca.crt':
|
|
|
|
return
|
|
|
|
raise Exception('cert file {} should include SAN'.format(filename))
|
|
|
|
|
2021-08-23 09:08:49 +02:00
|
|
|
def validate(self):
|
2020-02-11 06:47:55 +01:00
|
|
|
if not self.enabled:
|
2021-01-27 10:16:31 +01:00
|
|
|
# pass the validation if not enabled
|
2021-08-23 09:08:49 +02:00
|
|
|
return
|
2020-02-11 06:47:55 +01:00
|
|
|
|
|
|
|
if not internal_tls_dir.exists():
|
|
|
|
raise Exception('Internal dir for tls {} not exist'.format(internal_tls_dir))
|
|
|
|
|
|
|
|
for filename in self.required_filenames:
|
|
|
|
self._check(filename)
|
|
|
|
|
|
|
|
def prepare(self):
|
|
|
|
"""
|
|
|
|
Prepare moves certs in tls file to data volume with correct permission.
|
|
|
|
"""
|
|
|
|
if not self.enabled:
|
|
|
|
logging.info('internal tls NOT enabled...')
|
|
|
|
return
|
|
|
|
original_tls_dir = get_realpath(self.tls_dir)
|
2020-02-11 07:39:19 +01:00
|
|
|
if internal_tls_dir.exists():
|
|
|
|
rmtree(internal_tls_dir)
|
2020-02-11 06:47:55 +01:00
|
|
|
copytree(original_tls_dir, internal_tls_dir, symlinks=True)
|
|
|
|
|
|
|
|
for file in internal_tls_dir.iterdir():
|
|
|
|
if file.name.endswith('.key'):
|
|
|
|
file.chmod(0o600)
|
|
|
|
elif file.name.endswith('.crt'):
|
|
|
|
file.chmod(0o644)
|
|
|
|
|
|
|
|
if file.name in self.db_certs_filename:
|
|
|
|
os.chown(file, PG_UID, PG_GID)
|
|
|
|
else:
|
|
|
|
os.chown(file, DEFAULT_UID, DEFAULT_GID)
|
|
|
|
|
2020-10-18 18:16:02 +02:00
|
|
|
class Metric:
|
|
|
|
def __init__(self, enabled: bool = False, port: int = 8080, path: str = "metrics" ):
|
|
|
|
self.enabled = enabled
|
|
|
|
self.port = port
|
|
|
|
self.path = path
|
2020-11-17 13:14:11 +01:00
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
if not port_number_valid(self.port):
|
2021-08-23 09:08:49 +02:00
|
|
|
raise Exception('Port number in metrics is not valid')
|
|
|
|
|
|
|
|
|
|
|
|
class JaegerExporter:
|
|
|
|
def __init__(self, config: dict):
|
|
|
|
if not config:
|
|
|
|
return None
|
|
|
|
self.endpoint = config.get('endpoint')
|
|
|
|
self.username = config.get('username')
|
|
|
|
self.password = config.get('password')
|
|
|
|
self.agent_host = config.get('agent_host')
|
|
|
|
self.agent_port = config.get('agent_port')
|
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
if not self.endpoint and self.agent_host is None:
|
|
|
|
raise Exception('Jaeger Colector Endpoint or Agent host not set')
|
|
|
|
|
|
|
|
class OtelExporter:
|
|
|
|
def __init__(self, config: dict):
|
|
|
|
if not config:
|
|
|
|
return None
|
|
|
|
self.endpoint = config.get('endpoint')
|
|
|
|
self.url_path = config.get('url_path')
|
|
|
|
self.compression = config.get('compression') or False
|
|
|
|
self.insecure = config.get('insecure') or False
|
|
|
|
self.timeout = config.get('timeout') or '10s'
|
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
if not self.endpoint:
|
|
|
|
raise Exception('Trace endpoint not set')
|
|
|
|
if not self.url_path:
|
|
|
|
raise Exception('Trace url path not set')
|
|
|
|
|
|
|
|
class Trace:
|
|
|
|
def __init__(self, config: dict):
|
|
|
|
self.enabled = config.get('enabled', False)
|
|
|
|
self.sample_rate = config.get('sample_rate', 1)
|
|
|
|
self.jaeger = config.get('jaeger', {})
|
|
|
|
self.otel_exporter = config.get('otel_exporter', {})
|
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
if self.jaeger is None and self.otel_exporter is None:
|
|
|
|
raise Exception('Trace enabled but no trace exporter set')
|
|
|
|
if self.jaeger is not None:
|
|
|
|
JaegerExporter(self.jaeger).validate()
|
|
|
|
if self.otel_exporter is not None:
|
|
|
|
OtelExporter(self.otel_exporter).validate()
|