Skip to content

Commit 012137d

Browse files
authored
move certs to universal builder (#482)
1 parent 67187c4 commit 012137d

19 files changed

Lines changed: 787 additions & 301 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: "certificates-example"
2+
uuid: "b8a4de1c-2f56-4bc1-a3b8-4fe40124cccc"
3+
description: "Exordos certificates example"
4+
schema_version: 1
5+
version: "0.0.1"
6+
api_version: "v1"
7+
8+
requirements:
9+
core:
10+
from_version: "0.0.0"
11+
12+
resources:
13+
$core.secret.certificates:
14+
# DNS Core certificate (ACME via Let's Encrypt with DNS-01 challenge)
15+
# Uses DNS CoreDNS plugin to automatically fulfill ACME challenges.
16+
# After provisioning, cert and key are stored on the agent.
17+
wildcard_cert:
18+
name: "wildcard-cert"
19+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
20+
email: "admin@example.com"
21+
method:
22+
kind: dns_core
23+
domains:
24+
- "*.example.com"
25+
- "example.com"
26+
expiration_threshold: 30
27+
overcome_threshold: false
28+
29+
# Multi-domain SAN certificate
30+
# Subject Alternative Names for multiple service domains.
31+
san_cert:
32+
name: "san-cert"
33+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
34+
email: "devops@example.com"
35+
method:
36+
kind: dns_core
37+
domains:
38+
- "api.example.com"
39+
- "auth.example.com"
40+
- "admin.example.com"
41+
expiration_threshold: 14
42+
43+
# Internal service certificate with no wildcards
44+
internal_cert:
45+
name: "internal-cert"
46+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
47+
email: "it@example.com"
48+
method:
49+
kind: dns_core
50+
domains:
51+
- "gitlab.internal.example.com"
52+
- "jenkins.internal.example.com"
53+
expiration_threshold: 7
54+
overcome_threshold: true

exordos_core/agent/universal/drivers/secret/backend/cert.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,11 @@
3636
class CertBotBackendClient(base.AbstractBackendClient):
3737
"""Cert bot backend client."""
3838

39-
DEFAULT_PRIVATE_KEY_PATH = "/etc/exordos_core/certbot/privkey.pem"
40-
4139
def __init__(
4240
self,
4341
dns_client: dns_clients.TinyDNSCoreClient,
4442
admin_email: str,
45-
private_key_path: str = DEFAULT_PRIVATE_KEY_PATH,
43+
private_key_path: str,
4644
) -> None:
4745
self._dns_client = dns_client
4846
self._admin_email = admin_email
@@ -89,7 +87,7 @@ def create(self, resource: models.Resource) -> tp.Dict[str, tp.Any]:
8987
cert_x509 = x509.load_pem_x509_certificate(fullchain_pem.encode())
9088
expiration_at = cert_x509.not_valid_after_utc
9189

92-
# Build storagable password and save
90+
# Build storage_cert and save
9391
driver_cert = driver_dm.Certificate.from_cert_resource(
9492
resource, pkey_pem, csr_pem, fullchain_pem, expiration_at
9593
)

exordos_core/agent/universal/drivers/secret/backend/db.py renamed to exordos_core/agent/universal/drivers/secret/backend/password.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
LOG = logging.getLogger(__name__)
3232

3333

34-
class DatabaseSecretBackendClient(base.AbstractBackendClient):
34+
class DatabasePasswordBackendClient(base.AbstractBackendClient):
3535
"""Secret Backend client based on SQL database."""
3636

3737
def get(self, resource: models.Resource) -> tp.Dict[str, tp.Any]:

exordos_core/agent/universal/drivers/secret/cert.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
CERT_TARGET_FIELDS_STORAGE = "/var/lib/exordos/exordos_core/cert_target_fields.json"
3030
DEFAULT_ADMIN_EMAIL = "infra@exordos.com"
31+
DEFAULT_PRIVATE_KEY_PATH = "/etc/exordos_core/certbot/privkey.pem"
3132

3233

3334
class CoreDNSCertificateCapabilityDriver(direct.DirectAgentDriver):
@@ -38,9 +39,11 @@ def __init__(
3839
user_api_base_url: str,
3940
username: str,
4041
password: str,
42+
private_key_path: str = DEFAULT_PRIVATE_KEY_PATH,
4143
admin_email: str = DEFAULT_ADMIN_EMAIL,
44+
storage_path: str = CERT_TARGET_FIELDS_STORAGE,
4245
) -> None:
43-
storage = fs.TargetFieldsFileStorage(CERT_TARGET_FIELDS_STORAGE)
46+
storage = fs.TargetFieldsFileStorage(storage_path)
4447

4548
auth = core_client_base.CoreIamAuthenticator(
4649
base_url=user_api_base_url, username=username, password=password
@@ -49,7 +52,9 @@ def __init__(
4952
base_url=user_api_base_url, auth=auth
5053
)
5154

52-
client = cert_back.CertBotBackendClient(dns_client, admin_email)
55+
client = cert_back.CertBotBackendClient(
56+
dns_client, admin_email, private_key_path
57+
)
5358

5459
super().__init__(storage=storage, client=client)
5560

exordos_core/agent/universal/drivers/secret/password.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from gcl_sdk.agents.universal.drivers import direct
2121
from gcl_sdk.agents.universal.storage import fs
2222

23-
from exordos_core.agent.universal.drivers.secret.backend import db as db_back
23+
from exordos_core.agent.universal.drivers.secret.backend import password as db_back
2424

2525
LOG = logging.getLogger(__name__)
2626

@@ -34,7 +34,7 @@ class PasswordCapabilityDriver(direct.DirectAgentDriver):
3434

3535
def __init__(self, storage_path: str = PASSWORD_TARGET_FIELDS_STORAGE):
3636
storage = fs.TargetFieldsFileStorage(storage_path)
37-
client = db_back.DatabaseSecretBackendClient()
37+
client = db_back.DatabasePasswordBackendClient()
3838

3939
super().__init__(storage=storage, client=client)
4040

exordos_core/common/constants.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,13 @@
4141
NODE_UUID_PATH = os.path.join(WORK_DIR, "node-id")
4242

4343
DEFAULT_USER_API_HOST = "127.0.0.1"
44-
DEFAULT_USER_API_PORT = 11010
45-
DEFAULT_ROOT_ENDPOINT = f"http://{DEFAULT_USER_API_HOST}:{DEFAULT_USER_API_PORT}/v1/"
44+
DEFAULT_USER_API_PORT = 80
45+
DEFAULT_ROOT_ENDPOINT = (
46+
f"http://{DEFAULT_USER_API_HOST}:{DEFAULT_USER_API_PORT}/api/core/v1/"
47+
)
48+
49+
DEFAULT_DNS_CERT_USERNAME = "admin"
50+
DEFAULT_DNS_CERT_PASSWORD = "admin"
4651

4752
DEFAULT_GLOBAL_SALT = "FOy/2kwwdn0ig1QOq7cestqe"
4853
DEFAULT_ADMIN_SALT = "d4JJ9QYuEEJxHCFja9FZskG4"

exordos_core/gservice/service.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
from exordos_core.network.lb.builders import paas as net_lb_paas
5656
from exordos_core.network.lb.dm import models as lb_models
5757
from exordos_core.secret import service as secret_service
58-
from exordos_core.secret.builders import service as password_builder_svc
58+
from exordos_core.secret.builders import service as secret_builder_svc
5959
from exordos_core.telemetry import service as telemetry_service
6060
from exordos_core.vs.builders import service as vs_builder_svc
6161

@@ -195,7 +195,10 @@ def __init__(self, iter_min_period=3, iter_pause=0.1):
195195
secret_svc = secret_service.SecretServiceBuilder(
196196
iter_min_period=iter_min_period,
197197
)
198-
password_builder = password_builder_svc.PasswordBuilder(
198+
password_builder = secret_builder_svc.PasswordBuilder(
199+
iter_min_period=iter_min_period
200+
)
201+
cert_builder = secret_builder_svc.CertificateBuilder(
199202
iter_min_period=iter_min_period
200203
)
201204
# Build the event sender only when event delivery is enabled (the
@@ -240,6 +243,7 @@ def __init__(self, iter_min_period=3, iter_pause=0.1):
240243
net_border_paas_builder,
241244
secret_svc,
242245
password_builder,
246+
cert_builder,
243247
em_builder,
244248
dns_sync,
245249
# non-essential services should be last

exordos_core/secret/builders/service.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,38 @@ def actualize_outdated_instance(
9696

9797
if status_updated or password_updated:
9898
current_instance.save()
99+
100+
101+
class CertificateBuilder(sdk_builder.UniversalBuilderService):
102+
def __init__(
103+
self,
104+
iter_min_period: int = 1,
105+
iter_pause: float = 0.1,
106+
) -> None:
107+
super().__init__(
108+
instance_model=Certificate,
109+
iter_min_period=iter_min_period,
110+
iter_pause=iter_pause,
111+
)
112+
113+
def actualize_outdated_instance(
114+
self,
115+
current_instance: Certificate,
116+
actual_instance: Certificate,
117+
) -> None:
118+
status_updated = current_instance.status != actual_instance.status
119+
if status_updated:
120+
current_instance.status = actual_instance.status
121+
122+
cert_updated = (
123+
current_instance.key != actual_instance.key
124+
or current_instance.cert != actual_instance.cert
125+
or current_instance.expiration_at != actual_instance.expiration_at
126+
)
127+
if cert_updated:
128+
current_instance.key = actual_instance.key
129+
current_instance.cert = actual_instance.cert
130+
current_instance.expiration_at = actual_instance.expiration_at
131+
132+
if status_updated or cert_updated:
133+
current_instance.save()

exordos_core/secret/service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,5 +547,5 @@ def _actualize_ssh_keys(self) -> None:
547547

548548
def _iteration(self) -> None:
549549
with contexts.Context().session_manager():
550-
self._actualize_certificates()
550+
# self._actualize_certificates()
551551
self._actualize_ssh_keys()

exordos_core/tests/functional/conftest.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from restalchemy.dm import filters as dm_filters
3838
from restalchemy.storage.sql import engines
3939

40+
from exordos_core.agent.universal.drivers.secret import cert as cert_driver
4041
from exordos_core.agent.universal.drivers.secret import password as password_driver
4142
from exordos_core.common import constants as c
4243
from exordos_core.common import utils
@@ -1433,19 +1434,18 @@ def factory(hostname, ip_addresses=None, key=None):
14331434

14341435

14351436
@pytest.fixture()
1436-
def agent_service(
1437+
def password_agent_service(
14371438
default_node: tp.Dict[str, tp.Any],
14381439
user_api_client: iam_clients.GenesisCoreTestRESTClient,
14391440
):
14401441
agent_uuid = sys_uuid.UUID(default_node["uuid"])
14411442
orch_client = orch_db.DatabaseOrchClient()
14421443
with tempfile.TemporaryDirectory() as temp_dir:
1443-
temp_file_path = os.path.join(temp_dir, "password_target_fields.json")
1444-
1445-
with open(temp_file_path, "w", encoding="utf-8") as f:
1444+
passwords_temp_file_path = os.path.join(temp_dir, "password_target_fields.json")
1445+
with open(passwords_temp_file_path, "w", encoding="utf-8") as f:
14461446
json.dump({}, f, indent=4)
14471447

1448-
p_d = password_driver.PasswordCapabilityDriver(temp_file_path)
1448+
p_d = password_driver.PasswordCapabilityDriver(passwords_temp_file_path)
14491449
caps_drivers = [
14501450
p_d,
14511451
]
@@ -1464,11 +1464,53 @@ def agent_service(
14641464
yield agent
14651465

14661466

1467+
@pytest.fixture()
1468+
def cert_agent_service(
1469+
default_node: tp.Dict[str, tp.Any],
1470+
user_api_client: iam_clients.GenesisCoreTestRESTClient,
1471+
):
1472+
agent_uuid = sys_uuid.UUID(default_node["uuid"])
1473+
orch_client = orch_db.DatabaseOrchClient()
1474+
with tempfile.TemporaryDirectory() as temp_dir:
1475+
cert_temp_file_path = os.path.join(temp_dir, "cert_target_fields.json")
1476+
with open(cert_temp_file_path, "w", encoding="utf-8") as f:
1477+
json.dump({}, f, indent=4)
1478+
privkey_temp_file_path = os.path.join(temp_dir, "privkey.pem")
1479+
c_d = cert_driver.CoreDNSCertificateCapabilityDriver(
1480+
user_api_base_url=c.DEFAULT_ROOT_ENDPOINT,
1481+
username=c.DEFAULT_DNS_CERT_USERNAME,
1482+
password=c.DEFAULT_DNS_CERT_PASSWORD,
1483+
storage_path=cert_temp_file_path,
1484+
private_key_path=privkey_temp_file_path,
1485+
)
1486+
caps_drivers = [
1487+
c_d,
1488+
]
1489+
agent = ua_agent_service.UniversalAgentService(
1490+
system_uuid=agent_uuid,
1491+
agent_uuid=agent_uuid,
1492+
orch_client=orch_client,
1493+
caps_drivers=caps_drivers,
1494+
facts_drivers=[],
1495+
iter_min_period=3,
1496+
payload_path=None,
1497+
verify_node_on_register=False,
1498+
)
1499+
agent._setup()
1500+
agent._register_agent()
1501+
yield agent
1502+
1503+
14671504
@pytest.fixture()
14681505
def password_builder():
14691506
yield secret_service.PasswordBuilder()
14701507

14711508

1509+
@pytest.fixture()
1510+
def certificate_builder():
1511+
yield secret_service.CertificateBuilder()
1512+
1513+
14721514
@pytest.fixture()
14731515
def universal_scheduler():
14741516
yield ua_scheduler_service.UniversalAgentSchedulerService(capabilities=["*"])

0 commit comments

Comments
 (0)