Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions simplyblock_core/models/storage_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def client(self, **kwargs):
"""Return API client to this node
"""
host = self.api_endpoint
if Settings().tls_enabled:
if Settings().tls_connect != "disabled":
port = self.api_endpoint.rsplit(":", 1)[1]
host = f"{self._k8s_node_label()}.simplyblock-storage-node-api.{self.cr_namespace}.svc.cluster.local:{port}"
return SNodeClient(host, **kwargs)
Expand All @@ -159,7 +159,7 @@ def rpc_client(self, **kwargs):
"""Return rpc client to this node
"""
host = self.mgmt_ip
if Settings().tls_enabled:
if Settings().tls_connect != "disabled":
host = f"{self._k8s_node_label()}.simplyblock-spdk-proxy.{self.cr_namespace}.svc.cluster.local"
return RPCClient(
host, self.rpc_port,
Expand Down
6 changes: 3 additions & 3 deletions simplyblock_core/rpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@ def __init__(self, host, port, username, password, timeout=180, retry=3):
self.host = host
self.port = port
settings = Settings()
scheme = "https" if settings.tls_enabled else "http"
scheme = "https" if settings.tls_connect != "disabled" else "http"
self.url = '%s://%s:%s/' % (scheme, self.host, self.port)
self.username = username
self.password = password
self.timeout = timeout
self.session = requests.session()
if settings.tls_enabled:
self.session.verify = str(settings.certificate_authority)
if settings.tls_connect != "disabled":
self.session.verify = str(settings.tls_certificate_authority)
self.session.auth = (self.username, self.password)
retries = Retry(total=retry, backoff_factor=1, connect=retry, read=retry,
allowed_methods=self.DEFAULT_ALLOWED_METHODS)
Expand Down
2 changes: 1 addition & 1 deletion simplyblock_core/services/spdk_http_proxy_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def run_server(host, port, user, password, is_threading_enabled=False):
ServerHandler.key = key
httpd = (ThreadingHTTPServer if is_threading_enabled else HTTPServer)((host, port), ServerHandler)
settings = Settings()
if settings.tls_enabled:
if settings.tls_serve:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(settings.tls_certificate, settings.tls_key)
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
Expand Down
60 changes: 51 additions & 9 deletions simplyblock_core/settings.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,61 @@
from pathlib import Path
from typing import Annotated, Literal, Optional

from pydantic import Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="sb_", case_sensitive=False)

tls_serve: Annotated[
bool,
Field(
description="Run servers in TLS mode. Requires certificate and key to be present."
),
] = False
tls_connect: Annotated[
Literal["disabled", "anonymous"],
Field(description="Connect to internal services via TLS."),
] = "disabled"
tls_provider: Annotated[
Optional[Literal["openshift", "cert-manager"]],
Field(description="Provider for TLS certificates in the cluster."),
] = None
tls_certificate: Path = Path("/etc/simplyblock/tls/tls.crt")
tls_key: Path = Path("/etc/simplyblock/tls/tls.key")
certificate_authority: Path = Path("/etc/simplyblock/tls/ca.crt")

@property
def tls_enabled(self) -> bool:
return all([
self.tls_certificate.is_file(),
self.tls_key.is_file(),
self.certificate_authority.is_file(),
])
tls_certificate_authority: Path = Path("/etc/simplyblock/tls/ca.crt")

@model_validator(mode="after")
def validate_tls_files(self):
if not self.tls_serve and self.tls_connect == "disabled":
return self

if self.tls_serve and (
missing := [
name
for name in ["tls_certificate", "tls_key"]
if not getattr(self, name).is_file()
]
):
raise ValueError(
"SB_TLS_SERVE=true requires TLS files to exist: " + ", ".join(missing)
)

if (
self.tls_connect != "disabled"
and not self.tls_certificate_authority.is_file()
):
raise ValueError(
"SB_TLS_CONNECT != 'disabled' requires certificate authority to exist"
)

return self

@model_validator(mode="after")
def validate_tls_provider(self):
if self.tls_connect != "disabled" and self.tls_provider is None:
raise ValueError(
"TLS provider needs to be configured for TLS connections to be used"
)
return self
6 changes: 3 additions & 3 deletions simplyblock_core/snode_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ class SNodeClient:

def __init__(self, host, timeout=300, retry=5):
settings = Settings()
scheme = "https" if settings.tls_enabled else "http"
scheme = "https" if settings.tls_connect != "disabled" else "http"
self.url = f'{scheme}://{host}/snode/'
self.timeout = timeout
self.session = requests.session()
if settings.tls_enabled:
self.session.verify = str(settings.certificate_authority)
if settings.tls_connect != "disabled":
self.session.verify = str(settings.tls_certificate_authority)
self.session.headers['Content-Type'] = "application/json"
retries = Retry(total=retry, backoff_factor=1, connect=retry, read=retry)
self.session.mount("http://", HTTPAdapter(max_retries=retries))
Expand Down
4 changes: 3 additions & 1 deletion simplyblock_web/api/internal/storage_node/kubernetes.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ class SPDKParams(BaseModel):
})}}},
})
def spdk_process_start(body: SPDKParams):
settings = Settings()
ssd_pcie_params = " ".join(" -A " + addr for addr in body.ssd_pcie) if body.ssd_pcie else "none"
ssd_pcie_list = " ".join(body.ssd_pcie)

Expand Down Expand Up @@ -373,7 +374,8 @@ def spdk_process_start(body: SPDKParams):
'FW_PORT': body.firewall_port,
'CPU_TOPOLOGY_ENABLED': cpu_topology_enabled,
'RESERVED_SYSTEM_CPUS': reserved_system_cpus,
'TLS_ENABLED': Settings().tls_enabled,
'TLS_ENABLED': settings.tls_serve,
'TLS_PROVIDER': settings.tls_provider,
}

if ubuntu_host:
Expand Down
4 changes: 2 additions & 2 deletions simplyblock_web/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ def main() -> None:
access_log=False,
proxy_headers=True,
forwarded_allow_ips='192.168.1.0/24',
ssl_certfile=settings.tls_certificate if settings.tls_enabled else None,
ssl_keyfile=settings.tls_key if settings.tls_enabled else None,
ssl_certfile=settings.tls_certificate if settings.tls_serve else None,
ssl_keyfile=settings.tls_key if settings.tls_serve else None,
)
server: uvicorn.Server = uvicorn.Server(config)
server.run()
Expand Down
2 changes: 1 addition & 1 deletion simplyblock_web/node_webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ def status():
app.register_api(internal_api.storage_node.kubernetes.api)

settings = Settings()
ssl_ctx = (settings.tls_certificate, settings.tls_key) if settings.tls_enabled else None
ssl_ctx = (settings.tls_certificate, settings.tls_key) if settings.tls_serve else None
app.run(host='0.0.0.0', debug=constants.LOG_WEB_DEBUG, ssl_context=ssl_ctx)
9 changes: 9 additions & 0 deletions simplyblock_web/templates/storage_deploy_spdk.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ spec:
path: /var/log/pods
{% if TLS_ENABLED %}
- name: tls
{% if TLS_PROVIDER == "cert-manager" %}
secret:
secretName: simplyblock-spdk-proxy-tls
{% else %}
projected:
sources:
- secret:
Expand All @@ -68,6 +72,7 @@ spec:
- key: service-ca.crt
path: ca.crt
{% endif %}
{% endif %}

containers:
- name: spdk-container
Expand Down Expand Up @@ -155,6 +160,10 @@ spec:
value: "True"
- name: TIMEOUT
value: "300"
- name: SB_TLS_ENABLED
value: "{{ TLS_ENABLED }}"
- name: SB_TLS_PROVIDER
value: "{{ TLS_PROVIDER }}"
{% if CPU_TOPOLOGY_ENABLED %}
resources:
limits:
Expand Down
59 changes: 59 additions & 0 deletions tests/test_storage_deploy_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import unittest
from pathlib import Path

from jinja2 import Environment, FileSystemLoader


TEMPLATE_DIR = Path(__file__).resolve().parents[1] / "simplyblock_web" / "templates"


def _render_storage_deploy(tls_provider: str) -> str:
env = Environment(
loader=FileSystemLoader(str(TEMPLATE_DIR)),
trim_blocks=True,
lstrip_blocks=True,
)
template = env.get_template("storage_deploy_spdk.yaml.j2")
return template.render(
SPDK_IMAGE="spdk:test",
L_CORES="0-1",
SPDK_MEM=1024,
CORES=2,
SERVER_IP="10.0.0.10",
RPC_PORT=8080,
RPC_USERNAME="admin",
RPC_PASSWORD="secret",
HOSTNAME="node-a",
NAMESPACE="simplyblock",
SIMPLYBLOCK_DOCKER_IMAGE="proxy:test",
GRAYLOG_SERVER_IP="10.0.0.20",
MODE="kubernetes",
CLUSTER_ID="cluster1",
SSD_PCIE="none",
PCI_ALLOWED="",
TOTAL_HP="",
NSOCKET=0,
FW_PORT=50001,
CPU_TOPOLOGY_ENABLED=False,
MEM_MEGA=1536,
MEM2_MEGA=1024,
TLS_ENABLED=True,
TLS_PROVIDER=tls_provider,
)


class TestStorageDeployTemplate(unittest.TestCase):

def test_openshift_uses_service_ca_key(self):
rendered = _render_storage_deploy("openshift")
self.assertIn("key: service-ca.crt", rendered)
self.assertIn('name: SB_TLS_PROVIDER', rendered)
self.assertIn('value: "openshift"', rendered)

def test_cert_manager_mounts_secret_directly(self):
rendered = _render_storage_deploy("cert-manager")
self.assertIn("secretName: simplyblock-spdk-proxy-tls", rendered)
self.assertNotIn("simplyblock-certificate-authority", rendered)
self.assertNotIn("projected:", rendered)
self.assertIn('name: SB_TLS_PROVIDER', rendered)
self.assertIn('value: "cert-manager"', rendered)
Loading